[Feature-7753] Addon some specific bacnet object type to IGN Bacnet driver process?

@Kevin.Herron
Currently, we are revamp a old SCADA with an Ignition. Communications to Siemens APIs being done in Bacnet, it turns out that certain bacnet objects are specific to Siemens.
After performing a wireshark of the communication, the specific objects are composed only of standard/primitive type.
Is it possible, by means of a specific module, to complete the bacnet objects managed by the Bacnet Ignition module?
Thus these objects would be interrogated like the others through the system.bacnet.readRaw(...) or a derivate function.
Any suggestions?

If this is possible, it would be via readRaw, modifying the example from system.bacnet.readRaw - Ignition User Manual 8.1 - Ignition Documentation like this:

from system.bacnet.enumerated import ObjectType
from system.bacnet.enumerated import PropertyIdentifier
     
ot = ObjectType(1234)
pid = PropertyIdentifier(5678)

where you have used Wireshark or whatever other means to figure out the type and property ids of the custom objects.

I don't know if the underlying BACnet library supports custom objects or not, so I think you'll be a pioneer.

2 Likes

It seems not possible to add some new ObjectType without changes in the Bacnet4J stack used by Ignition Bacnet Driver ?

The aim is to be able to define some new ObjectType (id > 59) with its ObjectProperties we could read/write with system.bacnet.ReadRaw/WriteRaw to cover Proprietary Devices with custom ObjectType.

A function like system.bacnet.addObjectType... would be very welcome !

we plan to use readRaw to read a large amount of data.
I suppose each call to readRaw will generate a request to the device ?

If it's the case, to be more efficient would it be possible to add a system script function:

  • to read multiple properties on the same object ?
  • or perhpas the same propertie on multiple objects ?
1 Like

perhaps @ggross has an experience feedback on this topic ?

You are correct that each request via system.bacnet.readRaw/writeRaw would be handled as an individual request. The original goal of exposing the library was to allow for one offs on objects that we don't currently support but customers have a requirement to access. While we could potentially go down the route of reading/writing via custom functions for multiple items at once, I think we would want to understand the specific objects you are trying to work with and potentially enhance the existing driver so that the additional work of dealing with the library isn't needed for any of our customers.

That being said, can you elaborate on the use case and the specific objects you are trying to get access to at all?

Garth

@ggross,
Objects we needs to read/write are mainly:

    1. Standard bacnet Object : Loop, Calendar, Schedule
    1. Vender specific bacnet Object (Siemens)

We have more than 50 devices per site * 4 sites.

For 1. we would really appreciate a new system script function to:

  • read multiple properties on the same object
  • perhpas the same propertie on multiple objects

For 2. A script fonction to be able to add the definition of the vendor specific object woiuld be perfect !

@mazeyrat,

Calendar and Schedule are objects that come up quite a bit as objects requested for support. Based on my discussions internally, we really can't do anything about adding direct support for these objects until we can support OPC UA 1.05 which is a much larger hurdle we need to clear. The intention is to come back and add those objects at some point in the future.

The other requested objects sounds like the general reasoning the Raw capabilities were added. I have created a feature ticket to potentially add a readRawMultiple scripting function to the system.bacnet capabilities.

Garth

1 Like

Thanks, I Hope readRawMultiple will be added as soon as possible ! It will be a great addition for us.

But for item 2, concerning objects with id above 59, vendor specific definition and not present in bacnet4J stack, but quite commonly used in BMS devices...

Did you see where I explained how to construct an ObjectType and PropertyIdentifier with custom id? Does this not work?

We will try next week with the siemens devices.
I thought it was necessary to make some changes in the driver to accept those custom objects with the readRaw function.
We will keep you updated.

I did some tests as Kevin said before and
ObjectType / PropertyType construction aren't allowed by the module. Indeed, the driver raises an exception after checking in the Enumerated classes:
"com.serotonin.bacnet4j.type.enumerated.ObjectType" for objects

"com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier" for properties

pid = PropertyIdentifier(3017)

ot = ObjectType(201)

INFO | jvm 1 | 2023/05/22 15:23:31 | File "function:runAction", line 37, in runAction
INFO | jvm 1 | 2023/05/22 15:23:31 | TypeError: com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier(): 1st arg can't be coerced to com.serotonin.bacnet4j.util.sero.ByteQueue
INFO | jvm 1 | 2023/05/22 15:23:31 |
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.Py.TypeError(Py.java:236)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyReflectedFunction.throwError(PyReflectedFunction.java:213)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyReflectedFunction.throwBadArgError(PyReflectedFunction.java:316)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyReflectedFunction.throwError(PyReflectedFunction.java:325)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyReflectedConstructor.call(PyReflectedConstructor.java:179)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyObject.call(PyObject.java:422)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyMethod.instancemethod___call__(PyMethod.java:237)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyMethod.call(PyMethod.java:228)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyMethod.call(PyMethod.java:223)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.Deriveds.dispatch__init__(Deriveds.java:20)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyObjectDerived.dispatch__init__(PyObjectDerived.java:1112)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyType.type___call__(PyType.java:2408)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyType.call(PyType.java:2389)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyObject.call(PyObject.java:461)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyObject.call(PyObject.java:465)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.pycode.pyx4269.runAction$1(function:runAction:65)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.pycode.pyx4269.call_function(function:runAction)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyTableCode.call(PyTableCode.java:173)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyFunction.function___call
(PyFunction.java:474)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyFunction.call(PyFunction.java:469)
INFO | jvm 1 | 2023/05/22 15:23:31 | at org.python.core.PyFunction.call(PyFunction.java:464)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:846)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:828)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:832)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1009)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:897)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invokeScript(ScriptFunctionHelper.java:141)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:180)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:74)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.action.ActionDecorator.runAction(ActionDecorator.java:18)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.action.SecuredAction.runAction(SecuredAction.java:44)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)
INFO | jvm 1 | 2023/05/22 15:23:31 | at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
INFO | jvm 1 | 2023/05/22 15:23:31 | at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
INFO | jvm 1 | 2023/05/22 15:23:31 | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
INFO | jvm 1 | 2023/05/22 15:23:31 | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
INFO | jvm 1 | 2023/05/22 15:23:31 | at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
INFO | jvm 1 | 2023/05/22 15:23:31 | at java.base/java.lang.Thread.run(Unknown Source)
INFO | jvm 1 | 2023/05/22 15:23:31 | Caused by: org.python.core.PyException: TypeError: com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier(): 1st arg can't be coerced to com.serotonin.bacnet4j.util.sero.ByteQueue
INFO | jvm 1 | 2023/05/22 15:23:31 | ... 43 common frames omitted

Oops, try this instead:

pid = PropertyIdentifier.forId(42)

Our goal here isn't that inductive automation handles all specific properties of all constructors but that there is a default implementation of Bacnet properties as shown by Yabe (a must have Bacnet tool: https://sourceforge.net /projects/yetanotherbacnetexplorer/).

Yabe reports properties according to the Primitive type although identified by a constructor-specific Id (unknown to Yabe). ex: id:3017 (specific id of the manufacturer) value:100 (real type value, indicating the PID adjustment slope).

In this way we can make a binding according to the necessary properties.

Thanks Kevin,
For readings it works fine. A specific bacnet property/object can be accessed through the readRaw query and using the 'forId' function of the system.bacnet.enumerated.ObjectType and system.bacnet.enumerated.PropertyIdentifier classes

from system.bacnet.enumerated import ObjectType
from system.bacnet.enumerated import PropertyIdentifier

ot = ObjectType.forId(201)
pid = PropertyIdentifier.forId(3011)

On the same model as readRaw implementing a "readPropertyMultiple" function would optimize read requests.

Regarding the writeRaw function, it seems that there is a pre-write check of the property type / object type although the supplied types / object identifiers are correct.

com.inductiveautomation.ignition.common.script.JythonExecException
Traceback (most recent call last):
File "function:runAction", line 32, in runAction
TypeError: invalid object type or property identifier

at org.python.core.Py.TypeError(Py.java:236)
at com.inductiveautomation.ignition.drivers.bacnet.scripting.BacnetScriptingFunctions.writeRaw(BacnetScriptingFunctions.kt:239)
at jdk.internal.reflect.GeneratedMethodAccessor2088.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.call(PyReflectedFunction.java:190)
at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.call(ScriptManager.java:552)
at org.python.core.PyObject.call(PyObject.java:400)
at org.python.pycode.pyx4393.runAction$1(function:runAction:36)
at org.python.pycode.pyx4393.call_function(function:runAction)
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
at org.python.core.PyFunction.function___call
(PyFunction.java:474)
at org.python.core.PyFunction.call(PyFunction.java:469)
at org.python.core.PyFunction.call(PyFunction.java:464)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:846)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:828)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:832)
at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1009)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:897)
at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invokeScript(ScriptFunctionHelper.java:141)
at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:180)
at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:74)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)
at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)
at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException
Traceback (most recent call last):
File "function:runAction", line 32, in runAction
TypeError: invalid object type or property identifier

... 34 more

Ignition v8.1.26 (b2023032308)
Java: Azul Systems, Inc. 11.0.18

@ggross Any idea why writeRaw doesn't allow custom object type id or custom property Identifier, like the readRaw script function ?

It's because we have to coerce the values provided to the scripting call into the corresponding types in the BACnet library, and we can't do this for object/properties we know nothing about.

Ok I see, would it be possible to add an optional parameter for the writeRaw function to provide the type of the property, in case of the property to write is vendor proprietary ?
That would help a lot !

The readRaw is fine with vendor proprietary ObjectType and Property despite id are unkown in the bacnet stack...I suppose in that case, the property type is deducted from the returned read value ?

ot = ObjectType.forId(201)
pid = PropertyIdentifier.forId(3011)

We'll have to modify the function so that if the value provided is a BACnet stack Encodable it skips the checks and coercion. You'll have to construct the value using the imports available under system.bacnet.enumerated, system.bacnet.primitive, system.bacnet.constructed and then instantiating the right type.

For read, the value returned is a BACnet stack Encodable, no coercion or prior knowledge of what's coming back is necessary.

1 Like

Awesome, we are ready to test a beta bacnet driver module with this change as soon as possible !