186 lines
5.4 KiB
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
|
|
}
|