From 049b6b928cfaaf8af6d027cea889b09d57c4f594 Mon Sep 17 00:00:00 2001 From: Sven Steudte Date: Wed, 31 May 2017 01:01:00 +0200 Subject: [PATCH] Added decoder software --- decoder/base91.py | 93 +++++++++++++++++++++++++++++++++++++++++++++ decoder/decoder.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 decoder/base91.py create mode 100755 decoder/decoder.py diff --git a/decoder/base91.py b/decoder/base91.py new file mode 100644 index 0000000..4cc6ab5 --- /dev/null +++ b/decoder/base91.py @@ -0,0 +1,93 @@ +# Base91 encode/decode for Python 2 and Python 3 +# +# Copyright (c) 2012 Adrien Beraud +# Copyright (c) 2015 Guillaume Jacquenot +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Adrien Beraud, Wisdom Vibes Pte. Ltd., nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +import struct + +base91_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$', + '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', + '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'] + +decode_table = dict((v,k) for k,v in enumerate(base91_alphabet)) + +def decode(encoded_str): + ''' Decode Base91 string to a bytearray ''' + v = -1 + b = 0 + n = 0 + out = bytearray() + for strletter in encoded_str: + if not strletter in decode_table: + continue + c = decode_table[strletter] + if(v < 0): + v = c + else: + v += c*91 + b |= v << n + n += 13 if (v & 8191)>88 else 14 + while True: + out += struct.pack('B', b&255) + b >>= 8 + n -= 8 + if not n>7: + break + v = -1 + if v+1: + out += struct.pack('B', (b | v << n) & 255 ) + return out + +def encode(bindata): + ''' Encode a bytearray to a Base91 string ''' + b = 0 + n = 0 + out = '' + for count in range(len(bindata)): + byte = bindata[count:count+1] + b |= struct.unpack('B', byte)[0] << n + n += 8 + if n>13: + v = b & 8191 + if v > 88: + b >>= 13 + n -= 13 + else: + v = b & 16383 + b >>= 14 + n -= 14 + out += base91_alphabet[v % 91] + base91_alphabet[v // 91] + if n: + out += base91_alphabet[b % 91] + if n>7 or b>90: + out += base91_alphabet[b // 91] + return out + diff --git a/decoder/decoder.py b/decoder/decoder.py new file mode 100755 index 0000000..f9c7482 --- /dev/null +++ b/decoder/decoder.py @@ -0,0 +1,94 @@ +#!/usr/bin/python + +import serial,os,re,datetime +from subprocess import call +import base91 +import binascii +import urllib2 +import io +import sys +import argparse + +# Parse arguments from terminal +parser = argparse.ArgumentParser(description='APRS/SSDV decoder') +parser.add_argument('-c', '--call', help='Callsign of the station', required=True) +parser.add_argument('-l', '--log', help='Name of the logfile') +parser.add_argument('-n', '--grouping', help='Amount packets that will be sent to the SSDV server in one request', default=1, type=int) +parser.add_argument('-d', '--device', help='Serial device (\'-\' for stdin)', default='-') +parser.add_argument('-b', '--baudrate', help='Baudrate for serial device', default=9600, type=int) +parser.add_argument('-s', '--server', help='Server URL', default='https://ssdv.habhub.org/api/v0/packets') +args = parser.parse_args() + +if args.device is not '-': # Use serial connection (probably TNC) + try: + serr = serial.Serial( + port=args.device, + baudrate=args.baudrate, + ) + except: + sys.stderr.write('Error: Could not open serial port\n') + sys.exit(1) + + ser = io.TextIOWrapper(io.BufferedRWPair(serr, serr, 1), newline = '\r', line_buffering = True) # Define Newline as \r + + +# Open logging file +if args.log is not None: + try: + f = open(args.log, 'a') + except: + sys.stderr.write('Error: Could not open logging file\n') + sys.exit(1) + +jsons = [] + +while 1: + # Read a line (from stdin or serial) + data = sys.stdin.readline() if args.device is '-' else ser.readline() + + # Parse line and detect data + m = re.search("(.*)\>APECAN:\{\{I(.*)", data) + try: + call = m.group(1) + aprs = m.group(2) + except: + continue # message format incorrect (probably no APRS message or line cut off too short) + + if args.log is not None: + f.write(data) # Log data to file + + data = base91.decode(aprs) # Decode Base91 + + if len(data) != 219: + continue # APRS message sampled too short + + # Calculate CRC for SSDV server + crc = binascii.crc32(data) & 0xffffffff + + # Create message for SSDV server (and save to array) + ssdv = '55' + binascii.hexlify(data) + ('%08x' % crc) + (64*'0') + jsons.append("""{ + \"type\": \"packet\", + \"packet\": \"""" + ssdv + """\", + \"encoding\": \"hex\", + \"received\": \"""" + datetime.datetime.now().isoformat('T')[:19] + """Z\", + \"receiver\": \"""" + args.call + """\" + }""") + + print 'Received packet call %02x%02x%02x%02x image %d packet %d' % (data[1], data[2], data[3], data[4], data[5], data[7] + data[6] * 256) + + if len(jsons) >= args.grouping: # Enough packets collected, send them all to the server + + req = urllib2.Request(args.server) + req.add_header('Content-Type', 'application/json') + + json = "{\"type\":\"packets\",\"packets\":[" + ",".join(jsons) + "]}" # Group all SSDV packets into a big JSON + jsons = [] + + try: + result = urllib2.urlopen(req, "".join(json.split(' '))) # Send packets to server + print 'Send to SSDV data server: OK' + except urllib2.HTTPError, error: # The server did not like our packets :( + print 'Send to SSDV data server: failed' + print error.read() +