10 from threading import Thread, Lock
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
21 # time for messages to wait for a response before the system clears away that
23 ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5
25 # address of our local zigbee radio
26 ZIGBEE_DEVICE_ADDRESS = "xxxxxxxxxxxxxxxx"
28 SYSTEM_MASTER_ADDRESS = ("192.168.2.108", 12345) # ip address and portof the system master node
31 # -----------------------------------------------------------------------------
32 # Global Variables and Objects
33 # -----------------------------------------------------------------------------
35 # zigbee communications object and its mutex
36 zigbeeConnection = None
37 zigbeeConnectionMutex = Lock()
39 # zigbee mapping from long to short object dict
40 zigbeeLongShortAddr = dict()
41 zigbeeLongShortAddrMutex = Lock()
43 # zigbee mapping from a sequence number to a client
44 # for correct response handling
45 zigbeeSeqNumberToClient = dict()
46 zigbeeSeqNumberToClientMutex = Lock()
48 zigbeeBindRequest = dict()
49 zigbeeBindRequestMutex = Lock()
51 # Keeps record of where to send callbacks to when an HA message is received
52 zigbeeHACallback = dict()
53 zigbeeHACallbackMutex = Lock()
55 # Keeps a record of device addresses whose short addresses have not been
57 zigbeeUnregisteredAddresses = []
58 zigbeeUnregisteredAddressesMutex = Lock()
60 # used to signal all threads to end
64 # 2 sockets, one for sending (not bound to a port manually)
65 # and one for receiving, known port binding by application
67 sendSocket = socket(AF_INET, SOCK_DGRAM)
68 receiveSocket = socket(AF_INET, SOCK_DGRAM)
70 # zigbee address authority list
71 zigbeeAddressAuthorityDict = dict()
75 # -----------------------------------------------------------------------------
77 # -----------------------------------------------------------------------------
80 def parseCommandLineArgs(argv):
82 opts, args = getopt.getopt(
83 argv, "h:u:", ["udpport="])
85 except getopt.GetoptError:
86 print 'test.py -u <udp_port>'
91 print 'test.py -u <udp_port>'
96 # Convenience (Stateless)
99 def shortToStr(short):
100 s=chr(short>>8) + chr(short & 0xff)
110 def hexListToChar(hexList):
111 ''' Method to convert a list/string of characters into their corresponding values
113 hexList -- list or string of hex characters
117 retString += chr(int(h, 16))
120 def splitByN(seq, n):
121 ''' Method to split a string into groups of n characters
126 return [seq[i:i+n] for i in range(0, len(seq), n)]
128 def changeEndian(hexString):
129 ''' Method to change endian of a hex string
131 hexList -- string of hex characters
133 split = splitByN(hexString, 2) # get each byte
134 split.reverse(); # reverse ordering of the bytes
143 ''' Method to change endian of a hex string
145 hexList -- string of hex characters
149 def printMessageData(data):
150 ''' Method to print a zigbee message to the console
152 data -- pre-parsed zigbee message
157 print "{0:02x}".format(ord(e)),
159 print "({})".format(data[d]),
162 def hexStringToZigbeeHexString(hexString):
163 ''' Method to change a hex string to a string of characters with the hex values
165 hexList -- string of hex characters
167 return hexListToChar(splitByN(hexString, 2))
169 def hexStringToAddr(hexString):
170 ''' Method to change a hex string to a string of characters with the hex values
172 hexList -- string of hex characters
174 split = splitByN(hexString, 2)
175 newstring = '[' + split[0]
181 def addrToHexString(addr):
183 list=[hex[i:i+2] for i in range(0, len(hex), 3)]
189 def zigbeeHexStringToHexString(zigbeeHexString):
190 ''' Method to change string of characters with the hex values to a hex string
192 hexList -- string of characters with hex values
196 for e in zigbeeHexString:
197 retString += "{0:02x}".format(ord(e))
200 def zclDataTypeToBytes(zclPayload):
201 ''' Method to determine data length of a zcl attribute
203 zclPayload -- ZCL payload data, must have dataType as first byte
205 attrType = ord(zclPayload[0])
207 if(attrType == 0x00):
209 elif (attrType == 0x08):
211 elif (attrType == 0x09):
213 elif (attrType == 0x0a):
215 elif (attrType == 0x0b):
217 elif (attrType == 0x0c):
219 elif (attrType == 0x0d):
221 elif (attrType == 0x0e):
223 elif (attrType == 0x0f):
225 elif (attrType == 0x10):
227 elif (attrType == 0x18):
229 elif (attrType == 0x19):
231 elif (attrType == 0x1a):
233 elif (attrType == 0x1b):
235 elif (attrType == 0x1c):
237 elif (attrType == 0x1d):
239 elif (attrType == 0x1e):
241 elif (attrType == 0x1f):
243 elif (attrType == 0x20):
245 elif (attrType == 0x21):
247 elif (attrType == 0x22):
249 elif (attrType == 0x23):
251 elif (attrType == 0x24):
253 elif (attrType == 0x25):
255 elif (attrType == 0x26):
257 elif (attrType == 0x27):
259 elif (attrType == 0x28):
261 elif (attrType == 0x29):
263 elif (attrType == 0x2a):
265 elif (attrType == 0x2b):
267 elif (attrType == 0x2c):
269 elif (attrType == 0x2d):
271 elif (attrType == 0x2e):
273 elif (attrType == 0x2f):
275 elif (attrType == 0x30):
277 elif (attrType == 0x31):
279 elif (attrType == 0x38):
281 elif (attrType == 0x39):
283 elif (attrType == 0x3a):
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):
295 elif (attrType == 0xe1):
297 elif (attrType == 0xe2):
299 elif (attrType == 0xe8):
301 elif (attrType == 0xe9):
303 elif (attrType == 0xea):
305 elif (attrType == 0xf0):
307 elif (attrType == 0xf1):
309 elif (attrType == 0xff):
316 def createSequenceNumberForClient(addr, packetId):
317 ''' Method to get and store a sequence number with a specific client
318 for a specific message.
320 addr -- UDP address of the client to associate with the seq. number
321 packetId -- packet id from the UDP packet
323 # keep trying to find a number to return
326 # get the current time
327 epoch_time = int(time.time())
329 # get the current list of used numbers
330 zigbeeSeqNumberToClientMutex.acquire()
331 keysList = zigbeeSeqNumberToClient.keys()
332 zigbeeSeqNumberToClientMutex.release()
334 # if all the numbers are already used
335 if(len(keysList) == 256):
337 # get a list of all the items
338 zigbeeSeqNumberToClientMutex.acquire()
339 itemsList = zigbeeSeqNumberToClient.items()
340 zigbeeSeqNumberToClientMutex.release()
342 # search for a number that is old enough to get rid of otherwise use -1
344 for item in itemsList:
345 if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
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()
358 # not all numbers used yet so pick one randomly
359 randNum = random.randrange(0,256)
361 # check if we are using the number yet
362 if(randNum not in keysList):
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()
370 def getConnectedRadioLongAddress():
371 """ Method to make sure we get the MAC address of our local radio"""
372 global zigbeeConnection
374 global ZIGBEE_DEVICE_ADDRESS
376 data = ddo_get_param(None, 'SH')
379 valuehi += "{0:02x}".format(ord(e))
381 data = ddo_get_param(None, 'SL')
384 valuelo += "{0:02x}".format(ord(e))
385 ZIGBEE_DEVICE_ADDRESS = valuehi + valuelo
387 def addressUpdateWorkerMethod():
388 ''' Method to keep refreshing the short addresses of the known zigbee devices'''
390 global zigbeeLongShortAddr
391 global zigbeeLongShortAddrMutex
392 global zigbeeUnregisteredAddresses
393 global zigbeeUnregisteredAddressesMutex
394 global zigbeeConnectionMutex
395 global zigbeeConnection
397 # keep looping until signaled to quit
398 while(not doEndFlag):
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()
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()
414 # Loop through all the addresses and send messages for each address
416 # create payload for a query on the network for a short address
418 payload += hexStringToZigbeeHexString(changeEndian(ad))
421 # create and send binding command
422 zigbeeConnectionMutex.acquire()
423 # The Format of the tuple is:
424 # (address_string, endpoint, profile_id, cluster_id)
426 DESTINATION=(hexStringToAddr(ad), 0x0, 0x0, 0x0)
428 zigbeeConnection.sendto(payload, 0, DESTINATION);
429 zigbeeConnectionMutex.release()
438 def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
439 ''' Method to send a success or fail back to a client.
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)
451 # construct the message
452 message = "type: " + packetTypeStr.strip() + "\n"
453 message += "packet_id: " + packetIdStr + "\n"
456 message += "response: success \n"
458 message += "response: fail \n"
459 message += "reason: " + reason + "\n"
461 # send message in a UDP packet
462 sendSocket.sendto(message,addr)
464 def processUdpZdoBindReqMessage(parsedData, addr):
465 ''' Method handle a zdo bind request message
467 parsedData -- Pre-parsed Data that was in the UDP packet.
468 addr -- Address (IP and Port) of the UDP packet origin.
470 global zigbeeLongShortAddr
471 global zigbeeLongShortAddrMutex
472 global zigbeeBindRequestMutex
473 global zigbeeBindRequest
474 global zigbeeConnectionMutex
475 global zigbeeConnection
480 if(zigbeeAddressAuthorityDict.has_key(addr)):
481 l = zigbeeAddressAuthorityDict[addr]
482 if(parsedData['device_address_long'] not in l):
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()
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
496 if(shortAddr != None):
498 # get a request number
499 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
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')
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'],
515 zigbeeBindRequestMutex.release()
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)
522 # create the payload data
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'
532 # create and send binding command
533 zigbeeConnectionMutex.acquire()
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)
540 zigbeeConnectionMutex.release()
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')
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)
557 def processUdpSendAddressMessage(parsedData, addr):
558 ''' Method handle a send address command
560 parsedData -- Pre-parsed Data that was in the UDP packet.
561 addr -- Address (IP and Port) of the UDP packet origin.
563 global zigbeeLongShortAddr
564 global zigbeeLongShortAddrMutex
565 global zigbeeUnregisteredAddresses
566 global zigbeeUnregisteredAddressesMutex
569 if(zigbeeAddressAuthorityDict.has_key(addr)):
570 l = zigbeeAddressAuthorityDict[addr]
571 if(parsedData['device_address_long'] not in l):
577 # construct success message
578 message = "type: send_address_response\n"
579 message += "packet_id: " + parsedData['packet_id'] + "\n"
580 message += "response: success\n"
582 # tell client that we got their request
583 sendSocket.sendto(message,addr)
586 zigbeeLongShortAddrMutex.acquire()
587 doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
588 zigbeeLongShortAddrMutex.release()
591 # long address is already registered with the system so no need to do anything
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()
599 def processUdpZclReadAttributesMessage(parsedData, addr):
600 ''' Method handle a ZCL read attribute command
602 parsedData -- Pre-parsed Data that was in the UDP packet.
603 addr -- Address (IP and Port) of the UDP packet origin.
606 global zigbeeLongShortAddr
607 global zigbeeLongShortAddrMutex
608 global zigbeeBindRequestMutex
609 global zigbeeBindRequest
610 global zigbeeConnectionMutex
611 global zigbeeConnection
614 if(zigbeeAddressAuthorityDict.has_key(addr)):
615 l = zigbeeAddressAuthorityDict[addr]
616 if(parsedData['device_address_long'] not in l):
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()
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
634 if(shortAddr != None):
636 # get a request number
637 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
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')
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']
653 # get all the attributes
654 attributeIds = parsedData['attribute_ids'].split(',')
656 # create the payload data
658 payloadData += '\x00'
659 payloadData += chr(seqNumber)
660 payloadData += '\x00'
662 # make all the attributes payloads
663 for attr in attributeIds:
665 attr = changeEndian(attr)
666 payloadData += hexStringToZigbeeHexString(attr)
669 # (address_string, endpoint, profile_id, cluster_id)
670 DESTINATION = (hexStringToAddr(zigbeeHexStringToHexString(destLongAddr)), hexToInt(dstEndpoint), hexToInt(profileId), hexToInt(clusterId))
672 # create and send binding command
673 zigbeeConnectionMutex.acquire()
674 zigbeeConnection.sendto(payloadData, 0, DESTINATION)
675 zigbeeConnectionMutex.release()
679 # send a fail response
680 sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
683 def processUdpZclConfigureReportingMessage(parsedData, addr):
684 ''' Method handle a zcl configure reporting message
686 parsedData -- Pre-parsed Data that was in the UDP packet.
687 addr -- Address (IP and Port) of the UDP packet origin.
690 global zigbeeLongShortAddr
691 global zigbeeLongShortAddrMutex
692 global zigbeeBindRequestMutex
693 global zigbeeBindRequest
694 global zigbeeConnectionMutex
695 global zigbeeConnection
698 if(zigbeeAddressAuthorityDict.has_key(addr)):
699 l = zigbeeAddressAuthorityDict[addr]
700 if(parsedData['device_address_long'] not in l):
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()
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
716 if(shortAddr != None):
718 # get a request number
719 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
723 sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
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']
732 # create the payload data
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']))
743 if(parsedData.has_key('reportable_change')):
744 payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
746 DESTINATION = (hexStringToAddr(zigbeeHexStringToHexString(destLongAddr)), hexToInt(dstEndpoint), hexToInt(profileId), hexToInt(clusterId))
748 # create and send binding command
749 zigbeeConnectionMutex.acquire()
751 zigbeeConnection.sendto(payloadData, 0, DESTINATION)
752 zigbeeConnectionMutex.release()
756 sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
759 def processUdpPolicySet(parsedData, addr):
760 ''' Method handle a policy set message
762 parsedData -- Pre-parsed Data that was in the UDP packet.
763 addr -- Address (IP and Port) of the UDP packet origin.
765 print "=================================================================="
766 print "Policy set: ", parsedData
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'])
774 zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
776 def processUdpPolicyClear(parsedData, addr):
777 ''' Method handle a policy set message
779 parsedData -- Pre-parsed Data that was in the UDP packet.
780 addr -- Address (IP and Port) of the UDP packet origin.
782 print "=================================================================="
783 print "Clear policy: ", parsedData
785 # do nothing if wrong source
786 if addr == SYSTEM_MASTER_ADDRESS:
787 zigbeeAddressAuthorityDict.clear()
790 def processZigbeeRxExplicitCommandMessage(rawData, src_addr):
791 ''' Method to process a rx-explicit zigbee message
793 parsedData -- Pre-parsed (into a dict) data from message.
796 # The format for addr is the tuple (address_string, endpoint, profile_id, cluster_id).
797 global zigbeeBindRequestMutex
798 global zigbeeBindRequest
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
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'])
809 # check if this short address is for a device that has yet to be
812 # if this is a ZDO message/response
813 if(parsedData['profile'] == '\x00\x00'):
815 # if this is a device announcement so we can get some useful data from it
816 if(parsedData['cluster'] == '\x00\x13'):
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])
822 # change the endian of the address
823 longAddr = changeEndian(longAddr)
824 shortAddr = changeEndian(shortAddr)
826 # update the table with the new information
827 zigbeeLongShortAddrMutex.acquire()
828 zigbeeLongShortAddr[longAddr] = shortAddr
829 zigbeeLongShortAddrMutex.release()
831 # check if this short address is for a device that has yet to be
833 zigbeeUnregisteredAddressesMutex.acquire()
834 if(longAddr in zigbeeUnregisteredAddresses):
835 zigbeeUnregisteredAddresses.remove(longAddr)
836 zigbeeUnregisteredAddressesMutex.release()
838 # if this is a response to a zdo bind_req message
839 elif(parsedData['cluster'] == '\x80\x21'):
841 # get the status and sequence number from the message
842 seqNumber = parsedData['rf_data'][0]
843 statusCode = parsedData['rf_data'][1]
845 # get the bind tuple information
846 # for this specific bind request
848 zigbeeBindRequestMutex.acquire()
849 if(zigbeeBindRequest.has_key(ord(seqNumber))):
850 tup = zigbeeBindRequest[ord(seqNumber)]
851 zigbeeBindRequestMutex.release()
854 # cant really do anything in this case...
855 # don't have any information on who the data is for
859 if(ord(statusCode) == 0):
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])
867 zigbeeHACallback[(tup[0], tup[1])] = [tup[3]]
869 # send success message
870 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
873 elif (ord(statusCode) == 170):
874 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
877 elif (ord(statusCode) == 174):
878 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
880 # Other issue, dont have code for
882 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
884 # if this is a response to a short address query
885 elif(parsedData['cluster'] == '\x80\x00'):
888 statusCode = parsedData['rf_data'][0]
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
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]))
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()
905 # update/insert the short address
906 zigbeeLongShortAddrMutex.acquire()
907 zigbeeLongShortAddr[longAddr] = shortAddr
908 zigbeeLongShortAddrMutex.release()
910 # if this is a home automation zcl message/response
911 elif (parsedData['profile'] == '\x01\x04'):
913 # get the zcl message header
914 zclFrameControl = parsedData['rf_data'][0]
915 zclSeqNumber = parsedData['rf_data'][1]
916 zclCommand = parsedData['rf_data'][2]
918 # this is a zcl read attribute response
919 if(zclCommand == '\x01'):
921 # get the zcl payload
922 zclPayload = parsedData['rf_data'][3:]
923 attibuteResponseList = []
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:]
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"))
937 # get the data type and data length of the attributre
938 attributeType = zclPayload[0]
939 dataLength = zclDataTypeToBytes(zclPayload)
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:]
947 zclPayload = zclPayload[1:]
949 # package the data and add it to the list
950 newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
951 attibuteResponseList.append(newData)
953 # consume the data size of the payload
954 zclPayload = zclPayload[dataLength:]
956 # find who to send response to
958 zigbeeSeqNumberToClientMutex.acquire()
959 if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
960 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
961 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
962 zigbeeSeqNumberToClientMutex.release()
964 # no one to send the response to so just move on
966 # cant really do anything here
969 # create the response message
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: "
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)
988 message += "%0.2x" % ord(t[2])
993 dat += "%0.2x" % ord(c)
994 dat = changeEndian(dat)
998 attrIdStr = "%0.4x" % attrId
999 attrIdStr = changeEndian(attrIdStr)
1001 message += attrIdStr
1003 message += "not_supported"
1006 message = message[0:len(message) - 1]
1010 sendSocket.sendto(message,tup[0])
1012 # this is a zcl configure attribute response
1013 elif(zclCommand == '\x07'):
1015 # find who to send response to
1017 zigbeeSeqNumberToClientMutex.acquire()
1018 if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
1019 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1020 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1021 zigbeeSeqNumberToClientMutex.release()
1023 # no one to send the response to so just move on
1025 # cant really do anything here
1029 zclPayload = parsedData['rf_data'][3:]
1031 # construct the message
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: "
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])
1046 attibuteResponseList = []
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:]
1055 newData = (attributeStatus,attributeDirection, attributeId)
1056 attibuteResponseList.append(newData)
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)
1064 message += attrIdStr
1067 message += "success"
1074 message += "reported"
1076 message += "received"
1079 message = message[0:len(message) - 1]
1081 sendSocket.sendto(message,tup[0])
1083 # this is a zcl report attribute message
1084 elif(zclCommand == '\x0a'):
1086 # get teh zcl payload
1087 zclPayload = parsedData['rf_data'][3:]
1088 attibuteResponseList = []
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)
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:]
1102 zclPayload = zclPayload[1:]
1104 newData = (attributeId, attributeType ,zclPayload[0:dataLength])
1105 attibuteResponseList.append(newData)
1106 zclPayload = zclPayload[dataLength:]
1109 # get callback clients to respond to
1110 callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
1112 zigbeeHACallbackMutex.acquire()
1113 if(zigbeeHACallback.has_key(callbackIndex)):
1114 retAddr = zigbeeHACallback[callbackIndex]
1115 zigbeeHACallbackMutex.release()
1117 # no one to respond to so do nothing here
1118 if(retAddr == None):
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: "
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)
1134 message += attrIdStr
1136 message += "%0.2x" % ord(t[1])
1141 dat += "%0.2x" % ord(c)
1142 dat = changeEndian(dat)
1146 message = message[0:len(message) - 1]
1149 # send to all client that want this callback
1151 sendSocket.sendto(message,ra)
1154 # -----------------------------------------------------------------------------
1155 # Communication Callback/Parse Methods
1156 # -----------------------------------------------------------------------------
1157 def handleNewZigbeeMessage(parsedData, src_addr):
1158 ''' Method to process a zigbee message from the local radio.
1160 parsedData -- Pre-parsed (into a dict) data from message.
1162 processZigbeeRxExplicitCommandMessage(parsedData, src_addr)
1165 def handleNewUdpPacket(data, addr):
1166 ''' Method to parse and handle an incoming UDP packet.
1168 data -- Data that was in the UDP packet.
1169 addr -- Address (IP and Port) of the UDP packet origin.
1174 # data comes in as 'key: value\n key: value...' string and so needs to be
1175 # parsed into a dict
1178 # 1 key, value pair per line
1179 for line in data.split('\n'):
1181 # key and values are split based on a ':'
1182 fields = line.split(':')
1184 # make sure properly formated otherwise just ignore it
1185 if len(fields) == 2:
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()
1191 # wrap in try statement just in case there is an improperly formated packet we
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)
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()
1220 payload, src_addr = zigbeeConnection.recvfrom(1024)
1221 handleNewZigbeeMessage(payload, src_addr)
1224 # -----------------------------------------------------------------------------
1225 # Main Running Methods
1226 # -----------------------------------------------------------------------------
1229 '''Main function used for starting the application as the main driver'''
1231 global UDP_RECEIVE_PORT
1232 global zigbeeConnection
1236 parseCommandLineArgs(sys.argv[1:])
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
1244 zigbeeConnection = socket(AF_ZIGBEE, SOCK_DGRAM, XBS_PROT_APS)
1246 zigbeeConnection.bind(("", 0x0, 0, 0))
1248 # get the long address of our local radio before we start doing anything
1249 getConnectedRadioLongAddress();
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))
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)
1265 rlist = [ receiveSocket, zigbeeConnection ]
1268 rlist, wlist, xlist = select(rlist, [], [])
1269 if zigbeeConnection in rlist:
1272 if receiveSocket in rlist:
1273 data, addr = receiveSocket.recvfrom(4096)
1274 handleNewUdpPacket(data, addr)
1276 except KeyboardInterrupt:
1277 # use the keyboard interupt to catch a ctrl-c and kill the application
1281 # something went really wrong and so exit with error message
1282 traceback.print_exc()
1284 # signal all threads to exit
1287 # wait for threads to finish before closing of the resources
1288 addressUpdateWorkerThread.join()
1291 # make sure to close all the connections
1292 zigbeeConnection.close()
1293 receiveSocket.close()
1296 if __name__ == "__main__":
1297 # call main function since this is being run as the start