Coder Social home page Coder Social logo

Not working about ir-aprsisd HOT 9 CLOSED

kemenril avatar kemenril commented on August 12, 2024
Not working

from ir-aprsisd.

Comments (9)

 avatar commented on August 12, 2024

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'))

from ir-aprsisd.

 avatar commented on August 12, 2024

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'])

from ir-aprsisd.

 avatar commented on August 12, 2024

ir-aprsisd.cfg

[inReach]
User		= ABC
#If this is defined, we will authenticate to the inReach service.
# If it is not, we will assume public access is ok.
Password	= ABC

# This should be the location of your KML feed.
URL		= https://share.garmin.com/Feed/Share/%(User)s

[APRS]
#This SSID is used for logging into APRS-IS, and also as a base ID for 
# generating callsigns for devices.  The first device found will be this
# SSID, the next will be this ID + 1, and so on.  If you define a [Devices]
# section, it is _only_ used for the login, and the device mapping must be
# given in full in the [Devices] section.
SSID		= ABC
# If you don't have a password, you can use the --genpass command-line
# option to calculate it.
Password	= ABC
Skip_Login  = True
Port		= 27235 
Host        = cbaprs.de 

#If the separator is /, your icon will come from the primary symbol table.
# if it is \, it will draw from the secondary table.
Separator	= /
#This character represents an APRS icon from the table tied to Separator.
Symbol		= (

#This information is included at the end of each packet, along with some 
# other data.
Comment		= APRS-IS KML forwarder

#Define this section if you'd like to enforce an SSID to IMEI mapping.
# It must contain all devices you want to publish.  Anything without a
# mapping defined will be ignored if this section exists.
#[Devices]
#N0CALL-1	= 987654322987656
#N0CALL-15	= 987654321987656
#N0CALL-8	= 092847784398753

[General]

# Frequency in seconds with which to log packet forwarding stats to STDOUT
# If this is less than Period, stats will only be logged every Period seconds.
# Comment it out to skip printing packet stats entirely.
Logstats        = 10

# KML polling interval in seconds.
Period          = 10




from ir-aprsisd.

 avatar commented on August 12, 2024

Unfortunately, the original script doesn't work.
With the adjustments, unfortunately it doesn't work either.
No packages will be sent

Info:

PS C:\temp\iR-APRSISD-main> python.exe ir-aprsisd
Loaded configuration: C:\temp\iR-APRSISD-main\ir-aprsisd.cfg
Mapping: XXXXXXXXXXXXXXX -> C64IR1-1
----------------Packet Forwarding Summary----------------
|       2023-08-21 22:58
| SSID          DevID                   Packets forwarded
| C64IR1-1      XXXXXXXXXXXXXXX         0
---------------------------------------------------------

----------------Packet Forwarding Summary----------------
|       2023-08-21 22:58
| SSID          DevID                   Packets forwarded
| C64IR1-1      XXXXXXXXXXXXXXX         0
---------------------------------------------------------

from ir-aprsisd.

kemenril avatar kemenril commented on August 12, 2024

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?

from ir-aprsisd.

 avatar commented on August 12, 2024

The problem is that no packets are sent and the connection to the APRS server cannot be established.
I created new scripts so I can send the data.
Greeting

from ir-aprsisd.

kemenril avatar kemenril commented on August 12, 2024

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.

from ir-aprsisd.

kemenril avatar kemenril commented on August 12, 2024

Also, do you get "could not send APRS packet" errors?

from ir-aprsisd.

 avatar commented on August 12, 2024

Hi,
I also create a script.
Unfortunately without DB, I don't have the experience.
Of course, it would be great if the data is stored in a DB, then you could read the data with e.g. Google Earth.
Maybe this is an inspiration for you to revise your script.
Greeting

from ir-aprsisd.

Related Issues (5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.