-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Not working #6
Comments
ir-aprsisd #!/usr/bin/python3
############################################################################
##
## ir-aprsisd
##
## This is a KML-feed to APRS-IS forwarding daemon. It was written
## for Garmin/DeLorme inReach devices, but might work elsewhere too.
## It polls a KML feed in which it expects to find a point in rougly
## the form that the inReach online feeds use, with attendant course
## and altitude data. It transfers each new point found there to
## APRS-IS.
##
## K0SIN
##
###########################################################################
import aprslib
import urllib.request
import xml.dom.minidom
import time, calendar, math, re
import platform, sys, os, signal
import configparser
from optparse import OptionParser
#Name of our configuration file.
cf = "ir-aprsisd.cfg"
#Command-line options
op = OptionParser()
op.add_option("-C","--config",action="store",type="string",dest="config",help="Load the named configuration file.")
op.add_option("-s","--ssid",action="store",type="string",dest="ssid",help="APRS SSID")
op.add_option("-p","--pass",action="store",type="int",dest="passwd",help="APRS-IS password")
op.add_option("--port",action="store",type="int",dest="port",help="APRS-IS port")
op.add_option("-u","--user",action="store",type="string",dest="user",help="inReach username")
op.add_option("-P","--irpass",action="store",type="string",dest="irpass",help="inReach feed password")
op.add_option("-U","--url",action="store",type="string",dest="url",help="URL for KML feed")
op.add_option("-i","--imei",action="store",type="int",dest="imei",help="This instance should watch *only* for the single IMEI given in this option. For a more complicated mapping, use the Device section in the configuration file.")
op.add_option("-c","--comment",action="store",type="string",dest="comment",help="APRS-IS location beacon comment text")
op.add_option("-d","--delay",action="store",type="int",dest="delay",help="Delay between polls of KML feed")
op.add_option("--genpass",action="store_true",dest="genpass",help="Generate the correct passcode for the SSID given in the configuration, or on the command line, print it, and exit.")
(opts,args) = op.parse_args()
#Handle term and int signals
def trapexit(_signo,_stack_frame):
print()
print("Exiting.")
sys.exit(0)
signal.signal(signal.SIGTERM,trapexit)
signal.signal(signal.SIGINT,trapexit)
#This needs to be defined before the load below happens.
#Load a configuration file, and try to validate that it is loaded.
def loadConfig(cfile):
global conf
conf = configparser.ConfigParser()
if conf.read(cfile):
if conf.has_section('General'):
print("Loaded configuration: " + cfile)
return True
return False
#Handle loading of the configuration file first.
#Other command-line options may override things defined in the file.
if opts.config:
if not loadConfig(opts.config):
print("Can't load configuration: " + opts.config)
sys.exit(1)
else: #Default behavior if no file specified.
if not loadConfig(os.path.join("/etc", cf)):
if not loadConfig(os.path.join(os.path.dirname(os.path.abspath(__file__)),cf)):
if not loadConfig(cf):
print("Can't find configuration: " + cf)
sys.exit(1)
#Allow command-line arguments to override the config file.
if opts.ssid:
conf['APRS']['SSID'] = opts.ssid
if opts.passwd:
conf['APRS']['Password'] = str(opts.passwd)
if opts.port:
conf['APRS']['Port'] = str(opts.port)
if opts.user:
conf['inReach']['User'] = opts.user
if opts.irpass:
conf['inReach']['Password'] = opts.irpass
if opts.url:
conf['inReach']['URL'] = opts.url
if opts.comment:
conf['APRS']['Comment'] = opts.comment
if opts.delay:
conf['General']['Period'] = opts.delay
#SSID should be standardized to upper-case.
conf['APRS']['SSID'] = conf['APRS']['SSID'].upper()
#Running in passcode generator mode.
if opts.genpass:
print("Using SSID: " + conf['APRS']['SSID'])
print("The passcode is: " + str(aprslib.passcode(conf['APRS']['SSID'])))
print()
sys.exit(0)
#Handle the special case where we've specified an IMEI on the command-line
if opts.imei:
conf['Devices'] = {}
conf['Devices'][conf['APRS']['SSID']] = str(opts.imei)
#Get the number and call from from the default SSID
#If we have multiple devices with non-specific ID mapping, we'll make it up
# from this.
match = re.search('(\w+)-(\d+)$',conf['APRS']['SSID'])
if match is not None:
(Call,SSNum) = match.groups()
SSNum = int(SSNum)
else:
# Handle the case when there is no match
# For example, assign some default values or raise an exception
Call = (conf['APRS']['SSID'])
SSNum = 1
#The beginning of our APRS packets should contain a source, path,
# q construct, and gateway address. We'll reuse the same SSID as a gate.
# This is the part between the source and the gate.
ARPreamble = ''.join(['>APRS,','TCPIP*,','qAS,',conf['APRS']['SSID']])
NAME = "iR-APRSISD"
REV = "0.3"
#Set up the handler for HTTP connections
if conf.has_option('inReach','Password'):
passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, conf['inReach']['URL'],'',conf['inReach']['Password'])
httpauth = urllib.request.HTTPBasicAuthHandler(passman)
http = urllib.request.build_opener(httpauth)
else:
http = urllib.request.build_opener()
urllib.request.install_opener(http)
#Handle connection to APRS-IS
def reconnect():
global AIS
attempt = 1
while True:
AIS = aprslib.IS(conf['APRS']['SSID'],passwd=conf['APRS']['Password'],host=conf['APRS']['Host'],port=conf['APRS']['Port'],skip_login=conf['APRS']['Skip_Login'])
try:
AIS.connect()
break
except Exception as e:
print("Connection failed. Reconnecting: " + str(attempt))
attempt += 1
time.sleep(3)
continue
#We'll store the device to ssid mapping here.
SSIDList = {}
#We'll store timestamps here
lastUpdate = {}
#Packet counts here
transmitted = {}
#Last time stats() was run:
lastStats = calendar.timegm(time.gmtime())
#Load any preconfigured mappings
if conf.has_section('Devices'):
print("Loading predefined SSID mappings.")
for device in conf['Devices'].keys():
SSIDList[conf['Devices'][device]] = device.upper()
print("Static mapping: " + SSIDList[conf['Devices'][device]] + " -> " + device.upper())
#Get an SSID
def getSSID(DID):
global lastUpdate, SSIDList, transmitted, SSNum, Call
if not DID: # Don't map None
return None
#If we have a Devices section, the SSID list is static.
if DID not in SSIDList:
if conf.has_section('Devices'):
return None
SSIDList[DID] = ''.join([Call,"-",str(SSNum)])
SSNum = SSNum + 1
print("Mapping: " + DID + " -> " + SSIDList[DID])
#Add a timestamp on the first call
# This prevents us from redelivering an old message, which can stay
# in the feed.
if DID not in lastUpdate:
lastUpdate[DID] = calendar.timegm(time.gmtime())
if DID not in transmitted:
transmitted[DID] = 0
return SSIDList[DID]
#APRS-IS Setup
AIS = None
reconnect()
#Get information from a Placemark
def parsePlacemark(Placemark):
#We only care about the Placemarks with the ExtendedData sections.
if not Placemark.getElementsByTagName('ExtendedData'):
return None
#Now process the extended data into something easier to handle.
extended = {}
for xd in Placemark.getElementsByTagName('ExtendedData')[0].getElementsByTagName('Data'):
if not xd.getElementsByTagName('value')[0].firstChild == None:
extended[xd.getAttribute('name')] = xd.getElementsByTagName('value')[0].firstChild.nodeValue
#Make sure the device mapping is good.
if not 'IMEI' in extended:
return None
if not getSSID(extended['IMEI']):
return None
#Now build the position vector
latitude = None
longitude = None
elevation = None
velocity = None
course = None
uttime = None
device = None
IMEI = extended['IMEI']
if 'Latitude' in extended and 'Longitude' in extended:
latitude = float(extended['Latitude'])
longitude = float(extended['Longitude'])
if 'Elevation' in extended:
#Altitude needs to be in feet above sea-level
# what we get instead is a string with a number of meters
# at the beginning.
elevation = re.sub(r'^(-?\d+\.?\d+)\s*m.*',r'\1',extended['Elevation'])
elevation = float(elevation) * 3.2808399
if 'Velocity' in extended:
#Velocity in knots, according to APRS.
velocity = float(re.sub(r'(\d+\.?\d+).*',r'\1', extended['Velocity']))*0.5399568
if 'Course' in extended:
#... and the course is just a heading in degrees.
course = float(re.sub(r'(\d+\.?\d+).*',r'\1', extended['Course']))
uttime = time.strptime(extended['Time UTC'] + " UTC","%m/%d/%Y %I:%M:%S %p %Z")
device = extended['Device Type']
#If we have SMS data, add that.
if 'Text' in extended:
comment = extended['Text']
else: #Default comment
comment = conf['APRS']['Comment']
return [device,IMEI,ARPreamble,uttime,latitude,longitude,elevation,course,velocity,comment]
#Return a list of all events available. Each one is a list of arguments
# for the below sendAPRS function
def getEvents():
events = []
try:
KML = http.open(conf['inReach']['URL']).read()
except Exception as e:
print("Error reading URL: " + conf['inReach']['URL'])
return events
try:
data = xml.dom.minidom.parseString(KML).documentElement
except Exception as e:
print("Can't process KML feed on this pass.")
return events
#The first placemark will have the expanded current location information.
for PM in data.getElementsByTagName('Placemark'):
res = parsePlacemark(PM)
if res:
events.append(res)
return events
#Compile and send an APRS packet from the given information
#According to spec, one valid format for location beacons is
# @092345z/4903.50N/07201.75W>088/036
# with a comment on the end that can include altitude and other
# information.
## Arguments are:
# Device type, Device ID, APRS Preamble, struct Timestamp, float Latitude
# float Longitude, float Altitude in feet, int float course in degrees,
# float speed in knots, comment
def sendAPRS(device, DevID, ARPreamble, tstamp, lat, long, alt, course, speed, comment):
global conf, transmitted, lastUpdate
etime = calendar.timegm(tstamp)
#Latitude conversion
#We start with the truncated degrees, filled to two places
# then add fractional minutes two 2x2 digits.
slat = str(abs(math.trunc(lat))).zfill(2)
slat += '{:.02f}'.format(round((abs(lat)-abs(math.trunc(lat)))*60,2)).zfill(5)
if lat > 0:
slat += "N"
else:
slat += "S"
#Longitude
slong = str(abs(math.trunc(long))).zfill(3)
slong += '{:.02f}'.format(round((abs(long)-abs(math.trunc(long)))*60,2)).zfill(5)
if long > 0:
slong += "E"
else:
slong += "W"
pos = ''.join([slat,conf['APRS']['Separator'],slong,conf['APRS']['Symbol']])
gateInfo = ''.join([" : ", NAME, " v", REV, " : ", platform.system(), " on ", platform.machine(), " : "])
if device:
gateInfo = gateInfo + device
aprsPacket = ''.join([getSSID(DevID),ARPreamble, ':@', time.strftime("%d%H%Mz",tstamp), pos])
#Check to make sure the update is new:
if not etime > lastUpdate[DevID]:
return None
#In theory a course/speed of 000/000 sholdn't be much different
# from not reporting one, but also in theory, more space is
# available for a comment if we don't add the data extension.
if (speed != None) and (course != None):
aprsPacket = ''.join([aprsPacket,str(round(course)).zfill(3), '/', str(min(round(speed),999)).zfill(3)])
#Same with altitude:
if alt:
#We need six digits of altitude.
comment = "/A=" + str(round(alt)).zfill(6)[0:6] + comment
#Regardless:
comment = comment + gateInfo
#We have the whole comment in one place now.
#In the format we're using, APRS comments can be 36 characters
# ... but APRS-IS doesn't seem to care, so leave this off.
#comment = comment[:36]
aprsPacket = aprsPacket + comment
try:
aprslib.parse(aprsPacket) # For syntax
AIS.sendall(aprsPacket)
#If the above doesn't raise an exception, we should assume we've sent the packet.
lastUpdate[DevID] = calendar.timegm(tstamp)
transmitted[DevID] += 1
print("Sent: " + aprsPacket)
except Exception as e:
print("Could not send APRS packet: ")
print(aprsPacket)
print("Attempting reconnect just in case.")
reconnect()
return None
def stats():
global transmitted, lastStats
lastStats = calendar.timegm(time.gmtime())
print("----------------Packet Forwarding Summary----------------")
print("|\t" + time.strftime("%Y-%m-%d %R",time.localtime()))
print("| SSID DevID Packets forwarded")
for device in transmitted:
print("| " + getSSID(device) + "\t" + device + "\t\t" + str(transmitted[device]))
print("---------------------------------------------------------")
print()
#... and here is the main loop.
while True:
for packet in getEvents():
sendAPRS(*packet) #Otherwise a list of sendAPRS args for the next packet to send.
if "Logstats" in conf["General"]:
if calendar.timegm(time.gmtime()) > lastStats + conf.getint("General","Logstats"):
stats()
time.sleep(conf.getfloat('General','Period')) |
The following adjustment was made to change the server ir-aprsisd AIS = aprslib.IS(conf['APRS']['SSID'],passwd=conf['APRS']['Password'],host=conf['APRS']['Host'],port=conf['APRS']['Port'],skip_login=conf['APRS']['Skip_Login']) |
ir-aprsisd.cfg
|
Unfortunately, the original script doesn't work. Info:
|
Hello, it's been a bit of time since I wrote this, so I'll need to dig in a bit to see what I've done. First, it looks like you're using Windows. This may not be related to the problem, but I feel like I should mention that I have never tested this on a Windows machine, and I don't know whether anyone has. You might run into trouble that the rest of us have not, though I'm happy to help you work it out. I should ask you what the original error was before you changed the script. It might give me some direction. Would you mind pasting the error in? |
The problem is that no packets are sent and the connection to the APRS server cannot be established. |
If you can't connect to the APRS server, that's definitely a problem. So did the program initially start up and run ok, and just not send any packets? Also, I never really wanted to connect to an alternate host, and since nobody else requested the feature, you might actually be the first to need it, but it looks reasonably easy to add that feature, and I think it's a good idea. I'll try to go ahead and do that soon. |
Also, do you get "could not send APRS packet" errors? |
Hi, |
Hello,
unfortunately the script doesn't work.
However, no packages will be sent.
Can someone fix the script?
Greeting
The text was updated successfully, but these errors were encountered: