749 lines
24 KiB
Go
749 lines
24 KiB
Go
package device
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"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"
|
|
)
|
|
|
|
// zkl3000RcMaximumWaitForMeasurement is the maximum time to wait until a measurement should arrive
|
|
//nolint: gochecknoglobals
|
|
var zkl3000RcMaximumWaitForMeasurement = 3 * time.Second
|
|
|
|
// errZKL3000RcSwitchStatsRetriesExceeded when the switch status is not valid after retries
|
|
var errZKL3000RcSwitchStatsRetriesExceeded = errors.New("zkl3000rc: switch status retries exceeded")
|
|
|
|
// SwitchCommand is a command to request a switch change.
|
|
// The algorithm to calculate the right value is defined in: mtinfo/common: include/zkl-common.h:902
|
|
type SwitchCommand byte
|
|
|
|
const (
|
|
// SwitchStatusCommand (0xF0)
|
|
SwitchStatusCommand SwitchCommand = ((iota) | ((^(iota) & 0xF) << 4))
|
|
//ShortCircuitOffSwitchCommand turns the short circuit off (0xE1)
|
|
ShortCircuitOffSwitchCommand
|
|
//ShortCircuitOnSwitchCommand turns the short circuit on (0xD2)
|
|
ShortCircuitOnSwitchCommand
|
|
//MeasurementOffSwitchCommand turns the measurement off (0xC3)
|
|
MeasurementOffSwitchCommand
|
|
//MeasurementOnSwitchCommand turns the measurement on (0xB4)
|
|
MeasurementOnSwitchCommand
|
|
//RemoveTokenSwitchCommand removes the token in the switch (0xA5)
|
|
RemoveTokenSwitchCommand
|
|
//WriteTokenSwichCommand writes the token in the switch (0x96)
|
|
WriteTokenSwichCommand
|
|
)
|
|
|
|
// Zkl3000Rc is a ZKL 3000 RC device
|
|
type Zkl3000Rc struct {
|
|
Zkl3000
|
|
|
|
switchInfo *SwitchInfo
|
|
active bool
|
|
}
|
|
|
|
// construct is called when the device is created. This should be non blocking
|
|
func (z *Zkl3000Rc) construct() {
|
|
z.decoder = statusupdate.NewStatusDecoder(z.dev.Logger(), statusupdate.StatUpdateAll)
|
|
|
|
z.dev.Subscribe(rpc.ClassMethodSensorInfo, rpc.MsgTypeRequest, z.sensorInfo)
|
|
z.dev.AddConfig(rpc.ConfigToken, z.configGetToken, z.configSetToken, z.configResetToken)
|
|
z.dev.AddConfig(rpc.ConfigActive, z.configGetActivate, z.configSetActivate, z.configResetActivate)
|
|
z.dev.AddConfig(rpc.ConfigEndpoint, z.configGetEndpoint, z.configSetEndpoint, nil)
|
|
}
|
|
|
|
// init is called when the device is created. This can be blocking
|
|
func (z *Zkl3000Rc) init() {
|
|
if _, _, err := z.requestSwitchInfo(); err != nil {
|
|
z.dev.Logger().WithError(err).Warning("problem reading token")
|
|
}
|
|
}
|
|
|
|
// Below are all the CP3000 callbacks
|
|
|
|
// Statusupdate is a callback for the $STAT message
|
|
// Suppress the linter because statusUpdateCmd needs to satisfy the interface
|
|
//nolint: unparam
|
|
func (z *Zkl3000Rc) 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
|
|
}
|
|
|
|
// heartbeatCmd is the Watchdog callback (used as heartbeat)
|
|
// Suppress the linter because heartbeatCmd needs to satisfy the interface
|
|
//nolint: unparam
|
|
func (z *Zkl3000Rc) heartbeatCmd(r cp3000.Router, args []string) error {
|
|
if err := z.dev.SendDeviceData(); err != nil {
|
|
return err
|
|
}
|
|
if z.dev.GetState() == rpc.DeviceStateActive {
|
|
go z.continueHeartbeats()
|
|
}
|
|
return z.client.Send("WD")
|
|
}
|
|
|
|
// RegisterCallbacks overrides the heartbeat command
|
|
func (z *Zkl3000Rc) RegisterCallbacks() error {
|
|
err := z.Zkl3000.RegisterCallbacks()
|
|
z.client.Register(cp3000.CommandWatchdog, z.heartbeatCmd)
|
|
z.client.Register(cp3000.CommandStatus, z.statusUpdateCmd)
|
|
return err
|
|
}
|
|
|
|
// Below are all the DI Net callbacks
|
|
|
|
// sensorInfo returns all the sensor information
|
|
func (z *Zkl3000Rc) sensorInfo(req *rpc.Msg) *rpc.Msg {
|
|
rep := req.CreateReply()
|
|
// dupl with the test, so suppress it
|
|
// nolint: dupl
|
|
rep.Result = rpc.NewResult([]rpc.ResultInfoItem{
|
|
rpc.SensorBattery1Voltage.Info(),
|
|
rpc.SensorBattery1State.Info(),
|
|
rpc.SensorBattery2Voltage.Info(),
|
|
rpc.SensorBattery2State.Info(),
|
|
rpc.SensorCharger1State.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(),
|
|
rpc.ZKLRCSensorSectionsShort.Info(),
|
|
rpc.ZKLRCSensorSectionsBattery.Info(),
|
|
rpc.ZKLRCSensorSwitchShort.Info(),
|
|
rpc.ZKLRCSensorKeySwitch.Info(),
|
|
})
|
|
return rep
|
|
}
|
|
|
|
// configSetToken request is called when the project is released
|
|
func (z *Zkl3000Rc) configSetToken(p rpc.ConfigParam) rpc.ErrorCode {
|
|
logger := z.dev.Logger().WithField("action", "config:set token")
|
|
val, err := p.ValueInt64()
|
|
if err != nil {
|
|
logger.WithError(err).WithField("type", fmt.Sprintf("%T", p.Value)).Error("token is not an int64")
|
|
return rpc.EParam
|
|
}
|
|
newToken := uint32(val)
|
|
|
|
up, switchInfo, retVal := z.preflightChecks(logger, true, true)
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
|
|
// When there is already a different token, deny operation
|
|
if switchInfo.Released && switchInfo.Token != newToken {
|
|
logger.WithFields(dilog.Fields{
|
|
"currenttoken": switchInfo.Token,
|
|
"newtoken": newToken,
|
|
}).Error("token mismatch")
|
|
return rpc.EFirmwareMismatchToken
|
|
}
|
|
|
|
if up.KeySwitch() != rpc.KeyswitchStateOperational {
|
|
logger.WithField("keyswitch", up.KeySwitch()).Error("keyswitch is not operational")
|
|
return rpc.EFirmwareKeyswitchNotOperational
|
|
}
|
|
|
|
retVal = z.batteryChecks(logger, up, false)
|
|
if retVal.IsError() {
|
|
return retVal
|
|
}
|
|
|
|
// Always at the end, because you cannot for example release with an invalid keyswitch even if the device is
|
|
// already released
|
|
if switchInfo.Released {
|
|
if up.Released() {
|
|
logger.Info("device is already released")
|
|
return rpc.Ok
|
|
}
|
|
logger.Warning("device already released, but STAT data say otherwise. DEADLOCK")
|
|
}
|
|
// It is possible that the token was already written, but the status update is still reporting not released
|
|
// We are in a deadlock! Just write it again to the switch and hopefully it is fixed.
|
|
return z.writeSwitchToken(retVal, newToken)
|
|
}
|
|
|
|
// configSetActivate request is called when the project is activated
|
|
func (z *Zkl3000Rc) configSetActivate(p rpc.ConfigParam) rpc.ErrorCode {
|
|
logger := z.dev.Logger().WithFields(dilog.Fields{
|
|
"action": "config:set activate",
|
|
"value": p.Value,
|
|
})
|
|
activationState, ok := p.Value.(bool)
|
|
if !ok {
|
|
logger.WithField("type", fmt.Sprintf("%T", p.Value)).Error("activate is not a bool")
|
|
return rpc.EParam
|
|
}
|
|
return z.activateDeactivateSwitch(logger, activationState)
|
|
}
|
|
|
|
// configResetToken request is called when the project is returned
|
|
func (z *Zkl3000Rc) configResetToken() rpc.ErrorCode {
|
|
logger := z.dev.Logger().WithField("action", "config:reset token")
|
|
|
|
_, switchInfo, retVal := z.preflightChecks(logger, false, false)
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
|
|
// When there is no token it is already returned
|
|
if !switchInfo.Released {
|
|
logger.Info("device is already returned")
|
|
return rpc.Ok
|
|
}
|
|
return z.writeSwitchRemoveToken(retVal, switchInfo.Token)
|
|
}
|
|
|
|
// configResetActivate request is called when the project is deactivated
|
|
func (z *Zkl3000Rc) configResetActivate() rpc.ErrorCode {
|
|
logger := z.dev.Logger().WithField("action", "config:reset activate")
|
|
return z.activateDeactivateSwitch(logger, false)
|
|
}
|
|
|
|
// configGetToken request is called when the backend wants to know what the token is
|
|
func (z *Zkl3000Rc) configGetToken(rep *rpc.Msg) *rpc.Msg {
|
|
logger := z.dev.Logger().WithField("action", "config:get token")
|
|
switchInfo, rpcerr, err := z.requestSwitchInfo()
|
|
if err != nil {
|
|
logger.WithError(err).Error("cannot read switch information")
|
|
return rep.SetError(rpcerr)
|
|
}
|
|
rep.Result = rpc.NewResult([]*rpc.ResultConfigValueItem{rpc.ConfigToken.Data(switchInfo.Token)})
|
|
return rep
|
|
}
|
|
|
|
// configGetActivate is called when the backend wants to know if the project is active
|
|
func (z *Zkl3000Rc) configGetActivate(rep *rpc.Msg) *rpc.Msg {
|
|
rep.Result = rpc.NewResult([]*rpc.ResultConfigValueItem{rpc.ConfigActive.Data(z.active)})
|
|
return rep
|
|
}
|
|
|
|
// Below are all the child device callbacks
|
|
|
|
// Versions returns the firmware and hardware versions
|
|
func (z *Zkl3000Rc) Versions() (rpc.VersionMap, rpc.ErrorCode) {
|
|
var mcu, wcpu, fwSwitchMeas, fwSwitchDrive string
|
|
var rpcerr rpc.ErrorCode
|
|
if mcu, rpcerr = z.requestVersion("FW-MCU"); rpcerr != rpc.Ok {
|
|
return nil, rpcerr
|
|
}
|
|
if wcpu, rpcerr = z.requestVersion("FW-WCPU"); rpcerr != rpc.Ok {
|
|
return nil, rpcerr
|
|
}
|
|
if fwSwitchMeas, rpcerr = z.requestVersion("FW-SW3000-M"); rpcerr != rpc.Ok {
|
|
return nil, rpcerr
|
|
}
|
|
if fwSwitchDrive, rpcerr = z.requestVersion("FW-SW3000-D"); rpcerr != rpc.Ok {
|
|
return nil, rpcerr
|
|
}
|
|
|
|
// Legacy Zkl3000RC don't have "VersionKeyFwZKLRCSwitchConrol" and "rpc.VersionKeyHwZKLRCSwitch"
|
|
return rpc.VersionMap{
|
|
rpc.VersionKeyHwZKLRCMain: z.hwVersion,
|
|
rpc.VersionKeyFwZKLRCMain: mcu,
|
|
rpc.VersionKeyFwZKLRCWcpu: wcpu,
|
|
rpc.VersionKeyFwZKLRCSwitchMeas: fwSwitchMeas,
|
|
rpc.VersionKeyFwZKLRCSwitchDrive: fwSwitchDrive,
|
|
}, rpc.Ok
|
|
}
|
|
|
|
// TimeoutDuration returns when the device should be considered offline
|
|
func (z *Zkl3000Rc) TimeoutDuration() time.Duration {
|
|
return time.Minute
|
|
}
|
|
|
|
// Transport returns the transportation type
|
|
func (z *Zkl3000Rc) Transport() (interface{}, rpc.ErrorCode) {
|
|
return z.cp3000Transport()
|
|
}
|
|
|
|
// updateDeviceState is called when the device state needs to be updated
|
|
func (z *Zkl3000Rc) updateDeviceState() bool {
|
|
curState := z.dev.GetState()
|
|
switch {
|
|
case !z.switchInfo.Released:
|
|
z.dev.SetState(rpc.DeviceStateIdle)
|
|
case z.active:
|
|
z.dev.SetState(rpc.DeviceStateActive)
|
|
default:
|
|
z.dev.SetState(rpc.DeviceStateArmed)
|
|
}
|
|
return curState != z.dev.GetState()
|
|
}
|
|
|
|
// batteryStateError returns the error code when the device needs to be released or activated.
|
|
// When activation is true, the code expects that the device should be activated, false means release.
|
|
func (z *Zkl3000Rc) batteryStateError(state rpc.BatteryState, activation bool) rpc.ErrorCode {
|
|
switch state {
|
|
case rpc.BatteryStateFull:
|
|
// Valid
|
|
return rpc.Ok
|
|
case rpc.BatteryStateHalf:
|
|
// Should not occur set it on valid
|
|
return rpc.Ok
|
|
case rpc.BatteryStateLow:
|
|
// Should not occur set it on valid
|
|
return rpc.Ok
|
|
case rpc.BatteryStateCritical:
|
|
// Warning
|
|
return rpc.EWrnFirmwareBattery2Critical
|
|
case rpc.BatteryStateEmpty:
|
|
if activation {
|
|
// Warning
|
|
return rpc.EWrnFirmwareBattery2Empty
|
|
}
|
|
// Error
|
|
return rpc.EFirmwareBattery2Empty
|
|
case rpc.BatteryStateRemoved:
|
|
if activation {
|
|
// Warning
|
|
return rpc.EWrnFirmwareBattery2Removed
|
|
}
|
|
return rpc.EFirmwareBattery2Removed
|
|
default:
|
|
// Unknown
|
|
return rpc.EOpdenied
|
|
}
|
|
}
|
|
|
|
// batteryChecks checks if the battery levels are correct
|
|
func (z *Zkl3000Rc) batteryChecks(logger dilog.Logger, up *statusupdate.StatusUpdate, activation bool) rpc.ErrorCode {
|
|
batteryError := z.batteryStateError(up.Battery2State(), activation)
|
|
if batteryError.IsError() {
|
|
logger.WithFields(dilog.Fields{
|
|
"state": up.Battery2State(),
|
|
"battery": 2,
|
|
}).Error("device battery")
|
|
}
|
|
// the warning is the new default return error
|
|
if batteryError.IsWarning() {
|
|
logger.WithFields(dilog.Fields{
|
|
"state": up.Battery2State(),
|
|
"battery": 2,
|
|
}).Warning("device battery")
|
|
}
|
|
return batteryError
|
|
}
|
|
|
|
func (z *Zkl3000Rc) preflightChecks(logger dilog.Logger,
|
|
checkKeyswitch bool, checkDeviceErrors bool) (*statusupdate.StatusUpdate, *SwitchInfo, rpc.ErrorCode) {
|
|
// Always ask for the newest STAT value
|
|
up, retVal := z.requestNewSensorData()
|
|
if retVal != rpc.Ok {
|
|
logger.Error("cannot request new sensor data")
|
|
return nil, nil, retVal
|
|
}
|
|
|
|
// Always ask for the newest switch information
|
|
switchInfo, retVal, err := z.requestSwitchInfo()
|
|
if err != nil {
|
|
logger.WithError(err).Error("cannot read switch information")
|
|
return nil, nil, retVal
|
|
}
|
|
|
|
// Start the preflight checks
|
|
|
|
if up.ErrMCU() {
|
|
// Should never occur. Just log
|
|
logger.Warning("internal device error")
|
|
}
|
|
|
|
if up.ErrSecureHandshake() {
|
|
logger.Warning("secure handshake failed")
|
|
}
|
|
|
|
if checkDeviceErrors && z.dev.HasError() {
|
|
logger.Error("device contains errors")
|
|
return nil, nil, rpc.EFirmwareDeviceContainsErrors
|
|
}
|
|
|
|
if z.dev.GetState() == rpc.DeviceStateService {
|
|
logger.Error("device is in service")
|
|
return nil, nil, rpc.EFirmwareDeviceService
|
|
}
|
|
|
|
if checkKeyswitch && up.KeySwitch() != rpc.KeyswitchStateOperational {
|
|
logger.WithField("keyswitch", up.KeySwitch()).Error("keyswitch is not operational")
|
|
return nil, nil, rpc.EFirmwareKeyswitchNotOperational
|
|
}
|
|
return up, switchInfo, rpc.Ok
|
|
}
|
|
|
|
// StatusUpdatePredicate is called in the function waitForValidSensorData to check if the status update is valid
|
|
type StatusUpdatePredicate func(up *statusupdate.StatusUpdate) bool
|
|
|
|
// waitForValidSensorData requests the latest STAT data and waits until the given predicate is valid or when there is a
|
|
// timeout. When there is a timeout, rpc.EFirmwareNoMeasurement is returned
|
|
func (z *Zkl3000Rc) waitForValidStatusUpdate(f StatusUpdatePredicate) (*statusupdate.StatusUpdate, rpc.ErrorCode) {
|
|
var lastSensorData *statusupdate.StatusUpdate
|
|
|
|
timer := time.NewTimer(zkl3000RcMaximumWaitForMeasurement)
|
|
defer timer.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
z.dev.Logger().Error("didn't received a measurement in time")
|
|
return lastSensorData, rpc.Ok
|
|
default:
|
|
sensorData, rpcErr := z.requestNewSensorData()
|
|
if rpcErr != rpc.Ok {
|
|
return nil, rpcErr
|
|
}
|
|
if f(sensorData) {
|
|
return sensorData, rpc.Ok
|
|
}
|
|
lastSensorData = sensorData
|
|
}
|
|
}
|
|
}
|
|
|
|
// requestNewSensorData requests the latest sensor data
|
|
func (z *Zkl3000Rc) requestNewSensorData() (*statusupdate.StatusUpdate, rpc.ErrorCode) {
|
|
value, dierr := z.requestValue("STAT")
|
|
if dierr != rpc.Ok {
|
|
return nil, dierr
|
|
}
|
|
// Fire the callback, because the router doesn't fire reply messages
|
|
// We drop the errors, because this is an error when we cannot write to the SMP which is not imported for
|
|
// requesting the sensor data
|
|
err := z.statusUpdateCmd(nil, []string{"", value})
|
|
_ = err
|
|
// Grab the current status update of the zkl3000RC
|
|
return z.decoder.StatusUpdate(), rpc.Ok
|
|
}
|
|
|
|
// writeSwitchToken writes the token in the switch. It also checks if the write is successful
|
|
func (z *Zkl3000Rc) writeSwitchToken(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
switchInfo, rpcErr := z.writeSwitch(token, WriteTokenSwichCommand)
|
|
if rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
if !switchInfo.Released {
|
|
z.dev.Logger().Error("token is written, but device replied with no token")
|
|
return rpc.EBackendCp3000CommError
|
|
}
|
|
if switchInfo.Token != token {
|
|
z.dev.Logger().WithField("token", switchInfo.Token).
|
|
Error("token is written, but the device replied with a wrong token")
|
|
return rpc.EBackendCp3000CommError
|
|
}
|
|
lastSensorData, retVal := z.waitForValidStatusUpdate(func(up *statusupdate.StatusUpdate) bool {
|
|
// stop when the device is reporting released
|
|
return up.Released()
|
|
})
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
switch {
|
|
case lastSensorData.ErrSwitch3000():
|
|
z.dev.Logger().Error("switch error")
|
|
return rpc.EFirmwareReleaseSwitchComm
|
|
case !lastSensorData.Released():
|
|
z.dev.Logger().Warning("STAT data says device is not released. DEADLOCK")
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// writeSwitchRemoveToken removes the token from the switch. It also checks if the removal is successful
|
|
func (z *Zkl3000Rc) writeSwitchRemoveToken(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
switchInfo, rpcErr := z.writeSwitch(token, RemoveTokenSwitchCommand)
|
|
if rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
if switchInfo.Released {
|
|
z.dev.Logger().Error("token in switch info is not removed")
|
|
return rpc.EBackendCp3000CommError
|
|
}
|
|
lastSensorData, retVal := z.waitForValidStatusUpdate(func(up *statusupdate.StatusUpdate) bool {
|
|
// stop when the device is reporting not released
|
|
return !up.Released()
|
|
})
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
if lastSensorData.Released() {
|
|
z.dev.Logger().Warning("STAT data is still released. DEADLOCK")
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// activateDeactivateSwitch activates of deactivates the short circuit. It also checks if the change is successful.
|
|
func (z *Zkl3000Rc) activateDeactivateSwitch(logger dilog.Logger, enable bool) rpc.ErrorCode {
|
|
up, switchInfo, retVal := z.preflightChecks(logger, true, enable)
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
|
|
// When there is no token, deny the transaction
|
|
if !switchInfo.Released {
|
|
logger.Error("switch info has no token")
|
|
return rpc.EFirmwareDeviceIdle
|
|
}
|
|
|
|
// Only do battery checks during activate
|
|
if enable {
|
|
retVal = z.batteryChecks(logger, up, true)
|
|
if retVal.IsError() {
|
|
return retVal
|
|
}
|
|
}
|
|
|
|
if !up.Released() {
|
|
// If this occur we have a serious problem. We are in a deadlock, so just ignore it...
|
|
logger.Warning("STAT data is not released. DEADLOCK")
|
|
}
|
|
|
|
if up.SwitchShort() && enable {
|
|
logger.WithField("status", up.SwitchShort).Info("switch already changed")
|
|
}
|
|
|
|
if enable {
|
|
// Turn short circuit on
|
|
retVal = z.turnMeasurementOn(retVal, switchInfo.Token)
|
|
retVal = z.turnSwitchOn(retVal, switchInfo.Token)
|
|
} else {
|
|
// Turn short circuit off
|
|
retVal = z.turnSwitchOff(retVal, switchInfo.Token)
|
|
retVal = z.turnMeasurementOff(retVal, switchInfo.Token)
|
|
}
|
|
if !retVal.IsError() {
|
|
z.active = enable
|
|
// possible transition from armed <--> active
|
|
z.updateDeviceState()
|
|
}
|
|
return retVal
|
|
}
|
|
|
|
// turnMeasurementOn turns the measurement on. It also checks if the measurement is indeed on.
|
|
func (z *Zkl3000Rc) turnMeasurementOn(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
if _, rpcErr := z.writeSwitch(token, MeasurementOnSwitchCommand); rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
lastSensorData, retVal := z.waitForValidStatusUpdate(func(up *statusupdate.StatusUpdate) bool {
|
|
// stop when the measurement is indeed on or when the BA-value is not 0
|
|
return up.Measurement() || up.Ba() != 0
|
|
})
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
if !lastSensorData.SwitchShort() && lastSensorData.DetectionStatus() {
|
|
z.dev.Logger().Warning("short already presented while activating")
|
|
return rpc.EWrnFirmwareActivateShortPresent
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// turnSwitchOn turns the short circuit on. It also checks if the short circuit is indeed on.
|
|
func (z *Zkl3000Rc) turnSwitchOn(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
if _, rpcErr := z.writeSwitch(token, ShortCircuitOnSwitchCommand); rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
lastSensorData, retVal := z.waitForValidStatusUpdate(func(up *statusupdate.StatusUpdate) bool {
|
|
// stop when the short is on and we have a valid detection
|
|
return up.SwitchShort() && up.DetectionStatus()
|
|
})
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
switch {
|
|
case lastSensorData.ErrSwitch3000():
|
|
z.dev.Logger().Error("switch error")
|
|
return rpc.EFirmwareActivateSwitchComm
|
|
case !lastSensorData.SwitchShort():
|
|
z.dev.Logger().Error("switch short circuit is not on")
|
|
return rpc.EFirmwareActivateShortNotEnabled
|
|
case !lastSensorData.DetectionStatus():
|
|
z.dev.Logger().Warning("detection is not ok")
|
|
return rpc.EWrnFirmwareActivateDetectionNok
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// turnSwitchOn turns the short circuit off. It also checks if the short circuit is indeed off.
|
|
func (z *Zkl3000Rc) turnSwitchOff(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
if _, rpcErr := z.writeSwitch(token, ShortCircuitOffSwitchCommand); rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
lastSensorData, retVal := z.waitForValidStatusUpdate(func(up *statusupdate.StatusUpdate) bool {
|
|
// stop when the short is off and we have an invalid detection
|
|
return !up.SwitchShort() && !up.DetectionStatus()
|
|
})
|
|
if retVal != rpc.Ok {
|
|
return retVal
|
|
}
|
|
switch {
|
|
case lastSensorData.ErrSwitch3000():
|
|
z.dev.Logger().Error("switch error")
|
|
return rpc.EFirmwareDeactivateSwitchComm
|
|
case lastSensorData.SwitchShort():
|
|
z.dev.Logger().Error("switch short circuit is on")
|
|
return rpc.EFirmwareDeactivateShortEnabled
|
|
case lastSensorData.DetectionStatus():
|
|
z.dev.Logger().Warning("short still presented while deactivating")
|
|
return rpc.EWrnFirmwareDeactivateShortPresent
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// turnMeasurementOff turns the measurement off. It also checks if the measurement is indeed off.
|
|
func (z *Zkl3000Rc) turnMeasurementOff(currErr rpc.ErrorCode, token uint32) rpc.ErrorCode {
|
|
if currErr.IsError() {
|
|
// There was already an error. Don't proceed
|
|
return currErr
|
|
}
|
|
if _, rpcErr := z.writeSwitch(token, MeasurementOffSwitchCommand); rpcErr != rpc.Ok {
|
|
z.dev.Logger().Error("communication to switch failed")
|
|
return rpcErr
|
|
}
|
|
return currErr
|
|
}
|
|
|
|
// writeSwitch writes the command to the switch. The returned switch information is just for verification and can be
|
|
// ignored
|
|
func (z *Zkl3000Rc) writeSwitch(token uint32, code SwitchCommand) (*SwitchInfo, rpc.ErrorCode) {
|
|
logger := z.dev.Logger().WithField("cmd", fmt.Sprintf("%X,%d", code, token))
|
|
logger.Debug("sending switch command to device")
|
|
// Write to the switch
|
|
if err := z.client.Send(cp3000.CommandStore, "SWITCH", fmt.Sprintf("%X,%d", code, token)); err != nil {
|
|
logger.Error("cannot write to switch")
|
|
return nil, rpc.EBackendCp3000CommError
|
|
}
|
|
// Check if we receive something to check the connection
|
|
switchInfo, rpcerr, err := z.requestSwitchInfo()
|
|
if err != nil {
|
|
logger.WithError(err).Error("cannot receive switch info")
|
|
return nil, rpcerr
|
|
}
|
|
return switchInfo, rpc.Ok
|
|
}
|
|
|
|
// requestSwitchInfo requests the latest switch information. This contains the device token and the status
|
|
func (z *Zkl3000Rc) requestSwitchInfo() (*SwitchInfo, rpc.ErrorCode, error) {
|
|
if err := z.client.Send(cp3000.CommandStore, "SWITCH", fmt.Sprintf("%X", SwitchStatusCommand)); err != nil {
|
|
z.dev.Logger().WithError(err).Error("cannot write SwitchStatusCommand to device during requestSwitchInfo")
|
|
return nil, rpc.EBackendCp3000CommError, err
|
|
}
|
|
|
|
var switchInfo *SwitchInfo
|
|
switchRequestRetries := 1
|
|
switchRetryAttempt := 0
|
|
|
|
for {
|
|
msg, rpcerr := z.cp3000ReqRep(DefaultCommandTimeout, cp3000.CommandRetrieve, "SWITCH")
|
|
if rpcerr != rpc.Ok {
|
|
return nil, rpcerr, errors.New("cannot request reply")
|
|
}
|
|
si, err := NewSwitchInfo(msg.Params)
|
|
if err != nil {
|
|
return nil, rpc.EIofailed, err
|
|
}
|
|
|
|
if si.Valid() {
|
|
switchInfo = si
|
|
break
|
|
}
|
|
|
|
// Always suppress the first retry because the ZKL 3000 RC SWCMD_STATUS takes a second try
|
|
// as the valid status is async refreshed, next call it is normally valid
|
|
if switchRetryAttempt > 0 {
|
|
z.dev.Logger().WithField("cp3000-reply", msg.String()).Warning(
|
|
"switch status retrying request, due to previous incorrect status")
|
|
}
|
|
|
|
// Switch malfunction is only retried once
|
|
if switchRequestRetries == 0 {
|
|
return nil, rpc.EBackendCp3000CommError, errZKL3000RcSwitchStatsRetriesExceeded
|
|
}
|
|
|
|
if si.Malfunction() {
|
|
z.dev.Logger().Warning("switch status is malfunction")
|
|
switchRequestRetries--
|
|
}
|
|
|
|
// Wait in an exponential backoff fashion
|
|
time.Sleep(DefaultBackoffPolicy.Duration(switchRetryAttempt))
|
|
switchRetryAttempt++
|
|
}
|
|
|
|
// Device is released/returned
|
|
if switchInfo.Changed(z.switchInfo) {
|
|
z.switchInfo = switchInfo
|
|
if switchInfo.Released {
|
|
z.dev.Logger().WithField("token", switchInfo.Token).Info("new token value")
|
|
} else {
|
|
z.dev.Logger().WithField("token", nil).Info("new token value")
|
|
}
|
|
// possible transition from idle <--> armed
|
|
if z.updateDeviceState() {
|
|
z.dev.Logger().Info("send device:data")
|
|
if err := z.dev.SendDeviceData(); err != nil {
|
|
return switchInfo, rpc.EBackendCp3000CommError, err
|
|
}
|
|
}
|
|
}
|
|
return switchInfo, rpc.Ok, nil
|
|
}
|
|
|
|
// continueHeartbeats continues to send heartbeats every 1s for 7s
|
|
func (z *Zkl3000Rc) continueHeartbeats() {
|
|
ticker := time.NewTicker(time.Second)
|
|
after := time.After(7 * time.Second)
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if err := z.dev.SendDeviceData(); err != nil {
|
|
z.dev.Logger().WithError(err).Error("failed sending device:data")
|
|
}
|
|
case <-after:
|
|
// escape from the for-loop
|
|
return
|
|
}
|
|
}
|
|
}
|