412 lines
8.9 KiB
Go
412 lines
8.9 KiB
Go
package mk2driver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type scaling struct {
|
|
scale float64
|
|
offset float64
|
|
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)
|
|
}
|
|
|
|
// 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 if len(frame) == 6 {
|
|
tmp.supported = true
|
|
scl := uint16(frame[2])<<8 + uint16(frame[1])
|
|
ofs := int16(uint16(frame[4])<<8 + uint16(frame[3]))
|
|
|
|
tmp.offset = float64(ofs)
|
|
if scl >= 0x4000 {
|
|
tmp.scale = math.Abs(1 / (0x8000 - float64(scl)))
|
|
} else {
|
|
tmp.scale = math.Abs(float64(scl))
|
|
}
|
|
} else {
|
|
tmp.supported = true
|
|
scl := uint16(frame[2])<<8 + uint16(frame[1])
|
|
ofs := int16(uint16(frame[5])<<8 + uint16(frame[4]))
|
|
|
|
tmp.offset = float64(ofs)
|
|
if scl >= 0x4000 {
|
|
tmp.scale = math.Abs(1 / (0x8000 - float64(scl)))
|
|
} else {
|
|
tmp.scale = math.Abs(float64(scl))
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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->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.applyScale(getSigned(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.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
|
|
} 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.applyScale(getSigned(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
|
|
}
|