Questions on modelling Equipment

Good morning,

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.

Edit: I take no responsibility for whatever bizarre thing my son was recently listening toā€¦

2 Likes

Hi Justin,

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.

1 Like

Good morning Justin,

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. :slight_smile:

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:

load(java.lang.System.getProperty("openhab.conf") + "/automation/lib/sendCommand.js")
sendCommand( "AO", LIGHTS_OFF );

Regarding the multiple points:

OK, maybe my observation was wrong in this case, not sure anymore. :slight_smile:

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.

2 Likes

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ā€¦

1 Like

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.

Hi Stefan, one follow-up question: do you also create separate Points for each structure, or do you put the same Point under different groups?

No, I donā€™t create separate points - the point has two parents, though

Letā€™s take the rollershutter point F2_BedRoom_shutter_side

It is contained in

Haus ā†’ OG ā†’ Schlafzimmer ā†’ F2_BedRoom_shutter_side

so it it parent is basically Schlafzimmer

but there is the rollershutter hierarchy

Alle RolllƤden ā†’ RolllƤden OG ā†’ RolllƤden Schlafzimmer (F2_bedoom_shutters) ā†’ F2_BedRoom_shutter_side

So it has another parent RolllƤden Schlafzimmer

Hence, the rollershutter has two parents:

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.

Now I am a bit torn between the two approaches.

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.

Decisions, decisions, decisions ā€¦ :wink:

Not that Iā€™m aware of.

There are (not surprisingly) a lot of different options here. If you are using JSscripting rules (which appears to be the case) then you can use runRule:
https://openhab.github.io/openhab-js/rules.html#.runRule
or the rules cache:
https://openhab.github.io/openhab-js/cache.html

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.

2 Likes

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. :wink:

1 Like