EDIT: I have now converted the components of this system into rules and a widget available in the marketplace. The information below on usage and general concept is still correct, but installation is simplified and mediated by the marketplace and the dependencies on helper libraries and custom library have been removed. The creation of the proxy items for the scenes and the widget (see: Setup) is still required.
I’ve had various questions about the system I use for UI-based control of scenes in OH3. I’ve also recently taken the time to update/streamline the system so I thought I’d put it all down in one place here. There are enough moving parts that at the moment I’m not sure how to best get this into the marketplace so I’m going to put here in the tutorials section for now.
What is it?
This is a fully integrated system for the control and general activation of scenes: groups of associated item states that should all result from a single trigger. It is not difficult to setup basic scenes in OH, that is one of the most fundamental uses of rules, but having scenes based entirely in rules does have a few downsides:
- You have to edit the rule to change something about the scene configuration
- You have to look at the rule code to even see what the current configuration is
- Only users with the experience/comfort with writing rules and access to the system administration can create or modify rules-based scenes
The basic layout for this system has 5 parts:
- A custom MainUI widget which shows all the scenes you have configured, allows enabling/disabling of the scenes and configuration of all the items and item states associated with the scene
- One rule that handles all the actual modification of the metadata where the scene information is stored
- One external library script that handles converting scene metadata into item commands
- One basic rule template that calls the external library script and can be used as the basis for all the rules that will trigger scenes
- The helper libraries (this is, of course, not strictly necessary, you could convert all the rules and scripts to do the metadata actions from scratch, but that’s not really recommended)
This system uses proxy items (Type: Switch) to control each scene. By doing this there is one item that holds all the metadata for one scene but also gives the user control over whether the scene is currently enabled or disabled. I find this useful for scenes where the trigger is some daily event or cron time (such as a Wake Up scene) which doesn’t need to occur when you are not at home. Each proxy item that needs to act as a scene controller only needs to have a non-semantic tag,
Scene Controller added to it. No other configuration is required. I Give all of my controllers a prefix on the label such as
Scene Contoller - XXXX but this is not necessary. If you do, use a hyphen in the label and the widget will only display text after the hyphen, otherwise the widget will show the entire item label.
One additional proxy item (Type: String) is required to carry information between the widget and the configuration rule. In my sample configuration, this item is named
Rule_SceneModify. This can be configured in the widget, but that is the default name. If you change this default name you will also need to change the triggers and conditions of the scene modification rule (see below).
The centerpiece of this is the UI widget. The widget starts in a basic form which displays each of the available scene controllers in a list and displays whether they are currently enabled or disabled.
Each of these is an accordion and can be expanded to show some details of the scene.
This is all fairly straightforward, the only thing that might require comment is that the badge on the devices line shows the number of devices that are currently programmed for that scene, and this devices line is also a accordion that can be further expanded to show those devices and their settings.
Adding a Device to a Scene
Let’s start with putting devices into the scene. The widget will record the current state of each item you add. So, the first step will be to make sure all the items you are going to want in the scene are currently set the way you want them (basically, set up the actual environment you want). Then press the plus icon on the
Add a new device... line under the scene you want to add the items to. This will bring up an item selection popup.
You can toggle each of the buttons at the top to show all the items of a particular type, or you can just type in the search box to bring up items filtered by item name. When you locate the item you want, just press the red arrow. The item you select will not disappear from the list, but in the background you should see the item appear in the widget (or at least see the number of items in the badge increment). Do this for each item you want. (If you are interested in the main trick behind this item selection popup, see this post here).
In the fully expanded list, each device has two buttons associated with it. The trash can, naturally, removes the device from the scene (it does not delete the actual item, it only removes the metadata in the scene controller). You should see the item disappear immediately.
The circular arrows allow you to update the state of the item if you decide that the scene is not quite to your liking. For example, if you have a dimmer in the scene set to 60, but decide that’s too bright, just change the dimmer item to some lower setting and then press the circular arrows to refresh the state in the scene definition to the new setting. You should see the state badge change accordingly.
Here's the full code for the widget
uid: jag_scene_controllers tags:  props: parameters: - context: item default: Rule_SceneModify description: Proxy item to run scene modification rule label: Rule Item name: ruleItem required: false type: TEXT groupName: general parameterGroups: - name: general label: General settings component: oh-list-card config: accordionList: true title: Scene controllers slots: default: - component: oh-repeater config: accordionList: true fetchMetadata: ActiveItems for: controller fragment: true itemTags: Scene Controller key: =Math.random() + items[props.ruleItem].state sourceType: itemsWithTags slots: default: - component: oh-list-item config: badge: "=(items[loop.controller.name].state == 'ON') ? 'Enabled' : ((items[loop.controller.name].state == 'OFF') ? 'Disabled' : 'Unknown')" badgeColor: "=(items[loop.controller.name].state == 'ON') ? 'green' : ((items[loop.controller.name].state == 'OFF') ? 'red' : 'yellow')" title: =loop.controller.label.split('-').at(-1) slots: accordion: - component: oh-list slots: default: - component: oh-list config: accordionList: true slots: default: - component: oh-toggle-item config: class: - margin-left item: =loop.controller.name title: "=(items[loop.controller.name].state == 'OFF') ? 'Enable' : 'Disable'" - component: oh-list-item config: badge: "=(!!loop.controller.metadata && !!loop.controller.metadata.ActiveItems && !!loop.controller.metadata.ActiveItems.config) ? JSON.stringify(loop.controller.metadata.ActiveItems.config).slice(1,-1).split(',').length : '0'" class: - margin-left title: Devices slots: accordion: - component: oh-repeater config: for: activeItem fragment: true in: =JSON.stringify(loop.controller.metadata.ActiveItems.config).slice(1,-1).split(',') slots: default: - component: oh-list-item config: badge: =loop.activeItem.split(':').slice(1,-1) class: - margin-left title: =loop.activeItem.split(':').slice(1,-1) slots: after: - component: f7-row config: style: padding: 10 slots: default: - component: oh-link config: action: command actionCommand: =loop.controller.name + ',' + loop.activeItem.split(':').slice(1,-1) + ',DELETE' actionItem: =props.ruleItem iconF7: trash_circle_fill style: padding-left: 15px - component: oh-link config: action: command actionCommand: =loop.controller.name + ',' + loop.activeItem.split(':').slice(1,-1) + ',REFRESH' actionItem: =props.ruleItem iconF7: arrow_2_circlepath_circle_fill style: padding-left: 15px - component: oh-list-item config: class: - margin-left title: Add a new device... slots: after: - component: oh-link config: action: variable actionVariable: addToController actionVariableValue: =loop.controller.name iconF7: plus_circle_fill popupOpen: .popup.add-item-pop - component: f7-popup config: class: - add-item-pop style: overflow-y: scroll slots: default: - component: f7-card config: title: "='Add Items to Scene Controller: ' + vars.addToController" slots: default: - component: oh-list config: virtualList: true slots: default: - component: f7-segmented config: raised: true slots: default: - component: oh-repeater config: for: filterType fragment: true in: - Switch - Dimmer - Player slots: default: - component: oh-button config: action: variable actionVariable: ='filter'+loop.filterType actionVariableValue: "=(!!vars['filter'+loop.filterType]) ? false : true" active: =vars['filter'+loop.filterType] text: =loop.filterType - component: f7-segmented config: raised: true slots: default: - component: oh-repeater config: for: filterType fragment: true in: - Roller - Color - Number slots: default: - component: oh-button config: action: variable actionVariable: ='filter'+loop.filterType actionVariableValue: "=(!!vars['filter'+loop.filterType]) ? false : true" active: =vars['filter'+loop.filterType] text: =loop.filterType - component: f7-row config: style: align-items: center slots: default: - component: oh-icon config: color: gray icon: f7:search - component: oh-input config: clearButton: true outline: true placeholder: " type to search" style: flex-grow: 10 type: text variable: filterText - component: oh-repeater config: filter: "((!!vars.filterSwitch && loop.ohItem.type=='Switch') || (!!vars.filterPlayer && loop.ohItem.type=='Player') || (!!vars.filterDimmer && loop.ohItem.type=='Dimmer') || (!!vars.filterRoller && loop.ohItem.type=='Rollershutter') || (!!vars.filterColor && loop.ohItem.type=='Color') || (!!vars.filterNumber && loop.ohItem.type.substring(0,6)=='Number') || (!!vars.filterText && loop.ohItem.name.includes(vars.filterText))) ? true : false" for: ohItem fragment: true itemTags: "," sourceType: itemsWithTags slots: default: - component: oh-list-item config: after: =loop.ohItem.state title: =loop.ohItem.label + ' (' + loop.ohItem.name + ')' slots: after: - component: oh-link config: action: command actionCommand: =vars.addToController + ',' + loop.ohItem.name + ',ADD' actionFeedback: =loop.ohItem.name + ' (' + loop.ohItem.state + ') added to ' + vars.addToController actionItem: =props.ruleItem iconF7: arrow_right_circle_fill style: padding-left: 15px
If you look through the code you can see that each one of the action buttons simply creates a comma separated string with the name of the scene controller item, the item to be controlled, and the action to be performed. This string is then sent as the command to the proxy item that is connected to our scene modification rule. Changing the state of this proxy item is what will trigger that rule to write our metatdata changes.
This is the rule that runs whenever a scene setting is changed via the widget. It is also the rule that must have it’s triggering item and conditional item changed if you do not use the default name for the widget proxy item.
Scene Modification Rule
Looking at the script itself you can see that all the metadata for this setup is stored in a namespace named
ActiveItems. An item’s name is a key within this namespace and the state to set it to is the value of that key. The script first parses the proxy item’s state to get the appropriate controller, target item, and action and then either writes these data or removes them from the namespace as required. At the end of the run the rule clears out the information held in the proxy item state to avoid any duplication of actions.
Scene Activation Script
If you have installed the helper libraries properly then you have an
automation folder tree in your OH conf folder. This folder tree contains folders to place the personal scripted automation libraries that you may need to access from your rules:
automation control.js with the following content:
This library, when loaded into a rule, will make the
run_controller function available. When
run_controller is called with the name of one of the scene controller items, it reads the
ActiveItems metadata in the controller item to identify all the other items to be changed and then runs through those items sending the appropriate command.
There’s an extra feature you can see in this script that I haven’t mentioned yet. For some scenes, it makes sense to be able to turn the scene on, and then simply reverse the effects of that scene when it is over. So, there is a second, optional, parameter to the
undoSettings. When this second parameter is passed a true value, then instead of sending the commands stored in the metadata, for any switch item, the opposite command is sent. The best example of this is the “scene” for my wife’s seedling table. She likes the grow lights and the seedling warmer mats to be turned on at sunrise and off at sunset. I could set up two different scenes: one with all the
ON states and one with all the
OFF states. Instead, I just have a single rule which runs on the astro daylight event triggers. This rule calls the
run_controller function with
event.event=='END' in the second parameter. Now if it is
START of daylight
undoSettings is false and the scene is run as specified to turn on all the various devices, but if it is
END of daylight then
undoSettings is true and every item that has an
ON state in the metadata gets an
OFF command instead.
Scene Triggering Rule
The very last step is that every scene needs a scene triggering rule (such as the plant table rule mentioned above). With the
automation control library, these are now very simple and straightforward to set up in the MainUI. Each rule simple needs whatever trigger you want for your scene, buttons, presence, astro, etc. As a rule condition (“But only if…”), the switch state of the scene controller should be checked so that the rule doesn’t run if the scene is disabled, and then the rule action is a script with only two pieces. Load the
automation control library and call
run_controller with the name of the scene controller.
Scene Rule Example
As I stated in the beginning, this is a lot of extra complexity when a scene can simply be rendered as a series of item commands in a single rule. For many users, however, I hope this system helps provide one of the feature sets that that seems to be a common expectation, an easy to use scene system that integrates with the UI. If not, then at least as a tutorial, I hope this provides some examples of effective ways to integrate widgets and rules, use metadata, and personal library scripts.