From b3245aba9bfa62d21578f51427b4f1ce10996450 Mon Sep 17 00:00:00 2001 From: Hendrik van Wyk Date: Wed, 8 Dec 2021 22:54:47 +0200 Subject: [PATCH] No longer use RAM value scale signedness for info frames. The signedness calculated along with the RAM value scale and offset was incorrectly applied to the info frame value. This caused some values to be interpreted as unsigned instead of signed leading to negative values reporting as very large positive values. --- mk2driver/mk2.go | 10 +- mk2driver/mk2_test.go | 221 ++++++++++++++++++++++++++++++------------ 2 files changed, 166 insertions(+), 65 deletions(-) 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) } }) }