kopia lustrzana https://github.com/projecthorus/wenet
Add more data into GPS packets, and upload this to sondehub-amateur
rodzic
b94ffcbcf5
commit
95ee72cd20
|
@ -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
|
||||||
|
|
|
@ -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))
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
Ładowanie…
Reference in New Issue