diff --git a/mk2driver/mk2.go b/mk2driver/mk2.go index 5c7d3b8..e8b7da9 100644 --- a/mk2driver/mk2.go +++ b/mk2driver/mk2.go @@ -49,7 +49,7 @@ const ( acL1InfoFrame = 0x08 dcInfoFrame = 0x0C setTargetFrame = 0x41 - infoReqFrame = 0x46 + infoReqFrame = 0x46 //F ledFrame = 0x4C vFrame = 0x56 winmonFrame = 0x57 @@ -358,10 +358,10 @@ func (m *mk2Ser) dcDecode(frame []byte) { // Decodes AC frame. func (m *mk2Ser) acDecode(frame []byte) { - m.info.InVoltage = m.applyScaleAndSign(frame[5:7], ramVarVMains) - m.info.InCurrent = m.applyScaleAndSign(frame[7:9], ramVarIMains) - m.info.OutVoltage = m.applyScaleAndSign(frame[9:11], ramVarVInverter) - m.info.OutCurrent = m.applyScaleAndSign(frame[11:13], ramVarIInverter) + m.info.InVoltage = m.applyScale(getSigned(frame[5:7]), ramVarVMains) + m.info.InCurrent = m.applyScale(getSigned(frame[7:9]), ramVarIMains) + m.info.OutVoltage = m.applyScale(getSigned(frame[9:11]), ramVarVInverter) + m.info.OutCurrent = m.applyScale(getSigned(frame[11:13]), ramVarIInverter) if frame[13] == 0xff { m.info.InFrequency = 0 diff --git a/mk2driver/mk2_test.go b/mk2driver/mk2_test.go index 7326e20..f96a7e8 100644 --- a/mk2driver/mk2_test.go +++ b/mk2driver/mk2_test.go @@ -33,7 +33,7 @@ var knownWrites = []byte{ var writeBuffer = bytes.NewBuffer(nil) const ( - testEpsilon = 0.00000001 + testDelta = 0.00000001 ) type testIo struct { @@ -50,62 +50,162 @@ func NewIOStub(readBuffer []byte) io.ReadWriter { // Test a know sequence as reference as extracted from Mk2 func TestSync(t *testing.T) { - knownReadBuffer := []byte{ - //Len Cmd - 0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, 0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf, - 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, - 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, - 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, - 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, - 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, - 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, - 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, - 0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x00, 0x01, 0xb5, - 0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x00, 0x00, 0xda, - 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, - 0x08, 0xff, 0x57, 0x8e, 0x04, 0x00, 0x8f, 0x00, 0x80, 0x01, - 0x08, 0xff, 0x57, 0x8e, 0x01, 0x00, 0x8f, 0x00, 0x80, 0x04, - 0x08, 0xff, 0x57, 0x8e, 0x02, 0x00, 0x8f, 0x00, 0x80, 0x03, - 0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x00, 0x00, 0xce, - 0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf, - 0x0f, 0x20, 0xf3, 0x00, 0xc8, 0x02, 0x0c, 0xa1, 0x05, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x88, 0xb2, - 0x0f, 0x20, 0x01, 0x01, 0xca, 0x09, 0x08, 0xaa, 0x58, 0xab, 0x00, 0xaa, 0x58, 0x9a, 0x00, 0xc3, 0xe8, - 0x06, 0xff, 0x4c, 0x03, 0x00, 0x00, 0x00, 0xac, - 0x05, 0xff, 0x57, 0x85, 0xc8, 0x00, 0x58, + tests := []struct { + name string + knownReadBuffer []byte + knownWrites []byte + result Mk2Info + }{ + { + name: "basic", + knownReadBuffer: []byte{ + //Len Cmd + 0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, + 0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf, + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, + 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, + 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a, + 0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x00, 0x01, 0xb5, + 0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x00, 0x00, 0xda, + 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1, + 0x08, 0xff, 0x57, 0x8e, 0x04, 0x00, 0x8f, 0x00, 0x80, 0x01, + 0x08, 0xff, 0x57, 0x8e, 0x01, 0x00, 0x8f, 0x00, 0x80, 0x04, + 0x08, 0xff, 0x57, 0x8e, 0x02, 0x00, 0x8f, 0x00, 0x80, 0x03, + 0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x00, 0x00, 0xce, + 0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf, + 0x0f, 0x20, 0xf3, 0x00, 0xc8, 0x02, 0x0c, 0xa1, 0x05, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x88, 0xb2, + 0x0f, 0x20, 0x01, 0x01, 0xca, 0x09, 0x08, 0xaa, 0x58, 0xab, 0x00, 0xaa, 0x58, 0x9a, 0x00, 0xc3, 0xe8, + 0x06, 0xff, 0x4c, 0x03, 0x00, 0x00, 0x00, 0xac, + 0x05, 0xff, 0x57, 0x85, 0xc8, 0x00, 0x58, + }, + knownWrites: []byte{ + 0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, + 0x05, 0xff, 0x57, 0x36, 0x00, 0x00, 0x6f, + 0x05, 0xff, 0x57, 0x36, 0x01, 0x00, 0x6e, + 0x05, 0xff, 0x57, 0x36, 0x02, 0x00, 0x6d, + 0x05, 0xff, 0x57, 0x36, 0x03, 0x00, 0x6c, + 0x05, 0xff, 0x57, 0x36, 0x04, 0x00, 0x6b, + 0x05, 0xff, 0x57, 0x36, 0x05, 0x00, 0x6a, + 0x05, 0xff, 0x57, 0x36, 0x06, 0x00, 0x69, + 0x05, 0xff, 0x57, 0x36, 0x07, 0x00, 0x68, + 0x05, 0xff, 0x57, 0x36, 0x08, 0x00, 0x67, + 0x05, 0xff, 0x57, 0x36, 0x09, 0x00, 0x66, + 0x05, 0xff, 0x57, 0x36, 0x0a, 0x00, 0x65, + 0x05, 0xff, 0x57, 0x36, 0x0b, 0x00, 0x64, + 0x05, 0xff, 0x57, 0x36, 0x0c, 0x00, 0x63, + 0x05, 0xff, 0x57, 0x36, 0x0d, 0x00, 0x62, + 0x03, 0xff, 0x46, 0x00, 0xb8, + 0x03, 0xff, 0x46, 0x01, 0xb7, + 0x02, 0xff, 0x4c, 0xb3, + 0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68, + }, + result: Mk2Info{ + Version: uint32(2736), + BatVoltage: 14.41, + BatCurrent: -0.4, + InVoltage: 226.98, + InCurrent: 1.71, + InFrequency: 50.10256410256411, + OutVoltage: 226.980, + OutCurrent: 1.54, + OutFrequency: 50.025510204081634, + ChargeState: 1, + LEDs: map[Led]LEDstate{ + LedMain: LedOn, + LedAbsorption: LedOn, + LedBulk: LedOff, + LedFloat: LedOff, + LedInverter: LedOff, + LedOverload: LedOff, + LedLowBattery: LedOff, + LedTemperature: LedOff, + }, + }, + }, + { + name: "multiplus24/3000", + knownReadBuffer: []byte{ + //Len Cmd + 0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, + 0x07, 0xff, 0x56, 0x98, 0x3e, 0x11, 0x00, 0x00, 0xbd, // version + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 0 + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 1 + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 2 + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 3 + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 4 + 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x0, 0x0, 0xa1, // scale 5 + 0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 6 + 0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x0, 0x1, 0xb5, // scale 7 + 0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x0, 0x0, 0xda, // scale 8 + 0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x0, 0x0, 0xa1, //scale 9 + 0x08, 0xff, 0x57, 0x8e, 0x4, 0x0, 0x8f, 0x0, 0x80, 0x1, // scale 10 + 0x08, 0xff, 0x57, 0x8e, 0x1, 0x0, 0x8f, 0x0, 0x80, 0x4, // scale 11 + 0x08, 0xff, 0x57, 0x8e, 0x6, 0x0, 0x8f, 0x0, 0x80, 0xff, // scale 12 + 0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x0, 0x0, 0xce, // scale 13 + 0x07, 0xff, 0x56, 0x98, 0x3e, 0x11, 0x0, 0x0, 0xbd, // version + 0x0f, 0x20, 0xb6, 0x89, 0x6d, 0xb7, 0xc, 0x4e, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x82, // dc info + 0x0f, 0x20, 0x1, 0x1, 0x6d, 0xb7, 0x8, 0x77, 0x5b, 0x21, 0x0, 0x77, 0x5b, 0xfe, 0xff, 0xc3, 0x1e, // ac info + 0x08, 0xff, 0x4c, 0x9, 0x0, 0x0, 0x0, 0x3, 0x0, 0xa1, + 0x05, 0xff, 0x57, 0x85, 0xc8, 0x0, 0x58, + }, + knownWrites: []byte{}, + result: Mk2Info{ + Version: 0xac0, + BatVoltage: 26.38, + BatCurrent: 0, + InVoltage: 234.15, + InCurrent: 0.33, + InFrequency: 50.1025641025641, + OutVoltage: 234.15, + OutCurrent: -0.02, + OutFrequency: 50.025510204081634, + ChargeState: 1, + LEDs: map[Led]LEDstate{ + LedMain: LedOn, + LedAbsorption: LedOff, + LedBulk: LedOff, + LedFloat: LedOn, + LedInverter: LedOff, + LedOverload: LedOff, + LedLowBattery: LedOff, + LedTemperature: LedOff, + }, + }, + }, } - expectedLEDs := map[Led]LEDstate{ - LedMain: LedOn, - LedAbsorption: LedOn, - LedBulk: LedOff, - LedFloat: LedOff, - LedInverter: LedOff, - LedOverload: LedOff, - LedLowBattery: LedOff, - LedTemperature: LedOff, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testIO := NewIOStub(tt.knownReadBuffer) + mk2, err := NewMk2Connection(testIO) + assert.NoError(t, err, "Could not open MK2") + + event := <-mk2.C() + mk2.Close() + + if len(tt.knownWrites) > 0 { + assert.Equal(t, 0, bytes.Compare(writeBuffer.Bytes(), knownWrites), "Expected writes did not match received writes") + } + assert.True(t, event.Valid, "data not valid") + assert.Equal(t, tt.result.Version, event.Version, "Invalid version decoded") + assert.Equal(t, 0, len(event.Errors), "Reported errors not empty") + assert.Equal(t, tt.result.LEDs, event.LEDs, "Reported LEDs incorrect") + + assert.InDelta(t, tt.result.BatVoltage, event.BatVoltage, testDelta, "BatVoltage conversion failed") + assert.InDelta(t, tt.result.BatCurrent, event.BatCurrent, testDelta, "BatCurrent conversion failed") + assert.InDelta(t, tt.result.InVoltage, event.InVoltage, testDelta, "InVoltage conversion failed") + assert.InDelta(t, tt.result.InCurrent, event.InCurrent, testDelta, "InCurrent conversion failed") + assert.InDelta(t, tt.result.InFrequency, event.InFrequency, testDelta, "InFrequency conversion failed") + assert.InDelta(t, tt.result.OutVoltage, event.OutVoltage, testDelta, "OutVoltage conversion failed") + assert.InDelta(t, tt.result.OutCurrent, event.OutCurrent, testDelta, "OutCurrent conversion failed") + assert.InDelta(t, tt.result.OutFrequency, event.OutFrequency, testDelta, "OutFrequency conversion failed") + assert.InDelta(t, tt.result.ChargeState, event.ChargeState, testDelta, "ChargeState conversion failed") + }) } - testIO := NewIOStub(knownReadBuffer) - mk2, err := NewMk2Connection(testIO) - assert.NoError(t, err, "Could not open MK2") - - event := <-mk2.C() - mk2.Close() - - assert.Equal(t, 0, bytes.Compare(writeBuffer.Bytes(), knownWrites), "Expected writes did not match received writes") - assert.True(t, event.Valid, "data not valid") - assert.Equal(t, uint32(2736), event.Version, "Invalid version decoded") - assert.Equal(t, 0, len(event.Errors), "Reported errors not empty") - assert.Equal(t, expectedLEDs, event.LEDs, "Reported LEDs incorrect") - - assert.InEpsilon(t, 14.41, event.BatVoltage, testEpsilon, "BatVoltage conversion failed") - assert.InEpsilon(t, -0.4, event.BatCurrent, testEpsilon, "BatCurrent conversion failed") - assert.InEpsilon(t, 226.98, event.InVoltage, testEpsilon, "InVoltage conversion failed") - assert.InEpsilon(t, 1.71, event.InCurrent, testEpsilon, "InCurrent conversion failed") - assert.InEpsilon(t, 50.10256410256411, event.InFrequency, testEpsilon, "InFrequency conversion failed") - assert.InEpsilon(t, 226.980, event.OutVoltage, testEpsilon, "OutVoltage conversion failed") - assert.InEpsilon(t, 1.54, event.OutCurrent, testEpsilon, "OutCurrent conversion failed") - assert.InEpsilon(t, 50.025510204081634, event.OutFrequency, testEpsilon, "OutFrequency conversion failed") - assert.InEpsilon(t, 1, event.ChargeState, testEpsilon, "ChargeState conversion failed") } func Test_mk2Ser_scaleDecode(t *testing.T) { @@ -116,16 +216,16 @@ func Test_mk2Ser_scaleDecode(t *testing.T) { }{ { name: "Valid scale", - frame: []byte{0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a}, + frame: []byte{0x8e, 0x9c, 0x7f, 0x8f, 0x01, 0x00, 0x6a}, expectedScaling: scaling{ - scale: 0.00013679890560875513, - offset: 143, + scale: 0.01, + offset: 1, supported: true, }, }, { name: "Unsupported frame", - frame: []byte{0x57, 0x00}, + frame: []byte{0x00}, expectedScaling: scaling{ supported: false, }, @@ -141,9 +241,10 @@ func Test_mk2Ser_scaleDecode(t *testing.T) { assert.Equal(t, 1, len(m.scales)) assert.Equal(t, 1, m.scaleCount) assert.Equal(t, tt.expectedScaling.supported, m.scales[0].supported) + assert.Equal(t, tt.expectedScaling.signed, m.scales[0].signed) if tt.expectedScaling.supported { - assert.InEpsilon(t, tt.expectedScaling.offset, m.scales[0].offset, testEpsilon) - assert.InEpsilon(t, tt.expectedScaling.scale, m.scales[0].scale, testEpsilon) + assert.InDelta(t, tt.expectedScaling.offset, m.scales[0].offset, testDelta) + assert.InDelta(t, tt.expectedScaling.scale, m.scales[0].scale, testDelta) } }) }