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 }