I have a few questions on modelling my equipment. I have been playing around with openHAB for a few days now and would like to get the principle of my model right before I create the majority of my devices, to save me from having to rework everything later.
So far I have been creating Equipment with Points under them, but also linked the Equipment directly to a Channel, for example for lights to the Channel that switches the lights. In another thread, @rlkoshak advised me that such Channel links for Groups are not really supported and recommended removing them.
So I tried to model my Equipment without Channel links, and that raised two questions:
First, my Shelly plugs have multiple Points below them of type Switch. One of them is for Power, the other two for enabling/disabling status lights. When I sent an ON/OFF command to the Equipment, the system somehow knows to only change the state of the Power, but not the other Switches. That is good, but how does the system know which of the Points is the one that shall receive the command? Is it the hasPoint semantic property? If yes, how can I edit it? It is only displayed on the UI but I cannot find a way to edit it.
Below a screenshot of such an Equipment with the multiple Points.
And my second question is about the aggregation function. If Iād want the Equipment to reflect the status of a single point under it, how would I go about it? On the UI I only have the all ON or one ON options, neither of them works the way Iād like to. Can more elaborate aggregation functions only be defined when creating the Items by textual definition?
It looks like you are trying to do more with the model than it is intended to handle. Really the idea behind the model is that it should reflect the actual physical layout of your system as a user would expect to interact with it. It is not intended to handle a lot of behind-the-scenes functionality. There are not a lot of hard and fast rules about using the model, but one of the few is: Not every item needs to be a part of the model, and, in fact, many should not be. If a user doesnāt have to interact with it directly, it probably doesnāt make sense as part of the model.
So, in your case, it sounds like a lot of the group function you want to implement should be done through groups that are not part of the model (standard non-semantic group items). That way you donāt have to worry about how equipment items handle commands (in fact, if your model is arranged in the most common, intended way, there is never a reason to send a command to an equipment item).
This is exactly how the model is expected to work. Youāve collected the user interaction important functions of this device into the equipment that represents this device.
This is the part you shouldnāt do. There is no reason to send a command to the equipment group, it should simple be sent directly to the item you wish to change. In fact, Iām not sure what behavior you are seeing, because a command sent to a group (even if that group is a semantic element) should be distributed to all the members of that group.
You do not edit this tag directly. This tag is automatically generated if the item is a member of a semantic group such as an equipment and has a proper semantic class tag (e.g., point, switch, setpoint, etc) appiled.
You donāt. An aggregation function for an equipment doesnāt make any sense. What is the average of a thermostat? What is the sum of a lightbulb?
If you want the equipment item detail page to display just the value of one of itās points (or a summary of multiple points) then you what you want to do is to create a custom widget for that equipment that utilized some of the member item states. For example, hereās one of my sonos speakers. The speaker equipment group has a custom standalone widget for interaction with the many of the speakers items, but none of these have to do with a group state which in this case would be meaningless.
Thanks for your detailed response, I think I get most of it. But I am still having issues making sense of if and how to send commands to groups.
I understand that I would not send a command to an Equipment group but send it to the Point below it that I want to command. However, Iād like to send a command to a Location group, for example to switch off all lights in that room or on that floor. Is that not an intended use case?
While this worked for me initially when the hasPoint was set to the Point representing Power, now the hasPoint has randomly changed to another Point. Now, the command gets distributed to all Points of type Switch, so not only the Power but also the status LEDs.
Another issue I have: the command sent to the group is only sent to its members if the groupās Member Base Type=Switch. If I set this to None, then the command does not get distributed at all. This applies to both Equipment and Location groups.
Yes and no. The semantic information is very helpful in many cases like this. But, because locations are still just regular groups, there is no mechanism to automatically select which members get a command. A group propagates a command to all members, then it is up to the member item to determine if the command is meaningful to that item.
Now, I have exactly the same use case. I want to be able to turn off all the lights on one floor of my house. Each floor is a location group, but I canāt just send OFF to that group or Iāll also turn off ceiling fans and TVs, and my wifeās treadmill etc. So this is the domain of rules.
I have a rule which is triggered when I set a string item to the name of any location item. The rule then filters the members of that location for all Switch Items which have a semantic tag of Light and then iterates through that list to switch off all the lights. Hereās the script for the rule:
//Get Location From Rule Item
var locationItem = items.getItem(event.itemName,true);
if (locationItem) {
//Find Lights In A Location
var locationList = items.getItem(locationItem.state).descendents;
var locationLights = locationList.filter(x => ((x.type == 'SwitchItem') && (x.tags.indexOf('Light') >= 0)));
//Turn Off All Lights
locationLights.forEach(x => x.sendCommand('OFF'));
}
//Cleanup
items.getItem('Rule_LocationLightsOff').postUpdate('None');
This is the expected behavior. Iām not sure what was going on in your first case when it appeared that only the power switches were getting the group command. That is just not a capability that has ever existed in OH to my knowledge and certainly doesnāt exist now. So, something else must have been happening.
To my knowledge, the member base type only affects which aggregation functions are available for that group, not what commands that group distributes. However, itās possible that is a new limitation Iām not aware of. Even so, it makes sense. If you have a collection of items you want to send the same command to, then you put those items in a group. In order for the same command to be sensible those items should all be of the same type. So limiting the commands a group can relay to those of its member base type is actually not unreasonable.
Would be interesting which of these two mechanisms (let the item decide or limit command forwarding) is now implemented in the current version of OH. Do you maybe have an idea where in the documentation this could be covered? I could not find anything.
Thanks for the code!
I have now also written my own function to implement the logic I want to have. Basically I would like to send commands to Locations and to decide which devices shall receive the command use only information that is already in the model and automatically generated.
Basically it is relying on the semantic type to identify the right Equipment and then on the tag of the Points below it to identify the right Point. I have defined custom commands like āLIGHTS_OFFā for the outside world to use, so that the mapping to the semantic model stays within the function in one place.
My function now looks like this:
var runtime = require( "@runtime" );
// Constants for our custom commands
var LIGHTS_OFF = "LIGHTS_OFF";
var LIGHTS_ON = "LIGHTS_ON";
var SHUTTER_DOWN = "SHUTTER_DOWN";
var SHUTTER_UP = "SHUTTER_UP";
var SPEAKER_PLAY = "PLAYER_PLAY";
var SPEAKER_PAUSE = "PLAYER_PAUSE";
// Maps our custom commands to semantic classes and openHAB commands
function sendCommand( itemName, command )
{
console.info( "sendCommand: received command '" + command + "' for '" + itemName + "'" );
switch( command )
{
case LIGHTS_OFF: sendCommandInternal( items.getItem( itemName ), "Lightbulb", runtime.OFF ); break;
case LIGHTS_ON: sendCommandInternal( items.getItem( itemName ), "Lightbulb", runtime.ON ); break;
case SHUTTER_DOWN: sendCommandInternal( items.getItem( itemName ), "Window", runtime.DOWN ); break;
case SHUTTER_UP: sendCommandInternal( items.getItem( itemName ), "Window", runtime.UP ); break;
case SPEAKER_PLAY: sendCommandInternal( items.getItem( itemName ), "Speaker", runtime.PLAY ); break;
case SPEAKER_PAUSE: sendCommandInternal( items.getItem( itemName ), "Speaker", runtime.PAUSE ); break;
}
}
// Recursive function to forward the command to group members
// and execute the command for equipment
function sendCommandInternal( item, equipmentType, command )
{
console.info( "sendCommand: received command " + equipmentType + "=" + command + " for '" + item.name + "' (" + item.semantics.semanticType + ")");
// Loop through all the members ...
for( member of item.members )
{
// ... and forward the command to any groups of type locations or equipment.
if( ( member.type == "GroupItem" ) && ( member.semantics.semanticType == "Location" || member.semantics.semanticType == "Equipment" ) )
{
console.info( "sendCommand: forwarding command to location/equipment '" + member.name + "'" );
sendCommandInternal( member, equipmentType, command );
}
// For points we check if our current item is of the right type ...
else if( ( member.semantics.semanticType == "Point" ) && ( item.semantics.equipmentType == equipmentType ) )
{
// ... and then match the type with the points we want
// to forward the command to, based on the point's tags
if( equipmentType == "Lightbulb" && hasTag( member, "Switch" ) ||
equipmentType == "Speaker" && hasTag( member, "Control" ) ||
equipmentType == "Window" && hasTag( member, "OpenLevel" )
)
{
console.info( "sendCommand: executing command for equipment '" + item.name + "' on point '" + member.name + "'" );
member.sendCommand( command );
}
}
}
}
function hasTag( item, tag )
{
return item.tags.indexOf( tag ) >= 0;
}
Since I am totally new to openHAB and its scripting, any advice on how to improve this or make it more elegant is appreciated.
Particularly I am wondering if there are some constants defined anywhere for the strings I am using for item types and semantic types.
Also, what would be the most elegant way of encapsulating this function and call it from the multiple different rules that trigger the commands? Right now I have the function as file and use a script action to call it. However it would be much nicer if I could store the function as script in the UI, but it seems for those scripts defined on the UI, there is no way to pass in parameters, or is there?
Here is the code snippet I now use to call the function:
I actually have a similar situation in my house where I model my equipment the way you describe it but also model light groups at the same time in parallel. It looks like this
house
first floor
room 1
light equipment 1.1
item 1.1a (switch)
item 1.1b (something else)
light equipment 1.2
item 1.2a (switch)
item 1.2b (something else)
light equipment 1.3
item 1.3a (switch)
item 1.3b (something else)
second floor
similar to first floor
light equipment 2.1
item 2.1a (switch)
item 2.1b (something else)
light equipment 2.2
item 2.2a (switch)
item 2.2b (something else)
all lights
lights first floor
lights room 1
item 1.1a (switch)
item 1.2a (switch)
item 1.3a (switch)
indirect lights room 1
item 1.1a (switch)
item 1.3a (switch)
lights room 2
item 2.1a (switch)
item 2.2a (switch)
lights second floor
Note the the lights groups and all their items are non-semantic and it has a bit of a downside as the model doesnāt show the all-lights ā lights-first-floor->lights-room1 in a tree-like structure anymore (if I recall correctly because it tries to avoid recursion) but you can still click on āall lightsā and the item shows its groups (lights first floor, light second floor) and from there you can go into further sub groups as well (which I have but didnāt depict above).
I then send on-off commands to respective lights group which works quite well.
Yes, that would probably work for me as well. But Iād have to create similar parallel groups for music players and shutters, which seems to be a bit redundant and not very elegant. That is why I have been trying to solve it with one group structure with different device types in it.
And this is what I did. I also have a parallel group for roller shutters. Basically it comes down to the fact that we use grouping fir different purposes: the physical model and the action model. To me it is worth. It also has another advantage: Aggregation works quite well for example for the rollershutters as it tells me the percentage of the opened windows per room, per floor, per houseā¦
Yes, I can see how that has advantages for aggregated views on the houseās status. In my case Iād have at least three structures: one for lights, one for shutters and one for music. Maybe two for music, because volume control are separate points, as is mute.
But I am not dead-set on the scripted approach, though it was fun coding it. Iāll try the non-semantic groups approach as well and then decide.
OK, I tried it like that as well and it works fine. My non-semantic structure would look like in the image below, with three parallel structures for lights, shutters and music.
From data modelling perspective, the more nerdy scripted approach relying on the semantic model is less effort and with the script in my own hands I have maximum flexibility to implement the actions how I want them.
But on the other hand, that data modelling is a one-time job for a rainy afternoon, and working with the non-semantic groups seems to be more in line with OH standards and for example easier to migrate to newer versions in the future. Also setting up rules based on these groups is easier and relies on UI only, without having to add a two-line script to every rule.
If youāre really using it in a lot of different contexts then you might find it valuable to package it up as a node-module which can then be installed in /automation/js/node_modules and called as needed.
Also an excellent solution and a great example of the division between semantic and non-semantic modeling.
I went the rule route because it was more reliable than me remembering to update a bunch of group memberships when I change something.
Thanks again for all your inputs, especially regarding the scripting. I have now modeled all my data based on the non-semantic group solution, it seems to work fine, and I see some value in staying with a openHAB standard solution vs. a scripted one. Still, Iāll keep the script on file and might revert back to it if I need a level of customization that the solution based on non-semantic groups cannot offer.
And in any case, writing the script taught me more about openHAB, which will for sure be useful for future customization. The list of ideas is long.