-
Notifications
You must be signed in to change notification settings - Fork 0
/
xbeed.py
executable file
·341 lines (278 loc) · 12.2 KB
/
xbeed.py
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#!/usr/bin/env python
# encoding: utf-8
"""
xbeed.py
Communicates with an XBee Series 2.5 module thorough a serial port.
Allows access to the Zigbee PAN through DBUS or XML-RPC.
Created by Rob O'Dwyer on 2009-10-21.
Copyright (c) 2009 Turk Innovations. All rights reserved.
"""
import os
import sys
from optparse import OptionParser
from serial import Serial
from struct import pack, unpack
from StringIO import StringIO
import yaml
import gobject
import dbus
import dbus.service
import dbus.mainloop.glib
XBEED_SERVICE = 'org.turkinnovations.xbeed'
XBEED_INTERFACE = 'org.turkinnovations.xbeed.XBeeInterface'
XBEED_DAEMON_OBJECT = '/XBeeInterfaces/%s'
XBEED_MODULE_OBJECT = '/XBeeModules/%X'
class XBeeDaemon(dbus.service.Object):
"""
Main class which connects dbus API and serial communication
name: Unique DBUS object name for this instance
All other arguments are passed off to the underlying pySerial implementation
"""
def __init__(self, name, port, escaping=True, baudrate=9600):
if escaping:
self.serial = EscapingSerial(port=port, baudrate=baudrate, timeout=0)
else:
self.serial = Serial(port=port, baudrate=baudrate, timeout=0)
self.object_path = XBEED_DAEMON_OBJECT % name
self.partial = PartialFrame()
dbus.service.Object.__init__(self, BUS_NAME, self.object_path)
gobject.io_add_watch(self.serial.fileno(), gobject.IO_IN, self.serial_read)
def serial_read(self, fd, condition, *args):
""" Called when there is data available from the serial port """
buffer = self.serial.read(256)
print 'xbeed: read %d bytes' % len(buffer)
try:
if(self.partial.add(buffer)):
packet = XBeeModuleFrame.parse(*self.partial.get_data())
self.handle_packet(packet)
except ChecksumFail, e:
print 'xbeed: ', e
except UnknownFrameType, e:
print 'xbeed: ', e
return True # Keep calling this function when data is available
def handle_packet(self, packet):
if isinstance(packet, ReceivePacket):
XBeeModule.get(packet.hw_addr).RecievedData(packet.rf_data, packet.hw_addr)
@dbus.service.method(XBEED_INTERFACE, in_signature='ayty', out_signature='', byte_arrays=True)
def SendData(self, rf_data, hw_addr, frame_id):
""" Sends an RF data packet to the specified XBee module """
print 'xbeed: SendData called, sending %d bytes to address 0x%X' % (len(rf_data), hw_addr)
packet = TransmitRequest(hw_addr=hw_addr, rf_data=str(rf_data), frame_id=frame_id)
packet.write_frame(self.serial)
@dbus.service.method(XBEED_INTERFACE, in_signature='s', out_signature='s')
def GetInfo(self, arg):
""" Returns some marginally useful info about the current xbeed instance """
print 'xbeed: GetInfo called'
return self.object_path
@dbus.service.method(XBEED_INTERFACE, in_signature='tay', out_signature='', byte_arrays=True)
def FakeReceivedData(self, rf_data, hw_addr):
print 'xbeed: Faking receive of packet from 0x%X, %d bytes' % (hw_addr, len(rf_data))
XBeeModule.get(hw_addr).RecievedData(rf_data, hw_addr)
class EscapingSerial(Serial):
"""
Handles escaping and un-escaping of framing bytes
Bytes 0x7E, 0x7D, 0x11, 0x13 are escaped as [0x7D, byte^0x20]
"""
def __init__(self, *args, **kwargs):
Serial.__init__(self, *args, **kwargs)
self.escape_flag = False
def write(self, data):
Serial.write(self, ''.join(escape(data)))
def read(self, size=1):
data = Serial.read(self, size)
out = StringIO()
for byte in data:
if self.escape_flag:
out.write(chr(ord(byte) ^ 0x20))
self.escape_flag = False
elif byte == '\x7D':
self.escape_flag = True
else:
out.write(byte)
return out.getvalue()
class PartialFrame(object):
""" Stores up serial data until a full frame is received """
def __init__(self):
self.buffer = ''
self.last = None
def add(self, data):
packets = ''.join([self.buffer, data]).split('\x7E')
packets = packets[1:] if packets[0] == '' else packets
if(len(packets[0]) > 6):
frame_len, api_id = unpack('>HB', packets[0][0:3])
if len(packets[0]) >= (frame_len + 3):
self.last = (packets[0][:frame_len+3], api_id, frame_len)
self.buffer = ''.join(packets[1:])
return True
self.buffer = ''.join(packets)
return False
def get_data(self):
""" Returns all the buffered data """
return self.last
class XBeeModuleFrame(object):
"""Abstract class for parsing serial API packets"""
# Used for mapping frame data to packet types based on api_id field
api_ids = {}
# Used to format frame signatures based on variable-length fields
length_mod = None
@classmethod
def parse(cls, data, api_id, length):
# Raise exception containing real / expected value if checksum fails
if not validate_checksum(data, offset=2):
raise ChecksumFail(ord(data[-1]), generate_checksum(data, offset=2))
# Parse and return specific packet structure
if api_id not in API_IDS:
raise UnknownFrameType("Received unknown frame, api_id=0x%X" % api_id)
ptype = API_IDS[api_id]
if ptype.length_mod:
signature = ptype.signature % (length - ptype.length_mod)
else:
signature = ptype.signature
fields = unpack(signature, data)
return ptype(*fields)
class TransmitStatus(XBeeModuleFrame):
""" When a TX Request is completed, the module sends a TX Status message. This message
will indicate if the packet was transmitted successfully or if there was a failure."""
signature = '>3xBHBBBx'
api_id = 0x8B
statuses = {0x00:'Success', 0x02:'CCA Failure', 0x15:'Invalid Destination', 0x21:'ACK Failure',
0x22:'Not Joined', 0x23:'Self-Addressed', 0x24:'Address Not Found', 0x25:'Route Not Found'}
def __init__(self, frame_id, net_addr, retries, status, discovery):
self.frame_id = frame_id
self.net_addr = net_addr
self.retries = retries
self.status = (status, self.statuses.get(status, 'Unknown Status'))
self.discovery = discovery
def __str__(self):
return '[Status for frame %d: %s]' % (self.frame_id, self.status)
class ReceivePacket(XBeeModuleFrame):
""" When the module receives an RF packet, it is sent out the UART using this message type. """
signature = '>3xQHB%dsx'
length_mod = 12
api_id = 0x90
def __init__(self, hw_addr, net_addr, options, rf_data):
self.hw_addr = hw_addr
self.net_addr = net_addr
self.options = options
self.rf_data = rf_data
def __str__(self):
return '[Received %d bytes from 0x%X]' % (len(self.rf_data), self.hw_addr)
class ModemStatus(XBeeModuleFrame):
""" RF module status messages are sent from the module in response to specific conditions."""
signature = '>3xBx'
api_id = 0x8A
statuses = {0:'Hardware Reset', 1:'Watchdog Timer Reset', 2:'Associated', 3:'Disassociated',
4:'Synchonization Lost', 5:'Coordinator Re-alignment', 6:'Coordinator started'}
def __init__(self, status):
self.status = (status, self.statuses.get(status, 'Unknown Status'))
def __str__(self):
return '[Modem Status: %s]' % (self.status,)
class XBeeClientFrame(object):
"""Abstract class for constructing new packets to send to XBee module"""
def get_frame(self):
""" Returns the binary frame representation of the packet """
s = StringIO()
self.write_frame(s)
return s.getvalue()
def write_frame(self, fd):
""" Writes the binary representation of the packet to a file-like object """
try:
fd.write(pack('>BH', 0x7E, self.length))
frame_data = pack(self.signature, *self.fields)
checksum = generate_checksum(frame_data)
fd.write(frame_data)
fd.write(chr(checksum))
except OSError:
print 'xbeed: Error writing to serial port'
class TransmitRequest(XBeeClientFrame):
api_id = 0x10
signature = '>BBQHBB%ds'
def __init__(self, hw_addr, rf_data, frame_id=0, net_addr=0xFFFE):
self.frame_id = frame_id
self.fields = (self.api_id, frame_id, hw_addr, net_addr, 0x00, 0x00, rf_data)
self.signature = self.signature % len(rf_data)
self.length = len(rf_data) + 14
API_IDS = {0x8B: TransmitStatus, 0x90: ReceivePacket, 0x8A: ModemStatus}
class XBeeModule(dbus.service.Object):
""" Represents a remote XBee module, and signals when packets are received
from that module."""
modules = {}
def __init__(self, hw_addr):
dbus.service.Object.__init__(self, BUS_NAME, XBEED_MODULE_OBJECT % (hw_addr))
@classmethod
def get(cls, hw_addr):
if hw_addr not in cls.modules:
cls.modules[hw_addr] = XBeeModule(hw_addr)
return cls.modules[hw_addr]
@dbus.service.signal(dbus_interface=XBEED_INTERFACE, signature='ayt')
def RecievedData(self, rf_data, hw_addr):
""" Called when data is received from the module """
pass
def generate_checksum(frame, offset=0):
""" Generates the checksum byte for this frame. The algorithm consists of
adding all bytes in the frame data and subtracting the result from 0xFF.
NOTE: this assumes the checksum is initially set to zero or not included """
return 0xFF - (reduce(lambda x, y: x + ord(y), frame[offset:], 0x00) & 0xFF)
def validate_checksum(frame, offset=0):
""" Validates the checksum by adding all bytes and comparing to 0xFF """
return (reduce(lambda x, y: x + ord(y), frame[offset:], 0x00) & 0xFF) == 0xFF
class ChecksumFail(Exception):
def __str__(self):
return '0x%X != 0x%X' % self.args if len(self.args) is 2 else None
class InvalidPacketLength(Exception): pass
class UnknownFrameType(Exception):
def __repr__():
return 'Unknown frame type'
class SendFailure(dbus.DBusException):
_dbus_error_name = 'org.turkinnovations.xbeed.SendFailure'
class UnknownFrameType(Exception):
pass
def escape(data):
yield data[0]
for byte in data[1:]:
if byte in ['\x7E', '\x7D', '\x11', '\x13']:
yield '\x7D'
yield chr(ord(byte) ^ 0x20)
else:
yield byte
def unescape(data):
escape_flag = False
for byte in data:
if byte == '\x7D':
escape_flag = True
else:
yield chr(ord(byte) ^ 0x20) if escape_flag else byte
escape_flag = False
def get_daemon(name, bus):
return bus.get_object(XBEED_SERVICE, XBEED_DAEMON_OBJECT % name)
def get_module(hw_addr, bus):
return bus.get_object(XBEED_SERVICE, XBEED_MODULE_OBJECT % hw_addr)
def fake_data(name, bus, hw_addr, data):
daemon = get_daemon(name, bus)
daemon.FakeReceivedData(dbus.ByteArray(data), dbus.UInt64(hw_addr))
def print_hex(data):
for byte in data:
print '0x%X ' % ord(byte),
BUS_NAME = None
def main():
global BUS_NAME
usage = "usage: %prog [options]"
parser = OptionParser(usage)
parser.add_option("-f", "--config-file", dest="config", type="string", default='core.yml',
help="default configuration file")
(options, args) = parser.parse_args()
try:
conf_file = os.getenv('TURK_CORE_CONF', options.config)
conf = yaml.load(open(conf_file, 'rU'))['xbeed']
except:
print 'xbeed: error loading config file'
parser.print_help()
exit(1)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = getattr(dbus, conf.get('bus', 'SystemBus'))()
BUS_NAME = dbus.service.BusName(XBEED_SERVICE, bus)
daemon = XBeeDaemon(name=conf['name'], port=conf['port'], baudrate=conf['baudrate'], escaping=conf['escaping'])
mainloop = gobject.MainLoop()
mainloop.run()
if __name__ == "__main__":
sys.exit(main())