Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAC0 writing to a proprietary property of a proprietary object #450

Open
TomasPaier opened this issue Mar 25, 2024 · 8 comments
Open

BAC0 writing to a proprietary property of a proprietary object #450

TomasPaier opened this issue Mar 25, 2024 · 8 comments

Comments

@TomasPaier
Copy link

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!

@TomasPaier
Copy link
Author

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?

@ChristianTremblay
Copy link
Owner

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

@TomasPaier
Copy link
Author

TomasPaier commented Mar 29, 2024

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).

@ChristianTremblay
Copy link
Owner

Next implementation is drastically different

https://github.com/ChristianTremblay/BAC0/blob/async/BAC0/core/proprietary_objects/jci_5.py

@TomasPaier
Copy link
Author

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?

@ChristianTremblay
Copy link
Owner

ChristianTremblay commented Apr 13, 2024

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

@ChristianTremblay
Copy link
Owner

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

Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants