User:Chrisbirkinshaw
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
#
# 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
# note that a trigger is in a different format to an enable/disable message, as for the enable/disable message we ignore the source id
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 => '175 1 192', :disable => '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 => '176 1 192', :disable => '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 => '177 1 192', :disable => '177 1 193' },
{ :trigger => '82 9 1 694 2 5 9 48', :response => "162 102 1 192", :name => "bedroom orbiter registered"},
{ :trigger => '162 2 9 25 1', :response => "162 102 1 192", :name => "bedroom md shutdown"},
{ :trigger => '162 2 9 25 1', :response => "162 102 1 192", :name => "sw 1 on"},
{ :trigger => "162 2 9 25 0", :response => "162 102 1 193", :name => "sw 1 off"},
{ :trigger => '162 2 9 25 1', :response => "162 102 1 192", :name => "sw 2 on"},
{ :trigger => "162 2 9 25 0", :response => "162 102 1 193", :name => "sw 2 off"},
{ :trigger => '162 2 9 25 1', :response => "162 154 1 192", :name => "sw 3 on"},
{ :trigger => "162 2 9 25 0", :response => "162 154 1 193", :name => "sw 3 off"},
{ :trigger => '162 2 9 25 1', :response => "162 99 1 192", :name => "sw 3 on"},
{ :trigger => "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})"
msg_without_from_address = msg.gsub(/^[0-9]*\s/, '')
@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_without_from_address == zone[:enable]
puts "Enabling auto lights for #{zone[:name]}"
zone[:auto] = 1
end
if msg_without_from_address == 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