From 6708540020d6c13724aa48ca0a4db8d357152221 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Sun, 8 Oct 2023 11:26:27 +1030 Subject: [PATCH] Very preliminary RS41 subframe data extraction utility --- auto_rx/utils/rs41cal.py | 253 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 auto_rx/utils/rs41cal.py diff --git a/auto_rx/utils/rs41cal.py b/auto_rx/utils/rs41cal.py new file mode 100644 index 0000000..45e6692 --- /dev/null +++ b/auto_rx/utils/rs41cal.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# +# RS41 SubFrame Handling Utilities +# +import base64 +import json +import logging +import struct +import requests + +class RS41Subframe(object): + def __init__(self, filename=None, raw_bytes=None): + """ + RS41 Subframe Storage and data extraction + """ + + # Output data + self.data = {} + + self.raw_data = b'' + + if filename: + # Read in binary file + _f = open(filename, 'rb') + self.raw_data = _f.read() + _f.close() + + if raw_bytes: + self.raw_data = raw_bytes + + if (filename is None) and (raw_bytes is None): + raise IOError("Either a filename, or raw_bytes must be provided!") + + if (len(self.raw_data) != 800) and (len(self.raw_data) != 816): + raise IOError("Subframe data must be either 800 or 816 bytes in length!") + + # TODO - Check CRC16 + + # Extract main cal fields + self.parse_subframe() + + # Extract runtime-variable area + if len(self.raw_data) == 816: + self.parse_runtime_variable() + + pass + + + def check_subframe_crc(self): + pass + + def parse_subframe(self): + """ + Extract all available fields from the binary subframe data + + Reference: https://github.com/einergehtnochrein/ra-firmware/blob/master/src/rs41/rs41private.h#L233 + """ + + self.data['frequency'] = 400 + 0.01*self.extract_uint16(0x002)/64 + self.data['startupTxPower'] = self.extract_uint8(0x004) + self.data['optionFlags'] = self.extract_uint16(0x007) + self.data['serial'] = self.extract_string(0x00D, 8).rstrip(b'\x00').decode() + self.data['firmwareVersion'] = self.extract_uint32(0x015) + self.data['minHeight4Flight'] = self.extract_uint16(0x019) + self.data['lowBatVoltageThreshold'] = self.extract_uint8(0x01B)*0.1 # Volts - Guess! + self.data['nfcDetectorThreshold'] = self.extract_uint8(0x01C)*0.025 # Volts + self.data['refTemperatureTarget'] = self.extract_int8(0x021) + self.data['lowBatCapacityThreshold'] = self.extract_uint8(0x022) + self.data['flightKillFrames'] = self.extract_int16(0x027) + self.data['burstKill'] = self.extract_uint8(0x02B) # Convert to enum + self.data['freshBatteryCapacity'] = self.extract_uint16(0x02E) + self.data['allowXdata'] = self.extract_uint8(0x032) + self.data['ubloxHwVersionHigh'] = self.extract_uint16(0x033) + self.data['ubloxHwVersionLow'] = self.extract_uint16(0x035) + self.data['ubloxSwVersion'] = self.extract_uint16(0x037) + self.data['ubloxSwBuild'] = self.extract_uint16(0x039) + self.data['ubloxConfigErrors'] = self.extract_uint8(0x03B) + self.data['radioVersionCode'] = self.extract_uint8(0x03C) + # Main PTU Calibration Fields + self.data['refResistorLow'] = self.extract_float(0x03D) + self.data['refResistorHigh'] = self.extract_float(0x041) + self.data['refCapLow'] = self.extract_float(0x045) + self.data['refCapHigh'] = self.extract_float(0x049) + self.data['taylorT'] = self.extract_float_array(0x04D,3) + self.data['calT'] = self.extract_float(0x059) + self.data['polyT'] = self.extract_float_array(0x05D, 6) + self.data['calibU'] = self.extract_float_array(0x075, 2) + self.data['matrixU'] = self.extract_float_array(0x07D, 7, 6) + self.data['taylorTU'] = self.extract_float_array(0x125, 3) + self.data['calTU'] = self.extract_float(0x131) + self.data['polyTrh'] = self.extract_float_array(0x135, 6) + # Other status/config fields + self.data['startIWDG'] = self.extract_uint8(0x1EC) + self.data['parameterSetupDone'] = self.extract_uint8(0x1ED) + self.data['enableTestMode'] = self.extract_uint8(0x1EE) + self.data['enableTx'] = self.extract_uint8(0x1EF) + self.data['pressureLaunchSite'] = self.extract_float_array(0x210, 2) # Unsure if this is right? + # Board version/serial numbers + self.data['variant'] = self.extract_string(0x218, 10).rstrip(b'\x00').decode() + self.data['mainboard_version'] = self.extract_string(0x222, 10).rstrip(b'\x00').decode() + self.data['mainboard_serial'] = self.extract_string(0x22C, 9).rstrip(b'\x00').decode() + self.data['pressureSensor_serial'] = self.extract_string(0x243, 8).rstrip(b'\x00').decode() + # More status/config fields + self.data['xdataUartBaud'] = self.extract_uint8(0x253) # Convert to enum + self.data['cpuTempSensorVoltageAt25deg'] = self.extract_float(0x255) + # Pressure sensor calibration data + self.data['matrixP'] = self.extract_float_array(0x25E, 18) + self.data['vectorBp'] = self.extract_float_array(0x2A6, 3) + self.data['matrixBt'] = self.extract_float_array(0x2BA, 12) + # More settings + self.data['burstKillFrames'] = self.extract_int16(0x316) + + + + def parse_runtime_variable(self): + """ + Extract all available runtime-variable fields from the binary subframe data + + Reference: https://github.com/einergehtnochrein/ra-firmware/blob/master/src/rs41/rs41private.h#L233 + """ + + self.data['killCountdown'] = self.extract_int16(0x320) + self.data['launchAltitude'] = self.extract_int16(0x322) + self.data['heightOfFlightStart'] = self.extract_uint16(0x324) + self.data['lastTxPowerLevel'] = self.extract_uint8(0x326) + self.data['numSoftwareResets'] = self.extract_uint8(0x327) + self.data['intTemperatureCpu'] = self.extract_int8(0x328) + self.data['intTemperatureRadio'] = self.extract_int8(0x329) + self.data['remainingBatteryCapacity'] = self.extract_uint16(0x32A) + self.data['numUbxDiscarded'] = self.extract_uint8(0x32C) + self.data['numUbxStall'] = self.extract_uint8(0x32D) + + + def extract_uint16(self, address): + _r = struct.unpack('