1 from xbee import ZigBee
9 from threading import Thread, Lock
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
21 # time for messages to wait for a response before the system clears away that
23 ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5
25 ZIGBEE_SERIAL_PORT = "/dev/cu.usbserial-DN01DJIP" # USB-Serial port of local radio
26 ZIGBEE_SERIAL_BAUD = 115200 # Baud rate for above port
28 # address of our local zigbee radio
29 ZIGBEE_DEVICE_ADDRESS = "xxxxxxxxxxxxxxxx"
31 # -----------------------------------------------------------------------------
32 # Global Variables and Objects
33 # -----------------------------------------------------------------------------
35 # signals to see if a request needs to be made
36 didGetLocalRadioHighAddress = False;
37 didGetLocalRadioLowAddress = False;
39 # zigbee communications object and its mutex
40 zigbeeConnection = None
41 zigbeeConnectionMutex = Lock()
43 # zigbee mapping from long to short object dict
44 zigbeeLongShortAddr = dict()
45 zigbeeLongShortAddrMutex = Lock()
47 # zigbee mapping from a sequence number to a client
48 # for correct response handling
49 zigbeeSeqNumberToClient = dict()
50 zigbeeSeqNumberToClientMutex = Lock()
52 zigeeBindRequest = dict()
53 zigeeBindRequestMutex = Lock()
55 # Keeps record of where to send callbacks to when an HA message is received
56 zibeeHACallback = dict()
57 zibeeHACallbackMutex = Lock()
60 # Keeps a record of device addresses whose short addresses have not been
62 zigbeeUnregisteredAddresses = []
63 zigbeeUnregisteredAddressesMutex = Lock()
65 # used to signal all threads to end
69 # 2 sockets, one for sending (not bound to a port manually)
70 # and one for receiving, known port binding by application
72 sendSoceket = socket(AF_INET, SOCK_DGRAM)
73 receiveSoceket = socket(AF_INET, SOCK_DGRAM)
76 # zigbee address authority list
77 zigbeeAddressAuthorityDict = dict()
79 # -----------------------------------------------------------------------------
81 # -----------------------------------------------------------------------------
84 def parseCommandLineArgs(argv):
85 global ZIGBEE_SERIAL_PORT
86 global ZIGBEE_SERIAL_BAUD
88 opts, args = getopt.getopt(
89 argv, "hp:b:u:", ["port=", "baud=", "udpport="])
91 except getopt.GetoptError:
92 print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
97 print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
100 elif opt in ("-p", "--port"):
101 ZIGBEE_SERIAL_PORT = arg
103 elif opt in ("-b", "--baud"):
105 ZIGBEE_SERIAL_BAUD = int(arg)
107 print "Buad rate must be an integer"
112 # Convenience (Stateless)
115 def hexListToChar(hexList):
116 ''' Method to convert a list/string of characters into their corresponding values
118 hexList -- list or string of hex characters
122 retString += chr(int(h, 16))
125 def splitByN(seq, n):
126 ''' Method to split a string into groups of n characters
131 return [seq[i:i+n] for i in range(0, len(seq), n)]
133 def changeEndian(hexString):
134 ''' Method to change endian of a hex string
136 hexList -- string of hex characters
138 split = splitByN(hexString, 2) # get each byte
139 split.reverse(); # reverse ordering of the bytes
147 def printMessageData(data):
148 ''' Method to print a zigbee message to the console
150 data -- pre-parsed zigbee message
155 print "{0:02x}".format(ord(e)),
157 print "({})".format(data[d]),
160 def hexStringToZigbeeHexString(hexString):
161 ''' Method to change a hex string to a string of characters with the hex values
163 hexList -- string of hex characters
165 return hexListToChar(splitByN(hexString, 2))
167 def zigbeeHexStringToHexString(zigbeeHexString):
168 ''' Method to change string of characters with the hex values to a hex string
170 hexList -- string of characters with hex values
174 for e in zigbeeHexString:
175 retString += "{0:02x}".format(ord(e))
178 def zclDataTypeToBytes(zclPayload):
179 ''' Method to determine data length of a zcl attribute
181 zclPayload -- ZCL payload data, must have dataType as first byte
183 attrType = ord(zclPayload[0])
185 if(attrType == 0x00):
187 elif (attrType == 0x08):
189 elif (attrType == 0x09):
191 elif (attrType == 0x0a):
193 elif (attrType == 0x0b):
195 elif (attrType == 0x0c):
197 elif (attrType == 0x0d):
199 elif (attrType == 0x0e):
201 elif (attrType == 0x0f):
203 elif (attrType == 0x10):
205 elif (attrType == 0x18):
207 elif (attrType == 0x19):
209 elif (attrType == 0x1a):
211 elif (attrType == 0x1b):
213 elif (attrType == 0x1c):
215 elif (attrType == 0x1d):
217 elif (attrType == 0x1e):
219 elif (attrType == 0x1f):
221 elif (attrType == 0x20):
223 elif (attrType == 0x21):
225 elif (attrType == 0x22):
227 elif (attrType == 0x23):
229 elif (attrType == 0x24):
231 elif (attrType == 0x25):
233 elif (attrType == 0x26):
235 elif (attrType == 0x27):
237 elif (attrType == 0x28):
239 elif (attrType == 0x29):
241 elif (attrType == 0x2a):
243 elif (attrType == 0x2b):
245 elif (attrType == 0x2c):
247 elif (attrType == 0x2d):
249 elif (attrType == 0x2e):
251 elif (attrType == 0x2f):
253 elif (attrType == 0x30):
255 elif (attrType == 0x31):
257 elif (attrType == 0x38):
259 elif (attrType == 0x39):
261 elif (attrType == 0x3a):
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):
273 elif (attrType == 0xe1):
275 elif (attrType == 0xe2):
277 elif (attrType == 0xe8):
279 elif (attrType == 0xe9):
281 elif (attrType == 0xea):
283 elif (attrType == 0xf0):
285 elif (attrType == 0xf1):
287 elif (attrType == 0xff):
294 def createSequenceNumberForClient(addr, packetId):
295 ''' Method to get and store a sequence number with a specific client
296 for a specific message.
298 addr -- UDP address of the client to associate with the seq. number
299 packetId -- packet id from the UDP packet
301 # keep trying to find a number to return
304 # get the current time
305 epoch_time = int(time.time())
307 # get the current list of used numbers
308 zigbeeSeqNumberToClientMutex.acquire()
309 keysList = zigbeeSeqNumberToClient.keys()
310 zigbeeSeqNumberToClientMutex.release()
312 # if all the numbers are already used
313 if(len(keysList) == 256):
315 # get a list of all the items
316 zigbeeSeqNumberToClientMutex.acquire()
317 itemsList = zigbeeSeqNumberToClient.items()
318 zigbeeSeqNumberToClientMutex.release()
320 # search for a number that is old enough to get rid of otherwise use -1
322 for item in itemsList:
323 if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
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()
336 # not all numbers used yet so pick one randomly
337 randNum = random.randrange(0,256)
339 # check if we are using the number yet
340 if(randNum not in keysList):
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()
348 def getConnectedRadioLongAddress():
349 """ Method to make sure we get the MAC address of our local radio"""
350 global zigbeeConnection
353 # keep looping until we get both the MSBs and the LSBs
354 while ((not didGetLocalRadioHighAddress) or (not didGetLocalRadioLowAddress)):
357 zigbeeConnection.send('at', command="SH")
358 zigbeeConnection.send('at', command="SL")
360 # sleep for a bit to give the radio time to respond before we check again
363 def addressUpdateWorkerMethod():
364 ''' Method to keep refreshing the short addresses of the known zigbee devices'''
366 global zigbeeLongShortAddr
367 global zigbeeLongShortAddrMutex
368 global zigbeeUnregisteredAddresses
369 global zigbeeUnregisteredAddressesMutex
370 global zigbeeConnectionMutex
371 global zigbeeConnection
373 # keep looping until signaled to quit
374 while(not doEndFlag):
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()
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()
390 # Loop through all the addresses and send messages for each address
393 # create payload for a query on the network for a short address
395 payload += hexStringToZigbeeHexString(changeEndian(ad))
398 # create and send binding command
399 zigbeeConnectionMutex.acquire()
400 zigbeeConnection.send('tx_explicit',
402 dest_addr_long=hexStringToZigbeeHexString(ad),
403 dest_addr='\xff\xfd',
405 dest_endpoint='\x00',
410 zigbeeConnectionMutex.release()
419 def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
420 ''' Method to send a success or fail back to a client.
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)
432 # construct the message
433 message = "type: " + packetTypeStr.strip() + "\n"
434 message += "packet_id: " + packetIdStr + "\n"
437 message += "response: success \n"
439 message += "response: fail \n"
440 message += "reason: " + reason + "\n"
442 # send message in a UDP packet
443 sendSoceket.sendto(message,addr)
445 def processUdpZdoBindReqMessage(parsedData, addr):
449 if(zigbeeAddressAuthorityDict.has_key(addr)):
450 l = zigbeeAddressAuthorityDict[addr]
451 if(parsedData['device_address_long'] not in l):
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()
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
465 if(shortAddr != None):
467 # get a request number
468 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
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')
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'],
484 zigeeBindRequestMutex.release()
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)
491 # create the payload data
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'
501 # create and send binding command
502 zigbeeConnectionMutex.acquire()
503 zigbeeConnection.send('tx_explicit',
505 # frame_id=chr(seqNumber),
506 dest_addr_long=destLongAddr,
507 dest_addr=destShortAddr,
509 dest_endpoint='\x00',
514 zigbeeConnectionMutex.release()
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')
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)
531 def processUdpSendAddressMessage(parsedData, addr):
532 ''' Method handle a send address command
534 parsedData -- Pre-parsed Data that was in the UDP packet.
535 addr -- Address (IP and Port) of the UDP packet origin.
537 global zigbeeLongShortAddr
538 global zigbeeLongShortAddrMutex
539 global zigbeeUnregisteredAddresses
540 global zigbeeUnregisteredAddressesMutex
543 if(zigbeeAddressAuthorityDict.has_key(addr)):
544 l = zigbeeAddressAuthorityDict[addr]
545 if(parsedData['device_address_long'] not in l):
551 # construct success message
552 message = "type: send_address_response\n"
553 message += "packet_id: " + parsedData['packet_id'] + "\n"
554 message += "response: success\n"
556 # tell client that we got their request
557 sendSoceket.sendto(message,addr)
560 zigbeeLongShortAddrMutex.acquire()
561 doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
562 zigbeeLongShortAddrMutex.release()
565 # long address is already registered with the system so no need to do anything
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()
573 def processUdpZclReadAttributesMessage(parsedData, addr):
574 ''' Method handle a ZCL read attribute command
576 parsedData -- Pre-parsed Data that was in the UDP packet.
577 addr -- Address (IP and Port) of the UDP packet origin.
580 global zigbeeLongShortAddr
581 global zigbeeLongShortAddrMutex
582 global zigeeBindRequestMutex
583 global zigeeBindRequest
584 global zigbeeConnectionMutex
585 global zigbeeConnection
588 if(zigbeeAddressAuthorityDict.has_key(addr)):
589 l = zigbeeAddressAuthorityDict[addr]
590 if(parsedData['device_address_long'] not in l):
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()
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
608 if(shortAddr != None):
610 # get a request number
611 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
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')
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'])
627 # get all the attributes
628 attributeIds = parsedData['attribute_ids'].split(',')
630 # create the payload data
632 payloadData += '\x00'
633 payloadData += chr(seqNumber)
634 payloadData += '\x00'
636 # make all the attributes payloads
637 for attr in attributeIds:
639 attr = changeEndian(attr)
640 payloadData += hexStringToZigbeeHexString(attr)
642 # create and send binding command
643 zigbeeConnectionMutex.acquire()
644 zigbeeConnection.send('tx_explicit',
646 # frame_id=chr(seqNumber),
647 dest_addr_long=destLongAddr,
648 dest_addr=destShortAddr,
650 dest_endpoint=dstEndpoint,
655 zigbeeConnectionMutex.release()
659 # send a fail response
660 sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
663 def processUdpZclConfigureReportingMessage(parsedData, addr):
664 ''' Method handle a zcl configure reporting message
666 parsedData -- Pre-parsed Data that was in the UDP packet.
667 addr -- Address (IP and Port) of the UDP packet origin.
670 global zigbeeLongShortAddr
671 global zigbeeLongShortAddrMutex
672 global zigeeBindRequestMutex
673 global zigeeBindRequest
674 global zigbeeConnectionMutex
675 global zigbeeConnection
678 if(zigbeeAddressAuthorityDict.has_key(addr)):
679 l = zigbeeAddressAuthorityDict[addr]
680 if(parsedData['device_address_long'] not in l):
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()
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
696 if(shortAddr != None):
698 # get a request number
699 seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
703 sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
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'])
712 # create the payload data
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']))
723 if(parsedData.has_key('reportable_change')):
724 payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
727 # create and send binding command
728 zigbeeConnectionMutex.acquire()
729 zigbeeConnection.send('tx_explicit',
731 # frame_id=chr(seqNumber),
732 dest_addr_long=destLongAddr,
733 dest_addr=destShortAddr,
735 dest_endpoint=dstEndpoint,
740 zigbeeConnectionMutex.release()
744 sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
747 def processUdpPolicySet(parsedData, addr):
748 ''' Method handle a policy set message
750 parsedData -- Pre-parsed Data that was in the UDP packet.
751 addr -- Address (IP and Port) of the UDP packet origin.
753 print "=================================================================="
754 print "Policy set: ", parsedData
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'])
762 zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
764 def processUdpPolicyClear(parsedData, addr):
765 ''' Method handle a policy set message
767 parsedData -- Pre-parsed Data that was in the UDP packet.
768 addr -- Address (IP and Port) of the UDP packet origin.
770 print "=================================================================="
771 print "Clear policy: ", parsedData
773 # do nothing if wrong source
774 if addr == SYSTEM_MASTER_ADDRESS:
775 zigbeeAddressAuthorityDict.clear()
781 def processZigbeeATCommandMessage(parsedData):
782 ''' Method to process an AT zigbee message
784 parsedData -- Pre-parsed (into a dict) data from message.
786 global ZIGBEE_DEVICE_ADDRESS
787 global didGetLocalRadioHighAddress
788 global didGetLocalRadioLowAddress
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)
794 for e in parsedData['parameter']:
795 value += "{0:02x}".format(ord(e))
797 # set the correct portion of the address
798 ZIGBEE_DEVICE_ADDRESS = value + ZIGBEE_DEVICE_ADDRESS[8:]
800 #signal that we got this part of the address
801 didGetLocalRadioHighAddress = True
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)
807 for e in parsedData['parameter']:
808 value += "{0:02x}".format(ord(e))
810 # set the correct portion of the address
811 ZIGBEE_DEVICE_ADDRESS = ZIGBEE_DEVICE_ADDRESS[0:8] + value
813 #signal that we got this part of the address
814 didGetLocalRadioLowAddress = True
816 def processZigbeeRxExplicitCommandMessage(parsedData):
817 ''' Method to process a rx-explicit zigbee message
819 parsedData -- Pre-parsed (into a dict) data from message.
821 global zigeeBindRequestMutex
822 global zigeeBindRequest
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'])
829 # check if this short address is for a device that has yet to be
831 zigbeeUnregisteredAddressesMutex.acquire()
832 if(longAddr in zigbeeUnregisteredAddresses):
833 zigbeeUnregisteredAddresses.remove(longAddr)
834 zigbeeUnregisteredAddressesMutex.release()
836 # update/ or insert the short address
837 zigbeeLongShortAddrMutex.acquire()
838 zigbeeLongShortAddr[longAddr] = shortAddr
839 zigbeeLongShortAddrMutex.release()
842 # if this is a ZDO message/response
843 if(parsedData['profile'] == '\x00\x00'):
845 # if this is a device announcement so we can get some useful data from it
846 if(parsedData['cluster'] == '\x00\x13'):
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])
852 # change the endian of the address
853 longAddr = changeEndian(longAddr)
854 shortAddr = changeEndian(shortAddr)
856 # update the table with the new information
857 zigbeeLongShortAddrMutex.acquire()
858 zigbeeLongShortAddr[longAddr] = shortAddr
859 zigbeeLongShortAddrMutex.release()
861 # check if this short address is for a device that has yet to be
863 zigbeeUnregisteredAddressesMutex.acquire()
864 if(longAddr in zigbeeUnregisteredAddresses):
865 zigbeeUnregisteredAddresses.remove(longAddr)
866 zigbeeUnregisteredAddressesMutex.release()
868 # if this is a response to a zdo bind_req message
869 elif(parsedData['cluster'] == '\x80\x21'):
871 # get the status and sequence number from the message
872 seqNumber = parsedData['rf_data'][0]
873 statusCode = parsedData['rf_data'][1]
875 # get the bind tuple information
876 # for this specific bind request
878 zigeeBindRequestMutex.acquire()
879 if(zigeeBindRequest.has_key(ord(seqNumber))):
880 tup = zigeeBindRequest[ord(seqNumber)]
881 zigeeBindRequestMutex.release()
884 # cant really do anything in this case...
885 # don't have any information on who the data is for
889 if(ord(statusCode) == 0):
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])
898 zibeeHACallback[(tup[0], tup[1])] = [tup[3]]
899 zibeeHACallbackMutex.release()
901 # send success message
902 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
905 elif (ord(statusCode) == 170):
906 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
909 elif (ord(statusCode) == 174):
910 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
912 # Other issue, dont have code for
914 sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
916 # if this is a response to a short address query
917 elif(parsedData['cluster'] == '\x80\x00'):
920 statusCode = parsedData['rf_data'][0]
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
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]))
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()
937 # update/insert the short address
938 zigbeeLongShortAddrMutex.acquire()
939 zigbeeLongShortAddr[longAddr] = shortAddr
940 zigbeeLongShortAddrMutex.release()
942 # if this is a home automation zcl message/response
943 elif (parsedData['profile'] == '\x01\x04'):
945 # get the zcl message header
946 zclFrameControl = parsedData['rf_data'][0]
947 zclSeqNumber = parsedData['rf_data'][1]
948 zclCommand = parsedData['rf_data'][2]
950 # this is a zcl read attribute response
951 if(zclCommand == '\x01'):
953 # get the zcl payload
954 zclPayload = parsedData['rf_data'][3:]
955 attibuteResponseList = []
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:]
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"))
969 # get the data type and data length of the attributre
970 attributeType = zclPayload[0]
971 dataLength = zclDataTypeToBytes(zclPayload)
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:]
979 zclPayload = zclPayload[1:]
981 # package the data and add it to the list
982 newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
983 attibuteResponseList.append(newData)
985 # consume the data size of the payload
986 zclPayload = zclPayload[dataLength:]
988 # find who to send response to
990 zigbeeSeqNumberToClientMutex.acquire()
991 if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
992 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
993 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
994 zigbeeSeqNumberToClientMutex.release()
996 # no one to send the response to so just move on
998 # cant really do anything here
1001 # create the response message
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: "
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)
1016 message += attrIdStr
1018 message += "success"
1020 message += "%0.2x" % ord(t[2])
1025 dat += "%0.2x" % ord(c)
1026 dat = changeEndian(dat)
1030 attrIdStr = "%0.4x" % attrId
1031 attrIdStr = changeEndian(attrIdStr)
1033 message += attrIdStr
1035 message += "not_supported"
1038 message = message[0:len(message) - 1]
1042 sendSoceket.sendto(message,tup[0])
1044 # this is a zcl configure attribute response
1045 elif(zclCommand == '\x07'):
1047 # find who to send response to
1049 zigbeeSeqNumberToClientMutex.acquire()
1050 if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
1051 tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1052 del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
1053 zigbeeSeqNumberToClientMutex.release()
1055 # no one to send the response to so just move on
1057 # cant really do anything here
1061 zclPayload = parsedData['rf_data'][3:]
1063 # construct the message
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: "
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])
1078 attibuteResponseList = []
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:]
1087 newData = (attributeStatus,attributeDirection, attributeId)
1088 attibuteResponseList.append(newData)
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)
1096 message += attrIdStr
1099 message += "success"
1106 message += "reported"
1108 message += "received"
1111 message = message[0:len(message) - 1]
1113 sendSoceket.sendto(message,tup[0])
1115 # this is a zcl report attribute message
1116 elif(zclCommand == '\x0a'):
1118 # get teh zcl payload
1119 zclPayload = parsedData['rf_data'][3:]
1120 attibuteResponseList = []
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)
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:]
1134 zclPayload = zclPayload[1:]
1136 newData = (attributeId, attributeType ,zclPayload[0:dataLength])
1137 attibuteResponseList.append(newData)
1138 zclPayload = zclPayload[dataLength:]
1141 # get callback clients to respond to
1142 callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
1144 zibeeHACallbackMutex.acquire()
1145 if(zibeeHACallback.has_key(callbackIndex)):
1146 retAddr = zibeeHACallback[callbackIndex]
1147 zibeeHACallbackMutex.release()
1149 # no one to respond to so do nothing here
1150 if(retAddr == None):
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: "
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)
1166 message += attrIdStr
1168 message += "%0.2x" % ord(t[1])
1173 dat += "%0.2x" % ord(c)
1174 dat = changeEndian(dat)
1178 message = message[0:len(message) - 1]
1181 # send to all client that want this callback
1183 sendSoceket.sendto(message,ra)
1185 # -----------------------------------------------------------------------------
1186 # Communication Callback/Parse Methods
1187 # -----------------------------------------------------------------------------
1188 def handleNewZigbeeMessage(parsedData):
1189 ''' Method to process a zigbee message from the local radio.
1191 parsedData -- Pre-parsed (into a dict) data from message.
1193 print "=================================================================="
1194 print "New Zigbee Message"
1195 # printMessageData(parsedData)
1197 # dispatch to the correct zigbee handler
1198 if (parsedData['id'] == 'at_response'):
1199 processZigbeeATCommandMessage(parsedData)
1201 elif (parsedData['id'] == 'rx_explicit'):
1202 #printMessageData(parsedData)
1203 processZigbeeRxExplicitCommandMessage(parsedData)
1206 print "Unknown API format"
1208 print "=================================================================="
1210 def handleNewUdpPacket(data, addr):
1211 ''' Method to parse and handle an incoming UDP packet.
1213 data -- Data that was in the UDP packet.
1214 addr -- Address (IP and Port) of the UDP packet origin.
1217 print "=================================================================="
1218 print "Got New UDP packet..."
1222 # data comes in as 'key: value\n key: value...' string and so needs to be
1223 # parsed into a dict
1226 # 1 key, value pair per line
1227 for line in data.split('\n'):
1229 # key and values are split based on a ':'
1230 fields = line.split(':')
1232 # make sure properly formated otherwise just ignore it
1233 if len(fields) == 2:
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()
1239 # wrap in try statement just in case there is an improperly formated packet we
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)
1258 #print "unknown Packet: " + parsedData["type"]
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()
1266 print "=================================================================="
1269 # -----------------------------------------------------------------------------
1270 # Main Running Methods
1271 # -----------------------------------------------------------------------------
1274 '''Main function used for starting the application as the main driver'''
1276 global ZIGBEE_SERIAL_PORT
1277 global ZIGBEE_SERIAL_BAUD
1278 global UDP_RECEIVE_PORT
1279 global zigbeeConnection
1283 parseCommandLineArgs(sys.argv[1:])
1285 # create serial object used for communication to the zigbee radio
1286 sc = serial.Serial(ZIGBEE_SERIAL_PORT, ZIGBEE_SERIAL_BAUD)
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
1293 zigbeeConnection = ZigBee(sc, callback=handleNewZigbeeMessage)
1295 # get the long address of our local radio before we start doing anything
1296 getConnectedRadioLongAddress();
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))
1302 # create the thread that does short address lookups
1303 addressUpdateWorkerThread = threading.Thread(target=addressUpdateWorkerMethod)
1304 addressUpdateWorkerThread.start()
1309 print "=================================================================="
1311 print "=================================================================="
1313 # wait for an incoming UDP packet
1314 # this is a blocking call
1315 data, addr = receiveSoceket.recvfrom(4096)
1317 # handle the UDP packet appropriately
1318 handleNewUdpPacket(data, addr)
1320 except KeyboardInterrupt:
1321 # use the keyboard interupt to catch a ctrl-c and kill the application
1325 # something went really wrong and so exit with error message
1326 traceback.print_exc()
1328 # signal all threads to exit
1331 # wait for threads to finish before closing of the resources
1332 addressUpdateWorkerThread.join()
1335 # make sure to close all the connections
1336 zigbeeConnection.halt()
1337 receiveSoceket.close()
1340 if __name__ == "__main__":
1341 # call main function since this is being run as the start