I’ve implemented exactly this multiple times in the past in multiple ways. You say UI but not which one. I’ll assume OH 3 MainUI.
As rossko57 recommends, I use two Items, an actuator Switch Item which acts like a button so its state is kind of meaningless and a sensor Contact Item which represents the open/closed state of my garage door.
See Example Custom List Widgets for the List Widget that I use. It uses expressions to change the appearance and contents of the widget based on the state of the Contact. Clicking on the widget sends a command to the Switch Item to trigger the opener.
Note, in OH 3.2 you will be able to find and install this widget from the marketplace. For now you’ll have to copy and paste the YAML into a custom widget under Developer Tools.
I’ve used a similar approach for the main widget. I’ve two garage door openers and a camera (though the camera isn’t working right now) so I’ve combined all three into a unified widget.
Again, the color, icon and text changes based on the Contact Item and clicking on the widget sends a command to the Switch Item to trigger the opener. I know I’ve posted the code for this widget somewhere but can’t remember where so here it is.
uid: garage_widget
tags:
- card
- garage
props:
parameters: []
parameterGroups: []
timestamp: Feb 12, 2021, 10:02:20 AM
component: f7-card
config:
title: Garage Doors
slots:
default:
- component: f7-row
slots:
default:
- component: oh-image
config:
item: GarageCamera_Image
style:
width: 100%
height: auto
- component: f7-row
config:
class:
- justify-content-left
slots:
default:
- component: f7-col
slots:
default:
- component: oh-label-card
config:
footer: Small Garage Door Opener
action: command
actionItem: Small_Garagedoor_Opener
actionCommand: ON
label: '=(items.Small_Garagedoor_Sensor.state == "OPEN") ? "close" : "open"'
item: Small_Garagedoor_Sensor
icon: '=(items.Small_Garagedoor_Sensor.state == "CLOSED") ? "f7:house" : "f7:house_fill"'
iconColor: '=(items.Small_Garagedoor_Sensor.state == "CLOSED") ? "green" : "orange"'
- component: f7-col
slots:
default:
- component: oh-label-card
config:
footer: Large Garage Door Opener
action: command
actionItem: Large_Garagedoor_Opener
actionCommand: ON
label: '=(items.Large_Garagedoor_Sensor.state == "OPEN") ? "close" : "open"'
icon: '=(items.Large_Garagedoor_Sensor.state == "CLOSED") ? "f7:house" : "f7:house_fill"'
iconColor: '=(items.Large_Garagedoor_Sensor.state == "CLOSED") ? "green" : "orange"'
That should show you kind of how a widget like this would work.
The rule I have is pretty simple, though it depends on libraries from https://github.com/rkoshak/openhab-rules-tools as well as some personal libraries and Item metadata to get the name and amount of time a give door needs to be open before alerting.
triggers:
- id: "1"
configuration:
groupName: DoorsStatus
state: OPEN
type: core.GroupStateChangeTrigger
- id: "4"
configuration:
groupName: DoorsStatus
state: CLOSED
type: core.GroupStateChangeTrigger
conditions:
- inputs: {}
id: "2"
label: The door's state didn't change to an UnDefType
configuration:
type: application/javascript
script: >-
event.itemState.class != UnDefType.class
&& event.oldItemState.class != UnDefType.class
type: script.ScriptCondition
actions:
- inputs: {}
id: "3"
configuration:
type: application/javascript
script: >
var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Open Door Reminder");
var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");
load(OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js');
load(OPENHAB_CONF+'/automation/lib/javascript/personal/alerting.js');
load(OPENHAB_CONF+'/automation/lib/javascript/personal/metadata.js')
this.timers = (this.timers === undefined) ? new TimerMgr() : this.timers;
var reminderGenerator = function(itemName, name, when, sendAlert, timers){
return function() {
if(items[itemName] != OPEN) {
logger.warn(itemName + " open timer expired but the door is " + items[itemName] + ". Timer should have been cancelled.");
}
else {
sendAlert(name + " has been open for " + when);
// Reschedule if it's night
var tod = items["TimeOfDay"].toString();
if(tod == "NIGHT" || tod == "BED") {
logger.info("Rescheduling timer for " + name + " because it's night");
timers.check(itemName, when, reminderGenerator(itemName, name, when));
}
}
}
}
logger.debug("Handling new door state for reminder: " + event.itemName + " = " + event.itemState);
if(event.itemState == CLOSED && this.timers.hasTimer(event.itemName)) {
logger.debug("Cancelling the timer for " + event.itemName);
this.timers.cancel(event.itemName);
}
else {
var name = getName(event.itemName);
var remTime = getMetadataValue(event.itemName, "rem_time");
if(remTime === null) {
logger.warn("rem_time metadata is not defined for " + event.itemName + ", defaulting to 60m");
remTime = "60m";
}
logger.debug("Creating a reminder timer for " + event.itemName + " for " + remTime);
timers.check(event.itemName, remTime, reminderGenerator(event.itemName, name, remTime, sendAlert, timers));
}
type: script.ScriptAction
These are clearly all customized for my use cases but you should be able to use them as examples to build on.