Beispiel #1
0
    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()
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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)))
Beispiel #5
0
    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
Beispiel #6
0
    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))
Beispiel #7
0
    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
Beispiel #9
0
    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)
Beispiel #10
0
    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()
Beispiel #11
0
    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()
Beispiel #12
0
    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")
Beispiel #14
0
    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)
                    )
Beispiel #15
0
    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
Beispiel #16
0
    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
Beispiel #17
0
    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
Beispiel #18
0
    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
Beispiel #19
0
    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
Beispiel #20
0
    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
Beispiel #21
0
    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")
Beispiel #23
0
    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))
Beispiel #24
0
    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)