def what_is_network_number(self, args=""): """ winn [ <addr> ] Send a What-Is-Network-Number message. If the address is unspecified the message is locally broadcast. """ args = args.split() # build a request try: request = WhatIsNetworkNumber() if len(args) > 0: request.pduDestination = Address(args[0]) else: request.pduDestination = LocalBroadcast() except: self._log.error( "Cannot build request (invalid arguments) : {}".format(args)) return iocb = IOCB((self.this_application.nsap.local_adapter, request)) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.nse.request_io, iocb) iocb.wait()
def whois_router_to_network(self, network=None, *, destination=None): # build a request try: request = WhoIsRouterToNetwork() if network: request.wirtnNetwork = int(network) if destination: request.pduDestination = Address(destination) self._log.debug("WhoIsRouterToNetwork Destination : {}".format( destination)) else: request.pduDestination = LocalBroadcast() except: self._log.error("WhoIsRouterToNetwork : invalid arguments") return iocb = IOCB((self.this_application.nsap.local_adapter, request)) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.nse.request_io, iocb) iocb.wait() try: self.init_routing_table(str( self.this_application.nse._iartn.pop())) except IndexError: pass
def whois_router_to_network(self, args=None): # build a request try: request = WhoIsRouterToNetwork() if not args: request.pduDestination = LocalBroadcast() elif args[0].isdigit(): request.pduDestination = LocalBroadcast() request.wirtnNetwork = int(args[0]) else: request.pduDestination = Address(args[0]) if len(args) > 1: request.wirtnNetwork = int(args[1]) except: self._log.error("WhoIsRouterToNetwork : invalid arguments") return iocb = IOCB((self.this_application.nsap.local_adapter, request)) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.nse.request_io, iocb) iocb.wait() try: self.init_routing_table(str( self.this_application.nse._iartn.pop())) except IndexError: pass
def write_text_value(self, request, timeout=10): try: iocb = IOCB(request) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(apdu, SimpleAckPDU): # expect an ACK self._log.error("Not an ack, see debug for more infos.") return if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) raise NoResponseFromController( "APDU Abort Reason : {}".format(reason)) except WritePropertyException as error: # construction error self._log.error(("exception: {!r}".format(error)))
def whohas( self, object_id=None, object_name=None, instance_range_low_limit=0, instance_range_high_limit=4194303, destination=None, global_broadcast=False, ): """ Object ID : analogInput:1 Object Name : string Instance Range Low Limit : 0 Instance Range High Limit : 4194303 destination (optional) : If empty, local broadcast will be used. global_broadcast : False """ obj_id = ObjectIdentifier(object_id) if object_name and not object_id: obj_name = CharacterString(object_name) obj = WhoHasObject(objectName=obj_name) elif object_id and not object_name: obj = WhoHasObject(objectIdentifier=obj_id) else: obj = WhoHasObject(objectIdentifier=obj_id, objectName=obj_name) limits = WhoHasLimits( deviceInstanceRangeLowLimit=instance_range_low_limit, deviceInstanceRangeHighLimit=instance_range_high_limit, ) request = WhoHasRequest(object=obj, limits=limits) if destination: request.pduDestination = Address(destination) else: if global_broadcast: request.pduDestination = GlobalBroadcast() else: request.pduDestination = LocalBroadcast() iocb = IOCB(request) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.request_io, iocb) iocb.wait() iocb = IOCB(request) # make an IOCB self.this_application._last_i_have_received = [] if iocb.ioResponse: # successful response apdu = iocb.ioResponse if iocb.ioError: # unsuccessful: error/reject/abort pass time.sleep(3) # self.discoveredObjects = self.this_application.i_am_counter return self.this_application._last_i_have_received
def writeMultiple(self, addr=None, args=None, vendor_id=0, timeout=10): """ Build a WritePropertyMultiple request, wait for an answer, and return status [True if ok, False if not]. :param addr: destination of request (ex. '2:3' or '192.168.1.2') :param args: list of String with <type> <inst> <prop> <value> [ <indx> ] - [ <priority> ] :param vendor_id: Mandatory for registered proprietary object and properties :param timeout: used by IOCB to discard request if timeout reached :returns: return status [True if ok, False if not] *Example*:: import BAC0 bacnet = BAC0.lite() r = ['analogValue 1 presentValue 100','analogValue 2 presentValue 100','analogValue 3 presentValue 100 - 8','@obj_142 1 @prop_1042 True'] bacnet.writeMultiple(addr='2:5',args=r,vendor_id=842) # or # bacnet.writeMultiple('2:5',r) """ if not self._started: raise ApplicationNotStarted( "BACnet stack not running - use startApp()") self.log_title("Write property multiple", args) try: # build a WritePropertyMultiple request iocb = IOCB( self.build_wpm_request(args, vendor_id=vendor_id, addr=addr)) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) self._log.debug("{:<20} {!r}".format("iocb", iocb)) except WritePropertyException as error: # construction error self._log.exception("exception: {!r}".format(error)) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(iocb.ioResponse, SimpleAckPDU): # expect an ACK self._log.warning("Not an ack, see debug for more infos.") self._log.debug("Not an ack. | APDU : {} / {}".format( (apdu, type(apdu)))) return if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) raise NoResponseFromController( "APDU Abort Reason : {}".format(reason))
def write(self, args, vendor_id=0, timeout=10): """ Build a WriteProperty request, wait for an answer, and return status [True if ok, False if not]. :param args: String with <addr> <type> <inst> <prop> <value> [ <indx> ] - [ <priority> ] :returns: return status [True if ok, False if not] *Example*:: import BAC0 bacnet = BAC0.lite() bacnet.write('2:5 analogValue 1 presentValue 100 - 8') Direct the controller at (Network 2, address 5) to write 100 to the presentValues of its analogValue 1 (AV:1) at priority 8 """ if not self._started: raise ApplicationNotStarted( "BACnet stack not running - use startApp()") args = args.split() self.log_title("Write property", args) try: # build a WriteProperty request iocb = IOCB(self.build_wp_request(args, vendor_id=vendor_id)) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) self._log.debug("{:<20} {!r}".format("iocb", iocb)) except WritePropertyException as error: # construction error self._log.exception("exception: {!r}".format(error)) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(iocb.ioResponse, SimpleAckPDU): # expect an ACK self._log.warning("Not an ack, see debug for more infos.") self._log.debug("Not an ack. | APDU : {} / {}".format( (apdu, type(apdu)))) return if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) raise NoResponseFromController( "APDU Abort Reason : {}".format(reason))
def discover_devices(self, timeout=5): """ Send a WhoIsRequest and wait for timeout seconds to receive all responses. """ self.devices = {} req = WhoIsRequest( ) # How does this know it needs to store at devices? req.pduDestination = Address( "255.255.255.255") # Global Broadcast address iocb = IOCB(req) self.request_io(iocb) iocb.set_timeout(5) iocb.wait() time.sleep( timeout) # wait for 5 seconds so all the responses are received. self.update_device_metadata(self.devices) return self.devices
def request_values( self, device_address_str: str, objects: Sequence[Tuple[Union[int, str], int]], chunk_size: Optional[int] = None, request_timeout: Optional[Timedelta] = None, ): device_address = Address(device_address_str) device_info: DeviceInfo = self.deviceInfoCache.get_device_info( device_address) # we adjusted chunking for object property request, which requested 3 properties per object # here we request only 1 property so scale chunk_scale = 3 if not chunk_size: chunk_size = 20 * chunk_scale if device_info and device_info.segmentationSupported == "noSegmentation": chunk_size = 4 * chunk_scale logger.debug( f"Chunking for device {device_address_str} is {chunk_size}") for objects_chunk in chunks(objects, chunk_size): prop_reference_list = [ PropertyReference(propertyIdentifier="presentValue") ] read_access_specs = [ ReadAccessSpecification( objectIdentifier=ObjectIdentifier(object_identifier), listOfPropertyReferences=prop_reference_list, ) for object_identifier in objects_chunk ] request = ReadPropertyMultipleRequest( listOfReadAccessSpecs=read_access_specs) request.pduDestination = device_address iocb = IOCB(request) iocb.add_callback(self._iocb_callback) if request_timeout: iocb.set_timeout(request_timeout.s) deferred(self.request_io, iocb)
def what_is_network_number(self, destination=None): """ winn [ <addr> ] Send a What-Is-Network-Number message. If the address is unspecified the message is locally broadcast. """ # build a request request = WhatIsNetworkNumber() if destination: request.pduDestination = Address(destination) else: request.pduDestination = LocalBroadcast() iocb = IOCB((self.this_application.nsap.local_adapter, request)) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.nse.request_io, iocb) iocb.wait()
def init_routing_table(self, address): """ irt <addr> Send an empty Initialize-Routing-Table message to an address, a router will return an acknowledgement with its routing table configuration. """ # build a request self._log.info("Addr : {}".format(address)) try: request = InitializeRoutingTable() request.pduDestination = Address(address) except: self._log.error("invalid arguments") return iocb = IOCB((self.this_application.nsap.local_adapter, request)) # make an IOCB iocb.set_timeout(2) deferred(self.this_application.nse.request_io, iocb) iocb.wait()
def send_weeklyschedule_request(self, request): iocb = IOCB(request) iocb.set_timeout(10) deferred(self.this_application.request_io, iocb) iocb.wait() if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(apdu, SimpleAckPDU): # expect an ACK self._log.warning("Not an ack, see debug for more infos.") self._log.debug("Not an ack. | APDU : {} / {}".format( (apdu, type(apdu)))) return if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) raise NoResponseFromController( "APDU Abort Reason : {}".format(reason)) self._log.info("Schedule Write request sent to device : {}".format( request.pduDestination))
def do_write(self, device_id, object_type, object_instance, prop_id, value, \ prop_type='invalid prop_type', indx=None, priority=None): """ do_write( <object id>, <type>, <instance>, <property_id>, <value>, <optional property type>, <optional index>, <optional priority> ) write a property to a specific object. return Nothing if successful, else Raise an exception which can be logged. """ addrlist = device_id["mac"] #4 bytes of address, 2 bytes of port if len(addrlist) != 6: raise IOError('invalid address') addr = ".".join(str(x) for x in addrlist[:4]) + ":" + str((addrlist[4] << 8) + addrlist[5]) obj_id = str(object_type) + ":" + str(object_instance) obj_id = ObjectIdentifier(obj_id).value datatype = get_datatype(obj_id[0], prop_id) # change atomic values into something encodeable, null is a special case if value == 'null': value = Null() elif issubclass(datatype, AnyAtomic): datatype = self.datatype_map[prop_type] value = datatype(value) # based on prop type build a value elif issubclass(datatype, Atomic): if datatype is Integer: value = int(value) elif datatype is Real: value = float(value) elif datatype is Unsigned: value = int(value) value = datatype(value) elif issubclass(datatype, Array): if indx is None: raise Exception("Index field missing") if indx == 0: value = Integer(value) elif issubclass(datatype.subtype, Atomic): value = datatype.subtype(value) elif not isinstance(value, datatype.subtype): raise TypeError("invalid result datatype, expecting %s" % (datatype.subtype.__name__, )) elif not isinstance(value, datatype): raise TypeError("invalid result datatype, expecting %s" % (datatype.__name__, )) # build request & save the value request = WritePropertyRequest(objectIdentifier=obj_id, propertyIdentifier=prop_id) request.pduDestination = Address(addr) request.propertyValue = Any() request.propertyValue.cast_in(value) if indx is not None: request.propertyArrayIndex = indx if priority is not None: request.priority = priority # make an IOCB iocb = IOCB(request) self.request_io(iocb) iocb.set_timeout(3) iocb.wait() if iocb.ioResponse: if not isinstance(iocb.ioResponse, SimpleAckPDU): raise Exception("Response Not an ACK") return # write success if iocb.ioError: raise Exception("ioError: %s" + str(iocb.ioError)) raise Exception("do_write failed")
def readRange( self, args, range_params=None, arr_index=None, vendor_id=0, bacoid=None, timeout=10, ): """ Build a ReadProperty request, wait for the answer and return the value :param args: String with <addr> <type> <inst> <prop> [ <indx> ] :returns: data read from device (str representing data like 10 or True) *Example*:: import BAC0 myIPAddr = '192.168.1.10/24' bacnet = BAC0.connect(ip = myIPAddr) bacnet.read('2:5 analogInput 1 presentValue') Requests the controller at (Network 2, address 5) for the presentValue of its analog input 1 (AI:1). """ if not self._started: raise ApplicationNotStarted("BACnet stack not running - use startApp()") args_split = args.split() self.log_title("Read range ", args_split) vendor_id = vendor_id bacoid = bacoid try: # build ReadProperty request request = self.build_rrange_request( args_split, range_params=range_params, arr_index=arr_index, vendor_id=vendor_id, bacoid=bacoid, ) iocb = IOCB(request) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) self._log.debug("{:<20} {!r}".format("iocb", iocb)) except ReadRangeException as error: # construction error self._log.exception("exception: {!r}".format(error)) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(apdu, ReadRangeACK): # expecting an ACK self._log.warning("Not an ack, see debug for more infos.") self._log.debug( "Not an ack. | APDU : {} / {}".format((apdu, type(apdu))) ) return # find the datatype datatype = get_datatype( apdu.objectIdentifier[0], apdu.propertyIdentifier, vendor_id=vendor_id ) if not datatype: # raise TypeError("unknown datatype") datatype = cast_datatype_from_tag( apdu.propertyValue, apdu.objectIdentifier[0], apdu.propertyIdentifier, ) try: value = apdu.itemData.cast_out(datatype) except TypeError as error: self._log.error( "Problem casting value : {} | Datatype : {} | error : {}".format( apdu.itemData, datatype, error ) ) return apdu self._log.debug("{:<20} {:<20}".format("value", "datatype")) self._log.debug("{!r:<20} {!r:<20}".format(value, datatype)) return value if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) if reason == "segmentationNotSupported": self._log.warning( "Segmentation not supported... will read properties one by one..." ) self._log.debug("The Request was : {}".format(args_split)) value = self._split_the_read_request(args, arr_index) return value else: if reason == "unknownProperty": if "priorityArray" in args: self._log.debug("Unknown property {}".format(args)) else: self._log.warning("Unknown property {}".format(args)) if "description" in args: return "" elif "inactiveText" in args: return "Off" elif "activeText" in args: return "On" else: raise UnknownPropertyError("Unknown property {}".format(args)) elif reason == "unknownObject": self._log.warning("Unknown object {}".format(args)) raise UnknownObjectError("Unknown object {}".format(args)) else: # Other error... consider NoResponseFromController (65) # even if the realy reason is another one raise NoResponseFromController( "APDU Abort Reason : {}".format(reason) )
def do_ReadPropertyMultiple(self, args, body_json): """ Example Read http://localhost/read/adderss/analogValue:1 http://localhost/read/<address>/<object type>:<object instance number> http://localhost/read/<address>/<object type>:<object instance number>/<property> http://localhost:8081/read/192.168.1.100/analogValue:0/presentValue Example WhoIs http://localhost/whois/<address> Example ReadPropertyMultiple http://localhost:8081/readpropertymultiple/ args = {'bacnet_objects': [{'object': 'analogInput:1', 'property': 'presentValue'}, {'object': 'analogInput:4', 'property': 'presentValue'}, {'object': 'analogInput:8', 'property': 'presentValue'}, {'object': 'analogInput:12', 'property': 'presentValue'}], 'address': '665002'} headers = {'Content-Type':'application/json'} res = requests.post(url, headers=headers, data=json.dumps(body), timeout=2) """ # Init results JSON results = {} try: request = self._form_ReadPropertyMultiple_request(args, body_json) except ValueError as e: # Headers were already written if _debug: HTTPRequestHandler._debug(" - request: %r", str(e)) self.send_header('Content-Type','text/plain') self.end_headers() self.wfile.write(str(e)) self.wfile.write(b'End of response') return # Make IOControlBlock iocb = IOCB(request) timeout = int(self.headers.get('X-bacnet-timeout', 4)) iocb.set_timeout(timeout, err=TimeoutError) # Give iocb to app deferred(this_application.request_io, iocb) # Wait for completion iocb.wait() """Format of the response ObjectIdentifier PropertyIdentifier : PropertyValue """ # Success if iocb.ioResponse: apdu = iocb.ioResponse # Acknowledgement response if not isinstance(apdu, ReadPropertyMultipleACK): msg = ('Received improper read property multiple response. ' + 'The expected response type was an acknowledgement, ' + 'got {}'.format(str(type(apdu))) ) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(bytes(msg, 'utf-8')) return # loop through the results for result in apdu.listOfReadAccessResults: # Object identifiers are top objectIdentifier = result.objectIdentifier results[str(objectIdentifier)] = {} for element in result.listOfResults: # Properties and array elements of each object are bottom propertyIdentifier = element.propertyIdentifier propertyArrayIndex = element.propertyArrayIndex readResult = element.readResult if readResult.propertyAccessError: results[str(objectIdentifier)][str(propertyIdentifier)] = \ readResult.propertyAccessError continue # Skip this result # Form the message response propertyValue = readResult.propertyValue dtype = get_datatype(objectIdentifier[0], propertyIdentifier) if issubclass(dtype, Array) and (propertyArrayIndex is not None): # The property value is an array of values if propertyArrayIndex == 0: # Result is the first index of array # See http://kargs.net/BACnet/Foundations2015-Developer-Q-A.pdf value = propertyValue.cast_out(Unsigned) else: # Cast BACnet array to python array with elements # Matching the BACnet array subtype # [BACint, BACint, BACint] -> [pyint, pyint, pyint] value = propertyValue.cast_out(dtype.subtype) else: # The value is not an array (single value) value = propertyValue.cast_out(dtype) # Form response JSON object """results = {'analogValue:1' : {'presentValue':'1', 'objectName':'some_name', 'arrayResult':[1,2,3]}, 'analogValue:2' : {'presentValue':'4'} } """ results[str(objectIdentifier)][str(propertyIdentifier)] = value else: # Error msg = ('Received improper BACnet response \n' + 'args : {}\n' + 'kwargs : {}\n' + 'ioState : {}\n' + 'ioComplete : {}\n' + 'ioCallback : {}\n' + 'ioResponse : {}') msg = msg.format(iocb.args, iocb.kwargs, iocb.ioState, iocb.ioComplete, iocb.ioCallback, iocb.ioResponse) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(bytes(msg, 'utf-8')) return # Write the response try: msg = bytes(json.dumps(results), 'utf-8') except TypeError: # Cannot serialize value of results msg = bytes(json.dumps(results, default=lambda o: str(o)), 'utf-8') if _debug: HTTPRequestHandler._debug(" - response: %r", str(msg)) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(msg) return
def whois(self, *args, global_broadcast=False, destination=None, timeout=None): """ Build a WhoIs request :param args: string built as [ <addr>] [ <lolimit> <hilimit> ] **optional** :returns: discoveredDevices as a defaultdict(int) Example:: whois(global_broadcast=True) # WhoIs broadcast globally. Every device will respond with an IAm whois('2:5') # WhoIs looking for the device at (Network 2, Address 5) whois('10 1000') # WhoIs looking for devices in the ID range (10 - 1000) """ if not self._started: raise ApplicationNotStarted( "BACnet stack not running - use startApp()") if args: args = args[0].split() msg = args if args else "any" self._log.debug("do_whois {!r}".format(msg)) # build a request request = WhoIsRequest() if (len(args) == 1) or (len(args) == 3): request.pduDestination = Address(args[0]) del args[0] else: if global_broadcast: request.pduDestination = GlobalBroadcast() else: request.pduDestination = LocalBroadcast() if len(args) == 2: try: request.deviceInstanceRangeLowLimit = int(args[0]) request.deviceInstanceRangeHighLimit = int(args[1]) except ValueError: pass self._log.debug("{:>12} {}".format("- request:", request)) if destination: request.pduDestination = Address(destination) iocb = IOCB(request) # make an IOCB if timeout: iocb.set_timeout(timeout) self.this_application._last_i_am_received = [] # pass to the BACnet stack deferred(self.this_application.request_io, iocb) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if iocb.ioError: # unsuccessful: error/reject/abort pass time.sleep(3) self.discoveredDevices = self.this_application.i_am_counter return self.this_application._last_i_am_received
def do_whois(self, args): NOT_IMPLEMENTED = True if NOT_IMPLEMENTED: self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(NotImplementedError('WhoIs not implemented')) return if _debug: HTTPRequestHandler._debug("do_whois %r", args) try: # build a request request = WhoIsRequest() if (len(args) == 1) or (len(args) == 3): request.pduDestination = Address(args[0]) del args[0] else: request.pduDestination = GlobalBroadcast() if len(args) == 2: request.deviceInstanceRangeLowLimit = int(args[0]) request.deviceInstanceRangeHighLimit = int(args[1]) if _debug: HTTPRequestHandler._debug(" - request: %r", request) # make an IOCB iocb = IOCB(request) timeout = int(self.headers.get('X-bacnet-timeout', 5)) iocb.set_timeout(timeout, err=TimeoutError) if _debug: HTTPRequestHandler._debug(" - iocb: %r", iocb) # Give it to the application deferred(this_application.request_io, iocb) iocb.wait() if iocb.ioError: if _debug: HTTPRequestHandler._debug(" - error: %r", iocb.ioError) result = {"error": str(iocb.ioError)} else: if _debug: HTTPRequestHandler._debug( " - response: %r", iocb.ioResponse ) apdu = iocb.ioResponse # find the datatype datatype = get_datatype( apdu.objectIdentifier[0], apdu.propertyIdentifier ) if _debug: HTTPRequestHandler._debug(" - datatype: %r", datatype) if not datatype: raise TypeError("unknown datatype") # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and ( apdu.propertyArrayIndex is not None ): if apdu.propertyArrayIndex == 0: datatype = Unsigned else: datatype = datatype.subtype if _debug: HTTPRequestHandler._debug( " - datatype: %r", datatype ) # convert the value to a dict if possible value = apdu.propertyValue.cast_out(datatype) if hasattr(value, "dict_contents"): value = value.dict_contents(as_class=OrderedDict) if _debug: HTTPRequestHandler._debug(" - value: %r", value) result = {"value": value} except Exception as err: HTTPRequestHandler._exception("exception: %r", err) result = {"exception": str(err)} self.send_header('Content-Type', 'application/json') self.end_headers() result_bytes = json.dumps(result).encode("utf-8") self.wfile.write(result_bytes) return # encode the results as JSON, convert to bytes result_bytes = json.dumps(result).encode("utf-8") # write the result self.send_header('Content-Type', 'application/json') self.end_headers() self.wfile.write(result_bytes) return
def readMultiple(self, args, vendor_id=0, timeout=10, prop_id_required=False): """ Build a ReadPropertyMultiple request, wait for the answer and return the values :param args: String with <addr> ( <type> <inst> ( <prop> [ <indx> ] )... )... :returns: data read from device (str representing data like 10 or True) *Example*:: import BAC0 myIPAddr = '192.168.1.10/24' bacnet = BAC0.connect(ip = myIPAddr) bacnet.readMultiple('2:5 analogInput 1 presentValue units') Requests the controller at (Network 2, address 5) for the (presentValue and units) of its analog input 1 (AI:1). """ if not self._started: raise ApplicationNotStarted( "BACnet stack not running - use startApp()") args = args.split() vendor_id = vendor_id values = [] self.log_title("Read Multiple", args) try: # build an ReadPropertyMultiple request iocb = IOCB(self.build_rpm_request(args, vendor_id=vendor_id)) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) self._log.debug("{:<20} {!r}".format("iocb", iocb)) except ReadPropertyMultipleException as error: # construction error self._log.exception("exception: {!r}".format(error)) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(apdu, ReadPropertyMultipleACK): # expecting an ACK self._log.debug("{:<20}".format("not an ack")) self._log.warning("Not an Ack. | APDU : {} / {}".format( (apdu, type(apdu)))) return # loop through the results for result in apdu.listOfReadAccessResults: # here is the object identifier objectIdentifier = result.objectIdentifier self.log_subtitle( "{!r} : {!r}".format(objectIdentifier[0], objectIdentifier[1]), width=114, ) self._log.debug("{:<20} {:<20} {:<30} {:<20}".format( "propertyIdentifier", "propertyArrayIndex", "value", "datatype")) self._log.debug("-" * 114) # now come the property values per object for element in result.listOfResults: # get the property and array index propertyIdentifier = element.propertyIdentifier propertyArrayIndex = element.propertyArrayIndex readResult = element.readResult if readResult.propertyAccessError is not None: self._log.debug("Property Access Error for {}".format( readResult.propertyAccessError)) values.append(None) else: # here is the value propertyValue = readResult.propertyValue # find the datatype datatype = get_datatype(objectIdentifier[0], propertyIdentifier, vendor_id=vendor_id) if not datatype: value = cast_datatype_from_tag( propertyValue, objectIdentifier[0], propertyIdentifier) else: # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and (propertyArrayIndex is not None): if propertyArrayIndex == 0: value = propertyValue.cast_out(Unsigned) else: value = propertyValue.cast_out( datatype.subtype) else: value = propertyValue.cast_out(datatype) self._log.debug( "{!r:<20} {!r:<20} {!r:<30} {!r:<20}".format( propertyIdentifier, propertyArrayIndex, value, datatype, )) if prop_id_required: try: int(propertyIdentifier) prop_id = "@prop_{}".format(propertyIdentifier) value = list(value.items())[0][1] except ValueError: prop_id = propertyIdentifier values.append((value, prop_id)) else: values.append(value) return values if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) self._log.warning("APDU Abort Reject Reason : {}".format(reason)) self._log.debug("The Request was : {}".format(args)) if reason == "unrecognizedService": raise UnrecognizedService() elif reason == "segmentationNotSupported": # value = self._split_the_read_request(args, arr_index) # return value self.segmentation_supported = False raise SegmentationNotSupported() elif reason == "unknownObject": self._log.warning("Unknown object {}".format(args)) raise UnknownObjectError("Unknown object {}".format(args)) elif reason == "unknownProperty": self._log.warning("Unknown property {}".format(args)) values.append("") return values else: self._log.warning( "No response from controller : {}".format(reason)) values.append("") return values
def request_device_properties( self, device_address_str: str, properties=None, skip_when_cached=False, request_timeout: Optional[Timedelta] = None, ): if threading.current_thread() == threading.main_thread(): logger.error( "request_device_properties called from main thread! Run it from an executor!", stack_info=True, ) if properties is None: properties = ["objectName", "description"] device_address = Address(device_address_str) device_info: DeviceInfo = self.deviceInfoCache.get_device_info( device_address) if not device_info: for retry in range(self._retry_count): deferred(self.who_is, address=device_address) time.sleep(5 * (retry + 1)) device_info: DeviceInfo = self.deviceInfoCache.get_device_info( device_address) if device_info: break else: device_info: DeviceInfo = self.deviceInfoCache.get_device_info( device_address) if not device_info: logger.error( "Device with address {} is not in device cache!", device_address_str, ) return if skip_when_cached: cache_key = (device_address_str, "device", device_info.deviceIdentifier) if cache_key in self._object_info_cache: cached_object_info = self._object_info_cache[cache_key] if all(property in cached_object_info for property in properties): logger.debug("Device info already in cache. Skipping!") return prop_reference_list = [ PropertyReference(propertyIdentifier=property) for property in properties ] device_object_identifier = ("device", device_info.deviceIdentifier) read_access_spec = ReadAccessSpecification( objectIdentifier=device_object_identifier, listOfPropertyReferences=prop_reference_list, ) request = ReadPropertyMultipleRequest( listOfReadAccessSpecs=[read_access_spec]) request.pduDestination = device_address iocb = IOCB(request) deferred(self.request_io, iocb) if request_timeout: iocb.set_timeout(request_timeout.s) iocb.wait() if iocb.ioResponse: result_values = self._unpack_iocb(iocb) if device_object_identifier in result_values: with self._object_info_cache_lock: cache_key = ( device_address_str, "device", device_info.deviceIdentifier, ) if cache_key not in self._object_info_cache: self._object_info_cache[cache_key] = {} self._object_info_cache[cache_key].update( result_values[device_object_identifier]) return result_values[device_object_identifier] # do something for error/reject/abort if iocb.ioError: logger.error( "IOCB returned with error for device properties request (device {}, objects {}, props {}) : {}", device_address_str, device_object_identifier, properties, iocb.ioError, ) return None
def request_object_properties( self, device_address_str: str, objects: Sequence[Tuple[Union[int, str], int]], properties=None, skip_when_cached=False, chunk_size: Optional[int] = None, request_timeout: Optional[Timedelta] = None, ): if threading.current_thread() == threading.main_thread(): logger.error( "request_object_properties called from main thread! Run it from an executor!", stack_info=True, ) if properties is None: properties = ["objectName", "description", "units"] device_address = Address(device_address_str) device_info: DeviceInfo = self.deviceInfoCache.get_device_info( device_address) if skip_when_cached: object_to_request = [] for object_type, object_instance in objects: cache_key = (device_address_str, object_type, object_instance) if cache_key in self._object_info_cache: cached_object_info = self._object_info_cache[cache_key] if all(property in cached_object_info for property in properties): logger.debug( "Object info for {} already in cache. Skipping!", (object_type, object_instance), ) continue object_to_request.append((object_type, object_instance)) if not object_to_request: logger.debug("All objects already in cache") return objects = object_to_request result_values = {} if not chunk_size: chunk_size = 20 if device_info and device_info.segmentationSupported == "noSegmentation": chunk_size = 4 for objects_chunk in chunks(objects, chunk_size): prop_reference_list = [ PropertyReference(propertyIdentifier=property) for property in properties ] read_access_specs = [ ReadAccessSpecification( objectIdentifier=ObjectIdentifier(object_identifier), listOfPropertyReferences=prop_reference_list, ) for object_identifier in objects_chunk ] request = ReadPropertyMultipleRequest( listOfReadAccessSpecs=read_access_specs) request.pduDestination = device_address iocb = IOCB(request) deferred(self.request_io, iocb) if request_timeout: iocb.set_timeout(request_timeout.s) iocb.wait() if iocb.ioResponse: chunk_result_values = self._unpack_iocb(iocb) for object_identifier in chunk_result_values: object_type, object_instance = object_identifier with self._object_info_cache_lock: cache_key = (device_address_str, object_type, object_instance) if cache_key not in self._object_info_cache: self._object_info_cache[cache_key] = {} self._object_info_cache[cache_key].update( chunk_result_values[object_identifier]) result_values.update(chunk_result_values) # do something for error/reject/abort if iocb.ioError: logger.error( "IOCB returned with error for object properties request (device {}, objects {}, props {}): {}", device_address_str, objects, properties, iocb.ioError, ) # TODO: maybe raise error here return result_values
def do_read(self, addr, obj_id, prop_id, indx=None): """ do_read( <address to read from>, <object_id>, <property_id>, <optinal index>) read a property from a specific object. if read fails return None. """ try: obj_id = ObjectIdentifier(obj_id).value datatype = get_datatype(obj_id[0], prop_id) if not datatype: self.logger.info("%s: invalid property for object type" % prop_id) return None # build a request request = ReadPropertyRequest( objectIdentifier=obj_id, propertyIdentifier=prop_id, ) request.pduDestination = Address(addr) if indx is not None: request.propertyArrayIndex = indx iocb = IOCB(request) self.request_io(iocb) # time.sleep(3) iocb.set_timeout(5) iocb.wait() if iocb.ioError: self.logger.error("READ ERROR:" + str(iocb.ioError) + "\n") elif iocb.ioResponse: apdu = iocb.ioResponse if not isinstance( apdu, ReadPropertyACK): # should be an ack to our request self.logger.error("Response not an ACK") return None datatype = get_datatype(apdu.objectIdentifier[0], apdu.propertyIdentifier) if not datatype: self.logger.info("unknown datatype") return None # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and (apdu.propertyArrayIndex is not None): if apdu.propertyArrayIndex == 0: value = apdu.propertyValue.cast_out(Unsigned) else: value = apdu.propertyValue.cast_out(datatype.subtype) else: value = apdu.propertyValue.cast_out(datatype) return value else: self.logger.error("ioError or ioResponse expected") except Exception as error: self.logger.error("exception:", error) return None
def do_read( self, dev_addr: str, obj_type: str, obj_instance: int, prop_id: str, indx: int = None, ): """ read a property of a specific object from a device at `dev_addr`. if read fails, raise exception """ obj_id = make_obj_id(obj_type, obj_instance) obj_id = ObjectIdentifier(obj_id).value datatype = get_datatype(obj_id[0], prop_id) if not datatype: raise Exception( f"{prop_id}:invalid property for object type '{obj_type}'") # build a request request = ReadPropertyRequest( objectIdentifier=obj_id, propertyIdentifier=prop_id, ) request.pduDestination = Address(dev_addr) if indx is not None: request.propertyArrayIndex = indx iocb = IOCB(request) self.request_io(iocb) iocb.set_timeout(3) iocb.wait() if iocb.ioError: raise Exception("READ ERROR:" + str(iocb.ioError)) if iocb.ioResponse: apdu = iocb.ioResponse #print("read resp %s"%apdu) if not isinstance( apdu, ReadPropertyACK): #should be an ack to our request raise Exception("Response not an ACK") datatype = get_datatype(apdu.objectIdentifier[0], apdu.propertyIdentifier) if not datatype: raise Exception("unknown datatype") # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and (apdu.propertyArrayIndex is not None): if apdu.propertyArrayIndex == 0: value = apdu.propertyValue.cast_out(Unsigned) else: value = apdu.propertyValue.cast_out(datatype.subtype) else: value = apdu.propertyValue.cast_out(datatype) if type(value) in [Enumerated, Unsigned]: return value.__dict__["value"] return value else: raise Exception("ioError or ioResponse expected")
def read( self, args, arr_index=None, vendor_id=0, bacoid=None, timeout=10, prop_id_required=False, ): """ Build a ReadProperty request, wait for the answer and return the value :param args: String with <addr> <type> <inst> <prop> [ <indx> ] :returns: data read from device (str representing data like 10 or True) *Example*:: import BAC0 myIPAddr = '192.168.1.10/24' bacnet = BAC0.connect(ip = myIPAddr) bacnet.read('2:5 analogInput 1 presentValue') Requests the controller at (Network 2, address 5) for the presentValue of its analog input 1 (AI:1). """ if not self._started: raise ApplicationNotStarted( "BACnet stack not running - use startApp()") args_split = args.split() self.log_title("Read property", args_split) vendor_id = vendor_id bacoid = bacoid try: # build ReadProperty request iocb = IOCB( self.build_rp_request(args_split, arr_index=arr_index, vendor_id=vendor_id, bacoid=bacoid)) iocb.set_timeout(timeout) # pass to the BACnet stack deferred(self.this_application.request_io, iocb) self._log.debug("{:<20} {!r}".format("iocb", iocb)) except ReadPropertyException as error: # construction error self._log.exception("exception: {!r}".format(error)) iocb.wait() # Wait for BACnet response if iocb.ioResponse: # successful response apdu = iocb.ioResponse if not isinstance(apdu, ReadPropertyACK): # expecting an ACK self._log.warning("Not an ack, see debug for more infos.") self._log.debug("Not an ack. | APDU : {} / {}".format( (apdu, type(apdu)))) return # find the datatype datatype = get_datatype(apdu.objectIdentifier[0], apdu.propertyIdentifier, vendor_id=vendor_id) if not datatype: # raise TypeError("unknown datatype") value = cast_datatype_from_tag( apdu.propertyValue, apdu.objectIdentifier[0], apdu.propertyIdentifier, ) else: # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and (apdu.propertyArrayIndex is not None): if apdu.propertyArrayIndex == 0: value = apdu.propertyValue.cast_out(Unsigned) else: value = apdu.propertyValue.cast_out(datatype.subtype) else: value = apdu.propertyValue.cast_out(datatype) self._log.debug("{:<20} {:<20}".format("value", "datatype")) self._log.debug("{!r:<20} {!r:<20}".format(value, datatype)) if prop_id_required: try: int(apdu.propertyIdentifier) prop_id = "@prop_{}".format(apdu.propertyIdentifier) value = list(value.items())[0][1] except ValueError: prop_id = apdu.propertyIdentifier return (value, prop_id) else: return value if iocb.ioError: # unsuccessful: error/reject/abort apdu = iocb.ioError reason = find_reason(apdu) if reason == "segmentationNotSupported": value = self._split_the_read_request(args, arr_index) return value else: if reason == "unknownProperty": if "description" in args: return "Not Implemented" elif "inactiveText" in args: return "Off" elif "activeText" in args: return "On" else: raise UnknownPropertyError( "Unknown property {}".format(args)) elif reason == "unknownObject": self._log.warning("Unknown object {}".format(args)) raise UnknownObjectError("Unknown object {}".format(args)) else: # Other error... consider NoResponseFromController (65) # even if the real reason is another one raise NoResponseFromController( "APDU Abort Reason : {}".format(reason))
def do_read(self, args): if _debug: HTTPRequestHandler._debug("do_read %r", args) try: addr, obj_id = args[:2] obj_id = ObjectIdentifier(obj_id).value # get the object type if not get_object_class(obj_id[0]): raise ValueError("unknown object type") # implement a default property, the bain of committee meetings if len(args) == 3: prop_id = args[2] else: prop_id = "presentValue" # look for its datatype, an easy way to see if the property is # appropriate for the object datatype = get_datatype(obj_id[0], prop_id) if not datatype: raise ValueError("invalid property for object type") # build a request request = ReadPropertyRequest( objectIdentifier=obj_id, propertyIdentifier=prop_id ) request.pduDestination = Address(addr) # look for an optional array index if len(args) == 5: request.propertyArrayIndex = int(args[4]) if _debug: HTTPRequestHandler._debug(" - request: %r", request) # make an IOCB iocb = IOCB(request) timeout = int(self.headers.get('X-bacnet-timeout', 5)) iocb.set_timeout(timeout, err=TimeoutError) if _debug: HTTPRequestHandler._debug(" - iocb: %r", iocb) # give it to the application deferred(this_application.request_io, iocb) # Wait for it to complete iocb.wait() # filter out errors and aborts if iocb.ioError: if _debug: HTTPRequestHandler._debug(" - error: %r", iocb.ioError) result = {"error": str(iocb.ioError)} else: if _debug: HTTPRequestHandler._debug( " - response: %r", iocb.ioResponse ) apdu = iocb.ioResponse # find the datatype datatype = get_datatype( apdu.objectIdentifier[0], apdu.propertyIdentifier ) if _debug: HTTPRequestHandler._debug(" - datatype: %r", datatype) if not datatype: raise TypeError("unknown datatype") # special case for array parts, others are managed by cast_out if issubclass(datatype, Array) and ( apdu.propertyArrayIndex is not None ): if apdu.propertyArrayIndex == 0: datatype = Unsigned else: datatype = datatype.subtype if _debug: HTTPRequestHandler._debug( " - datatype: %r", datatype ) # convert the value to a dict if possible value = apdu.propertyValue.cast_out(datatype) if hasattr(value, "dict_contents"): value = value.dict_contents(as_class=OrderedDict) if _debug: HTTPRequestHandler._debug(" - value: %r", value) result = {"value": value} except Exception as err: HTTPRequestHandler._exception("exception: %r", err) result = {"exception": str(err)} # encode the results as JSON, convert to bytes result_bytes = json.dumps(result).encode("utf-8") # write the result self.send_header('Content-Type', 'Application/json') self.end_headers() self.wfile.write(result_bytes)