src.dualinventive.com/go/cp3000-interface/internal/device/zkl3000.go

186 lines
5.4 KiB
Go

package device
import (
"time"
"src.dualinventive.com/go/cp3000-interface/internal/statusupdate"
"src.dualinventive.com/go/dinet/rpc"
"src.dualinventive.com/go/lib/cp3000"
"src.dualinventive.com/go/lib/dilog"
)
// Zkl3000 is a ZKL 3000 device
type Zkl3000 struct {
*Base
Charger1State bool
}
func (z *Zkl3000) construct() {
z.decoder = statusupdate.NewStatusDecoder(z.dev.Logger(), statusupdate.StatUpdateZKL3000)
z.dev.Subscribe(rpc.ClassMethodSensorInfo, rpc.MsgTypeRequest, z.sensorInfo)
z.dev.AddConfig(rpc.ConfigEndpoint, z.configGetEndpoint, z.configSetEndpoint, nil)
}
// nolint: dupl
func (z *Zkl3000) init() {
}
// Statusupdate is a callback for the $STAT message
// Suppress the linter because statusUpdateCmd needs to satisfy the interface
//nolint: unparam
func (z *Zkl3000) statusUpdateCmd(r cp3000.Router, args []string) error {
err := z.updateSensorData(args)
if z.updateErrors() {
z.dev.Logger().Info("send device:data")
if pErr := z.dev.SendDeviceData(); pErr != nil {
return pErr
}
}
return err
}
func (z *Zkl3000) updateSensorData(args []string) error {
// Get the old sensor data. Decode the new sensor data (which stores the new sensor data)
old := z.decoder.StatusUpdate()
err := z.decoder.Decode(args)
if err != nil {
z.dev.Logger().WithError(err).Warning("status decoding failed")
// This error is no important enough for the caller
return nil
}
// Get the new sensor data
s := z.decoder.StatusUpdate()
data := []*rpc.ResultValueItem{}
data = old.CheckBattery1(data, s)
data = old.CheckBattery1State(data, s)
data = old.CheckBattery2(data, s)
data = old.CheckBattery2State(data, s)
if z.Charger1State {
data = old.CheckCharger1State(data, s)
}
data = old.CheckGPS(data, s)
data = old.CheckRSSI(data, s)
data = old.CheckBER(data, s)
if s.SwitchShort() {
// Only send the detection quality when the switch is on
data = old.CheckDetectionQuality(data, s)
}
data = old.CheckDetectionStatus(data, s)
data = old.CheckMeasurement(data, s)
data = old.CheckBAActual(data, s)
data = old.CheckFrequency(data, s)
data = old.CheckSWShort(data, s)
data = old.CheckSWBattery(data, s)
data = old.CheckSwitchState(data, s)
data = old.CheckKeyswitch(data, s)
data = old.CheckRMS(data, s)
data = old.CheckBAAutocal(data, s)
data = old.CheckTempOnboard(data, s, &rpc.ZKLSensorTempOnboard)
data = old.CheckTempNTC(data, s, &rpc.ZKLSensorTempNTC)
if len(data) > 0 {
z.dev.Logger().WithField("count", len(data)).Debug("send sensor data")
return z.dev.Send(&rpc.Msg{
ClassMethod: rpc.ClassMethodSensorData,
Type: rpc.MsgTypePublish,
Result: rpc.NewResult(data),
})
}
return nil
}
func (z *Zkl3000) updateErrors() bool {
s := z.decoder.StatusUpdate()
if s.ErrMeasurementFailed() {
z.dev.Logger().Warning("measurement error occurred")
}
uChanged := z.updateError(s.ErrUARTGPS(), "uart (gps) error", rpc.EFirmwareGps)
mcuChanged := z.updateError(s.ErrMCU(), "mcu comm error", rpc.EFirmwareMcuComm)
if s.ErrUARTMcu() {
// This should never occur, so just log to check this hypothesis
z.dev.Logger().Warning("uart (mcu) error")
}
// Not used updateError, because there are two conditions when EFirmwareSwitchComm should occur
var switchChanged bool
if s.ErrSwitch3000Timeout() || s.ErrSwitch3000() {
switchChanged = z.dev.SetError(rpc.EFirmwareSwitchComm)
} else {
switchChanged = z.dev.ClearError(rpc.EFirmwareSwitchComm)
}
if switchChanged {
z.dev.Logger().WithFields(dilog.Fields{
"timeout": s.ErrSwitch3000Timeout(),
"failed": s.ErrSwitch3000(),
}).Info("switch 3000 error")
}
// Check if we are forced to send the device data
return uChanged || mcuChanged || switchChanged
}
// Versions returns the firmware and hardware versions
func (z *Zkl3000) Versions() (rpc.VersionMap, rpc.ErrorCode) {
mcu, rpcerr := z.requestVersion("FW-MCU")
if rpcerr != rpc.Ok {
return nil, rpcerr
}
wcpu, rpcerr := z.requestVersion("FW-WCPU")
if rpcerr != rpc.Ok {
return nil, rpcerr
}
return rpc.VersionMap{
rpc.VersionKeyHwZKLMain: z.hwVersion,
rpc.VersionKeyFwZKLMain: mcu,
rpc.VersionKeyFwZKLWcpu: wcpu,
}, rpc.Ok
}
// TimeoutDuration returns when the device should be considered offline
func (z *Zkl3000) TimeoutDuration() time.Duration {
return time.Minute
}
// Transport returns the transportation type
func (z *Zkl3000) Transport() (interface{}, rpc.ErrorCode) {
return z.cp3000Transport()
}
// sensorInfo returns all the sensor information
func (z *Zkl3000) sensorInfo(req *rpc.Msg) *rpc.Msg {
rep := req.CreateReply()
rep.Result = rpc.NewResult([]rpc.ResultInfoItem{
rpc.SensorBattery1Voltage.Info(),
rpc.SensorBattery1State.Info(),
rpc.SensorBattery2Voltage.Info(),
rpc.SensorBattery2State.Info(),
rpc.SensorGPS.Info(),
rpc.SensorRSSI.Info(),
rpc.SensorBER.Info(),
rpc.ZKLSensorDetectionQuality.Info(),
rpc.ZKLSensorDetection.Info(),
rpc.ZKLSensorMeasurement.Info(),
rpc.ZKLSensorBa.Info(),
rpc.ZKLSensorFrequency.Info(),
rpc.ZKLSensorRMS.Info(),
rpc.ZKLSensorBAAutocal.Info(),
rpc.ZKLSensorTempOnboard.Info(),
rpc.ZKLSensorTempNTC.Info(),
})
return rep
}
// RegisterCallbacks registers additional callbacks for ZKL 3000 devices once they authenticated
func (z *Zkl3000) RegisterCallbacks() error {
z.client.Register(cp3000.CommandWatchdog, z.heartbeatCmd)
z.client.Register(cp3000.CommandStatus, z.statusUpdateCmd)
z.client.Register(cp3000.CommandSync, z.syncCmd)
z.client.Register(cp3000.CommandExit, z.exitCmd)
return nil
}