src.dualinventive.com/go/cp3000-interface/internal/statusupdate/status_update.go

551 lines
19 KiB
Go

package statusupdate
import (
"math"
"time"
"src.dualinventive.com/go/dinet/ditime"
"src.dualinventive.com/go/dinet/rpc"
)
// StatusUpdate is a status-update via CP3000
type StatusUpdate struct {
MCUState uint16
RCState uint16
SwitchState uint16
MCULocalState uint16
NATWSCounter uint64
NATWSDirection int64
BACalibrated float64
Frequency int64
RMS uint64
BAActual float64
BatSelected int64
Battery1 float64
Battery2 float64
TempOnboard float64
TempNTC float64
GSMRSSI int64
GSMBER int64
GPSLatitude float64
GPSLongitude float64
GPSAltitude float64
GPSHDOP float64
GPSSpeed float64
GPSHeading float64
GPSTime time.Time
Tilt struct {
X float64
Y float64
Z float64
}
WCPUState uint32
MCUPersistent uint32
GPSFIX GpsFixType
Fields StatUpdateField
}
// GpsFixType is the type of Fix (or no fix) by a device
type GpsFixType byte
const (
// GpsFixTypeAutonomous autonomous fix
GpsFixTypeAutonomous GpsFixType = 'A'
// GpsFixTypeStationary stationary fix
GpsFixTypeStationary GpsFixType = 'S'
// GpsFixTypeDifferential differential fix
GpsFixTypeDifferential GpsFixType = 'D'
// GpsFixTypeEstimated estimated fix
GpsFixTypeEstimated GpsFixType = 'E'
// GpsFixTypeInitFromMCU fix from MCU config
GpsFixTypeInitFromMCU GpsFixType = 'I'
// GpsFixTypeInvalid no fix
GpsFixTypeInvalid GpsFixType = 'N'
// GpsFixTypeUninitialized no fix (uninitialized)
GpsFixTypeUninitialized GpsFixType = 'U'
)
// StatUpdateField is a mask which indicates which field is updated in the $UPDT status
type StatUpdateField uint32
const (
// StatUpdateAll all fields
StatUpdateAll StatUpdateField = 0xFFFFFFFF
// StatUpdateZKL3000 all ZKL 3000 fields
StatUpdateZKL3000 StatUpdateField = 0xFFFFFFF3
// StatUpdateState system state
StatUpdateState StatUpdateField = 0x0001
// StatUpdateWcpuState modem state (or communication device in general)
StatUpdateWcpuState StatUpdateField = 0x0002
// StatUpdatePMGT power management
StatUpdatePMGT StatUpdateField = 0x0004
// StatUpdateSwitch switch state
StatUpdateSwitch StatUpdateField = 0x0008
// StatUpdateRMS measurement, RMS^2
StatUpdateRMS StatUpdateField = 0x0010
// StatUpdateBA measurement, b/a
StatUpdateBA StatUpdateField = 0x0020
// StatUpdateNATWS measurement, detection counter and direction
StatUpdateNATWS StatUpdateField = 0x0040
// StatUpdateAutocal measurement, calibrated b/a and frequency
StatUpdateAutocal StatUpdateField = 0x0080
// StatUpdateBattSel battery selection
StatUpdateBattSel StatUpdateField = 0x0800
// StatUpdateBatt1Level battery #1 level (V)
StatUpdateBatt1Level StatUpdateField = 0x0100
// StatUpdateBatt2Level battery #2 level (V)
StatUpdateBatt2Level StatUpdateField = 0x0200
// StatUpdateIntTemp internal temperature sensor (C; on-board sensor on ZKL3000)
StatUpdateIntTemp StatUpdateField = 0x1000
// StatUpdateExtTemp external temperature sensor(s) (C; external NTC on ZKL3000)
StatUpdateExtTemp StatUpdateField = 0x2000
// StatUpdateGPS GPS co-ordinates and additional info
StatUpdateGPS StatUpdateField = 0x4000
// StatUpdateGSM GSM RSSI and BER
StatUpdateGSM StatUpdateField = 0x8000
// StatUpdateTime time field present
StatUpdateTime StatUpdateField = 0x00010000
// StatUpdateTimeSource time source present in time field (internal use)
StatUpdateTimeSource StatUpdateField = 0x00020000
// StatUpdateSW3000Temperature temperature sensor of the Switch 3000
StatUpdateSW3000Temperature StatUpdateField = 0x00100000
// StatUpdateTilt tilt sensor
StatUpdateTilt StatUpdateField = 0x00200000
// StatUpdateVibration vibration sensor
StatUpdateVibration StatUpdateField = 0x00400000
)
const voltageDiff = 0.001
func mustSend(s *StatusUpdate, field StatUpdateField) bool {
return (s.Fields & field) == field
}
// Battery1Voltage returns the Battery1 voltage
func (s *StatusUpdate) Battery1Voltage() float64 {
return s.Battery1
}
// Battery1State returns the Battery1 state
func (s *StatusUpdate) Battery1State() rpc.BatteryState {
return decodeBatState((s.MCUState & 0x0F00) >> 8)
}
// Battery2Voltage returns the Battery2 voltage
func (s *StatusUpdate) Battery2Voltage() float64 {
return s.Battery2
}
// Battery2State returns the Battery2 state
func (s *StatusUpdate) Battery2State() rpc.BatteryState {
return decodeBatState((s.MCUState & 0x0F000) >> 12)
}
// Charger1State returns the charger 1 state
func (s *StatusUpdate) Charger1State() rpc.ChargerState {
// If the pm3000 charging bit is set, the pm3000 is charging
if s.RCState&0x04 == 0x04 {
return rpc.ChargerStateCharging
}
// The pm3000 is not charging. If bat2 is available, there is a charger.
switch s.Battery2State() {
case rpc.BatteryStateFull, rpc.BatteryStateHalf, rpc.BatteryStateLow, rpc.BatteryStateCritical, rpc.BatteryStateEmpty:
return rpc.ChargerStateConnected
default:
return rpc.ChargerStateDisconnected
}
}
// GPS returns the GPS position and HDOP
func (s *StatusUpdate) GPS() rpc.GPSSensorData {
return rpc.GPSSensorData{
HDOP: s.GPSHDOP,
Latitude: s.GPSLatitude,
Longitude: s.GPSLongitude,
}
}
// RSSI returns the RSSI
func (s *StatusUpdate) RSSI() int64 {
return s.GSMRSSI
}
// BER returns the BER
func (s *StatusUpdate) BER() int64 {
return s.GSMBER
}
// DetectionQuality returns the detection quality
func (s *StatusUpdate) DetectionQuality() float64 {
val := (100 * (1 - (math.Pow(s.BAActual-0.125, 4) / math.Pow(s.BACalibrated-0.125, 4))))
if val < 0 {
return 0
}
return val
}
// DetectionStatus returns if the detection is OK
func (s *StatusUpdate) DetectionStatus() bool {
return s.MCUState&0x2 == 0x2
}
// RelayOpen is a reused bitfield which indicates the rail-contact during sleep
func (s *StatusUpdate) RelayOpen() bool {
return s.MCUState&0x8 == 0x8
}
// Measurement returns if the measurement is on
func (s *StatusUpdate) Measurement() bool {
return s.MCUState&0x1 == 0x1
}
// Ba returns the Ba
func (s *StatusUpdate) Ba() float64 {
return s.BAActual
}
// Freq returns the Frequency
func (s *StatusUpdate) Freq() int64 {
return s.Frequency
}
// SectionsShort returns which sections have a short
func (s *StatusUpdate) SectionsShort() rpc.SectionsShort {
return decodeSWBatteryShort(s.SwitchState & 0xF)
}
// SectionsBattery returns which sections have battery power
func (s *StatusUpdate) SectionsBattery() rpc.SectionsShort {
return decodeSWBatteryShort((s.SwitchState & 0xF00) >> 8)
}
// SwitchShort returns if the short is on
func (s *StatusUpdate) SwitchShort() bool {
// This checks if sections 1 and 4 or 2 and 3 are shorted. When either of this
// is true, the switch is on
return (s.SwitchState&0x9 == 0x9) || (s.SwitchState&0x6 == 0x6)
}
// KeySwitch returns the key switch state
func (s *StatusUpdate) KeySwitch() rpc.KeyswitchState {
switch s.RCState & 0xC {
case 0x0: // operational
return rpc.KeyswitchStateOperational
case 0x4: // on
return rpc.KeyswitchStateOn
case 0x8: // off
return rpc.KeyswitchStateOff
}
return rpc.KeyswitchStateUnknown
}
// CRTMTemperature1 returns the Temperature1 for the CRTM
func (s *StatusUpdate) CRTMTemperature1() float64 {
return s.TempOnboard
}
// CRTMTemperature2 returns the Temperature2 for the CRTM
func (s *StatusUpdate) CRTMTemperature2() float64 {
return s.TempNTC
}
// Released returns true when the switch is released. This is set when the token is stored successfully
func (s *StatusUpdate) Released() bool {
return s.MCUPersistent&0x80 == 0x80
}
// ErrSecureHandshake returns true when the security check is not passed (programming of the device is locked)
func (s *StatusUpdate) ErrSecureHandshake() bool {
return s.WCPUState&0x1 == 0x1
}
// ErrMeasurementFailed returns if there is a measurement error
func (s *StatusUpdate) ErrMeasurementFailed() bool {
return s.MCUState&0x4 == 0x4
}
// ErrUARTMcu returns if there is an UART MCU error
func (s *StatusUpdate) ErrUARTMcu() bool {
return s.WCPUState&0x10 == 0x10
}
// ErrUARTGPS returns if there is an UART GPS error
func (s *StatusUpdate) ErrUARTGPS() bool {
return s.WCPUState&0x20 == 0x20
}
// ErrMCU returns if there is a MCU error
func (s *StatusUpdate) ErrMCU() bool {
return s.WCPUState&0x80 == 0x80
}
// ErrSwitch3000Timeout returns if there is a switch 3000 timeout
func (s *StatusUpdate) ErrSwitch3000Timeout() bool {
return s.SwitchState&0x10 == 0x10
}
// ErrSwitch3000 returns if there is a switch 3000 error
func (s *StatusUpdate) ErrSwitch3000() bool {
return s.SwitchState&0x20 == 0x20
}
func decodeBatState(state uint16) rpc.BatteryState {
switch state {
case 0x00: // ok
return rpc.BatteryStateFull
case 0x01: // alarm
return rpc.BatteryStateCritical
case 0x03: // leeg
return rpc.BatteryStateEmpty
case 0x07: // verwijderd
return rpc.BatteryStateRemoved
}
return rpc.BatteryState(-1)
}
func decodeSWBatteryShort(data uint16) rpc.SectionsShort {
var sections rpc.SectionsShort
sections.State = ((data & 0xF) == 0xF)
sections.Section1 = ((data & 0x1) == 0x1)
sections.Section2 = ((data & 0x2) == 0x2)
sections.Section3 = ((data & 0x4) == 0x4)
sections.Section4 = ((data & 0x8) == 0x8)
return sections
}
// CheckBattery1 checks if Battery1 is changed and appends the sensor-value
func (s *StatusUpdate) CheckBattery1(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if math.Abs(s.Battery1Voltage()-n.Battery1Voltage()) >= voltageDiff || mustSend(s, StatUpdateBatt1Level) {
return append(data, rpc.SensorBattery1Voltage.Data(n.Battery1Voltage()))
}
return data
}
// CheckBattery1State checks if the state of Battery1 is changed and appends the sensor-value
func (s *StatusUpdate) CheckBattery1State(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sBat1State := s.Battery1State()
nBat1State := n.Battery1State()
if sBat1State != nBat1State || mustSend(s, StatUpdateState) {
return append(data, rpc.SensorBattery1State.Data(nBat1State))
}
return data
}
// CheckBattery2 checks if Battery2 is changed and appends the sensor-value
func (s *StatusUpdate) CheckBattery2(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if math.Abs(s.Battery2-n.Battery2) >= voltageDiff || mustSend(s, StatUpdateBatt2Level) {
return append(data, rpc.SensorBattery2Voltage.Data(n.Battery2))
}
return data
}
// CheckBattery2State checks if the state of Battery2 is changed and appends the sensor-value
func (s *StatusUpdate) CheckBattery2State(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sBat2State := s.Battery2State()
nBat2State := n.Battery2State()
if sBat2State != nBat2State || mustSend(s, StatUpdateState) {
return append(data, rpc.SensorBattery2State.Data(nBat2State))
}
return data
}
// CheckCharger1State checks if the charger1 state is changed and appends the sensor-value
func (s *StatusUpdate) CheckCharger1State(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sChargerState := s.Charger1State()
nChargerState := n.Charger1State()
if sChargerState != nChargerState || mustSend(s, StatUpdatePMGT) || mustSend(s, StatUpdateState) {
return append(data, rpc.SensorCharger1State.Data(nChargerState))
}
return data
}
// CheckGPS checks if GPS data is changed, has a fix and appends the sensor value
func (s *StatusUpdate) CheckGPS(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if n.GPSFIX != GpsFixTypeAutonomous && n.GPSFIX != GpsFixTypeStationary &&
n.GPSFIX != GpsFixTypeDifferential && n.GPSFIX != GpsFixTypeEstimated {
return data
}
if s.GPSTime != n.GPSTime || mustSend(s, StatUpdateGPS) {
return append(data, rpc.SensorGPS.Data(n.GPS(), ditime.FromTime(n.GPSTime)))
}
return data
}
// CheckRSSI checks if the RSSI is changed and appends the sensor-value
func (s *StatusUpdate) CheckRSSI(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.RSSI() != n.RSSI() || mustSend(s, StatUpdateGSM) {
return append(data, rpc.SensorRSSI.Data(n.RSSI()))
}
return data
}
// CheckBER checks if the BER is changed and appends the sensor-value
func (s *StatusUpdate) CheckBER(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.BER() != n.BER() || mustSend(s, StatUpdateGSM) {
return append(data, rpc.SensorBER.Data(n.BER()))
}
return data
}
// CheckDetectionQuality checks if the detection quality is changed and appends the sensor-value
func (s *StatusUpdate) CheckDetectionQuality(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sQuality := s.DetectionQuality()
nQuality := n.DetectionQuality()
if (math.Abs(sQuality-nQuality) >= 0.01) || mustSend(s, StatUpdateBA) || mustSend(s, StatUpdateAutocal) {
return append(data, rpc.ZKLSensorDetectionQuality.Data(nQuality))
}
return data
}
// CheckDetectionStatus checks if the status of the detection is changed and appends the sensor-value
func (s *StatusUpdate) CheckDetectionStatus(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.DetectionStatus() != n.DetectionStatus() || mustSend(s, StatUpdateState) {
return append(data, rpc.ZKLSensorDetection.Data(n.DetectionStatus()))
}
return data
}
// CheckMeasurement checks if the measurement state is changed and appends the sensor-value
func (s *StatusUpdate) CheckMeasurement(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.Measurement() != n.Measurement() || mustSend(s, StatUpdateState) {
return append(data, rpc.ZKLSensorMeasurement.Data(n.Measurement()))
}
return data
}
// CheckBAActual checks if the current B/A value is changed and appends the sensor value
func (s *StatusUpdate) CheckBAActual(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if (math.Abs(s.Ba()-n.Ba()) >= 0.001) || mustSend(s, StatUpdateBA) {
return append(data, rpc.ZKLSensorBa.Data(n.Ba()))
}
return data
}
// CheckFrequency checks if the Frequency is changed and appends the sensor-value
func (s *StatusUpdate) CheckFrequency(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.Freq() != n.Freq() || mustSend(s, StatUpdateAutocal) {
return append(data, rpc.ZKLSensorFrequency.Data(n.Freq()))
}
return data
}
// CheckSWShort checks if the switch state of the short is changed and appends the sensor-value
func (s *StatusUpdate) CheckSWShort(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sShort := s.SectionsShort()
nShort := n.SectionsShort()
if sShort != nShort || mustSend(s, StatUpdateSwitch) {
return append(data, rpc.ZKLRCSensorSectionsShort.Data(nShort))
}
return data
}
// CheckSWBattery checks if the switch battery state is changed and appends the sensor-value
func (s *StatusUpdate) CheckSWBattery(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sBattery := s.SectionsBattery()
nBattery := n.SectionsBattery()
if sBattery != nBattery || mustSend(s, StatUpdateSwitch) {
return append(data, rpc.ZKLRCSensorSectionsBattery.Data(nBattery))
}
return data
}
// CheckSwitchState checks if the short-circuit is changed and appends the sensor-value
func (s *StatusUpdate) CheckSwitchState(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.SwitchShort() != n.SwitchShort() || mustSend(s, StatUpdatePMGT) {
return append(data, rpc.ZKLRCSensorSwitchShort.Data(n.SwitchShort()))
}
return data
}
// CheckKeyswitch checks if the charger1 state is changed and appends the sensor-value
func (s *StatusUpdate) CheckKeyswitch(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
sSwitch := s.KeySwitch()
nSwitch := n.KeySwitch()
if sSwitch != nSwitch || mustSend(s, StatUpdatePMGT) {
return append(data, rpc.ZKLRCSensorKeySwitch.Data(nSwitch))
}
return data
}
// CheckRMS checks if the RMS is changed and appends the sensor-value
func (s *StatusUpdate) CheckRMS(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.RMS != n.RMS || mustSend(s, StatUpdateRMS) {
return append(data, rpc.ZKLSensorRMS.Data(n.RMS))
}
return data
}
// CheckBAAutocal checks if the BAAutoCal is changed and appends the sensor-value
func (s *StatusUpdate) CheckBAAutocal(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.BACalibrated != n.BACalibrated || mustSend(s, StatUpdateAutocal) {
return append(data, rpc.ZKLSensorBAAutocal.Data(n.BACalibrated))
}
return data
}
// CheckCRTMTemperature1 checks if the temperature is changed and appends the sensor-value
func (s *StatusUpdate) CheckCRTMTemperature1(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
return s.CheckTempOnboard(data, n, &rpc.CRTMSensorTemperature1)
}
// CheckCRTMTemperature2 checks if the temperature is changed and appends the sensor-value
func (s *StatusUpdate) CheckCRTMTemperature2(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
return s.CheckTempNTC(data, n, &rpc.CRTMSensorTemperature2)
}
// CheckTempOnboard checks if the temperature is changed and appends the sensor-value
func (s *StatusUpdate) CheckTempOnboard(data []*rpc.ResultValueItem, n *StatusUpdate,
sensor *rpc.Sensor) []*rpc.ResultValueItem {
if s.TempOnboard != n.TempOnboard || mustSend(s, StatUpdateIntTemp) {
return append(data, sensor.Data(n.TempOnboard))
}
return data
}
// CheckTempNTC checks if the temperature is changed and appends the sensor-value
func (s *StatusUpdate) CheckTempNTC(data []*rpc.ResultValueItem, n *StatusUpdate,
sensor *rpc.Sensor) []*rpc.ResultValueItem {
if s.TempNTC != n.TempNTC || mustSend(s, StatUpdateExtTemp) {
return append(data, sensor.Data(n.TempNTC))
}
return data
}
// CheckRailContact checks if the rail contact bit is set and appends a sensor value
func (s *StatusUpdate) CheckRailContact(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.DetectionStatus() != n.DetectionStatus() || mustSend(s, StatUpdateState) {
return append(data, rpc.CRTMSensorRailContact.Data(n.DetectionStatus()))
}
return data
}
// CheckRailContactSleep checks if the rail contact sleep bit is set and appends a sensor value
func (s *StatusUpdate) CheckRailContactSleep(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
if s.RelayOpen() != n.RelayOpen() || mustSend(s, StatUpdateState) {
return append(data, rpc.CRTMSensorRailContactSleep.Data(n.RelayOpen()))
}
return data
}
// CheckTilt checks if the tilt-data is present and appends the sensor value
func (s *StatusUpdate) CheckTilt(data []*rpc.ResultValueItem, n *StatusUpdate) []*rpc.ResultValueItem {
// Translate all values to milli-G's
oldX := int32(s.Tilt.X * 1000)
oldY := int32(s.Tilt.Y * 1000)
oldZ := int32(s.Tilt.Z * 1000)
newX := int32(n.Tilt.X * 1000)
newY := int32(n.Tilt.Y * 1000)
newZ := int32(n.Tilt.Z * 1000)
if oldX != newX || oldY != newY || oldZ != newZ || mustSend(s, StatUpdateTilt) {
return append(data, rpc.CRTMSensorAcceleration.Data(map[string]int32{
"X": newX,
"Y": newY,
"Z": newZ,
}))
}
return data
}