feat: Enhance MK2 driver with device state management and improved command handling
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
505
mk2driver/mk2.go
505
mk2driver/mk2.go
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -47,6 +48,8 @@ const (
|
||||
infoFrameHeader = 0x20
|
||||
frameHeader = 0xff
|
||||
bootupFrameHeader = 0x0
|
||||
frameLengthLEDBit = 0x80
|
||||
frameLEDBytes = 2
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -79,22 +82,42 @@ const (
|
||||
|
||||
// winmon frame commands
|
||||
const (
|
||||
commandSetState = 0x0E
|
||||
commandReadRAMVar = 0x30
|
||||
commandReadSetting = 0x31
|
||||
commandWriteRAMVar = 0x32
|
||||
commandWriteSetting = 0x33
|
||||
commandWriteData = 0x34
|
||||
commandReadSelected = 0x35
|
||||
commandGetRAMVarInfo = 0x36
|
||||
commandWriteViaID = 0x37
|
||||
commandWriteRAMViaID = 0x38
|
||||
|
||||
commandUnsupportedResponse = 0x80
|
||||
commandReadRAMResponse = 0x85
|
||||
commandReadSettingResponse = 0x86
|
||||
commandWriteRAMResponse = 0x87
|
||||
commandWriteSettingResponse = 0x88
|
||||
commandWriteNotAllowedResponse = 0x9B
|
||||
commandSetStateResponse = 0x89
|
||||
commandReadSelectedResponse = 0x8A
|
||||
commandWriteViaIDResponse = 0x8B
|
||||
commandWriteRAMViaIDResponse = 0x8C
|
||||
commandGetRAMVarInfoResponse = 0x8E
|
||||
commandWriteNotAllowedResponse = 0x9B
|
||||
)
|
||||
|
||||
const writeResponseTimeout = 3 * time.Second
|
||||
|
||||
var (
|
||||
errCommandUnsupported = errors.New("command is not supported by this device firmware")
|
||||
errWriteRejected = errors.New("write command rejected by device access level")
|
||||
)
|
||||
|
||||
type winmonResponse struct {
|
||||
command byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
type mk2Ser struct {
|
||||
info *Mk2Info
|
||||
p io.ReadWriter
|
||||
@@ -104,12 +127,43 @@ type mk2Ser struct {
|
||||
frameLock bool
|
||||
infochan chan *Mk2Info
|
||||
commandMu sync.Mutex
|
||||
pollPaused atomic.Bool
|
||||
writeAck chan byte
|
||||
winmonAck chan winmonResponse
|
||||
stateAck chan struct{}
|
||||
ifaceAck chan byte
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
var _ ProtocolControl = (*mk2Ser)(nil)
|
||||
|
||||
func parseFrameLength(raw byte) (payloadLength byte, hasLEDStatus bool) {
|
||||
if raw&frameLengthLEDBit != 0 {
|
||||
return raw &^ frameLengthLEDBit, true
|
||||
}
|
||||
return raw, false
|
||||
}
|
||||
|
||||
func (m *mk2Ser) beginCommand() {
|
||||
m.commandMu.Lock()
|
||||
m.pollPaused.Store(true)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) endCommand() {
|
||||
m.pollPaused.Store(false)
|
||||
m.commandMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *mk2Ser) sendMonitoringCommand(data []byte) {
|
||||
if m.pollPaused.Load() {
|
||||
if len(data) > 0 {
|
||||
mk2log.WithField("command", fmt.Sprintf("0x%02x", data[0])).Debug("Skipping monitoring command during control transaction")
|
||||
}
|
||||
return
|
||||
}
|
||||
m.sendCommand(data)
|
||||
}
|
||||
|
||||
func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
||||
mk2 := &mk2Ser{}
|
||||
mk2.p = dev
|
||||
@@ -118,6 +172,7 @@ func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
||||
mk2.frameLock = false
|
||||
mk2.scales = make([]scaling, 0, ramVarMaxOffset)
|
||||
mk2.writeAck = make(chan byte, 4)
|
||||
mk2.winmonAck = make(chan winmonResponse, 32)
|
||||
mk2.stateAck = make(chan struct{}, 1)
|
||||
mk2.ifaceAck = make(chan byte, 1)
|
||||
mk2.setTarget()
|
||||
@@ -132,7 +187,8 @@ func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
||||
// Locks to incoming frame.
|
||||
func (m *mk2Ser) frameLocker() {
|
||||
frame := make([]byte, 256)
|
||||
var frameLength byte
|
||||
ledStatus := make([]byte, frameLEDBytes)
|
||||
var frameLengthRaw byte
|
||||
for {
|
||||
select {
|
||||
case <-m.run:
|
||||
@@ -141,8 +197,14 @@ func (m *mk2Ser) frameLocker() {
|
||||
default:
|
||||
}
|
||||
if m.frameLock {
|
||||
frameLength = m.readByte()
|
||||
frameLengthRaw = m.readByte()
|
||||
frameLength, hasLEDStatus := parseFrameLength(frameLengthRaw)
|
||||
frameLengthOffset := int(frameLength) + 1
|
||||
if frameLengthOffset > len(frame) {
|
||||
m.addError(fmt.Errorf("Read Length Error: frame length %d exceeds buffer", frameLengthOffset))
|
||||
m.frameLock = false
|
||||
continue
|
||||
}
|
||||
l, err := io.ReadFull(m.p, frame[:frameLengthOffset])
|
||||
if err != nil {
|
||||
m.addError(fmt.Errorf("Read Error: %v", err))
|
||||
@@ -151,12 +213,26 @@ func (m *mk2Ser) frameLocker() {
|
||||
m.addError(errors.New("Read Length Error"))
|
||||
m.frameLock = false
|
||||
} else {
|
||||
m.handleFrame(frameLength, frame[:frameLengthOffset])
|
||||
var appendedLED []byte
|
||||
if hasLEDStatus {
|
||||
if _, err = io.ReadFull(m.p, ledStatus); err != nil {
|
||||
m.addError(fmt.Errorf("Read LED status error: %v", err))
|
||||
m.frameLock = false
|
||||
continue
|
||||
}
|
||||
appendedLED = ledStatus
|
||||
}
|
||||
m.handleFrame(frameLength, frame[:frameLengthOffset], appendedLED)
|
||||
}
|
||||
} else {
|
||||
tmp := m.readByte()
|
||||
frameLength, hasLEDStatus := parseFrameLength(frameLengthRaw)
|
||||
frameLengthOffset := int(frameLength)
|
||||
if tmp == frameHeader || tmp == infoFrameHeader {
|
||||
if frameLengthOffset > len(frame) {
|
||||
frameLengthRaw = tmp
|
||||
continue
|
||||
}
|
||||
l, err := io.ReadFull(m.p, frame[:frameLengthOffset])
|
||||
if err != nil {
|
||||
m.addError(fmt.Errorf("Read Error: %v", err))
|
||||
@@ -164,13 +240,20 @@ func (m *mk2Ser) frameLocker() {
|
||||
} else if l != frameLengthOffset {
|
||||
m.addError(errors.New("Read Length Error"))
|
||||
} else {
|
||||
if hasLEDStatus {
|
||||
if _, err = io.ReadFull(m.p, ledStatus); err != nil {
|
||||
m.addError(fmt.Errorf("Read LED status error: %v", err))
|
||||
frameLengthRaw = tmp
|
||||
continue
|
||||
}
|
||||
}
|
||||
if checkChecksum(frameLength, tmp, frame[:frameLengthOffset]) {
|
||||
m.frameLock = true
|
||||
mk2log.Info("Frame lock acquired")
|
||||
}
|
||||
}
|
||||
}
|
||||
frameLength = tmp
|
||||
frameLengthRaw = tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +275,22 @@ func (m *mk2Ser) WriteRAMVar(id uint16, value int16) error {
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).Info("WriteRAMVar requested")
|
||||
err := m.writeByID(commandWriteRAMVar, commandWriteRAMResponse, id, value)
|
||||
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
err := m.writeByIDOnly(commandWriteRAMViaID, commandWriteRAMViaIDResponse, id, value)
|
||||
if err != nil {
|
||||
if errors.Is(err, errWriteRejected) {
|
||||
mk2log.WithError(err).WithField("id", id).Error("WriteRAMVar failed")
|
||||
return err
|
||||
}
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).WithError(err).Warn("WriteRAMVar by-id command failed, falling back to legacy write sequence")
|
||||
err = m.writeBySelection(commandWriteRAMVar, commandWriteRAMResponse, id, value)
|
||||
}
|
||||
if err != nil {
|
||||
mk2log.WithError(err).WithField("id", id).Error("WriteRAMVar failed")
|
||||
return err
|
||||
@@ -206,7 +304,22 @@ func (m *mk2Ser) WriteSetting(id uint16, value int16) error {
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).Info("WriteSetting requested")
|
||||
err := m.writeByID(commandWriteSetting, commandWriteSettingResponse, id, value)
|
||||
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
err := m.writeByIDOnly(commandWriteViaID, commandWriteViaIDResponse, id, value)
|
||||
if err != nil {
|
||||
if errors.Is(err, errWriteRejected) {
|
||||
mk2log.WithError(err).WithField("id", id).Error("WriteSetting failed")
|
||||
return err
|
||||
}
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).WithError(err).Warn("WriteSetting by-id command failed, falling back to legacy write sequence")
|
||||
err = m.writeBySelection(commandWriteSetting, commandWriteSettingResponse, id, value)
|
||||
}
|
||||
if err != nil {
|
||||
mk2log.WithError(err).WithField("id", id).Error("WriteSetting failed")
|
||||
return err
|
||||
@@ -215,6 +328,125 @@ func (m *mk2Ser) WriteSetting(id uint16, value int16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteSettingByID(id uint16, value int16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.writeByIDOnly(commandWriteViaID, commandWriteViaIDResponse, id, value)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteRAMVarByID(id uint16, value int16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.writeByIDOnly(commandWriteRAMViaID, commandWriteRAMViaIDResponse, id, value)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) GetDeviceState() (DeviceState, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandSetState,
|
||||
0x00,
|
||||
})
|
||||
resp, err := m.waitForWinmonResponse(commandSetStateResponse)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return decodeDeviceStateResponse(resp)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) SetDeviceState(state DeviceState) error {
|
||||
if !validDeviceState(state) {
|
||||
return fmt.Errorf("invalid device state: 0x%02x", byte(state))
|
||||
}
|
||||
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandSetState,
|
||||
0x00,
|
||||
byte(state),
|
||||
})
|
||||
|
||||
resp, err := m.waitForWinmonResponse(commandSetStateResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ackState, err := decodeDeviceStateResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ackState != state {
|
||||
return fmt.Errorf("device acknowledged state %s (0x%02x), expected %s (0x%02x)", formatDeviceState(ackState), byte(ackState), formatDeviceState(state), byte(state))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ReadRAMVarByID(id uint16) (int16, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.readValueByID(commandReadRAMVar, commandReadRAMResponse, id)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ReadSettingByID(id uint16) (int16, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.readValueByID(commandReadSetting, commandReadSettingResponse, id)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) SelectRAMVar(id uint16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
_, err := m.readValueByID(commandReadRAMVar, commandReadRAMResponse, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *mk2Ser) SelectSetting(id uint16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
_, err := m.readValueByID(commandReadSetting, commandReadSettingResponse, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ReadSelected() (int16, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandReadSelected,
|
||||
})
|
||||
resp, err := m.waitForWinmonResponse(commandReadSelectedResponse)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return decodeInt16Response(resp)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ReadRAMVarInfo(id uint16) (RAMVarInfo, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandGetRAMVarInfo,
|
||||
byte(id),
|
||||
byte(id >> 8),
|
||||
})
|
||||
resp, err := m.waitForWinmonResponse(commandGetRAMVarInfoResponse)
|
||||
if err != nil {
|
||||
return RAMVarInfo{}, err
|
||||
}
|
||||
return decodeRAMVarInfoResponse(id, resp)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) SetPanelState(switchState PanelSwitchState, currentLimitA *float64) error {
|
||||
if !validPanelSwitchState(switchState) {
|
||||
return fmt.Errorf("invalid panel switch state: %d", switchState)
|
||||
@@ -225,8 +457,8 @@ func (m *mk2Ser) SetPanelState(switchState PanelSwitchState, currentLimitA *floa
|
||||
return err
|
||||
}
|
||||
|
||||
m.commandMu.Lock()
|
||||
defer m.commandMu.Unlock()
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
logEntry := mk2log.WithField("switch_state", switchState)
|
||||
if currentLimitA != nil {
|
||||
@@ -259,8 +491,8 @@ func (m *mk2Ser) SetStandby(enabled bool) error {
|
||||
lineState |= interfaceStandbyFlag
|
||||
}
|
||||
|
||||
m.commandMu.Lock()
|
||||
defer m.commandMu.Unlock()
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
logEntry := mk2log.WithField("standby_enabled", enabled)
|
||||
logEntry.Info("SetStandby requested")
|
||||
|
||||
@@ -288,6 +520,22 @@ func validPanelSwitchState(switchState PanelSwitchState) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func validDeviceState(state DeviceState) bool {
|
||||
switch state {
|
||||
case DeviceStateChargerOnly, DeviceStateInverterOnly, DeviceStateOn, DeviceStateOff:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func formatDeviceState(state DeviceState) string {
|
||||
if name, ok := DeviceStateNames[state]; ok {
|
||||
return name
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func encodePanelCurrentLimit(currentLimitA *float64) (uint16, error) {
|
||||
if currentLimitA == nil {
|
||||
return panelCurrentLimitUnknown, nil
|
||||
@@ -303,26 +551,29 @@ func encodePanelCurrentLimit(currentLimitA *float64) (uint16, error) {
|
||||
return uint16(raw), nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeByID(selectCommand, expectedResponse byte, id uint16, value int16) error {
|
||||
m.commandMu.Lock()
|
||||
defer m.commandMu.Unlock()
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"select_command": fmt.Sprintf("0x%02x", selectCommand),
|
||||
"expected_response": fmt.Sprintf("0x%02x", expectedResponse),
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).Debug("Issuing write-by-id command")
|
||||
|
||||
func (m *mk2Ser) writeByIDOnly(writeCommand, expectedResponse byte, id uint16, value int16) error {
|
||||
m.clearWriteResponses()
|
||||
rawValue := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
writeCommand,
|
||||
byte(id),
|
||||
byte(id >> 8),
|
||||
byte(rawValue),
|
||||
byte(rawValue >> 8),
|
||||
})
|
||||
return m.waitForWriteResponse(expectedResponse)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeBySelection(selectCommand, expectedResponse byte, id uint16, value int16) error {
|
||||
m.clearWriteResponses()
|
||||
rawValue := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
selectCommand,
|
||||
byte(id),
|
||||
byte(id >> 8),
|
||||
})
|
||||
|
||||
rawValue := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandWriteData,
|
||||
@@ -333,6 +584,77 @@ func (m *mk2Ser) writeByID(selectCommand, expectedResponse byte, id uint16, valu
|
||||
return m.waitForWriteResponse(expectedResponse)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) readValueByID(readCommand, expectedResponse byte, id uint16) (int16, error) {
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
readCommand,
|
||||
byte(id),
|
||||
byte(id >> 8),
|
||||
})
|
||||
|
||||
resp, err := m.waitForWinmonResponse(expectedResponse)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return decodeInt16Response(resp)
|
||||
}
|
||||
|
||||
func decodeInt16Response(resp winmonResponse) (int16, error) {
|
||||
if len(resp.data) < 2 {
|
||||
return 0, fmt.Errorf("invalid response 0x%02x payload length %d", resp.command, len(resp.data))
|
||||
}
|
||||
return int16(uint16(resp.data[0]) | uint16(resp.data[1])<<8), nil
|
||||
}
|
||||
|
||||
func decodeDeviceStateResponse(resp winmonResponse) (DeviceState, error) {
|
||||
if len(resp.data) < 1 {
|
||||
return 0, fmt.Errorf("invalid device state response payload length %d", len(resp.data))
|
||||
}
|
||||
|
||||
var raw byte
|
||||
if len(resp.data) >= 2 {
|
||||
raw = resp.data[1]
|
||||
} else {
|
||||
raw = resp.data[0]
|
||||
}
|
||||
state := DeviceState(raw)
|
||||
if !validDeviceState(state) {
|
||||
return state, fmt.Errorf("unsupported device state 0x%02x", raw)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func decodeRAMVarInfoResponse(id uint16, resp winmonResponse) (RAMVarInfo, error) {
|
||||
info := RAMVarInfo{
|
||||
ID: id,
|
||||
}
|
||||
if len(resp.data) < 4 {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
scl := int16(resp.data[1])<<8 + int16(resp.data[0])
|
||||
var ofs int16
|
||||
if len(resp.data) == 4 {
|
||||
ofs = int16(uint16(resp.data[3])<<8 + uint16(resp.data[2]))
|
||||
} else {
|
||||
ofs = int16(uint16(resp.data[4])<<8 + uint16(resp.data[3]))
|
||||
}
|
||||
|
||||
info.Supported = true
|
||||
info.Scale = scl
|
||||
info.Offset = ofs
|
||||
info.Signed = scl < 0
|
||||
scale := int16Abs(scl)
|
||||
if scale >= 0x4000 {
|
||||
info.Factor = 1 / (0x8000 - float64(scale))
|
||||
} else {
|
||||
info.Factor = float64(scale)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) clearWriteResponses() {
|
||||
if m.writeAck == nil {
|
||||
m.writeAck = make(chan byte, 4)
|
||||
@@ -362,9 +684,9 @@ func (m *mk2Ser) waitForWriteResponse(expectedResponse byte) error {
|
||||
case expectedResponse:
|
||||
return nil
|
||||
case commandUnsupportedResponse:
|
||||
return errors.New("write command is not supported by this device firmware")
|
||||
return errCommandUnsupported
|
||||
case commandWriteNotAllowedResponse:
|
||||
return errors.New("write command rejected by device access level")
|
||||
return errWriteRejected
|
||||
default:
|
||||
return fmt.Errorf("unexpected write response 0x%02x", response)
|
||||
}
|
||||
@@ -385,6 +707,67 @@ func (m *mk2Ser) pushWriteResponse(response byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) clearWinmonResponses() {
|
||||
if m.winmonAck == nil {
|
||||
m.winmonAck = make(chan winmonResponse, 32)
|
||||
return
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-m.winmonAck:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForWinmonResponse(expectedResponse byte) (winmonResponse, error) {
|
||||
if m.winmonAck == nil {
|
||||
return winmonResponse{}, errors.New("winmon response channel is not initialized")
|
||||
}
|
||||
|
||||
timeout := time.After(writeResponseTimeout)
|
||||
for {
|
||||
select {
|
||||
case response := <-m.winmonAck:
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"expected_response": fmt.Sprintf("0x%02x", expectedResponse),
|
||||
"received_response": fmt.Sprintf("0x%02x", response.command),
|
||||
"response_len": len(response.data),
|
||||
}).Debug("Received winmon response")
|
||||
|
||||
switch response.command {
|
||||
case expectedResponse:
|
||||
return response, nil
|
||||
case commandUnsupportedResponse:
|
||||
return winmonResponse{}, errCommandUnsupported
|
||||
case commandWriteNotAllowedResponse:
|
||||
return winmonResponse{}, errWriteRejected
|
||||
default:
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"expected_response": fmt.Sprintf("0x%02x", expectedResponse),
|
||||
"received_response": fmt.Sprintf("0x%02x", response.command),
|
||||
}).Debug("Ignoring unrelated winmon response while waiting")
|
||||
}
|
||||
case <-timeout:
|
||||
return winmonResponse{}, fmt.Errorf("timed out waiting for winmon response 0x%02x after %s", expectedResponse, writeResponseTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) pushWinmonResponse(command byte, data []byte) {
|
||||
if m.winmonAck == nil {
|
||||
return
|
||||
}
|
||||
payloadCopy := make([]byte, len(data))
|
||||
copy(payloadCopy, data)
|
||||
|
||||
select {
|
||||
case m.winmonAck <- winmonResponse{command: command, data: payloadCopy}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) clearStateResponses() {
|
||||
if m.stateAck == nil {
|
||||
m.stateAck = make(chan struct{}, 1)
|
||||
@@ -515,41 +898,84 @@ func (m *mk2Ser) updateReport() {
|
||||
}
|
||||
|
||||
// Checks for valid frame and chooses decoding.
|
||||
func (m *mk2Ser) handleFrame(l byte, frame []byte) {
|
||||
func (m *mk2Ser) handleFrame(l byte, frame []byte, appendedLED []byte) {
|
||||
mk2log.Debugf("[handleFrame] frame %#v", frame)
|
||||
if len(frame) == 0 {
|
||||
mk2log.Warn("[handleFrame] empty frame")
|
||||
return
|
||||
}
|
||||
if checkChecksum(l, frame[0], frame[1:]) {
|
||||
switch frame[0] {
|
||||
case bootupFrameHeader:
|
||||
if m.pollPaused.Load() {
|
||||
mk2log.Debug("Skipping setTarget during active control transaction")
|
||||
return
|
||||
}
|
||||
m.setTarget()
|
||||
case frameHeader:
|
||||
if len(frame) < 2 {
|
||||
mk2log.Warnf("[handleFrame] truncated frameHeader frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
switch frame[1] {
|
||||
case interfaceFrame:
|
||||
if len(frame) > 2 {
|
||||
m.pushInterfaceResponse(frame[2])
|
||||
}
|
||||
case stateFrame:
|
||||
if len(appendedLED) == frameLEDBytes {
|
||||
m.setLEDState(appendedLED[0], appendedLED[1])
|
||||
}
|
||||
m.pushStateResponse()
|
||||
case vFrame:
|
||||
if len(frame) < 6 {
|
||||
mk2log.Warnf("[handleFrame] truncated version frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
m.versionDecode(frame[2:])
|
||||
case winmonFrame:
|
||||
if len(frame) < 3 {
|
||||
mk2log.Warnf("[handleFrame] truncated winmon frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
winmonCommand := frame[2]
|
||||
var winmonData []byte
|
||||
if len(frame) > 3 {
|
||||
winmonData = frame[3 : len(frame)-1]
|
||||
}
|
||||
m.pushWinmonResponse(winmonCommand, winmonData)
|
||||
switch frame[2] {
|
||||
case commandGetRAMVarInfoResponse:
|
||||
m.scaleDecode(frame[2:])
|
||||
if !m.pollPaused.Load() {
|
||||
m.scaleDecode(frame[2:])
|
||||
}
|
||||
case commandReadRAMResponse:
|
||||
m.stateDecode(frame[2:])
|
||||
case commandWriteRAMResponse, commandWriteSettingResponse, commandUnsupportedResponse, commandWriteNotAllowedResponse:
|
||||
if !m.pollPaused.Load() {
|
||||
m.stateDecode(frame[2:])
|
||||
}
|
||||
case commandReadSettingResponse, commandReadSelectedResponse:
|
||||
// Responses are consumed by synchronous protocol command methods.
|
||||
case commandSetStateResponse, commandWriteRAMResponse, commandWriteSettingResponse, commandWriteViaIDResponse, commandWriteRAMViaIDResponse, commandUnsupportedResponse, commandWriteNotAllowedResponse:
|
||||
m.pushWriteResponse(frame[2])
|
||||
default:
|
||||
mk2log.Warnf("[handleFrame] invalid winmonFrame %v", frame[2:])
|
||||
}
|
||||
|
||||
case ledFrame:
|
||||
if len(frame) < 4 {
|
||||
mk2log.Warnf("[handleFrame] truncated LED frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
m.ledDecode(frame[2:])
|
||||
default:
|
||||
mk2log.Warnf("[handleFrame] invalid frameHeader %v", frame[1])
|
||||
}
|
||||
|
||||
case infoFrameHeader:
|
||||
if len(frame) < 6 {
|
||||
mk2log.Warnf("[handleFrame] truncated info frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
switch frame[5] {
|
||||
case dcInfoFrame:
|
||||
m.dcDecode(frame[1:])
|
||||
@@ -582,7 +1008,7 @@ func (m *mk2Ser) reqScaleFactor(in byte) {
|
||||
cmd[0] = winmonFrame
|
||||
cmd[1] = commandGetRAMVarInfo
|
||||
cmd[2] = in
|
||||
m.sendCommand(cmd)
|
||||
m.sendMonitoringCommand(cmd)
|
||||
}
|
||||
|
||||
func int16Abs(in int16) uint16 {
|
||||
@@ -648,7 +1074,7 @@ func (m *mk2Ser) versionDecode(frame []byte) {
|
||||
cmd := make([]byte, 2)
|
||||
cmd[0] = infoReqFrame
|
||||
cmd[1] = infoReqAddrDC
|
||||
m.sendCommand(cmd)
|
||||
m.sendMonitoringCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +1130,7 @@ func (m *mk2Ser) dcDecode(frame []byte) {
|
||||
cmd := make([]byte, 2)
|
||||
cmd[0] = infoReqFrame
|
||||
cmd[1] = infoReqAddrACL1
|
||||
m.sendCommand(cmd)
|
||||
m.sendMonitoringCommand(cmd)
|
||||
}
|
||||
|
||||
// Decodes AC frame.
|
||||
@@ -720,7 +1146,7 @@ func (m *mk2Ser) acDecode(frame []byte) {
|
||||
// Send status request
|
||||
cmd := make([]byte, 1)
|
||||
cmd[0] = ledFrame
|
||||
m.sendCommand(cmd)
|
||||
m.sendMonitoringCommand(cmd)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) calcFreq(data byte, scaleIndex int) float64 {
|
||||
@@ -739,14 +1165,21 @@ func (m *mk2Ser) stateDecode(frame []byte) {
|
||||
|
||||
// Decode the LED state frame.
|
||||
func (m *mk2Ser) ledDecode(frame []byte) {
|
||||
|
||||
m.info.LEDs = getLEDs(frame[0], frame[1])
|
||||
if len(frame) < 2 {
|
||||
mk2log.Warnf("Skipping LED decode for short frame: %#v", frame)
|
||||
return
|
||||
}
|
||||
m.setLEDState(frame[0], frame[1])
|
||||
// Send charge state request
|
||||
cmd := make([]byte, 4)
|
||||
cmd[0] = winmonFrame
|
||||
cmd[1] = commandReadRAMVar
|
||||
cmd[2] = ramVarChargeState
|
||||
m.sendCommand(cmd)
|
||||
m.sendMonitoringCommand(cmd)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) setLEDState(ledsOn, ledsBlink byte) {
|
||||
m.info.LEDs = getLEDs(ledsOn, ledsBlink)
|
||||
}
|
||||
|
||||
// Adds active LEDs to list.
|
||||
|
||||
Reference in New Issue
Block a user