#!/usr/bin/python """ HTTPServer """ import threading import json import zlib from collections import OrderedDict from urlparse import urlparse, parse_qs import SocketServer import SimpleHTTPServer from bacpypes.debugging import class_debugging, ModuleLogger from bacpypes.consolelogging import ConfigArgumentParser from bacpypes.core import run, deferred from bacpypes.iocb import IOCB from bacpypes.pdu import Address, GlobalBroadcast from bacpypes.apdu import ReadPropertyRequest, WhoIsRequest from bacpypes.primitivedata import Unsigned, ObjectIdentifier from bacpypes.constructeddata import Array from bacpypes.app import BIPSimpleApplication from bacpypes.object import get_object_class, get_datatype from bacpypes.local.device import LocalDeviceObject # some debugging _debug = 0 _log = ModuleLogger(globals()) # reference a simple application this_application = None server = None # favorite icon favicon = zlib.decompress( 'x\x9c\xb5\x93\xcdN\xdb@\x14\x85\x07\x95\x07\xc8\x8amYv\xc9#\xe4\x11x\x04\x96}' '\x8c\x88\x1dl\xa0\x9b\xb6A\xa2)\x0bVTB\xa9"\xa5?*I\x16\xad"\x84d\x84DE\x93' '\x14;v\xc01M\xe2$\x988\xb1l\x9d\xde;v\\\x03\x89TU\xea\xb5N\xe4\xb9\x9a\xef' '\x1c\xcfO\x84X\xa0\'\x95\x12\xf4\xbb,\x9e/\n\xb1$\x84xF\xa2\x16u\xc2>WzQ\xfc' '\xf7\xca\xad\xafo\x91T\xd2\x1ai\xe5\x1fx[\xf9\xf4\x01\xc57\xbb\xd8\xdf\xd8' '\x00\x8d\x11\xf9\x95\x12\xda\x9a\xc3\xae\xe5_\xbdDpk\x03\xc3\xaeT\xd0\xb3\xd0' '>?\x83Z\xfd\x86Z\xa5\x84\x1fG_\xa4\xe7\x1c^\xa9W\xbfJ\xfe\xb4\xf0\x0e^\xdb' '\x88}0 \xafA\x0f\xa3+c&O\xbd\xf4\xc1\xf6\xb6d\x9d\xc6\x05\xdcVSz\xb0x\x1c\x10' '\x0fo\x02\xc7\xd0\xe7\xf1%\xe5\xf3\xc78\xdb\xf9Y\x93\x1eI\x1f\xf8>\xfa\xb5' '\x8bG<\x8dW\x0f^\x84\xd9\xee\xb5~\x8f\xe1w\xaf{\x83\x80\xb2\xbd\xe1\x10\x83' '\x88\'\xa5\x12\xbcZ?9\x8e\xb3%\xd3\xeb`\xd4\xd2\xffdS\xb9\x96\x89!}W!\xfb\x9a' '\xf9t\xc4f\x8aos\x92\x9dtn\xe0\xe8Z\xcc\xc8=\xec\xf7d6\x97\xa3]\xc2Q\x1b(\xec' 'd\x99_\x8dx\xd4\x15%\xce\x96\xf9\xbf\xacP\xd1:\xfc\xf1\x18\xbe\xeb\xe2\xaey' '\x89;]\xc5\xf1\xfb<\xf3\x99\xe9\x99\xefon\xa2\xdb6\xe5\x1c\xbb^\x8b}FV\x1b' '\x9es+\xb3\xbd\x81M\xeb\xd1\xe0^5\xf1\xbd|\xc4\xfca\xf2\xde\xf0w\x9cW\xabr.' '\xe7\xd9\x8dFx\x0e\xa6){\x93\x8e\x85\xf1\xb5\x81\x89\xd9\x82\xa1\x9c\xc8;\xf9' '\xe0\x0cV\xb8W\xdc\xdb\x83\xa9i\xb1O@g\xa6T*\xd3=O\xeaP\xcc(^\x17\xfb\xe4\xb3' 'Y\xc9\xb1\x17{N\xf7\xfbo\x8b\xf7\x97\x94\xe3;\xcd\xff)\xd2\xf2\xacy\xa0\x9b' '\xd4g=\x11B\x8bT\x8e\x94Y\x08%\x12\xe2q\x99\xd4\x7f*\x84O\xfa\r\xb5\x916R' ) # # ThreadedHTTPRequestHandler # @class_debugging class ThreadedHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): if _debug: ThreadedHTTPRequestHandler._debug("do_GET") global favicon # get the thread cur_thread = threading.current_thread() if _debug: ThreadedHTTPRequestHandler._debug(" - cur_thread: %r", cur_thread) # parse query data and params to find out what was passed parsed_params = urlparse(self.path) if _debug: ThreadedHTTPRequestHandler._debug(" - parsed_params: %r", parsed_params) parsed_query = parse_qs(parsed_params.query) if _debug: ThreadedHTTPRequestHandler._debug(" - parsed_query: %r", parsed_query) # find the pieces args = parsed_params.path.split('/') if _debug: ThreadedHTTPRequestHandler._debug(" - args: %r", args) if (args[1] == 'read'): self.do_read(args[2:]) elif (args[1] == 'whois'): self.do_whois(args[2:]) if args[1] == 'favicon.ico': self.send_response(200) self.send_header("Content-type", 'image/x-icon') self.send_header("Content-Length", len(favicon)) self.end_headers() self.wfile.write(favicon) else: return "'read' or 'whois' expected" def do_read(self, args): if _debug: ThreadedHTTPRequestHandler._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: ThreadedHTTPRequestHandler._debug(" - request: %r", request) # make an IOCB iocb = IOCB(request) if _debug: ThreadedHTTPRequestHandler._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: ThreadedHTTPRequestHandler._debug(" - error: %r", iocb.ioError) result = { "error": str(iocb.ioError) } else: if _debug: ThreadedHTTPRequestHandler._debug(" - response: %r", iocb.ioResponse) apdu = iocb.ioResponse # find the datatype datatype = get_datatype(apdu.objectIdentifier[0], apdu.propertyIdentifier) if _debug: ThreadedHTTPRequestHandler._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: ThreadedHTTPRequestHandler._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: ThreadedHTTPRequestHandler._debug(" - value: %r", value) result = { "value": value } except Exception as err: ThreadedHTTPRequestHandler._exception("exception: %r", err) result = { "exception": str(err) } # write the result json.dump(result, self.wfile) def do_whois(self, args): if _debug: ThreadedHTTPRequestHandler._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: ThreadedHTTPRequestHandler._debug(" - request: %r", request) # make an IOCB iocb = IOCB(request) if _debug: ThreadedHTTPRequestHandler._debug(" - iocb: %r", iocb) # give it to the application this_application.request_io(iocb) # no result -- it would be nice if these were the matching I-Am's result = {} except Exception as err: ThreadedHTTPRequestHandler._exception("exception: %r", err) result = { "exception": str(err) } # write the result json.dump(result, self.wfile) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass # # __main__ # try: # parse the command line arguments parser = ConfigArgumentParser(description=__doc__) # add an option to override the port in the config file parser.add_argument('--port', type=int, help="override the port in the config file to PORT", default=9000, ) args = parser.parse_args() if _debug: _log.debug("initialization") if _debug: _log.debug(" - args: %r", args) # make a device object this_device = LocalDeviceObject(ini=args.ini) if _debug: _log.debug(" - this_device: %r", this_device) # make a simple application this_application = BIPSimpleApplication(this_device, args.ini.address) # local host, special port HOST, PORT = "", int(args.port) server = ThreadedTCPServer((HOST, PORT), ThreadedHTTPRequestHandler) if _debug: _log.debug(" - server: %r", server) # Start a thread with the server -- that thread will then start a thread for each request server_thread = threading.Thread(target=server.serve_forever) if _debug: _log.debug(" - server_thread: %r", server_thread) # exit the server thread when the main thread terminates server_thread.daemon = True server_thread.start() if _debug: _log.debug("running") run() except Exception as err: _log.exception("an error has occurred: %s", err) finally: if server: server.shutdown() if _debug: _log.debug("finally")