Idea: Introduce a way to apply multiple profiles on item-channel-link, making it effectively a chain of transformations/callbacks executed when state/command is exchanged between channel and item.
Problem statement: existing openHAB profile mechanism is limited and constrained to only one profile per item-channel link. While profiles are quite powerful, you can use just one power at the time. Some of earlier discussions - OpenSmartHouse: consider support for multiple profiles per item-channel link, there was a concept for openHAB core: incoming and outgoing profiles (very interesting!). Finally there is also some amount of contributors asking for more profiles, which are usually refused. However, even if core gets more profiles, the problem of calling just one of them as part of state update/command submission is still there.
Problem origin: origin is that profiles (which btw. is a bit unfortunate name) were introduced in late stage of Eclipse Smarthome and never been given right to evolve. Code around profile handling is rather basic, received some updates to get ScriptProfile
in, however even very common use cases for profiles are still refused / difficult to pass review. With scripted profiles being part of openHAB 4.0, the scripted profile will be new glorified way to actually work-around limitations imposed of profiles themselves.
Given that profiles rely on existing configuration framework (config descriptors), hence dynamism of their configuration parameters is rather limited. Configuration framework supports only scalar values, which means that getting more complex (nested) structures out of it, require fair amount of code to re-construct intended structure. Think of a configuration which is incremental and allows you to append further profiles while constructing a chain of invocation. Making config descriptors dynamic is possible, but require a lot of work.
Please note that later part of this post will keep repeating “connectorio”, since I am bringing a concept which I already implemented.
Lets look at initial initialization chain:
+-----------------------+ +-----------------+ +----------------+
| Communication Manager |-->| Profile Factory |-->| Custom Profile |
+-----------------------+ +-----------------+ +----------------+
In above returned custom profile will be used as one and only one to handle invocations between binding and item.
I was able to solve that problem through making “stacked profile” implementation: connectorio-addons/ConnectorioProfile.java at c272c0a499d27feb54c67808d33660fd477c305a · ConnectorIO/connectorio-addons · GitHub. This profile uses its own configuration to initialize other profiles and chain them together. Configuration does not look too nice:
<link item="Installations_Heat_WaterTank_1_CenterTemperature">
<channel>modbus:data:temperatures:status_sensor0:number</channel>
<config>
<profile>connectorio:profiles</profile>
<quantity.profile>connectorio:quantity</quantity.profile>
<quantity.unit>°C</quantity.unit>
</config>
</link>
In above example there is only one profile which apply quantity to received number from binding and remove it while sending it back to the binding.
Below exampel is bit more complex as it consist of three profiles, beyond earlier quantity handling there are two profiles which are added to limit upper and bottom values accepted by item. Here you can see that configuration got much longer, and as you can see it limits range between 0-15000 W. The initial use of quantity profile is to ensure that later conditions are evaluated in same context, but in fact these two conditions could be turned into one, i.e. RangeFilterProfile
:
<link item="Installations_Photovoltaic_PowerInverter_1_DC_Power">
<channel>co7io-plc4x-canopen:ta-device:can0:ez3_40:ta-analog-power#analog-output_1</channel>
<config>
<profile>connectorio:profiles</profile>
<aquantity.profile>connectorio:quantity</aquantity.profile>
<aquantity.unit>W</aquantity.unit>
<bottom.profile>connectorio:limit-bottom-quantity</bottom.profile>
<bottom.unit>W</bottom.unit>
<bottom.bottomLimit>0</bottom.bottomLimit>
<top.profile>connectorio:limit-top-quantity</top.profile>
<top.unit>W</top.unit>
<top.topLimit>15000</top.topLimit>
</config>
</link>
Note, there is openHAB’s ‘Range’ profile which is emits ON/OFF states from received number. In above example I want to refuse values which are out-of-range and do want them be processed.
Reason why above configuration is so long is basic - it needs to be “ordered” alpabetically, maybe not while writing but before applying, because there are no guarantees that configuration properties will be delivered in same order. In fact they are not, because there are hash maps involved along the way, which tend to swap order of elements.
Now, coming back to the initial approach, it could be seen as below:
+-----------------------+ +-----------------+ +---------------------+
| Communication Manager |-->| Profile Factory |-->| Connectorio Profile |
+-----------------------+ +-----------------+ +----+--+-------------+
| |
+------------------+ | |
| Profile Factory1 |<-------+ |
+------------------+ |
|
+------------------+ |
| Profile Factory2 |<----------+
+------------------+
While it works, it needs to be repeated on each and every link, making it fairly difficult to keep multiple places same. That’s why I thought of making it a bit smarter by introducing named profiles, then configuration could be much simpler:
<link item="Installations_Photovoltaic_PowerInverter_1_DC_Power">
<channel>co7io-plc4x-canopen:ta-device:can0:ez3_40:ta-analog-power#analog-output_1</channel>
<config>
<profile>connectorio:named-profile</profile>
<name>power-limit-0_15kW</name>
</config>
</link>
Obviously it does not change in any drastic way how profiles are initialized, but makes the first profile, one called by OH’s CommunicationManager
free to call “other manager”, let say NamedProfileManager
(?) which can manage these profiles configuration and coordinate initialization of these:
+-----------------------+ +-----------------+ +---------------------+ +---------------------+
| Communication Manager |-->| Profile Factory |-->| Connectorio Profile |-->| NamedProfileManager |
+-----------------------+ +-----------------+ +---------------------+ +---------------------+
| |
+------------------+ | |
| Profile Factory1 |<-------+ |
+------------------+ |
|
+------------------+ |
| Profile Factory2 |<----------+
+------------------+
This approach brings one more advantage - all profiles and their configuration options will be known after named profile is created/updated, thus it will be possible to calculate config descriptor for entire thing.
Asymetric profiles
Idea of asymetric profiles is not wrong, it is actually fairly reasonable. as some operations are only relevant in single direction, and not both. One of these could be “expire” which defers sending command until certain amount of time passes. This is operation which, fairly speaking unnecessarily implemented as a special care solution in openhab-core, is relevant only when sending command to the binding. It does not do anything at all with received state updates, however we might have “bounce” profile to prevent system being flooded with far too many events from binding.
The core concept of profile assumes that it is called always when state update is received or command is dispatched. Its consistent with overall design which assumes single profile per link, however it is not required when we turn profiles into chain of invocations.
Lets look first at openhab-core and how it does wire profile in:
.-------.
| Event | +-----------------+
`-------' | Event Subsriber |
| +-----------------+
| ^
V |
+-----------------------+ |
| Communication Manager +---+
+---------+-------------+
| (update, command, trigger)
|
V
+----------------+
| Lookup Profile | (state profile)
+-------+--------+
|
+---------------+------------+
| | |
v | v
+-------------------+ | +--------------------+
| onCommandFromItem | | | onStateFromHandler |
+-------------------+ | +--------------------+
v
+----------------------+
| onCommandFromHandler |
+----------------------+
In above example communication manager will decide which method should be called. With “inbound” and “outbound” profiles there would be separation of methods into “sides”. For state profile inbound (incoming update) would contain onStateFromHandler
and onCommandFromHandler
while outbound (sending update) would only declare onCommandFromItem
. Looking at Java classes now:
interface InboundStateProfile {
onStateFromHandler(State)
onCommandFromHandler(Command)
}
interface OutboundStateProfile {
onCommandFromItem(Command);
}
Because introduction of named profiles gives us a bit more flexibility in how we declare profiles, we can stack them all together and look, if they are callable in context of received event.
+--------+
| Item |
+---+----+
|
| (ItemCommandEvent)
V
+----------------+
| NamedProfile |
+-------+--------+
|
V
+---------------------+
| Outbound Profile #1 +----+
+---------------------+ |
| chained smart
+---------------------+ | profile callback
| Inbound Profile #1 | |
+---------------------+ |
|
+---------------------+ |
+---+ Outbound Profile #2 |<---+
| +---------------------+
|
| +------------------+
+---->| State Profile #1 |
+--------+---------+
|
V
(final call)
Final call might be either ProfileCallbackImpl#handleCommand
which dispatches call to binding/handler, or ProfileCallbackImpl#sendCommand
which will result in item command event. Overall, above invocation chain can be reversed, so when binding reports a state it will bypass outbound profiles and stick only with instances of StateProfile
and InboundProfile
:
+---------+
| Binding |
+----+----+
|
| (State)
V
+----------------+
| NamedProfile +------+
+----------------+ |
|
+---------------------+ |
| Outbound Profile #1 | |
+---------------------+ | chained smart
| profile callback
+---------------------+ |
+---| Inbound Profile #1 |<---+
| +---------------------+
|
| +---------------------+
| | Outbound Profile #2 |
| +---------------------+
|
| +------------------+
+---->| State Profile #1 |
+--------+---------+
|
V
(final call)
Once we know which direction we go in the stack/chain and what kind of operation is being dispatched properly constructed loop can filter profiles relevant in given context.
Final remarks
The big question is, is this complicates more setups or simplifies them. With scripted profiles a lot of things can be made out of the box. However we can’t forget about drawbacks of scripts.
In general:
- Most problematic part of scripts is their very generic interface which leads to possible resource leaks. A well known trouble with majority of scripts/rules is handling of various timers and executors.
- Scripted profiles are harder to test, as unit test which is intended to verify their output must fire language/platform in which they are implemented.
- Handling of UoM across various scripts is a never ending story.
Given above, an approach of smaller and fairly limited in scope profiles gives possibility to make reusable functional blocks with decent testing capabilities and minimum overhead.
Because profiles can have their own configuration descriptors they are able to make use of various inputs to provide thresholds, limits and bearers which is, as far I know, impossible with scripted profiles.
Hope you enjoy this write up.