326 lines
10 KiB
Go
326 lines
10 KiB
Go
package statusupdate
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"src.dualinventive.com/go/lib/dilog"
|
|
)
|
|
|
|
// Decoder decodes cp3000 status/update messages.
|
|
type Decoder interface {
|
|
Decode(args []string) error
|
|
StatusUpdate() *StatusUpdate
|
|
}
|
|
|
|
func decodeFloat(logger dilog.Logger, field *float64, fields, mask StatUpdateField, records []string) []string {
|
|
if (fields&mask) == mask && len(records) > 0 {
|
|
var err error
|
|
if len(records[0]) == 0 {
|
|
return records[1:]
|
|
}
|
|
*field, err = strconv.ParseFloat(records[0], 64)
|
|
if err != nil {
|
|
logger.WithField("value", records[0]).Error("cannot decode float")
|
|
}
|
|
return records[1:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
// nolint: dupl
|
|
func decodeInt(logger dilog.Logger, field *int64, fields, mask StatUpdateField, records []string) []string {
|
|
if (fields&mask) == mask && len(records) > 0 {
|
|
var err error
|
|
if len(records[0]) == 0 {
|
|
return records[1:]
|
|
}
|
|
*field, err = strconv.ParseInt(records[0], 10, 64)
|
|
if err != nil {
|
|
logger.WithField("value", records[0]).Error("cannot decode int")
|
|
}
|
|
return records[1:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
// nolint: dupl
|
|
func decodeUint(logger dilog.Logger, field *uint64, fields, mask StatUpdateField, records []string) []string {
|
|
if (fields&mask) == mask && len(records) > 0 {
|
|
var err error
|
|
if len(records[0]) == 0 {
|
|
return records[1:]
|
|
}
|
|
*field, err = strconv.ParseUint(records[0], 10, 64)
|
|
if err != nil {
|
|
logger.WithField("value", records[0]).Error("cannot decode uint")
|
|
}
|
|
return records[1:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeHex16(logger dilog.Logger, field *uint16, fields, mask StatUpdateField, records []string) []string {
|
|
if (fields&mask) == mask && len(records) > 0 {
|
|
var err error
|
|
if len(records[0]) == 0 {
|
|
return records[1:]
|
|
}
|
|
r, err := strconv.ParseUint(records[0], 16, 16)
|
|
if err == nil {
|
|
*field = uint16(r)
|
|
} else {
|
|
logger.WithField("value", records[0]).Error("cannot decode hex16")
|
|
}
|
|
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeWCPUState(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
if (fields&StatUpdateWcpuState) == StatUpdateWcpuState && len(records) > 0 {
|
|
if len(records[0]) == 0 {
|
|
return records[1:]
|
|
}
|
|
i, err := strconv.ParseInt(records[0], 16, 64)
|
|
if err == nil {
|
|
stat.WCPUState = uint32(i)
|
|
} else {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "WCPUState",
|
|
"value": records[0],
|
|
}).Error("cannot decode uint32")
|
|
}
|
|
return records[1:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeBatSelected(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "BatSelected")
|
|
return decodeInt(l, &stat.BatSelected, fields, StatUpdateBattSel, records)
|
|
}
|
|
|
|
func decodeBatt1Level(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "Battery1")
|
|
return decodeFloat(l, &stat.Battery1, fields, StatUpdateBatt1Level, records)
|
|
}
|
|
|
|
func decodeBatt2Level(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "Battery2")
|
|
return decodeFloat(l, &stat.Battery2, fields, StatUpdateBatt2Level, records)
|
|
}
|
|
|
|
func decodePMGT(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "RCState")
|
|
return decodeHex16(l, &stat.RCState, fields, StatUpdatePMGT, records)
|
|
}
|
|
|
|
func decodeSwitch(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "SwitchState")
|
|
return decodeHex16(l, &stat.SwitchState, fields, StatUpdateSwitch, records)
|
|
}
|
|
|
|
func decodeTempOnBoard(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "TempOnboard")
|
|
return decodeFloat(l, &stat.TempOnboard, fields, StatUpdateIntTemp, records)
|
|
}
|
|
|
|
func decodeTempNTC(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
if (fields&StatUpdateExtTemp) != StatUpdateExtTemp || len(records) == 0 {
|
|
return records
|
|
}
|
|
|
|
f := strings.Split(records[0], "/")
|
|
if len(f) > 0 {
|
|
l := logger.WithField("field", "TempNTC")
|
|
r := decodeFloat(l, &stat.TempNTC, fields, StatUpdateExtTemp, f)
|
|
l = logger.WithField("field", "TempOnboard (NTC)")
|
|
decodeFloat(l, &stat.TempOnboard, fields, StatUpdateExtTemp, r)
|
|
return records[1:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeGSM(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "GSMRSSI")
|
|
r := decodeInt(l, &stat.GSMRSSI, fields, StatUpdateGSM, records)
|
|
l = logger.WithField("field", "GSMBER")
|
|
return decodeInt(l, &stat.GSMBER, fields, StatUpdateGSM, r)
|
|
}
|
|
|
|
func decodeRMS(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "RMS")
|
|
return decodeUint(l, &stat.RMS, fields, StatUpdateRMS, records)
|
|
}
|
|
|
|
func decodeBA(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "BAActual")
|
|
return decodeFloat(l, &stat.BAActual, fields, StatUpdateBA, records)
|
|
}
|
|
|
|
func decodeNATWS(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "NATWSCounter")
|
|
r := decodeUint(l, &stat.NATWSCounter, fields, StatUpdateNATWS, records)
|
|
l = logger.WithField("field", "NATWSDirection")
|
|
return decodeInt(l, &stat.NATWSDirection, fields, StatUpdateNATWS, r)
|
|
}
|
|
|
|
func decodeMCUPersistent(logger dilog.Logger, stat *StatusUpdate, field string) {
|
|
if len(field) == 0 {
|
|
return
|
|
}
|
|
r, err := strconv.ParseUint(field, 16, 32)
|
|
if err == nil {
|
|
stat.MCUPersistent = uint32(r)
|
|
} else {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "MCUPersistent",
|
|
"value": field,
|
|
}).Error("cannot decode uint32")
|
|
}
|
|
}
|
|
|
|
func decodeAutocal(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "BACalibrated")
|
|
r := decodeFloat(l, &stat.BACalibrated, fields, StatUpdateAutocal, records)
|
|
l = logger.WithField("field", "Frequency")
|
|
return decodeInt(l, &stat.Frequency, fields, StatUpdateAutocal, r)
|
|
}
|
|
|
|
func parseMCUState(logger dilog.Logger, stat *StatusUpdate, field string) {
|
|
var err error
|
|
if len(field) == 0 {
|
|
return
|
|
}
|
|
r, err := strconv.ParseUint(field, 16, 16)
|
|
if err == nil {
|
|
stat.MCUState = uint16(r)
|
|
} else {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "MCUState",
|
|
"value": field,
|
|
}).Error("cannot decode uint16")
|
|
}
|
|
}
|
|
|
|
func decodeState(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
if (fields&StatUpdateState) == StatUpdateState && len(records) > 0 {
|
|
fields := strings.Split(records[0], "/")
|
|
if len(fields) >= 1 {
|
|
parseMCUState(logger, stat, fields[0])
|
|
}
|
|
if len(fields) >= 2 {
|
|
decodePMGT(logger, stat, StatUpdatePMGT, fields[1:])
|
|
}
|
|
if len(fields) >= 3 {
|
|
decodeSwitch(logger, stat, StatUpdateSwitch, fields[2:])
|
|
}
|
|
if len(records[1]) != 0 {
|
|
w, err := strconv.ParseUint(records[1], 16, 16)
|
|
if err == nil {
|
|
stat.MCULocalState = uint16(w)
|
|
} else {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "MCULocalState",
|
|
"value": records[1],
|
|
}).Error("cannot decode uint16")
|
|
}
|
|
}
|
|
fields = strings.Split(records[2], "/")
|
|
if len(fields) >= 1 {
|
|
decodeMCUPersistent(logger, stat, fields[0])
|
|
}
|
|
if len(fields) >= 2 {
|
|
decodeNATWS(logger, stat, StatUpdateNATWS, fields[1:])
|
|
}
|
|
return records[3:]
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeGPSField(logger dilog.Logger, field *float64, record, fieldName string) {
|
|
if len(record) == 0 {
|
|
return
|
|
}
|
|
var err error
|
|
*field, err = strconv.ParseFloat(record, 64)
|
|
if err != nil {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": fieldName,
|
|
"value": record,
|
|
}).Error("cannot decode float")
|
|
}
|
|
}
|
|
|
|
func decodeGPSHDOP(logger dilog.Logger, field *float64, fix *GpsFixType, record string) {
|
|
if len(record) == 0 {
|
|
return
|
|
}
|
|
|
|
gpsHDOP := record[:len(record)-1]
|
|
if len(gpsHDOP) != 0 {
|
|
var err error
|
|
*field, err = strconv.ParseFloat(gpsHDOP, 64)
|
|
if err != nil && len(record) != 0 {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "GPSHDOP",
|
|
"value": gpsHDOP,
|
|
}).Error("cannot decode float")
|
|
}
|
|
}
|
|
*fix = GpsFixType(record[len(record)-1])
|
|
}
|
|
|
|
// regex is global, because it is valid to compile it only once and not everytime decodeGPS is called
|
|
//nolint: gochecknoglobals
|
|
var regex = regexp.MustCompile(`([0-9]+)([A-Z\-])?`)
|
|
|
|
func decodeGPS(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, record []string) []string {
|
|
if (fields&StatUpdateGPS) != StatUpdateGPS || len(record) == 0 {
|
|
return record
|
|
}
|
|
decodeGPSField(logger, &stat.GPSLatitude, record[0], "GPSLatitude")
|
|
decodeGPSField(logger, &stat.GPSLongitude, record[1], "GPSLongitude")
|
|
decodeGPSField(logger, &stat.GPSAltitude, record[2], "GPSAltitude")
|
|
|
|
decodeGPSHDOP(logger, &stat.GPSHDOP, &stat.GPSFIX, record[3])
|
|
decodeGPSField(logger, &stat.GPSSpeed, record[4], "GPSSpeed")
|
|
decodeGPSField(logger, &stat.GPSHeading, record[5], "GPSHeading")
|
|
|
|
ret := regex.FindAllStringSubmatch(record[6], -1)
|
|
if len(ret[0]) >= 2 {
|
|
t, err := strconv.ParseInt(ret[0][1], 10, 64)
|
|
if err != nil && len(ret[0][1]) != 0 {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": "GPS time",
|
|
"value": ret[0][1],
|
|
}).Error("cannot decode int")
|
|
}
|
|
stat.GPSTime = time.Unix(t, 0)
|
|
}
|
|
if len(ret[0]) >= 3 && len(ret[0][2]) > 0 {
|
|
stat.GPSFIX = GpsFixType(ret[0][2][0])
|
|
}
|
|
return record[7:]
|
|
}
|
|
|
|
func decodeAcceleration(logger dilog.Logger, field *float64, fields StatUpdateField, records []string) []string {
|
|
if (fields&StatUpdateTilt) == StatUpdateTilt && len(records) > 0 {
|
|
records[0] = strings.TrimRight(records[0], "g")
|
|
return decodeFloat(logger, field, fields, StatUpdateTilt, records)
|
|
}
|
|
return records
|
|
}
|
|
|
|
func decodeTilt(logger dilog.Logger, stat *StatusUpdate, fields StatUpdateField, records []string) []string {
|
|
l := logger.WithField("field", "Tilt.X")
|
|
r := decodeAcceleration(l, &stat.Tilt.X, fields, records)
|
|
l = logger.WithField("field", "Tilt.Y")
|
|
r = decodeAcceleration(l, &stat.Tilt.Y, fields, r)
|
|
l = logger.WithField("field", "Tilt.Z")
|
|
return decodeAcceleration(l, &stat.Tilt.Z, fields, r)
|
|
}
|