Closed ChristianTremblay closed 3 months ago
I saw the custom example which looks interesting. Even if, I think it is meant for local objects (served by bacpypes app).
I think it would be nice to have something similar to load an existing vendor (or create if not in cache) to specify proprietary objects and properties.
# extract from what I would do to extend JCI objects
class ProprietaryPropertyIdentifier(PropertyIdentifier):
"""
This is a list of the property identifiers that are used in custom object
types or are used in custom properties of standard types.
"""
# AV, AI, AO
FLOW_SP_EEPROM = 3113
Offset = 956
Offline = 913
SABusAddr = 3645
PeerToPeer = 748
P2P_ErrorStatus = 746
InputRangeLow = 1293
InputRangeHigh = 1294
OutputRangeLow = 1295
OutputRangeHigh = 1296
MIN_OUT_VALUE = 652
MAX_OUT_VALUE = 653
#polarity = polarity
stroketime = 3478
class AnalogInputObject(_AnalogInputObject):
Offset: Real
Offline: Boolean
SABusAddr: Unsigned
InputRangeLow: Real
InputRangeHigh: Real
OutputRangeLow: Real
OutputRangeHigh: Real
class AnalogValueObject(_AnalogValueObject):
FLOW_SP_EEPROM: Real
Offset: Real
Offline: Boolean
SABusAddr: Unsigned
PeerToPeer: Atomic
P2P_ErrorStatus: Enumerated
class AnalogOutputObejt(_AnalogOutputObject):
Offline: Boolean
SABusAddr: Unsigned
MIN_OUT_VALUE: Real
MAX_OUT_VALUE: Real
#polarity = polarity
stroketime: Real
I saw the custom example which looks interesting. Even if, I think it is meant for local objects (served by bacpypes app).
It's for both clients and servers. For clients the module should import from the "vanilla" classes like AnalogValueObject
from the bacpyes3.object
module. For servers it inherits from the "local" classes to get the default behavior. The sample client application happens to not create instances of the objects which is a bit of a dodge.
I think it would be nice to have something similar to load an existing vendor (or create if not in cache) to specify proprietary objects and properties.
If the vendor wanted to distribute a Python module for their definitions that would be awesome, and a bit surprising! It's more likely that they would publish a CSML document, which could then be translated into Python and RDF classes. If you know any existing CSML documents we could try, double check the licensing for public consumption and we'll work on that.
I'm working with this : https://github.com/ChristianTremblay/BAC0/blob/async/BAC0/core/proprietary_objects/jci_5.py It is based on a pdf I found in the past...on their public web site
>>> import asyncio
>>> import BAC0
>>> bacnet = BAC0.lite()
[08/09/24 23:58:51] INFO 2024-08-09 23:58:51,428 - INFO | Starting Asynchronous BAC0 version 2024 (Lite) notes.py:272
INFO 2024-08-09 23:58:51,434 - INFO | Using bacpypes3 version 0.0.98 notes.py:272
INFO 2024-08-09 23:58:51,436 - INFO | Use BAC0.log_level to adjust verbosity of the app. notes.py:272
INFO 2024-08-09 23:58:51,439 - INFO | Ex. BAC0.log_level('silence') or BAC0.log_level('error') notes.py:272
INFO 2024-08-09 23:58:51,896 - INFO | Using ip : 192.168.211.208/24 on port 47808 | broadcast : 192.168.211.255 Lite.py:168
[08/09/24 23:58:52] INFO 2024-08-09 23:58:52,187 - INFO | Using JSON Stored in user folder ~/.BAC0 notes.py:272
[08/09/24 23:58:52] INFO 2024-08-09 23:58:52,199 - INFO | Registered as BACnet/IP App notes.py:272
INFO 2024-08-09 23:58:52,207 - INFO | Device instance (id) : 3056381 notes.py:272
>>> cgm = BAC0.device('303:5', 5705, bacnet)
[08/09/24 23:59:13] INFO 2024-08-09 23:59:13,911 - INFO | Changing device state to DeviceDisconnected'> notes.py:272
>>> [08/09/24 23:59:14] INFO 2024-08-09 23:59:14,283 - INFO | Changing device state to RPMDeviceConnected'> notes.py:272
INFO 2024-08-09 23:59:14,472 - INFO | Device 5705:[CGM-7-005] found... building points list Device.py:537
[08/09/24 23:59:19] INFO 2024-08-09 23:59:19,865 - INFO | Points and trendlogs (if any) created notes.py:272
[08/09/24 23:59:19] INFO 2024-08-09 23:59:19,866 - INFO | Device defined for normal polling with a delay of 10sec Poll.py:179
INFO 2024-08-09 23:59:19,868 - INFO | Polling started, values read every 10 seconds notes.py:272
INFO 2024-08-09 23:59:19,869 - INFO | Device ready, use device_name.points and start interact with it Device.py:551
await cgm['DA-T'].update_bacnet_properties()
>>> await cgm['DA-T'].bacnet_properties
{
<PropertyIdentifier: 3407>: None,
<PropertyIdentifier: 722>: None,
<PropertyIdentifier: 3542>: None,
<PropertyIdentifier: 3543>: None,
<PropertyIdentifier: 3422>: None,
<PropertyIdentifier: 725>: None,
<PropertyIdentifier: 3164>: None,
<PropertyIdentifier: update-interval>: 400,
<PropertyIdentifier: 3544>: None,
<PropertyIdentifier: 2179>: None,
<PropertyIdentifier: 1294>: None,
<PropertyIdentifier: 1293>: None,
<PropertyIdentifier: 956>: None,
<PropertyIdentifier: 1296>: None,
<PropertyIdentifier: 1295>: None,
<PropertyIdentifier: 2180>: None,
<PropertyIdentifier: 1433>: None,
<PropertyIdentifier: 518>: None,
<PropertyIdentifier: 519>: None,
<PropertyIdentifier: 3645>: None,
<PropertyIdentifier: present-value>: -62.288089752197266,
<PropertyIdentifier: out-of-service>: 0,
<PropertyIdentifier: reliability>: <Reliability: no-sensor>,
<PropertyIdentifier: status-flags>: <StatusFlags: fault>,
<PropertyIdentifier: min-pres-value>: -46.0,
<PropertyIdentifier: max-pres-value>: 121.0,
<PropertyIdentifier: 913>: None,
<PropertyIdentifier: units>: <EngineeringUnits: degrees-celsius>,
<PropertyIdentifier: 661>: None,
<PropertyIdentifier: cov-increment>: 0.15000000596046448,
<PropertyIdentifier: resolution>: 0.10000000149011612,
<PropertyIdentifier: device-type>: 'UI IN1',
<PropertyIdentifier: event-state>: <EventState: normal>,
<PropertyIdentifier: 3930>: None,
<PropertyIdentifier: 3807>: None,
<PropertyIdentifier: 569>: None,
<PropertyIdentifier: event-detection-enable>: 0,
<PropertyIdentifier: event-enable>: <EventTransitionBits: to-offnormal;to-fault;to-normal>,
<PropertyIdentifier: limit-enable>: <LimitEnable: low-limit-enable;high-limit-enable>,
<PropertyIdentifier: high-limit>: 70.0,
<PropertyIdentifier: low-limit>: 65.0,
<PropertyIdentifier: deadband>: 0.0,
<PropertyIdentifier: time-delay>: 0,
<PropertyIdentifier: notification-class>: 4194303,
<PropertyIdentifier: notify-type>: <NotifyType: alarm>,
<PropertyIdentifier: acked-transitions>: <EventTransitionBits: to-offnormal;to-fault;to-normal>,
<PropertyIdentifier: event-time-stamps>: [
<bacpypes3.basetypes.TimeStamp object at 0x00000277023C3B60>,
<bacpypes3.basetypes.TimeStamp object at 0x00000276FE326090>,
<bacpypes3.basetypes.TimeStamp object at 0x00000277021E4770>
],
<PropertyIdentifier: event-message-texts>: ['', '', ''],
<PropertyIdentifier: event-message-texts-config>: ['', '', ''],
<PropertyIdentifier: 536>: None,
<PropertyIdentifier: 32581>: None,
<PropertyIdentifier: 32623>: None,
<PropertyIdentifier: 4304>: None,
<PropertyIdentifier: 4305>: None,
<PropertyIdentifier: 4306>: None,
<PropertyIdentifier: 32527>: None,
<PropertyIdentifier: 2390>: None,
<PropertyIdentifier: object-name>: 'DA-T',
<PropertyIdentifier: description>: 'Discharge Air Temperature',
<PropertyIdentifier: 512>: None,
<PropertyIdentifier: 673>: None,
<PropertyIdentifier: 2197>: None,
<PropertyIdentifier: 908>: None,
<PropertyIdentifier: 1006>: None,
<PropertyIdentifier: object-type>: <ObjectType: analog-input>,
<PropertyIdentifier: object-identifier>: (<ObjectType: analog-input>, 287831)
}
>>> from BAC0.core.proprietary_objects import jci_5
>>> await cgm['DA-T'].update_bacnet_properties()
Traceback (most recent call last):
File "D:\0Programmes\Github\BAC0\BAC0\core\devices\Points.py", line 223, in update_bacnet_properties
res = await self.properties.device.properties.network.readMultiple(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\0Programmes\Github\BAC0\BAC0\core\io\Read.py", line 260, in readMultiple
response = await _app.read_property_multiple(address, parameter_list)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ctremblay\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\bacpypes3\service\object.py", line 623, in read_property_multiple
property_value = read_result.propertyValue.cast_out(datatype)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ctremblay\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\bacpypes3\constructeddata.py", line 1751, in cast_out
result = cls.decode(tag_list)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ctremblay\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\bacpypes3\primitivedata.py", line 1013, in decode
raise InvalidTag(f"unsigned application tag expected, got {tag.tag_number}")
bacpypes3.errors.InvalidTag: unsigned application tag expected, got 0
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.1520.0_x64__qbz5n2kfra8p0\Lib\concurrent\futures\_base.py", line 456, in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.1520.0_x64__qbz5n2kfra8p0\Lib\concurrent\futures\_base.py", line 401, in __get_result
raise self._exception
File "<console>", line 1, in <module>
File "D:\0Programmes\Github\BAC0\BAC0\core\devices\Points.py", line 239, in update_bacnet_properties
raise Exception(f"Problem reading : {self.properties.name} | {e}")
Exception: Problem reading : DA-T | unsigned application tag expected, got 0
>>>
It is related to this proprietary property which is not declared in the device.... and we really get NULL
}[4]
.... 1... = Tag Class: Context Specific Tag
0100 .... = Context Tag Number: 4
.... .111 = Named Tag: Closing Tag (7)
Property Identifier: (3645) Vendor Proprietary Value (3645)
Context Tag: 2, Length/Value/Type: 2
.... 1... = Tag Class: Context Specific Tag
0010 .... = Context Tag Number: 2
Length Value Type: 2
Property Identifier: Unknown (3645)
{[4]
.... 1... = Tag Class: Context Specific Tag
0100 .... = Context Tag Number: 4
.... .110 = Named Tag: Opening Tag (6)
(3645) Vendor Proprietary Value: NULL
Application Tag: Null, Length/Value/Type: 0
.... 0... = Tag Class: Application Tag
0000 .... = Application Tag Number: Null (0)
Length Value Type: 0
}[4]
As this makes reading "all" fails... maybe BACpypes could just return None if tag is 0 ? Or the proprietary properties definition could support 2 types ? (in this case...unsigned OR null(?))
Thanks for your help with that
The implementation works for me. Closing this
What is the best way to register proprietray objects for a specific vendor ?
Let say I have a manufacturer vendor id and the implementation details on their object. In bacpypes, there was
register_object_type
In bp3, I see a similar
register_object_class
but part of the VendorInfo class. Should I create a VendorInfo class ?Or should I share what I have so it's part of bp3 as those infos are public ?