Category Archives: OpenHab

Hive Motion Sensor and OpenHab2

Recently I’ve started adding ZigBee based devices to my home automation solution – specifically I’ve added the Husbzb-1 stick to my setup. This has the benefit of combining both a Z-Wave and Zigbee controller in the same physical device, so I get to be agnostic as to the devices I add.

My next step was to try out a Hive Motion Sensor, for no other reason then there was one in the local store and a quick Google showed that it was Zigbee based.

This device is interesting in terms of form factor – suits what I was planning quite well, and the channels it provides;

  • Motion Intrusion (_intrusion) – Switch
  • Temperature (_temperature) – Number:Temperature
  • Tamper (_tamper) – Switch
  • Motion Presence (_motion) – Switch
  • Low Battery (_iaslowbattery) – Switch
  • Battery Voltage (_batteryvoltage) – Number
  • Illuminance (_illuminance) – Number

So I actually ended up with more then I expected since neither the illuminance nor the temperature are referenced in any documentation that I can find. I’ve no clue what the difference between Motion Intrusion and Motion Presence are though – both seem to trigger at the same time… will update when I learn more.

The other interesting thing is that on the very first trigger, the state is updated from NULL to ON for presence (_intrusion and _motion), but the sensor itself never seems to trigger a state change to OFF. In order to handle this, I’ve defined an item, subscribed to the _motion channel, and then a rule to reset it back to off after 5 mins.

var Timer vFrontDoor_holdDown  = null

rule "Reset Front Door trigger"
when
    Item FrontDoorMotion changed to ON
then
    if(vFrontDoor_holdDown === null || vFrontDoor_holdDown.hasTerminated){
        vFrontDoor_holdDown = createTimer(now.plusMinutes(5), [| 
            postUpdate(FrontDoorMotion, OFF)
            vFrontDoor_holdDown = null
        ])
    }
    else{
        vFrontDoor_holdDown.reschedule(now.plusMinutes(5)) 
    }
end

Wifi Controlled Relay and OpenHab

Items used:

  • LOLIN D1 mini V3.1.0 (ESP8266)
  • LOLIN Relay Shield V2.0.0
// Load Wi-Fi library
#include <ESP8266WiFi.h>

// Replace with your network credentials
const char* ssid     = "mySSID";
const char* password = "VerySecret";

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Auxiliary variables to store the current output state
String relayState = "OFF";
// we will have the relay on 5
const int relay = 5;


void setup() {
  Serial.begin(115200);
  // Initialize the output variables as outputs
  pinMode(relay, OUTPUT);
  // set the relay off by default
  digitalWrite(relay, LOW);
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  // we match the LED state to the relay state
  digitalWrite(LED_BUILTIN, HIGH);  // High is off, low is on

  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}
void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/plain");
            client.println("Connection: close");


            // turns the GPIOs on and off
            if (header.indexOf("GET /relay/on") >= 0) {
              Serial.println("Relay on");
              relayState = "ON";
              digitalWrite(LED_BUILTIN, LOW);  // High is off, low is on
              digitalWrite(relay, HIGH);
            } else if (header.indexOf("GET /relay/off") >= 0) {
              Serial.println("Relay off");
              relayState = "OFF";
              digitalWrite(relay, LOW);
              digitalWrite(LED_BUILTIN, HIGH);  // High is off, low is on
            }
            else if (header.indexOf("GET /relay/status") >= 0) {
              Serial.print("Sending status: ");
              Serial.println(relayState);
              
              client.println();
              client.print(relayState);
              client.println();
              
            }
            client.println();
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    delay(1);
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}        
  

Now for the OpenHab side

Here, I’m making use of the HTTP binding (and the REGEX mapping), and we basically have 3 calls we need to handle

  • On
  • Off
  • Status

And so our items definition is simple to handle those 3 cases:

Switch MyRelay "MyRelay" <switch> ( gSwitches, gRelay ) { http=">[ON:GET:http://<IP Address>/relay/on] >[OFF:GET:http://<IP Address>/relay/off] <[http://<IP Address>/relay/status:10000:REGEX((.*))]"}

And that is basically it – I can now control the on/off of my relays, and check on the current status.

Conclusion / Finishing Thoughts

Some obvious points to consider are:

  • Lack of security – anything that can reach the IPs of the relays can manipulate their state
  • Lack of rate limiting – a follow up from the 1st point is that we don’t do any cooldowns or rate limiting to prevent accident or maliciousness from repeatedly toggling the relays, potentially damaging the relay and worse, the appliance
  • Updating the relays – requires them to be disconnected and connected via USB to a PC/programmer

OpenWeatherMap and OpenHab

With the end of the YahooWeather API, I signed up for some API keys from OpenWeatherMap’s free tier, but had real trouble getting the default weather binding to work. After many wasted hours puzzling over the following error message, I eventually found this thread.

[ERROR] [nal.provider.AbstractWeatherProvider] - OPENWEATHERMAP[home]: Can't retreive weather data: Invalid API key. Please see http://openweathermap.org/faq#error401 for more info.

The error is correct, but uninformative – the issue is that default binding is calling on the “paid for” API rather then the free one, so yes my free API key is invalid for the API call I’m making. Unfortunately that is less then informative when testing your API keys in a browser and receiving valid JSON!!

The solution for me was to add org.openhab.binding.weather-1.13.0-SNAPSHOT.jar to my addons directory, then using the following weather.items I was up and running:

Group gWeather

// atmosphere
Number   Humidity		  "Humidity [%d %%]"	 (gWeather) {weather="locationId=home, type=atmosphere, property=humidity"}
Number   Visibility		  "Visibility [%.2f km]" (gWeather) {weather="locationId=home, type=atmosphere, property=visibility"}
Number   Visibility_Mph   "Visibility [%.2f mi]" (gWeather) {weather="locationId=home, type=atmosphere, property=visibility, unit=mph"}
Number   Pressure		  "Pressure [%.2f mb]"   (gWeather) {weather="locationId=home, type=atmosphere, property=pressure"}
Number   Pressure_Inches  "Pressure [%.2f in]"   (gWeather) {weather="locationId=home, type=atmosphere, property=pressure, unit=inches"}
String   Pressure_Trend   "Pressuretrend [%s]"   (gWeather) {weather="locationId=home, type=atmosphere, property=pressureTrend"}
Number   Ozone            "Ozone [%d ppm]"		 (gWeather) {weather="locationId=home, type=atmosphere, property=ozone"}
Number   UV_Index         "UV Index"              (gWeather){weather="locationId=home, type=atmosphere, property=uvIndex, scale=0"}

// clouds
Number   Clouds   "Clouds [%.0f %%]"  (gWeather) {weather="locationId=home, type=clouds, property=percent"}

// condition
String   Condition        "Condition [%s]"     (gWeather) {weather="locationId=home, type=condition, property=text"}
String   Condition_ID     "Condition id [%s]"  (gWeather) {weather="locationId=home, type=condition, property=id"}
DateTime ObservationTime  "Observation time [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"  (gWeather) {weather="locationId=home, type=condition, property=observationTime"}
DateTime LastUpdate       "Last update [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"      (gWeather)  {weather="locationId=home, type=condition, property=lastUpdate"}
String   CommonId         "Common id [%s]"     (gWeather) {weather="locationId=home, type=condition, property=commonId"}
Image    LocalCurrentConditionIcon "Icon" (gWeather) { weather="locationId=home, type=condition, property=icon-id" }

// precipitation
Number   Rain          "Rain [%.2f mm/h]"  (gWeather) {weather="locationId=home, type=precipitation, property=rain"}
Number   Rain_Inches   "Rain [%.2f in/h]"  (gWeather) {weather="locationId=home, type=precipitation, property=rain, unit=inches"}
Number   Snow          "Snow [%.2f mm/h]"  (gWeather) {weather="locationId=home, type=precipitation, property=snow"}
Number   Snow_Inches   "Snow [%.2f in/h]"  (gWeather) {weather="locationId=home, type=precipitation, property=snow, unit=inches"}
Number   Precip_Probability   "Precip probability [%d %%]"  (gWeather) {weather="locationId=home, type=precipitation, property=probability"}
// new total property in 1.8, only Wunderground
Number   Precip_Total         "Precip total [%d mm]"  (gWeather) {weather="locationId=home, type=precipitation, property=total"}
Number   Precip_Total_Inches  "Precip total [%d in]"  (gWeather) {weather="locationId=home, type=precipitation, property=total, unit=inches"}

// temperature
Number   Temperature      "Temperature [%.2f °C]"      (gWeather) {weather="locationId=home, type=temperature, property=current"}
Number   Temperature_F    "Temperature [%.2f °F]"      (gWeather) {weather="locationId=home, type=temperature, property=current, unit=fahrenheit"}
Number   Temp_Feel        "Temperature feel [%.2f °C]" (gWeather) {weather="locationId=home, type=temperature, property=feel"}
Number   Temp_Feel_F      "Temperature feel [%.2f °F]" (gWeather) {weather="locationId=home, type=temperature, property=feel, unit=fahrenheit"}
Number   Temp_Dewpoint    "Dewpoint [%.2f °C]"         (gWeather) {weather="locationId=home, type=temperature, property=dewpoint"}
Number   Temp_Dewpoint_F  "Dewpoint [%.2f °F]"         (gWeather) {weather="locationId=home, type=temperature, 
property=dewpoint, unit=fahrenheit"}
// min and max values only available in forecasts
Number   Temp_Min         "Temperature min [%.2f °C]"  (gWeather) {weather="locationId=home, type=temperature, property=min"}
Number   Temp_Min_F       "Temperature min [%.2f °F]"  (gWeather) {weather="locationId=home, type=temperature, property=min, unit=fahrenheit"}
Number   Temp_Max         "Temperature max [%.2f °C]"  (gWeather) {weather="locationId=home, type=temperature, property=max"}
Number   Temp_Max_F       "Temperature max [%.2f °F]"  (gWeather) {weather="locationId=home, type=temperature, property=max, unit=fahrenheit"}
String   Temp_MinMax      "Min/Max [%s °C]"            (gWeather) {weather="locationId=home, type=temperature, property=minMax"}
String   Temp_MinMax_F    "Min/Max [%s °F]"            (gWeather) {weather="locationId=home, type=temperature, property=minMax, unit=fahrenheit"}

// wind
Number   Wind_Speed           "Windspeed [%.2f km/h]"   (gWeather) {weather="locationId=home, type=wind, property=speed"}
Number   Wind_Speed_Beaufort  "Windspeed Beaufort [%d]" (gWeather) {weather="locationId=home, type=wind, property=speed, unit=beaufort"}
Number   Wind_Speed_Knots     "Windspeed [%.2f kn]"     (gWeather) {weather="locationId=home, type=wind, property=speed, unit=knots"}
Number   Wind_Speed_Mps       "Windspeed [%.2f mps]"    (gWeather) {weather="locationId=home, type=wind, property=speed, unit=mps"}
Number   Wind_Speed_Mph       "Windspeed [%.2f mph]"    (gWeather) {weather="locationId=home, type=wind, property=speed, unit=mph"}
String	 Wind_Direction       "Wind direction [%s]"     (gWeather) {weather="locationId=home, type=wind, property=direction"}
Number   Wind_Degree          "Wind degree [%.0f °]"    (gWeather) {weather="locationId=home, type=wind, property=degree"}
Number   Wind_Gust            "Wind gust [%.2f km/h]"   (gWeather) {weather="locationId=home, type=wind, property=gust"}
Number   Wind_Gust_Beaufort   "Wind gust Beaufort [%d]" (gWeather) {weather="locationId=home, type=wind, property=gust, unit=beaufort"}
Number   Wind_Gust_Knots      "Wind gust [%.2f kn]"     (gWeather) {weather="locationId=home, type=wind, property=gust, unit=knots"}
Number   Wind_Gust_Mps        "Wind gust [%.2f mps]"    (gWeather) {weather="locationId=home, type=wind, property=gust, unit=mps"}
Number   Wind_Gust_Mph        "Wind gust [%.2f mph]"    (gWeather) {weather="locationId=home, type=wind, property=gust, unit=mph"}
Number   Wind_Chill           "Wind chill [%.2f °C]"    (gWeather) {weather="locationId=home, type=wind, property=chill"}
Number   Wind_Chill_F         "Wind chill [%.2f °F]"    (gWeather) {weather="locationId=home, type=wind, property=chill, unit=fahrenheit"}

OpenHab – Boost Button on the Basic UI

While I’ve been adding simple on/off controls and rules controlling timers for my heating, I noticed something that was missing was the idea of a 1+ hour boost button, so I decided to create one.

The standard pattern for building this type of thing is to create a “dummy” Switch – one not bound to any channel, and make use of that + timers in the rules to handle the off transition. In my sitemap however, I was aiming to have a push button type setup, rather then a toggle.

One pattern I found for this made use of an additional dummy Switch to store the state, and use the “visibility” options in the sitemap to show the “on” or “running” buttons depending on that state. While nice, I prefer not to have a second dummy variable, so I don’t 🙂

Define the item

First I define the “dummy” switch:

Group gVirtual

Switch HeaterTimer "1 Hour Boost" <temperature> (gHeating, gVirtual, gSwitches)

The rules

Then I define my rules for this switch:

var Timer boostTimer = null

rule "One Hour Heat On"
when
    Item HeaterTimer received command
then
    Heating.sendCommand(ON)
    postUpdate(HeaterTimer, ON)
    if(boostTimer === null || boostTimer.hasTerminated){
        boostTimer = createTimer(now.plusHours(1), [| 
            Heating.sendCommand(OFF)
            postUpdate(HeaterTimer, OFF)
            boostTimer = null
        ])
    }
    else{
        boostTimer.reschedule(now.plusHours(1)) // if pressed a second time keep it on for another hour from now
    }
end

So what I’m doing here is triggering on the HeaterTimer receiving a command, then I turn the heating on, and update the state of HeaterTimer to ‘On’.

If the timer is equal to null, or has ended, I create a timer set at 1 hour from now, and when the timer triggers I switch the heating off, update the state, and set the timer to null.

If we have an command, and the timer is already running e.g. I pushed the button again, then I reschedule to 1 hour from now.

Now for the sitemap

   Frame label="Heating" {
      Switch item=HeaterTimer mappings=[ON="Boost"] icon="fire-off" visibility=[HeaterTimer == OFF, HeaterTimer == "NULL"]
      Switch item=HeaterTimer mappings=[ON="Running"] icon="fire-on" visibility=[HeaterTimer == ON]

   }

This is where it gets a little interesting.

The first switch is showing an ON/Boost button, with the icon in the off state. This is only visible if the HeatingTimer is OFF, or equal to “NULL”.

Why the second/NULL check?

When the “dummy” switch is created, it is created with a null state – my persistence config will restore the state if we ever reload, but for the very first time, we need to handle the lack of ON/OFF state. You could update the sitemap after the first time you use it to remove the NULL comparison as it will only be used once. The other option is to only add the visibility options after you run for the first time.

The second switch should be obvious now – we have a “Running” button, that if you push it, will add 1 hour from now (remember the rules) to the timer.