BAC0 writing to a proprietary property of a proprietary object
Hello,
I'm looking for a way to write to a proprietary property of a proprietary object. (In this case, it's a Produal Room Controlloller https://www.produal.com/en/trc-3a.html , User Guide on https://produal-pim.rockon.io/rockon/api/v1/int/extmedia/openFile/01TGWJBKHN5NJ7WCUCEBD3WTAZWWYXGYKY ).
Basically, they implemented objects Config1 and Config2 as proprietary objects with id 128, each with a bunch of proprietary properties like setpoint and modes (simple Real and Unsigned properties). I'm able to read them all with Yabe, I'm able to read them with
SetPoint=bacnet.read('2001:2 @obj_128 0 @prop_40100')
But I'm wondering on how to WRITE to them. I'm using the BAC0 manual page https://bac0.readthedocs.io/en/latest/proprietary_objects.html which describes how to add proprietary properties on a standard bacnet object type (device), but I don't know how to define the "objectType" and "bacpypes_type" as they don't exist. I took a look in bacpypes\object.py definitions (here maybe I could define these objects) but I don't know where to define at least the object type Id (128 in this case).
So is there a way to define something like:
ProdualConfigType = {
"name": "ProdualConfigObject",
"vendor_id": 651,
"objectType": **_128_**,
"bacpypes_type": **_Proprietary_**,
"properties": {
"Setpoint": {"obj_id": 40100, "datatype": Real, "mutable": True},
"SetpointType": {"obj_id": 40101, "datatype": Unsigned, "mutable": True},
},
}
I'd need just use bacnet.write, I'm using the BAC0.lite version. Something like
bacnet.write('2001:2 @obj_128 0 @prop_40100 21.5', vendor_id=651)
but clearly that prop_40100 has to be defined somewhere as a Real. Am I missing anything?
Thank you, any help will be very welcomed!
I'have found a way -- please comment if it could be considered THE RIGHT way ;-) I've created a file produal.py with:
"""
Produal Proprietary Objects
"""
from bacpypes.object import (
Object,
)
from bacpypes.primitivedata import (
Atomic,
Boolean, # Signed,
CharacterString,
Date,
Enumerated,
Real,
Time,
Unsigned,
)
PConfigObject = {
"name": "PConfigObject",
"vendor_id": 651,
"objectType": 128,
"bacpypes_type": Object,
"properties": {
"NOMINAL_SETPOINT": {"obj_id": 40100, "datatype": Real, "mutable": True},
"SETPOINT_UNIT": {"obj_id": 40101, "datatype": Unsigned, "mutable": True},
"SENSOR3_SOURCE": {"obj_id": 40102, "datatype": Unsigned, "mutable": True},
"MINIMUM_SETPOINT": {"obj_id": 40104, "datatype": Real, "mutable": True},
"MAXIMUM_SETPOINT": {"obj_id": 40105, "datatype": Real, "mutable": True},
},
}
then I can do:
from BAC0.core.proprietary_objects.produal import PConfigObject
from BAC0.core.proprietary_objects.object import create_proprietary_object
create_proprietary_object(PConfigObject)
bacnet.write('2001:2 128 0 40100 22.5',vendor_id=651)
(Writing to device 2 on network 2001, object type 128 with instance 0, I'm writing to property 40100 a value 22.5.) The write is effectively executed.
Comments: the manual page https://bac0.readthedocs.io/en/latest/proprietary_objects.html need a small update -- instead of rows
from BAC0.core.proprietary_objects.object import create_proprietaryobject
create_proprietaryobject(**JCIDeviceObject)
should be
from BAC0.core.proprietary_objects.object import create_proprietary_object
create_proprietary_object(JCIDeviceObject)
(missing underscore in create_proprietary_object and "**" on the second row). Just for anyone looking for the solution...
Also, in the "How to implement" section, the comments should be more generic like:
...
# objectType : see bacpypes.object for reference (ex. 'device') or an integer of the object type id
# bacpypes_type : base class to instanciate (ex. BinaryValueObject) or Object for a new generic object type
...
including thus instructions on how to add a new generic object type definition
Other hints or comments?
Thanks for the great feedback and happy you made it work ! I'm actually working hard on the next version of BAC0 that will be async and use bacpypes3 so I'm still thinking about "what am I doing with the old stuff..."
I'm having some issues making proprietary objects and properties work in the new version, but stay tuned... I should be able to get to something
Thank you, Christian, for your precious work on BAC0! I confirm proprietary objects & properties are for us an important part for the BAC0 functionality (both reading & writing)...
By the way some other notes, with this occasion (tips for the new version):
currently, on every line of proprietary property definition:
"properties": {
"NameOfProprietaryProp": {"obj_id": 1110, "datatype": Boolean, "mutable": True},
},
the key name "obj_id" in the code is slightly misleading (it is in facts an Id of the Property, not of the object), so maybe in the new revision it could be better to rename it "prop_id".
As BACnet is usually used, the device is called with its BACnet Id (while network number & MAC remain in the behind and often are not even known/considered by a common user, although net & MAC ("physical address") in needed on the low-level. I've written this code:
def getPhysAddr(dev : str , bacnet , whoisTimeout = 2.0):
""" Get physical BACnet address
Returns physical BACnet address like (net#, MAC) for use in BAC0 calls using discoveredDevices
list if possible, or requesting who-is when needed
Parameters:
dev : str
string containing BACnet Id of requested device
bacnet : bacnet object connection from BAC0
bacnet object from BAC0.connect() or BAC0.lite() calls
whoisTimeout : float
timeout in seconds for the device to respond to who-is call
Returns:
phAddr : str
string containing '(net#, MAC)' of the device, or None if not found
"""
devs=bacnet.discoveredDevices
phAddr = None
# Let's try if the address is cached in discoveredDevices
if devs != None:
for each in list(devs):
if str(each[1]) == dev:
phAddr = each[0]
# It's not cached, so let's try who-is
if phAddr == None:
oldTime = perf_counter()
bacnet.whois(dev + ' ' + dev, global_broadcast=True)
while perf_counter() < (oldTime + whoisTimeout) and phAddr == None:
devs=bacnet.discoveredDevices
if devs != None:
for each in list(devs):
if str(each[1]) == dev:
phAddr = each[0]
return phAddr
which permits me to make calls like bacnet.read and bacnet.write using BACnet Id instead of "(net#, MAC)" string.
bacnet.write ( f"{getPhysAddr (bacnetId, bacnet)} {objType} {objInstance} {objProperty} {objValue} - {writePriority}" )
Not sure if it is the more efficient way to do so, but it works well. Maybe consider adding some functionality like this to the new BAC0 itself. (Feel free to use this my code if you think it could help).
Next implementation is drastically different
https://github.com/ChristianTremblay/BAC0/blob/async/BAC0/core/proprietary_objects/jci_5.py
Thank you, it seems very intelligible.
Please, keep in mind also cases like mine:
- a Proprietary Object (so, not an extension of a standardized type) containing Proprietary properties (how to define them in the new version? If I define something like
from bacpypes3.object import Object as _MyObject, (so, using as a base a generic Object) and then define its properties, will it work? This is a typical case used by many vendors. - In my case, the producer is using (on different products with the SAME vendor Id) the SAME Proprietary Property (with the same Id), ONCE as Unsigned and on other device type as Real. It is probably not permitted by the BACnet standard -- however, it did it. The only way I know to approach this is to extend the library with one definition OR with another. Do you think there is a better way to solve it?
I started playing with what I found on Produal... I'll need your help on that
produal_651.py
# Produal https://produal-pim.rockon.io/rockon/api/v1/int/extmedia/openFile/01TGWJBKHN5NJ7WCUCEBD3WTAZWWYXGYKY
"""
Custom Objects and Properties
"""
from bacpypes3.basetypes import PropertyIdentifier
from bacpypes3.debugging import ModuleLogger
from bacpypes3.local.object import _Object
from bacpypes3.primitivedata import (
ObjectType,
Real,
Unsigned,
)
# some debugging
_debug = 0
_log = ModuleLogger(globals())
# this vendor identifier reference is used when registering custom classes
_vendor_id = 651
_vendor_name = "SyxthSense Ltd"
class ProprietaryObjectType(ObjectType):
"""
This is a list of the object type enumerations for proprietary object types,
see Clause 23.4.1.
"""
CONFIG = 128
class Config(_Object):
"""
This is a proprietary object type.
"""
# object identifiers are interpreted from this customized subclass of the
# standard ObjectIdentifier that leverages the ProprietaryObjectType
# enumeration in the vendor information
objectIdentifier: ProprietaryObjectType.CONFIG
# all objects get the object-type property to be this value
objectType = ProprietaryObjectType("CONFIG")
# all objects have an object-name property, provided by the parent class
# with special hooks if an instance of this class is bound to an application
# objectName: CharacterString
# the property-list property of this object is provided by the getter
# method defined in the parent class and computed dynamically
# propertyList: ArrayOf(PropertyIdentifier)
NOMINAL_SETPOINT = Real
SETPOINT_UNIT = Unsigned
SENSOR3_SOURCE = Unsigned
MINIMUM_SETPOINT = Real
MAXIMUM_SETPOINT = Real
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.
"""
# this is a custom property using a standard datatype
NOMINAL_SETPOINT = 40100
SETPOINT_UNIT = 40101
SENSOR3_SOURCE = 40102
MINIMUM_SETPOINT = 40104
MAXIMUM_SETPOINT = 40105
produal_783.py
# Produal https://produal-pim.rockon.io/rockon/api/v1/int/extmedia/openFile/01TGWJBKHN5NJ7WCUCEBD3WTAZWWYXGYKY
"""
Custom Objects and Properties
"""
from bacpypes3.basetypes import PropertyIdentifier
from bacpypes3.debugging import ModuleLogger
from bacpypes3.local.object import _Object
from bacpypes3.primitivedata import (
ObjectType,
Real,
Unsigned,
)
# some debugging
_debug = 0
_log = ModuleLogger(globals())
# this vendor identifier reference is used when registering custom classes
_vendor_id = 783
_vendor_name = "Produal Oy"
class ProprietaryObjectType(ObjectType):
"""
This is a list of the object type enumerations for proprietary object types,
see Clause 23.4.1.
"""
CONFIG1 = 128
CONFIG2 = 128
class Config1(_Object):
"""
This is a proprietary object type.
"""
# object identifiers are interpreted from this customized subclass of the
# standard ObjectIdentifier that leverages the ProprietaryObjectType
# enumeration in the vendor information
objectIdentifier: ProprietaryObjectType.CONFIG1
# all objects get the object-type property to be this value
objectType = ProprietaryObjectType("CONFIG1")
# all objects have an object-name property, provided by the parent class
# with special hooks if an instance of this class is bound to an application
# objectName: CharacterString
# the property-list property of this object is provided by the getter
# method defined in the parent class and computed dynamically
# propertyList: ArrayOf(PropertyIdentifier)
TEMPSP_LL: Real
TEMPSP_HL: Real
NMBHTGSTAGES: Unsigned
class Config2(_Object):
"""
This is a proprietary object type.
"""
# object identifiers are interpreted from this customized subclass of the
# standard ObjectIdentifier that leverages the ProprietaryObjectType
# enumeration in the vendor information
objectIdentifier: ProprietaryObjectType.CONFIG2
# all objects get the object-type property to be this value
objectType = ProprietaryObjectType("CONFIG2")
# all objects have an object-name property, provided by the parent class
# with special hooks if an instance of this class is bound to an application
# objectName: CharacterString
# the property-list property of this object is provided by the getter
# method defined in the parent class and computed dynamically
# propertyList: ArrayOf(PropertyIdentifier)
LOCK_MODE: Unsigned
LOCK_PWD: Unsigned
BOOST_TRGT: Unsigned
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.
"""
# this is a custom property using a standard datatype
LOCK_MODE = 40155
LOCK_PWD = 40156
BOOST_TRGT = 40158
Looks like there was more than 1 vendor ID... not sure who is who... Without the product, it's hard to test
This issue had no activity for a long period of time. If this issue is still required, please update the status or else, it will be closed. Please note that an issue can be reopened if required.