diff --git a/.gitignore b/.gitignore index af69914..346f722 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ # Editor backups *.py~ +.idea/ # C extensions *.so diff --git a/py27/bacpypes/app.py b/py27/bacpypes/app.py index bd35a64..5ddfe56 100755 --- a/py27/bacpypes/app.py +++ b/py27/bacpypes/app.py @@ -200,7 +200,9 @@ class Application(ApplicationServiceElement, Collector): # keep track of the local device if localDevice: + self.smap = StateMachineAccessPoint(localDevice) self.localDevice = localDevice + self.smap._localDevice = self.localDevice # bind the device object to this application localDevice._app = self @@ -224,6 +226,8 @@ class Application(ApplicationServiceElement, Collector): # use the provided cache or make a default one self.deviceInfoCache = deviceInfoCache or DeviceInfoCache() + if not self.smap.deviceInfoCache: + self.smap.deviceInfoCache = self.deviceInfoCache # controllers for managing confirmed requests as a client self.controllers = {} @@ -470,14 +474,6 @@ class BIPSimpleApplication(ApplicationIOController, WhoIsIAmServices, ReadWriteP # 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() @@ -526,14 +522,6 @@ class BIPForeignApplication(ApplicationIOController, WhoIsIAmServices, ReadWrite # 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() diff --git a/py27/bacpypes/appservice.py b/py27/bacpypes/appservice.py index f11edd1..906fe6c 100755 --- a/py27/bacpypes/appservice.py +++ b/py27/bacpypes/appservice.py @@ -1118,6 +1118,13 @@ class StateMachineAccessPoint(Client, ServiceAccessPoint): def confirmation(self, pdu): """Packets coming up the stack are APDU's.""" if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu) + if pdu.apduService != 17: + if _debug: StateMachineAccessPoint._debug("DeviceCommunicationRequest") + if getattr(self._localDevice, "_dcc_disable_all", None): + raise RuntimeError("All communications disabled") + if getattr(self._localDevice, "_dcc_disable", None): + if pdu.apduService != 8: + raise RuntimeError("All communications disabled except indication for Who-IS.") # make a more focused interpretation atype = apdu_types.get(pdu.apduType) @@ -1217,6 +1224,13 @@ class StateMachineAccessPoint(Client, ServiceAccessPoint): a new transaction as a client.""" if _debug: StateMachineAccessPoint._debug("sap_indication %r", apdu) + if apdu.apduService != 17: + if getattr(self._localDevice, "_dcc_disable_all", None): + raise RuntimeError("All communications disabled.") + if getattr(self._localDevice, "_dcc_disable", None): + if apdu.apduService != 0: + raise RuntimeError("All communications disabled except indication for Who-IS.") + if isinstance(apdu, UnconfirmedRequestPDU): # deliver to the device self.request(apdu) diff --git a/py27/bacpypes/service/device.py b/py27/bacpypes/service/device.py index 3defb47..67a4a1f 100644 --- a/py27/bacpypes/service/device.py +++ b/py27/bacpypes/service/device.py @@ -7,11 +7,16 @@ from ..pdu import GlobalBroadcast from ..primitivedata import Date, Time, ObjectIdentifier from ..constructeddata import ArrayOf -from ..apdu import WhoIsRequest, IAmRequest, IHaveRequest +from ..apdu import WhoIsRequest, IAmRequest, IHaveRequest, SimpleAckPDU, Error from ..errors import ExecutionError, InconsistentParameters, \ MissingRequiredParameter, ParameterOutOfRange from ..object import register_object_type, registered_object_types, \ Property, DeviceObject +from ..task import FunctionTask + +from ..basetypes import ErrorClass, ErrorCode + +from time import time as _time # some debugging _debug = 0 @@ -90,6 +95,11 @@ class LocalDeviceObject(DeviceObject): if attr not in kwargs: kwargs[attr] = value + for key, value in kwargs.items(): + if key.startswith("_"): + setattr(self, key, value) + del kwargs[key] + # check for registration if self.__class__ not in registered_object_types.values(): if 'vendorIdentifier' not in kwargs: @@ -125,6 +135,10 @@ class LocalDeviceObject(DeviceObject): if 'objectList' not in self.propertyList: self.propertyList.append('objectList') + def enable_communications(self): + self._dcc_disable_all = False + self._dcc_disable = False + # # Who-Is I-Am Services # @@ -361,3 +375,53 @@ class WhoHasIHaveServices(Capability): raise MissingRequiredParameter("objectName required") ### check to see if the application is looking for this object + + +@bacpypes_debugging +class DeviceCommunicationControlServices(Capability): + + def __init__(self): + if _debug: DeviceCommunicationControlServices._debug("__init__") + Capability.__init__(self) + self._enable_task = None + + def do_DeviceCommunicationControlRequest(self, apdu): + if _debug: DeviceCommunicationControlServices._debug("do_CommunicationControlRequest, %r", apdu) + + _response = SimpleAckPDU(context=apdu) + security_error = False + + if getattr(self.localDevice, "_dcc_password", None): + if not apdu.password or apdu.password != getattr(self.localDevice, "_dcc_password"): + _response = Error(errorClass=ErrorClass("security"), errorCode=ErrorCode("passwordFailure"), context=apdu) + security_error = True + + self.localDevice._app.smap.sap_confirmation(_response) + + if security_error: + return + + time_duration = apdu.timeDuration + start_time = None + + if apdu.enableDisable == "enable": + if self._enable_task: + self._enable_task.suspend_task() + self._enable_communications() + + elif apdu.enableDisable == "disable": + self.localDevice._dcc_disable_all = True + self.localDevice._dcc_disable = True + start_time = _time() + else: + self.localDevice._dcc_disable_all = False + self.localDevice._dcc_disable = True + start_time = _time() + + if time_duration: + self._enable_task = FunctionTask(self._enable_communications) + self._enable_task.install_task(start_time + time_duration) + + def _enable_communications(self): + if _debug: DeviceCommunicationControlServices._debug("enable_communications") + self.localDevice.enable_communications() diff --git a/py34/bacpypes/app.py b/py34/bacpypes/app.py index bd35a64..5ddfe56 100755 --- a/py34/bacpypes/app.py +++ b/py34/bacpypes/app.py @@ -200,7 +200,9 @@ class Application(ApplicationServiceElement, Collector): # keep track of the local device if localDevice: + self.smap = StateMachineAccessPoint(localDevice) self.localDevice = localDevice + self.smap._localDevice = self.localDevice # bind the device object to this application localDevice._app = self @@ -224,6 +226,8 @@ class Application(ApplicationServiceElement, Collector): # use the provided cache or make a default one self.deviceInfoCache = deviceInfoCache or DeviceInfoCache() + if not self.smap.deviceInfoCache: + self.smap.deviceInfoCache = self.deviceInfoCache # controllers for managing confirmed requests as a client self.controllers = {} @@ -470,14 +474,6 @@ class BIPSimpleApplication(ApplicationIOController, WhoIsIAmServices, ReadWriteP # 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() @@ -526,14 +522,6 @@ class BIPForeignApplication(ApplicationIOController, WhoIsIAmServices, ReadWrite # 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() diff --git a/py34/bacpypes/appservice.py b/py34/bacpypes/appservice.py index f11edd1..906fe6c 100755 --- a/py34/bacpypes/appservice.py +++ b/py34/bacpypes/appservice.py @@ -1118,6 +1118,13 @@ class StateMachineAccessPoint(Client, ServiceAccessPoint): def confirmation(self, pdu): """Packets coming up the stack are APDU's.""" if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu) + if pdu.apduService != 17: + if _debug: StateMachineAccessPoint._debug("DeviceCommunicationRequest") + if getattr(self._localDevice, "_dcc_disable_all", None): + raise RuntimeError("All communications disabled") + if getattr(self._localDevice, "_dcc_disable", None): + if pdu.apduService != 8: + raise RuntimeError("All communications disabled except indication for Who-IS.") # make a more focused interpretation atype = apdu_types.get(pdu.apduType) @@ -1217,6 +1224,13 @@ class StateMachineAccessPoint(Client, ServiceAccessPoint): a new transaction as a client.""" if _debug: StateMachineAccessPoint._debug("sap_indication %r", apdu) + if apdu.apduService != 17: + if getattr(self._localDevice, "_dcc_disable_all", None): + raise RuntimeError("All communications disabled.") + if getattr(self._localDevice, "_dcc_disable", None): + if apdu.apduService != 0: + raise RuntimeError("All communications disabled except indication for Who-IS.") + if isinstance(apdu, UnconfirmedRequestPDU): # deliver to the device self.request(apdu) diff --git a/py34/bacpypes/service/device.py b/py34/bacpypes/service/device.py index cc257ae..838dab1 100644 --- a/py34/bacpypes/service/device.py +++ b/py34/bacpypes/service/device.py @@ -7,11 +7,16 @@ from ..pdu import GlobalBroadcast from ..primitivedata import Date, Time, ObjectIdentifier from ..constructeddata import ArrayOf -from ..apdu import WhoIsRequest, IAmRequest, IHaveRequest +from ..apdu import WhoIsRequest, IAmRequest, IHaveRequest, SimpleAckPDU, Error from ..errors import ExecutionError, InconsistentParameters, \ MissingRequiredParameter, ParameterOutOfRange from ..object import register_object_type, registered_object_types, \ Property, DeviceObject +from ..task import FunctionTask + +from ..basetypes import ErrorClass, ErrorCode + +from time import time as _time # some debugging _debug = 0 @@ -90,6 +95,11 @@ class LocalDeviceObject(DeviceObject): if attr not in kwargs: kwargs[attr] = value + for key, value in kwargs.items(): + if key.startswith("_"): + setattr(self, key, value) + del kwargs[key] + # check for registration if self.__class__ not in registered_object_types.values(): if 'vendorIdentifier' not in kwargs: @@ -125,6 +135,9 @@ class LocalDeviceObject(DeviceObject): if 'objectList' not in self.propertyList: self.propertyList.append('objectList') + def enable_communications(self): + self._dcc_disable_all = False + self._dcc_disable = False # # Who-Is I-Am Services # @@ -337,3 +350,53 @@ class WhoHasIHaveServices(Capability): raise MissingRequiredParameter("objectName required") ### check to see if the application is looking for this object + + +@bacpypes_debugging +class DeviceCommunicationControlServices(Capability): + + def __init__(self): + if _debug: DeviceCommunicationControlServices._debug("__init__") + Capability.__init__(self) + self._enable_task = None + + def do_DeviceCommunicationControlRequest(self, apdu): + if _debug: DeviceCommunicationControlServices._debug("do_CommunicationControlRequest, %r", apdu) + + _response = SimpleAckPDU(context=apdu) + security_error = False + + if getattr(self.localDevice, "_dcc_password", None): + if not apdu.password or apdu.password != getattr(self.localDevice, "_dcc_password"): + _response = Error(errorClass=ErrorClass("security"), errorCode=ErrorCode("passwordFailure"), context=apdu) + security_error = True + + self.localDevice_app.smap.sap_confirmation(_response) + + if security_error: + return + + time_duration = apdu.timeDuration + start_time = None + + if apdu.enableDisable == "enable": + if self._enable_task: + self._enable_task.suspend_task() + self._enable_communications() + + elif apdu.enableDisable == "disable": + self.localDevice._dcc_disable_all = True + self.localDevice._dcc_disable = True + start_time = _time() + else: + self.localDevice._dcc_disable_all = False + self.localDevice._dcc_disable = True + start_time = _time() + + if time_duration: + self._enable_task = FunctionTask(self._enable_communications) + self._enable_task.install_task(start_time + time_duration) + + def _enable_communications(self): + if _debug: DeviceCommunicationControlServices._debug("enable_communications") + self.localDevice.enable_communications()