--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<data>
+ <profile>
+ <description_file>xbp24-s2c-th_4055.xml</description_file>
+ <settings>
+ <setting command="ID">0</setting>
+ <setting command="SC">7FFF</setting>
+ <setting command="SD">3</setting>
+ <setting command="ZS">2</setting>
+ <setting command="NJ">FF</setting>
+ <setting command="NW">0</setting>
+ <setting command="JV">0</setting>
+ <setting command="JN">0</setting>
+ <setting command="CE">1</setting>
+ <setting command="DO">0</setting>
+ <setting command="DC">0</setting>
+ <setting command="DH">0</setting>
+ <setting command="DL">0</setting>
+ <setting command="NI">0x20</setting>
+ <setting command="NH">1E</setting>
+ <setting command="BH">0</setting>
+ <setting command="AR">FF</setting>
+ <setting command="DD">A0000</setting>
+ <setting command="NT">3C</setting>
+ <setting command="NO">0</setting>
+ <setting command="CR">3</setting>
+ <setting command="SE">E8</setting>
+ <setting command="DE">E8</setting>
+ <setting command="CI">11</setting>
+ <setting command="TO">0</setting>
+ <setting command="PL">4</setting>
+ <setting command="EE">1</setting>
+ <setting command="EO">0</setting>
+ <setting command="KY"></setting>
+ <setting command="NK"></setting>
+ <setting command="BD">7</setting>
+ <setting command="NB">0</setting>
+ <setting command="SB">0</setting>
+ <setting command="RO">3</setting>
+ <setting command="D7">1</setting>
+ <setting command="D6">0</setting>
+ <setting command="AP">1</setting>
+ <setting command="AO">3</setting>
+ <setting command="CT">64</setting>
+ <setting command="GT">3E8</setting>
+ <setting command="CC">2B</setting>
+ <setting command="SP">20</setting>
+ <setting command="SN">1</setting>
+ <setting command="SM">0</setting>
+ <setting command="ST">1388</setting>
+ <setting command="SO">0</setting>
+ <setting command="WH">0</setting>
+ <setting command="PO">0</setting>
+ <setting command="D0">1</setting>
+ <setting command="D1">0</setting>
+ <setting command="D2">0</setting>
+ <setting command="D3">0</setting>
+ <setting command="D4">0</setting>
+ <setting command="D5">1</setting>
+ <setting command="D8">1</setting>
+ <setting command="D9">1</setting>
+ <setting command="P0">1</setting>
+ <setting command="P1">0</setting>
+ <setting command="P2">0</setting>
+ <setting command="P3">1</setting>
+ <setting command="P4">1</setting>
+ <setting command="PR">1FFF</setting>
+ <setting command="PD">1FBF</setting>
+ <setting command="LT">0</setting>
+ <setting command="RP">28</setting>
+ <setting command="IR">0</setting>
+ <setting command="IC">0</setting>
+ <setting command="V+">0</setting>
+ </settings>
+ </profile>
+</data>
--- /dev/null
+
+
+From Application to driver:
+---------------------------
+type: zdo_bind_request
+packet_id: <number_to_id_packet>
+device_address_long: <zigbee_long_address>
+cluster_id: <cluser_id_to_bind_to>
+device_endpoint: <endpoint_for_binding>
+
+
+
+From Driver to Application:
+---------------------------
+
+type: callback_register_ack
+packet_id: <number_to_id_packet>
+response: success or fail
+reason: <blank if success>
\ No newline at end of file
--- /dev/null
+---------------------------------------
+ To set a new policy for the driver
+----------------------------------------
+
+type: policy_set
+ip_address: <ip_address>
+port: <port_numer>
+device_address_long: <zigbee_address>
+
+
+
+
+
+---------------------------------------
+ To clear all the policies from the driver
+----------------------------------------
+type: policy_clear
--- /dev/null
+
+
+From Application to driver:
+---------------------------
+type: send_address
+packet_id: <number_to_id_packet>
+device_address_long: <zigbee_long_address>
+
+
+From Driver to Application:
+---------------------------
+type: send_address_response
+packet_id: <number_to_id_packet>
+response: success
--- /dev/null
+From Application to driver:
+---------------------------
+type: zcl_read_attributes
+packet_id: <number_to_id_packet>
+device_address_long: <zigbee_long_address>
+device_endpoint: <device_endpoint>
+cluster_id: <cluster_number>
+profile_id: <profile_number>
+attribute_ids: <attr_1>[, <attr2> ......]
+
+From Driver to Application:
+---------------------------
+
+type: zcl_read_attributes_response
+packet_id: <number_to_id_packet>
+device_address_long: <zigbee_long_address>
+cluster_id: <cluster_number>
+profile_id: <profile_number>
+attribute_ids: <attr1 stuff, attr1 stuff> ; <attr2> ......]
--- /dev/null
+from zigbee import *
+from socket import *
+from select import *
+import serial
+import time
+import collections
+import sys
+import getopt
+import traceback
+from threading import Thread, Lock
+import random
+import threading
+
+
+# -----------------------------------------------------------------------------
+# Constants ans Pseudo-Constants
+# -----------------------------------------------------------------------------
+UDP_RECEIVE_PORT = 5005 # port used for incoming UDP data
+UDP_RECEIVE_BUFFER_SIZE = 4096 # max buffer size of an incoming UDP packet
+
+# time for messages to wait for a response before the system clears away that
+# sequence identifier
+ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5
+
+# address of our local zigbee radio
+ZIGBEE_DEVICE_ADDRESS = "xxxxxxxxxxxxxxxx"
+
+SYSTEM_MASTER_ADDRESS = ("192.168.2.108", 12345) # ip address and portof the system master node
+
+
+# -----------------------------------------------------------------------------
+# Global Variables and Objects
+# -----------------------------------------------------------------------------
+
+# zigbee communications object and its mutex
+zigbeeConnection = None
+zigbeeConnectionMutex = Lock()
+
+# zigbee mapping from long to short object dict
+zigbeeLongShortAddr = dict()
+zigbeeLongShortAddrMutex = Lock()
+
+# zigbee mapping from a sequence number to a client
+# for correct response handling
+zigbeeSeqNumberToClient = dict()
+zigbeeSeqNumberToClientMutex = Lock()
+
+zigbeeBindRequest = dict()
+zigbeeBindRequestMutex = Lock()
+
+# Keeps record of where to send callbacks to when an HA message is received
+zigbeeHACallback = dict()
+zigbeeHACallbackMutex = Lock()
+
+# Keeps a record of device addresses whose short addresses have not been
+# determined yet
+zigbeeUnregisteredAddresses = []
+zigbeeUnregisteredAddressesMutex = Lock()
+
+# used to signal all threads to end
+doEndFlag = False
+
+
+# 2 sockets, one for sending (not bound to a port manually)
+# and one for receiving, known port binding by application
+# both UDP sockets
+sendSocket = socket(AF_INET, SOCK_DGRAM)
+receiveSocket = socket(AF_INET, SOCK_DGRAM)
+
+# zigbee address authority list
+zigbeeAddressAuthorityDict = dict()
+
+
+
+# -----------------------------------------------------------------------------
+# Helper Methods
+# -----------------------------------------------------------------------------
+
+
+def parseCommandLineArgs(argv):
+ try:
+ opts, args = getopt.getopt(
+ argv, "h:u:", ["udpport="])
+
+ except getopt.GetoptError:
+ print 'test.py -u <udp_port>'
+ sys.exit(2)
+
+ for opt, arg in opts:
+ if opt == '-h':
+ print 'test.py -u <udp_port>'
+ sys.exit()
+
+
+# -------------
+# Convenience (Stateless)
+# -------------
+
+def shortToStr(short):
+ s=chr(short>>8) + chr(short & 0xff)
+ return s
+
+def hexToInt(str):
+ ret = 0
+ for h in str:
+ ret = ret << 4
+ ret += int(h, 16)
+ return ret
+
+def hexListToChar(hexList):
+ ''' Method to convert a list/string of characters into their corresponding values
+
+ hexList -- list or string of hex characters
+ '''
+ retString = ""
+ for h in hexList:
+ retString += chr(int(h, 16))
+ return retString
+
+def splitByN(seq, n):
+ ''' Method to split a string into groups of n characters
+
+ seq -- string
+ n -- group by number
+ '''
+ return [seq[i:i+n] for i in range(0, len(seq), n)]
+
+def changeEndian(hexString):
+ ''' Method to change endian of a hex string
+
+ hexList -- string of hex characters
+ '''
+ split = splitByN(hexString, 2) # get each byte
+ split.reverse(); # reverse ordering of the bytes
+
+ # reconstruct
+ retString = ''
+ for s in split:
+ retString += s
+ return retString
+
+def reverse(string):
+ ''' Method to change endian of a hex string
+
+ hexList -- string of hex characters
+ '''
+ return string[::-1]
+
+def printMessageData(data):
+ ''' Method to print a zigbee message to the console
+
+ data -- pre-parsed zigbee message
+ '''
+ for d in data:
+ print d, ' : ',
+ for e in data[d]:
+ print "{0:02x}".format(ord(e)),
+ if (d == 'id'):
+ print "({})".format(data[d]),
+ print
+
+def hexStringToZigbeeHexString(hexString):
+ ''' Method to change a hex string to a string of characters with the hex values
+
+ hexList -- string of hex characters
+ '''
+ return hexListToChar(splitByN(hexString, 2))
+
+def hexStringToAddr(hexString):
+ ''' Method to change a hex string to a string of characters with the hex values
+
+ hexList -- string of hex characters
+ '''
+ split = splitByN(hexString, 2)
+ newstring = '[' + split[0]
+ for h in split[1:]:
+ newstring += ':' + h
+ newstring += ']!'
+ return newstring
+
+def addrToHexString(addr):
+ hex=addr[1:25]
+ list=[hex[i:i+2] for i in range(0, len(hex), 3)]
+ retstring = ''
+ for e in list:
+ retstring += e
+ return retstring
+
+def zigbeeHexStringToHexString(zigbeeHexString):
+ ''' Method to change string of characters with the hex values to a hex string
+
+ hexList -- string of characters with hex values
+ '''
+
+ retString = ''
+ for e in zigbeeHexString:
+ retString += "{0:02x}".format(ord(e))
+ return retString
+
+def zclDataTypeToBytes(zclPayload):
+ ''' Method to determine data length of a zcl attribute
+
+ zclPayload -- ZCL payload data, must have dataType as first byte
+ '''
+ attrType = ord(zclPayload[0])
+
+ if(attrType == 0x00):
+ return 0
+ elif (attrType == 0x08):
+ return 1
+ elif (attrType == 0x09):
+ return 2
+ elif (attrType == 0x0a):
+ return 3
+ elif (attrType == 0x0b):
+ return 4
+ elif (attrType == 0x0c):
+ return 5
+ elif (attrType == 0x0d):
+ return 6
+ elif (attrType == 0x0e):
+ return 7
+ elif (attrType == 0x0f):
+ return 8
+ elif (attrType == 0x10):
+ return 1
+ elif (attrType == 0x18):
+ return 1
+ elif (attrType == 0x19):
+ return 2
+ elif (attrType == 0x1a):
+ return 3
+ elif (attrType == 0x1b):
+ return 4
+ elif (attrType == 0x1c):
+ return 5
+ elif (attrType == 0x1d):
+ return 6
+ elif (attrType == 0x1e):
+ return 7
+ elif (attrType == 0x1f):
+ return 8
+ elif (attrType == 0x20):
+ return 1
+ elif (attrType == 0x21):
+ return 2
+ elif (attrType == 0x22):
+ return 3
+ elif (attrType == 0x23):
+ return 4
+ elif (attrType == 0x24):
+ return 5
+ elif (attrType == 0x25):
+ return 6
+ elif (attrType == 0x26):
+ return 7
+ elif (attrType == 0x27):
+ return 8
+ elif (attrType == 0x28):
+ return 1
+ elif (attrType == 0x29):
+ return 2
+ elif (attrType == 0x2a):
+ return 3
+ elif (attrType == 0x2b):
+ return 4
+ elif (attrType == 0x2c):
+ return 5
+ elif (attrType == 0x2d):
+ return 6
+ elif (attrType == 0x2e):
+ return 7
+ elif (attrType == 0x2f):
+ return 8
+ elif (attrType == 0x30):
+ return 1
+ elif (attrType == 0x31):
+ return 2
+ elif (attrType == 0x38):
+ return 2
+ elif (attrType == 0x39):
+ return 4
+ elif (attrType == 0x3a):
+ return 8
+ elif (attrType == 0x41):
+ return ord(zclPayload[1])
+ elif (attrType == 0x42):
+ return ord(zclPayload[1])
+ elif (attrType == 0x43):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0x44):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0xe0):
+ return 4
+ elif (attrType == 0xe1):
+ return 4
+ elif (attrType == 0xe2):
+ return 4
+ elif (attrType == 0xe8):
+ return 2
+ elif (attrType == 0xe9):
+ return 2
+ elif (attrType == 0xea):
+ return 4
+ elif (attrType == 0xf0):
+ return 8
+ elif (attrType == 0xf1):
+ return 16
+ elif (attrType == 0xff):
+ return 0
+
+# -------------
+# Other
+# -------------
+
+def createSequenceNumberForClient(addr, packetId):
+ ''' Method to get and store a sequence number with a specific client
+ for a specific message.
+
+ addr -- UDP address of the client to associate with the seq. number
+ packetId -- packet id from the UDP packet
+ '''
+ # keep trying to find a number to return
+ while(True):
+
+ # get the current time
+ epoch_time = int(time.time())
+
+ # get the current list of used numbers
+ zigbeeSeqNumberToClientMutex.acquire()
+ keysList = zigbeeSeqNumberToClient.keys()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # if all the numbers are already used
+ if(len(keysList) == 256):
+
+ # get a list of all the items
+ zigbeeSeqNumberToClientMutex.acquire()
+ itemsList = zigbeeSeqNumberToClient.items()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # search for a number that is old enough to get rid of otherwise use -1
+ seqNum = -1
+ for item in itemsList:
+ if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
+ seqNumber = item[0]
+ break
+
+ if(seqNum != -1):
+ # replace the record with new data if we found one to replace
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[seqNumber] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+
+ return seqNumber
+
+ else:
+ # not all numbers used yet so pick one randomly
+ randNum = random.randrange(0,256)
+
+ # check if we are using the number yet
+ if(randNum not in keysList):
+
+ # we are not so insert to keep track who this number is for and return it
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[randNum] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+ return randNum
+
+def getConnectedRadioLongAddress():
+ """ Method to make sure we get the MAC address of our local radio"""
+ global zigbeeConnection
+ global zigbeeMutex
+ global ZIGBEE_DEVICE_ADDRESS
+
+ data = ddo_get_param(None, 'SH')
+ valuehi = ""
+ for e in data:
+ valuehi += "{0:02x}".format(ord(e))
+
+ data = ddo_get_param(None, 'SL')
+ valuelo = ""
+ for e in data:
+ valuelo += "{0:02x}".format(ord(e))
+ ZIGBEE_DEVICE_ADDRESS = valuehi + valuelo
+
+def addressUpdateWorkerMethod():
+ ''' Method to keep refreshing the short addresses of the known zigbee devices'''
+ global doEndFlag
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+ # keep looping until signaled to quit
+ while(not doEndFlag):
+
+ addrList = []
+
+ # add unregistered (short addresses unknown) devices so
+ # that we can look them up
+ zigbeeUnregisteredAddressesMutex.acquire()
+ addrList.extend(zigbeeUnregisteredAddresses)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # add the devices that we have short addresses for so we can
+ # get their most recent short addresses
+ zigbeeLongShortAddrMutex.acquire()
+ addrList.extend(zigbeeLongShortAddr.keys())
+ zigbeeLongShortAddrMutex.release()
+
+ # Loop through all the addresses and send messages for each address
+ for ad in addrList:
+ # create payload for a query on the network for a short address
+ payload = '\x00'
+ payload += hexStringToZigbeeHexString(changeEndian(ad))
+ payload += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ # The Format of the tuple is:
+ # (address_string, endpoint, profile_id, cluster_id)
+
+ DESTINATION=(hexStringToAddr(ad), 0x0, 0x0, 0x0)
+
+ zigbeeConnection.sendto(payload, 0, DESTINATION);
+ zigbeeConnectionMutex.release()
+
+ time.sleep(30)
+
+
+# -------------
+# UDP
+# -------------
+
+def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
+ ''' Method to send a success or fail back to a client.
+
+ addr -- UDP address to send packet to
+ packetTypeStr -- name of this specific packet
+ packetIdStr -- packet id to send
+ sucOrFail -- whether this is a success or fail message (True = success)
+ reason -- reason of failure (if needed, default is None)
+
+ '''
+
+ global sendSocket
+
+ # construct the message
+ message = "type: " + packetTypeStr.strip() + "\n"
+ message += "packet_id: " + packetIdStr + "\n"
+
+ if(sucOrFail):
+ message += "response: success \n"
+ else:
+ message += "response: fail \n"
+ message += "reason: " + reason + "\n"
+
+ # send message in a UDP packet
+ sendSocket.sendto(message,addr)
+
+def processUdpZdoBindReqMessage(parsedData, addr):
+ ''' Method handle a zdo bind request message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeBindRequestMutex
+ global zigbeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+ shortAddr = None
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # a bind request was made so must store and wait for response
+ # before we setup callbacks, so keep just the data we need to create the callback
+ zigbeeBindRequestMutex.acquire()
+ zigbeeBindRequest[seqNumber] = (parsedData['device_address_long'],
+ parsedData['cluster_id'],
+ parsedData['packet_id'],
+ addr)
+ zigbeeBindRequestMutex.release()
+
+ # construct the short and long addresses of the message for sending
+ # make sure they are in the correct format
+ destLongAddr = parsedData['device_address_long']
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+
+ # create the payload data
+ payloadData = ""
+ payloadData += chr(seqNumber)
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_address_long']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_endpoint']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['cluster_id']))
+ payloadData += '\x03'
+ payloadData += hexStringToZigbeeHexString(changeEndian(ZIGBEE_DEVICE_ADDRESS))
+ payloadData += '\x01'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+
+ # The Format of the tuple is:
+ # (address_string, endpoint, profile_id, cluster_id)
+ DESTINATION = (hexStringToAddr(destLongAddr), 0x0, 0x0, 0x21)
+ zigbeeConnection.sendto(payloadData, 0, DESTINATION)
+
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a failure packet since there is no short address available
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+
+def processUdpZdoUnBindReqMessage(parsedData, addr):
+ zigbeeHACallbackMutex.acquire();
+ if(zigbeeHACallback.has_key(parsedData['device_address_long'], parsedData['cluster_id'])):
+ zigbeeHACallback(parsedData['device_address_long'], parsedData['cluster_id']).remove(addr)
+ zigbeeHACallbackMutex.release()
+ sendUdpSuccessFail(addr, 'zdo_unbind_request', parsedData['packet_id'], True)
+
+
+def processUdpSendAddressMessage(parsedData, addr):
+ ''' Method handle a send address command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global sendSocket
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ # construct success message
+ message = "type: send_address_response\n"
+ message += "packet_id: " + parsedData['packet_id'] + "\n"
+ message += "response: success\n"
+
+ # tell client that we got their request
+ sendSocket.sendto(message,addr)
+
+ # construct
+ zigbeeLongShortAddrMutex.acquire()
+ doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
+ zigbeeLongShortAddrMutex.release()
+
+ if(doesHaveKey):
+ # long address is already registered with the system so no need to do anything
+ return
+
+ # long address not registered so add it for short address lookup
+ zigbeeUnregisteredAddressesMutex.acquire()
+ zigbeeUnregisteredAddresses.append(parsedData['device_address_long'])
+ zigbeeUnregisteredAddressesMutex.release()
+
+def processUdpZclReadAttributesMessage(parsedData, addr):
+ ''' Method handle a ZCL read attribute command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeBindRequestMutex
+ global zigbeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = parsedData['profile_id']
+ clusterId = parsedData['cluster_id']
+ dstEndpoint = parsedData['device_endpoint']
+
+ # get all the attributes
+ attributeIds = parsedData['attribute_ids'].split(',')
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x00'
+
+ # make all the attributes payloads
+ for attr in attributeIds:
+ attr = attr.strip()
+ attr = changeEndian(attr)
+ payloadData += hexStringToZigbeeHexString(attr)
+
+
+ # (address_string, endpoint, profile_id, cluster_id)
+ DESTINATION = (hexStringToAddr(zigbeeHexStringToHexString(destLongAddr)), hexToInt(dstEndpoint), hexToInt(profileId), hexToInt(clusterId))
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.sendto(payloadData, 0, DESTINATION)
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpZclConfigureReportingMessage(parsedData, addr):
+ ''' Method handle a zcl configure reporting message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeBindRequestMutex
+ global zigbeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = parsedData['profile_id']
+ clusterId = parsedData['cluster_id']
+ dstEndpoint = parsedData['device_endpoint']
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x06'
+ payloadData += '\x00'
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['attribute_id']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['data_type']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['min_reporting_interval']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['max_reporting_interval']))
+
+ if(parsedData.has_key('reportable_change')):
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
+
+ DESTINATION = (hexStringToAddr(zigbeeHexStringToHexString(destLongAddr)), hexToInt(dstEndpoint), hexToInt(profileId), hexToInt(clusterId))
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+
+ zigbeeConnection.sendto(payloadData, 0, DESTINATION)
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpPolicySet(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Policy set: ", parsedData
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS:
+ key = (parsedData['ip_address'], int(parsedData['port']))
+ if (zigbeeAddressAuthorityDict.has_key(key)):
+ zigbeeAddressAuthorityDict[key].append(parsedData['device_address_long'])
+ else:
+ zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
+
+def processUdpPolicyClear(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Clear policy: ", parsedData
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS:
+ zigbeeAddressAuthorityDict.clear()
+
+
+def processZigbeeRxExplicitCommandMessage(rawData, src_addr):
+ ''' Method to process a rx-explicit zigbee message
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+
+# The format for addr is the tuple (address_string, endpoint, profile_id, cluster_id).
+ global zigbeeBindRequestMutex
+ global zigbeeBindRequest
+ parsedData = dict()
+ parsedData['source_addr_long']=hexStringToZigbeeHexString(addrToHexString(src_addr[0]))
+ parsedData['cluster']=shortToStr(src_addr[3])
+ parsedData['profile']=shortToStr(src_addr[2])
+ parsedData['rf_data']=rawData
+
+ # get the long and short addresses from the message payload since we can
+ # use these to update the short addresses since this short address is fresh
+ longAddr = zigbeeHexStringToHexString(parsedData['source_addr_long'])
+
+ # check if this short address is for a device that has yet to be
+ # registered
+
+ # if this is a ZDO message/response
+ if(parsedData['profile'] == '\x00\x00'):
+
+ # if this is a device announcement so we can get some useful data from it
+ if(parsedData['cluster'] == '\x00\x13'):
+
+ # pick out the correct parts of the payload
+ longAddr = zigbeeHexStringToHexString(parsedData['rf_data'][3:11])
+ shortAddr = zigbeeHexStringToHexString(parsedData['rf_data'][1:3])
+
+ # change the endian of the address
+ longAddr = changeEndian(longAddr)
+ shortAddr = changeEndian(shortAddr)
+
+ # update the table with the new information
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ # check if this short address is for a device that has yet to be
+ # registered
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # if this is a response to a zdo bind_req message
+ elif(parsedData['cluster'] == '\x80\x21'):
+
+ # get the status and sequence number from the message
+ seqNumber = parsedData['rf_data'][0]
+ statusCode = parsedData['rf_data'][1]
+
+ # get the bind tuple information
+ # for this specific bind request
+ tup = None
+ zigbeeBindRequestMutex.acquire()
+ if(zigbeeBindRequest.has_key(ord(seqNumber))):
+ tup = zigbeeBindRequest[ord(seqNumber)]
+ zigbeeBindRequestMutex.release()
+
+ if(tup == None):
+ # cant really do anything in this case...
+ # don't have any information on who the data is for
+ return
+
+ # successful binding
+ if(ord(statusCode) == 0):
+
+ # add a callback for this specific device and cluster
+ # to the HA callback dict
+ if(zigbeeHACallback.has_key((tup[0], tup[1]))):
+ if(tup[3] not in zigbeeHACallback[(tup[0], tup[1])]):
+ zigbeeHACallback[(tup[0], tup[1])].append(tup[3])
+ else:
+ zigbeeHACallback[(tup[0], tup[1])] = [tup[3]]
+
+ # send success message
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
+
+ # Not Supported
+ elif (ord(statusCode) == 170):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
+
+ # Table Full
+ elif (ord(statusCode) == 174):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
+
+ # Other issue, dont have code for
+ else:
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
+
+ # if this is a response to a short address query
+ elif(parsedData['cluster'] == '\x80\x00'):
+
+ # get a status code
+ statusCode = parsedData['rf_data'][0]
+
+ # does not matter if this is not a success, we can try again later
+ if(statusCode != '\x00'):
+ # status code was not success so do not do anything
+ return
+
+ # get the short and long address information
+ longAddr = changeEndian(zigbeeHexStringToHexString(parsedData['rf_data'][2:10]))
+ shortAddr = changeEndian(zigbeeHexStringToHexString( parsedData['rf_data'][10:12]))
+
+ # remove device from list of unregistered devices if it is in it
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # update/insert the short address
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ # if this is a home automation zcl message/response
+ elif (parsedData['profile'] == '\x01\x04'):
+
+ # get the zcl message header
+ zclFrameControl = parsedData['rf_data'][0]
+ zclSeqNumber = parsedData['rf_data'][1]
+ zclCommand = parsedData['rf_data'][2]
+
+ # this is a zcl read attribute response
+ if(zclCommand == '\x01'):
+
+ # get the zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # get the data for each data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ attributeStatus = zclPayload[2]
+ zclPayload = zclPayload[3:]
+
+ if(ord(attributeStatus) != 0):
+ # if attribute is not supported then it has no data
+ # package the data and add it to the list
+ attibuteResponseList.append((attributeId,"not_supported"))
+ else:
+
+ # get the data type and data length of the attributre
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ # consume zcl payload data
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ # package the data and add it to the list
+ newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+
+ # consume the data size of the payload
+ zclPayload = zclPayload[dataLength:]
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # create the response message
+ packetId = tup[2]
+ message = "type : zcl_read_attributes_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # create the message for each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ if(t[1] == "success"):
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "success"
+ message += ", "
+ message += "%0.2x" % ord(t[2])
+ message += ", "
+
+ dat = ""
+ for c in (t[3]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+ else:
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "not_supported"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+
+ # send the socket
+ sendSocket.sendto(message,tup[0])
+
+ # this is a zcl configure attribute response
+ elif(zclCommand == '\x07'):
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # get zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+
+ # construct the message
+ packetId = tup[2]
+ message = "type : zcl_configure_reporting_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ if(len(zclPayload) == 1):
+ # if all the configurations are a success then only send back a success
+ # based on zigbee specs
+ message += "all_success \n";
+ sendSocket.sendto(message,tup[0])
+
+ else:
+ attibuteResponseList = []
+
+ # get each attributes data
+ while(len(zclPayload) > 0):
+ attributeStatus = zclPayload[0]
+ attributeDirection = zclPayload[1]
+ attributeId = zclPayload[2:4]
+ zclPayload = zclPayload[4:]
+
+ newData = (attributeStatus,attributeDirection, attributeId)
+ attibuteResponseList.append(newData)
+
+ # package each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[2][0]) + (256 * ord(t[2][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ if(ord(t[0]) == 0):
+ message += "success"
+ else:
+ message += "error"
+
+ message += ", "
+
+ if(ord(t[1]) == 0):
+ message += "reported"
+ else:
+ message += "received"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+ sendSocket.sendto(message,tup[0])
+
+ # this is a zcl report attribute message
+ elif(zclCommand == '\x0a'):
+
+ # get teh zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # extract the attribute data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ zclPayload = zclPayload[2:]
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ newData = (attributeId, attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+ zclPayload = zclPayload[dataLength:]
+
+
+ # get callback clients to respond to
+ callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
+ retAddr = None
+ zigbeeHACallbackMutex.acquire()
+ if(zigbeeHACallback.has_key(callbackIndex)):
+ retAddr = zigbeeHACallback[callbackIndex]
+ zigbeeHACallbackMutex.release()
+
+ # no one to respond to so do nothing here
+ if(retAddr == None):
+ return
+
+ # construct the message
+ message = "type : zcl_report_attributes \n"
+ message += "packet_id: " + ("%0.2x" % ord(zclSeqNumber)) + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # package the attributes
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "%0.2x" % ord(t[1])
+ message += ", "
+
+ dat = ""
+ for c in (t[2]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+
+ # send to all client that want this callback
+ for ra in retAddr:
+ sendSocket.sendto(message,ra)
+
+
+# -----------------------------------------------------------------------------
+# Communication Callback/Parse Methods
+# -----------------------------------------------------------------------------
+def handleNewZigbeeMessage(parsedData, src_addr):
+ ''' Method to process a zigbee message from the local radio.
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ processZigbeeRxExplicitCommandMessage(parsedData, src_addr)
+
+
+def handleNewUdpPacket(data, addr):
+ ''' Method to parse and handle an incoming UDP packet.
+
+ data -- Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+
+
+ # data comes in as 'key: value\n key: value...' string and so needs to be
+ # parsed into a dict
+ parsedData = dict()
+
+ # 1 key, value pair per line
+ for line in data.split('\n'):
+
+ # key and values are split based on a ':'
+ fields = line.split(':')
+
+ # make sure properly formated otherwise just ignore it
+ if len(fields) == 2:
+
+ # do strips to remove any white spacing that may have resulted
+ # from improper packing on the sender side
+ parsedData[fields[0].strip()] = fields[1].strip()
+
+ # wrap in try statement just in case there is an improperly formated packet we
+ # can deal with it
+ try:
+ # dispatch to the correct process method
+ if(parsedData["type"] == "zdo_bind_request"):
+ processUdpZdoBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "zdo_unbind_request"):
+ processUdpZdoUnBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "send_address"):
+ processUdpSendAddressMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_read_attributes"):
+ processUdpZclReadAttributesMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_configure_reporting"):
+ processUdpZclConfigureReportingMessage(parsedData, addr)
+ elif(parsedData["type"] == "policy_set"):
+ processUdpPolicySet(parsedData, addr)
+ elif(parsedData["type"] == "policy_clear"):
+ processUdpPolicyClear(parsedData, addr)
+ else:
+ pass
+ except:
+ # if we ever get here then something went wrong and so just ignore this
+ # packet and try again later
+ print "I didn't expect this error:", sys.exc_info()[0]
+ traceback.print_exc()
+
+
+
+def pollMessages():
+ payload, src_addr = zigbeeConnection.recvfrom(1024)
+ handleNewZigbeeMessage(payload, src_addr)
+
+
+# -----------------------------------------------------------------------------
+# Main Running Methods
+# -----------------------------------------------------------------------------
+
+def main():
+ '''Main function used for starting the application as the main driver'''
+
+ global UDP_RECEIVE_PORT
+ global zigbeeConnection
+ global zigbeeMutex
+ global doEndFlag
+
+ parseCommandLineArgs(sys.argv[1:])
+
+
+ # create a zigbee object that handles all zigbee communication
+ # we use this to do all communication to and from the radio
+ # when data comes from the radio it will get a bit of unpacking
+ # and then a call to the callback specified will be done with the
+ # unpacked data
+ zigbeeConnection = socket(AF_ZIGBEE, SOCK_DGRAM, XBS_PROT_APS)
+
+ zigbeeConnection.bind(("", 0x0, 0, 0))
+
+ # get the long address of our local radio before we start doing anything
+ getConnectedRadioLongAddress();
+
+ # setup incoming UDP socket and bind it to self and specified UDP port
+ # sending socket does not need to be bound to anything
+ receiveSocket.bind(("", UDP_RECEIVE_PORT))
+
+ # create the thread that does short address lookups
+ addressUpdateWorkerThread = threading.Thread(target=addressUpdateWorkerMethod)
+ addressUpdateWorkerThread.start()
+ zigbeeConnection.setblocking(0)
+ receiveSocket.setblocking(0)
+
+
+ try:
+ # Main running loop
+ while(True):
+ rlist = [ receiveSocket, zigbeeConnection ]
+ wlist = []
+ xlist = []
+ rlist, wlist, xlist = select(rlist, [], [])
+ if zigbeeConnection in rlist:
+ pollMessages()
+
+ if receiveSocket in rlist:
+ data, addr = receiveSocket.recvfrom(4096)
+ handleNewUdpPacket(data, addr)
+
+ except KeyboardInterrupt:
+ # use the keyboard interupt to catch a ctrl-c and kill the application
+ pass
+
+ except:
+ # something went really wrong and so exit with error message
+ traceback.print_exc()
+
+ # signal all threads to exit
+ doEndFlag = True
+
+ # wait for threads to finish before closing of the resources
+ addressUpdateWorkerThread.join()
+
+
+ # make sure to close all the connections
+ zigbeeConnection.close()
+ receiveSocket.close()
+ sendSocket.close()
+
+if __name__ == "__main__":
+ # call main function since this is being run as the start
+ main()
--- /dev/null
+from xbee import ZigBee
+import serial
+import time
+import collections
+import sys
+import getopt
+from socket import *
+import traceback
+from threading import Thread, Lock
+import random
+import threading
+
+
+# -----------------------------------------------------------------------------
+# Constants ans Pseudo-Constants
+# -----------------------------------------------------------------------------
+UDP_RECEIVE_PORT = 5005 # port used for incoming UDP data
+UDP_RECEIVE_BUFFER_SIZE = 4096 # max buffer size of an incoming UDP packet
+SYSTEM_MASTER_ADDRESS = ("192.168.2.108", 12345) # ip address and portof the system master node computer ip addr running java
+
+# time for messages to wait for a response before the system clears away that
+# sequence identifier
+ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5
+
+ZIGBEE_SERIAL_PORT = "/dev/cu.usbserial-DN01DJIP" # USB-Serial port of local radio
+ZIGBEE_SERIAL_BAUD = 115200 # Baud rate for above port
+
+# address of our local zigbee radio
+ZIGBEE_DEVICE_ADDRESS = "xxxxxxxxxxxxxxxx"
+
+# -----------------------------------------------------------------------------
+# Global Variables and Objects
+# -----------------------------------------------------------------------------
+
+# signals to see if a request needs to be made
+didGetLocalRadioHighAddress = False;
+didGetLocalRadioLowAddress = False;
+
+# zigbee communications object and its mutex
+zigbeeConnection = None
+zigbeeConnectionMutex = Lock()
+
+# zigbee mapping from long to short object dict
+zigbeeLongShortAddr = dict()
+zigbeeLongShortAddrMutex = Lock()
+
+# zigbee mapping from a sequence number to a client
+# for correct response handling
+zigbeeSeqNumberToClient = dict()
+zigbeeSeqNumberToClientMutex = Lock()
+
+zigeeBindRequest = dict()
+zigeeBindRequestMutex = Lock()
+
+# Keeps record of where to send callbacks to when an HA message is received
+zibeeHACallback = dict()
+zibeeHACallbackMutex = Lock()
+
+
+# Keeps a record of device addresses whose short addresses have not been
+# determined yet
+zigbeeUnregisteredAddresses = []
+zigbeeUnregisteredAddressesMutex = Lock()
+
+# used to signal all threads to end
+doEndFlag = False
+
+
+# 2 sockets, one for sending (not bound to a port manually)
+# and one for receiving, known port binding by application
+# both UDP sockets
+sendSoceket = socket(AF_INET, SOCK_DGRAM)
+receiveSoceket = socket(AF_INET, SOCK_DGRAM)
+
+
+# zigbee address authority list
+zigbeeAddressAuthorityDict = dict()
+
+# -----------------------------------------------------------------------------
+# Helper Methods
+# -----------------------------------------------------------------------------
+
+
+def parseCommandLineArgs(argv):
+ global ZIGBEE_SERIAL_PORT
+ global ZIGBEE_SERIAL_BAUD
+ try:
+ opts, args = getopt.getopt(
+ argv, "hp:b:u:", ["port=", "baud=", "udpport="])
+
+ except getopt.GetoptError:
+ print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
+ sys.exit(2)
+
+ for opt, arg in opts:
+ if opt == '-h':
+ print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
+ sys.exit()
+
+ elif opt in ("-p", "--port"):
+ ZIGBEE_SERIAL_PORT = arg
+
+ elif opt in ("-b", "--baud"):
+ try:
+ ZIGBEE_SERIAL_BAUD = int(arg)
+ except ValueError:
+ print "Buad rate must be an integer"
+ sys.exit()
+
+
+# -------------
+# Convenience (Stateless)
+# -------------
+
+def hexListToChar(hexList):
+ ''' Method to convert a list/string of characters into their corresponding values
+
+ hexList -- list or string of hex characters
+ '''
+ retString = ""
+ for h in hexList:
+ retString += chr(int(h, 16))
+ return retString
+
+def splitByN(seq, n):
+ ''' Method to split a string into groups of n characters
+
+ seq -- string
+ n -- group by number
+ '''
+ return [seq[i:i+n] for i in range(0, len(seq), n)]
+
+def changeEndian(hexString):
+ ''' Method to change endian of a hex string
+
+ hexList -- string of hex characters
+ '''
+ split = splitByN(hexString, 2) # get each byte
+ split.reverse(); # reverse ordering of the bytes
+
+ # reconstruct
+ retString = ''
+ for s in split:
+ retString += s
+ return retString
+
+def printMessageData(data):
+ ''' Method to print a zigbee message to the console
+
+ data -- pre-parsed zigbee message
+ '''
+ for d in data:
+ print d, ' : ',
+ for e in data[d]:
+ print "{0:02x}".format(ord(e)),
+ if (d == 'id'):
+ print "({})".format(data[d]),
+ print
+
+def hexStringToZigbeeHexString(hexString):
+ ''' Method to change a hex string to a string of characters with the hex values
+
+ hexList -- string of hex characters
+ '''
+ return hexListToChar(splitByN(hexString, 2))
+
+def zigbeeHexStringToHexString(zigbeeHexString):
+ ''' Method to change string of characters with the hex values to a hex string
+
+ hexList -- string of characters with hex values
+ '''
+
+ retString = ''
+ for e in zigbeeHexString:
+ retString += "{0:02x}".format(ord(e))
+ return retString
+
+def zclDataTypeToBytes(zclPayload):
+ ''' Method to determine data length of a zcl attribute
+
+ zclPayload -- ZCL payload data, must have dataType as first byte
+ '''
+ attrType = ord(zclPayload[0])
+
+ if(attrType == 0x00):
+ return 0
+ elif (attrType == 0x08):
+ return 1
+ elif (attrType == 0x09):
+ return 2
+ elif (attrType == 0x0a):
+ return 3
+ elif (attrType == 0x0b):
+ return 4
+ elif (attrType == 0x0c):
+ return 5
+ elif (attrType == 0x0d):
+ return 6
+ elif (attrType == 0x0e):
+ return 7
+ elif (attrType == 0x0f):
+ return 8
+ elif (attrType == 0x10):
+ return 1
+ elif (attrType == 0x18):
+ return 1
+ elif (attrType == 0x19):
+ return 2
+ elif (attrType == 0x1a):
+ return 3
+ elif (attrType == 0x1b):
+ return 4
+ elif (attrType == 0x1c):
+ return 5
+ elif (attrType == 0x1d):
+ return 6
+ elif (attrType == 0x1e):
+ return 7
+ elif (attrType == 0x1f):
+ return 8
+ elif (attrType == 0x20):
+ return 1
+ elif (attrType == 0x21):
+ return 2
+ elif (attrType == 0x22):
+ return 3
+ elif (attrType == 0x23):
+ return 4
+ elif (attrType == 0x24):
+ return 5
+ elif (attrType == 0x25):
+ return 6
+ elif (attrType == 0x26):
+ return 7
+ elif (attrType == 0x27):
+ return 8
+ elif (attrType == 0x28):
+ return 1
+ elif (attrType == 0x29):
+ return 2
+ elif (attrType == 0x2a):
+ return 3
+ elif (attrType == 0x2b):
+ return 4
+ elif (attrType == 0x2c):
+ return 5
+ elif (attrType == 0x2d):
+ return 6
+ elif (attrType == 0x2e):
+ return 7
+ elif (attrType == 0x2f):
+ return 8
+ elif (attrType == 0x30):
+ return 1
+ elif (attrType == 0x31):
+ return 2
+ elif (attrType == 0x38):
+ return 2
+ elif (attrType == 0x39):
+ return 4
+ elif (attrType == 0x3a):
+ return 8
+ elif (attrType == 0x41):
+ return ord(zclPayload[1])
+ elif (attrType == 0x42):
+ return ord(zclPayload[1])
+ elif (attrType == 0x43):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0x44):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0xe0):
+ return 4
+ elif (attrType == 0xe1):
+ return 4
+ elif (attrType == 0xe2):
+ return 4
+ elif (attrType == 0xe8):
+ return 2
+ elif (attrType == 0xe9):
+ return 2
+ elif (attrType == 0xea):
+ return 4
+ elif (attrType == 0xf0):
+ return 8
+ elif (attrType == 0xf1):
+ return 16
+ elif (attrType == 0xff):
+ return 0
+
+# -------------
+# Other
+# -------------
+
+def createSequenceNumberForClient(addr, packetId):
+ ''' Method to get and store a sequence number with a specific client
+ for a specific message.
+
+ addr -- UDP address of the client to associate with the seq. number
+ packetId -- packet id from the UDP packet
+ '''
+ # keep trying to find a number to return
+ while(True):
+
+ # get the current time
+ epoch_time = int(time.time())
+
+ # get the current list of used numbers
+ zigbeeSeqNumberToClientMutex.acquire()
+ keysList = zigbeeSeqNumberToClient.keys()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # if all the numbers are already used
+ if(len(keysList) == 256):
+
+ # get a list of all the items
+ zigbeeSeqNumberToClientMutex.acquire()
+ itemsList = zigbeeSeqNumberToClient.items()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # search for a number that is old enough to get rid of otherwise use -1
+ seqNum = -1
+ for item in itemsList:
+ if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
+ seqNumber = item[0]
+ break
+
+ if(seqNum != -1):
+ # replace the record with new data if we found one to replace
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[seqNumber] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+
+ return seqNumber
+
+ else:
+ # not all numbers used yet so pick one randomly
+ randNum = random.randrange(0,256)
+
+ # check if we are using the number yet
+ if(randNum not in keysList):
+
+ # we are not so insert to keep track who this number is for and return it
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[randNum] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+ return randNum
+
+def getConnectedRadioLongAddress():
+ """ Method to make sure we get the MAC address of our local radio"""
+ global zigbeeConnection
+ global zigbeeMutex
+
+ # keep looping until we get both the MSBs and the LSBs
+ while ((not didGetLocalRadioHighAddress) or (not didGetLocalRadioLowAddress)):
+
+ # reissue requests
+ zigbeeConnection.send('at', command="SH")
+ zigbeeConnection.send('at', command="SL")
+
+ # sleep for a bit to give the radio time to respond before we check again
+ time.sleep(0.5)
+
+def addressUpdateWorkerMethod():
+ ''' Method to keep refreshing the short addresses of the known zigbee devices'''
+ global doEndFlag
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+ # keep looping until signaled to quit
+ while(not doEndFlag):
+
+ addrList = []
+
+ # add unregistered (short addresses unknown) devices so
+ # that we can look them up
+ zigbeeUnregisteredAddressesMutex.acquire()
+ addrList.extend(zigbeeUnregisteredAddresses)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # add the devices that we have short addresses for so we can
+ # get their most recent short addresses
+ zigbeeLongShortAddrMutex.acquire()
+ addrList.extend(zigbeeLongShortAddr.keys())
+ zigbeeLongShortAddrMutex.release()
+
+ # Loop through all the addresses and send messages for each address
+ for ad in addrList:
+
+ # create payload for a query on the network for a short address
+ payload = '\x00'
+ payload += hexStringToZigbeeHexString(changeEndian(ad))
+ payload += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ dest_addr_long=hexStringToZigbeeHexString(ad),
+ dest_addr='\xff\xfd',
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x00',
+ profile='\x00\x00',
+ data=payload
+ )
+ zigbeeConnectionMutex.release()
+
+ time.sleep(1)
+
+
+# -------------
+# UDP
+# -------------
+
+def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
+ ''' Method to send a success or fail back to a client.
+
+ addr -- UDP address to send packet to
+ packetTypeStr -- name of this specific packet
+ packetIdStr -- packet id to send
+ sucOrFail -- whether this is a success or fail message (True = success)
+ reason -- reason of failure (if needed, default is None)
+
+ '''
+
+ global sendSoceket
+
+ # construct the message
+ message = "type: " + packetTypeStr.strip() + "\n"
+ message += "packet_id: " + packetIdStr + "\n"
+
+ if(sucOrFail):
+ message += "response: success \n"
+ else:
+ message += "response: fail \n"
+ message += "reason: " + reason + "\n"
+
+ # send message in a UDP packet
+ sendSoceket.sendto(message,addr)
+
+def processUdpZdoBindReqMessage(parsedData, addr):
+
+ shortAddr = None
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # a bind request was made so must store and wait for response
+ # before we setup callbacks, so keep just the data we need to create the callback
+ zigeeBindRequestMutex.acquire()
+ zigeeBindRequest[seqNumber] = (parsedData['device_address_long'],
+ parsedData['cluster_id'],
+ parsedData['packet_id'],
+ addr)
+ zigeeBindRequestMutex.release()
+
+ # construct the short and long addresses of the message for sending
+ # make sure they are in the correct format
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+
+ # create the payload data
+ payloadData = ""
+ payloadData += chr(seqNumber)
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_address_long']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_endpoint']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['cluster_id']))
+ payloadData += '\x03'
+ payloadData += hexStringToZigbeeHexString(changeEndian(ZIGBEE_DEVICE_ADDRESS))
+ payloadData += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x21',
+ profile='\x00\x00',
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a failure packet since there is no short address available
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpZdoUnBindReqMessage(parsedData, addr):
+ zibeeHACallbackMutex.acquire();
+ if(zibeeHACallback.has_key(parsedData['device_address_long'], parsedData['cluster_id'])):
+ zibeeHACallback(parsedData['device_address_long'], parsedData['cluster_id']).remove(addr)
+ zibeeHACallbackMutex.release()
+ sendUdpSuccessFail(addr, 'zdo_unbind_request', parsedData['packet_id'], True)
+
+
+
+def processUdpSendAddressMessage(parsedData, addr):
+ ''' Method handle a send address command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global sendSoceket
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ # construct success message
+ message = "type: send_address_response\n"
+ message += "packet_id: " + parsedData['packet_id'] + "\n"
+ message += "response: success\n"
+
+ # tell client that we got their request
+ sendSoceket.sendto(message,addr)
+
+ # construct
+ zigbeeLongShortAddrMutex.acquire()
+ doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
+ zigbeeLongShortAddrMutex.release()
+
+ if(doesHaveKey):
+ # long address is already registered with the system so no need to do anything
+ return
+
+ # long address not registered so add it for short address lookup
+ zigbeeUnregisteredAddressesMutex.acquire()
+ zigbeeUnregisteredAddresses.append(parsedData['device_address_long'])
+ zigbeeUnregisteredAddressesMutex.release()
+
+def processUdpZclReadAttributesMessage(parsedData, addr):
+ ''' Method handle a ZCL read attribute command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # get all the attributes
+ attributeIds = parsedData['attribute_ids'].split(',')
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x00'
+
+ # make all the attributes payloads
+ for attr in attributeIds:
+ attr = attr.strip()
+ attr = changeEndian(attr)
+ payloadData += hexStringToZigbeeHexString(attr)
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpZclConfigureReportingMessage(parsedData, addr):
+ ''' Method handle a zcl configure reporting message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x06'
+ payloadData += '\x00'
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['attribute_id']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['data_type']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['min_reporting_interval']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['max_reporting_interval']))
+
+ if(parsedData.has_key('reportable_change')):
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
+
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpPolicySet(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Policy set: ", parsedData
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS:
+ key = (parsedData['ip_address'], int(parsedData['port']))
+ if (zigbeeAddressAuthorityDict.has_key(key)):
+ zigbeeAddressAuthorityDict[key].append(parsedData['device_address_long'])
+ else:
+ zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
+
+def processUdpPolicyClear(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Clear policy: ", parsedData
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS:
+ zigbeeAddressAuthorityDict.clear()
+
+# -------------
+# Zigbee
+# -------------
+
+def processZigbeeATCommandMessage(parsedData):
+ ''' Method to process an AT zigbee message
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ global ZIGBEE_DEVICE_ADDRESS
+ global didGetLocalRadioHighAddress
+ global didGetLocalRadioLowAddress
+
+ # command response for the high bytes of the local device long address
+ if(parsedData['command'] == 'SH'):
+ # convert the parameter to a string value (human readable)
+ value = ""
+ for e in parsedData['parameter']:
+ value += "{0:02x}".format(ord(e))
+
+ # set the correct portion of the address
+ ZIGBEE_DEVICE_ADDRESS = value + ZIGBEE_DEVICE_ADDRESS[8:]
+
+ #signal that we got this part of the address
+ didGetLocalRadioHighAddress = True
+
+ # command response for the low bytes of the local device long address
+ elif(parsedData['command'] == 'SL'):
+ # convert the parameter to a string value (human readable)
+ value = ""
+ for e in parsedData['parameter']:
+ value += "{0:02x}".format(ord(e))
+
+ # set the correct portion of the address
+ ZIGBEE_DEVICE_ADDRESS = ZIGBEE_DEVICE_ADDRESS[0:8] + value
+
+ #signal that we got this part of the address
+ didGetLocalRadioLowAddress = True
+
+def processZigbeeRxExplicitCommandMessage(parsedData):
+ ''' Method to process a rx-explicit zigbee message
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+
+ # get the long and short addresses from the message payload since we can
+ # use these to update the short addresses since this short address is fresh
+ longAddr = zigbeeHexStringToHexString(parsedData['source_addr_long'])
+ shortAddr = zigbeeHexStringToHexString( parsedData['source_addr'])
+
+ # check if this short address is for a device that has yet to be
+ # registered
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # update/ or insert the short address
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if this is a ZDO message/response
+ if(parsedData['profile'] == '\x00\x00'):
+
+ # if this is a device announcement so we can get some useful data from it
+ if(parsedData['cluster'] == '\x00\x13'):
+
+ # pick out the correct parts of the payload
+ longAddr = zigbeeHexStringToHexString(parsedData['rf_data'][3:11])
+ shortAddr = zigbeeHexStringToHexString(parsedData['rf_data'][1:3])
+
+ # change the endian of the address
+ longAddr = changeEndian(longAddr)
+ shortAddr = changeEndian(shortAddr)
+
+ # update the table with the new information
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ # check if this short address is for a device that has yet to be
+ # registered
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # if this is a response to a zdo bind_req message
+ elif(parsedData['cluster'] == '\x80\x21'):
+
+ # get the status and sequence number from the message
+ seqNumber = parsedData['rf_data'][0]
+ statusCode = parsedData['rf_data'][1]
+
+ # get the bind tuple information
+ # for this specific bind request
+ tup = None
+ zigeeBindRequestMutex.acquire()
+ if(zigeeBindRequest.has_key(ord(seqNumber))):
+ tup = zigeeBindRequest[ord(seqNumber)]
+ zigeeBindRequestMutex.release()
+
+ if(tup == None):
+ # cant really do anything in this case...
+ # don't have any information on who the data is for
+ return
+
+ # successful binding
+ if(ord(statusCode) == 0):
+
+ # add a callback for this specific device and cluster
+ # to the HA callback dict
+ zibeeHACallbackMutex.acquire();
+ if(zibeeHACallback.has_key((tup[0], tup[1]))):
+ if(tup[3] not in zibeeHACallback[(tup[0], tup[1])]):
+ zibeeHACallback[(tup[0], tup[1])].append(tup[3])
+ else:
+ zibeeHACallback[(tup[0], tup[1])] = [tup[3]]
+ zibeeHACallbackMutex.release()
+
+ # send success message
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
+
+ # Not Supported
+ elif (ord(statusCode) == 170):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
+
+ # Table Full
+ elif (ord(statusCode) == 174):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
+
+ # Other issue, dont have code for
+ else:
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
+
+ # if this is a response to a short address query
+ elif(parsedData['cluster'] == '\x80\x00'):
+
+ # get a status code
+ statusCode = parsedData['rf_data'][0]
+
+ # does not matter if this is not a success, we can try again later
+ if(statusCode != '\x00'):
+ # status code was not success so do not do anything
+ return
+
+ # get the short and long address information
+ longAddr = changeEndian(zigbeeHexStringToHexString(parsedData['rf_data'][2:10]))
+ shortAddr = changeEndian(zigbeeHexStringToHexString( parsedData['rf_data'][10:12]))
+
+ # remove device from list of unregistered devices if it is in it
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # update/insert the short address
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ # if this is a home automation zcl message/response
+ elif (parsedData['profile'] == '\x01\x04'):
+
+ # get the zcl message header
+ zclFrameControl = parsedData['rf_data'][0]
+ zclSeqNumber = parsedData['rf_data'][1]
+ zclCommand = parsedData['rf_data'][2]
+
+ # this is a zcl read attribute response
+ if(zclCommand == '\x01'):
+
+ # get the zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # get the data for each data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ attributeStatus = zclPayload[2]
+ zclPayload = zclPayload[3:]
+
+ if(ord(attributeStatus) != 0):
+ # if attribute is not supported then it has no data
+ # package the data and add it to the list
+ attibuteResponseList.append((attributeId,"not_supported"))
+ else:
+
+ # get the data type and data length of the attributre
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ # consume zcl payload data
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ # package the data and add it to the list
+ newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+
+ # consume the data size of the payload
+ zclPayload = zclPayload[dataLength:]
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # create the response message
+ packetId = tup[2]
+ message = "type : zcl_read_attributes_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # create the message for each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ if(t[1] == "success"):
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "success"
+ message += ", "
+ message += "%0.2x" % ord(t[2])
+ message += ", "
+
+ dat = ""
+ for c in (t[3]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+ else:
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "not_supported"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+
+ # send the socket
+ sendSoceket.sendto(message,tup[0])
+
+ # this is a zcl configure attribute response
+ elif(zclCommand == '\x07'):
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # get zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+
+ # construct the message
+ packetId = tup[2]
+ message = "type : zcl_configure_reporting_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ if(len(zclPayload) == 1):
+ # if all the configurations are a success then only send back a success
+ # based on zigbee specs
+ message += "all_success \n";
+ sendSoceket.sendto(message,tup[0])
+
+ else:
+ attibuteResponseList = []
+
+ # get each attributes data
+ while(len(zclPayload) > 0):
+ attributeStatus = zclPayload[0]
+ attributeDirection = zclPayload[1]
+ attributeId = zclPayload[2:4]
+ zclPayload = zclPayload[4:]
+
+ newData = (attributeStatus,attributeDirection, attributeId)
+ attibuteResponseList.append(newData)
+
+ # package each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[2][0]) + (256 * ord(t[2][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ if(ord(t[0]) == 0):
+ message += "success"
+ else:
+ message += "error"
+
+ message += ", "
+
+ if(ord(t[1]) == 0):
+ message += "reported"
+ else:
+ message += "received"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+ sendSoceket.sendto(message,tup[0])
+
+ # this is a zcl report attribute message
+ elif(zclCommand == '\x0a'):
+
+ # get teh zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # extract the attribute data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ zclPayload = zclPayload[2:]
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ newData = (attributeId, attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+ zclPayload = zclPayload[dataLength:]
+
+
+ # get callback clients to respond to
+ callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
+ retAddr = None
+ zibeeHACallbackMutex.acquire()
+ if(zibeeHACallback.has_key(callbackIndex)):
+ retAddr = zibeeHACallback[callbackIndex]
+ zibeeHACallbackMutex.release()
+
+ # no one to respond to so do nothing here
+ if(retAddr == None):
+ return
+
+ # construct the message
+ message = "type : zcl_report_attributes \n"
+ message += "packet_id: " + ("%0.2x" % ord(zclSeqNumber)) + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # package the attributes
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "%0.2x" % ord(t[1])
+ message += ", "
+
+ dat = ""
+ for c in (t[2]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+
+ # send to all client that want this callback
+ for ra in retAddr:
+ sendSoceket.sendto(message,ra)
+
+# -----------------------------------------------------------------------------
+# Communication Callback/Parse Methods
+# -----------------------------------------------------------------------------
+def handleNewZigbeeMessage(parsedData):
+ ''' Method to process a zigbee message from the local radio.
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ print "=================================================================="
+ print "New Zigbee Message"
+ # printMessageData(parsedData)
+
+ # dispatch to the correct zigbee handler
+ if (parsedData['id'] == 'at_response'):
+ processZigbeeATCommandMessage(parsedData)
+
+ elif (parsedData['id'] == 'rx_explicit'):
+ #printMessageData(parsedData)
+ processZigbeeRxExplicitCommandMessage(parsedData)
+
+ else:
+ print "Unknown API format"
+
+ print "=================================================================="
+
+def handleNewUdpPacket(data, addr):
+ ''' Method to parse and handle an incoming UDP packet.
+
+ data -- Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ print "=================================================================="
+ print "Got New UDP packet..."
+ # print data
+
+
+ # data comes in as 'key: value\n key: value...' string and so needs to be
+ # parsed into a dict
+ parsedData = dict()
+
+ # 1 key, value pair per line
+ for line in data.split('\n'):
+
+ # key and values are split based on a ':'
+ fields = line.split(':')
+
+ # make sure properly formated otherwise just ignore it
+ if len(fields) == 2:
+
+ # do strips to remove any white spacing that may have resulted
+ # from improper packing on the sender side
+ parsedData[fields[0].strip()] = fields[1].strip()
+
+ # wrap in try statement just in case there is an improperly formated packet we
+ # can deal with it
+ try:
+ # dispatch to the correct process method
+ if(parsedData["type"] == "zdo_bind_request"):
+ processUdpZdoBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "zdo_unbind_request"):
+ processUdpZdoUnBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "send_address"):
+ processUdpSendAddressMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_read_attributes"):
+ processUdpZclReadAttributesMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_configure_reporting"):
+ processUdpZclConfigureReportingMessage(parsedData, addr)
+ elif(parsedData["type"] == "policy_set"):
+ processUdpPolicySet(parsedData, addr)
+ elif(parsedData["type"] == "policy_clear"):
+ processUdpPolicyClear(parsedData, addr)
+ else:
+ #print "unknown Packet: " + parsedData["type"]
+ pass
+ except:
+ # if we ever get here then something went wrong and so just ignore this
+ # packet and try again later
+ print "I didn't expect this error:", sys.exc_info()[0]
+ traceback.print_exc()
+
+ print "=================================================================="
+
+
+# -----------------------------------------------------------------------------
+# Main Running Methods
+# -----------------------------------------------------------------------------
+
+def main():
+ '''Main function used for starting the application as the main driver'''
+
+ global ZIGBEE_SERIAL_PORT
+ global ZIGBEE_SERIAL_BAUD
+ global UDP_RECEIVE_PORT
+ global zigbeeConnection
+ global zigbeeMutex
+ global doEndFlag
+
+ parseCommandLineArgs(sys.argv[1:])
+
+ # create serial object used for communication to the zigbee radio
+ sc = serial.Serial(ZIGBEE_SERIAL_PORT, ZIGBEE_SERIAL_BAUD)
+
+ # create a zigbee object that handles all zigbee communication
+ # we use this to do all communication to and from the radio
+ # when data comes from the radio it will get a bit of unpacking
+ # and then a call to the callback specified will be done with the
+ # unpacked data
+ zigbeeConnection = ZigBee(sc, callback=handleNewZigbeeMessage)
+
+ # get the long address of our local radio before we start doing anything
+ getConnectedRadioLongAddress();
+
+ # setup incoming UDP socket and bind it to self and specified UDP port
+ # sending socket does not need to be bound to anything
+ receiveSoceket.bind(('127.0.0.1', UDP_RECEIVE_PORT))
+
+ # create the thread that does short address lookups
+ addressUpdateWorkerThread = threading.Thread(target=addressUpdateWorkerMethod)
+ addressUpdateWorkerThread.start()
+
+ try:
+ # Main running loop
+ while(True):
+ print "=================================================================="
+ print "Waiting..."
+ print "=================================================================="
+
+ # wait for an incoming UDP packet
+ # this is a blocking call
+ data, addr = receiveSoceket.recvfrom(4096)
+
+ # handle the UDP packet appropriately
+ handleNewUdpPacket(data, addr)
+
+ except KeyboardInterrupt:
+ # use the keyboard interupt to catch a ctrl-c and kill the application
+ pass
+
+ except:
+ # something went really wrong and so exit with error message
+ traceback.print_exc()
+
+ # signal all threads to exit
+ doEndFlag = True
+
+ # wait for threads to finish before closing of the resources
+ addressUpdateWorkerThread.join()
+
+
+ # make sure to close all the connections
+ zigbeeConnection.halt()
+ receiveSoceket.close()
+ sendSoceket.close()
+
+if __name__ == "__main__":
+ # call main function since this is being run as the start
+ main()
--- /dev/null
+from xbee import ZigBee
+import serial
+import time
+import collections
+import sys
+import getopt
+from socket import *
+import traceback
+from threading import Thread, Lock
+import random
+import threading
+
+
+# -----------------------------------------------------------------------------
+# Constants ans Pseudo-Constants
+# -----------------------------------------------------------------------------
+UDP_RECEIVE_PORT = 5005 # port used for incoming UDP data
+UDP_RECEIVE_BUFFER_SIZE = 4096 # max buffer size of an incoming UDP packet
+SYSTEM_MASTER_ADDRESS = ("127.0.0.1", 12345) # ip address and portof the system master node
+#SYSTEM_MASTER_ADDRESS = ("127.0.0.1", 22222) # ip address and portof the system master node
+#SYSTEM_MASTER_ADDRESS2 = ("127.0.0.1", 11111)
+#SYSTEM_MASTER_ADDRESS3 = ("127.0.0.1", 11222)
+
+# time for messages to wait for a response before the system clears away that
+# sequence identifier
+ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC = 5
+
+#ZIGBEE_SERIAL_PORT = "/dev/cu.usbserial-DN01DCRH" # USB-Serial port of local radio
+ZIGBEE_SERIAL_PORT = "/dev/ttyUSB0"
+ZIGBEE_SERIAL_BAUD = 115200 # Baud rate for above port
+
+# address of our local zigbee radio
+ZIGBEE_DEVICE_ADDRESS = "0013a20040d99cb4"
+
+# -----------------------------------------------------------------------------
+# Global Variables and Objects
+# -----------------------------------------------------------------------------
+
+# signals to see if a request needs to be made
+didGetLocalRadioHighAddress = False;
+didGetLocalRadioLowAddress = False;
+
+# zigbee communications object and its mutex
+zigbeeConnection = None
+zigbeeConnectionMutex = Lock()
+
+#singleton mabe by changwoo
+matchDescriptorReqSingleton = True
+deviceAnnouncementSingleton = True
+ManagementPermitJoiningReqSuccess = False
+
+# zigbee mapping from long to short object dict
+zigbeeLongShortAddr = dict()
+zigbeeLongShortAddrMutex = Lock()
+
+# zigbee mapping from a sequence number to a client
+# for correct response handling
+zigbeeSeqNumberToClient = dict()
+zigbeeSeqNumberToClientMutex = Lock()
+
+zigeeBindRequest = dict()
+zigeeBindRequestMutex = Lock()
+
+# Keeps record of where to send callbacks to when an HA message is received
+zibeeHACallback = dict()
+zibeeHACallbackMutex = Lock()
+
+
+# Keeps a record of device addresses whose short addresses have not been
+# determined yet
+zigbeeUnregisteredAddresses = []
+zigbeeUnregisteredAddressesMutex = Lock()
+
+# used to signal all threads to end
+doEndFlag = False
+
+
+# 2 sockets, one for sending (not bound to a port manually)
+# and one for receiving, known port binding by application
+# both UDP sockets
+sendSoceket = socket(AF_INET, SOCK_DGRAM)
+receiveSoceket = socket(AF_INET, SOCK_DGRAM)
+
+
+# zigbee address authority list
+zigbeeAddressAuthorityDict = dict()
+
+# made by changwoo
+seqNumberForNotification = dict()
+
+# -----------------------------------------------------------------------------
+# Helper Methods
+# -----------------------------------------------------------------------------
+def reverseShortAddress(shortAddr):
+ result = shortAddr[len(shortAddr)/2:]+shortAddr[0:len(shortAddr)/2]
+ return result
+
+def parseCommandLineArgs(argv):
+ global ZIGBEE_SERIAL_PORT
+ global ZIGBEE_SERIAL_BAUD
+ try:
+ opts, args = getopt.getopt(
+ argv, "hp:b:u:", ["port=", "baud=", "udpport="])
+
+ except getopt.GetoptError:
+ print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
+ sys.exit(2)
+
+ for opt, arg in opts:
+ if opt == '-h':
+ print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
+ sys.exit()
+
+ elif opt in ("-p", "--port"):
+ ZIGBEE_SERIAL_PORT = arg
+
+ elif opt in ("-b", "--baud"):
+ try:
+ ZIGBEE_SERIAL_BAUD = int(arg)
+ except ValueError:
+ print "Buad rate must be an integer"
+ sys.exit()
+
+
+# -------------
+# Convenience (Stateless)
+# -------------
+
+def hexListToChar(hexList):
+ ''' Method to convert a list/string of characters into their corresponding values
+
+ hexList -- list or string of hex characters
+ '''
+ retString = ""
+ for h in hexList:
+ retString += chr(int(h, 16))
+ return retString
+
+def splitByN(seq, n):
+ ''' Method to split a string into groups of n characters
+
+ seq -- string
+ n -- group by number
+ '''
+ return [seq[i:i+n] for i in range(0, len(seq), n)]
+
+def changeEndian(hexString):
+ ''' Method to change endian of a hex string
+
+ hexList -- string of hex characters
+ '''
+ split = splitByN(hexString, 2) # get each byte
+ split.reverse(); # reverse ordering of the bytes
+
+ # reconstruct
+ retString = ''
+ for s in split:
+ retString += s
+ return retString
+
+def printMessageData(data):
+ ''' Method to print a zigbee message to the console
+
+ data -- pre-parsed zigbee message
+ '''
+ for d in data:
+ print d, ' : ',
+ for e in data[d]:
+ print "{0:02x}".format(ord(e)),
+ if (d == 'id'):
+ print "({})".format(data[d]),
+ print
+
+def hexStringToZigbeeHexString(hexString):
+ ''' Method to change a hex string to a string of characters with the hex values
+
+ hexList -- string of hex characters
+ '''
+ return hexListToChar(splitByN(hexString, 2))
+
+def zigbeeHexStringToHexString(zigbeeHexString):
+ ''' Method to change string of characters with the hex values to a hex string
+
+ hexList -- string of characters with hex values
+ '''
+
+ retString = ''
+ for e in zigbeeHexString:
+ retString += "{0:02x}".format(ord(e))
+ return retString
+
+def zclDataTypeToBytes(zclPayload):
+ ''' Method to determine data length of a zcl attribute
+
+ zclPayload -- ZCL payload data, must have dataType as first byte
+ '''
+ attrType = ord(zclPayload[0])
+
+ if(attrType == 0x00):
+ return 0
+ elif (attrType == 0x08):
+ return 1
+ elif (attrType == 0x09):
+ return 2
+ elif (attrType == 0x0a):
+ return 3
+ elif (attrType == 0x0b):
+ return 4
+ elif (attrType == 0x0c):
+ return 5
+ elif (attrType == 0x0d):
+ return 6
+ elif (attrType == 0x0e):
+ return 7
+ elif (attrType == 0x0f):
+ return 8
+ elif (attrType == 0x10):
+ return 1
+ elif (attrType == 0x18):
+ return 1
+ elif (attrType == 0x19):
+ return 2
+ elif (attrType == 0x1a):
+ return 3
+ elif (attrType == 0x1b):
+ return 4
+ elif (attrType == 0x1c):
+ return 5
+ elif (attrType == 0x1d):
+ return 6
+ elif (attrType == 0x1e):
+ return 7
+ elif (attrType == 0x1f):
+ return 8
+ elif (attrType == 0x20):
+ return 1
+ elif (attrType == 0x21):
+ return 2
+ elif (attrType == 0x22):
+ return 3
+ elif (attrType == 0x23):
+ return 4
+ elif (attrType == 0x24):
+ return 5
+ elif (attrType == 0x25):
+ return 6
+ elif (attrType == 0x26):
+ return 7
+ elif (attrType == 0x27):
+ return 8
+ elif (attrType == 0x28):
+ return 1
+ elif (attrType == 0x29):
+ return 2
+ elif (attrType == 0x2a):
+ return 3
+ elif (attrType == 0x2b):
+ return 4
+ elif (attrType == 0x2c):
+ return 5
+ elif (attrType == 0x2d):
+ return 6
+ elif (attrType == 0x2e):
+ return 7
+ elif (attrType == 0x2f):
+ return 8
+ elif (attrType == 0x30):
+ return 1
+ elif (attrType == 0x31):
+ return 2
+ elif (attrType == 0x38):
+ return 2
+ elif (attrType == 0x39):
+ return 4
+ elif (attrType == 0x3a):
+ return 8
+ elif (attrType == 0x41):
+ return ord(zclPayload[1])
+ elif (attrType == 0x42):
+ return ord(zclPayload[1])
+ elif (attrType == 0x43):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0x44):
+ return ord(zclPayload[1]) + (256 * ord(zclPayload[2]))
+ elif (attrType == 0xe0):
+ return 4
+ elif (attrType == 0xe1):
+ return 4
+ elif (attrType == 0xe2):
+ return 4
+ elif (attrType == 0xe8):
+ return 2
+ elif (attrType == 0xe9):
+ return 2
+ elif (attrType == 0xea):
+ return 4
+ elif (attrType == 0xf0):
+ return 8
+ elif (attrType == 0xf1):
+ return 16
+ elif (attrType == 0xff):
+ return 0
+
+# -------------
+# Other
+# -------------
+
+def createSequenceNumberForClient(addr, packetId):
+ ''' Method to get and store a sequence number with a specific client
+ for a specific message.
+
+ addr -- UDP address of the client to associate with the seq. number
+ packetId -- packet id from the UDP packet
+ '''
+ # keep trying to find a number to return
+ while(True):
+
+ # get the current time
+ epoch_time = int(time.time())
+
+ # get the current list of used numbers
+ zigbeeSeqNumberToClientMutex.acquire()
+ keysList = zigbeeSeqNumberToClient.keys()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # if all the numbers are already used
+ if(len(keysList) == 256):
+
+ # get a list of all the items
+ zigbeeSeqNumberToClientMutex.acquire()
+ itemsList = zigbeeSeqNumberToClient.items()
+ zigbeeSeqNumberToClientMutex.release()
+
+ # search for a number that is old enough to get rid of otherwise use -1
+ seqNum = -1
+ for item in itemsList:
+ if((epoch_time - item[1][1]) > ZIGBEE_SEQUENCE_NUMBER_CLEAR_TIME_SEC):
+ seqNumber = item[0]
+ break
+
+ if(seqNum != -1):
+ # replace the record with new data if we found one to replace
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[seqNumber] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+
+ return seqNumber
+
+ else:
+ # not all numbers used yet so pick one randomly
+ randNum = random.randrange(0,256)
+
+ # check if we are using the number yet
+ if(randNum not in keysList):
+
+ # we are not so insert to keep track who this number is for and return it
+ zigbeeSeqNumberToClientMutex.acquire()
+ zigbeeSeqNumberToClient[randNum] = (addr, epoch_time, packetId)
+ zigbeeSeqNumberToClientMutex.release()
+ return randNum
+
+def getConnectedRadioLongAddress():
+ """ Method to make sure we get the MAC address of our local radio"""
+ global zigbeeConnection
+ global zigbeeMutex
+
+ # keep looping until we get both the MSBs and the LSBs
+ while ((not didGetLocalRadioHighAddress) or (not didGetLocalRadioLowAddress)):
+
+ # reissue requests
+ zigbeeConnection.send('at', command="SH")
+ zigbeeConnection.send('at', command="SL")
+
+ # sleep for a bit to give the radio time to respond before we check again
+ time.sleep(2)
+
+def addressUpdateWorkerMethod():
+ ''' Method to keep refreshing the short addresses of the known zigbee devices'''
+ global doEndFlag
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+ # keep looping until signaled to quit
+ while(not doEndFlag):
+
+ addrList = []
+
+ # add unregistered (short addresses unknown) devices so
+ # that we can look them up
+ zigbeeUnregisteredAddressesMutex.acquire()
+ addrList.extend(zigbeeUnregisteredAddresses)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # add the devices that we have short addresses for so we can
+ # get their most recent short addresses
+ zigbeeLongShortAddrMutex.acquire()
+ addrList.extend(zigbeeLongShortAddr.keys())
+ zigbeeLongShortAddrMutex.release()
+
+ # Loop through all the addresses and send messages for each address
+ for ad in addrList:
+
+ # create payload for a query on the network for a short address
+ payload = '\x00'
+ payload += hexStringToZigbeeHexString(changeEndian(ad))
+ payload += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ dest_addr_long=hexStringToZigbeeHexString(ad),
+ dest_addr='\xff\xfd',
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x00',
+ profile='\x00\x00',
+ data=payload
+ )
+ zigbeeConnectionMutex.release()
+
+ time.sleep(8)
+
+
+# -------------
+# UDP
+# -------------
+
+def sendUdpSuccessFail(addr, packetTypeStr, packetIdStr, sucOrFail, reason=None):
+ ''' Method to send a success or fail back to a client.
+
+ addr -- UDP address to send packet to
+ packetTypeStr -- name of this specific packet
+ packetIdStr -- packet id to send
+ sucOrFail -- whether this is a success or fail message (True = success)
+ reason -- reason of failure (if needed, default is None)
+
+ '''
+
+ global sendSoceket
+
+ # construct the message
+ message = "type: " + packetTypeStr.strip() + "\n"
+ message += "packet_id: " + packetIdStr + "\n"
+
+ if(sucOrFail):
+ message += "response: success \n"
+ else:
+ message += "response: fail \n"
+ message += "reason: " + reason + "\n"
+
+ # send message in a UDP packet
+ sendSoceket.sendto(message,addr)
+
+def processUdpZdoBindReqMessage(parsedData, addr):
+
+ shortAddr = None
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # a bind request was made so must store and wait for response
+ # before we setup callbacks, so keep just the data we need to create the callback
+ zigeeBindRequestMutex.acquire()
+ zigeeBindRequest[seqNumber] = (parsedData['device_address_long'],
+ parsedData['cluster_id'],
+ parsedData['packet_id'],
+ addr)
+ zigeeBindRequestMutex.release()
+
+ # construct the short and long addresses of the message for sending
+ # make sure they are in the correct format
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+
+ # create the payload data
+ payloadData = ""
+ payloadData += chr(seqNumber)
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_address_long']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['device_endpoint']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['cluster_id']))
+ payloadData += '\x03'
+ payloadData += hexStringToZigbeeHexString(changeEndian(ZIGBEE_DEVICE_ADDRESS))
+ payloadData += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x21',
+ profile='\x00\x00',
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a failure packet since there is no short address available
+ sendUdpSuccessFail(addr, 'zdo_bind_request', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpZdoUnBindReqMessage(parsedData, addr):
+ zibeeHACallbackMutex.acquire();
+ if(zibeeHACallback.has_key(parsedData['device_address_long'], parsedData['cluster_id'])):
+ zibeeHACallback(parsedData['device_address_long'], parsedData['cluster_id']).remove(addr)
+ zibeeHACallbackMutex.release()
+ sendUdpSuccessFail(addr, 'zdo_unbind_request', parsedData['packet_id'], True)
+
+
+
+def processUdpSendAddressMessage(parsedData, addr):
+ ''' Method handle a send address command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigbeeUnregisteredAddresses
+ global zigbeeUnregisteredAddressesMutex
+ global sendSoceket
+
+ print "process send address"
+
+
+ # construct success message
+ message = "type: send_address_response\n"
+ message += "packet_id: " + parsedData['packet_id'] + "\n"
+ message += "response: success\n"
+
+ # tell client that we got their request
+ sendSoceket.sendto(message,addr)
+ print "responding", message
+
+ # construct
+ zigbeeLongShortAddrMutex.acquire()
+ doesHaveKey = zigbeeLongShortAddr.has_key(parsedData['device_address_long'])
+ zigbeeLongShortAddrMutex.release()
+
+ if(doesHaveKey):
+ # long address is already registered with the system so no need to do anything
+ return
+
+ # long address not registered so add it for short address lookup
+ zigbeeUnregisteredAddressesMutex.acquire()
+ zigbeeUnregisteredAddresses.append(parsedData['device_address_long'])
+ zigbeeUnregisteredAddressesMutex.release()
+
+
+
+#made by changwoo
+def processUdpEnrollmentResponse(parsedData, addr):
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zcl_enrollment_response', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x01'
+ payloadData += chr(seqNumber)
+ payloadData += '\x00'
+ payloadData += '\x00\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x40',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x01',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+ print '> EnrollmentResponse is sent'
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_enrollment_response', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+
+
+
+#made by changwoo
+def processUdpZclWriteAttributesMessage(parsedData, addr):
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zcl_write_attributes', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x02'
+ payloadData += '\x10\x00'
+ payloadData += '\xF0'
+# payloadData += '\xDA\x9A\xD9\x40\x00\xA2\x13\x00'
+ payloadData += hexStringToZigbeeHexString(changeEndian(ZIGBEE_DEVICE_ADDRESS))
+
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x08',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x01',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+
+ print ''
+ print '> WriteAttributesReq is sent : '+str(shortAddr)
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_write_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+#made by changwoo
+def processUdpZclChangeSwitchReqMessage(parsedData, addr):
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'change_switch_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ value = hexStringToZigbeeHexString(parsedData['value'])
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x40',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x01',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data='\x01'+chr(seqNumber)+value
+ )
+ time.sleep(1)
+ if parsedData['value']==1:
+ print '> The outlet sensor turned on'
+ else :
+ print '> The outlet sensor turned off'
+
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+
+
+# made by changwoo
+def processUdpBroadcastingRouteRecordReqMessage(parsedData, addr):
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'broadcast_route_record_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long='\x00\x00\x00\x00\x00\x00\xff\xff',
+ dest_addr='\xff\xfe',
+ src_endpoint='\x00',
+ dest_endpoint=dstEndpoint,
+ cluster='\x00\x32',
+ profile='\x00\x00',
+ data='\x12'+'\x01'
+ )
+ time.sleep(1)
+ print '> BroadcastingRouteRecordReq is sent'
+
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+
+#made by changwoo
+def processUdpManagementPermitJoiningReqMessage(parsedData, addr):
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ global matchDescriptorReqSingleton
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'management_permit_joining_request', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+
+ # create the payload data
+ payloadData = ""
+ payloadData += chr(seqNumber)
+ payloadData += '\x5a'
+ payloadData += '\x00'
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster=clusterId,
+ profile='\x00\x00',
+ data=payloadData
+ )
+ print '> ManagementPermitJoiningReq is sent'
+
+ #stop answering 0x6
+ matchDescriptorReqSingleton= False
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'management_permit_joining_request', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+
+def processUdpZclReadAttributesMessage(parsedData, addr):
+ ''' Method handle a ZCL read attribute command
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+
+ # send an error message, could not get a sequence number to use at this time
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ # get the info for sending
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # get all the attributes
+ attributeIds = parsedData['attribute_ids'].split(',')
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x00'
+
+ # make all the attributes payloads
+ for attr in attributeIds:
+ attr = attr.strip()
+ attr = changeEndian(attr)
+ payloadData += hexStringToZigbeeHexString(attr)
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ # send a fail response
+ sendUdpSuccessFail(addr, 'zcl_read_attributes', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpZclConfigureReportingMessage(parsedData, addr):
+ ''' Method handle a zcl configure reporting message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+
+ global zigbeeLongShortAddr
+ global zigbeeLongShortAddrMutex
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+
+ if(zigbeeAddressAuthorityDict.has_key(addr)):
+ l = zigbeeAddressAuthorityDict[addr]
+ if(parsedData['device_address_long'] not in l):
+ return
+ else:
+ return
+
+
+ shortAddr = None
+
+ # get the short address for this device long address if possible
+ zigbeeLongShortAddrMutex.acquire()
+ if(zigbeeLongShortAddr.has_key(parsedData['device_address_long'])):
+ shortAddr = zigbeeLongShortAddr[parsedData['device_address_long']]
+ zigbeeLongShortAddrMutex.release()
+
+ # if there is a short address than we can send the message
+ # if there is not one then we cannot since we need both the short and
+ # the long address
+ if(shortAddr != None):
+
+ # get a request number
+ seqNumber = createSequenceNumberForClient(addr, parsedData['packet_id'])
+
+ # send back failure
+ if(seqNumber == -1):
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'out_of_space')
+ return
+
+ destLongAddr = hexStringToZigbeeHexString(parsedData['device_address_long'])
+ destShortAddr = hexStringToZigbeeHexString(shortAddr)
+ profileId = hexStringToZigbeeHexString(parsedData['profile_id'])
+ clusterId = hexStringToZigbeeHexString(parsedData['cluster_id'])
+ dstEndpoint = hexStringToZigbeeHexString(parsedData['device_endpoint'])
+
+ # create the payload data
+ payloadData = ""
+ payloadData += '\x00'
+ payloadData += chr(seqNumber)
+ payloadData += '\x06'
+ payloadData += '\x00'
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['attribute_id']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['data_type']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['min_reporting_interval']))
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['max_reporting_interval']))
+
+ if(parsedData.has_key('reportable_change')):
+ payloadData += hexStringToZigbeeHexString(changeEndian(parsedData['reportable_change']))
+
+
+ # create and send binding command
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=destLongAddr,
+ dest_addr=destShortAddr,
+ src_endpoint='\x00',
+ dest_endpoint=dstEndpoint,
+ cluster=clusterId,
+ profile=profileId,
+ data=payloadData
+ )
+ zigbeeConnectionMutex.release()
+
+
+ else:
+ sendUdpSuccessFail(addr, 'zcl_configure_reporting', parsedData['packet_id'], False, 'short_address_unknown')
+ pass
+
+def processUdpPolicySet(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Policy set: ", parsedData
+ print 'addr : ', addr
+
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS : #or addr == SYSTEM_MASTER_ADDRESS2 or addr == SYSTEM_MASTER_ADDRESS3 :
+ key = (parsedData['ip_address'], int(parsedData['port']))
+ if (zigbeeAddressAuthorityDict.has_key(key)):
+ zigbeeAddressAuthorityDict[key].append(parsedData['device_address_long'])
+ else:
+ zigbeeAddressAuthorityDict[key] = [parsedData['device_address_long']]
+
+
+def processUdpPolicyClear(parsedData, addr):
+ ''' Method handle a policy set message
+
+ parsedData -- Pre-parsed Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ print "=================================================================="
+ print "Clear policy: ", parsedData
+
+ # do nothing if wrong source
+ if addr == SYSTEM_MASTER_ADDRESS : #or addr == SYSTEM_MASTER_ADDRESS2 or addr == SYSTEM_MASTER_ADDRESS3:
+ zigbeeAddressAuthorityDict.clear()
+
+
+# -------------
+# Zigbee
+# -------------
+
+def processZigbeeATCommandMessage(parsedData):
+ ''' Method to process an AT zigbee message
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ global ZIGBEE_DEVICE_ADDRESS
+ global didGetLocalRadioHighAddress
+ global didGetLocalRadioLowAddress
+
+ # command response for the high bytes of the local device long address
+ if(parsedData['command'] == 'SH'):
+ # convert the parameter to a string value (human readable)
+ value = ""
+ for e in parsedData['parameter']:
+ value += "{0:02x}".format(ord(e))
+
+ # set the correct portion of the address
+ ZIGBEE_DEVICE_ADDRESS = value + ZIGBEE_DEVICE_ADDRESS[8:]
+
+ #signal that we got this part of the address
+ didGetLocalRadioHighAddress = True
+
+ # command response for the low bytes of the local device long address
+ elif(parsedData['command'] == 'SL'):
+ # convert the parameter to a string value (human readable)
+ value = ""
+ for e in parsedData['parameter']:
+ value += "{0:02x}".format(ord(e))
+
+ # set the correct portion of the address
+ ZIGBEE_DEVICE_ADDRESS = ZIGBEE_DEVICE_ADDRESS[0:8] + value
+
+ #signal that we got this part of the address
+ didGetLocalRadioLowAddress = True
+
+def processZigbeeRxExplicitCommandMessage(parsedData):
+ ''' Method to process a rx-explicit zigbee message
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ global zigeeBindRequestMutex
+ global zigeeBindRequest
+ global zigbeeConnectionMutex
+ global zigbeeConnection
+ global ManagementPermitJoiningReqSuccess
+
+ # get the long and short addresses from the message payload since we can
+ # use these to update the short addresses since this short address is fresh
+ longAddr = zigbeeHexStringToHexString(parsedData['source_addr_long'])
+ shortAddr = zigbeeHexStringToHexString(parsedData['source_addr'])
+
+ # check if this short address is for a device that has yet to be
+ # registered
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # update/ or insert the short address
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ global matchDescriptorReqSingleton
+ global deviceAnnouncementSingleton
+ global seqNumberForNotification
+
+ # if this is a ZDO message/response
+ if(parsedData['profile'] == '\x00\x00'):
+
+ # mabe by changwoo
+ # if this is a Match Descriptor Request so we need to answer.
+ if(parsedData['cluster'] == '\x00\x06' and matchDescriptorReqSingleton):
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x08',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=parsedData['source_addr_long'],
+ dest_addr=parsedData['source_addr'],
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x06',
+ profile='\x00\x00',
+ data=parsedData['rf_data']
+ )
+ time.sleep(1)
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x40',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=parsedData['source_addr_long'],
+ dest_addr=parsedData['source_addr'],
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x80\x06',
+ profile='\x00\x00',
+ data=parsedData['rf_data'][0]+ '\x00\x00\x00' + '\x01\x01'
+ )
+ time.sleep(1)
+ print ''
+ print '[ 0x0006 ] Match Descriptor Request - answered'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+ zigbeeConnectionMutex.release()
+
+
+ # if this is a device announcement so we can get some useful data from it
+ elif(parsedData['cluster'] == '\x00\x13' and deviceAnnouncementSingleton):
+
+ # pick out the correct parts of the payload
+ longAddr = zigbeeHexStringToHexString(parsedData['rf_data'][3:11])
+ shortAddr = zigbeeHexStringToHexString(parsedData['rf_data'][1:3])
+
+ # change the endian of the address
+ longAddr = changeEndian(longAddr)
+ shortAddr = changeEndian(shortAddr)
+
+ # update the table with the new information
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ # check if this short address is for a device that has yet to be
+ # registered
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+
+ # made by changwoo
+ zigbeeConnectionMutex.acquire()
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x08',
+ # frame_id=chr(seqNumber),
+ dest_addr_long=parsedData['source_addr_long'],
+ dest_addr=parsedData['source_addr'],
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x13',
+ profile='\x00\x00',
+ data=parsedData['rf_data']
+ )
+ print ''
+ print '[ 0x0013 ] device announcement - answered'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+ deviceAnnouncementSingleton = False
+ zigbeeConnectionMutex.release()
+
+
+ # if this is a response to a zdo bind_req message
+ elif(parsedData['cluster'] == '\x80\x21'):
+
+ # get the status and sequence number from the message
+ seqNumber = parsedData['rf_data'][0]
+ statusCode = parsedData['rf_data'][1]
+
+ # get the bind tuple information
+ # for this specific bind request
+ tup = None
+ zigeeBindRequestMutex.acquire()
+ if(zigeeBindRequest.has_key(ord(seqNumber))):
+ tup = zigeeBindRequest[ord(seqNumber)]
+ zigeeBindRequestMutex.release()
+
+ if(tup == None):
+ # cant really do anything in this case...
+ # don't have any information on who the data is for
+ return
+
+ # successful binding
+ if(ord(statusCode) == 0):
+
+ # add a callback for this specific device and cluster
+ # to the HA callback dict
+ zibeeHACallbackMutex.acquire();
+ if(zibeeHACallback.has_key((tup[0], tup[1]))):
+ if(tup[3] not in zibeeHACallback[(tup[0], tup[1])]):
+ zibeeHACallback[(tup[0], tup[1])].append(tup[3])
+ else:
+ zibeeHACallback[(tup[0], tup[1])] = [tup[3]]
+ zibeeHACallbackMutex.release()
+
+ # send success message
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], True)
+
+ # Not Supported
+ elif (ord(statusCode) == 170):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'not_supported')
+
+ # Table Full
+ elif (ord(statusCode) == 174):
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'table_full')
+
+ # Other issue, dont have code for
+ else:
+ sendUdpSuccessFail(tup[3], 'zdo_bind_request', tup[2], False, 'other')
+
+ # if this is a response to a short address query
+ elif(parsedData['cluster'] == '\x80\x00'):
+
+ # get a status code
+ statusCode = parsedData['rf_data'][0]
+
+ # does not matter if this is not a success, we can try again later
+ if(statusCode != '\x00'):
+ # status code was not success so do not do anything
+ return
+
+ # get the short and long address information
+ longAddr = changeEndian(zigbeeHexStringToHexString(parsedData['rf_data'][2:10]))
+ shortAddr = changeEndian(zigbeeHexStringToHexString( parsedData['rf_data'][10:12]))
+
+ # remove device from list of unregistered devices if it is in it
+ zigbeeUnregisteredAddressesMutex.acquire()
+ if(longAddr in zigbeeUnregisteredAddresses):
+ zigbeeUnregisteredAddresses.remove(longAddr)
+ zigbeeUnregisteredAddressesMutex.release()
+
+ # update/insert the short address
+ zigbeeLongShortAddrMutex.acquire()
+ zigbeeLongShortAddr[longAddr] = shortAddr
+ zigbeeLongShortAddrMutex.release()
+
+ #made by changwoo
+ elif(parsedData['cluster'] == '\x80\x06'):
+ print ''
+ print '[ 0x8006 ] get Match Descriptor Response'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+
+ #made by changwoo
+ elif(parsedData['cluster'] == '\x80\x36'):
+ print ''
+ print '[ 0x8036 ] get Management Permit Joining Response'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+
+ ManagementPermitJoiningReqSuccess = True
+
+ #made by changwoo
+ else :
+ print ''
+ print '[ '+zigbeeHexStringToHexString(parsedData['cluster'])+' ] ...'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+
+
+ # if this is a home automation zcl message/response
+ elif (parsedData['profile'] == '\x01\x04'):
+
+ # get the zcl message header
+ zclFrameControl = parsedData['rf_data'][0]
+ zclSeqNumber = parsedData['rf_data'][1]
+ zclCommand = parsedData['rf_data'][2]
+ zclStatus = parsedData['rf_data'][3]
+
+ #made by changwoo
+ if(zclCommand == '\x00'):
+ print ''
+ print '> ('+zigbeeHexStringToHexString(zclStatus)+') notification! : '+ zigbeeHexStringToHexString( parsedData['rf_data'] )
+
+ # find who to send response
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+
+ if(longAddr in seqNumberForNotification):
+ key = longAddr
+ if(zigbeeSeqNumberToClient.has_key(seqNumberForNotification[key])):
+ tup = zigbeeSeqNumberToClient[seqNumberForNotification[key]]
+ #del zigbeeSeqNumberToClient[seqNumberForNotification] # don't delete.
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+ # create the response message
+ packetId = tup[2]
+ message = "type : zcl_zone_status_change_notification\n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "status: " + zigbeeHexStringToHexString(zclStatus) + "\n"
+ message += "attributes: success"
+ message += "\n"
+ # send the socket
+ sendSoceket.sendto(message,tup[0])
+ print(">port : ", tup[0][1])
+
+
+
+ # this is a zcl read attribute response
+ elif(zclCommand == '\x01'):
+
+ # get the zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # get the data for each data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ attributeStatus = zclPayload[2]
+ zclPayload = zclPayload[3:]
+
+ if(ord(attributeStatus) != 0):
+ # if attribute is not supported then it has no data
+ # package the data and add it to the list
+ attibuteResponseList.append((attributeId,"not_supported"))
+ else:
+
+ # get the data type and data length of the attributre
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ # consume zcl payload data
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ # package the data and add it to the list
+ newData = (attributeId,"success", attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+
+ # consume the data size of the payload
+ zclPayload = zclPayload[dataLength:]
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # create the response message
+ packetId = tup[2]
+ message = "type : zcl_read_attributes_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # create the message for each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ if(t[1] == "success"):
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "success"
+ message += ", "
+ message += "%0.2x" % ord(t[2])
+ message += ", "
+
+ dat = ""
+ for c in (t[3]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+ else:
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "not_supported"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+ # send the socket
+ sendSoceket.sendto(message,tup[0])
+
+
+
+
+ # made by changwoo
+ # this is a zcl write attribute response
+ elif(zclCommand == '\x04'):
+
+ # get the zcl payload
+ zclPayload = parsedData['rf_data'][3]
+ # the response is '70' which means already resister the mac address or 'success', then let JAVA knows it
+ if(zclStatus == '\x70' or zclPayload == '\x00'):
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ seqNumberForNotification[longAddr] = ord(zclSeqNumber)
+ #del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # create the response message
+ packetId = tup[2]
+ message = "type : zcl_write_attributes_response\n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: success"
+ message += "\n"
+ # send the socket
+ sendSoceket.sendto(message,tup[0])
+ print ''
+ print '[ 0x0500 ] get Write Attribute Response success'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+
+ else:
+ print ''
+ print '[ 0x0500 ] get Write Attribute Response'
+ print '> rfdata : '+zigbeeHexStringToHexString(parsedData['rf_data'])
+
+
+
+ # this is a zcl configure attribute response
+ elif(zclCommand == '\x07'):
+
+ # find who to send response to
+ tup = None
+ zigbeeSeqNumberToClientMutex.acquire()
+ if(zigbeeSeqNumberToClient.has_key(ord(zclSeqNumber))):
+ tup = zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ del zigbeeSeqNumberToClient[ord(zclSeqNumber)]
+ zigbeeSeqNumberToClientMutex.release()
+
+ # no one to send the response to so just move on
+ if(tup == None):
+ # cant really do anything here
+ return
+
+ # get zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+
+ # construct the message
+ packetId = tup[2]
+ message = "type : zcl_configure_reporting_response \n"
+ message += "packet_id: " + packetId + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ if(len(zclPayload) == 1):
+ # if all the configurations are a success then only send back a success
+ # based on zigbee specs
+ message += "all_success \n";
+ sendSoceket.sendto(message,tup[0])
+
+ else:
+ attibuteResponseList = []
+
+ # get each attributes data
+ while(len(zclPayload) > 0):
+ attributeStatus = zclPayload[0]
+ attributeDirection = zclPayload[1]
+ attributeId = zclPayload[2:4]
+ zclPayload = zclPayload[4:]
+
+ newData = (attributeStatus,attributeDirection, attributeId)
+ attibuteResponseList.append(newData)
+
+ # package each attribute
+ for t in attibuteResponseList:
+ attrId = ord(t[2][0]) + (256 * ord(t[2][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ if(ord(t[0]) == 0):
+ message += "success"
+ else:
+ message += "error"
+
+ message += ", "
+
+ if(ord(t[1]) == 0):
+ message += "reported"
+ else:
+ message += "received"
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+ sendSoceket.sendto(message,tup[0])
+
+ # this is a zcl report attribute message
+ elif(zclCommand == '\x0a'):
+ print "get Report attribute "
+ # get teh zcl payload
+ zclPayload = parsedData['rf_data'][3:]
+ attibuteResponseList = []
+
+ # extract the attribute data
+ while(len(zclPayload) > 0):
+ attributeId = zclPayload[0:2]
+ zclPayload = zclPayload[2:]
+ attributeType = zclPayload[0]
+ dataLength = zclDataTypeToBytes(zclPayload)
+
+ if ((ord(attributeType) == 0x41) or (ord(attributeType) == 0x42)):
+ zclPayload = zclPayload[2:]
+ elif ((ord(attributeType) == 0x43) or (ord(attributeType) == 0x44)):
+ zclPayload = zclPayload[3:]
+ else:
+ zclPayload = zclPayload[1:]
+
+ newData = (attributeId, attributeType ,zclPayload[0:dataLength])
+ attibuteResponseList.append(newData)
+ zclPayload = zclPayload[dataLength:]
+
+
+ # get callback clients to respond to
+ callbackIndex = (zigbeeHexStringToHexString(parsedData['source_addr_long']), zigbeeHexStringToHexString(parsedData['cluster']))
+ retAddr = None
+ zibeeHACallbackMutex.acquire()
+ if(zibeeHACallback.has_key(callbackIndex)):
+ retAddr = zibeeHACallback[callbackIndex]
+ zibeeHACallbackMutex.release()
+
+ # no one to respond to so do nothing here
+ if(retAddr == None):
+ return
+
+ # construct the message
+ message = "type : zcl_report_attributes \n"
+ message += "packet_id: " + ("%0.2x" % ord(zclSeqNumber)) + "\n"
+ message += "cluster_id: " + zigbeeHexStringToHexString(parsedData['cluster']) + "\n"
+ message += "profile_id: " + zigbeeHexStringToHexString(parsedData['profile']) + "\n"
+ message += "attributes: "
+
+ # package the attributes
+ for t in attibuteResponseList:
+ attrId = ord(t[0][0]) + (256 * ord(t[0][1]))
+ attrIdStr = "%0.4x" % attrId
+ attrIdStr = changeEndian(attrIdStr)
+
+ message += attrIdStr
+ message += ", "
+ message += "%0.2x" % ord(t[1])
+ message += ", "
+
+ dat = ""
+ for c in (t[2]):
+ dat += "%0.2x" % ord(c)
+ dat = changeEndian(dat)
+ message += dat
+ message += ";"
+
+ message = message[0:len(message) - 1]
+ message += "\n"
+ print "Sending", message
+
+ # send to all client that want this callback
+ for ra in retAddr:
+ sendSoceket.sendto(message,ra)
+
+# -----------------------------------------------------------------------------
+# Communication Callback/Parse Methods
+# -----------------------------------------------------------------------------
+def handleNewZigbeeMessage(parsedData):
+ ''' Method to process a zigbee message from the local radio.
+
+ parsedData -- Pre-parsed (into a dict) data from message.
+ '''
+ #print "=================================================================="
+ #print ''
+ #print "New Zigbee Message"
+ #printMessageData(parsedData)
+
+ # dispatch to the correct zigbee handler
+ if (parsedData['id'] == 'at_response'):
+ processZigbeeATCommandMessage(parsedData)
+
+ elif (parsedData['id'] == 'rx_explicit'):
+ processZigbeeRxExplicitCommandMessage(parsedData)
+
+ else:
+ print "Unknown API format"
+
+ #print "=================================================================="
+
+
+
+def handleNewUdpPacket(data, addr):
+ ''' Method to parse and handle an incoming UDP packet.
+
+ data -- Data that was in the UDP packet.
+ addr -- Address (IP and Port) of the UDP packet origin.
+ '''
+ global ManagementPermitJoiningReqSuccess
+
+ #print "=================================================================="
+ #print ''
+ #print "Got New UDP packet..."
+ #print data
+
+
+ # data comes in as 'key: value\n key: value...' string and so needs to be
+ # parsed into a dict
+ parsedData = dict()
+
+ # 1 key, value pair per line
+ for line in data.split('\n'):
+
+ # key and values are split based on a ':'
+ fields = line.split(':')
+
+ # make sure properly formated otherwise just ignore it
+ if len(fields) == 2:
+
+ # do strips to remove any white spacing that may have resulted
+ # from improper packing on the sender side
+ parsedData[fields[0].strip()] = fields[1].strip()
+
+
+ # wrap in try statement just in case there is an improperly formated packet we
+ # can deal with it
+ try:
+ # dispatch to the correct process method
+ if(parsedData["type"] == "zdo_bind_request"):
+ processUdpZdoBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "zdo_unbind_request"):
+ processUdpZdoUnBindReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "send_address"):
+ processUdpSendAddressMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_read_attributes"):
+ processUdpZclReadAttributesMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_configure_reporting"):
+ processUdpZclConfigureReportingMessage(parsedData, addr)
+ elif(parsedData["type"] == "policy_set"):
+ processUdpPolicySet(parsedData, addr)
+ elif(parsedData["type"] == "policy_clear"):
+ processUdpPolicyClear(parsedData, addr)
+ elif(parsedData["type"] == "management_permit_joining_request"): #made by changwoo
+ processUdpManagementPermitJoiningReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_write_attributes" and ManagementPermitJoiningReqSuccess): #made by changwoo
+ processUdpZclWriteAttributesMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_enrollment_response"): #made by changwoo
+ processUdpEnrollmentResponse(parsedData, addr)
+ elif(parsedData["type"] == "zdo_broadcast_route_record_request"): #made by changwoo
+ processUdpBroadcastingRouteRecordReqMessage(parsedData, addr)
+ elif(parsedData["type"] == "zcl_change_switch_request"): #made by changwoo
+ processUdpZclChangeSwitchReqMessage(parsedData, addr)
+ else:
+ #print "unknown Packet: " + parsedData["type"]
+ pass
+ except:
+ # if we ever get here then something went wrong and so just ignore this
+ # packet and try again later
+ print "I didn't expect this error:", sys.exc_info()[0]
+ traceback.print_exc()
+
+ #print "=================================================================="
+
+
+# -----------------------------------------------------------------------------
+# Main Running Methods
+# -----------------------------------------------------------------------------
+
+def main():
+ '''Main function used for starting the application as the main driver'''
+
+ global ZIGBEE_SERIAL_PORT
+ global ZIGBEE_SERIAL_BAUD
+ global UDP_RECEIVE_PORT
+ global zigbeeConnection
+ global zigbeeMutex
+ global doEndFlag
+
+ parseCommandLineArgs(sys.argv[1:])
+
+ # create serial object used for communication to the zigbee radio
+ sc = serial.Serial(ZIGBEE_SERIAL_PORT, ZIGBEE_SERIAL_BAUD)
+
+ # create a zigbee object that handles all zigbee communication
+ # we use this to do all communication to and from the radio
+ # when data comes from the radio it will get a bit of unpacking
+ # and then a call to the callback specified will be done with the
+ # unpacked data
+ zigbeeConnection = ZigBee(sc, callback=handleNewZigbeeMessage)
+
+ # get the long address of our local radio before we start doing anything
+ getConnectedRadioLongAddress();
+
+ # setup incoming UDP socket and bind it to self and specified UDP port
+ # sending socket does not need to be bound to anything
+ receiveSoceket.bind(('127.0.0.1', UDP_RECEIVE_PORT))
+
+ # create the thread that does short address lookups
+ addressUpdateWorkerThread = threading.Thread(target=addressUpdateWorkerMethod)
+ addressUpdateWorkerThread.start()
+
+ try:
+ # Main running loop
+ while(True):
+ #print "=================================================================="
+ #print ''
+ #print "Waiting..."
+ #print "=================================================================="
+
+ # wait for an incoming UDP packet
+ # this is a blocking call
+ data, addr = receiveSoceket.recvfrom(4096)
+
+ # handle the UDP packet appropriately
+ handleNewUdpPacket(data, addr)
+
+ except KeyboardInterrupt:
+ # use the keyboard interupt to catch a ctrl-c and kill the application
+ pass
+
+ except:
+ # something went really wrong and so exit with error message
+ traceback.print_exc()
+
+ # signal all threads to exit
+ doEndFlag = True
+
+ # wait for threads to finish before closing of the resources
+ addressUpdateWorkerThread.join()
+
+
+ # make sure to close all the connections
+ zigbeeConnection.halt()
+ receiveSoceket.close()
+ sendSoceket.close()
+
+if __name__ == "__main__":
+ # call main function since this is being run as the start
+ main()
--- /dev/null
+
+from xbee import ZigBee
+import serial
+import time
+import collections
+import sys
+import getopt
+from socket import *
+import traceback
+from threading import Thread, Lock
+import random
+
+# Communication Parameters
+ZIGBEE_SERIAL_PORT = "/dev/cu.usbserial-DN01DJIP"
+ZIGBEE_SERIAL_BAUD = 115200
+UDP_RECEIVE_PORT = 5005
+
+# Radio Parameters
+ZIGBEE_DEVICE_ADDRESS = "0000000000000000"
+didzigbeeAddressLSBs = False
+didzigbeeAddressMSBs = False
+
+
+def parseCommandLineArgs(argv):
+ global ZIGBEE_SERIAL_PORT
+ global ZIGBEE_SERIAL_BAUD
+ try:
+ opts, args = getopt.getopt(
+ argv, "hp:b:u:", ["port=", "baud=", "udpport="])
+
+ except getopt.GetoptError:
+ print 'test.py -p <serial_port> -b <baud_rate> -u <udp_port>'
+ sys.exit(2)
+
+ for opt, arg in opts:
+ if opt == '-h':
+ print 'test.py -i <inputfile> -o <outputfile>'
+ sys.exit()
+
+ elif opt in ("-p", "--port"):
+ ZIGBEE_SERIAL_PORT = arg
+
+ elif opt in ("-b", "--baud"):
+ try:
+ ZIGBEE_SERIAL_BAUD = int(arg)
+ except ValueError:
+ print "Buad rate must be an integer"
+ sys.exit()
+
+ # elif opt in ("-u", "--udpport"):
+ # try:
+ # UDP_PORT = int(arg)
+ # except ValueError:
+ # print "Udp port must be an integer"
+ # sys.exit()
+
+
+def printMessageData(data):
+ for d in data:
+ print d, ' : ',
+ for e in data[d]:
+ print "{0:02x}".format(ord(e)),
+ if (d == 'id'):
+ print "({})".format(data[d]),
+ print
+
+
+def convertMessageToString(data):
+ retString = ""
+
+ for d in data:
+ retString += d + ' : '
+
+ for e in data[d]:
+ retString += "{0:02x}".format(ord(e))
+
+ if (d == 'id'):
+ retString += "({})".format(data[d])
+
+ retString += "\n"
+
+ return retString
+
+
+def splitByN(seq, n):
+ """A generator to divide a sequence into chunks of n units."""
+ while seq:
+ yield seq[:n]
+ seq = seq[n:]
+
+
+def hexListToChar(hexList):
+ retString = ""
+ for h in hexList:
+ retString += chr(int(h, 16))
+ return retString
+
+
+
+
+def zigbeeMessageCallbackHandler(data):
+ global ZIGBEE_DEVICE_ADDRESS
+ global didzigbeeAddressLSBs
+ global didzigbeeAddressMSBs
+
+ print "================================================================================"
+ printMessageData(data)
+ print "================================================================================"
+
+
+
+if __name__ == "__main__":
+
+ # zigbeeClientCallbackDict["000d6f0003ebf2ee"] = [("127.0.0.1", 5556)]
+
+ # parse the command line arguments
+ parseCommandLineArgs(sys.argv[1:])
+
+ # create serial object used for communication to the zigbee radio
+ serialConnection = serial.Serial(ZIGBEE_SERIAL_PORT, ZIGBEE_SERIAL_BAUD)
+
+ # create a zigbee object that handles all zigbee communication
+ # we use this to do all communication to and from the radio
+ # when data comes from the radio it will get a bit of unpacking
+ # and then a call to the callback specified will be done with the
+ # unpacked data
+ zigbeeConnection = ZigBee(
+ serialConnection, callback=zigbeeMessageCallbackHandler)
+
+
+
+ print "Starting main loop..."
+ try:
+ # zigbeeConnection.send('tx_explicit',
+ # frame_id='\x01',
+ # dest_addr_long='\x00\x0d\x6f\x00\x03\xeb\xf2\xee',
+ # dest_addr='\xff\xfd',
+ # src_endpoint='\x00',
+ # dest_endpoint='\x00',
+ # cluster='\x00\x00',
+ # profile='\x00\x00',
+ # data='\xb4' + '\xee\xf2\xeb\x03\x00\x6f\x0d\x00' + '\x00'
+ # )
+
+ # zigbeeConnection.send('tx_explicit',
+ # frame_id='\x01',
+ # dest_addr_long='\x00\x0d\x6f\x00\x03\xeb\xf2\xee',
+ # dest_addr='\xff\xfd',
+ # src_endpoint='\x00',
+ # dest_endpoint='\x00',
+ # cluster='\x00\x21',
+ # profile='\x00\x00',
+ # data='\xb4' + '\xee\xf2\xeb\x03\x00\x6f\x0d\x00' + '\x01' + '\x05\x04' +
+ # '\x03' + '\xda\x9a\xd9\x40\x00\xa2\x13\x00' + '\x00'
+ # )
+
+ # zigbeeConnection.send('tx_explicit',
+ # frame_id='\x01',
+ # dest_addr_long='\x00\x0d\x6f\x00\x03\xeb\xf2\xee',
+ # dest_addr='\x65\x04',
+ # src_endpoint='\x00',
+ # dest_endpoint='\x01',
+ # cluster='\x04\x05',
+ # profile='\x01\x04',
+ # data='\x00' + '\xa1' + '\x06' + '\x00' + '\x00\x00' +
+ # '\x21' + '\x01\x00' + '\x3c\x00' + '\x00\x00'
+ # )
+ # time.sleep(0.1)
+
+
+ print "looping"
+ while True:
+ print "sending"
+ # zigbeeConnection.send('tx_explicit',
+ # frame_id='\x01',
+ # dest_addr_long='\x00\x0d\x6f\x00\x03\xeb\xf2\xee',
+ # dest_addr='\x9a\xb7',
+ # src_endpoint='\x00',
+ # dest_endpoint='\x00',
+ # cluster='\x00\x00',
+ # profile='\x00\x00',
+ # data='\xb4' + '\xee\xf2\xeb\x03\x00\x6f\x0d\x00' + '\x00'
+ # )
+
+
+ zigbeeConnection.send('tx_explicit',
+ frame_id='\x01',
+ dest_addr_long='\x00\x0d\x6f\x00\x03\xeb\xf2\xee',
+ dest_addr='\x9a\xb7',
+ src_endpoint='\x00',
+ dest_endpoint='\x00',
+ cluster='\x00\x21',
+ profile='\x00\x00',
+ data='\xb4' + '\xee\xf2\xeb\x03\x00\x6f\x0d\x00' + '\x01' + '\x05\x04' +
+ '\x03' + '\xda\x9a\xd9\x40\x00\xa2\x13\x00' + '\x00'
+ )
+
+ time.sleep(1)
+
+ except KeyboardInterrupt:
+ pass
+ except:
+ traceback.print_exc()
+
+
+ zigbeeConnection.halt()
+ serialConnection.close()
+
+
+
+