Tuesday, February 16, 2016

OpenHAB living room lights automation

Since I am the proud owner of five ESP8266 modules I was wondering how to make best use of them. After trying NodeMCU and Arduino I finaly tipped over to the NodeMCU side. I don't really like NodeMCU since I want more control over the hardware but it is simple enough and has some working libraries that really speed things up.

For starters I uploaded the firmware that I downloaded from nodemcu-build.com with only the absolutely essential modules enabled, plus mqtt and ow in order to be able to read from a Dallas DS18B20S OneWire digital temperature sensor. MQTT is the protocol developed for the Internet of Things (IoT) and it is a really convenient way for communication between the server and the IoT device. Further information on MQTT you can find here.

Then I set up the server. I am using an old Pentium4@3GHz PC with 1GB RAM with Ubuntu Server 14.04 LTS operating system installed. I installed mosquito which is the MQTT server, mysql for data persistence and openhab which is the main framework that provides the user interface and binds everything together.

ESP-01 module

On the ESP8266 side I used the two GPIO. GPIO0 was used to read the DS18B20S and GPIO2 was used to control a MOSFET, through a resistor of course, connected to the LEDs. The files I uploaded to the module are the following:
init.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
--Init  
 DeviceID="esp01"  
 RoomID="LR"
 gpio0 = 3
 gpio2 = 4

 Broker="192.168.X.X"
 t = require("ds18b20")
 t.setup(gpio0)addrs = t.addrs()
 pwm.setup(gpio2,100,0)

 m = mqtt.Client("ESP8266".. DeviceID, 180, "user", "password")  
 m:lwt("/lwt", "ESP8266", 0, 0)  
 m:on("offline", function(con)   
    print ("Mqtt Reconnecting...")   
    tmr.alarm(1, 10000, tmr.ALARM_SINGLE, function()  
      m:connect(Broker, 1883, 0, function(conn)   
        print("Mqtt Connected to:" .. Broker)  
        mqtt_sub() --run the subscription function  
      end)  
    end)  
 end)  

 tmr.alarm(2,10000,tmr.ALARM_AUTO, function()
   lrtemp = t.read()
   print ("Temperature: "..lrtemp.."'C")
   m:publish("/home/".. RoomID .."/" .. DeviceID .. "/p2/state",lrtemp,0,0)
 end)

 -- on publish message receive event  
 m:on("message", function(conn, topic, data)   
    print("Recieved:" .. topic .. ":" .. data)   
      if (data=="ON") then  
      print("Enabling Output")   
      pwm.setduty(gpio2,1023)
      m:publish("/home/".. RoomID .."/" .. DeviceID .. "/p1/state","ON",0,0)  
    elseif (data=="OFF") then  
      print("Disabling Output")   
      pwm.setduty(gpio2,0)
      m:publish("/home/".. RoomID .."/" .. DeviceID .. "/p1/state","OFF",0,0)  
    elseif ((tonumber(data)>=0) and (tonumber(data)<=100)) then
      print("Setting PWM to " .. data)
      pwm.setduty(gpio2,((data * 1024) / 100) - 1)
      m:publish("/home/".. RoomID .."/" .. DeviceID .. "/p1/state",data .. "%",0,0)
    else  
      print("Invalid - Ignoring")   
    end   
 end)  
 function mqtt_sub()  
    m:subscribe("/home/".. RoomID .."/" .. DeviceID .. "/p1/com",0, function(conn)   
      print("Mqtt Subscribed to OpenHAB feed for device " .. DeviceID)  
    end)  
 end  
 tmr.alarm(0, 1000, tmr.ALARM_AUTO, function()  
  if wifi.sta.status() == 5 and wifi.sta.getip() ~= nil then  
    tmr.stop(0)  
    m:connect(Broker, 1883, 0, function(conn)   
      print("Mqtt Connected to:" .. Broker)  
      mqtt_sub() --run the subscription function  
    end)  
  end  
 end)

ds18b20.lua

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
--------------------------------------------------------------------------------
-- DS18B20 one wire module for NODEMCU
-- NODEMCU TEAM
-- LICENCE: http://opensource.org/licenses/MIT
-- Vowstar <vowstar@nodemcu.com>
-- 2015/02/14 sza2 <sza2trash@gmail.com> Fix for negative values
--------------------------------------------------------------------------------

-- Set module name as parameter of require
local modname = ...
local M = {}
_G[modname] = M
--------------------------------------------------------------------------------
-- Local used variables
--------------------------------------------------------------------------------
-- DS18B20 dq pin
local pin = nil
-- DS18B20 default pin
local defaultPin = 9
--------------------------------------------------------------------------------
-- Local used modules
--------------------------------------------------------------------------------
-- Table module
local table = table
-- String module
local string = string
-- One wire module
local ow = ow
-- Timer module
local tmr = tmr
-- Limited to local environment
setfenv(1,M)
--------------------------------------------------------------------------------
-- Implementation
--------------------------------------------------------------------------------
C = 0
F = 1
K = 2
function setup(dq)
  pin = dq
  if(pin == nil) then
    pin = defaultPin
  end
  ow.setup(pin)
end

function addrs()
  setup(pin)
  tbl = {}
  ow.reset_search(pin)
  repeat
    addr = ow.search(pin)
    if(addr ~= nil) then
      table.insert(tbl, addr)
    end
    tmr.wdclr()
  until (addr == nil)
  ow.reset_search(pin)
  return tbl
end

function readNumber(addr, unit)
  result = nil
  setup(pin)
  flag = false
  if(addr == nil) then
    ow.reset_search(pin)
    count = 0
    repeat
      count = count + 1
      addr = ow.search(pin)
      tmr.wdclr()
    until((addr ~= nil) or (count > 100))
    ow.reset_search(pin)
  end
  if(addr == nil) then
    return result
  end
  crc = ow.crc8(string.sub(addr,1,7))
  if (crc == addr:byte(8)) then
    if ((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then
      -- print("Device is a DS18S20 family device.")
      ow.reset(pin)
      ow.select(pin, addr)
      ow.write(pin, 0x44, 1)
      -- tmr.delay(1000000)
      present = ow.reset(pin)
      ow.select(pin, addr)
      ow.write(pin,0xBE,1)
      -- print("P="..present)
      data = nil
      data = string.char(ow.read(pin))
      for i = 1, 8 do
        data = data .. string.char(ow.read(pin))
      end
      -- print(data:byte(1,9))
      crc = ow.crc8(string.sub(data,1,8))
      -- print("CRC="..crc)
      if (crc == data:byte(9)) then
        t = (data:byte(1) + data:byte(2) * 256)
        if (t > 32767) then
          t = t - 65536
        end

        if (addr:byte(1) == 0x28) then
          t = t * 625  -- DS18B20, 4 fractional bits
        else
          t = t * 5000 -- DS18S20, 1 fractional bit
        end

        if(unit == nil or unit == 'C') then
        else
          return nil
        end
        t = t / 10000
        return t
      end
      tmr.wdclr()
    else
    -- print("Device family is not recognized.")
    end
  else
  -- print("CRC is not valid!")
  end
  return result
end

function read(addr, unit)
  t = readNumber(addr, unit)
  if (t == nil) then
    return nil
  else
    return t
  end
end

-- Return module table
return M

Of course I changed the MQTT broker  (which is another name for server) address to the real one. Also there is a modification I made in the ds18b20.lua file, which I downloaded from the NodeMCU site,  because I installed the firmware which does not support float point numbers, and this is a choice I have absolutely no good reason for making it. There are two more things to be done, firstly provide a way to store the dimming percentage value and recall it on the ON command, and secondly add some error handling code. The fortunate thing about NodeMCU is that when it runs into an unhandled error it just restarts.

OpenHAB Main Menu
Now it is time to set up the server side of things. Below are the three configuration files :
default.sitemap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
sitemap default label="Main Menu"
{
  Frame label="Home" {
    Switch item=lamp1 label="Living Room LEDs"
    Slider item=led1 label="Living Room Dimmer"
  }
  Frame label="Temperatures" {
    Text item=lrtemp {
      Frame {
        Chart item=lrtemp period=D refresh=30000
        Chart item=lrtemp period=W refresh=30000
      }
    }
  }  
}

default.items

1
2
3
4
5
6
7
Group All

Switch lamp1 "Living Room LEDs" (all) {mqtt=">[broker:/home/LR/esp01/p1/com:command:on:ON],>[broker:/home/LR/esp01/p1/com:command:off:OFF],<[broker:/home/LR/esp01/p1/state:state:default]"}

Dimmer  led1  "LR Dimmer [%s %%]"  (all) {mqtt=">[broker:/home/LR/esp01/p1/com:command:*:default"}

Number lrtemp "Living Room Temperature [%.1f]" (all) {mqtt="<[broker:/home/LR/esp01/p2/state:state:default]"}

mysql.persist

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Strategies {
    everyHour : "0 0 * * * ?"
    everyDay  : "0 0 0 * * ?"
    everyMinute : "0 * * * * ?"

    // if no strategy is specified for an item entry below, the default list will be used
    default = everyChange
}

Items {
    lrtemp : strategy = everyMinute
}

After everything is setup I can now sit on my favorite sofa and enjoy staring at the charts below for hours on end.
OpenHAB Living Room temperature chart
Thanks for reading this