1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-10-05 22:18:16 +08:00
bacpypes/samples/DiscoverToDo.py
2020-04-22 18:51:14 -04:00

1627 lines
51 KiB
Python

#!/usr/bin/python3
"""
Autodiscover
This is an application to assist with the "discovery" process of finding
BACnet routers, devices, objects, and property values. It reads and/or writes
a tab-delimited property values text file of the values that it has received.
"""
import sys
import time
import json
from collections import defaultdict, OrderedDict
from bacpypes.debugging import bacpypes_debugging, ModuleLogger
from bacpypes.consolelogging import ConfigArgumentParser
from bacpypes.consolecmd import ConsoleCmd
from bacpypes.pdu import Address, LocalBroadcast, GlobalBroadcast
from bacpypes.comm import bind
from bacpypes.core import run, deferred, enable_sleeping
from bacpypes.task import FunctionTask
from bacpypes.iocb import IOCB, IOQController
# application layer
from bacpypes.primitivedata import Unsigned, ObjectIdentifier
from bacpypes.constructeddata import Array, ArrayOf
from bacpypes.basetypes import PropertyIdentifier, ServicesSupported
from bacpypes.object import get_object_class, get_datatype, DeviceObject
from bacpypes.app import ApplicationIOController
from bacpypes.appservice import StateMachineAccessPoint, ApplicationServiceAccessPoint
from bacpypes.apdu import (
WhoIsRequest,
IAmRequest,
ReadPropertyRequest,
ReadPropertyACK,
ReadPropertyMultipleRequest,
PropertyReference,
ReadAccessSpecification,
ReadPropertyMultipleACK,
)
# network layer
from bacpypes.netservice import NetworkServiceAccessPoint, NetworkServiceElement
from bacpypes.npdu import (
WhoIsRouterToNetwork,
IAmRouterToNetwork,
InitializeRoutingTable,
InitializeRoutingTableAck,
WhatIsNetworkNumber,
NetworkNumberIs,
)
# IPv4 virtual link layer
from bacpypes.bvllservice import BIPSimple, AnnexJCodec, UDPMultiplexer
# basic objects
from bacpypes.local.device import LocalDeviceObject
# basic services
from bacpypes.service.device import WhoIsIAmServices
from bacpypes.service.object import ReadWritePropertyServices
# some debugging
_debug = 0
_log = ModuleLogger(globals())
# globals
args = None
this_device = None
this_application = None
snapshot = None
# device information
device_profile = defaultdict(DeviceObject)
# print statements just for interactive
interactive = sys.stdin.isatty()
# lists of things to do
network_path_to_do_list = None
who_is_to_do_list = None
application_to_do_list = None
#
# Snapshot
#
@bacpypes_debugging
class Snapshot:
def __init__(self):
if _debug:
Snapshot._debug("__init__")
# empty database
self.data = {}
def read_file(self, filename):
if _debug:
Snapshot._debug("read_file %r", filename)
# empty database
self.data = {}
try:
with open(filename) as infile:
lines = infile.readlines()
for line in lines:
devid, objid, propid, version, value = line[:-1].split("\t")
devid = int(devid)
version = int(version)
key = (devid, objid, propid)
self.data[key] = (version, value)
except IOError:
if _debug:
Snapshot._debug(" - file not found")
pass
def write_file(self, filename):
if _debug:
Snapshot._debug("write_file %r", filename)
data = list(k + v for k, v in self.data.items())
data.sort()
with open(filename, "w") as outfile:
for row in data:
outfile.write("\t".join(str(x) for x in row) + "\n")
def upsert(self, devid, objid, propid, value):
if _debug:
Snapshot._debug("upsert %r %r %r %r", devid, objid, propid, value)
key = (devid, objid, propid)
if key not in self.data:
if _debug:
Snapshot._debug(" - new key")
self.data[key] = (1, value)
else:
version, old_value = self.data[key]
if value != old_value:
if _debug:
Snapshot._debug(" - new value")
self.data[key] = (version + 1, value)
def get_value(self, devid, objid, propid):
if _debug:
Snapshot._debug("get_value %r %r %r", devid, objid, propid)
key = (devid, objid, propid)
if key not in self.data:
return None
else:
return self.data[key][1]
#
# ToDoItem
#
@bacpypes_debugging
class ToDoItem:
def __init__(self, _thread=None, _delay=None):
if _debug:
ToDoItem._debug("__init__")
# basic status information
self._completed = False
# may depend on another item to complete, may have a delay
self._thread = _thread
self._delay = _delay
def prepare(self):
if _debug:
ToDoItem._debug("prepare")
raise NotImplementedError
def complete(self, iocb):
if _debug:
ToDoItem._debug("complete %r", iocb)
self._completed = True
#
# ToDoList
#
@bacpypes_debugging
class ToDoList:
def __init__(self, controller, active_limit=1):
if _debug:
ToDoList._debug("__init__")
# save a reference to the controller for workers
self.controller = controller
# limit to the number of active workers
self.active_limit = active_limit
# no workers, nothing active
self.pending = []
self.active = set()
# launch already deferred
self.launch_deferred = False
def append(self, item):
if _debug:
ToDoList._debug("append %r", item)
# add the item to the list of pending items
self.pending.append(item)
# if an item can be started, schedule to launch it
if len(self.active) < self.active_limit and not self.launch_deferred:
if _debug:
ToDoList._debug(" - will launch")
self.launch_deferred = True
deferred(self.launch)
def launch(self):
if _debug:
ToDoList._debug("launch")
# find some workers and launch them
while self.pending and (len(self.active) < self.active_limit):
# look for the next to_do_item that can be started
for i, item in enumerate(self.pending):
if not item._thread:
break
if item._thread._completed:
break
else:
if _debug:
ToDoList._debug(" - waiting")
break
if _debug:
ToDoList._debug(" - item: %r", item)
# remove it from the pending list, add it to active
del self.pending[i]
self.active.add(item)
# prepare it and capture the IOCB
iocb = item.prepare()
if _debug:
ToDoList._debug(" - iocb: %r", iocb)
# break the reference to the completed to_do_item
item._thread = None
iocb._to_do_item = item
# add our completion routine
iocb.add_callback(self.complete)
# submit it to our controller
self.controller.request_io(iocb)
# clear the deferred flag
self.launch_deferred = False
if _debug:
ToDoList._debug(" - done launching")
# check for idle
if (not self.active) and (not self.pending):
self.idle()
def complete(self, iocb):
if _debug:
ToDoList._debug("complete %r", iocb)
# extract the to_do_item
item = iocb._to_do_item
if _debug:
ToDoList._debug(" - item: %r", item)
# if the item has a delay, schedule to call it later
if item._delay:
task = FunctionTask(self._delay_complete, item, iocb)
task.install_task(delta=item._delay)
if _debug:
ToDoList._debug(" - task: %r", task)
else:
self._delay_complete(item, iocb)
def _delay_complete(self, item, iocb):
if _debug:
ToDoList._debug("_delay_complete %r %r", item, iocb)
# tell the item it completed, remove it from active
item.complete(iocb)
self.active.remove(item)
# find another to_do_item
if not self.launch_deferred:
if _debug:
ToDoList._debug(" - will launch")
self.launch_deferred = True
deferred(self.launch)
def idle(self):
if _debug:
ToDoList._debug("idle")
#
# DiscoverNetworkServiceElement
#
@bacpypes_debugging
class DiscoverNetworkServiceElement(NetworkServiceElement, IOQController):
def __init__(self):
if _debug:
DiscoverNetworkServiceElement._debug("__init__")
NetworkServiceElement.__init__(self)
IOQController.__init__(self)
def process_io(self, iocb):
if _debug:
DiscoverNetworkServiceElement._debug("process_io %r", iocb)
# this request is active
self.active_io(iocb)
# reference the service access point
sap = self.elementService
if _debug:
NetworkServiceElement._debug(" - sap: %r", sap)
# the iocb contains an NPDU, pass it along to the local adapter
self.request(sap.local_adapter, iocb.args[0])
def indication(self, adapter, npdu):
if _debug:
DiscoverNetworkServiceElement._debug("indication %r %r", adapter, npdu)
global network_path_to_do_list
if not self.active_iocb:
pass
elif isinstance(npdu, IAmRouterToNetwork):
if interactive:
print("{} router to {}".format(npdu.pduSource, npdu.iartnNetworkList))
# reference the request
request = self.active_iocb.args[0]
if isinstance(request, WhoIsRouterToNetwork):
if request.wirtnNetwork in npdu.iartnNetworkList:
self.complete_io(self.active_iocb, npdu.pduSource)
elif isinstance(npdu, InitializeRoutingTableAck):
if interactive:
print("{} routing table".format(npdu.pduSource))
for rte in npdu.irtaTable:
print(
" {} {} {}".format(rte.rtDNET, rte.rtPortID, rte.rtPortInfo)
)
# reference the request
request = self.active_iocb.args[0]
if isinstance(request, InitializeRoutingTable):
if npdu.pduSource == request.pduDestination:
self.complete_io(self.active_iocb, npdu.irtaTable)
elif isinstance(npdu, NetworkNumberIs):
if interactive:
print("{} network number is {}".format(npdu.pduSource, npdu.nniNet))
# reference the request
request = self.active_iocb.args[0]
if isinstance(request, WhatIsNetworkNumber):
self.complete_io(self.active_iocb, npdu.nniNet)
# forward it along
NetworkServiceElement.indication(self, adapter, npdu)
#
# DiscoverApplication
#
@bacpypes_debugging
class DiscoverApplication(
ApplicationIOController, WhoIsIAmServices, ReadWritePropertyServices
):
def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None):
if _debug:
DiscoverApplication._debug(
"__init__ %r %r deviceInfoCache=%r aseID=%r",
localDevice,
localAddress,
deviceInfoCache,
aseID,
)
ApplicationIOController.__init__(
self, localDevice, localAddress, deviceInfoCache, aseID=aseID
)
# local address might be useful for subclasses
if isinstance(localAddress, Address):
self.localAddress = localAddress
else:
self.localAddress = Address(localAddress)
# include a application decoder
self.asap = ApplicationServiceAccessPoint()
# pass the device object to the state machine access point so it
# can know if it should support segmentation
self.smap = StateMachineAccessPoint(localDevice)
# the segmentation state machines need access to the same device
# information cache as the application
self.smap.deviceInfoCache = self.deviceInfoCache
# a network service access point will be needed
self.nsap = NetworkServiceAccessPoint()
# give the NSAP a generic network layer service element
self.nse = DiscoverNetworkServiceElement()
bind(self.nse, self.nsap)
# bind the top layers
bind(self, self.asap, self.smap, self.nsap)
# create a generic BIP stack, bound to the Annex J server
# on the UDP multiplexer
self.bip = BIPSimple()
self.annexj = AnnexJCodec()
self.mux = UDPMultiplexer(self.localAddress)
# bind the bottom layers
bind(self.bip, self.annexj, self.mux.annexJ)
# bind the BIP stack to the network, no network number
self.nsap.bind(self.bip, address=self.localAddress)
def close_socket(self):
if _debug:
DiscoverApplication._debug("close_socket")
# pass to the multiplexer, then down to the sockets
self.mux.close_socket()
def do_IAmRequest(self, apdu):
if _debug:
DiscoverApplication._debug("do_IAmRequest %r", apdu)
global who_is_to_do_list
# pass it along to line up with active requests
who_is_to_do_list.received_i_am(apdu)
#
# WhoIsToDo
#
@bacpypes_debugging
class WhoIsToDo(ToDoItem):
def __init__(self, addr, lolimit, hilimit):
if _debug:
WhoIsToDo._debug("__init__ %r %r %r", addr, lolimit, hilimit)
ToDoItem.__init__(self, _delay=3.0)
# save the parameters
self.addr = addr
self.lolimit = lolimit
self.hilimit = hilimit
# hold on to the request and make a placeholder for responses
self.request = None
self.i_am_responses = []
# give it to the list
who_is_to_do_list.append(self)
def prepare(self):
if _debug:
WhoIsToDo._debug("prepare(%r %r %r)", self.addr, self.lolimit, self.hilimit)
# build a request
self.request = WhoIsRequest(
destination=self.addr,
deviceInstanceRangeLowLimit=self.lolimit,
deviceInstanceRangeHighLimit=self.hilimit,
)
if _debug:
WhoIsToDo._debug(" - request: %r", self.request)
# build an IOCB
iocb = IOCB(self.request)
if _debug:
WhoIsToDo._debug(" - iocb: %r", iocb)
return iocb
def complete(self, iocb):
if _debug:
WhoIsToDo._debug("complete %r", iocb)
# process the responses
for apdu in self.i_am_responses:
device_instance = apdu.iAmDeviceIdentifier[1]
# print out something
if interactive:
print("{} @ {}".format(device_instance, apdu.pduSource))
# update the snapshot database
snapshot.upsert(device_instance, "-", "address", str(apdu.pduSource))
snapshot.upsert(
device_instance,
"-",
"maxAPDULengthAccepted",
str(apdu.maxAPDULengthAccepted),
)
snapshot.upsert(
device_instance,
"-",
"segmentationSupported",
apdu.segmentationSupported,
)
# read stuff
ReadServicesSupported(device_instance)
ReadObjectList(device_instance)
# pass along
ToDoItem.complete(self, iocb)
#
# WhoIsToDoList
#
@bacpypes_debugging
class WhoIsToDoList(ToDoList):
def received_i_am(self, apdu):
if _debug:
WhoIsToDoList._debug("received_i_am %r", apdu)
# line it up with an active item
for item in self.active:
if _debug:
WhoIsToDoList._debug(" - item: %r", item)
# check the source against the request
if item.addr.addrType == Address.localBroadcastAddr:
if apdu.pduSource.addrType != Address.localStationAddr:
if _debug:
WhoIsToDoList._debug(" - not a local station")
continue
elif item.addr.addrType == Address.localStationAddr:
if apdu.pduSource != item.addr:
if _debug:
WhoIsToDoList._debug(" - not from station")
continue
elif item.addr.addrType == Address.remoteBroadcastAddr:
if apdu.pduSource.addrType != Address.remoteStationAddr:
if _debug:
WhoIsToDoList._debug(" - not a remote station")
continue
if apdu.pduSource.addrNet != item.addr.addrNet:
if _debug:
WhoIsToDoList._debug(" - not from remote net")
continue
elif item.addr.addrType == Address.remoteStationAddr:
if apdu.pduSource != item.addr:
if _debug:
WhoIsToDoList._debug(" - not correct remote station")
continue
# check the range restriction
device_instance = apdu.iAmDeviceIdentifier[1]
if (item.lolimit is not None) and (device_instance < item.lolimit):
if _debug:
WhoIsToDoList._debug(" - lo limit")
continue
if (item.hilimit is not None) and (device_instance > item.hilimit):
if _debug:
WhoIsToDoList._debug(" - hi limit")
continue
# debug in case something kicked it out
if _debug:
WhoIsToDoList._debug(" - passed")
# save this response
item.i_am_responses.append(apdu)
def idle(self):
if _debug:
WhoIsToDoList._debug("idle")
#
# ApplicationToDoList
#
@bacpypes_debugging
class ApplicationToDoList(ToDoList):
def __init__(self):
if _debug:
ApplicationToDoList._debug("__init__")
global this_application
ToDoList.__init__(self, this_application)
#
# ReadPropertyToDo
#
@bacpypes_debugging
class ReadPropertyToDo(ToDoItem):
def __init__(self, devid, objid, propid, index=None):
if _debug:
ReadPropertyToDo._debug(
"__init__ %r %r %r index=%r", devid, objid, propid, index
)
ToDoItem.__init__(self)
# save the parameters
self.devid = devid
self.objid = objid
self.propid = propid
self.index = index
# give it to the list
application_to_do_list.append(self)
def prepare(self):
if _debug:
ReadPropertyToDo._debug(
"prepare(%r %r %r)", self.devid, self.objid, self.propid
)
# map the devid identifier to an address from the database
addr = snapshot.get_value(self.devid, "-", "address")
if not addr:
raise ValueError("unknown device")
if _debug:
ReadPropertyToDo._debug(" - addr: %r", addr)
# build a request
request = ReadPropertyRequest(
destination=Address(addr),
objectIdentifier=self.objid,
propertyIdentifier=self.propid,
)
if self.index is not None:
request.propertyArrayIndex = self.index
if _debug:
ReadPropertyToDo._debug(" - request: %r", request)
# make an IOCB
iocb = IOCB(request)
if _debug:
ReadPropertyToDo._debug(" - iocb: %r", iocb)
return iocb
def complete(self, iocb):
if _debug:
ReadPropertyToDo._debug("complete %r", iocb)
# do something for error/reject/abort
if iocb.ioError:
if interactive:
print("{} error: {}".format(self.propid, iocb.ioError))
# do something more
self.returned_error(iocb.ioError)
# do something for success
elif iocb.ioResponse:
apdu = iocb.ioResponse
# should be an ack
if not isinstance(apdu, ReadPropertyACK):
if _debug:
ReadPropertyToDo._debug(" - not an ack")
return
# find the datatype
datatype = get_datatype(apdu.objectIdentifier[0], apdu.propertyIdentifier)
if _debug:
ReadPropertyToDo._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:
ReadPropertyToDo._debug(" - datatype: %r", datatype)
try:
value = apdu.propertyValue.cast_out(datatype)
if _debug:
ReadPropertyToDo._debug(" - value: %r", value)
# convert the value to a string
if hasattr(value, "dict_contents"):
dict_contents = value.dict_contents(as_class=OrderedDict)
str_value = json.dumps(dict_contents)
else:
str_value = str(value)
except Exception as err:
str_value = "!" + str(err)
if interactive:
print(str_value)
# make a pretty property identifier
str_prop = apdu.propertyIdentifier
if apdu.propertyArrayIndex is not None:
str_prop += "[{}]".format(apdu.propertyArrayIndex)
# save it in the snapshot
snapshot.upsert(
self.devid, "{}:{}".format(*apdu.objectIdentifier), str_prop, str_value
)
# do something more
self.returned_value(value)
# do something with nothing?
else:
if _debug:
ReadPropertyToDo._debug(" - ioError or ioResponse expected")
def returned_error(self, error):
if _debug:
ReadPropertyToDo._debug("returned_error %r", error)
def returned_value(self, value):
if _debug:
ReadPropertyToDo._debug("returned_value %r", value)
#
# ReadServicesSupported
#
@bacpypes_debugging
class ReadServicesSupported(ReadPropertyToDo):
def __init__(self, devid):
if _debug:
ReadServicesSupported._debug("__init__ %r", devid)
ReadPropertyToDo.__init__(
self, devid, ("device", devid), "protocolServicesSupported"
)
def returned_value(self, value):
if _debug:
ReadServicesSupported._debug("returned_value %r", value)
# build a value
services_supported = ServicesSupported(value)
print(
"{} supports rpm: {}".format(
self.devid, services_supported["readPropertyMultiple"]
)
)
# device profile
devobj = device_profile[self.devid]
devobj.protocolServicesSupported = services_supported
#
# ReadObjectList
#
@bacpypes_debugging
class ReadObjectList(ReadPropertyToDo):
def __init__(self, devid):
if _debug:
ReadObjectList._debug("__init__ %r", devid)
ReadPropertyToDo.__init__(self, devid, ("device", devid), "objectList")
def returned_error(self, error):
if _debug:
ReadObjectList._debug("returned_error %r", error)
# try reading the length of the list
ReadObjectListLen(self.devid)
def returned_value(self, value):
if _debug:
ReadObjectList._debug("returned_value %r", value)
# update the device profile
devobj = device_profile[self.devid]
devobj.objectList = ArrayOf(ObjectIdentifier)(value)
# read the properties of the objects
for objid in value:
ReadObjectProperties(self.devid, objid)
#
# ReadPropertyMultipleToDo
#
@bacpypes_debugging
class ReadPropertyMultipleToDo(ToDoItem):
def __init__(self, devid, objid, proplist):
if _debug:
ReadPropertyMultipleToDo._debug("__init__ %r %r %r", devid, objid, proplist)
ToDoItem.__init__(self)
# save the parameters
self.devid = devid
self.objid = objid
self.proplist = proplist
# give it to the list
application_to_do_list.append(self)
def prepare(self):
if _debug:
ReadPropertyMultipleToDo._debug(
"prepare(%r %r %r)", self.devid, self.objid, self.proplist
)
# map the devid identifier to an address from the database
addr = snapshot.get_value(self.devid, "-", "address")
if not addr:
raise ValueError("unknown device")
if _debug:
ReadPropertyMultipleToDo._debug(" - addr: %r", addr)
prop_reference_list = [
PropertyReference(propertyIdentifier=propid) for propid in self.proplist
]
# build a read access specification
read_access_spec = ReadAccessSpecification(
objectIdentifier=self.objid, listOfPropertyReferences=prop_reference_list
)
# build the request
request = ReadPropertyMultipleRequest(
destination=Address(addr), listOfReadAccessSpecs=[read_access_spec]
)
if _debug:
ReadPropertyMultipleToDo._debug(" - request: %r", request)
# make an IOCB
iocb = IOCB(request)
if _debug:
ReadPropertyMultipleToDo._debug(" - iocb: %r", iocb)
return iocb
def complete(self, iocb):
if _debug:
ReadPropertyMultipleToDo._debug("complete %r", iocb)
# do something for error/reject/abort
if iocb.ioError:
if interactive:
print(str(iocb.ioError))
# do something more
self.returned_error(iocb.ioError)
# do something for success
elif iocb.ioResponse:
apdu = iocb.ioResponse
# should be an ack
if not isinstance(apdu, ReadPropertyMultipleACK):
if _debug:
ReadPropertyMultipleToDo._debug(" - not an ack")
return
# loop through the results
for result in apdu.listOfReadAccessResults:
# here is the object identifier
objectIdentifier = result.objectIdentifier
if _debug:
ReadPropertyMultipleToDo._debug(
" - objectIdentifier: %r", objectIdentifier
)
# now come the property values per object
for element in result.listOfResults:
# get the property and array index
propertyIdentifier = element.propertyIdentifier
if _debug:
ReadPropertyMultipleToDo._debug(
" - propertyIdentifier: %r", propertyIdentifier
)
propertyArrayIndex = element.propertyArrayIndex
if _debug:
ReadPropertyMultipleToDo._debug(
" - propertyArrayIndex: %r", propertyArrayIndex
)
# here is the read result
readResult = element.readResult
property_label = str(propertyIdentifier)
if propertyArrayIndex is not None:
property_label += "[" + str(propertyArrayIndex) + "]"
# check for an error
if readResult.propertyAccessError is not None:
if interactive:
print(
"{} ! {}".format(
property_label, readResult.propertyAccessError
)
)
else:
# here is the value
propertyValue = readResult.propertyValue
# find the datatype
datatype = get_datatype(objectIdentifier[0], propertyIdentifier)
if _debug:
ReadPropertyMultipleToDo._debug(
" - datatype: %r", datatype
)
if not datatype:
str_value = "?"
else:
# special case for array parts, others are managed by cast_out
if issubclass(datatype, Array) and (
propertyArrayIndex is not None
):
if propertyArrayIndex == 0:
datatype = Unsigned
else:
datatype = datatype.subtype
if _debug:
ReadPropertyMultipleToDo._debug(
" - datatype: %r", datatype
)
try:
value = propertyValue.cast_out(datatype)
if _debug:
ReadPropertyMultipleToDo._debug(
" - value: %r", value
)
# convert the value to a string
if hasattr(value, "dict_contents"):
dict_contents = value.dict_contents(
as_class=OrderedDict
)
str_value = json.dumps(dict_contents)
else:
str_value = str(value)
except Exception as err:
str_value = "!" + str(err)
if interactive:
print("{}: {}".format(propertyIdentifier, str_value))
# save it in the snapshot
snapshot.upsert(
self.devid,
"{}:{}".format(*objectIdentifier),
str(propertyIdentifier),
str_value,
)
# do something with nothing?
else:
if _debug:
ReadPropertyMultipleToDo._debug(" - ioError or ioResponse expected")
def returned_error(self, error):
if _debug:
ReadPropertyMultipleToDo._debug("returned_error %r", error)
def returned_value(self, value):
if _debug:
ReadPropertyMultipleToDo._debug("returned_value %r", value)
#
# ReadObjectListLen
#
@bacpypes_debugging
class ReadObjectListLen(ReadPropertyToDo):
def __init__(self, devid):
if _debug:
ReadObjectListLen._debug("__init__ %r", devid)
ReadPropertyToDo.__init__(self, devid, ("device", devid), "objectList", 0)
def returned_error(self, error):
if _debug:
ReadObjectListLen._debug("returned_error %r", error)
def returned_value(self, value):
if _debug:
ReadObjectListLen._debug("returned_value %r", value)
# start with an empty list
devobj = device_profile[self.devid]
devobj.objectList = ArrayOf(ObjectIdentifier)()
# read each of the individual items
for i in range(1, value + 1):
ReadObjectListElement(self.devid, i)
#
# ReadObjectListElement
#
@bacpypes_debugging
class ReadObjectListElement(ReadPropertyToDo):
def __init__(self, devid, indx):
if _debug:
ReadObjectListElement._debug("__init__ %r", devid, indx)
ReadPropertyToDo.__init__(self, devid, ("device", devid), "objectList", indx)
def returned_error(self, error):
if _debug:
ReadObjectListElement._debug("returned_error %r", error)
def returned_value(self, value):
if _debug:
ReadObjectListElement._debug("returned_value %r", value)
# update the list
devobj = device_profile[self.devid]
devobj.objectList.append(value)
# read the properties of the object
ReadObjectProperties(self.devid, value)
#
# ReadObjectProperties
#
@bacpypes_debugging
def ReadObjectProperties(devid, objid):
if _debug:
ReadObjectProperties._debug("ReadObjectProperties %r %r", devid, objid)
# get the profile, it contains the protocol services supported
devobj = device_profile[devid]
supports_rpm = devobj.protocolServicesSupported["readPropertyMultiple"]
if _debug:
ReadObjectProperties._debug(" - supports rpm: %r", supports_rpm)
# read all the properties at once if it's an option
if supports_rpm:
ReadPropertyMultipleToDo(devid, objid, ["all"])
else:
ReadObjectPropertyList(devid, objid)
#
# ReadObjectPropertyList
#
@bacpypes_debugging
class ReadObjectPropertyList(ReadPropertyToDo):
def __init__(self, devid, objid):
if _debug:
ReadObjectPropertyList._debug("__init__ %r", devid)
ReadPropertyToDo.__init__(self, devid, objid, "propertyList")
def returned_error(self, error):
if _debug:
ReadObjectPropertyList._debug("returned_error %r", error)
# get the class
object_class = get_object_class(self.objid[0])
if _debug:
ReadObjectPropertyList._debug(" - object_class: %r", object_class)
# get a list of properties, including optional ones
object_properties = list(object_class._properties.keys())
if _debug:
ReadObjectPropertyList._debug(
" - object_properties: %r", object_properties
)
# dont bother reading the property list, it already failed
if "propertyList" in object_properties:
object_properties.remove("propertyList")
# try to read all the properties
for propid in object_properties:
ReadPropertyToDo(self.devid, self.objid, propid)
def returned_value(self, value):
if _debug:
ReadObjectPropertyList._debug("returned_value %r", value)
# add the other properties that are always present but not
# returned in property list
value.extend(("objectName", "objectType", "objectIdentifier"))
# read each of the individual properties
for propid in value:
ReadPropertyToDo(self.devid, self.objid, propid)
#
# DiscoverConsoleCmd
#
@bacpypes_debugging
class DiscoverConsoleCmd(ConsoleCmd):
def do_sleep(self, args):
"""
sleep <secs>
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_sleep %r", args)
time.sleep(float(args[0]))
def do_wirtn(self, args):
"""
wirtn [ <addr> ] [ <net> ]
Send a Who-Is-Router-To-Network message. If <addr> is not specified
the message is locally broadcast.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_wirtn %r", args)
# 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:
print("invalid arguments")
return
# give it to the network service element
this_application.nse.request(this_application.nsap.local_adapter, request)
# sleep for responses
time.sleep(3.0)
def do_irt(self, args):
"""
irt <addr>
Send an empty Initialize-Routing-Table message to an address, a router
will return an acknowledgement with its routing table configuration.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_irt %r", args)
# build a request
try:
request = InitializeRoutingTable()
request.pduDestination = Address(args[0])
except:
print("invalid arguments")
return
# give it to the network service element
this_application.nse.request(this_application.nsap.local_adapter, request)
def do_winn(self, args):
"""
winn [ <addr> ]
Send a What-Is-Network-Number message. If the address is unspecified
the message is locally broadcast.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_winn %r", args)
# build a request
try:
request = WhatIsNetworkNumber()
if len(args) > 0:
request.pduDestination = Address(args[0])
else:
request.pduDestination = LocalBroadcast()
except:
print("invalid arguments")
return
# give it to the network service element
this_application.nse.request(this_application.nsap.local_adapter, request)
# sleep for responses
time.sleep(3.0)
def do_whois(self, args):
"""
whois [ <addr> ] [ <lolimit> <hilimit> ]
Send a Who-Is Request and wait 3 seconds for the I-Am "responses" to
be returned.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_whois %r", args)
try:
# parse parameters
if (len(args) == 1) or (len(args) == 3):
addr = Address(args[0])
del args[0]
else:
addr = GlobalBroadcast()
if len(args) == 2:
lolimit = int(args[0])
hilimit = int(args[1])
else:
lolimit = hilimit = None
# make an item
item = WhoIsToDo(addr, lolimit, hilimit)
if _debug:
DiscoverConsoleCmd._debug(" - item: %r", item)
except Exception as err:
DiscoverConsoleCmd._exception("exception: %r", err)
def do_iam(self, args):
"""
iam [ <addr> ]
Send an I-Am request. If the address is unspecified the message is
locally broadcast.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_iam %r", args)
try:
# build a request
request = IAmRequest()
if len(args) == 1:
request.pduDestination = Address(args[0])
else:
request.pduDestination = GlobalBroadcast()
# set the parameters from the device object
request.iAmDeviceIdentifier = this_device.objectIdentifier
request.maxAPDULengthAccepted = this_device.maxApduLengthAccepted
request.segmentationSupported = this_device.segmentationSupported
request.vendorID = this_device.vendorIdentifier
if _debug:
DiscoverConsoleCmd._debug(" - request: %r", request)
# make an IOCB
iocb = IOCB(request)
if _debug:
DiscoverConsoleCmd._debug(" - iocb: %r", iocb)
# give it to the application
this_application.request_io(iocb)
except Exception as err:
DiscoverConsoleCmd._exception("exception: %r", err)
def do_rp(self, args):
"""
rp <devid> <objid> <prop> [ <indx> ]
Send a Read-Property request to a device identified by its device
identifier.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_rp %r", args)
global application_to_do_list
try:
devid, objid, propid = args[:3]
devid = int(devid)
objid = ObjectIdentifier(objid).value
datatype = get_datatype(objid[0], propid)
if not datatype:
raise ValueError("invalid property for object type")
if len(args) == 4:
index = int(args[3])
else:
index = None
# make something to do
ReadPropertyToDo(devid, objid, propid, index)
except Exception as error:
DiscoverConsoleCmd._exception("exception: %r", error)
def do_rpm(self, args):
"""
rpm <devid> ( <objid> ( <prop> [ <indx> ] )... )...
Send a Read-Property-Multiple request to a device identified by its
device identifier.
"""
args = args.split()
if _debug:
DiscoverConsoleCmd._debug("do_rpm %r", args)
try:
i = 0
devid = int(args[i])
if _debug:
DiscoverConsoleCmd._debug(" - devid: %r", devid)
i += 1
# map the devid identifier to an address from the database
addr = snapshot.get_value(devid, "-", "address")
if not addr:
raise ValueError("unknown device")
if _debug:
DiscoverConsoleCmd._debug(" - addr: %r", addr)
read_access_spec_list = []
while i < len(args):
obj_id = ObjectIdentifier(args[i]).value
if _debug:
DiscoverConsoleCmd._debug(" - obj_id: %r", obj_id)
i += 1
prop_reference_list = []
while i < len(args):
prop_id = args[i]
if _debug:
DiscoverConsoleCmd._debug(" - prop_id: %r", prop_id)
if prop_id not in PropertyIdentifier.enumerations:
break
i += 1
if prop_id in ("all", "required", "optional"):
pass
else:
datatype = get_datatype(obj_id[0], prop_id)
if not datatype:
raise ValueError("invalid property for object type")
# build a property reference
prop_reference = PropertyReference(propertyIdentifier=prop_id)
# check for an array index
if (i < len(args)) and args[i].isdigit():
prop_reference.propertyArrayIndex = int(args[i])
i += 1
# add it to the list
prop_reference_list.append(prop_reference)
# check for at least one property
if not prop_reference_list:
raise ValueError("provide at least one property")
# build a read access specification
read_access_spec = ReadAccessSpecification(
objectIdentifier=obj_id,
listOfPropertyReferences=prop_reference_list,
)
# add it to the list
read_access_spec_list.append(read_access_spec)
# check for at least one
if not read_access_spec_list:
raise RuntimeError("at least one read access specification required")
# build the request
request = ReadPropertyMultipleRequest(
listOfReadAccessSpecs=read_access_spec_list
)
request.pduDestination = Address(addr)
if _debug:
DiscoverConsoleCmd._debug(" - request: %r", request)
# make an IOCB
iocb = IOCB(request)
if _debug:
DiscoverConsoleCmd._debug(" - iocb: %r", iocb)
# give it to the application
this_application.request_io(iocb)
# wait for it to complete
iocb.wait()
# do something for success
if iocb.ioResponse:
apdu = iocb.ioResponse
# should be an ack
if not isinstance(apdu, ReadPropertyMultipleACK):
if _debug:
DiscoverConsoleCmd._debug(" - not an ack")
return
# loop through the results
for result in apdu.listOfReadAccessResults:
# here is the object identifier
objectIdentifier = result.objectIdentifier
if _debug:
DiscoverConsoleCmd._debug(
" - objectIdentifier: %r", objectIdentifier
)
# now come the property values per object
for element in result.listOfResults:
# get the property and array index
propertyIdentifier = element.propertyIdentifier
if _debug:
DiscoverConsoleCmd._debug(
" - propertyIdentifier: %r", propertyIdentifier
)
propertyArrayIndex = element.propertyArrayIndex
if _debug:
DiscoverConsoleCmd._debug(
" - propertyArrayIndex: %r", propertyArrayIndex
)
# here is the read result
readResult = element.readResult
property_label = str(propertyIdentifier)
if propertyArrayIndex is not None:
property_label += "[" + str(propertyArrayIndex) + "]"
# check for an error
if readResult.propertyAccessError is not None:
if interactive:
print(
"{} ! {}".format(
property_label, readResult.propertyAccessError
)
)
else:
# here is the value
propertyValue = readResult.propertyValue
# find the datatype
datatype = get_datatype(
objectIdentifier[0], propertyIdentifier
)
if _debug:
DiscoverConsoleCmd._debug(
" - datatype: %r", datatype
)
if not datatype:
str_value = "?"
else:
# special case for array parts, others are managed by cast_out
if issubclass(datatype, Array) and (
propertyArrayIndex is not None
):
if propertyArrayIndex == 0:
datatype = Unsigned
else:
datatype = datatype.subtype
if _debug:
DiscoverConsoleCmd._debug(
" - datatype: %r", datatype
)
try:
value = propertyValue.cast_out(datatype)
if _debug:
DiscoverConsoleCmd._debug(
" - value: %r", value
)
# convert the value to a string
if hasattr(value, "dict_contents"):
dict_contents = value.dict_contents(
as_class=OrderedDict
)
str_value = json.dumps(dict_contents)
else:
str_value = str(value)
except Exception as err:
str_value = "!" + str(err)
if interactive:
print("{}: {}".format(property_label, str_value))
# save it in the snapshot
snapshot.upsert(
devid,
"{}:{}".format(*objectIdentifier),
str(propertyIdentifier),
str_value,
)
# do something for error/reject/abort
if iocb.ioError:
if interactive:
print(str(iocb.ioError))
except Exception as error:
DiscoverConsoleCmd._exception("exception: %r", error)
#
# __main__
#
def main():
global args, this_device, this_application, snapshot, who_is_to_do_list, application_to_do_list
# parse the command line arguments
parser = ConfigArgumentParser(description=__doc__)
# input file for exiting configuration
parser.add_argument("infile", default="-", help="input file")
# output file for discovered configuration
parser.add_argument(
"outfile", nargs="?", default="-unspecified-", help="output file"
)
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 = DiscoverApplication(this_device, args.ini.address)
if _debug:
_log.debug(" - this_application: %r", this_application)
# make a snapshot 'database'
snapshot = Snapshot()
# read in an existing snapshot
if args.infile != "-":
snapshot.read_file(args.infile)
# special lists
# network_path_to_do_list = NetworkPathToDoList(this_application.nse)
who_is_to_do_list = WhoIsToDoList(this_application)
application_to_do_list = ApplicationToDoList()
# make a console
this_console = DiscoverConsoleCmd()
_log.debug(" - this_console: %r", this_console)
# enable sleeping will help with threads
enable_sleeping()
_log.debug("running")
run()
_log.debug("fini")
# write out the snapshot, outfile defaults to infile if not specified
if args.outfile == "-unspecified-":
args.outfile = args.infile
if args.outfile != "-":
snapshot.write_file(args.outfile)
if __name__ == "__main__":
main()