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 }