We were decoding the scale as unsigned while it is signed. We were also ignoring the fact that the sign of the scale determines the signedness of the value it scales.
437 lines
9.3 KiB
Go
437 lines
9.3 KiB
Go
package mk2driver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type scaling struct {
|
|
scale float64
|
|
offset float64
|
|
signed bool
|
|
supported bool
|
|
}
|
|
|
|
//nolint:deadcode,varcheck
|
|
const (
|
|
ramVarVMains = iota
|
|
ramVarIMains
|
|
ramVarVInverter
|
|
ramVarIInverter
|
|
ramVarVBat
|
|
ramVarIBat
|
|
ramVarVBatRipple
|
|
ramVarInverterPeriod
|
|
ramVarMainPeriod
|
|
ramVarIACLoad
|
|
ramVarVirSwitchPos
|
|
ramVarIgnACInState
|
|
ramVarMultiFuncRelay
|
|
ramVarChargeState
|
|
ramVarInverterPower1
|
|
ramVarInverterPower2
|
|
ramVarOutPower
|
|
|
|
ramVarMaxOffset = 14
|
|
)
|
|
|
|
const (
|
|
infoFrameHeader = 0x20
|
|
frameHeader = 0xff
|
|
)
|
|
|
|
const (
|
|
acL1InfoFrame = 0x08
|
|
dcInfoFrame = 0x0C
|
|
setTargetFrame = 0x41
|
|
infoReqFrame = 0x46
|
|
ledFrame = 0x4C
|
|
vFrame = 0x56
|
|
winmonFrame = 0x57
|
|
)
|
|
|
|
// info frame types
|
|
const (
|
|
infoReqAddrDC = 0x00
|
|
infoReqAddrACL1 = 0x01
|
|
)
|
|
|
|
// winmon frame commands
|
|
const (
|
|
commandReadRAMVar = 0x30
|
|
commandGetRAMVarInfo = 0x36
|
|
|
|
commandReadRAMResponse = 0x85
|
|
commandGetRAMVarInfoResponse = 0x8E
|
|
)
|
|
|
|
type mk2Ser struct {
|
|
info *Mk2Info
|
|
p io.ReadWriter
|
|
scales []scaling
|
|
scaleCount int
|
|
run chan struct{}
|
|
frameLock bool
|
|
infochan chan *Mk2Info
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
|
mk2 := &mk2Ser{}
|
|
mk2.p = dev
|
|
mk2.info = &Mk2Info{}
|
|
mk2.scaleCount = 0
|
|
mk2.frameLock = false
|
|
mk2.scales = make([]scaling, 0, ramVarMaxOffset)
|
|
mk2.setTarget()
|
|
mk2.run = make(chan struct{})
|
|
mk2.infochan = make(chan *Mk2Info)
|
|
mk2.wg.Add(1)
|
|
go mk2.frameLocker()
|
|
return mk2, nil
|
|
}
|
|
|
|
// Locks to incoming frame.
|
|
func (m *mk2Ser) frameLocker() {
|
|
frame := make([]byte, 256)
|
|
var frameLength byte
|
|
for {
|
|
select {
|
|
case <-m.run:
|
|
m.wg.Done()
|
|
return
|
|
default:
|
|
}
|
|
if m.frameLock {
|
|
frameLength = m.readByte()
|
|
frameLengthOffset := int(frameLength) + 1
|
|
l, err := io.ReadFull(m.p, frame[:frameLengthOffset])
|
|
if err != nil {
|
|
m.addError(fmt.Errorf("Read Error: %v", err))
|
|
m.frameLock = false
|
|
} else if l != frameLengthOffset {
|
|
m.addError(errors.New("Read Length Error"))
|
|
m.frameLock = false
|
|
} else {
|
|
m.handleFrame(frameLength, frame[:frameLengthOffset])
|
|
}
|
|
} else {
|
|
tmp := m.readByte()
|
|
frameLengthOffset := int(frameLength)
|
|
if tmp == frameHeader || tmp == infoFrameHeader {
|
|
l, err := io.ReadFull(m.p, frame[:frameLengthOffset])
|
|
if err != nil {
|
|
m.addError(fmt.Errorf("Read Error: %v", err))
|
|
time.Sleep(1 * time.Second)
|
|
} else if l != frameLengthOffset {
|
|
m.addError(errors.New("Read Length Error"))
|
|
} else {
|
|
if checkChecksum(frameLength, tmp, frame[:frameLengthOffset]) {
|
|
m.frameLock = true
|
|
logrus.Info("Locked")
|
|
}
|
|
}
|
|
}
|
|
frameLength = tmp
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close Mk2
|
|
func (m *mk2Ser) Close() {
|
|
close(m.run)
|
|
m.wg.Wait()
|
|
}
|
|
|
|
func (m *mk2Ser) C() chan *Mk2Info {
|
|
return m.infochan
|
|
}
|
|
|
|
func (m *mk2Ser) readByte() byte {
|
|
buffer := make([]byte, 1)
|
|
_, err := io.ReadFull(m.p, buffer)
|
|
if err != nil {
|
|
m.addError(fmt.Errorf("Read error: %v", err))
|
|
return 0
|
|
}
|
|
return buffer[0]
|
|
}
|
|
|
|
// Adds error to error slice.
|
|
func (m *mk2Ser) addError(err error) {
|
|
if m.info.Errors == nil {
|
|
m.info.Errors = make([]error, 0)
|
|
}
|
|
m.info.Errors = append(m.info.Errors, err)
|
|
m.info.Valid = false
|
|
}
|
|
|
|
// Updates report.
|
|
func (m *mk2Ser) updateReport() {
|
|
m.info.Timestamp = time.Now()
|
|
select {
|
|
case m.infochan <- m.info:
|
|
default:
|
|
}
|
|
m.info = &Mk2Info{}
|
|
}
|
|
|
|
// Checks for valid frame and chooses decoding.
|
|
func (m *mk2Ser) handleFrame(l byte, frame []byte) {
|
|
if checkChecksum(l, frame[0], frame[1:]) {
|
|
switch frame[0] {
|
|
case frameHeader:
|
|
switch frame[1] {
|
|
case vFrame:
|
|
m.versionDecode(frame[2:])
|
|
case winmonFrame:
|
|
switch frame[2] {
|
|
case commandGetRAMVarInfoResponse:
|
|
m.scaleDecode(frame[2:])
|
|
case commandReadRAMResponse:
|
|
m.stateDecode(frame[2:])
|
|
}
|
|
|
|
case ledFrame:
|
|
m.ledDecode(frame[2:])
|
|
}
|
|
|
|
case infoFrameHeader:
|
|
switch frame[5] {
|
|
case dcInfoFrame:
|
|
m.dcDecode(frame[1:])
|
|
case acL1InfoFrame:
|
|
m.acDecode(frame[1:])
|
|
}
|
|
}
|
|
} else {
|
|
logrus.Errorf("Invalid incoming frame checksum: %x", frame)
|
|
m.frameLock = false
|
|
}
|
|
}
|
|
|
|
// Set the target VBus device.
|
|
func (m *mk2Ser) setTarget() {
|
|
cmd := make([]byte, 3)
|
|
cmd[0] = setTargetFrame
|
|
cmd[1] = 0x01
|
|
cmd[2] = 0x00
|
|
m.sendCommand(cmd)
|
|
}
|
|
|
|
// Request the scaling factor for entry 'in'.
|
|
func (m *mk2Ser) reqScaleFactor(in byte) {
|
|
cmd := make([]byte, 4)
|
|
cmd[0] = winmonFrame
|
|
cmd[1] = commandGetRAMVarInfo
|
|
cmd[2] = in
|
|
m.sendCommand(cmd)
|
|
}
|
|
|
|
func int16Abs(in int16) uint16 {
|
|
if in < 0 {
|
|
return uint16(-in)
|
|
}
|
|
return uint16(in)
|
|
}
|
|
|
|
// Decode the scale factor frame.
|
|
func (m *mk2Ser) scaleDecode(frame []byte) {
|
|
tmp := scaling{}
|
|
logrus.Infof("Scale frame(%d): 0x%x", len(frame), frame)
|
|
if len(frame) < 6 {
|
|
tmp.supported = false
|
|
logrus.Warnf("Skiping scaling factors for: %d", m.scaleCount)
|
|
} else {
|
|
tmp.supported = true
|
|
var scl int16
|
|
var ofs int16
|
|
if len(frame) == 6 {
|
|
scl = int16(frame[2])<<8 + int16(frame[1])
|
|
ofs = int16(uint16(frame[4])<<8 + uint16(frame[3]))
|
|
} else {
|
|
scl = int16(frame[2])<<8 + int16(frame[1])
|
|
ofs = int16(uint16(frame[5])<<8 + uint16(frame[4]))
|
|
}
|
|
if scl < 0 {
|
|
tmp.signed = true
|
|
}
|
|
tmp.offset = float64(ofs)
|
|
scale := int16Abs(scl)
|
|
if scale >= 0x4000 {
|
|
tmp.scale = 1 / (0x8000 - float64(scale))
|
|
} else {
|
|
tmp.scale = float64(scale)
|
|
}
|
|
}
|
|
m.scales = append(m.scales, tmp)
|
|
m.scaleCount++
|
|
if m.scaleCount < ramVarMaxOffset {
|
|
m.reqScaleFactor(byte(m.scaleCount))
|
|
} else {
|
|
logrus.Info("Monitoring starting.")
|
|
}
|
|
}
|
|
|
|
// Decode the version number
|
|
func (m *mk2Ser) versionDecode(frame []byte) {
|
|
m.info.Version = 0
|
|
m.info.Valid = true
|
|
for i := 0; i < 4; i++ {
|
|
m.info.Version += uint32(frame[i]) << uint(i) * 8
|
|
}
|
|
|
|
if m.scaleCount < ramVarMaxOffset {
|
|
logrus.Info("Get scaling factors.")
|
|
m.reqScaleFactor(byte(m.scaleCount))
|
|
} else {
|
|
// Send DC status request
|
|
cmd := make([]byte, 2)
|
|
cmd[0] = infoReqFrame
|
|
cmd[1] = infoReqAddrDC
|
|
m.sendCommand(cmd)
|
|
}
|
|
}
|
|
|
|
// Decode with correct signedness and apply scale
|
|
func (m *mk2Ser) applyScaleAndSign(data []byte, scale int) float64 {
|
|
var value float64
|
|
if !m.scales[scale].supported {
|
|
return 0
|
|
}
|
|
if m.scales[scale].signed {
|
|
value = getSigned(data)
|
|
} else {
|
|
value = getUnsigned16(data)
|
|
}
|
|
return m.applyScale(value, scale)
|
|
}
|
|
|
|
// Apply scaling to float
|
|
func (m *mk2Ser) applyScale(value float64, scale int) float64 {
|
|
if !m.scales[scale].supported {
|
|
return value
|
|
}
|
|
return m.scales[scale].scale * (value + m.scales[scale].offset)
|
|
}
|
|
|
|
// Convert bytes->int16->float
|
|
func getSigned(data []byte) float64 {
|
|
return float64(int16(data[0]) + int16(data[1])<<8)
|
|
}
|
|
|
|
// Convert bytes->int16->float
|
|
func getUnsigned16(data []byte) float64 {
|
|
return float64(uint16(data[0]) + uint16(data[1])<<8)
|
|
}
|
|
|
|
// Convert bytes->uint32->float
|
|
func getUnsigned(data []byte) float64 {
|
|
return float64(uint32(data[0]) + uint32(data[1])<<8 + uint32(data[2])<<16)
|
|
}
|
|
|
|
// Decodes DC frame.
|
|
func (m *mk2Ser) dcDecode(frame []byte) {
|
|
m.info.BatVoltage = m.applyScaleAndSign(frame[5:7], ramVarVBat)
|
|
|
|
usedC := m.applyScale(getUnsigned(frame[7:10]), ramVarIBat)
|
|
chargeC := m.applyScale(getUnsigned(frame[10:13]), ramVarIBat)
|
|
m.info.BatCurrent = usedC - chargeC
|
|
|
|
m.info.OutFrequency = 10 / (m.applyScale(float64(frame[13]), ramVarInverterPeriod))
|
|
|
|
// Send L1 status request
|
|
cmd := make([]byte, 2)
|
|
cmd[0] = infoReqFrame
|
|
cmd[1] = infoReqAddrACL1
|
|
m.sendCommand(cmd)
|
|
}
|
|
|
|
// 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)
|
|
|
|
if frame[13] == 0xff {
|
|
m.info.InFrequency = 0
|
|
} else {
|
|
m.info.InFrequency = 10 / (m.applyScale(float64(frame[13]), ramVarMainPeriod))
|
|
}
|
|
|
|
// Send status request
|
|
cmd := make([]byte, 1)
|
|
cmd[0] = ledFrame
|
|
m.sendCommand(cmd)
|
|
}
|
|
|
|
// Decode charge state of battery.
|
|
func (m *mk2Ser) stateDecode(frame []byte) {
|
|
m.info.ChargeState = m.applyScaleAndSign(frame[1:3], ramVarChargeState)
|
|
m.updateReport()
|
|
}
|
|
|
|
// Decode the LED state frame.
|
|
func (m *mk2Ser) ledDecode(frame []byte) {
|
|
|
|
m.info.LEDs = getLEDs(frame[0], frame[1])
|
|
// Send charge state request
|
|
cmd := make([]byte, 4)
|
|
cmd[0] = winmonFrame
|
|
cmd[1] = commandReadRAMVar
|
|
cmd[2] = ramVarChargeState
|
|
m.sendCommand(cmd)
|
|
}
|
|
|
|
// Adds active LEDs to list.
|
|
func getLEDs(ledsOn, ledsBlink byte) map[Led]LEDstate {
|
|
|
|
leds := map[Led]LEDstate{}
|
|
for i := 0; i < 8; i++ {
|
|
on := (ledsOn >> uint(i)) & 1
|
|
blink := (ledsBlink >> uint(i)) & 1
|
|
if on == 1 {
|
|
leds[Led(i)] = LedOn
|
|
} else if blink == 1 {
|
|
leds[Led(i)] = LedBlink
|
|
} else {
|
|
leds[Led(i)] = LedOff
|
|
}
|
|
}
|
|
return leds
|
|
}
|
|
|
|
// Adds header and trailing crc for frame to send.
|
|
func (m *mk2Ser) sendCommand(data []byte) {
|
|
l := len(data)
|
|
dataOut := make([]byte, l+3)
|
|
dataOut[0] = byte(l + 1)
|
|
dataOut[1] = frameHeader
|
|
cr := -dataOut[0] - dataOut[1]
|
|
for i := 0; i < len(data); i++ {
|
|
cr = cr - data[i]
|
|
dataOut[i+2] = data[i]
|
|
}
|
|
dataOut[l+2] = cr
|
|
|
|
_, err := m.p.Write(dataOut)
|
|
if err != nil {
|
|
m.addError(fmt.Errorf("Write error: %v", err))
|
|
}
|
|
}
|
|
|
|
// Checks the frame crc.
|
|
func checkChecksum(l, t byte, d []byte) bool {
|
|
cr := (uint16(l) + uint16(t)) % 256
|
|
for i := 0; i < len(d); i++ {
|
|
cr = (cr + uint16(d[i])) % 256
|
|
}
|
|
return cr == 0
|
|
}
|