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:
@@ -50,6 +50,31 @@ func NewIOStub(readBuffer []byte) io.ReadWriter {
|
||||
}
|
||||
}
|
||||
|
||||
func buildTestFrame(frameType byte, payload ...byte) (byte, []byte) {
|
||||
length := byte(len(payload) + 1)
|
||||
sum := int(length) + int(frameType)
|
||||
for _, b := range payload {
|
||||
sum += int(b)
|
||||
}
|
||||
checksum := byte((-sum) & 0xff)
|
||||
frame := append([]byte{frameType}, payload...)
|
||||
frame = append(frame, checksum)
|
||||
return length, frame
|
||||
}
|
||||
|
||||
func buildSentCommand(payload ...byte) []byte {
|
||||
length := byte(len(payload) + 1)
|
||||
sum := int(length) + int(frameHeader)
|
||||
out := make([]byte, 0, len(payload)+3)
|
||||
out = append(out, length, frameHeader)
|
||||
for _, b := range payload {
|
||||
sum += int(b)
|
||||
out = append(out, b)
|
||||
}
|
||||
out = append(out, byte((-sum)&0xff))
|
||||
return out
|
||||
}
|
||||
|
||||
// Test a know sequence as reference as extracted from Mk2
|
||||
func TestSync(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -320,6 +345,29 @@ func Test_mk2Ser_WriteSetting(t *testing.T) {
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteViaIDResponse)
|
||||
}()
|
||||
|
||||
err := m.WriteSetting(0x1234, 1234)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := []byte{
|
||||
0x07, 0xff, 0x57, 0x37, 0x34, 0x12, 0xd2, 0x04, 0x50,
|
||||
}
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteSetting_FallbackLegacy(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
writeAck: make(chan byte, 2),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandUnsupportedResponse)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteSettingResponse)
|
||||
}()
|
||||
@@ -328,12 +376,254 @@ func Test_mk2Ser_WriteSetting(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := []byte{
|
||||
0x07, 0xff, 0x57, 0x37, 0x34, 0x12, 0xd2, 0x04, 0x50,
|
||||
0x05, 0xff, 0x57, 0x33, 0x34, 0x12, 0x2c,
|
||||
0x05, 0xff, 0x57, 0x34, 0xd2, 0x04, 0x9b,
|
||||
}
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteRAMVar(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
writeAck: make(chan byte, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteRAMViaIDResponse)
|
||||
}()
|
||||
|
||||
err := m.WriteRAMVar(0x000d, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandWriteRAMViaID, 0x0d, 0x00, 0x01, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteRAMVar_FallbackLegacy(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
writeAck: make(chan byte, 2),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandUnsupportedResponse)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteRAMResponse)
|
||||
}()
|
||||
|
||||
err := m.WriteRAMVar(0x000d, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := append([]byte{}, buildSentCommand(winmonFrame, commandWriteRAMViaID, 0x0d, 0x00, 0x01, 0x00)...)
|
||||
expected = append(expected, buildSentCommand(winmonFrame, commandWriteRAMVar, 0x0d, 0x00)...)
|
||||
expected = append(expected, buildSentCommand(winmonFrame, commandWriteData, 0x01, 0x00)...)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteSettingByID(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
writeAck: make(chan byte, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteViaIDResponse)
|
||||
}()
|
||||
|
||||
err := m.WriteSettingByID(0x1234, 1234)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandWriteViaID, 0x34, 0x12, 0xd2, 0x04)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteRAMVarByID(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
writeAck: make(chan byte, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWriteResponse(commandWriteRAMViaIDResponse)
|
||||
}()
|
||||
|
||||
err := m.WriteRAMVarByID(0x000d, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandWriteRAMViaID, 0x0d, 0x00, 0x01, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_GetDeviceState(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandSetStateResponse, []byte{0x00, byte(DeviceStateOn)})
|
||||
}()
|
||||
|
||||
state, err := m.GetDeviceState()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, DeviceStateOn, state)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandSetState, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_SetDeviceState(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandSetStateResponse, []byte{0x00, byte(DeviceStateOff)})
|
||||
}()
|
||||
|
||||
err := m.SetDeviceState(DeviceStateOff)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandSetState, 0x00, byte(DeviceStateOff))
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_ReadRAMVarByID(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandReadRAMResponse, []byte{0x34, 0x12})
|
||||
}()
|
||||
|
||||
value, err := m.ReadRAMVarByID(0x0021)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int16(0x1234), value)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandReadRAMVar, 0x21, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_ReadSettingByID(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandReadSettingResponse, []byte{0xcd, 0xab})
|
||||
}()
|
||||
|
||||
value, err := m.ReadSettingByID(0x0042)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int16(-21555), value)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandReadSetting, 0x42, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_ReadSelected(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandReadSelectedResponse, []byte{0x78, 0x56})
|
||||
}()
|
||||
|
||||
value, err := m.ReadSelected()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int16(0x5678), value)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandReadSelected)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_SelectRAMVar(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandReadRAMResponse, []byte{0x11, 0x22})
|
||||
}()
|
||||
|
||||
err := m.SelectRAMVar(0x0022)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandReadRAMVar, 0x22, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_SelectSetting(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandReadSettingResponse, []byte{0x11, 0x22})
|
||||
}()
|
||||
|
||||
err := m.SelectSetting(0x0023)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandReadSetting, 0x23, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_ReadRAMVarInfo(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
p: testIO,
|
||||
winmonAck: make(chan winmonResponse, 1),
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.pushWinmonResponse(commandGetRAMVarInfoResponse, []byte{0x9c, 0x7f, 0x00, 0x8f, 0x00})
|
||||
}()
|
||||
|
||||
info, err := m.ReadRAMVarInfo(0x0001)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, info.Supported)
|
||||
assert.Equal(t, uint16(0x0001), info.ID)
|
||||
assert.Equal(t, int16(0x7f9c), info.Scale)
|
||||
assert.Equal(t, int16(0x008f), info.Offset)
|
||||
assert.InDelta(t, 0.01, info.Factor, testDelta)
|
||||
|
||||
expected := buildSentCommand(winmonFrame, commandGetRAMVarInfo, 0x01, 0x00)
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_mk2Ser_WriteRAMVarRejected(t *testing.T) {
|
||||
testIO := NewIOStub(nil)
|
||||
m := &mk2Ser{
|
||||
@@ -435,3 +725,54 @@ func Test_mk2Ser_SetStandby_Disabled(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, expected, writeBuffer.Bytes())
|
||||
}
|
||||
|
||||
func Test_parseFrameLength(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw byte
|
||||
expectedLen byte
|
||||
expectedFlag bool
|
||||
}{
|
||||
{
|
||||
name: "normal length",
|
||||
raw: 0x07,
|
||||
expectedLen: 0x07,
|
||||
expectedFlag: false,
|
||||
},
|
||||
{
|
||||
name: "length with LED flag",
|
||||
raw: 0x86,
|
||||
expectedLen: 0x06,
|
||||
expectedFlag: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
length, hasLEDStatus := parseFrameLength(tt.raw)
|
||||
assert.Equal(t, tt.expectedLen, length)
|
||||
assert.Equal(t, tt.expectedFlag, hasLEDStatus)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mk2Ser_handleFrame_StateFrameWithAppendedLED(t *testing.T) {
|
||||
m := &mk2Ser{
|
||||
info: &Mk2Info{},
|
||||
stateAck: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
length, frame := buildTestFrame(frameHeader, stateFrame)
|
||||
m.handleFrame(length, frame, []byte{0x03, 0x00})
|
||||
|
||||
select {
|
||||
case <-m.stateAck:
|
||||
default:
|
||||
t.Fatal("expected state acknowledgement")
|
||||
}
|
||||
|
||||
assert.NotNil(t, m.info.LEDs)
|
||||
assert.Equal(t, LedOn, m.info.LEDs[LedMain])
|
||||
assert.Equal(t, LedOn, m.info.LEDs[LedAbsorption])
|
||||
assert.Equal(t, LedOff, m.info.LEDs[LedBulk])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user