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) }