I was getting tired of my old Nexa Wireless 400MHz uni-directional system were I had several dimmers paired to a single wall-remote. Almost every time I wanted to turn the group on or off one or more of the dimmers failed to receive the signal. Had to move my body away from the signal path even though it was just 2m away. The WAF was falling too, so I had to do something …
I opted for Z-wave HW as the availability is great ‘over the counter’ in my area.
So, to decide what SW to put on my Shuttle XS35G Ubuntu box…?
Took a demo license of HS3 for a spin, but was not too exited with the GUI and the cost of the license.
After discussing this with a co-worker, he recommended openHAB and I have not looked back since.
This was 25. February 2017.
After installing Ubuntu Server 16.04 LTS on my Shuttle, I followed the setup tutorial and was up and running in less than a day. I ended up using Paper UI for including new Z-wave items, Eclipse SmartHome Designer for configuration, HABmin for troubleshooting and HABdroid for remote control.
Since I was already a bit Nexa-centric I started with a few Nexa nodes, the AeonTech USB Z-stick (gen5), 2 NodOn wall remotes and a few Aeon MultiSensor 6.
Using the Nexa nodes ruled out using openHAB 2.0 Stable as it has issues with the Alarm channel. It sort of worked, but the log was full of errors. Going snapshot fixed that.
So, I will attach my site-map, items and rules files as a starting point for other fellow beginners.
It will show examples of:
- NodOn wall remote events
- Movement detection events (with multiple OFF filter)
- Time of day timer events (motor block heater ON MON-FRI)
- Time of day rule filter (motion at night turning lights on dimmed)
- Z-wave node groups, and group commands
- Humidity level turning fan to MAX
Site MAP:
sitemap GTV7 label=“Me Casa”
{
Frame label=“Soner” {
Group item=groupSwitches label=“Soner Av/På” icon=“firstfloor”
}
Frame label=“Lys” {
Group item=livingRoom label=“Stua” icon=“firstfloor”
Group item=kitchen label=“Kjøkken” icon=“firstfloor”
Group item=diningRoom label=“Spisestue” icon=“firstfloor”
}Frame label=“Annet” {
Group item=div label=“Diverse” icon=“settings”
Group item=lanItems label=“LAN” icon=“settings”
Group item=sensors label=“Sensorer” icon=“settings”
}
}
Items:
Group kitchen
Group livingRoom
Group diningRoom
Group switches
Group lights
Group sensors
Group remotes
Group div
Group lanItems
Group groupSwitches
Dimmer WallplugStuaCornerLamp “Lampe (sofa)” (livingRoom, lights) { channel = “zwave:device:3f5c8071:node13:switch_dimmer” }
Dimmer WallplugStuaShelfLamp “Lampett (hylle)” (livingRoom, lights) { channel = “zwave:device:3f5c8071:node10:switch_dimmer” }
Dimmer WallplugStuaWooferLamp “Lampett (woofer)” (livingRoom, lights) { channel = “zwave:device:3f5c8071:node8:switch_dimmer” }
Dimmer WallplugStuaPianoLamp “Pianolampe” (livingRoom, lights) { channel = “zwave:device:3f5c8071:node9:switch_dimmer” }
Dimmer BuildInStuaWindowLamps “Karnapp” (diningRoom, lights) { channel = “zwave:device:3f5c8071:node36:switch_dimmer” }
Dimmer BuildInKjDiningTableLamp “Pendel (spisebord)” (diningRoom, lights) { channel = “zwave:device:3f5c8071:node18:switch_dimmer” }
Dimmer WallplugKjCornerLamp “Lampe (spisestue)” (diningRoom, lights) { channel = “zwave:device:3f5c8071:node14:switch_dimmer” }
Dimmer BuildInKjRoofBenchLamp “Tak (kjøkkenbenk)” (kitchen, lights) { channel = “zwave:device:3f5c8071:node16:switch_dimmer” }
Dimmer BuildInKjRoofIslandLamp “Tak (kjøkkenøy)” (kitchen, lights) { channel = “zwave:device:3f5c8071:node17:switch_dimmer” }
Switch WallplugKjokkenPCBenchLamp “Benk (PCer)” (kitchen, lights) { channel = “zwave:device:3f5c8071:node3:switch_binary” }
Switch WallplugClosetLamp “Kott (trapp)” (div) { channel = “zwave:device:3f5c8071:node31:switch_binary” }
Switch BuildInKjokkenBench “Benk (overskap)” (kitchen, lights) { channel = “zwave:device:3f5c8071:node15:switch_binary” }
Dimmer BuildInMasterBathRoof “Tak (bad/oppe)” (div) { channel = “zwave:device:3f5c8071:node35:switch_dimmer” }
Switch BuildInMasterBathMirror “Speil (bad/oppe)” (div) { channel = “zwave:device:3f5c8071:node34:switch_binary” }
Switch BuildInWashingRoom “Tak (vaskerom)” (div) { channel = “zwave:device:3f5c8071:node32:switch_binary” }
Number ClosetTemperature “Temperatur (kott)” (sensors) { channel = “zwave:device:3f5c8071:node7:sensor_temperature” }
Number ClosetHumidity “Fuktighet (kott)” (sensors) { channel = “zwave:device:3f5c8071:node7:sensor_relhumidity” }
Switch ClosetMovement “Bevegelse (kott)” { channel = “zwave:device:3f5c8071:node7:sensor_binary” }
Number MasterBathTemperature “Temperatur (bad/oppe)” (sensors) { channel = “zwave:device:3f5c8071:node29:sensor_temperature” }
Number MasterBathHumidity “Fuktighet (bad/oppe)” (sensors) { channel = “zwave:device:3f5c8071:node29:sensor_relhumidity” }
Switch MasterBathMovement “Bevegelse (bad/oppe)” { channel = “zwave:device:3f5c8071:node29:sensor_binary” }
Number BathTemperature “Temperatur (bad/nede)” (sensors) { channel = “zwave:device:3f5c8071:node24:sensor_temperature” }
Number BathHumidity “Fuktighet (bad/nede)” (sensors) { channel = “zwave:device:3f5c8071:node24:sensor_relhumidity” }
Switch BathMovement “Bevegelse (bad/nede)” { channel = “zwave:device:3f5c8071:node24:sensor_binary” }
Number WashingRoomTemperature “Temperatur (vaskerom)” (sensors) { channel = “zwave:device:3f5c8071:node30:sensor_temperature” }
Number WashingRoomHumidity “Fuktighet (vaskerom)” (sensors) { channel = “zwave:device:3f5c8071:node30:sensor_relhumidity” }
Switch WashingRoomMovement “Bevegelse (vaskerom)” { channel = “zwave:device:3f5c8071:node30:sensor_binary” }
Switch WallplugHeaterLexus “Motorvarmer Lexus” (div, switches) { channel = “zwave:device:3f5c8071:node12:switch_binary” }
Switch WallplugReceiver “Receiver (loft)” (div, switches) { channel = “zwave:device:3f5c8071:node11:switch_binary” }
Switch WallplugTV “TV” (div, switches) { channel = “zwave:device:3f5c8071:node4:switch_binary” }
Switch FanS1 “Vifte av/1” (div, switches) { channel = “zwave:device:3f5c8071:node28:switch_binary” }
Switch FanS2 “Vifte 2” (div, switches) { channel = “zwave:device:3f5c8071:node26:switch_binary” }
Switch FanS3 “Vifte 3” (div, switches) { channel = “zwave:device:3f5c8071:node27:switch_binary” }
Number WallSwitchStua “Bryter (stua)” (div, remotes) { channel = “zwave:device:3f5c8071:node22:scene_number” }
Number WallSwitchKjokken “Bryter (kjøkken)” (div, remotes) { channel = “zwave:device:3f5c8071:node6:scene_number” }
Switch PhoneOMR “OMR on LAN” (lanItems) { channel = “network:device:192_168_1_50:online” }
Switch PhoneMDY “MDY on LAN” (lanItems) { channel = “network:device:192_168_1_51:online” }
Switch PhoneMJR “MJR on LAN” (lanItems) { channel = “network:device:192_168_1_52:online” }
Switch Onkyo “Receiver on LAN” (lanItems) { channel = “network:device:192_168_1_30:online” }
Switch All “Alle” (groupSwitches)
Switch AllLR “Stue” (groupSwitches)
Switch AllDR “Spisestue” (groupSwitches)
Switch AllK “Kjøkken” (groupSwitches)
String MotionTimeOn “Trappekott ON[%s]” (sensors)
String MotionTimeOff “Trappekott OFF[%s]” (sensors)
String MasterBathMotionTimeOn “Bad oppe ON[%s]” (sensors)
String MasterBathMotionTimeOff “Bad oppe OFF[%s]” (sensors)
String BathMotionTimeOn “Bad nede ON[%s]” (sensors)
String BathMotionTimeOff “Bad nede OFF[%s]” (sensors)
String WashingRoomMotionTimeOn “Vaskerom ON[%s]” (sensors)
String WashingRoomMotionTimeOff “Vaskerom OFF[%s]” (sensors)
Dimmer SqueezeBoxRadio “Radio (bad/oppe) [%.1f %%]” (div) { channel = “squeezebox:squeezeboxplayer:2C50A8B2-B856-484B-A862-344F14F36ED9:0004202641ce:volume” }
Wall-switch rules (aka remote)
//import java.time.LocalDateTime
import java.time.*
//import org.openhab.core.library.types.*
rule "All"
when
Item All received command
then
sendCommand(lights, if (All.state==ON) ON else OFF);
AllLR.state=All.state
AllK.state=All.state
AllDR.state=All.state
end
rule "AllLR"
when
Item AllLR received command
then
sendCommand(livingRoom, if (AllLR.state==ON) ON else OFF);
end
rule "AllDR"
when
Item AllDR received command
then
sendCommand(diningRoom, if (AllDR.state==ON) ON else OFF);
end
rule "AllK"
when
Item AllK received command
then
sendCommand(kitchen, if (AllK.state==ON) ON else OFF);
end
rule "WallSwitchLivingRoom"
when
Item WallSwitchStua received update
then
var swState = WallSwitchStua.state
logInfo("RemoteNodon22", "Rule: Got: " + swState)
Thread::sleep(150)
switch swState {
case 1.0: {logInfo("RemoteNodon22", "Rule: Button1 SingleClick " + swState) sendCommand(lights, if (BuildInKjokkenBench.state==OFF) ON else OFF);
lights.state = if (BuildInKjokkenBench.state==OFF) ON else OFF
logInfo("RemoteNodon22", "lights.state=" + lights.state)
All.state=lights.state
AllLR.state=lights.state
AllK.state=lights.state
AllDR.state=lights.state
}
case 1.1: {logInfo("RemoteNodon22", "Rule: Button1 Release " + swState)}
case 1.2: {logInfo("RemoteNodon22", "Rule: Button1 Hold-down " + swState)}
case 1.3: {logInfo("RemoteNodon22", "Rule: Button1 DoubleClick " + swState)}
case 2.0: {logInfo("RemoteNodon22", "Rule: Button2 SingleClick " + swState) sendCommand(livingRoom, if (WallplugStuaPianoLamp.state==0) ON else OFF);
livingRoom.state = if (WallplugStuaPianoLamp.state==OFF) ON else OFF
logInfo("RemoteNodon22", "livingRoom.state=" + lights.state)
AllLR.state=livingRoom.state
}
case 2.1: {logInfo("RemoteNodon22", "Rule: Button2 Release " + swState)}
case 2.2: {logInfo("RemoteNodon22", "Rule: Button2 Hold-down " + swState)}
case 2.3: {logInfo("RemoteNodon22", "Rule: Button2 DoubleClick " + swState)}
case 3.0: {logInfo("RemoteNodon22", "Rule: Button3 SingleClick " + swState)}
case 3.1: {logInfo("RemoteNodon22", "Rule: Button3 Release " + swState)}
case 3.2: {logInfo("RemoteNodon22", "Rule: Button3 Hold-down " + swState)}
case 3.3: {logInfo("RemoteNodon22", "Rule: Button3 DoubleClick " + swState)}
case 4.0: {logInfo("RemoteNodon22", "Rule: Button4 SingleClick " + swState)
}
case 4.1: {logInfo("RemoteNodon22", "Rule: Button4 Release " + swState)}
case 4.2: {logInfo("RemoteNodon22", "Rule: Button4 Hold-down " + swState)}
case 4.3: {logInfo("RemoteNodon22", "Rule: Button4 DoubleClick " + swState)}
default: {logInfo("RemoteNodon22", "Rule: Say what? " + swState)}
}
end
rule "WallSwitchKitchen"
when
Item WallSwitchKjokken received update
then
var swState = WallSwitchKjokken.state
logInfo("RemoteNodon6", "Rule: Got: " + swState)
Thread::sleep(150)
switch swState {
case 1.0: {
logInfo("RemoteNodon6", "Rule: Button1 SingleClick " + swState) sendCommand(lights, if (BuildInKjokkenBench.state==OFF) ON else OFF)
lights.state = if (BuildInKjokkenBench.state==OFF) ON else OFF
logInfo("RemoteNodon6", "lights.state=" + lights.state)
All.state=lights.state
AllLR.state=lights.state
AllK.state=lights.state
AllDR.state=lights.state
}
case 1.1: {logInfo("RemoteNodon6", "Rule: Button1 Release " + swState)}
case 1.2: {logInfo("RemoteNodon6", "Rule: Button1 Hold-down " + swState)}
case 1.3: {logInfo("RemoteNodon6", "Rule: Button1 DoubleClick " + swState)}
case 2.0: {logInfo("RemoteNodon6", "Rule: Button2 SingleClick " + swState) sendCommand(kitchen, if (BuildInKjokkenBench.state==OFF) ON else OFF)
kitchen.state = if (BuildInKjokkenBench.state==OFF) ON else OFF
logInfo("RemoteNodon6", "kitchen.state=" + kitchen.state)
AllK.state=kitchen.state
}
case 2.1: {logInfo("RemoteNodon6", "Rule: Button2 Release " + swState)}
case 2.2: {logInfo("RemoteNodon6", "Rule: Button2 Hold-down " + swState)}
case 2.3: {logInfo("RemoteNodon6", "Rule: Button2 DoubleClick " + swState)}
case 3.0: {logInfo("RemoteNodon6", "Rule: Button3 SingleClick " + swState)
logInfo("RemoteNodon6", "Date " + LocalDateTime.now())
MotionTimeOn.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
// MotionTime.postUpdate("Heia")
}
case 3.1: {logInfo("RemoteNodon6", "Rule: Button3 Release " + swState)}
case 3.2: {logInfo("RemoteNodon6", "Rule: Button3 Hold-down " + swState)}
case 3.3: {logInfo("RemoteNodon6", "Rule: Button3 DoubleClick " + swState)}
case 4.0: {logInfo("RemoteNodon6", "Rule: Button4 SingleClick " + swState) sendCommand(diningRoom, if (BuildInKjDiningTableLamp.state==0) ON else OFF)
diningRoom.state = if (BuildInKjDiningTableLamp.state==0) ON else OFF
logInfo("RemoteNodon6", "BuildInKjDiningTableLamp.state=" + BuildInKjDiningTableLamp.state)
logInfo("RemoteNodon6", "diningRoom.state=" + diningRoom.state)
AllDR.state=diningRoom.state
}
case 4.1: {logInfo("RemoteNodon6", "Rule: Button4 Release " + swState)}
case 4.2: {logInfo("RemoteNodon6", "Rule: Button4 Hold-down " + swState)}
case 4.3: {logInfo("RemoteNodon6", "Rule: Button4 DoubleClick " + swState)}
default: {logInfo("RemoteNodon6", "Rule: Say what? " + swState)}
}
end
Motion rules:
import java.time.LocalTime
import java.time.LocalDate
var boolean onLR = false
var boolean onMBR = false
var boolean onBR = false
var boolean onVR = false
val int masterBathDimlevelDay = 35
val int masterBathDimlevelNight = 4
val int masterBathVolumeDay = 25
val int masterBathVolumeNight = 5
val int dayStartHour = 07
val int dayStartMin = 00
val int dayEndHour = 23
val int dayEndMin = 30
rule "MotionCloset"
when
Item ClosetMovement received update
then
logInfo("Motion", "ClosetMovement.state=" + ClosetMovement.state)
if (ClosetMovement.state==ON) {
onLR = true
MotionTimeOn.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
sendCommand(WallplugClosetLamp, ON)
} else {
if (onLR) {
onLR = false
MotionTimeOff.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
sendCommand(WallplugClosetLamp, OFF)
}
}
end
rule "MotionMasterBad"
when
Item MasterBathMovement received update
then
var LocalTime start = LocalTime.of( dayStartHour , dayStartMin );
var LocalTime stop = LocalTime.of( dayEndHour , dayEndMin );
var Boolean isNowOnOrAfterStart = ( ! LocalTime.now().isBefore( start ) ) ; // A briefer way of asking "is equal to OR is after" is "is not before".
var Boolean isNowBeforeStop = LocalTime.now().isBefore( stop );
var Boolean isNowInTargetZone = ( isNowOnOrAfterStart && isNowBeforeStop ); // Half-Open: beginning is inclusive while ending is exclusive.
logInfo("Motion", "MasterBathMovement.state=" + MasterBathMovement.state)
if (MasterBathMovement.state==ON) {
onMBR = true
MasterBathMotionTimeOn.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
if (isNowInTargetZone) {
logInfo("Light", "Turning on Mirror light, dimming roof light to " + masterBathDimlevelDay + "%")
sendCommand(BuildInMasterBathMirror, ON)
sendCommand(BuildInMasterBathRoof, masterBathDimlevelDay)
logInfo("Motion", "Increasing radio volume")
sendCommand(SqueezeBoxRadio, masterBathVolumeDay)
logInfo("Fan", "Fan speed2 ON")
sendCommand(FanS2, ON)
} else {
sendCommand(BuildInMasterBathRoof, masterBathDimlevelNight)
logInfo("Motion", "It's night, do not increase radio volume")
logInfo("Fan", "It's night, do not turn on fan")
logInfo("Light", "It's night, No mirror light, dimming roof light to " + masterBathDimlevelNight + "%")
}
} else {
if (onMBR) {
onMBR = false
MasterBathMotionTimeOff.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
logInfo("Motion", "Muting radio volume")
sendCommand(SqueezeBoxRadio, masterBathVolumeNight)
logInfo("Light", "Turning off lights")
sendCommand(BuildInMasterBathMirror, OFF)
sendCommand(BuildInMasterBathRoof, OFF)
if (!onMBR && !onBR && !onVR) {
logInfo("Fan", "Fan speed2 OFF")
sendCommand(FanS2, OFF)
} else {
logInfo("Motion", "One of FAN controllers is still active, postponing switch-off")
}
}
}
end
rule "MotionNedeBad"
when
Item BathMovement received update
then
var LocalTime start = LocalTime.of( dayStartHour , dayStartMin );
var LocalTime stop = LocalTime.of( dayEndHour , dayEndMin );
var Boolean isNowOnOrAfterStart = ( ! LocalTime.now().isBefore( start ) ) ; // A briefer way of asking "is equal to OR is after" is "is not before".
var Boolean isNowBeforeStop = LocalTime.now().isBefore( stop );
var Boolean isNowInTargetZone = ( isNowOnOrAfterStart && isNowBeforeStop ); // Half-Open: beginning is inclusive while ending is exclusive.
logInfo("Motion", "BathMovement.state=" + BathMovement.state)
if (BathMovement.state==ON) {
onBR = true
BathMotionTimeOn.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
if (isNowInTargetZone) {
logInfo("Fan", "Fan speed2 ON")
sendCommand(FanS2, ON)
} else {
logInfo("Fan", "It's night, do not turn on fan")
}
} else {
if (onBR) {
onBR = false
BathMotionTimeOff.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
if (!onMBR && !onBR && !onVR) {
logInfo("Fan", "Fan speed2 OFF")
sendCommand(FanS2, OFF)
} else {
logInfo("Motion", "One of FAN controllers is still active, postponing switch-off")
}
}
}
end
rule "MotionVaskerom"
when
Item WashingRoomMovement received update
then
var LocalTime start = LocalTime.of( dayStartHour , dayStartMin );
var LocalTime stop = LocalTime.of( dayEndHour , dayEndMin );
var Boolean isNowOnOrAfterStart = ( ! LocalTime.now().isBefore( start ) ) ; // A briefer way of asking "is equal to OR is after" is "is not before".
var Boolean isNowBeforeStop = LocalTime.now().isBefore( stop );
var Boolean isNowInTargetZone = ( isNowOnOrAfterStart && isNowBeforeStop ); // Half-Open: beginning is inclusive while ending is exclusive.
logInfo("Motion", "WashingRoomMovement.state=" + WashingRoomMovement.state)
if (WashingRoomMovement.state==ON) {
onVR = true
WashingRoomMotionTimeOn.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
sendCommand(BuildInWashingRoom, ON)
if (isNowInTargetZone) {
logInfo("Fan", "Fan speed2 ON")
sendCommand(FanS2, ON)
} else {
logInfo("Fan", "It's night, do not turn on fan")
}
} else {
if (onVR) {
onVR = false
WashingRoomMotionTimeOff.postUpdate(LocalTime.now().toString() + " (" + LocalDate.now().toString() + ")")
sendCommand(BuildInWashingRoom, OFF)
if (!onMBR && !onBR && !onVR) {
logInfo("Fan", "Fan speed2 OFF")
sendCommand(FanS2, OFF)
} else {
logInfo("Motion", "One of FAN controllers is still active, postponing switch-off")
}
}
}
end
Timer rules:
//import org.openhab.core.library.types.*
rule “MotorvarmerOn”
when
Time cron “0 0 6 ? * MON-FRI *” // 04:00 ma-fr
// Time cron “0 0 8 ? * * *” // 07:00
then
sendCommand(WallplugHeaterLexus, ON)
end
rule “MotorvarmerOff”
when
Time cron “0 0 9 ? * * *” // Every day
then
sendCommand(WallplugHeaterLexus, OFF)
end
rule “ReceiverOff”
when
Time cron “0 0 3 ? * * *” // Every day
then
sendCommand(WallplugReceiver, OFF)
end
rule “ReceiverOn”
when
Time cron “0 0 6 ? * * *” // Every day
then
sendCommand(WallplugReceiver, ON)
end
And finally my Fan rules:
var boolean on = false
rule "FanS3"
when
Item MasterBathHumidity received update or
Item BathHumidity received update or
Item WashingRooomHumidity received update
then
if ((MasterBathHumidity.state > 50) || (BathHumidity.state > 50) || (WashingRoomHumidity.state > 50)) {
logInfo("Fan", "Fan speed3 ON")
sendCommand(FanS3, ON);
on = true;
} else {
if (on) {
on = false
logInfo("Fan", "Fan speed3 OFF")
sendCommand(FanS3, OFF);
}
}
end
Note that the fan is controlled by both motion and humidity from 3 locations, 2 baths and a washing room.
There is logic so that a motion OFF signal will not turn the fan off too early if another room just started it’s motion ON period. Motion sensor OFF-ON period is set to 10min.
Note also, that I have no persistence, but every motion ON and OFF generates a text string variable timestamp items in a group that the sitemap shows.
Hope this can inspire others and maybe trigger some feedback on things I have gotten wrong