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) }