-
Notifications
You must be signed in to change notification settings - Fork 0
Taking Rules to New Heights
Here are some examples of how complex automation rules can be developed in OpenHAB in order to achieve whatever smart building automation that one can imagine. The focus here being on pushing the limits, and not code simplicity or simple comprehension.
This example provides a scripted mechanism for managing a very large number of switches, lights, and motion detectors in order to accomplish the following functions:
- turn lights on and off normally with switches
- turn area lights on if motion is detected and it is dark outside
- turn off lights in an area should motion not be detected for a set time
By importing and using various Java libraries it is possible to create configurable maps for each light allowing things like motion detection timeout, and auto-on after dusk to be set per light (which can later be looked up or looped through in the script).
Please note that rule must be reloaded manually each time the definition (*.items file) of the item used in the map changes. This is due to static reference to Item object which is no longer valid after the *.items file is reloaded.
/* First you start out by defining 3 groups: one for the switches, one for the dimmers, and one for the motion sensors */
Group:Contact:OR(OPEN,CLOSED) gMotionSensors "motion sensors" (All)
Group:Switch:OR(ON,OFF) gMotionSwitches "motion sensored switches" (All)
Group gMotionDimmers "motion sensored dimmers" (All)
/* Then put the light switches into the right groups. This example uses insteon switches, adjust as needed. There are no dimmers in this example but you can just put dimmers into gMotionDimmers, you get the idea: */
Switch officeLight "office light" (gMotionSwitches) {insteonplm="xx.xx.xx:F00.00.02#switch"}
Switch garageLightMudroomSw "garage light mudroom sw" (gMotionSwitches) {insteonplm="xx.xx.xx:F00.00.02#switch"}
Switch garageLightEastWallSw "garage light east wall sw" (gMotionSwitches) {insteonplm="xx.xx.xx:F00.00.02#switch"}
/* Then put the relevant motion sensors into the gMotionSensors group (example here uses alarmdecoder binding): */
Contact garageMotion "garage motion [MAP(contact.map):%s]" (gMotionSensors) {alarmdecoder="RFX:0000000#contact,bitmask=0x80"}
Contact officeMotion "office motion [MAP(contact.map):%s]" (gMotionSensors) {alarmdecoder="RFX:0000000#contact,bitmask=0x80"}
/* Use the Astro binding to see when it's dark or light outside. Using a light sensor would be
even better, but that's not too hard to hack in later */
DateTime dawnStart "dawn start [%1$tH:%1$tM]" {astro="planet=sun,type=civilDawn,property=start"}
DateTime duskEnd "dusk end [%1$tH:%1$tM]" {astro="planet=sun,type=civilDusk,property=end"}
Lastly install the below rule as e.g. light_off.rule in the rules folder of your openhab configuration directory, and adjust the hash tables accordingly.
import org.joda.time.*
import org.openhab.core.library.types.*
import org.openhab.core.library.types.PercentType
import org.openhab.core.library.items.SwitchItem
import org.openhab.model.script.actions.*
import org.openhab.model.script.actions.Timer
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.ArrayList
import java.util.Map
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
//
// Rules to switch off lights when no activity is present.
//
// Carefully modify the hash maps to achieve the desired result:
//
// Notes:
// - a single timer is maintained per light fixture, i.e. there is a timer
// per switched electrical circuit.
// - a light fixture can be switched from multiple switches.
// - a single motion sensor can affect multiple light fixtures
// - after a light fixture has been turned off, a restore timer
// is started. If there is motion within the restore timeout,
// the light fixture is switched back on
// - the flag "on_when_dark" causes the light to be switched on
// if motion is detected and it's dark outside.
//
// Hash maps:
//
// fixtures: - key is the name of the fixture (arbitrary string, but must be unique)
// - configure the timeout (how long the light will stay on)
// - specify which switches manipulate the fixture
//
// motionSensors: - key is name of motion sensor. Must be literally the same as
// as the name of the contact item as which it is configured in
// the .items file
// - specify a list of fixtures that will be kept on whenever the
// sensor is tripped
// Defines the timeouts for each fixture, and which switches control it
// the lock was needed because multiple threads were found
// to be entering the rule at once
var Lock lock = new ReentrantLock()
var HashMap<String, LinkedHashMap<String, Object>> fixtures =
newLinkedHashMap(
"officeFixture" -> (newLinkedHashMap("timer" -> null as Timer,
"restore_timer" -> null as Timer,
"timeout" -> 3600, "on_when_dark" -> true,
"switches" -> newArrayList(officeLight))
as LinkedHashMap<String, Object>),
"garageFixture" -> (newLinkedHashMap("timer" -> null as Timer,
"restore_timer" -> null as Timer,
"timeout" -> 600, "on_when_dark" -> true,
"switches" -> newArrayList(garageLightMudroomSw, garageLightEastWallSw))
as LinkedHashMap<String, Object>)
)
var HashMap<String, ArrayList<String>> motionSensors =
newLinkedHashMap(
"garageMotion" -> newArrayList("garageFixture"),
"officeMotion" -> newArrayList("officeFixture")
)
rule "motion sensor tripped"
when
Item gMotionSensors changed
then
logInfo("motion_tripped", "motion sensors changed state")
lock.lock()
var DateTime daystart = new DateTime((dawnStart.state as DateTimeType).calendar.timeInMillis)
var DateTime dayend = new DateTime((duskEnd.state as DateTimeType).calendar.timeInMillis)
val boolean isdark = now.isBefore(daystart) || now.isAfter(dayend)
var faultedSensors = gMotionSensors.members.filter(x|x.state == OPEN).map[it.name]
faultedSensors.forEach [ sensor |
var ArrayList<String> lightsAffected = motionSensors.get(sensor as String)
if (lightsAffected != null) {
logInfo("motion_tripped", " tripped sensor: " + sensor)
lightsAffected.forEach [ x |
var HashMap<String, ?> fixture = fixtures.get(x)
logInfo("motion_tripped", " affected fixture: " + x)
var Timer timer = fixture.get("timer") as Timer
if (timer != null) {
var dt = fixture.get("timeout") as Integer
timer.reschedule(now.plusSeconds(dt))
logInfo("motion_tripped", " restarted timer for: " + x + " to +" + dt + "sec")
} else {
var Timer restoreTimer = fixture.get("restore_timer") as Timer
if (restoreTimer != null || ((fixture.get("on_when_dark") as Boolean) && isdark)) {
// Motion happened right after light was switched off, or auto_on in the dark is requested.
// Switch light on!
logInfo("motion_tripped", " actively switching on fixture: " + x)
var ArrayList<SwitchItem> sl = fixture.get("switches") as ArrayList<SwitchItem>
sl.forEach [ ts |
logInfo("motion_tripped", " turning on switch: " + ts.name)
sendCommand(ts, ON) ]
if (restoreTimer != null) restoreTimer.cancel
fixture.put("restore_timer", null) // clear restore timer
} else {
logInfo("motion_tripped", " no reason to switch on fixture: " + x)
}
}
]
} else {
logInfo("motion_tripped", " tripped sensor " + sensor + " does not affect any lights")
}
]
logInfo("motion_tripped", "done handling " + faultedSensors.size + " tripped sensors")
lock.unlock()
end
rule "switch flipped"
when
Item gMotionDimmers changed or
Item gMotionSwitches changed
then
logInfo("switch_flipped", "motion timed switch or dimmer changed state!")
// first build the map from switch to fixtures
// XXX silly to do this every time the rule executes, but did not want to introduce another
// static map that would have to be maintained
var HashMap<String, ArrayList<String>> sToF = new HashMap<String, ArrayList<String>>()
for (f : fixtures.entrySet()) {
var ArrayList<SwitchItem> swl = f.getValue().get("switches") as ArrayList<SwitchItem>
for (SwitchItem si : swl) {
var fl = sToF.get(si.name)
if (fl == null) {
fl = new ArrayList<String>()
sToF.put(si.name, fl)
}
fl.add(f.getKey())
}
}
val switchToFixture = sToF; // make it final so we can use it in closure
//
// now iterate through members
//
lock.lock()
(gMotionSwitches.members + gMotionDimmers.members).forEach [ sw |
var ArrayList<String> fixtureStrings = switchToFixture.get(sw.name)
fixtureStrings.forEach [ x |
// The acceptedDataTypes.get(0) is a terrible hack. For
// whatever reason it won't accept a plain OnOffType here.
var stateOnOff = sw.getStateAs(sw.acceptedDataTypes.get(0))
var f = fixtures.get(x)
var Timer timer = f.get("timer") as Timer
var Integer timeOut = f.get("timeout") as Integer
if (stateOnOff == ON) {
if (timer == null) { // start timer for the corresponding fixture
var texp = now.plusSeconds(timeOut)
logInfo("switch_flipped", " creating timer for " + x + " in " + timeOut + "sec" )
timer = createTimerWithArgument(texp, x,
[ k |
logInfo("switch_timeout", "timer expired, switching off " + k)
lock.lock()
var ft = fixtures.get(k);
var ArrayList<SwitchItem> sl = ft.get("switches") as ArrayList<SwitchItem>
sl.forEach [ ts |
logInfo("switch_timeout", " turning off switch: " + ts.name)
sendCommand(ts, OFF)
]
ft.put("timer", null) // clear timer
// start restore timer
logInfo("switch_timeout", " starting restore timer for " + k)
var tExpRest = now.plusSeconds(20)
var Timer restoreTimer =
createTimerWithArgument(tExpRest, k,
[xr |
logInfo("restore__timer", "restore timer expired for " + xr)
lock.lock()
var rft = fixtures.get(xr);
rft.put("restore_timer", null);
lock.unlock()
])
ft.put("restore_timer", restoreTimer)
lock.unlock()
])
f.put("timer", timer) // put it in map so it can be tracked
} else {
logInfo("switch_flipped", "already have timer running for " + x)
}
}
]
]
logInfo("switch_flipped", "motion timed switch change state finished!")
lock.unlock()
end
###Linux / OS X
###Windows
- Cosm Persistence
- db4o Persistence
- Exec Persistence
- InfluxDB Persistence
- JDBC Persistence
- JPA Persistence
- Logging Persistence
- mapdb Persistence
- MongoDB Persistence
- MQTT Persistence
- my.openHAB Persistence
- MySQL Persistence
- rrd4j Persistence
- Sen.Se Persistence
- SiteWhere Persistence
- AlarmDecoder Binding
- Anel Binding
- Arduino SmartHome Souliss Binding
- Asterisk Binding
- Astro Binding
- Autelis Pool Control Binding
- BenQ Projector Binding
- Bluetooth Binding
- Bticino Binding
- CalDAV Binding
- Comfo Air Binding
- Config Admin Binding
- CUL Binding
- CUL Intertechno Binding
- CUPS Binding
- DAIKIN Binding
- Davis Binding
- Denon Binding
- digitalSTROM Binding
- DMX512 Binding
- DSC Alarm Binding
- DSMR Binding
- eBUS Binding
- Ecobee Binding
- EDS OWSever Binding
- eKey Binding
- Energenie Binding
- EnOcean Binding
- Enphase Energy Binding
- Epson Projector Binding
- Exec Binding
- Freebox Binding
- Freeswitch Binding
- Frontier Silicon Radio Binding
- Fritz AHA Binding
- Fritz!Box Binding
- FS20 Binding
- Global Cache IR Binding
- GPIO Binding
- HAI/Leviton OmniLink Binding
- HDAnywhere Binding
- Heatmiser Binding
- Homematic / Homegear Binding
- HTTP Binding
- IEC 62056-21 Binding
- IHC / ELKO Binding
- ImperiHome Binding
- Insteon Hub Binding
- Insteon PLM Binding
- IPX800 Binding
- IRtrans Binding
- jointSPACE-Binding
- KNX Binding
- Koubachi Binding
- LCN Binding
- LightwaveRF Binding
- Leviton/HAI Omnilink Binding
- Lg TV Binding
- Logitech Harmony Hub
- MailControl Binding
- MAX!Cube-Binding
- MAX! CUL Binding
- MiLight Binding
- MiOS Binding
- Modbus TCP Binding
- MPD Binding
- MQTT Binding
- MQTTitude binding
- Neohub Binding
- Nest Binding
- Netatmo Binding
- Network Health Binding
- Network UPS Tools Binding
- Nibe Heatpump Binding
- Nikobus Binding
- Novelan/Luxtronic Heatpump Binding
- NTP Binding
- One-Wire Binding
- Onkyo AV Receiver Binding
- Open Energy Monitor Binding
- OpenPaths presence detection binding
- OpenSprinkler Binding
- OSGi Configuration Admin Binding
- Panasonic TV Bindung
- panStamp Binding
- Philips Hue Binding
- Piface Binding
- pilight Binding
- Pioneer-AVR-Binding
- Plex Binding
- Plugwise Binding
- PLCBus Binding
- Primare Binding
- Pulseaudio Binding
- RFXCOM Binding
- RWE Smarthome Binding
- Sager WeatherCaster Binding
- Samsung AC Binding
- Samsung TV Binding
- Serial Binding
- Sallegra Binding
- Satel Alarm Binding
- Sinthesi Sapp Binding
- Snmp Binding
- Somfy URTSI II Binding
- Sonos Binding
- Squeezebox Binding
- Swegon ventilation Binding
- System Info Binding
- TA CMI Binding
- TCP/UDP Binding
- Tellstick Binding
- TinkerForge Binding
- Tivo Binding
- VDR Binding
- Velleman-K8055-Binding
- Wago Binding
- Wake-on-LAN Binding
- Waterkotte EcoTouch Heatpump Binding
- Weather Binding
- Wemo Binding
- Withings Binding
- XBMC Binding
- xPL Binding
- Yamahareceiver Binding
- Zibase Binding
- Z-Wave Binding
- Asterisk
- Google Calendar
- Linux Media Players
- ROS Robot Operating System
- Telldus Tellstick
- Zoneminder
- Wink Hub (rooted)
- Wink Monitoring
- Transformations
- XSLT
- JSON
- REST-API
- Security
- Service Discovery
- Voice Control
- BritishGasHive-Using-Ruby
- Dropbox Bundle
A good source of inspiration and tips from users gathered over the years. Be aware that things may have changed since they were written and some examples might not work correctly.
Please update the wiki if you do come across any out of date information.
- Comfo Air Binding
- Ecobee Examples
- Nest Examples
- Rollershutter Bindings
- Squeezebox
- WAC Binding
- WebSolarLog
- Alarm Clock
- Convert Farenheit to Celcius
- The mother of all lighting rules
- Reusable Rules via Functions
- Combining different Items
- Items, Rules and more Examples of a SmartHome
- Google Map
- Controlling openHAB with Android
- Usecase examples
- B-Control Manager
- Spell checking for foreign languages
- Flic via Tasker
- Chromecast via castnow