84e7a0b0ac3ac63351dcda7132599be0a5be5c1c
[iot2.git] / benchmarks / other / XbeePythonDriver / xbee_driver.py
1 from xbee import ZigBee
2 import serial
3 import time
4 import collections
5 import sys
6 import getopt
7 from socket import *
8 import traceback
9 from threading import Thread, Lock
10 import random
11 import threading
12
13
14 # -----------------------------------------------------------------------------
15 # Constants ans Pseudo-Constants
16 # -----------------------------------------------------------------------------
17 UDP_RECEIVE_PORT = 5005        # port used for incoming UDP data
18 UDP_RECEIVE_BUFFER_SIZE = 4096  # max buffer size of an incoming UDP packet
19 SYSTEM_MASTER_ADDRESS = ("192.168.2.108", 12345) # ip address and portof the system master node computer ip addr running java
20
21 # time for messages to wait for a response before the system clears away that 
22 # sequence identifier
23 ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5 
24
25 ZIGBEE_SERIAL_PORT = "/dev/cu.usbserial-DN01DJIP"  # USB-Serial port of local radio
26 ZIGBEE_SERIAL_BAUD = 115200                       # Baud rate for above port
27
28 # address of our local zigbee radio
29 ZIGBEE_DEVICE_ADDRESS = "xxxxxxxxxxxxxxxx"
30
31 # -----------------------------------------------------------------------------
32 # Global Variables and Objects
33 # -----------------------------------------------------------------------------
34
35 # signals to see if a request needs to be made
36 didGetLocalRadioHighAddress = False;
37 didGetLocalRadioLowAddress = False;
38
39 # zigbee communications object and its mutex
40 zigbeeConnection = None
41 zigbeeConnectionMutex = Lock()
42
43 # zigbee mapping from long to short object dict
44 zigbeeLongShortAddr = dict()
45 zigbeeLongShortAddrMutex = Lock()
46
47 # zigbee mapping from a sequence number to a client 
48 # for correct response handling
49 zigbeeSeqNumberToClient = dict()
50 zigbeeSeqNumberToClientMutex = Lock()
51
52 zigeeBindRequest = dict()
53 zigeeBindRequestMutex = Lock()
54
55 # Keeps record of where to send callbacks to when an HA message is received
56 zibeeHACallback = dict()
57 zibeeHACallbackMutex = Lock()
58
59
60 # Keeps a record of device addresses whose short addresses have not been 
61 # determined yet
62 zigbeeUnregisteredAddresses = []
63 zigbeeUnregisteredAddressesMutex = Lock()
64
65 # used to signal all threads to end
66 doEndFlag = False
67
68
69 # 2 sockets, one for sending (not bound to a port manually)
70 # and one for receiving, known port binding by application
71 # both UDP sockets
72 sendSoceket = socket(AF_INET, SOCK_DGRAM)
73 receiveSoceket = socket(AF_INET, SOCK_DGRAM)
74
75
76 # zigbee address authority list
77 zigbeeAddressAuthorityDict = dict()
78
79 # -----------------------------------------------------------------------------
80 # Helper Methods
81 # -----------------------------------------------------------------------------
82
83
84 def parseCommandLineArgs(argv):
85     global ZIGBEE_SERIAL_PORT
86     global ZIGBEE_SERIAL_BAUD
87     try:
88         opts, args = getopt.getopt(
89             argv, "hp:b:u:", ["port=", "baud=", "udpport="])
90
91     except getopt.GetoptError:
92         print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
93         sys.exit(2)
94
95     for opt, arg in opts:
96         if opt == '-h':
97             print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
98             sys.exit()
99
100         elif opt in ("-p", "--port"):
101             ZIGBEE_SERIAL_PORT = arg
102
103         elif opt in ("-b", "--baud"):
104             try:
105                 ZIGBEE_SERIAL_BAUD = int(arg)
106             except ValueError:
107                 print "Buad rate must be an integer"
108                 sys.exit()
109
110
111 # -------------
112 # Convenience (Stateless)
113 # -------------
114
115 def hexListToChar(hexList):
116     ''' Method to convert a list/string of characters into their corresponding values
117
118         hexList -- list or string of hex characters
119     '''
120     retString = ""
121     for h in hexList:
122         retString += chr(int(h, 16))
123     return retString
124
125 def splitByN(seq, n):
126     ''' Method to split a string into groups of n characters
127
128         seq -- string
129         n -- group by number
130     '''
131     return [seq[i:i+n] for i in range(0, len(seq), n)]
132
133 def changeEndian(hexString):
134     ''' Method to change endian of a hex string
135
136         hexList -- string of hex characters
137     '''
138     split = splitByN(hexString, 2) # get each byte
139     split.reverse();               # reverse ordering of the bytes
140
141     # reconstruct 
142     retString = ''
143     for s in split:
144         retString += s
145     return retString
146
147 def printMessageData(data):
148     ''' Method to print a zigbee message to the console
149
150         data -- pre-parsed zigbee message
151     '''
152     for d in data:
153         print d, ' : ',
154         for e in data[d]:
155             print "{0:02x}".format(ord(e)),
156         if (d == 'id'):
157             print "({})".format(data[d]),
158         print
159
160 def hexStringToZigbeeHexString(hexString):
161     ''' Method to change a hex string to a string of characters with the hex values
162
163         hexList -- string of hex characters
164     '''
165     return hexListToChar(splitByN(hexString, 2))
166
167 def zigbeeHexStringToHexString(zigbeeHexString):
168     ''' Method to change string of characters with the hex values to a hex string
169
170         hexList -- string of characters with hex values
171     '''
172
173     retString = ''
174     for e in zigbeeHexString:
175         retString += "{0:02x}".format(ord(e))
176     return retString
177
178 def zclDataTypeToBytes(zclPayload):
179     ''' Method to determine data length of a zcl attribute
180
181         zclPayload -- ZCL payload data, must have dataType as first byte
182     '''
183     attrType = ord(zclPayload[0])
184
185     if(attrType == 0x00):
186         return 0
187     elif (attrType == 0x08):
188         return 1
189     elif (attrType == 0x09):
190         return 2
191     elif (attrType == 0x0a):
192         return 3
193     elif (attrType == 0x0b):
194         return 4
195     elif (attrType == 0x0c):
196         return 5
197     elif (attrType == 0x0d):
198         return 6
199     elif (attrType == 0x0e):
200         return 7
201     elif (attrType == 0x0f):
202         return 8
203     elif (attrType == 0x10):
204         return 1
205     elif (attrType == 0x18):
206         return 1
207     elif (attrType == 0x19):
208         return 2
209     elif (attrType == 0x1a):
210         return 3
211     elif (attrType == 0x1b):
212         return 4
213     elif (attrType == 0x1c):
214         return 5
215     elif (attrType == 0x1d):
216         return 6
217     elif (attrType == 0x1e):
218         return 7
219     elif (attrType == 0x1f):
220         return 8
221     elif (attrType == 0x20):
222         return 1
223     elif (attrType == 0x21):
224         return 2
225     elif (attrType == 0x22):
226         return 3
227     elif (attrType == 0x23):
228         return 4
229     elif (attrType == 0x24):
230         return 5
231     elif (attrType == 0x25):
232         return 6
233     elif (attrType == 0x26):
234         return 7
235     elif (attrType == 0x27):
236         return 8
237     elif (attrType == 0x28):
238         return 1
239     elif (attrType == 0x29):
240         return 2
241     elif (attrType == 0x2a):
242         return 3
243     elif (attrType == 0x2b):
244         return 4
245     elif (attrType == 0x2c):
246         return 5
247     elif (attrType == 0x2d):
248         return 6
249     elif (attrType == 0x2e):
250         return 7
251     elif (attrType == 0x2f):
252         return 8
253     elif (attrType == 0x30):
254         return 1
255     elif (attrType == 0x31):
256         return 2
257     elif (attrType == 0x38):
258         return 2
259     elif (attrType == 0x39):
260         return 4
261     elif (attrType == 0x3a):
262         return 8
263     elif (attrType == 0x41):
264         return ord(zclPayload[1])
265     elif (attrType == 0x42):
266         return ord(zclPayload[1])
267     elif (attrType == 0x43):
268         return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
269     elif (attrType == 0x44):
270         return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
271     elif (attrType == 0xe0):
272         return 4
273     elif (attrType == 0xe1):
274         return 4
275     elif (attrType == 0xe2):
276         return 4
277     elif (attrType == 0xe8):
278         return 2
279     elif (attrType == 0xe9):
280         return 2
281     elif (attrType == 0xea):
282         return 4
283     elif (attrType == 0xf0):
284         return 8
285     elif (attrType == 0xf1):
286         return 16
287     elif (attrType == 0xff):
288         return 0
289
290 # -------------
291 # Other
292 # -------------
293
294 def createSequenceNumberForClient(addr, packetId):
295     ''' Method to get and store a sequence number with a specific client 
296         for a specific message.
297
298         addr -- UDP address of the client to associate with the seq. number
299         packetId -- packet id from the UDP packet
300     '''
301     # keep trying to find a number to return
302     while(True):
303
304         # get the current time
305         epoch_time = int(time.time())
306
307         # get the current list of used numbers
308         zigbeeSeqNumberToClientMutex.acquire()
309         keysList = zigbeeSeqNumberToClient.keys()
310         zigbeeSeqNumberToClientMutex.release()
311     
312         # if all the numbers are already used
313         if(len(keysList) == 256):
314
315             # get a list of all the items
316             zigbeeSeqNumberToClientMutex.acquire()
317             itemsList = zigbeeSeqNumberToClient.items()
318             zigbeeSeqNumberToClientMutex.release()
319
320             # search for a number that is old enough to get rid of otherwise use -1
321             seqNum = -1
322             for item in itemsList:
323                 if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
324                     seqNumber = item[0]
325                     break
326
327             if(seqNum != -1):
328                 # replace the record with new data if we found one to replace
329                 zigbeeSeqNumberToClientMutex.acquire()
330                 zigbeeSeqNumberToClient[seqNumber] = (addr, epoch_time, packetId)
331                 zigbeeSeqNumberToClientMutex.release()
332
333             return seqNumber
334             
335         else:
336             # not all numbers used yet so pick one randomly
337             randNum = random.randrange(0,256)
338
339             # check if we are using the number yet
340             if(randNum not in keysList):
341
342                 # we are not so insert to keep track who this number is for and return it
343                 zigbeeSeqNumberToClientMutex.acquire()
344                 zigbeeSeqNumberToClient[randNum] = (addr, epoch_time, packetId)
345                 zigbeeSeqNumberToClientMutex.release()
346                 return randNum
347
348 def getConnectedRadioLongAddress():
349     """ Method to make sure we get the MAC address of our local radio"""
350     global zigbeeConnection
351     global zigbeeMutex
352
353     # keep looping until we get both the MSBs and the LSBs
354     while ((not didGetLocalRadioHighAddress) or (not didGetLocalRadioLowAddress)):
355
356         # reissue requests
357         zigbeeConnection.send('at', command="SH")
358         zigbeeConnection.send('at', command="SL")
359         
360         # sleep for a bit to give the radio time to respond before we check again
361         time.sleep(0.5)
362
363 def addressUpdateWorkerMethod():
364     ''' Method to keep refreshing the short addresses of the known zigbee devices'''
365     global doEndFlag
366     global zigbeeLongShortAddr
367     global zigbeeLongShortAddrMutex
368     global zigbeeUnregisteredAddresses
369     global zigbeeUnregisteredAddressesMutex
370     global zigbeeConnectionMutex
371     global zigbeeConnection
372
373     # keep looping until signaled to quit
374     while(not doEndFlag):
375
376         addrList = []
377
378         # add unregistered (short addresses unknown) devices so
379         # that we can look them up
380         zigbeeUnregisteredAddressesMutex.acquire()
381         addrList.extend(zigbeeUnregisteredAddresses)
382         zigbeeUnregisteredAddressesMutex.release()
383
384         # add the devices that we have short addresses for so we can 
385         # get their most recent short addresses
386         zigbeeLongShortAddrMutex.acquire()
387         addrList.extend(zigbeeLongShortAddr.keys())
388         zigbeeLongShortAddrMutex.release()
389
390         # Loop through all the addresses and send messages for each address
391         for ad in addrList:
392
393             # create payload for a query on the network for a short address
394             payload = '\x00'
395             payload += hexStringToZigbeeHexString(changeEndian(ad))
396             payload += '\x00'
397
398             # create and send binding command
399             zigbeeConnectionMutex.acquire()
400             zigbeeConnection.send('tx_explicit',
401                                 frame_id='\x01',
402                                 dest_addr_long=hexStringToZigbeeHexString(ad),
403                                 dest_addr='\xff\xfd',
404                                 src_endpoint='\x00',
405                                 dest_endpoint='\x00',
406                                 cluster='\x00\x00',  
407                                 profile='\x00\x00',
408                                 data=payload
409                                 )
410             zigbeeConnectionMutex.release()
411
412         time.sleep(1)
413
414
415 # -------------
416 # UDP 
417 # -------------
418
419 def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
420     ''' Method to send a success or fail back to a client.
421
422         addr -- UDP address to send packet to
423         packetTypeStr -- name of this specific packet
424         packetIdStr -- packet id to send
425         sucOrFail -- whether this is a success or fail message (True = success)
426         reason -- reason of failure (if needed, default is None)
427
428     '''
429
430     global sendSoceket
431
432     # construct the message
433     message = "type: " + packetTypeStr.strip() + "\n"
434     message += "packet_id: " + packetIdStr + "\n"
435
436     if(sucOrFail):
437         message += "response: success \n"
438     else:
439         message += "response: fail \n"
440         message += "reason: " + reason + "\n"
441
442     # send message in a UDP packet
443     sendSoceket.sendto(message,addr)
444
445 def processUdpZdoBindReqMessage(parsedData, addr):
446
447     shortAddr = None
448
449     if(zigbeeAddressAuthorityDict.has_key(addr)):
450         l = zigbeeAddressAuthorityDict[addr]
451         if(parsedData['device_address_long'] not in l):
452             return
453     else:
454         return
455
456     # get the short address for this device long address if possible
457     zigbeeLongShortAddrMutex.acquire()
458     if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
459         shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
460     zigbeeLongShortAddrMutex.release()
461
462     # if there is a short address than we can send the message
463     # if there is not one then we cannot since we need both the short and
464     # the long address
465     if(shortAddr != None):
466
467         # get a request number
468         seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
469         
470         # send back failure
471         if(seqNumber == -1):
472
473             # send an error message, could not get a sequence number to use at this time
474             sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'out_of_space')
475             return
476
477         # a bind request was made so must store and wait for response 
478         # before we setup callbacks, so keep just the data we need to create the callback
479         zigeeBindRequestMutex.acquire()
480         zigeeBindRequest[seqNumber] = (parsedData['device_address_long'],
481                                         parsedData['cluster_id'], 
482                                         parsedData['packet_id'], 
483                                         addr)
484         zigeeBindRequestMutex.release()
485
486         # construct the short and long addresses of the message for sending
487         # make sure they are in the correct format
488         destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
489         destShortAddr = hexStringToZigbeeHexString(shortAddr)
490
491         # create the payload data
492         payloadData = ""
493         payloadData += chr(seqNumber)
494         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_address_long']))
495         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_endpoint']))
496         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['cluster_id'])) 
497         payloadData += '\x03' 
498         payloadData += hexStringToZigbeeHexString(changeEndian(ZIGBEE_DEVICE_ADDRESS))
499         payloadData += '\x00'
500
501         # create and send binding command
502         zigbeeConnectionMutex.acquire()
503         zigbeeConnection.send('tx_explicit',
504                             frame_id='\x01',
505                             # frame_id=chr(seqNumber),
506                             dest_addr_long=destLongAddr,
507                             dest_addr=destShortAddr,
508                             src_endpoint='\x00',
509                             dest_endpoint='\x00',
510                             cluster='\x00\x21',  
511                             profile='\x00\x00',
512                             data=payloadData
513                             )
514         zigbeeConnectionMutex.release()
515
516
517     else:
518         # send a failure packet since there is no short address available
519         sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'short_address_unknown')
520         pass
521
522 def processUdpZdoUnBindReqMessage(parsedData, addr):
523     zibeeHACallbackMutex.acquire();
524     if(zibeeHACallback.has_key(parsedData['device_address_long'], parsedData['cluster_id'])):
525         zibeeHACallback(parsedData['device_address_long'], parsedData['cluster_id']).remove(addr)
526     zibeeHACallbackMutex.release()
527     sendUdpSuccessFail(addr, 'zdo_unbind_request', parsedData['packet_id'], True)
528
529
530
531 def processUdpSendAddressMessage(parsedData, addr):
532     ''' Method handle a send address command
533
534         parsedData -- Pre-parsed Data that was in the UDP packet.
535         addr -- Address (IP and Port) of the UDP packet origin.
536     '''
537     global zigbeeLongShortAddr
538     global zigbeeLongShortAddrMutex
539     global zigbeeUnregisteredAddresses
540     global zigbeeUnregisteredAddressesMutex
541     global sendSoceket
542
543     if(zigbeeAddressAuthorityDict.has_key(addr)):
544         l = zigbeeAddressAuthorityDict[addr]
545         if(parsedData['device_address_long'] not in l):
546             return
547     else:
548         return
549
550
551     # construct success message
552     message = "type: send_address_response\n"
553     message += "packet_id: " + parsedData['packet_id'] + "\n"
554     message += "response: success\n"
555
556     # tell client that we got their request
557     sendSoceket.sendto(message,addr)
558
559     # construct 
560     zigbeeLongShortAddrMutex.acquire()
561     doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
562     zigbeeLongShortAddrMutex.release()
563
564     if(doesHaveKey):
565         # long address is already registered with the system so no need to do anything
566         return
567
568     # long address not registered so add it for short address lookup
569     zigbeeUnregisteredAddressesMutex.acquire()
570     zigbeeUnregisteredAddresses.append(parsedData['device_address_long'])
571     zigbeeUnregisteredAddressesMutex.release()
572
573 def processUdpZclReadAttributesMessage(parsedData, addr):
574     ''' Method handle a ZCL read attribute command
575
576         parsedData -- Pre-parsed Data that was in the UDP packet.
577         addr -- Address (IP and Port) of the UDP packet origin.
578     '''
579
580     global zigbeeLongShortAddr
581     global zigbeeLongShortAddrMutex
582     global zigeeBindRequestMutex
583     global zigeeBindRequest
584     global zigbeeConnectionMutex
585     global zigbeeConnection
586
587
588     if(zigbeeAddressAuthorityDict.has_key(addr)):
589         l = zigbeeAddressAuthorityDict[addr]
590         if(parsedData['device_address_long'] not in l):
591             return
592     else:
593         return
594
595
596     shortAddr = None
597
598     # get the short address for this device long address if possible
599     zigbeeLongShortAddrMutex.acquire()
600     if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
601         shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
602     zigbeeLongShortAddrMutex.release()
603
604
605     # if there is a short address than we can send the message
606     # if there is not one then we cannot since we need both the short and
607     # the long address
608     if(shortAddr != None):
609
610         # get a request number
611         seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
612         
613         # send back failure
614         if(seqNumber == -1):
615
616             # send an error message, could not get a sequence number to use at this time
617             sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'out_of_space')
618             return
619
620         # get the info for sending
621         destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
622         destShortAddr = hexStringToZigbeeHexString(shortAddr)
623         profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
624         clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
625         dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
626
627         # get all the attributes
628         attributeIds = parsedData['attribute_ids'].split(',')
629
630         # create the payload data
631         payloadData = ""
632         payloadData += '\x00'
633         payloadData += chr(seqNumber)
634         payloadData += '\x00'
635
636         # make all the attributes payloads
637         for attr in attributeIds:
638             attr = attr.strip()
639             attr = changeEndian(attr)
640             payloadData += hexStringToZigbeeHexString(attr)
641
642         # create and send binding command
643         zigbeeConnectionMutex.acquire()
644         zigbeeConnection.send('tx_explicit',
645                             frame_id='\x01',
646                             # frame_id=chr(seqNumber),
647                             dest_addr_long=destLongAddr,
648                             dest_addr=destShortAddr,
649                             src_endpoint='\x00',
650                             dest_endpoint=dstEndpoint,
651                             cluster=clusterId,  
652                             profile=profileId,
653                             data=payloadData
654                             )
655         zigbeeConnectionMutex.release()
656
657
658     else:
659         # send a fail response
660         sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
661         pass
662
663 def processUdpZclConfigureReportingMessage(parsedData, addr):
664     ''' Method handle a zcl configure reporting message
665
666         parsedData -- Pre-parsed Data that was in the UDP packet.
667         addr -- Address (IP and Port) of the UDP packet origin.
668     '''
669
670     global zigbeeLongShortAddr
671     global zigbeeLongShortAddrMutex
672     global zigeeBindRequestMutex
673     global zigeeBindRequest
674     global zigbeeConnectionMutex
675     global zigbeeConnection
676
677
678     if(zigbeeAddressAuthorityDict.has_key(addr)):
679         l = zigbeeAddressAuthorityDict[addr]
680         if(parsedData['device_address_long'] not in l):
681             return
682     else:
683         return
684
685     shortAddr = None
686
687     # get the short address for this device long address if possible
688     zigbeeLongShortAddrMutex.acquire()
689     if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
690         shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
691     zigbeeLongShortAddrMutex.release()
692
693     # if there is a short address than we can send the message
694     # if there is not one then we cannot since we need both the short and
695     # the long address
696     if(shortAddr != None):
697
698         # get a request number
699         seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
700         
701         # send back failure
702         if(seqNumber == -1):
703             sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
704             return
705
706         destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
707         destShortAddr = hexStringToZigbeeHexString(shortAddr)
708         profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
709         clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
710         dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
711
712         # create the payload data
713         payloadData = ""
714         payloadData += '\x00'
715         payloadData += chr(seqNumber)
716         payloadData += '\x06'
717         payloadData += '\x00'
718         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['attribute_id']))
719         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['data_type']))
720         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['min_reporting_interval']))
721         payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['max_reporting_interval']))
722
723         if(parsedData.has_key('reportable_change')):
724             payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
725
726
727         # create and send binding command
728         zigbeeConnectionMutex.acquire()
729         zigbeeConnection.send('tx_explicit',
730                             frame_id='\x01',
731                             # frame_id=chr(seqNumber),
732                             dest_addr_long=destLongAddr,
733                             dest_addr=destShortAddr,
734                             src_endpoint='\x00',
735                             dest_endpoint=dstEndpoint,
736                             cluster=clusterId,  
737                             profile=profileId,
738                             data=payloadData
739                             )
740         zigbeeConnectionMutex.release()
741
742
743     else:
744         sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
745         pass
746
747 def processUdpPolicySet(parsedData, addr):
748     ''' Method handle a policy set message
749
750         parsedData -- Pre-parsed Data that was in the UDP packet.
751         addr -- Address (IP and Port) of the UDP packet origin.
752     '''
753     print "=================================================================="
754     print "Policy set: ", parsedData
755     
756     # do nothing if wrong source
757     if addr == SYSTEM_MASTER_ADDRESS:
758         key = (parsedData['ip_address'], int(parsedData['port']))
759         if (zigbeeAddressAuthorityDict.has_key(key)):
760             zigbeeAddressAuthorityDict[key].append(parsedData['device_address_long'])
761         else:
762             zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
763
764 def processUdpPolicyClear(parsedData, addr):
765     ''' Method handle a policy set message
766
767         parsedData -- Pre-parsed Data that was in the UDP packet.
768         addr -- Address (IP and Port) of the UDP packet origin.
769     '''
770     print "=================================================================="
771     print "Clear policy: ", parsedData
772     
773     # do nothing if wrong source
774     if addr == SYSTEM_MASTER_ADDRESS:
775         zigbeeAddressAuthorityDict.clear()
776
777 # -------------
778 # Zigbee 
779 # -------------
780
781 def processZigbeeATCommandMessage(parsedData):
782     ''' Method to process an AT zigbee message
783
784         parsedData -- Pre-parsed (into a dict) data from message.
785     '''
786     global ZIGBEE_DEVICE_ADDRESS
787     global didGetLocalRadioHighAddress
788     global didGetLocalRadioLowAddress
789
790     # command response for the high bytes of the local device long address
791     if(parsedData['command'] == 'SH'):
792         # convert the parameter to a string value (human readable)
793         value = ""
794         for e in parsedData['parameter']:
795             value += "{0:02x}".format(ord(e))
796
797         # set the correct portion of the address
798         ZIGBEE_DEVICE_ADDRESS = value + ZIGBEE_DEVICE_ADDRESS[8:]
799         
800         #signal that we got this part of the address
801         didGetLocalRadioHighAddress = True
802
803     # command response for the low bytes of the local device long address
804     elif(parsedData['command'] == 'SL'):
805         # convert the parameter to a string value (human readable)
806         value = ""
807         for e in parsedData['parameter']:
808             value += "{0:02x}".format(ord(e))
809
810         # set the correct portion of the address
811         ZIGBEE_DEVICE_ADDRESS = ZIGBEE_DEVICE_ADDRESS[0:8] + value
812
813         #signal that we got this part of the address
814         didGetLocalRadioLowAddress = True
815
816 def processZigbeeRxExplicitCommandMessage(parsedData):
817     ''' Method to process a rx-explicit zigbee message
818
819         parsedData -- Pre-parsed (into a dict) data from message.
820     '''
821     global zigeeBindRequestMutex
822     global zigeeBindRequest
823
824     # get the long and short addresses from the message payload since we can 
825     # use these to update the short addresses since this short address is fresh
826     longAddr = zigbeeHexStringToHexString(parsedData['source_addr_long'])
827     shortAddr = zigbeeHexStringToHexString( parsedData['source_addr'])
828
829     # check if this short address is for a device that has yet to be 
830     # registered
831     zigbeeUnregisteredAddressesMutex.acquire()
832     if(longAddr in zigbeeUnregisteredAddresses):
833         zigbeeUnregisteredAddresses.remove(longAddr)
834     zigbeeUnregisteredAddressesMutex.release()
835
836     # update/ or insert the short address
837     zigbeeLongShortAddrMutex.acquire()
838     zigbeeLongShortAddr[longAddr] = shortAddr
839     zigbeeLongShortAddrMutex.release()
840
841
842     # if this is a ZDO message/response
843     if(parsedData['profile'] == '\x00\x00'):
844
845         # if this is a device announcement so we can get some useful data from it
846         if(parsedData['cluster'] == '\x00\x13'):
847             
848             # pick out the correct parts of the payload
849             longAddr = zigbeeHexStringToHexString(parsedData['rf_data'][3:11])
850             shortAddr = zigbeeHexStringToHexString(parsedData['rf_data'][1:3])
851
852             # change the endian of the address
853             longAddr = changeEndian(longAddr)
854             shortAddr = changeEndian(shortAddr)
855
856             # update the table with the new information
857             zigbeeLongShortAddrMutex.acquire()
858             zigbeeLongShortAddr[longAddr] = shortAddr
859             zigbeeLongShortAddrMutex.release()
860
861             # check if this short address is for a device that has yet to be 
862             # registered
863             zigbeeUnregisteredAddressesMutex.acquire()
864             if(longAddr in zigbeeUnregisteredAddresses):
865                 zigbeeUnregisteredAddresses.remove(longAddr)
866             zigbeeUnregisteredAddressesMutex.release()
867
868         # if this is a response to a zdo bind_req message
869         elif(parsedData['cluster'] == '\x80\x21'):
870
871             # get the status and sequence number from the message
872             seqNumber = parsedData['rf_data'][0]
873             statusCode = parsedData['rf_data'][1]
874
875             # get the bind tuple information
876             # for this specific bind request
877             tup = None
878             zigeeBindRequestMutex.acquire() 
879             if(zigeeBindRequest.has_key(ord(seqNumber))):
880                 tup = zigeeBindRequest[ord(seqNumber)]
881             zigeeBindRequestMutex.release()
882
883             if(tup == None):
884                 # cant really do anything in this case...
885                 # don't have any information on who the data is for
886                 return
887
888             # successful binding
889             if(ord(statusCode) == 0):
890
891                 # add a callback for this specific device and cluster 
892                 # to the HA callback dict 
893                 zibeeHACallbackMutex.acquire();
894                 if(zibeeHACallback.has_key((tup[0], tup[1]))):
895                     if(tup[3] not in zibeeHACallback[(tup[0], tup[1])]):
896                         zibeeHACallback[(tup[0], tup[1])].append(tup[3])
897                 else:
898                     zibeeHACallback[(tup[0], tup[1])] = [tup[3]]
899                 zibeeHACallbackMutex.release()
900
901                 # send success message
902                 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
903
904             # Not Supported
905             elif (ord(statusCode) == 170):
906                 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
907
908             # Table Full
909             elif (ord(statusCode) == 174):
910                 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
911
912             # Other issue, dont have code for
913             else:
914                 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
915
916         # if this is a response to a short address query
917         elif(parsedData['cluster'] == '\x80\x00'):
918             
919             # get a status code
920             statusCode = parsedData['rf_data'][0]
921
922             # does not matter if this is not a success, we can try again later
923             if(statusCode != '\x00'):
924                 # status code was not success so do not do anything
925                 return
926
927             # get the short and long address information
928             longAddr = changeEndian(zigbeeHexStringToHexString(parsedData['rf_data'][2:10]))
929             shortAddr = changeEndian(zigbeeHexStringToHexString( parsedData['rf_data'][10:12]))
930
931             # remove device from list of unregistered devices if it is in it
932             zigbeeUnregisteredAddressesMutex.acquire()
933             if(longAddr in zigbeeUnregisteredAddresses):
934                 zigbeeUnregisteredAddresses.remove(longAddr)
935             zigbeeUnregisteredAddressesMutex.release()
936
937             # update/insert the short address
938             zigbeeLongShortAddrMutex.acquire()
939             zigbeeLongShortAddr[longAddr] = shortAddr
940             zigbeeLongShortAddrMutex.release()
941
942     # if this is a home automation zcl message/response
943     elif (parsedData['profile'] == '\x01\x04'):
944
945         # get the zcl message header
946         zclFrameControl = parsedData['rf_data'][0]
947         zclSeqNumber = parsedData['rf_data'][1]
948         zclCommand = parsedData['rf_data'][2]
949
950         # this is a zcl read attribute response
951         if(zclCommand == '\x01'):
952
953             # get the zcl payload
954             zclPayload = parsedData['rf_data'][3:]
955             attibuteResponseList = []
956
957             # get the data for each data
958             while(len(zclPayload) > 0):
959                 attributeId = zclPayload[0:2]
960                 attributeStatus = zclPayload[2]
961                 zclPayload = zclPayload[3:]
962                 
963                 if(ord(attributeStatus) != 0):
964                     # if attribute is not supported then it has no data
965                     # package the data and add it to the list
966                     attibuteResponseList.append((attributeId,"not_supported"))
967                 else:
968
969                     # get the data type and data length of the attributre
970                     attributeType = zclPayload[0]
971                     dataLength = zclDataTypeToBytes(zclPayload)
972
973                     # consume zcl payload data
974                     if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
975                         zclPayload = zclPayload[2:]
976                     elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
977                         zclPayload = zclPayload[3:]
978                     else:
979                         zclPayload = zclPayload[1:]
980
981                     # package the data and add it to the list
982                     newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
983                     attibuteResponseList.append(newData)
984
985                     # consume the data size of the payload
986                     zclPayload = zclPayload[dataLength:]
987
988             # find who to send response to 
989             tup = None
990             zigbeeSeqNumberToClientMutex.acquire()
991             if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
992                 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
993                 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
994             zigbeeSeqNumberToClientMutex.release()
995
996             # no one to send the response to so just move on
997             if(tup == None):
998                 # cant really do anything here
999                 return
1000             
1001             # create the response message
1002             packetId = tup[2]
1003             message = "type : zcl_read_attributes_response \n"
1004             message += "packet_id: " + packetId + "\n"
1005             message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
1006             message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
1007             message += "attributes: " 
1008
1009             # create the message for each attribute
1010             for t in attibuteResponseList:
1011                 attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
1012                 if(t[1] == "success"):
1013                     attrIdStr = "%0.4x" % attrId
1014                     attrIdStr = changeEndian(attrIdStr)
1015
1016                     message += attrIdStr
1017                     message += ", "
1018                     message +=  "success"
1019                     message += ", "
1020                     message += "%0.2x" % ord(t[2])
1021                     message += ", "
1022
1023                     dat = ""
1024                     for c in (t[3]):
1025                         dat += "%0.2x" % ord(c)
1026                     dat = changeEndian(dat)
1027                     message += dat
1028                     message += ";"
1029                 else:
1030                     attrIdStr = "%0.4x" % attrId
1031                     attrIdStr = changeEndian(attrIdStr)
1032
1033                     message += attrIdStr
1034                     message += ", "
1035                     message +=  "not_supported"
1036                     message += ";"
1037
1038             message = message[0:len(message) - 1]
1039             message += "\n"
1040
1041             # send the socket
1042             sendSoceket.sendto(message,tup[0])
1043
1044         # this is a zcl configure attribute response
1045         elif(zclCommand == '\x07'):
1046
1047             # find who to send response to 
1048             tup = None
1049             zigbeeSeqNumberToClientMutex.acquire()
1050             if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
1051                 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1052                 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1053             zigbeeSeqNumberToClientMutex.release()
1054
1055             # no one to send the response to so just move on
1056             if(tup == None):
1057                 # cant really do anything here
1058                 return
1059
1060             # get zcl payload
1061             zclPayload = parsedData['rf_data'][3:]
1062             
1063             # construct the message
1064             packetId = tup[2]
1065             message = "type : zcl_configure_reporting_response \n"
1066             message += "packet_id: " + packetId + "\n"
1067             message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
1068             message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
1069             message += "attributes: " 
1070
1071             if(len(zclPayload) == 1):
1072                 # if all the configurations are a success then only send back a success
1073                 # based on zigbee specs
1074                 message +=  "all_success \n";
1075                 sendSoceket.sendto(message,tup[0])
1076             
1077             else:
1078                 attibuteResponseList = []
1079                 
1080                 # get each attributes data
1081                 while(len(zclPayload) > 0):
1082                     attributeStatus = zclPayload[0]
1083                     attributeDirection = zclPayload[1]
1084                     attributeId = zclPayload[2:4]
1085                     zclPayload = zclPayload[4:]
1086
1087                     newData = (attributeStatus,attributeDirection, attributeId)
1088                     attibuteResponseList.append(newData)
1089
1090                 # package each attribute 
1091                 for t in attibuteResponseList:
1092                     attrId = ord(t[2][0]) + (256 * ord(t[2][1]))
1093                     attrIdStr = "%0.4x" % attrId
1094                     attrIdStr = changeEndian(attrIdStr)
1095
1096                     message += attrIdStr
1097                     message += ", "
1098                     if(ord(t[0]) == 0):
1099                         message +=  "success"
1100                     else:
1101                         message +=  "error"
1102
1103                     message += ", "
1104
1105                     if(ord(t[1]) == 0):
1106                         message +=  "reported"
1107                     else:
1108                         message +=  "received"
1109                     message += ";"
1110
1111                 message = message[0:len(message) - 1]
1112                 message += "\n"
1113                 sendSoceket.sendto(message,tup[0])
1114
1115         # this is a zcl report attribute message
1116         elif(zclCommand == '\x0a'):
1117
1118             # get teh zcl payload
1119             zclPayload = parsedData['rf_data'][3:]
1120             attibuteResponseList = []
1121  
1122             # extract the attribute data
1123             while(len(zclPayload) > 0):
1124                 attributeId = zclPayload[0:2]
1125                 zclPayload = zclPayload[2:]
1126                 attributeType = zclPayload[0]
1127                 dataLength = zclDataTypeToBytes(zclPayload)
1128
1129                 if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
1130                     zclPayload = zclPayload[2:]
1131                 elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
1132                     zclPayload = zclPayload[3:]
1133                 else:
1134                     zclPayload = zclPayload[1:]
1135
1136                 newData = (attributeId, attributeType ,zclPayload[0:dataLength])
1137                 attibuteResponseList.append(newData)
1138                 zclPayload = zclPayload[dataLength:]
1139
1140
1141             # get callback clients to respond to
1142             callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
1143             retAddr = None
1144             zibeeHACallbackMutex.acquire()
1145             if(zibeeHACallback.has_key(callbackIndex)):
1146                 retAddr = zibeeHACallback[callbackIndex]
1147             zibeeHACallbackMutex.release()
1148
1149             # no one to respond to so do nothing here
1150             if(retAddr == None):
1151                 return
1152
1153             # construct the message
1154             message = "type : zcl_report_attributes \n"
1155             message += "packet_id: " + ("%0.2x" % ord(zclSeqNumber)) + "\n"
1156             message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
1157             message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
1158             message += "attributes: " 
1159
1160             # package the attributes
1161             for t in attibuteResponseList:
1162                 attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
1163                 attrIdStr = "%0.4x" % attrId
1164                 attrIdStr = changeEndian(attrIdStr)
1165
1166                 message += attrIdStr
1167                 message += ", "
1168                 message += "%0.2x" % ord(t[1])
1169                 message += ", "
1170
1171                 dat = ""
1172                 for c in (t[2]):
1173                     dat += "%0.2x" % ord(c)
1174                 dat = changeEndian(dat)
1175                 message += dat
1176                 message += ";"
1177
1178             message = message[0:len(message) - 1]
1179             message += "\n"
1180
1181             # send to all client that want this callback
1182             for ra in retAddr:
1183                 sendSoceket.sendto(message,ra)
1184
1185 # -----------------------------------------------------------------------------
1186 # Communication Callback/Parse Methods
1187 # -----------------------------------------------------------------------------
1188 def handleNewZigbeeMessage(parsedData):
1189     ''' Method to process a zigbee message from the local radio.
1190
1191         parsedData -- Pre-parsed (into a dict) data from message.
1192     '''
1193     print "=================================================================="
1194     print "New Zigbee Message"
1195     # printMessageData(parsedData)
1196
1197     # dispatch to the correct zigbee handler
1198     if (parsedData['id'] == 'at_response'):
1199         processZigbeeATCommandMessage(parsedData)
1200
1201     elif (parsedData['id'] == 'rx_explicit'):
1202         #printMessageData(parsedData)
1203         processZigbeeRxExplicitCommandMessage(parsedData)
1204
1205     else:
1206         print "Unknown API format"
1207
1208     print "=================================================================="
1209
1210 def handleNewUdpPacket(data, addr):
1211     ''' Method to parse and handle an incoming UDP packet.
1212
1213         data -- Data that was in the UDP packet.
1214         addr -- Address (IP and Port) of the UDP packet origin.
1215     '''
1216
1217     print "=================================================================="
1218     print "Got New UDP packet..."
1219     # print data
1220
1221
1222     # data comes in as 'key: value\n key: value...' string and so needs to be
1223     # parsed into a dict
1224     parsedData = dict()
1225
1226     # 1 key, value pair per line
1227     for line in data.split('\n'):
1228
1229         # key and values are split based on a ':'
1230         fields = line.split(':')
1231
1232         # make sure properly formated otherwise just ignore it
1233         if len(fields) == 2:
1234
1235             # do strips to remove any white spacing that may have resulted
1236             # from improper packing on the sender side
1237             parsedData[fields[0].strip()] = fields[1].strip()
1238
1239     # wrap in try statement just in case there is an improperly formated packet we
1240     # can deal with it
1241     try:
1242         # dispatch to the correct process method
1243         if(parsedData["type"] == "zdo_bind_request"):
1244             processUdpZdoBindReqMessage(parsedData, addr)
1245         elif(parsedData["type"] == "zdo_unbind_request"):
1246             processUdpZdoUnBindReqMessage(parsedData, addr)
1247         elif(parsedData["type"] == "send_address"):
1248             processUdpSendAddressMessage(parsedData, addr)
1249         elif(parsedData["type"] == "zcl_read_attributes"):
1250             processUdpZclReadAttributesMessage(parsedData, addr)
1251         elif(parsedData["type"] == "zcl_configure_reporting"):
1252             processUdpZclConfigureReportingMessage(parsedData, addr)
1253         elif(parsedData["type"] == "policy_set"):
1254             processUdpPolicySet(parsedData, addr)
1255         elif(parsedData["type"] == "policy_clear"):
1256             processUdpPolicyClear(parsedData, addr)
1257         else:
1258             #print "unknown Packet: " + parsedData["type"]
1259             pass
1260     except:
1261         # if we ever get here then something went wrong and so just ignore this
1262         # packet and try again later
1263         print "I didn't expect this error:", sys.exc_info()[0]
1264         traceback.print_exc()
1265
1266     print "=================================================================="
1267
1268
1269 # -----------------------------------------------------------------------------
1270 # Main Running Methods
1271 # -----------------------------------------------------------------------------
1272
1273 def main():
1274     '''Main function used for starting the application as the main driver'''
1275
1276     global ZIGBEE_SERIAL_PORT
1277     global ZIGBEE_SERIAL_BAUD
1278     global UDP_RECEIVE_PORT
1279     global zigbeeConnection
1280     global zigbeeMutex
1281     global doEndFlag
1282
1283     parseCommandLineArgs(sys.argv[1:])
1284
1285     # create serial object used for communication to the zigbee radio
1286     sc = serial.Serial(ZIGBEE_SERIAL_PORT, ZIGBEE_SERIAL_BAUD)
1287
1288     # create a zigbee object that handles all zigbee communication
1289     # we use this to do all communication to and from the radio
1290     # when data comes from the radio it will get a bit of unpacking
1291     # and then a call to the callback specified will be done with the
1292     # unpacked data
1293     zigbeeConnection = ZigBee(sc, callback=handleNewZigbeeMessage)
1294
1295     # get the long address of our local radio before we start doing anything
1296     getConnectedRadioLongAddress();
1297
1298     # setup incoming UDP socket and bind it to self and specified UDP port
1299     # sending socket does not need to be bound to anything
1300     receiveSoceket.bind(('127.0.0.1', UDP_RECEIVE_PORT))
1301
1302     # create the thread that does short address lookups
1303     addressUpdateWorkerThread = threading.Thread(target=addressUpdateWorkerMethod)
1304     addressUpdateWorkerThread.start()
1305
1306     try:
1307         # Main running loop
1308         while(True):
1309             print "=================================================================="
1310             print "Waiting..."
1311             print "=================================================================="
1312
1313             # wait for an incoming UDP packet
1314             # this is a blocking call
1315             data, addr = receiveSoceket.recvfrom(4096)
1316
1317             # handle the UDP packet appropriately
1318             handleNewUdpPacket(data, addr)
1319
1320     except KeyboardInterrupt:
1321         # use the keyboard interupt to catch a ctrl-c and kill the application
1322         pass
1323
1324     except:
1325         # something went really wrong and so exit with error message
1326         traceback.print_exc()
1327
1328     # signal all threads to exit
1329     doEndFlag = True
1330
1331     # wait for threads to finish before closing of the resources
1332     addressUpdateWorkerThread.join()
1333
1334
1335     # make sure to close all the connections
1336     zigbeeConnection.halt()
1337     receiveSoceket.close()
1338     sendSoceket.close()
1339
1340 if __name__ == "__main__":
1341     # call main function since this is being run as the start
1342     main()