-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAPRSClient.py
More file actions
128 lines (111 loc) · 4.71 KB
/
APRSClient.py
File metadata and controls
128 lines (111 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python
# Import required modules
from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
from termcolor import colored
import sys, re
# Variables that we'll need later
server = None
port = None
callsign = None
passcode = None
receive_filter = None
# The actual APRS-IS connector
class APRSConnector(LineReceiver):
def connectionMade(self):
print colored(server + " <<< " + "Logging in...", 'cyan')
self.sendLine("user " + callsign + " pass " + passcode + " vers Python/APRS 0.1 filter " + receive_filter)
def lineReceived(self, line):
parser = APRSParser(line)
parser.parse()
# APRSConnector factory class
class APRSConnectorFactory(ClientFactory):
protocol = APRSConnector
def clientConnectionFailed(self, connector, reason):
print 'connection failed:', reason.getErrorMessage()
reactor.stop()
def clientConnectionLost(self, connector, reason):
print 'connection lost:', reason.getErrorMessage()
reactor.stop()
# APRSParser class
class APRSParser:
packet = None
# These data types are taken directly from the APRS spec at http://aprs.org/doc/APRS101.PDF
# This is not an exhaustive list. These are the most common ones, and were added during
# testing.
data_types = {'!' : 'Position without timestamp',
'_' : 'Weather Report (without position)',
'@' : 'Position with timestamp (with APRS messaging)',
'/' : 'Position with timestamp (no APRS messaging)',
'=' : 'Position without timestamp (with APRS messaging)',
'T' : 'Telemetry data',
';' : 'Object',
'<' : 'Station Capabilities',
'>' : 'Status',
'`' : 'Current Mic-E Data (not used in TM-D700)',
'?' : 'Query',
'\'' : 'Old Mic-E Data (but Current data for TM-D700)',
':' : 'Message',
'$' : 'Raw GPS data or Ultimeter 2000',
}
def __init__(self, packet):
self.packet = packet
def parse(self):
if self.packet is not None:
if self.packet[0] == "#":
# We have a message beginning with '#' (a server message)
global server
print colored(server + " >>> " + self.packet, 'green')
else:
# Assume we have an APRS message, so...
# Pull the source callsign, destination, path and data from the raw packet
packet_segments = re.search('([\w\-]+)>([\w\-]+),([\w\-\*\,]+):(.*)', self.packet)
if packet_segments is not None:
(callsign, destination, path, data) = packet_segments.groups()
print "-----------------------------------------------------------------------------------------------------------"
print "RAW PACKET : " + self.packet
print "CALLSIGN : " + colored(callsign, 'yellow')
print "DESTINATION: " + colored(destination, 'cyan')
print "PATH : " + path
print "DATA : " + data
print " TYPE : " + self.parse_id(data)
else:
# We couldn't parse the packet
print colored("Could not parse - possibly non-packet data from server?", 'red')
def parse_id(self, data):
# Get the first character of the data field, and look it up
type_id = data[0]
data_type = self.data_types.get(type_id)
# Check we have a valid data type
if data_type is None:
# The spec allows '!' (and *only* '!') to appear anywhere in the first 40 characters of the data string to be valid
check_for_bang = re.search('!', data[0:40])
if check_for_bang is not None:
return colored(self.data_types.get('!'), 'green') + " ('!' at position " + str(check_for_bang.start()) + ")"
else:
# Couldn't find '!' in the first 40 characters either, so return 'Unknown'
return colored("Unknown", 'magenta')
else:
# Return the data type
return colored(data_type, 'green')
class APRSClientException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
def connect(server_arg, port_arg):
global server, port
server = server_arg
port = port_arg
print "Connecting to APRS server " + server + ", port " + str(port)
try:
if callsign is None or passcode is None or receive_filter is None:
raise APRSClientException("Missing callsign, passcode or filter.")
factory = APRSConnectorFactory()
reactor.connectTCP(server, port, factory)
reactor.run()
except APRSClientException, e:
print e.value
if __name__ == '__main__':
main()