powerpods/goTool/client_api.go

593 lines
17 KiB
Go

package main
import (
"fmt"
"time"
"google.golang.org/protobuf/proto"
"powerpod/gotool/pb"
)
func (m *managedSerial) getVersion() (*pb.VersionResponse, error) {
payload, err := m.exchange(byte(pb.MessageType_VERSION), "VERSION")
if err != nil {
return nil, err
}
return decodeVersionPayload(payload)
}
func (m *managedSerial) getVersionPoll() (*pb.VersionResponse, error) {
payload, err := m.exchangePoll(byte(pb.MessageType_VERSION), "VERSION")
if err != nil {
return nil, err
}
return decodeVersionPayload(payload)
}
func (m *managedSerial) listClients() ([]*pb.ClientInfo, error) {
payload, err := m.exchange(byte(pb.MessageType_CLIENT_INFO), "CLIENT_INFO")
if err != nil {
return nil, err
}
return decodeClientsPayload(payload)
}
func (m *managedSerial) listClientsPoll() ([]*pb.ClientInfo, error) {
payload, err := m.exchangePoll(byte(pb.MessageType_CLIENT_INFO), "CLIENT_INFO")
if err != nil {
return nil, err
}
return decodeClientsPayload(payload)
}
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)
}
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) TapNotify(req *pb.TapNotifyRequest) (*pb.TapNotifyResponse, error) {
return m.tapNotifyVia(m.withPort, req)
}
func (m *managedSerial) TapNotifyPoll(req *pb.TapNotifyRequest) (*pb.TapNotifyResponse, error) {
return m.tapNotifyVia(m.withPortPoll, req)
}
func (m *managedSerial) tapNotifyVia(
portFn func(func(*serialPort) error) error,
req *pb.TapNotifyRequest,
) (*pb.TapNotifyResponse, error) {
var resp *pb.TapNotifyResponse
err := portFn(func(sp *serialPort) error {
var e error
resp, e = sp.TapNotify(req)
return e
})
return resp, err
}
func (m *managedSerial) readCacheStatusPoll() (*pb.CacheStatusResponse, error) {
payload, err := m.exchangePoll(byte(pb.MessageType_CACHE_STATUS), "CACHE_STATUS")
if err != nil {
return nil, err
}
return decodeCacheStatusPayload(payload)
}
func decodeCacheStatusPayload(payload []byte) (*pb.CacheStatusResponse, error) {
if len(payload) < 1 {
return nil, fmt.Errorf("empty response payload")
}
if payload[0] != byte(pb.MessageType_CACHE_STATUS) {
return nil, fmt.Errorf("unexpected command id 0x%02x (want 0x%02x)",
payload[0], byte(pb.MessageType_CACHE_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_CACHE_STATUS {
return nil, fmt.Errorf("unexpected type %v", msg.GetType())
}
r := msg.GetCacheStatusResponse()
if r == nil {
return nil, fmt.Errorf("missing cache_status_response")
}
return r, 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)
}
func (m *managedSerial) AccelDeadzonePoll(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error) {
return m.accelDeadzoneVia(m.withPortPoll, req)
}
func (m *managedSerial) accelDeadzoneVia(
portFn func(func(*serialPort) error) error,
req *pb.AccelDeadzoneRequest,
) (*pb.AccelDeadzoneResponse, error) {
var resp *pb.AccelDeadzoneResponse
err := portFn(func(sp *serialPort) error {
var e error
resp, e = sp.AccelDeadzone(req)
return e
})
return resp, err
}
func (m *managedSerial) EspnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastTestResponse, error) {
var resp *pb.EspNowUnicastTestResponse
err := m.withPort(func(sp *serialPort) error {
var e error
resp, e = sp.EspnowUnicastTest(clientID, seq)
return e
})
return resp, err
}
func decodeVersionPayload(payload []byte) (*pb.VersionResponse, error) {
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_VERSION {
return nil, fmt.Errorf("unexpected type %v", msg.GetType())
}
ver := msg.GetVersionResponse()
if ver == nil {
return nil, fmt.Errorf("missing version_response")
}
return ver, nil
}
func decodeClientsPayload(payload []byte) ([]*pb.ClientInfo, error) {
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_CLIENT_INFO {
return nil, fmt.Errorf("unexpected type %v", msg.GetType())
}
info := msg.GetClientInfoResponse()
if info == nil {
return nil, fmt.Errorf("missing client_info_response")
}
return info.GetClients(), nil
}
func (s *serialPort) getVersion() (*pb.VersionResponse, error) {
payload, err := s.exchange(byte(pb.MessageType_VERSION), "VERSION")
if err != nil {
return nil, err
}
return decodeVersionPayload(payload)
}
func (s *serialPort) listClients() ([]*pb.ClientInfo, error) {
payload, err := s.exchange(byte(pb.MessageType_CLIENT_INFO), "CLIENT_INFO")
if err != nil {
return nil, err
}
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) TapNotify(req *pb.TapNotifyRequest) (*pb.TapNotifyResponse, error) {
msg := &pb.UartMessage{
Type: pb.MessageType_TAP_NOTIFY,
Payload: &pb.UartMessage_TapNotifyRequest{
TapNotifyRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_TAP_NOTIFY)}, body...)
respPayload, err := s.exchangePayload(payload, "TAP_NOTIFY")
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.GetTapNotifyResponse()
if r == nil {
return nil, fmt.Errorf("missing tap_notify_response")
}
return r, nil
}
func (s *serialPort) readCacheStatus() (*pb.CacheStatusResponse, error) {
payload, err := s.exchange(byte(pb.MessageType_CACHE_STATUS), "CACHE_STATUS")
if err != nil {
return nil, err
}
return decodeCacheStatusPayload(payload)
}
func (s *serialPort) accelDeadzone(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error) {
msg := &pb.UartMessage{
Type: pb.MessageType_ACCEL_DEADZONE,
Payload: &pb.UartMessage_AccelDeadzoneRequest{
AccelDeadzoneRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_ACCEL_DEADZONE)}, body...)
respPayload, err := s.exchangePayload(payload, "ACCEL_DEADZONE")
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.GetAccelDeadzoneResponse()
if r == nil {
return nil, fmt.Errorf("missing accel_deadzone_response")
}
return r, nil
}
func (s *serialPort) espnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastTestResponse, error) {
req := &pb.EspNowUnicastTestRequest{ClientId: clientID, Seq: seq}
msg := &pb.UartMessage{
Type: pb.MessageType_ESPNOW_UNICAST_TEST,
Payload: &pb.UartMessage_EspnowUnicastTestRequest{
EspnowUnicastTestRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_ESPNOW_UNICAST_TEST)}, body...)
respPayload, err := s.exchangePayload(payload, "ESPNOW_UNICAST_TEST")
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.GetEspnowUnicastTestResponse()
if r == nil {
return nil, fmt.Errorf("missing espnow_unicast_test_response")
}
return r, nil
}
// EchoPingResult is the host-side round-trip for ESP-NOW echo ping.
type EchoPingResult struct {
Success bool `json:"success"`
ClientID uint32 `json:"client_id"`
TimestampUs uint64 `json:"timestamp_us"`
RttMs float64 `json:"rtt_ms"` // goTool: full UART round-trip
EspRttUs uint32 `json:"esp_rtt_us"` // master: µs delta ping send → pong recv
}
func (s *serialPort) echoPing(clientID uint32) (*EchoPingResult, error) {
t0 := time.Now()
timestampUs := uint64(t0.UnixMicro())
req := &pb.EspNowEchoPingRequest{
ClientId: clientID,
TimestampUs: timestampUs,
}
msg := &pb.UartMessage{
Type: pb.MessageType_ESPNOW_ECHO_PING,
Payload: &pb.UartMessage_EspnowEchoPingRequest{
EspnowEchoPingRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_ESPNOW_ECHO_PING)}, body...)
respPayload, err := s.exchangePayload(payload, "ESPNOW_ECHO_PING")
if err != nil {
return nil, err
}
rttMs := float64(time.Since(t0).Microseconds()) / 1000.0
var respMsg pb.UartMessage
if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil {
return nil, fmt.Errorf("decode: %w", err)
}
r := respMsg.GetEspnowEchoPingResponse()
if r == nil {
return nil, fmt.Errorf("missing espnow_echo_ping_response")
}
if !r.GetSuccess() {
return &EchoPingResult{
Success: false,
ClientID: r.GetClientId(),
RttMs: rttMs,
}, nil
}
if r.GetTimestampUs() != timestampUs {
return nil, fmt.Errorf("timestamp mismatch: sent %d got %d", timestampUs, r.GetTimestampUs())
}
return &EchoPingResult{
Success: true,
ClientID: r.GetClientId(),
TimestampUs: r.GetTimestampUs(),
RttMs: rttMs,
EspRttUs: r.GetEspRttUs(),
}, nil
}
func (m *managedSerial) FindMe(clientID uint32) error {
return m.withPort(func(sp *serialPort) error {
return runFindMeClient(sp, clientID)
})
}
func (m *managedSerial) Restart(clientID uint32) error {
err := m.withPort(func(sp *serialPort) error {
return runRestartClient(sp, clientID)
})
if err != nil {
return err
}
if clientID == 0 {
m.recoverAfterMasterRestart()
}
return nil
}
func (s *serialPort) ledRingProgress(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error) {
msg := &pb.UartMessage{
Type: pb.MessageType_LED_RING,
Payload: &pb.UartMessage_LedRingProgressRequest{
LedRingProgressRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_LED_RING)}, body...)
respPayload, err := s.exchangePayload(payload, "LED_RING")
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.GetLedRingProgressResponse()
if r == nil {
return nil, fmt.Errorf("missing led_ring_progress_response")
}
return r, nil
}
func (s *serialPort) GetVersion() (*pb.VersionResponse, error) { return s.getVersion() }
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)
}
func (s *serialPort) EspnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastTestResponse, error) {
return s.espnowUnicastTest(clientID, seq)
}
func (s *serialPort) EchoPing(clientID uint32) (*EchoPingResult, error) {
return s.echoPing(clientID)
}
func (m *managedSerial) EchoPing(clientID uint32) (*EchoPingResult, error) {
var result *EchoPingResult
err := m.withPort(func(sp *serialPort) error {
var e error
result, e = sp.echoPing(clientID)
return e
})
return result, err
}
func (s *serialPort) LedRing(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error) {
return s.ledRingProgress(req)
}
func (m *managedSerial) LedRing(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error) {
var resp *pb.LedRingProgressResponse
err := m.withPort(func(sp *serialPort) error {
var e error
resp, e = sp.LedRing(req)
return e
})
return resp, err
}
func (s *serialPort) FindMe(clientID uint32) (*pb.EspNowFindMeResponse, error) {
return s.espnowFindMe(clientID)
}
func (s *serialPort) Restart(clientID uint32) (*pb.RestartResponse, error) {
return s.restart(clientID)
}
func (s *serialPort) OtaSlaveProgress(clientID uint32) (*pb.OtaSlaveProgressResponse, error) {
return QueryOtaSlaveProgress(s, clientID)
}