Put terminal and telnet functionality into classes

Development
Sven Steudte 2018-10-08 23:37:40 +02:00
rodzic 77c7f20eae
commit 27274ae73d
6 zmienionych plików z 276 dodań i 104 usunięć

83
decoder/aprsis.py 100644
Wyświetl plik

@ -0,0 +1,83 @@
import telnetlib
import time
import socket
class AprsIS(object):
__instance = None
wdg = None # Connection Watchdog
tn = None # Telnet connection
def __new__(cls):
if AprsIS.__instance is None:
AprsIS.__instance = object.__new__(cls)
return AprsIS.__instance
def __init__(self, ):
self.connect()
def __del__(self):
self.disconnect()
def resetWatchdog(self):
self.wdg = time.time() + 10
def hasWatchdogTimeout(self):
return self.wdg < time.time()
def connect(self):
print('Connect to APRS-IS')
while True:
try:
self.tn = telnetlib.Telnet("euro.aprs2.net", 14580, 3)
self.tn.write(("user N0CALL filter u/APECAN filter t/p\n").encode('ascii'))
print('Connected')
self.resetWatchdog()
return
except Exception as e:
print('Could not connect to APRS-IS: %s' % str(e))
print('Try again in 5 seconds...')
time.sleep(5)
def disconnect(self):
self.tn.get_socket().shutdown(socket.SHUT_WR)
self.tn.read_all()
self.tn.close()
def reconnect(self):
self.disconnect()
self.connect()
def getData(self):
# Read data
try:
buf = self.tn.read_until(b"\n").decode('charmap')
except EOFError: # Server has connection closed
print('APRS-IS connection lost (EOFError)... reconnect')
self.reconnect()
return
except UnicodeDecodeError:
return
# Filter non ASCII packets (bad packets)
if not all(ord(c) < 128 for c in buf):
return None
# Watchdog reconnection
if self.hasWatchdogTimeout():
print('APRS-IS connection lost (Watchdog)... reconnect')
self.reconnect()
self.resetWatchdog()
if '# aprsc' in buf: # Watchdog reload
print('Ping from APRS-IS')
return None
else: # Data handling
return buf

Wyświetl plik

@ -1,18 +0,0 @@
#!/usr/bin/python
import serial,os,re,sys
import pygame
from pygame.locals import *
import pygame.time
from cStringIO import StringIO
try:
ser = serial.Serial(port='/dev/ttyACM1')
except:
sys.stderr.write('Error: Could not open serial port\n')
sys.exit(1)
ser.write('\r\n')
ser.write('command '+str(int(sys.argv[1], 16))+' '+str(int(sys.argv[2], 16))+'\r\n')
ser.close()

Wyświetl plik

@ -1,14 +1,14 @@
#!/usr/bin/python3
import re,io,os
import sys
import re,io,os,sys,time
import argparse
import telnetlib
import time
import mysql.connector as mariadb
import image
import position
import json
from datetime import datetime,timezone
from subprocess import *
from terminal import Terminal
# Parse arguments from terminal
parser = argparse.ArgumentParser(description='APRS/SSDV decoder')
@ -90,95 +90,147 @@ db.cursor().execute("""
PRIMARY KEY (`call`,`rxtime`)
)
""")
db.cursor().execute("""
CREATE TABLE IF NOT EXISTS `raw`
(
`call` VARCHAR(10),
`rxtime` INTEGER,
`data` VARCHAR(1024),
`meta` VARCHAR(1024)
)
""")
db.cursor().execute("""
CREATE TABLE IF NOT EXISTS `location`
(
`call` VARCHAR(10),
`rxtime` INTEGER,
`lat` FLOAT,
`lon` FLOAT,
`packets` INTEGER DEFAULT 1,
PRIMARY KEY (`call`)
)
""")
""" Packet handler for received APRS packets"""
def received_data(data):
data = data.strip()
rawdata = data.strip()
term = Terminal(args.verbose)
# Parse line and detect data
callreg = "([A-Z]{2}[0-9][A-Z]{1,3}(?:-[0-9]{1,2})?)" # Callregex to filter bad igated packets
# Callregex to filter bad igated packets and packets which couldn't have been transmitted
# with the APRS protocol (because they don't fit in the APRS protocol)
callreg = "((?:[A-Z][A-Z0-9]?|[A-Z0-9][A-Z])[0-9][A-Z]{1,3}(?:-[0-9]{1,2})?)>"
all = re.search("^" + callreg + "\>APECAN(.*?):", data)
pos = re.search("^" + callreg + "\>APECAN(.*?):[\=|!](.{13})(.*?)\|(.*)\|", data)
dat = re.search("^" + callreg + "\>APECAN(.*?):\{\{(I|L)(.*)", data)
dir = re.search("^" + callreg + "\>APECAN(.*?)::(.{9}):Directs=(.*)", data)
# Callsign
re_call = re.search("^" + callreg, rawdata)
if pos or dat or dir:
# Debug
if args.verbose:
print('='*100)
print(data)
print('-'*100)
# APECAN packets
re_all = re.search("^" + callreg + "APECAN(.*?):", rawdata)
re_pos = re.search("^" + callreg + "APECAN(.*?):[!=](.{13})(.*?)\|(.*)\|", rawdata)
re_dat = re.search("^" + callreg + "APECAN(.*?):\{\{(I|L)(.*)", rawdata)
re_dir = re.search("^" + callreg + "APECAN(.*?)::(.{9}):Directs=(.*)", rawdata)
call = all.group(1).split(' ')[-1]
rxer = all.group(2).split(',')[-1]
if not len(rxer): rxer = args.call
# Regular position packets
re_loc_uncomp_no_time = re.search("^" + callreg + ".*?:[!=]([0-9]{4}.[0-9]{2})([SN]).([0-9]{5}.[0-9]{2})([WE]).*|", rawdata)
re_loc_uncomp_wi_time = re.search("^" + callreg + ".*?:[@\/][0-9]{6}[zh]([0-9]{4}.[0-9]{2})([SN]).([0-9]{5}.[0-9]{2})([WE]).*|", rawdata)
if pos: # Position packet (with comment and telementry)
# Log time
rxtime = int(datetime.now(timezone.utc).timestamp())
comm = pos.group(4)
position.insert_position(db, call, comm, 'pos')
if re_pos or re_dat or re_dir: # Is recognized APECAN packet
elif dat: # Data packet (Image or Logging)
call = re_all.group(1).split(' ')[-1]
typ = dat.group(3)
data = dat.group(4)
meta = {}
if typ is 'I': # Image packet
image.insert_image(db, rxer, call, data)
elif typ is 'L': # Log packet
position.insert_position(db, call, data, 'log')
if re_all:
elif dir: # Directs packet
position.insert_directs(db, call, dir.group(4))
# Position packet (with comment and telementry)
if re_pos:
comm = re_pos.group(4)
meta = position.insert_position(db, call, comm, 'pos', rxtime)
# Data packet (Image or Logging)
elif re_dat:
typ = re_dat.group(3)
data = re_dat.group(4)
if typ is 'I': # Image packet
meta = image.insert_image(db, call, data, rxtime)
elif typ is 'L': # Log packet
meta = position.insert_position(db, call, data, 'log', rxtime)
# Directs packet
elif re_dir:
meta = position.insert_directs(db, call, re_dir.group(4), rxtime)
term.packet_apecan(rxtime, rawdata, meta)
else:
term.packet_apecan(rxtime, rawdata, 'Unrecognized APECAN packet')
# Insert into raw database
db.cursor().execute("""
INSERT INTO `raw` (`call`,`rxtime`,`data`,`meta`)
VALUES (%s,%s,%s,%s)""",
(call, str(rxtime), rawdata, json.dumps(meta))
)
elif (re_loc_uncomp_no_time and re_loc_uncomp_no_time.group(0) != '') or (re_loc_uncomp_wi_time and re_loc_uncomp_wi_time.group(0) != ''):
re_un = re_loc_uncomp_no_time if re_loc_uncomp_no_time.group(0) != '' else re_loc_uncomp_wi_time
call = re_un.group(1)
try:
lat = (1 if re_un.group(3) == 'N' else -1) * round(float(re_un.group(2)) / 100, 4)
lon = (1 if re_un.group(5) == 'E' else -1) * round(float(re_un.group(4)) / 100, 4)
# Debug
term.packet_ext_info(rxtime, rawdata, 'Position call=%s lat=%f lon=%f' % (call, lat, lon))
db.cursor().execute("""
INSERT INTO `location` (`call`,`rxtime`,`lat`,`lon`)
VALUES (%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
`rxtime`=%s, `lat`=%s, `lon`=%s, packets=packets+1""",
(call, str(rxtime), lat, lon, str(rxtime), lat, lon)
)
except ValueError:
term.packet_ext_error(rxtime, rawdata, 'Value error in packet')
print(rawdata)
else:
if re_call == None:
# Invalid callsign: Must comply with APRS e.g. DL7AD-C => Inalid SSID
term.packet_ext_error(rxtime, rawdata, 'Invalid APRS callsign: ' + rawdata.split('>')[0])
else:
term.packet_ext_error(rxtime, rawdata, 'Unknown format')
if args.device == 'I': # Source APRS-IS
print('Connect to APRS-IS')
try:
tn = telnetlib.Telnet("euro.aprs2.net", 14580, 3)
tn.write(("user %s filter u/APECAN\n" % args.call).encode('ascii'))
print('Connected')
except Exception as e:
print('Could not connect to APRS-IS: %s' % str(e))
print('exit...')
sys.exit(1)
wdg = time.time() + 10 # Connection watchdog
from aprsis import AprsIS
conn = AprsIS()
last_db_commit = time.time()
while True:
# Read data
try:
buf = tn.read_until(b"\n").decode('charmap')
except EOFError: # Server has connection closed
wdg = 0 # Tell watchdog to reconnect
except UnicodeDecodeError:
pass
# Watchdog reconnection
if wdg < time.time():
print('APRS-IS connection lost... reconnect')
try:
tn = telnetlib.Telnet("euro.aprs2.net", 14580, 3)
tn.write(("user %s filter u/APECAN\n" % args.call).encode('ascii'))
print('Connected')
wdg = time.time() + 10
except Exception as e:
print('Could not connect to APRS-IS: %s' % str(e))
print('Try again...')
if '# aprsc' in buf: # Watchdog reload
print('Ping from APRS-IS')
wdg = time.time() + 30
else: # Data handling
received_data(buf)
time.sleep(0.01)
data = conn.getData()
if data is not None:
received_data(data)
if last_db_commit+1 < time.time():
db.commit()
last_db_commit = time.time()
elif args.device is '-': # Source stdin
while True:
received_data(sys.stdin.readline())
db.commit()
else: # Source Serial connection
@ -203,4 +255,5 @@ else: # Source Serial connection
data += chr(b[0])
received_data(data)
db.commit()

Wyświetl plik

@ -1,7 +1,6 @@
import binascii
import urllib.request
import urllib.error
from datetime import datetime
from subprocess import *
import time
import threading
@ -59,12 +58,12 @@ def imgproc():
time.sleep(1)
w = time.time()
def insert_image(db, receiver, call, data_b91):
def insert_image(db, call, data_b91, rxtime):
global imageProcessor,imageData,w
data = base91.decode(data_b91)
if len(data) != 174:
return # APRS message has invalid type or length (or both)
if len(data) != 174: # APRS message has invalid type or length (or both)
return {'type': 'img', 'error': 'Invalid data: message too short'}
cur = db.cursor()
@ -87,11 +86,18 @@ def insert_image(db, receiver, call, data_b91):
data = ('68%08x%02x%04x' % (ssdv_encode_callsign(bcall), imageID, packetID)) + data
data += "%08x" % (binascii.crc32(binascii.unhexlify(data)) & 0xffffffff)
timd = int(datetime.now().timestamp())
# Find image ID (or generate new one)
_id = None
cur.execute("SELECT `id`,`packetID` FROM `image` WHERE `call` = %s AND `imageID` = %s AND `rxtime`+5*60 >= %s ORDER BY `rxtime` DESC LIMIT 1", (call, imageID, timd))
cur.execute("""
SELECT `id`,`packetID`
FROM `image`
WHERE `call` = %s
AND `imageID` = %s
AND `rxtime`+5*60 >= %s
ORDER BY `rxtime`
DESC LIMIT 1""",
(call, imageID, str(rxtime))
)
fetch = cur.fetchall()
if len(fetch):
_id = fetch[0][0]
@ -113,7 +119,7 @@ def insert_image(db, receiver, call, data_b91):
cur.execute("""
INSERT IGNORE INTO `image` (`call`,`rxtime`,`imageID`,`packetID`,`data`,`id`)
VALUES (%s,%s,%s,%s,%s,%s)""",
(call, timd, imageID, packetID, data, _id)
(call, str(rxtime), imageID, packetID, data, _id)
)
if w+0.5 < time.time():
@ -131,3 +137,5 @@ def insert_image(db, receiver, call, data_b91):
imageProcessor = threading.Thread(target=imgproc)
imageProcessor.start()
return {'type': 'img', 'imageID': imageID, 'packetID': packetID, 'serverID': _id}

Wyświetl plik

@ -1,8 +1,7 @@
from datetime import datetime,timedelta,timezone
import base91
import struct
def insert_position(db, call, comm, typ):
def insert_position(db, call, comm, typ, rxtime):
try:
# Decode comment
data = base91.decode(comm)
@ -13,13 +12,12 @@ def insert_position(db, call, comm, typ):
si4464_temp,reset,_id,gps_time,sys_time,sys_error) = struct.unpack('HHHHhhHBBBBHiiIIIhhhBBBBhhHIIII', data[:72])
# Insert
rxtime = int(datetime.now(timezone.utc).timestamp())
db.cursor().execute(
"""INSERT INTO `position` (`call`,`rxtime`,`org`,`adc_vsol`,`adc_vbat`,`pac_vsol`,`pac_vbat`,`pac_pbat`,`pac_psol`,`light_intensity`,`gps_lock`,
`gps_sats`,`gps_ttff`,`gps_pdop`,`gps_alt`,`gps_lat`,`gps_lon`,`sen_i1_press`,`sen_e1_press`,`sen_e2_press`,`sen_i1_temp`,`sen_e1_temp`,
`sen_e2_temp`,`sen_i1_hum`,`sen_e1_hum`,`sen_e2_hum`,`sys_error`,`stm32_temp`,`si4464_temp`,`reset`,`id`,`sys_time`,`gps_time`)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""",
(call,rxtime,typ,adc_vsol,adc_vbat,pac_vsol,pac_vbat,pac_pbat,pac_psol,light_intensity,gps_lock,gps_sats,gps_ttff,
(call,str(rxtime),typ,adc_vsol,adc_vbat,pac_vsol,pac_vbat,pac_pbat,pac_psol,light_intensity,gps_lock,gps_sats,gps_ttff,
gps_pdop,gps_alt,gps_lat,gps_lon,sen_i1_press,sen_e1_press,sen_e2_press,sen_i1_temp,sen_e1_temp,sen_e2_temp,sen_i1_hum,
sen_e1_hum,sen_e2_hum,sys_error,stm32_temp,si4464_temp,reset,_id,sys_time,gps_time)
)
@ -28,15 +26,18 @@ def insert_position(db, call, comm, typ):
# Debug
print('Received %s packet packet Call=%s Reset=%d ID=%d' % (typ, call, reset, _id))
return {'type': 'pos', 'reset': reset, 'id': _id}
except struct.error:
print('Received erroneous %s packet Call=%s' % (typ, call))
print('Received erroneous %s packet Call=%s' % (typ, call))
def insert_directs(db, call, dir):
rxtime = int(datetime.now(timezone.utc).timestamp())
db.cursor().execute("INSERT INTO `directs` (`call`,`rxtime`,`directs`) VALUES (%s,%s,%s)", (call,rxtime,dir))
def insert_directs(db, call, dir, rxtime):
db.cursor().execute("INSERT INTO `directs` (`call`,`rxtime`,`directs`) VALUES (%s,%s,%s)", (call,str(rxtime),dir))
db.commit()
# Debug
print('Received dir packet packet Call=%s' % call)
return {'type': 'dir'}

Wyświetl plik

@ -0,0 +1,45 @@
class Terminal(object):
LEVEL_NONE = 0
LEVEL_ERROR = 1
LEVEL_WARN = 2
LEVEL_INFO = 3
LEVEL_DEBUG = 4
__instance = None
verbosity = None
def __new__(cls, verbosity):
if Terminal.__instance is None:
Terminal.__instance = object.__new__(cls)
Terminal.__instance.verbosity = verbosity
return Terminal.__instance
def error(self, str):
if self.verbosity >= LEVEL_ERROR:
print(str)
def warn(self, str):
if self.verbosity >= LEVEL_WARN:
print(str)
def info(self, str):
if self.verbosity >= LEVEL_INFO:
print(str)
def debug(self, str):
if self.verbosity >= LEVEL_DEBUG:
print(str)
def packet_apecan(self, rxtime, raw, decoded):
print(('='*10) + ' ' + str(rxtime) + ' ' + ('='*78))
print(raw)
print('-'*100)
print(decoded)
def packet_ext_info(self, rxtime, raw, decoded):
pass
def packet_ext_error(self, rxtime, raw, decoded):
pass