Difference between revisions of "User:Chrisbirkinshaw"

From LinuxMCE
Jump to: navigation, search
(Requirements)
(The Ruby Code)
 
(One intermediate revision by the same user not shown)
Line 61: Line 61:
 
# Doesn't cope with messages like this: 0 162 9 0 "&" 173 175,176,177, 1 192 97 "" 98 ""
 
# 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  
 
# 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  
 
# Disclaimer: This code is seriously messy and VERY limited! It's just a quick hack to get things working until  
Line 76: Line 73:
 
device_id=162
 
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 = [
 
ruleconfig = [
 
{ :trigger => '164 -108 2 9 25 "1"', :on => "162 103 1 192", :off => "162 103 1 193", :name => "entrance", :zoneid => 1, :roomid => 3,  
 
{ :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' },
+
         :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,  
 
{ :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' },
+
         :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,  
 
{ :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' },
+
         :timeout => 90, :enable => '177 1 192', :disable => '177 1 193' },
{ :trigger => '78 162 2 9 25 1', :response => "162 102 1 192", :name => "sw 1 on"},
+
{ :trigger => '82 9 1 694 2 5 9 48', :response => "162 102 1 192", :name => "bedroom orbiter registered"},
{ :trigger => "78 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 => "bedroom md shutdown"},
{ :trigger => '82 162 2 9 25 1', :response => "162 102 1 192", :name => "sw 2 on"},
+
{ :trigger => '162 2 9 25 1', :response => "162 102 1 192", :name => "sw 1 on"},
{ :trigger => "82 162 2 9 25 0", :response => "162 102 1 193", :name => "sw 2 off"},
+
{ :trigger => "162 2 9 25 0", :response => "162 102 1 193", :name => "sw 1 off"},
{ :trigger => '83 162 2 9 25 1', :response => "162 154 1 192", :name => "sw 3 on"},
+
{ :trigger => '162 2 9 25 1', :response => "162 102 1 192", :name => "sw 2 on"},
{ :trigger => "83 162 2 9 25 0", :response => "162 154 1 193", :name => "sw 3 off"},
+
{ :trigger => "162 2 9 25 0", :response => "162 102 1 193", :name => "sw 2 off"},
{ :trigger => '83 162 2 9 25 1', :response => "162 99 1 192", :name => "sw 3 on"},
+
{ :trigger => '162 2 9 25 1', :response => "162 154 1 192", :name => "sw 3 on"},
{ :trigger => "83 162 2 9 25 0", :response => "162 99 1 193", :name => "sw 3 off"}
+
{ :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"}
 
]
 
]
  
Line 175: Line 176:
 
def correlate_msg(msg)
 
def correlate_msg(msg)
 
puts "Correlating message: (#{msg})"
 
puts "Correlating message: (#{msg})"
 +
msg_without_from_address = msg.gsub(/^[0-9]*\s/, '')
 
@rules.each do |rule|
 
@rules.each do |rule|
 
if msg == rule[:trigger]
 
if msg == rule[:trigger]
Line 202: Line 204:
 
  end
 
  end
 
end
 
end
if msg == zone[:enable]
+
if msg_without_from_address == zone[:enable]
 
   puts "Enabling auto lights for #{zone[:name]}"
 
   puts "Enabling auto lights for #{zone[:name]}"
 
   zone[:auto] = 1
 
   zone[:auto] = 1
 
end
 
end
if msg == zone[:disable]
+
if msg_without_from_address == zone[:disable]
 
   puts "Disabling auto lights for #{zone[:name]}"
 
   puts "Disabling auto lights for #{zone[:name]}"
 
   zone[:auto] = 0
 
   zone[:auto] = 0
Line 239: Line 241:
 
     end
 
     end
 
}
 
}
 +
 +
 
</pre>
 
</pre>
  
Line 244: Line 248:
  
 
=== Requirements ===
 
=== Requirements ===
 
+
* SSH access to the core from your Mac without using passwords (Google will help)
1. SSH access to the core from your Mac without using passwords (Google will help)
+
* A lamp at your desk, controlled by LMCE
2. A lamp at your desk, controlled by LMCE
+
  
 
=== The Script ===
 
=== The Script ===

Latest revision as of 18:22, 20 February 2009

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