You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.2 KiB
158 lines
5.2 KiB
6 years ago
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Horus Binary (and oldschool) Telemetry Uploader
|
||
|
#
|
||
|
# Mark Jessop 2015-12-31
|
||
|
# <vk5qi@rfhead.net>
|
||
|
#
|
||
|
# This script takes either a hex representation of the binary payload, or
|
||
|
# a 'classic' ASCII sentence, and uploads it to Habitat.
|
||
|
#
|
||
|
# Currently this script tells the two apart by looking for 'HORUS' at the start
|
||
|
# of the argument to determine if it's an ASCII sentence.
|
||
|
#
|
||
|
# It's designed to be called from fsk_horus_stream.m, and is tailored for it's output.
|
||
|
#
|
||
|
# Dependencies:
|
||
|
# - Python 2.7 (Will probably break in Python 3)
|
||
|
# - crcmod (pip install crcmod)
|
||
|
#
|
||
|
|
||
|
import time, struct, json, socket, httplib, crcmod, argparse, sys
|
||
|
from base64 import b64encode
|
||
|
from hashlib import sha256
|
||
|
from datetime import datetime
|
||
|
|
||
|
def crc16_ccitt(data):
|
||
|
"""
|
||
|
Calculate the CRC16 CCITT checksum of *data*.
|
||
|
|
||
|
(CRC16 CCITT: start 0xFFFF, poly 0x1021)
|
||
|
"""
|
||
|
crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
|
||
|
return crc16(data)
|
||
|
|
||
|
# Binary packet format, from https://github.com/darksidelemm/PicoHorusBinary/tree/master/PicoPayloadGPS
|
||
|
# struct TBinaryPacket
|
||
|
# {
|
||
|
# uint8_t PayloadID;
|
||
|
# uint16_t Counter;
|
||
|
# uint8_t Hours;
|
||
|
# uint8_t Minutes;
|
||
|
# uint8_t Seconds;
|
||
|
# float Latitude;
|
||
|
# float Longitude;
|
||
|
# uint16_t Altitude;
|
||
|
# uint8_t Speed; // Speed in Knots (1-255 knots)
|
||
|
# uint8_t Sats;
|
||
|
# int8_t Temp; // Twos Complement Temp value.
|
||
|
# uint8_t BattVoltage; // 0 = 0.5v, 255 = 2.0V, linear steps in-between.
|
||
|
# uint16_t Checksum; // CRC16-CCITT Checksum.
|
||
|
# }; // __attribute__ ((packed));
|
||
|
|
||
|
def decode_horus_binary_telemetry(payload):
|
||
|
|
||
|
horus_format_struct = "<BHBBBffHBBbBH"
|
||
|
try:
|
||
|
unpacked = struct.unpack(horus_format_struct, payload)
|
||
|
except:
|
||
|
print "Wrong string length. Packet contents:"
|
||
|
print ":".join("{:02x}".format(ord(c)) for c in payload)
|
||
|
sys.exit(1)
|
||
|
|
||
|
telemetry = {}
|
||
|
telemetry['payload_id'] = unpacked[0]
|
||
|
telemetry['counter'] = unpacked[1]
|
||
|
telemetry['time'] = "%02d:%02d:%02d" % (unpacked[2],unpacked[3],unpacked[4])
|
||
|
telemetry['latitude'] = unpacked[5]
|
||
|
telemetry['longitude'] = unpacked[6]
|
||
|
telemetry['altitude'] = unpacked[7]
|
||
|
telemetry['speed'] = unpacked[8]
|
||
|
telemetry['sats'] = unpacked[9]
|
||
|
telemetry['temp'] = unpacked[10]
|
||
|
telemetry['batt_voltage_raw'] = unpacked[11]
|
||
|
telemetry['checksum'] = unpacked[12]
|
||
|
|
||
|
# Convert some of the fields into more useful units.
|
||
|
telemetry['batt_voltage'] = 0.5 + 1.5*telemetry['batt_voltage_raw']/255.0
|
||
|
|
||
|
return telemetry
|
||
|
|
||
|
# Compatible with Habitat Payload ID 55f39c02e518bdd2885c8aac7d1cdd7c
|
||
|
def telemetry_to_sentence(telemetry):
|
||
|
sentence = "$$PICOHORUSBINARY,%d,%s,%.5f,%.5f,%d,%d,%d,%d,%.2f" % (telemetry['counter'],telemetry['time'],telemetry['latitude'],
|
||
|
telemetry['longitude'],telemetry['altitude'],telemetry['speed'],telemetry['sats'],telemetry['temp'],telemetry['batt_voltage'])
|
||
|
|
||
|
checksum = hex(crc16_ccitt(sentence[2:]))[2:].upper().zfill(4)
|
||
|
output = sentence + "*" + checksum + "\n"
|
||
|
return output
|
||
|
|
||
|
# Habitat Upload Functions
|
||
|
def habitat_upload_sentence(sentence, callsign="N0CALL"):
|
||
|
|
||
|
sentence_b64 = b64encode(sentence)
|
||
|
|
||
|
date = datetime.utcnow().isoformat("T") + "Z"
|
||
|
|
||
|
data = {
|
||
|
"type": "payload_telemetry",
|
||
|
"data": {
|
||
|
"_raw": sentence_b64
|
||
|
},
|
||
|
"receivers": {
|
||
|
callsign: {
|
||
|
"time_created": date,
|
||
|
"time_uploaded": date,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
try:
|
||
|
c = httplib.HTTPConnection("habitat.habhub.org",timeout=4)
|
||
|
c.request(
|
||
|
"PUT",
|
||
|
"/habitat/_design/payload_telemetry/_update/add_listener/%s" % sha256(sentence_b64).hexdigest(),
|
||
|
json.dumps(data), # BODY
|
||
|
{"Content-Type": "application/json"} # HEADERS
|
||
|
)
|
||
|
|
||
|
response = c.getresponse()
|
||
|
sys.exit(0)
|
||
|
except Exception as e:
|
||
|
print("Failed to upload to Habitat.")
|
||
|
sys.exit(1)
|
||
|
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument("raw_data", help="Raw Data, either hex binary data, or ASCII payload string.")
|
||
|
parser.add_argument("-c","--callsign",default="N0CALL",help="Habitat Upload Callsign")
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
|
||
|
uploader_callsign = args.callsign
|
||
|
raw_data = args.raw_data
|
||
|
print(raw_data)
|
||
|
|
||
|
if raw_data.startswith("AX5ARG"):
|
||
|
# Assume the data is a standard telemetry string and just upload it.
|
||
|
# Append a Newline and checksum (if not alread there) to the end, and "$$"'s to the start.
|
||
|
if not '*' in raw_data:
|
||
|
# Assume there is no checksum on the end of the string, and add one.
|
||
|
checksum = hex(crc16_ccitt(raw_data))[2:].upper().zfill(4)
|
||
|
raw_data = raw_data + "*" + checksum
|
||
|
if raw_data[:-1] != '\n':
|
||
|
raw_data = "$$" + raw_data + '\n'
|
||
|
|
||
|
habitat_upload_sentence(raw_data, callsign = uploader_callsign)
|
||
|
else:
|
||
|
# Attempt to decode some hex data.
|
||
|
data = raw_data.decode("hex")
|
||
|
telem = decode_horus_binary_telemetry(data)
|
||
|
# Only convert and upload if checksum passes.
|
||
|
if(crc16_ccitt(data[:-2]) != telem['checksum']):
|
||
|
print("Checksum Failed!")
|
||
|
sys.exit(1)
|
||
|
|
||
|
sentence = telemetry_to_sentence(telem)
|
||
|
print("Uploading: %s"%(sentence))
|
||
|
habitat_upload_sentence(sentence, callsign = uploader_callsign)
|
||
|
|