Add more data into GPS packets, and upload this to sondehub-amateur

rfm98w_packettx
Mark Jessop 2024-09-27 16:05:39 +09:30
rodzic b94ffcbcf5
commit 95ee72cd20
5 zmienionych plików z 114 dodań i 21 usunięć

Wyświetl plik

@ -36,9 +36,9 @@ class WENET_PACKET_TYPES:
class WENET_PACKET_LENGTHS: class WENET_PACKET_LENGTHS:
GPS_TELEMETRY = 35 GPS_TELEMETRY = 61
ORIENTATION_TELEMETRY = 43 ORIENTATION_TELEMETRY = 43
IMAGE_TELEMETRY = 80 IMAGE_TELEMETRY = 80
def decode_packet_type(packet): def decode_packet_type(packet):
@ -178,7 +178,8 @@ def gps_telemetry_decoder(packet):
""" Extract GPS telemetry data from a packet, and return it as a dictionary. """ Extract GPS telemetry data from a packet, and return it as a dictionary.
Keyword Arguments: Keyword Arguments:
packet: A GPS telemetry packet, as per https://docs.google.com/document/d/12230J1X3r2-IcLVLkeaVmIXqFeo3uheurFakElIaPVo/edit?usp=sharing packet: A GPS telemetry packet, as per
https://github.com/projecthorus/wenet/wiki/Modem-&-Packet-Format-Details#0x01---gps-telemetry
This can be provided as either a string, or a list of integers, which will be converted This can be provided as either a string, or a list of integers, which will be converted
to a string prior to decoding. to a string prior to decoding.
@ -207,7 +208,7 @@ def gps_telemetry_decoder(packet):
# Wrap the next bit in exception handling. # Wrap the next bit in exception handling.
try: try:
# Unpack the packet into a list. # Unpack the packet into a list.
data = struct.unpack('>BHIBffffffBBB', packet) data = struct.unpack(">BHIBffffffBBBffHffff", packet)
gps_data['week'] = data[1] gps_data['week'] = data[1]
gps_data['iTOW'] = data[2]/1000.0 # iTOW provided as milliseconds, convert to seconds. gps_data['iTOW'] = data[2]/1000.0 # iTOW provided as milliseconds, convert to seconds.
@ -221,6 +222,27 @@ def gps_telemetry_decoder(packet):
gps_data['numSV'] = data[10] gps_data['numSV'] = data[10]
gps_data['gpsFix'] = data[11] gps_data['gpsFix'] = data[11]
gps_data['dynamic_model'] = data[12] gps_data['dynamic_model'] = data[12]
# New fields 2024-09
gps_data['radio_temp'] = round(data[13],1)
gps_data['cpu_temp'] = round(data[14],1)
gps_data['cpu_speed'] = data[15]
gps_data['load_avg_1'] = round(data[16],3)
gps_data['load_avg_5'] = round(data[17],3)
gps_data['load_avg_15'] = round(data[18],3)
gps_data['disk_percent'] = round(data[19],3)
# Check to see if we actually have real data in these new fields.
# If its an old transmitter, it will have 0x55 in these spots, which we can detect
if gps_data['cpu_speed'] == 21845:
# 0x5555 -> 21825, which we use as an indication that padding is in use.
# Set all the new fields to invalid values
gps_data['radio_temp'] = -999.0
gps_data['cpu_temp'] = -999.0
gps_data['cpu_speed'] = 0
gps_data['load_avg_1'] = 0
gps_data['load_avg_5'] = 0
gps_data['load_avg_15'] = 0
gps_data['disk_percent'] = -1.0
# Perform some post-processing on the data, to make some of the fields easier to read. # Perform some post-processing on the data, to make some of the fields easier to read.
@ -281,7 +303,7 @@ def gps_telemetry_string(packet):
if gps_data['error'] != 'None': if gps_data['error'] != 'None':
return "GPS: ERROR Could not decode." return "GPS: ERROR Could not decode."
else: else:
gps_data_string = "GPS: %s Lat/Lon: %.5f,%.5f Alt: %dm, Speed: H %dkph V %.1fm/s, Heading: %d deg, Fix: %s, SVs: %d, Model: %s " % ( gps_data_string = "GPS: %s Lat/Lon: %.5f,%.5f Alt: %dm, Speed: H %dkph V %.1fm/s, Heading: %d deg, Fix: %s, SVs: %d, DynModel: %s, Radio Temp: %.1f, CPU Temp: %.1f, CPU Speed: %d, Load Avg: %.2f, %.2f, %.2f, Disk Usage: %.1f%%" % (
gps_data['timestamp'], gps_data['timestamp'],
gps_data['latitude'], gps_data['latitude'],
gps_data['longitude'], gps_data['longitude'],
@ -291,7 +313,14 @@ def gps_telemetry_string(packet):
int(gps_data['heading']), int(gps_data['heading']),
gps_data['gpsFix_str'], gps_data['gpsFix_str'],
gps_data['numSV'], gps_data['numSV'],
gps_data['dynamic_model_str'] gps_data['dynamic_model_str'],
gps_data['radio_temp'],
gps_data['cpu_temp'],
gps_data['cpu_speed'],
gps_data['load_avg_1'],
gps_data['load_avg_5'],
gps_data['load_avg_15'],
gps_data['disk_percent']
) )
return gps_data_string return gps_data_string

Wyświetl plik

@ -212,7 +212,7 @@ while True:
# Only proceed if there are no decode errors. # Only proceed if there are no decode errors.
if packet_info['error'] != 'None': if packet_info['error'] != 'None':
logging.error(message['error']) logging.error(packet_info['error'])
continue continue
if (packet_info['image_id'] != current_image) or (packet_info['callsign'] != current_callsign) : if (packet_info['image_id'] != current_image) or (packet_info['callsign'] != current_callsign) :
@ -264,4 +264,5 @@ while True:
logging.debug("Unknown Packet Format.") logging.debug("Unknown Packet Format.")
except Exception as e: except Exception as e:
logging.exception(e)
logging.error("Error handling packet - " + str(e)) logging.error("Error handling packet - " + str(e))

Wyświetl plik

@ -133,6 +133,25 @@ def handle_gps_telemetry(gps_data):
if sondehub: if sondehub:
# Add to the SondeHub-Amateur uploader! # Add to the SondeHub-Amateur uploader!
_extra_fields = {
'ascent_rate': round(gps_data['ascent_rate'],1),
'speed': round(gps_data['ground_speed'],1)
}
# Add in new fields from 2024-09 if they exist and are valid
if 'radio_temp' in gps_data:
if gps_data['radio_temp'] > -999.0:
_extra_fields['radio_temp'] = gps_data['radio_temp']
if gps_data['cpu_temp'] > -999.0:
_extra_fields['cpu_temp'] = gps_data['cpu_temp']
_extra_fields['cpu_speed'] = gps_data['cpu_speed']
_extra_fields['load_avg_1'] = gps_data['load_avg_1']
_extra_fields['load_avg_5'] = gps_data['load_avg_5']
_extra_fields['load_avg_15'] = gps_data['load_avg_15']
_extra_fields['disk_percent'] = gps_data['disk_percent']
sondehub.add_telemetry( sondehub.add_telemetry(
current_callsign + "-Wenet", current_callsign + "-Wenet",
gps_data['timestamp'] + "Z", gps_data['timestamp'] + "Z",
@ -141,10 +160,7 @@ def handle_gps_telemetry(gps_data):
round(gps_data['altitude'],1), round(gps_data['altitude'],1),
sats = gps_data['numSV'], sats = gps_data['numSV'],
heading = round(gps_data['heading'],1), heading = round(gps_data['heading'],1),
extra_fields = { extra_fields = _extra_fields,
'ascent_rate': round(gps_data['ascent_rate'],1),
'speed': round(gps_data['ground_speed'],1)
},
modulation = "Wenet", modulation = "Wenet",
frequency = round(current_modem_stats['fcentre']/1e6, 5), frequency = round(current_modem_stats['fcentre']/1e6, 5),
snr = round(current_modem_stats['snr'],1) snr = round(current_modem_stats['snr'],1)
@ -317,7 +333,7 @@ if __name__ == "__main__":
parser.add_argument("callsign", help="SondeHub-Amateur Uploader Callsign") parser.add_argument("callsign", help="SondeHub-Amateur Uploader Callsign")
parser.add_argument("-l", "--listen_port", default=5003, help="Port to run Web Server on. (Default: 5003)") parser.add_argument("-l", "--listen_port", default=5003, help="Port to run Web Server on. (Default: 5003)")
parser.add_argument("-v", "--verbose", action='store_true', help="Enable debug output.") parser.add_argument("-v", "--verbose", action='store_true', help="Enable debug output.")
parser.add_argument("--no_sondehub", action='store_true', help="Disable SondeHub-Amateur position upload.") parser.add_argument("--no_sondehub", default=False, action='store_true', help="Disable SondeHub-Amateur position upload.")
parser.add_argument("-u", "--udp_port", default=None, type=int, help="Port to emit Horus UDP packets on. (Default: 0 (disabled), Typical: 55673)") parser.add_argument("-u", "--udp_port", default=None, type=int, help="Port to emit Horus UDP packets on. (Default: 0 (disabled), Typical: 55673)")
args = parser.parse_args() args = parser.parse_args()

Wyświetl plik

@ -21,8 +21,10 @@ import os
import datetime import datetime
import crcmod import crcmod
import json import json
import shutil
import socket import socket
import struct import struct
import subprocess
import traceback import traceback
from time import sleep from time import sleep
from threading import Thread from threading import Thread
@ -256,21 +258,39 @@ class PacketTX(object):
def transmit_gps_telemetry(self, gps_data): def transmit_gps_telemetry(self, gps_data):
""" Generate and Transmit a GPS Telemetry Packet. """ Generate and Transmit a GPS Telemetry Packet.
Host platform CPU speed, temperature and load averages are collected and included in this packet too.
Keyword Arguments: Keyword Arguments:
gps_data: A dictionary, as produced by the UBloxGPS class. It must have the following fields: gps_data: A dictionary, as produced by the UBloxGPS class. It must have the following fields:
latitude, longitude, altitude, ground_speed, ascent_rate, heading, gpsFix, numSV, latitude, longitude, altitude, ground_speed, ascent_rate, heading, gpsFix, numSV,
week, iTOW, leapS, dynamic_model. week, iTOW, leapS, dynamic_model.
The generated packet format is in accordance with the specification in: The generated packet format is in accordance with the specification in:
https://docs.google.com/document/d/12230J1X3r2-IcLVLkeaVmIXqFeo3uheurFakElIaPVo/edit?usp=sharing https://github.com/projecthorus/wenet/wiki/Modem-&-Packet-Format-Details#0x01---gps-telemetry
The corresponding decoder for this packet format is within rx/WenetPackets.py, in the function The corresponding decoder for this packet format is within rx/WenetPackets.py, in the function
gps_telemetry_decoder gps_telemetry_decoder
""" """
# Collect non-GPS information to add to the packet.
_radio_temp = self.radio.temperature
_cpu_speed = self.get_cpu_speed()
_cpu_temp = self.get_cpu_temperature()
_load_avg_1, _load_avg_5, _load_avg_15 = os.getloadavg()
# Collect disk usage information
# Unsure of the likelyhood of this failing, but wrapping it in a try/except anyway
try: try:
gps_packet = struct.pack(">BHIBffffffBBB", _disk_usage = shutil.disk_usage(".")
_disk_percent = 100.0 * (_disk_usage.used / _disk_usage.total)
except:
_disk_percent = -1.0
# Construct the packet
try:
gps_packet = struct.pack(">BHIBffffffBBBffHffff",
1, # Packet ID for the GPS Telemetry Packet. 1, # Packet ID for the GPS Telemetry Packet.
gps_data['week'], gps_data['week'],
int(gps_data['iTOW']*1000), # Convert the GPS week value to milliseconds, and cast to an int. int(gps_data['iTOW']*1000), # Convert the GPS week value to milliseconds, and cast to an int.
@ -283,7 +303,15 @@ class PacketTX(object):
gps_data['ascent_rate'], gps_data['ascent_rate'],
gps_data['numSV'], gps_data['numSV'],
gps_data['gpsFix'], gps_data['gpsFix'],
gps_data['dynamic_model'] gps_data['dynamic_model'],
# New fields 2024-09
_radio_temp,
_cpu_temp,
int(_cpu_speed),
_load_avg_1,
_load_avg_5,
_load_avg_15,
_disk_percent
) )
self.queue_telemetry_packet(gps_packet) self.queue_telemetry_packet(gps_packet)
@ -423,6 +451,25 @@ class PacketTX(object):
self.queue_telemetry_packet(_packet, repeats=repeats) self.queue_telemetry_packet(_packet, repeats=repeats)
def get_cpu_temperature(self):
""" Grab the temperature of the RPi CPU """
try:
data = subprocess.check_output("/usr/bin/vcgencmd measure_temp", shell=True)
temp = data.decode().split('=')[1].split('\'')[0]
return float(temp)
except Exception as e:
print("Error reading temperature - %s" % str(e))
return -999.0
def get_cpu_speed(self):
""" Get the current CPU Frequency """
try:
data = subprocess.check_output("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", shell=True)
freq = int(data.decode().strip())/1000
return freq
except Exception as e:
print("Error reading CPU Freq - %s" % str(e))
return 9999
# #
# UDP messaging functions. # UDP messaging functions.

Wyświetl plik

@ -90,7 +90,7 @@ class RFM98W_Serial(object):
self.lora.set_register(0x31,0x00) # Set Continuous Transmit Mode self.lora.set_register(0x31,0x00) # Set Continuous Transmit Mode
# Get the IC temperature # Get the IC temperature
self.temperature = self.get_temperature() self.get_temperature()
self.lora.set_freq(self.frequency) self.lora.set_freq(self.frequency)
logging.info(f"RFM98W - Frequency set to: {self.frequency} MHz.") logging.info(f"RFM98W - Frequency set to: {self.frequency} MHz.")
@ -213,12 +213,12 @@ class RFM98W_Serial(object):
Get radio module temperature (uncalibrated) Get radio module temperature (uncalibrated)
""" """
# Make temperature measurement # Make temperature measurement
temperature = self.lora.get_register(0x3c) * (-1) self.temperature = self.lora.get_register(0x3c) * (-1)
if temperature < -63: if self.temperature < -63:
temperature += 255 self.temperature += 255
logging.info(f"RFM98W - Temperature: {self.temperature} C") logging.info(f"RFM98W - Temperature: {self.temperature} C")
return temperature return self.temperature
class SerialOnly(object): class SerialOnly(object):
""" """