Add LiPo battery monitoring with ESP-NOW cache and dashboard API.
Slaves report pack voltages every 30s; the master caches them for fast BATTERY_STATUS reads. goTool exposes REST/WebSocket and shows values in the dashboard, with a nanopb fix so optional lipo submessages encode. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,86 @@ func (m *managedSerial) readAccelSnapshotPoll(clientID uint32) (*pb.AccelSnapsho
|
||||
return decodeAccelSnapshotPayload(respPayload)
|
||||
}
|
||||
|
||||
func decodeBatteryStatusPayload(payload []byte) (*pb.BatteryStatusResponse, error) {
|
||||
if len(payload) < 2 {
|
||||
return nil, fmt.Errorf("short battery response")
|
||||
}
|
||||
if payload[0] != byte(pb.MessageType_BATTERY_STATUS) {
|
||||
return nil, fmt.Errorf("unexpected command id 0x%02x (want 0x%02x)",
|
||||
payload[0], byte(pb.MessageType_BATTERY_STATUS))
|
||||
}
|
||||
var msg pb.UartMessage
|
||||
if err := proto.Unmarshal(payload[1:], &msg); err != nil {
|
||||
return nil, fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
if msg.GetType() != pb.MessageType_BATTERY_STATUS {
|
||||
return nil, fmt.Errorf("unexpected type %v", msg.GetType())
|
||||
}
|
||||
r := msg.GetBatteryStatusResponse()
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("missing battery_status_response")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (m *managedSerial) BatteryStatus(req *pb.BatteryStatusRequest) (*pb.BatteryStatusResponse, error) {
|
||||
var resp *pb.BatteryStatusResponse
|
||||
err := m.withPort(func(sp *serialPort) error {
|
||||
var e error
|
||||
resp, e = sp.batteryStatus(req)
|
||||
return e
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (m *managedSerial) BatteryStatusPoll(req *pb.BatteryStatusRequest) (*pb.BatteryStatusResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_BATTERY_STATUS,
|
||||
Payload: &pb.UartMessage_BatteryStatusRequest{
|
||||
BatteryStatusRequest: req,
|
||||
},
|
||||
}
|
||||
body, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode: %w", err)
|
||||
}
|
||||
payload := append([]byte{byte(pb.MessageType_BATTERY_STATUS)}, body...)
|
||||
respPayload, err := m.batteryStatusPayloadPoll(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeBatteryStatusPayload(respPayload)
|
||||
}
|
||||
|
||||
func (m *managedSerial) batteryStatusPayloadPoll(payload []byte) ([]byte, error) {
|
||||
var resp []byte
|
||||
err := m.withPortPoll(func(sp *serialPort) error {
|
||||
var e error
|
||||
resp, e = sp.exchangePayloadForBattery(payload, "BATTERY_STATUS")
|
||||
return e
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *serialPort) batteryStatus(req *pb.BatteryStatusRequest) (*pb.BatteryStatusResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_BATTERY_STATUS,
|
||||
Payload: &pb.UartMessage_BatteryStatusRequest{
|
||||
BatteryStatusRequest: req,
|
||||
},
|
||||
}
|
||||
body, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode: %w", err)
|
||||
}
|
||||
payload := append([]byte{byte(pb.MessageType_BATTERY_STATUS)}, body...)
|
||||
respPayload, err := s.exchangePayloadForBattery(payload, "BATTERY_STATUS")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeBatteryStatusPayload(respPayload)
|
||||
}
|
||||
|
||||
func (m *managedSerial) AccelStream(req *pb.AccelStreamRequest) (*pb.AccelStreamResponse, error) {
|
||||
return m.accelStreamVia(m.withPort, req)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user