Stream slave accel via ESP-NOW with master snapshot cache.
Slaves push BMA456 samples at 16ms when enabled; the master caches per client and exposes ACCEL_SNAPSHOT and ACCEL_STREAM over UART. goTool adds dashboard stream controls, HTTP accel-stream routes, and an external WebSocket API with per-connection receive/interval and slave stream commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+138
-7
@@ -40,6 +40,70 @@ func (m *managedSerial) listClientsPoll() ([]*pb.ClientInfo, error) {
|
||||
return decodeClientsPayload(payload)
|
||||
}
|
||||
|
||||
func (m *managedSerial) readAccelSnapshotPoll(clientID uint32) (*pb.AccelSnapshotResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_ACCEL_SNAPSHOT,
|
||||
Payload: &pb.UartMessage_AccelSnapshotRequest{
|
||||
AccelSnapshotRequest: &pb.AccelSnapshotRequest{ClientId: clientID},
|
||||
},
|
||||
}
|
||||
body, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode: %w", err)
|
||||
}
|
||||
payload := append([]byte{byte(pb.MessageType_ACCEL_SNAPSHOT)}, body...)
|
||||
respPayload, err := m.exchangePayloadPoll(payload, "ACCEL_SNAPSHOT")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeAccelSnapshotPayload(respPayload)
|
||||
}
|
||||
|
||||
func (m *managedSerial) AccelStream(req *pb.AccelStreamRequest) (*pb.AccelStreamResponse, error) {
|
||||
return m.accelStreamVia(m.withPort, req)
|
||||
}
|
||||
|
||||
func (m *managedSerial) AccelStreamPoll(req *pb.AccelStreamRequest) (*pb.AccelStreamResponse, error) {
|
||||
return m.accelStreamVia(m.withPortPoll, req)
|
||||
}
|
||||
|
||||
// SetAccelStream enables or disables the ESP-NOW accel stream for one slave (master UART).
|
||||
func (m *managedSerial) SetAccelStream(clientID uint32, enable bool) (*pb.AccelStreamResponse, error) {
|
||||
return m.AccelStream(&pb.AccelStreamRequest{
|
||||
Write: true,
|
||||
Enable: enable,
|
||||
ClientId: clientID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccelStream returns whether the accel stream is enabled for a slave on the master.
|
||||
func (m *managedSerial) GetAccelStream(clientID uint32) (bool, error) {
|
||||
resp, err := m.AccelStreamPoll(&pb.AccelStreamRequest{
|
||||
Write: false,
|
||||
ClientId: clientID,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !resp.GetSuccess() {
|
||||
return false, fmt.Errorf("accel stream read failed for client %d", clientID)
|
||||
}
|
||||
return resp.GetEnabled(), nil
|
||||
}
|
||||
|
||||
func (m *managedSerial) accelStreamVia(
|
||||
portFn func(func(*serialPort) error) error,
|
||||
req *pb.AccelStreamRequest,
|
||||
) (*pb.AccelStreamResponse, error) {
|
||||
var resp *pb.AccelStreamResponse
|
||||
err := portFn(func(sp *serialPort) error {
|
||||
var e error
|
||||
resp, e = sp.AccelStream(req)
|
||||
return e
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (m *managedSerial) AccelDeadzone(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error) {
|
||||
return m.accelDeadzoneVia(m.withPort, req)
|
||||
}
|
||||
@@ -101,25 +165,43 @@ func decodeClientsPayload(payload []byte) ([]*pb.ClientInfo, error) {
|
||||
return info.GetClients(), nil
|
||||
}
|
||||
|
||||
func (s *serialPort) readAccel() (*pb.AccelReadResponse, error) {
|
||||
payload, err := s.exchange(byte(pb.MessageType_ACCEL_READ), "ACCEL_READ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func decodeAccelSnapshotPayload(payload []byte) (*pb.AccelSnapshotResponse, error) {
|
||||
if len(payload) < 1 {
|
||||
return nil, fmt.Errorf("empty response payload")
|
||||
}
|
||||
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_ACCEL_READ {
|
||||
if msg.GetType() != pb.MessageType_ACCEL_SNAPSHOT {
|
||||
return nil, fmt.Errorf("unexpected type %v", msg.GetType())
|
||||
}
|
||||
r := msg.GetAccelReadResponse()
|
||||
r := msg.GetAccelSnapshotResponse()
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("missing accel_read_response")
|
||||
return nil, fmt.Errorf("missing accel_snapshot_response")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *serialPort) readAccelSnapshot(clientID uint32) (*pb.AccelSnapshotResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_ACCEL_SNAPSHOT,
|
||||
Payload: &pb.UartMessage_AccelSnapshotRequest{
|
||||
AccelSnapshotRequest: &pb.AccelSnapshotRequest{ClientId: clientID},
|
||||
},
|
||||
}
|
||||
body, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode: %w", err)
|
||||
}
|
||||
payload := append([]byte{byte(pb.MessageType_ACCEL_SNAPSHOT)}, body...)
|
||||
respPayload, err := s.exchangePayload(payload, "ACCEL_SNAPSHOT")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeAccelSnapshotPayload(respPayload)
|
||||
}
|
||||
|
||||
func (s *serialPort) getVersion() (*pb.VersionResponse, error) {
|
||||
payload, err := s.exchange(byte(pb.MessageType_VERSION), "VERSION")
|
||||
if err != nil {
|
||||
@@ -136,6 +218,33 @@ func (s *serialPort) listClients() ([]*pb.ClientInfo, error) {
|
||||
return decodeClientsPayload(payload)
|
||||
}
|
||||
|
||||
func (s *serialPort) AccelStream(req *pb.AccelStreamRequest) (*pb.AccelStreamResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_ACCEL_STREAM,
|
||||
Payload: &pb.UartMessage_AccelStreamRequest{
|
||||
AccelStreamRequest: req,
|
||||
},
|
||||
}
|
||||
body, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode: %w", err)
|
||||
}
|
||||
payload := append([]byte{byte(pb.MessageType_ACCEL_STREAM)}, body...)
|
||||
respPayload, err := s.exchangePayload(payload, "ACCEL_STREAM")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var respMsg pb.UartMessage
|
||||
if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil {
|
||||
return nil, fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
r := respMsg.GetAccelStreamResponse()
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("missing accel_stream_response")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *serialPort) accelDeadzone(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error) {
|
||||
msg := &pb.UartMessage{
|
||||
Type: pb.MessageType_ACCEL_DEADZONE,
|
||||
@@ -234,6 +343,28 @@ func (s *serialPort) GetVersion() (*pb.VersionResponse, error) { return s.getVer
|
||||
|
||||
func (s *serialPort) ListClients() ([]*pb.ClientInfo, error) { return s.listClients() }
|
||||
|
||||
func (s *serialPort) SetAccelStream(clientID uint32, enable bool) (*pb.AccelStreamResponse, error) {
|
||||
return s.AccelStream(&pb.AccelStreamRequest{
|
||||
Write: true,
|
||||
Enable: enable,
|
||||
ClientId: clientID,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serialPort) GetAccelStream(clientID uint32) (bool, error) {
|
||||
resp, err := s.AccelStream(&pb.AccelStreamRequest{
|
||||
Write: false,
|
||||
ClientId: clientID,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !resp.GetSuccess() {
|
||||
return false, fmt.Errorf("accel stream read failed for client %d", clientID)
|
||||
}
|
||||
return resp.GetEnabled(), nil
|
||||
}
|
||||
|
||||
func (s *serialPort) AccelDeadzone(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error) {
|
||||
return s.accelDeadzone(req)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user