kopia lustrzana https://github.com/DL7AD/pecanpico10
Put terminal and telnet functionality into classes
rodzic
77c7f20eae
commit
27274ae73d
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
Ładowanie…
Reference in New Issue