src.dualinventive.com/go/cp3000-interface/internal/statusupdate/decoder.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)
}