Sure, here’s a slightly more detailed run-down. Do note, however, that if I were to redo this system, certain parts, in particular the UI interaction, would change fairly dramatically. I haven’t gotten around to updating it though so here’s the original implementation.
Set Up
Items
The way I have it setup there is one proxy String
item for each “scene”. The state of this item will indicate the current status of the scene: ON (enabled), OFF (disabled), ADD (waiting to include new item states into the scene definition), and REMOVE (waiting to remove items and their states from the scene definition). These last two are really extra. The whole thing functions with just the ON and OFF states (and could therefore be a Switch
item just as well) but then you would have to manually enter all the metadata information that controls the individual items.
- These items must have a non-semantic tag that identifies them as the group of scene controlling proxy items (In my case I used
Automation Controller
).
- I also have these items in their own group for the widget although it could just as easily work the same tag as mentioned above.
- These items use the state options in the State Description metadata to create more human readable versions of the all caps states and to allow the widget to automatically populate the control buttons for each one
All items that you might want included in scene will also have to be in one or more groups. This is for triggering the rule that will add and remove items from scenes. For example I have a gLights group that every light in the house is a member of and a gActors group for other things such as ceiling fans and speakers and so I use these groups for the rule
Helper libraries
Because this relies heavily on metadata you must be able to access the metadata of an item from your rules scripts. This is possible without the helper libraries (there are examples here on the forum) but it is much more arduous so I recommend that you install the help libraries for whichever scripting language you prefer.
Widget
The widget works by cycling through all the items that match the tag you have given your scene controllers, rendering one list item with buttons that correspond to the state description options and a second accordion list that shows the details of the items included and their states.
Widget YAML
uid: widget_automation_controllers
tags:
- settings
- automation control
component: oh-list-card
config:
accordionList: true
title: Automation controllers
slots:
default:
- component: oh-repeater
config:
accordionList: true
sourceType: itemsInGroup
groupItem: gAutomationControllers
fetchMetadata: ActiveItems
fragment: true
for: controller
slots:
default:
- component: oh-list-item
config:
title: =loop.controller.label.substring(24)
badge: =items[loop.controller.name].displayState
badgeColor: "=(items[loop.controller.name].displayState == 'On') ? 'green' : ((items[loop.controller.name].displayState == 'Off') ? 'red' : 'yellow')"
slots:
accordion:
- component: oh-list
slots:
default:
- component: f7-segmented
config:
raised: true
slots:
default:
- component: oh-repeater
config:
sourceType: itemStateOptions
itemOptions: =loop.controller.name
fragment: true
for: option
slots:
default:
- component: oh-button
config:
text: =loop.option.label
raised: true
action: command
actionItem: =loop.controller.name
actionCommand: =loop.option.value
active: =(items[loop.controller.name].displayState==loop.option.label)
- component: oh-list
config:
accordionList: true
slots:
default:
- component: oh-list-item
config:
title: Devices
badge: "=(loop.controller.metadata.ActiveItems.config) ? JSON.stringify(loop.controller.metadata.ActiveItems.config).slice(1,-1).split(',').length : '0'"
slots:
accordion:
- component: oh-repeater
config:
for: activeItem
in: =JSON.stringify(loop.controller.metadata.ActiveItems.config).slice(1,-1).split(',')
fragment: true
slots:
default:
- component: oh-list-item
config:
title: =loop.activeItem.split(':')[0].slice(1,-1)
badge: =loop.activeItem.split(':')[1].slice(1,-1)
class:
- margin-left
Logic
There are really only 2 different pieces of rules logic involved: 1) reading the metadata and replicating the stored states, and 2) adding and removing items and associated states.
Running scenes
You need rules that can call the scene running library function (posted above) whenever you want that scene activated, whether that’s at some automated time or as the result of a button press or a TV setting. These are basic rules that are really just a trigger and the script up above that loads the library and makes the call to run-controller
with the appropriate scene controlling item name as the function parameter. Here’s an example of the full rule YAML (a rule from the mainUI rules builder) that runs at 11 pm every night but only if the controller item is set to ON:
Basic scene controller rule
triggers:
- id: "1"
configuration:
time: 23:00
type: timer.TimeOfDayTrigger
conditions:
- inputs: {}
id: "3"
configuration:
itemName: Auto_EndOfNight_Control
state: ON
operator: =
type: core.ItemStateCondition
actions:
- inputs: {}
id: "2"
configuration:
type: application/javascript
script: >-
load("/openhab/conf/automation/lib/javascript/personal/automation control.js");
run_controller("Auto_EndOfNight_Control")
type: script.ScriptAction
Scene membership
The scene membership is also handled by a single rule. The information about each scene is stored in a custom metadata namespace (in this case ActiveItems
). This rule runs any time one of the items in any of the basic groups changes. When an Item changes, it checks through each of the scene controlling items to see if any are in the ADD or REMOVE state. If a controller is in the add state then the ActiveItems
metadata gets a new key that is the name of the item which just changed and a value that is the new state it just changed to. If the controller is currently set to REMOVE than the metadata key matching that item name is deleted from the ActiveItems
namespace. So to populate your scenes you just set the scene controlling item to ADD (using the widget) and then change all of the items you want controlled to the states you want them to be in for the scene. To remove an item from a scene, you just set the controller to REMOVE and do anything that changes the state of the item you want removed. When you’re done you just reset the controller item to ON and your scene is ready to go whenever you have a rule that sends that controller item to the library function.
Scene membership script
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.controllerMembership');
load("/openhab/conf/automation/lib/javascript/core/metadata.js");
/*Loop through each controller and check state
if state is ADD then inlcude trigger item and newState in ActiveItems metadata
if state is REMOVE then remove trigger item key and value from ActiveItems metadata
*/
var controllerItems = ir.getItemsByTag('Automation Controller');
for( var i in controllerItems ){
var controllerState=controllerItems[i].getState().toString();
var controllerName=controllerItems[i].getName();
if (controllerState == "ADD") {
logger.info(['Adding ',event.itemName,' to ',controllerName,' as: ',event.itemState].join(''));
var targetData={};
targetData[event.itemName]=event.itemState.toString()
set_metadata(controllerName,"ActiveItems",targetData, null, false)
}
if (controllerState == "REMOVE") {
logger.info(['Removing ',event.itemName,' from ',controllerName].join(''));
remove_key_value(controllerName,"ActiveItems", event.itemName)
}
}