User:Chrisbirkinshaw
Contents
My System
Things which might be interesting:
Motion controlled lighting
Turn on lights when presence is detected in a room, taking into account day/night and also having a way of overriding (for those dark English afternoons!)
Automatic Computer Desk Lamp
Turn on desk lamp when I'm using my Mac and turn it off after XX idle mins (i.e. no mouse or keyboard input)
Motion Controlled Lighting
Reasons this can't be done in LMCE natively at the moment
1. It is not possible to set a timer and then reset that timer. This means your lights will be constantly going on and off, as the old timers expire and then movement is picked up again.
2. It is not possible to say "Turn on X if motion detected in R and Y is on", so you cannot have override buttons for your auto lights (Y, in this example)
Requirements
In this example we imagine a room called "hall"
- Security sensor in the hall
- A light in the hall controlled by LMCE
- Dummy On/Off Light Switch "Night/Day" (place on floorplan inside a box with a label drawn on)
- Dummy On/Off Light Switch "Hall Auto" (place on floorplan inside a box with a label drawn on)
- Event responder configured to switch "Night/Day" on and "Hall Auto" off at sunrise
- Event responder configured to switch "Night/Day" off and "Hall Auto" on at sunset
- Lighting scenario "Lights Auto" to switch "Hall Auto" on from the home screen
- Lighting scenario "Lights Manual" to switch "Hall Auto" off from the home screen
- Code below saved as interceptor.rb or similar (name is not important)
- Device configured for the interceptor (see Plain Text DCE Messages) and the id inserted into the code below at the top
Procedure
Run the code below in a loop:
while true ; do ruby interceptor.rb ; done
You should see it connect and register intercepts for Lighting and Security messages. You can now configure the interceptor by triggering events, looking at the output of the interceptor for where it says for example:
Correlating message: (165 -108 2 9 25 "0")
You can take the part from within the brackets and use it in the rules.
You can see a few rules defined in my example, and will notice that you can have rules for automatic lighting zones, and also straightforward rules for triggering events. The latter scenario is not really very useful as LMCE is pretty good at doing this anyway. I mostly used that stuff for testing.
I am not a programmer, so this code was written as I scoured the net for Ruby examples and tried to make sense of it. I know it is messy and very there is a lot of garbage in there, but I did not think it was worth spending the time on. Once LMCE has developed a little more this code is useless, so it should remain a simple hack to get along in the meantime.
The Ruby Code
# Known issues: # # Doesn't cope with messages like this: 0 162 9 0 "&" 173 175,176,177, 1 192 97 "" 98 "" # i.e. where a message is to multiple destinations # # Should ignore the source of a message when correlating (triggers etc should be setup as classes not just strings to allow # more flexibility) # # Disclaimer: This code is seriously messy and VERY limited! It's just a quick hack to get things working until # Linux MCE can support resettable timers and device state can be used as a condition for advanced event responders # require 'socket' include Socket::Constants device_id=162 ruleconfig = [ { :trigger => '164 -108 2 9 25 "1"', :on => "162 103 1 192", :off => "162 103 1 193", :name => "entrance", :zoneid => 1, :roomid => 3, :timeout => 90, :enable => '145 175 1 192', :disable => '145 175 1 193' }, { :trigger => '166 -108 2 9 25 "1"', :on => "162 104 1 192", :off => "162 104 1 193", :name => "kitchen", :zoneid => 2, :roomid => 5, :timeout => 90, :enable => '145 176 1 192', :disable => '145 176 1 193' }, { :trigger => '165 -108 2 9 25 "1"', :on => "162 105 1 192", :off => "162 105 1 193", :name => "landing", :zoneid => 3, :roomid => 4, :timeout => 90, :enable => '145 177 1 192', :disable => '145 177 1 193' }, { :trigger => '78 162 2 9 25 1', :response => "162 102 1 192", :name => "sw 1 on"}, { :trigger => "78 162 2 9 25 0", :response => "162 102 1 193", :name => "sw 1 off"}, { :trigger => '82 162 2 9 25 1', :response => "162 102 1 192", :name => "sw 2 on"}, { :trigger => "82 162 2 9 25 0", :response => "162 102 1 193", :name => "sw 2 off"}, { :trigger => '83 162 2 9 25 1', :response => "162 154 1 192", :name => "sw 3 on"}, { :trigger => "83 162 2 9 25 0", :response => "162 154 1 193", :name => "sw 3 off"}, { :trigger => '83 162 2 9 25 1', :response => "162 99 1 192", :name => "sw 3 on"}, { :trigger => "83 162 2 9 25 0", :response => "162 99 1 193", :name => "sw 3 off"} ] def configure(ruleconfig) @rules = [] @zones = [] ruleconfig.each do |rc| if rc[:zoneid] zone = {:id => rc[:zoneid], :roomid => rc[:roomid], :trigger => rc[:trigger], :name => rc[:name], :on => rc[:on], :off => rc[:off], :timeout => rc[:timeout], :watching_media => 0, :listening_media => 0, :auto => 1, :enable => rc[:enable], :disable => rc[:disable] } p zone @zones << zone else @rules << rc p rc end end puts "Configured #{@zones.size} zones and #{@rules.size} rules" end def connect_lmce(device_id) @in_socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) in_sockaddr = Socket.pack_sockaddr_in( 3450, 'localhost' ) @in_socket.connect( in_sockaddr ) @out_socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) out_sockaddr = Socket.pack_sockaddr_in( 3450, 'localhost' ) @out_socket.connect( out_sockaddr ) puts "Setting receiving connection" @in_socket.send("COMMAND #{device_id}\n",0) puts @in_socket.recv( 100 ) puts "Setting up sending connection" @out_socket.send("EVENT #{device_id}\n",0) puts @out_socket.recv( 100 ) sleep 1 puts "Registering for plain text messages" @out_socket.send("PLAIN_TEXT\n",0) puts @out_socket.recv( 100 ) puts "Finished initialising" return @in_socket, @out_socket end def register_messages(device_id) puts "Registering for message intercepts (lighting devices)" @out_socket.send("MESSAGET 24 \n #{device_id} -1000 8 0 5 2 4 84\n",0) puts @out_socket.recv(100) puts "Registering for message intercepts (security devices)" @out_socket.send("MESSAGET 24 \n #{device_id} -1000 8 0 5 1 4 73 \n",0) puts @out_socket.recv(100) puts "Finished registration" end def process_msg(msg) puts "Received message #{msg}" msg_type_1 = /(\w*)\s(-?\w*),?\s(\w*)\s(\w*)\s(\w*)\s(\"\w*\")\s(\w*)\s(\"\w*\")/ # 2 param msg_type_2 = /(\w*)\s(-?\w*),?\s(\w*)\s(\w*)\s(\w*)\s(\"\w*\")/ # 1 param msg_type_3 = /(\w*)\s(-?\w*),?\s(\w*)\s(\w*)/ # no params msg = msg.gsub(/.*\&\"\s/, "").gsub("\n", "") puts "Processing message #{msg}" case msg when msg_type_1 msg_type_1.match(msg) puts "Type 1: From=#{$1} To=#{$2} Type=#{$3} MsgId=#{$4} P1Id=#{$5} P1Val=#{$6} P2Id=#{$7} P2Val=#{$8}" correlate_msg("#{$1} #{$2} #{$3} #{$4} #{$5} #{$6} #{$7} #{$8}") when msg_type_2 msg_type_2.match(msg) puts "Type 2: From=#{$1} To=#{$2} Type=#{$3} MsgId=#{$4} P1Id=#{$5} P1Val=#{$6}" correlate_msg("#{$1} #{$2} #{$3} #{$4} #{$5} #{$6}") when msg_type_3 msg_type_3.match(msg) puts "Type 3: From=#{$1} To=#{$2} Type=#{$3} MsgId=#{$4}" correlate_msg("#{$1} #{$2} #{$3} #{$4}") else puts "Unknown message '#{msg}'" end end def correlate_msg(msg) puts "Correlating message: (#{msg})" @rules.each do |rule| if msg == rule[:trigger] puts "Rule #{rule[:name]} Matched" puts "Sending Message #{rule[:response]}, length #{rule[:response].length}" @out_socket.send("MESSAGET #{rule[:response].length + 1} \n #{rule[:response]} \n", 0) end end @zones.each do |zone| if msg == zone[:trigger] && zone[:auto] == 1 puts "Zone #{zone[:name]} matched - setting timer for #{zone[:timeout]} secs" if zone[:timer] and zone[:timer].alive? zone[:timer].kill else puts "Switching on light!" @out_socket.send("MESSAGET #{zone[:on].length + 1} \n #{zone[:on]} \n", 0) end zone[:timer] = Thread.new do puts "starting new thread" counter = 0 while counter < zone[:timeout] counter += 1 sleep 1 end puts "Switching off light!" @out_socket.send("MESSAGET #{zone[:off].length + 1} \n #{zone[:off]} \n", 0) end end if msg == zone[:enable] puts "Enabling auto lights for #{zone[:name]}" zone[:auto] = 1 end if msg == zone[:disable] puts "Disabling auto lights for #{zone[:name]}" zone[:auto] = 0 end end end configure(ruleconfig) @in_socket, @out_socket = connect_lmce(device_id) register_messages(device_id) loop { #puts "Start Loop" if data = @in_socket.read( 11 ) #puts "Received data: #{ data }" if (/MESSAGET\ (..)/.match(data)) #puts "Message size: #{$1}" len = $1.to_i + 2 msg = @in_socket.read( len ) @in_socket.send("OK \n", 0) process_msg(msg) end else @in_socket.close @out_socket.close sleep 3 @in_socket, @out_socket = connect_lmce(device_id) register_messages(device_id) end }
Automatic Computer Desk Lamp
Requirements
- SSH access to the core from your Mac without using passwords (Google will help)
- A lamp at your desk, controlled by LMCE
The Script
Save this as CheckIdle.command or similar and run by double clicking.
You might want to change "root@$dcerouter" to something more secure in the code below!!!
#!/bin/sh dcerouter="192.168.1.1" lamp="108" timeout="300" ## Do not change anything below here ## status=0 while true ; do idle=`ioreg -c IOHIDSystem | perl -ane 'if (/Idle/) {$idle=(pop @F)/1000000000; print $idle;last}' | sed 's/\..*//'` if [ $idle -gt $timeout ] ; then if [ $status -eq 1 ] ; then ssh root@$dcerouter "/usr/pluto/bin/MessageSend localhost $lamp $lamp 1 193" && status=0 && echo "Lamp turned off" fi else if [ $status -eq 0 ] ; then ssh root@$dcerouter "/usr/pluto/bin/MessageSend localhost $lamp $lamp 1 192" && status=1 && echo "Lamp turned on" fi fi sleep 2 done