Monthly Archives: July 2014

Configuring a Aeon Labs Multi Sensor with openHAB

I got a new sensor today, an Aeon Labs Aeotec Z-Wave Multi-Sensor
. This is a motion sensor that can report Light, Humidity, Temperature, Motion.

The first thing is to add the sensor to your z-wave network. I read something interesting in searches for how to set this up. If you configure your device at one location say your desk then move the sensor to another location you need to update the neighbors using ozwcp (for a guide on how to compile it on centos 6 see my last blog post).

I am using an Aeon DSA02203-ZWUS Labs Z-Wave Z-Stick Series 2 USB Dongle
, so I unplug it from the USB port then push the button, then push the black button on the Multi Sensor and it is joined to the network.

Then start ozwcp

./ozwcp -d -p 1234

Now make sure the red light is on for the sensor so we know it is awake, if not push the black button. Enter the device name, I have a special configuration so it always maps to /dev/zwave ( will write up how to do that later ). Click on initialize

Screen Shot 2014-07-12 at 12.37.58 AM

Select the multinode sensor (note is says awake)
Screen Shot 2014-07-12 at 12.41.04 AM

select Request Node Neighbor Update and press Go

Screen Shot 2014-07-12 at 12.42.19 AM

wait for the log window to stop updating.

Select configuration for the Sensor

Screen Shot 2014-07-12 at 12.47.49 AM

Set Group 1 Reports: to 225, which is decimal for the binary map 11100001, the first bit reports the battery, the 5th bit Temperature, the 6th bit Humidity, the 7th bit Luminance, then click the submit button next to it. I also set the Group 1 Interval: to 240 (4 minutes) while testing, again click the submit button next to it. Make sure the log messages have finished updating then press refresh to make sure the setting have taken. Once finished click on the close button in the Controller Interface box.

In open hab configure the following items

configuration/items/motion.items

 
Group Motion
Number sensor_1_temp "Temperature [%.1f °F]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=1"}
Number sensor_1_humidity "Humidity    [%.0f %%]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=5"}
Number sensor_1_luminance "Luminance    [%.0f Lux]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=3"}
Contact sensor_1_motion "sensor [MAP(motion.map):%s]" (Motion) {zwave="2:1:command=sensor_binary,respond_to_basic=true"}
Number sensor_1_battery "Battery [%s %%]" (Motion) {zwave="2:command=battery"}

configuration/rules/motion.rules

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*


var Number counter = 0
var Number lastCheck = 0

rule "corLightOn"
when
        Item sensor_1_motion changed from CLOSED to OPEN
then
        sendCommand(light_living_room_switch, ON)
end

rule "corLightOff"
when
        Item sensor_1_motion changed from OPEN to CLOSED
then
        sendCommand(light_living_room_switch, OFF)
end

sitemaps/motion.sitemap

sitemap home label="Main Menu"
{
        Frame {
                Group item=Motion label="Motion" icon="firstfloor"
                }
        }

}

transform/motion.map

CLOSED=No Motion
OPEN=Motion
-=No Motion

if everything goes to plan you should see the following when you visit http://hostname:8080/openhab.app?sitemap=motion

Screen Shot 2014-07-12 at 1.08.12 AM

And when you move in front of the motion detector it should turn on the light_living_room_switch, and turn it off 4 minutes later if no motion detected.

Compiling owzcp on Centos 6

I using a I am using an Aeon DSA02203-ZWUS Labs Z-Wave Z-Stick Series 2 USB Dongle with openHAB, and I have found that OpenZWave Control Pannel is a very useful companion tool for configuring and testing Z-Wave devices before you add them to openHAB

mkdir svn
cd svn
svn checkout http://open-zwave.googlecode.com/svn/trunk/ open-zwave
svn checkout http://openzwave-control-panel.googlecode.com/svn/trunk/ openzwave-control-panel-read-only
sudo yum install libudev-devel
cd ..
wget ftp://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.19.tar.gz
tar zxvf libmicrohttpd-0.9.19.tar.gz
mv libmicrohttpd-0.9.19 libmicrohttpd
cd libmicrohttpd
./configure
make
cd ..
cd open-zwave
make

uncomment the following lines and change the LIBZWAVE to match

# for Linux uncomment out next three lines
LIBZWAVE := $(wildcard $(OPENZWAVE)/*.a)
LIBUSB := -ludev
LIBS := $(LIBZWAVE) $(GNUTLS) $(LIBMICROHTTPD) -pthread $(LIBUSB)
make
ln -s ../open-zwave/config/ .
./ozwcp -d -p 1234

bring up a browers http://server:1234

I have my zwave configured for /dev/zwave (which i will post a blog in in the future)

Planning for future employment

I read an article today in the Washington Post. In IT I feel there is great value in moving from one company to another every 3-4 years, if for no other reason than it exposes you to new technologies and gives you a chance to jettison the technical debt you have become attached to over your tenure in your current position.

I have changed employers a number of times recently, one by choice, and one by proxy of the company I worked for being sold and purchased by another company. The biggest stress of changing jobs for me as been the upheaval in dealing with changing benefits.

If the tech industry is going to embrace the ‘tour of duty model’ described in the article then we also need to address being able to carry benefits over from one company to another.

For Medical make the companies contribution a deposit into a medical account like an HSA that you can use to pay for your own medical plan. If a company wants to negotiate a group rate then they still can, but I should have the choice to use it or not. When I leave a company I can continue to stay enrolled in my old companies plan. My old company doesn’t have to pay for me, but the company gets the benefit of theoretically larger enrollee pool if people like your plan. This idea probably falls apart when you remember that a lot of companies self insure, so they and you are not really paying for insurance, but you are contributing to the company pool that the company hopes will cover the expenses for the year, some years they loose money other years they make money.

The same thing goes for a 401K plan, lets get rid of vesting periods, then move the 401K from a company plan to a personal plan. If the company wants to negotiate a company 401k Plan that employees use great, if not then money is electronically deposited into my personal 401K plan. If the company wants to match on the 401k then that would get sent to my personal 401K plan as well. Before you go, oh no, companies will never get rid of the vesting period, remember we are talking about a 2 or 4 year tour of duty, so most employees would ever reach fully vested anyway, so lets make it an honest match rather than the bate and switch we currently have. If employers want employees to stay then increase the match based on anniversaries.

extending a Lennox Icomfort Thermostat: Part 1

See part 2 in my investigation

I recently had installed a new air conditioning system and it came with a new Lennox iComfort Thermostat that talks to the cloud over a wifi connection. After spending a little time looking at messages that get passed back and forward from chrome to the cloud service using the chrome developer tool, it looks like a fairly straight forward sort of REST api.

I managed to get some ruby code that log in, then queries the status of the the thermostat.

The authentication is a pain, since you log into the website using an ASP.NET web application, so it needs the __VIEWSTATE and the __EVENTVALIDATION form fields to be present. I have hardcoded them for the moment, but intended to pull them out of the of the response on the first page. I should set up a proxy and see if the android app uses a simpler API for authenticating.

Once authenticated you can send a JSON message like

{"hidden_gateway_SN":"XXXXXX",
"pref_temp_units":"0",
"userid":"XXXXXX",
"Central_Zoned_Away":"2",
"Cancel_Away":"-1",
"current_prg":"2",
"current_mode":"1",
"CurrentBrowser":"chrome",
"zoneNumber":"0",
"Current_Thermostat":"0",
"alertViewTypes":"0",
"alertTypes":"1",
"reminderTypes":"0"}

and you get back

{"Equipment_List"=>
  [{"SystemID"=>XXXXXX,
    "Friendly_Name"=>"Air Handler",
    "Device_Descriptor"=>XXXXXXX,
    "Control_Model_Nbr"=>"Equipment Interface Module ? AHC",
    "Control_Serial_Number"=>"XXXX-xxxxxx",
    "ReturnStatus"=>nil},
   {"SystemID"=>XXXXXX,
    "Friendly_Name"=>"Thermostat",
    "Device_Descriptor"=>XXXXXX,
    "Control_Model_Nbr"=>"103445-04",
    "Control_Serial_Number"=>"XXXXXXXX",
    "ReturnStatus"=>nil},
   {"SystemID"=>72204,
    "Friendly_Name"=>"Wi-Fi Controller",
    "Device_Descriptor"=>XXXXXX,
    "Control_Model_Nbr"=>"GS1500M",
    "Control_Serial_Number"=>"Wi-XXXXXXXXX",
    "ReturnStatus"=>nil}],
 "AlertInfo_List"=>[],
 "TStatInfo_List"=>
  [{"GatewaySN"=>"XXXXXXXX",
    "Zone_Number"=>0,
    "Zone_Name"=>"Zone 1",
    "Program_Schedule_Selection"=>0,
    "Schedule_Name"=>"0|summer^1|winter^2|spring fall^3|save energy^4|custom",
    "Program_Schedule_Mode"=>"0",
    "Away_Mode"=>0,
    "Pref_Temp_Units"=>"0",
    "DateTime_Mark"=>"/Date(1404941631787)/",
    "Zone_Enabled"=>1,
    "System_Status"=>0,
    "Indoor_Temp"=>74.0,
    "Indoor_Humidity"=>40,
    "Operation_Mode"=>2,
    "Heat_Set_Point"=>57.0,
    "Cool_Set_Point"=>74.0,
    "Fan_Mode"=>2,
    "Golden_Table_Updated"=>true,
    "ConnectionStatus"=>"GOOD",
    "Central_Zoned_Away"=>2,
    "Zones_Installed"=>1,
    "GMT_To_Local"=>-18000}],
 "current_GatewayInfo"=>
  {"SystemID"=>"XXXXX",
   "Pref_Language_Nbr"=>0,
   "Pref_Temp_Unit"=>"0",
   "Cool_Set_Point_High_Limit"=>99.0,
   "Cool_Set_Point_Low_Limit"=>60.0,
   "Heat_Set_Point_High_Limit"=>90.0,
   "Heat_Set_Point_Low_Limit"=>40.0,
   "Heat_Cool_Dead_Band"=>3.0,
   "Daylight_Savings_Time"=>1,
   "ReturnStatus"=>"SUCCESS"},
 "program_List"=>
  {"00"=>
    {"GatewaySN"=>"XXXXXXXXX",
     "ScheduleNum"=>0,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>68.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "01"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>0,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>20,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "10"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>1,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>68.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "11"=>
    {"GatewaySN"=>"XXXXXXXXX",
     "ScheduleNum"=>1,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>20,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "20"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>2,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>50.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>2,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "21"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>2,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>10,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>2,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "30"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>3,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>63.0,
     "Cool_Set_Point"=>76.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "31"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>3,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>17,
     "Cool_Set_Point"=>24,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "40"=>
    {"GatewaySN"=>"XXXXXXX",
     "ScheduleNum"=>4,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>67.0,
     "Cool_Set_Point"=>72.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "41"=>
    {"GatewaySN"=>"XXXXXXX",
     "ScheduleNum"=>4,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>19,
     "Cool_Set_Point"=>22,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"}},
 "CurrentAvailableZones"=>1,
 "Zones_Installed"=>1,
 "GatewaySN"=>"XXXXXXX",
 "ZoneNumber"=>0,
 "CurrentZoneName"=>"Zone 1",
 "ServerUpdated"=>true,
 "CurrentDate"=>"Jul-09-2014 09:33:51 PM",
 "Central_Zoned_Away"=>2,
 "Indoor_Temp"=>"74",
 "fraction_Temp"=>"00",
 "Indoor_Humidity"=>"40",
 "Heat_Set_Point"=>"57",
 "Cool_Set_Point"=>"74",
 "Fan_Mode"=>2,
 "Away_Mode"=>0,
 "Operation_Mode"=>2,
 "Pref_Temp_Units"=>0,
 "System_Status"=>"idle",
 "System_Name"=>"my icomfort system",
 "ConnectionStatus"=>"GOOD",
 "Program_Schedule_Selection"=>0,
 "Program_Schedule_Mode"=>"0",
 "manualSetting"=>
  "cool only,2&heat only,1&heat or cool,3&off,0&emergency heat,4&"}

As you can see lots of useful information. Short term I am planning on collecting stats about how long it takes the system to cool and how long it is running. The cloud site only updates when there is a change event, so the actual data that will be written is fairly small. Long term I want to be able to hook it into my Home Automation system, which is currently OpenHab. Depending on how far I get I will post my ruby code online, but nothing I am doing is hard to replicate.

 

A request came in for the sample ruby code i wrote, I originally didn’t want to post this, since this was just something I hacked together. You will need to need to log into the icomfort website first, then use a tool like the gooogle chrome developer tools to grab the __VIEWSTATE, __EVENTVALIDATION cookie, and update ctl00$RightContent$txtUserName, ctl00$RightContent$txtPwd it, hidden_gateway_SN, userid. Again apologies in advance for my hacked together example.

require 'rest_client'
require 'json'
require 'uri'
require 'pp'

#RestClient.proxy = "http://localhost:8008"

response3 = RestClient.get 'https://www.myicomfort.com/Default.aspx'
response3.cookies
# => {"_applicatioN_session_id" => "1234"}
p response3.cookies
#p response3
@session_cookies = response3.cookies

response2 = RestClient.post(
  'https://www.myicomfort.com/Default.aspx',
  {
    "__LASTFOCUS" => "",
    "__EVENTTARGET=" => "",
    "__EVENTARGUMENT=" => "",
#    "__VIEWSTATE" => "",
    "__VIEWSTATE" => '<REPLACE WITH YOUR VEIW STATE>',
#    "__EVENTVALIDATION" => '',
    "__EVENTVALIDATION" => '<REPLACE WITH YOUR EVENTVALIDATION>',
    'ctl00$RightContent$hdnPwd' => "",
    'ctl00$RightContent$txtUserName' => "<REPLACE WITH YOUR USERNAME>",
    'ctl00$RightContent$txtPwd' => "<REPLACE WITH YOUR PASSWORD>",
    'ctl00$RightContent$chkRemember' => "on",
    'ctl00$RightContent$btnLogin' => "Log in"},
  {:cookies => @session_cookies}
) { |response, request, result, &block|
  if [301, 302, 307].include? response.code
    p response.cookies
    @session_cookies = response.cookies
    #response.follow_redirection(request, result, &block)
    response
  else
    p response
    response.return!(request, result, &block)
  end
}
#p response2.cookies
#p response2.code
p @session_cookies


my_hash = { 
:hidden_gateway_SN =>"<REPLACE WITH YOUR thermostat serial number>",
:pref_temp_units => "0",
:userid => "<REPLACE WITH YOUR USERNAME>",
:Central_Zoned_Away => "2",
:Cancel_Away => "-1",
:current_prg => "2",
:current_mode => "1",
:CurrentBrowser =>"chrome",
:zoneNumber => "0",
:Current_Thermostat=>"0",
:alertViewTypes=>"0",
:alertTypes => "1",
:reminderTypes => "0"}

myjson = JSON.generate(my_hash)

p "CurrentDate,Cool_Set_Point,Indoor_Temp,Fan_Mode,Indoor_Humidity,System_Status"
lastupdate=0

while 1 do
  response4 = RestClient.post(
    'https://www.myicomfort.com/Dashboard.aspx/NewGetManualInfo',
    myjson,
    {:content_type => :json, :accept => :json, :cookies => @session_cookies} )  
  #{ |response, request, result, &block|
  #  if [301, 302, 307].include? response.code
  #    p response.cookies
  #    @session_cookies = response.cookies
  #    #response.follow_redirection(request, result, &block)
  #    response
  #  else
  #    p response
  #    response.return!(request, result, &block)
  #  end
  #}

  json_object = JSON.parse(response4)
  d = JSON.parse(json_object['d'])
#  pp d
#  if #{lastupdate} != #{d['CurrentDate']}
   if lastupdate != d['CurrentDate']
    lastupdate = d['CurrentDate']
    p "#{d['CurrentDate']},#{d['Cool_Set_Point']},#{d['Indoor_Temp']},#{d['Fan_Mode']},#{d['Indoor_Humidity']},#{d['System_Status']}"
  end
  sleep 1
end