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