Freeswitch is an open source telephony systems. In using it, it quickly became apparent to me that I needed a GUI tool to manage the configuration and provide user productivity tools.
What I found after searching is that there are not many options.
- FusionPBX really the only usable tools, but I found it had limitations. Mainly, around being a generic CMS.
- FreePBX, which is a great asterisk tool, is working to develop a Freeswitch version. It show great promise, but in my opinion is way to complicated to extend.
This was started, because I need a CMS as the glue to connect all my systems and content. I have been using Joomla for many years, an so this was an easy fit.
I created a component for Joomla and publish it for general use at some point. This work is a standard addon to Joomla that provides a backend/administrative interface for configuration, and a frontend interface that provides productivity tools for end users.
The following demonstrates the ability to use the Smarthome Smartlinc network controller, similar to a dedicated PLM.
I recently saw a post on the Smarthome forum that uncovered a previously undocument method to communicate directly with the PLM embedded with in the Smartlinc controller.
The Smartlinc listens on TCP port 9761 and allows bi-directional communication.
Insteon.rb Main program
#!/usr/bin/env ruby
require 'rubygems'
require 'thread'
require 'logger'
require '/usr/src/insteon/Sunrise.rb'
require 'pp'
require "mysql"
require 'net/smtp'
require '/usr/src/insteon/ControllerPLM.rb'
require '/usr/src/insteon/insteonDeviceTypes.rb'
require '/usr/src/insteon/insteonConfig.rb'
#$log.level = Logger::DEBUG
$log.info('Starting...')
$mysql = Mysql::new($dbhost, $dbusername, $dbpassword, $dbtable)
$recvqueue = Queue.new
$sendqueue = Queue.new
$controller = ControllerPLM.new($chost,$cport,$cusername,$cpassword,$shost,$sport,$susername,$spassword)
producer,webserver = $controller.start
producer.priority = 2
producer.abort_on_exception = true
webserver.abort_on_exception = true
def parse_buffstatus(e)
logdata = "Message: #{e['Description']}(#{e['Event']}) "
case e['Event']
when '0250' # received standard message
logdata += "From: #{e['From']} "
logdata += "To: #{e['To']} "
logdata += "Flags:[" + parse_flag(e['Flags']) + '] '
logdata += "Command: #{e['Command']} "
if e['Flags'].hex & 32 == 32
query = "SELECT * from jos_insteondevices WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
if res.num_rows() > 0
r = res.fetch_hash()
case r['type'][0..1]
when '01', '02'
#if e['Command'][0..1] == '00' or e['Command'][0..1] == '11' or e['Command'][0..1] == '13'
if e['Command'][2..3].hex > 0x00
query = "UPDATE jos_insteondevices SET state='ON',brightness='#{hextopercent(e['Command'][2..3])}',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
else
query = "UPDATE jos_insteondevices SET state='OFF',brightness='#{hextopercent(e['Command'][2..3])}',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
end
#end
if e['Command'][0..1] == '15' or e['Command'][0..1] == '16'
$controller.SendLightStatus(e['From'],0)
end
when '10'
if e['Command'][0..1] == '11'
query = "UPDATE jos_insteondevices SET state='ON',brightness='N/A',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
else
query = "UPDATE jos_insteondevices SET state='OFF',brightness='N/A',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
end
end
end
end
if e['Flags'].hex & 192 == 192
query = "SELECT * from jos_insteondevices WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
if res.num_rows() > 0
r = res.fetch_hash()
case r['type'][0..3]
when '1001'
if e['Command'][0..1] == '11'
query = "UPDATE jos_insteondevices SET state='ON',brightness='N/A',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
else
query = "UPDATE jos_insteondevices SET state='OFF',brightness='N/A',lastupdate=NOW() WHERE device = '#{e['From']}'"
$log.debug(query)
res = $mysql.query(query)
end
end
end
end
if e['Flags'].hex & 192 == 192
#query = "SELECT * from jos_insteondevices WHERE todeviceid = '#{e['From']}' and devicegroup = '#{e['To'][-2..-1]}'"
#$log.debug(query)
#res = $mysql.query(query)
#if res.num_rows() > 0
# r = res.fetch_hash()
# case r['type'][0..3]
# when '0005'
# case e['Command'][0..1]
# when '11'
# query = "UPDATE jos_insteondevices SET state='ON' WHERE todeviceid = '#{e['From']}' and devicegroup = '#{e['To'][-2..-1]}'"
# $log.debug(query)
# #res = $mysql.query(query)
# when '13'
# query = "UPDATE jos_insteondevices SET state='OFF' WHERE todeviceid = '#{e['From']}' and devicegroup = '#{e['To'][-2..-1]}'"
# $log.debug(query)
# #res = $mysql.query(query)
# end
# end
#end
end
when '0251' # received extended message
logdata += "From: #{e['From']} "
logdata += "To: #{e['To']} "
logdata += "Flags:[" + parse_flag(e['Flags']) + '] '
logdata += "Command: #{e['Command']} "
logdata += "Extended: #{e['Extended']} "
when '0253' # ALL-Linking Completed
logdata += "From: #{e['From']} "
logdata += "Group: #{e['To']} "
logdata += "Link Code: "
case e['Linkcode']
when '00'
logdata += "IM is a Responder"
when '01'
logdata += "IM is a Controller"
when 'FF'
logdata += "ALL-Link to the device was deleted"
else
logdata += "Unknown"
end
logdata += "Cat: #{e['Category']} "
logdata += "Sub Cat: #{e['Subcategory']} "
logdata += "Status: #{e['Status']}"
when '0254' # Button Event Report
logdata += "Status: "
case e['Status']
when '02'
logdata += "IM SET Button tapped"
when '03'
logdata += "IM SET Button held"
when '04'
logdata += "IM SET Button released after hold"
when '12'
logdata += "IM Button 2 tapped"
when '13'
logdata += "IM Button 2 held"
when '14'
logdata += "IM Button 2 released after hold"
when '22'
logdata += "IM Button 3 tapped"
when '23'
logdata += "IM Button 3 held"
when '24'
logdata += "IM Button 3 released after hold"
else
logdata += "Unknown"
end
when '0256' # ALL-Link Cleanup Failure Report
logdata += "Group: #{e['To']} "
logdata += "Device: #{e['From']} "
when '0257' # All-Link Report
logdata += "Flags:[" + parse_flag(e['Flags']) + '] '
logdata += "Group: #{e['To']} "
logdata += "Device: #{e['From']} "
logdata += "Data: #{e['Command']} "
when '0258' # ALL-Link Cleanup Status Report
logdata += "Status: #{e['Status']}"
when '0260' # Get IM Info
logdata += "Device: #{e['From']} "
logdata += "Cat: #{e['Category']} "
logdata += "Sub Cat: #{e['Subcategory']} "
logdata += "Version: #{e['Version']} "
logdata += "Status: #{e['Status']}"
when '0261' # Scene Command
logdata += "Scene: #{e['From']} "
logdata += "Command: "
case e['Command']
when '11'
logdata += "On"
when '12'
logdata += "Fast On"
when '13'
logdata += "Off"
when '14'
logdata += "Fast Off"
else
logdata += "Unknown"
end
logdata += " Result: #{e['Status']}"
when '0262' # Send Message
logdata += "To: #{e['To']} "
logdata += "Flags:[" + parse_flag(e['Flags']) + '] '
logdata += "Command: #{e['Command']} "
if e['Flags'].hex & 16 == 16
logdata += e['Extended'] + ' '
end
logdata += "Status: "
case e['Status']
when '06'
logdata += "Ack"
when '15'
logdata += "Nack"
when 'FF'
logdata += "Fail"
else
logdata += "Unknown"
end
when '0265' # Cancel All-linking
logdata += "Status: #{e['Status']} "
when '0266' # Get IM Info
logdata += "Cat: #{e['Category']} "
logdata += "Sub Cat: #{e['Subcategory']} "
logdata += "Version: #{e['Version']} "
logdata += "Status: #{e['Status']}"
when '0269' # Get First ALL-Link
logdata += "Status: #{e['Status']} "
when '026A' # Get Next ALL-Link
logdata += "Status: #{e['Status']} "
when '026B' # Set IM Configuration
logdata += "Command: #{e['Command']} "
logdata += "Status: #{e['Status']} "
when '026F' # Manage ALL-Link Record
logdata += "Command: #{e['Command']} "
logdata += "Flags: #{e['Command']} "
logdata += "Group: #{e['To']} "
logdata += "Device: #{e['From']} "
logdata += "Cat: #{e['Category']} "
logdata += "Sub Cat: #{e['Subcategory']} "
logdata += "Version: #{e['Version']} "
logdata += "Status: #{e['Status']} "
else
logdata += "Command: #{e['Command']} "
end
$log.info(logdata)
rescue
$log.error("Parse Buffer error #{$!} #{$@[0]} Line: #{$.}")
end
def parse_flag(flag)
retval = flag.hex
f = ''
if retval & 128 == 128
f = f + 'B'
else
f = f + 'b'
end
if retval & 64 == 64
f = f + 'G'
else
f = f + 'g'
end
if retval & 32 == 32
f = f + 'A'
else
f = f + 'a'
end
if retval & 16 == 16
f = f + 'E'
else
f = f + 'e'
end
f = f + ' HL:'
if retval & 8 == 8
f = f + '1'
else
f = f + '0'
end
if retval & 4 == 4
f = f + '1'
else
f = f + '0'
end
f = f + ' MH:'
if retval & 2 == 2
f = f + '1'
else
f = f + '0'
end
if retval & 1 == 1
f = f + '1'
else
f = f + '0'
end
return f
end
def percenttohex(level)
# convert from percent to byte
return "%X" %(level.to_i * 2.55).to_i
end
def hextopercent(level)
return (level.hex.to_i / 2.55).to_i
end
def checkSchedule(id,time=Time.now)
date = Date.parse(time.to_s)
calc = SolarEventCalculator.new(date, BigDecimal.new($long), BigDecimal.new($lat))
localSunrise = calc.compute_official_sunrise($timezone)
localSunset = calc.compute_official_sunset($timezone)
sr = Time.parse(localSunrise.to_s)
ss = Time.parse(localSunset.to_s)
logdata = ""
retval = false
query = "SELECT * from jos_insteonschedules as c WHERE c.id = '#{id}' and c.published = 1"
$log.debug(query)
sres = $mysql.query(query)
if sres.num_rows() > 0
s = sres.fetch_hash()
logdata += "Schedule: #{s['description']}, "
daymatch = false
case time.wday()
when 0
daymatch = true if s['dsun'] == '1'
when 1
daymatch = true if s['dmon'] == '1'
when 2
daymatch = true if s['dtue'] == '1'
when 3
daymatch = true if s['dwed'] == '1'
when 4
daymatch = true if s['dthu'] == '1'
when 5
daymatch = true if s['dfri'] == '1'
when 6
daymatch = true if s['dsat'] == '1'
end
if daymatch
logdata += "Days matched, "
case s['timecode']
when '0' # timecode 0 = before time
logdata += "Schedule before time "
stime = Time.parse("#{time.strftime("%Y-%m-%d")} #{s['stime']}")
#$log.info("Times ct #{time.strftime("%Y/%m/%d %I:%M%p")} at #{stime.strftime("%Y/%m/%d %I:%M%p")} ")
if (time - stime) <= 0
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '1' # timecode 1 = After time
logdata += "Schedule After time "
stime = Time.parse("#{time.strftime("%Y-%m-%d")} #{s['stime']}")
#$log.info("Times ct #{time.strftime("%Y/%m/%d %I:%M%p")} at #{stime.strftime("%Y/%m/%d %I:%M%p")} ")
if time >= stime
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '2' # timecode 2 = at Time
logdata += "Schedule AT time "
stime = Time.parse("#{time.strftime("%Y-%m-%d")} #{s['stime']}")
#$log.info("Times ct #{time.strftime("%Y/%m/%d %I:%M%p")} at #{stime.strftime("%Y/%m/%d %I:%M%p")} diff #{time - stime}")
diff = time - stime
if (diff >= 0 && diff <= 0.5)
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '3' # timecode 3 = before sunrise
logdata += "Schedule before sunrise "
if time >= ss
sr += 60*60*24 # add day for sunrise tomorrow
end
#$log.info("Times sr #{sr.strftime("%Y/%m/%d %I:%M%p")} ss #{ss.strftime("%Y/%m/%d %I:%M%p")} ")
if time <= sr - (s['timeoffset'].to_i * 60)
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '4' # timecode 4 = after sunrise
logdata += "Schedule after sunrise "
if time >= ss
sr += 60*60*24 # add day for sunrise tomorrow
end
#$log.info("Times sr #{sr.strftime("%Y/%m/%d %I:%M%p")} ss #{ss.strftime("%Y/%m/%d %I:%M%p")} ")
if time >= sr + (s['timeoffset'].to_i * 60)
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '5' # timecode 5 = before sunset
logdata += "Schedule before sunset "
if time <= sr
ss -= 60*60*24 # subtract day for sunrise yesterday
end
#$log.info("Times sr #{sr.strftime("%Y/%m/%d %I:%M%p")} ss #{ss.strftime("%Y/%m/%d %I:%M%p")} ")
if time <= ss - (s['timeoffset'].to_i * 60)
retval = true
#$log.info("Time match #{time} #{stime}")
end
when '6' # timecode 6 = after sunset
logdata += "Schedule after sunset "
if time <= sr
ss -= 60*60*24 # subtract day for sunrise yesterday
end
#$log.info("Times sr #{sr.strftime("%Y/%m/%d %I:%M%p")} ss #{ss.strftime("%Y/%m/%d %I:%M%p")} ")
if time >= ss + (s['timeoffset'].to_i * 60)
retval = true
#$log.info("Time match #{time} #{stime}")
end
end
end
logdata += "Matched"
#pp s
end
return retval, logdata
end
def sendcommands(id)
query = "SELECT * from jos_insteoncommands as c WHERE c.eventid = #{id} and c.published = 1 ORDER BY eventid, ordering,id"
$log.debug(query)
cres = $mysql.query(query)
if cres.num_rows() > 0
clearqueue = true
cres.each_hash() {|sd|
if sd['commandtype'] == '0' # insteon command
command = "#{sd['commandprefix']}#{sd['commanddevice']}#{sd['commandflag']}#{sd['command']}"
if clearqueue
$controller.ClearSendQueue(sd['commanddevice'])
clearqueue = false
end
$log.info("Sending insteon command #{sd['description']} #{sd['command']} delay #{sd['delay']}")
$controller.SendPLM(command,desc='Schedule command',sd['delay'].to_i)
end
if sd['commandtype'] == '1' # email command
$log.info("Sending email #{sd['description']} to #{sd['email']}")
time = Time.new
msg = "From: #{$emailfromname} <#{$emailfrom}>\n"
msg += "To: <#{sd['email']}>\n"
msg += "Subject: #{sd['description']}\n"
msg += "\n#{sd['description']} at #{time.strftime("%Y/%m/%d %I:%M%p")}\n"
Net::SMTP.start($emailserver) do |smtp|
smtp.send_message msg, $emailfrom, sd['email']
smtp.finish
end
end
}
end
end
def execute_message(m)
time = Time.now
if m == nil
query = "SELECT * from jos_insteonevents as c "
query += "WHERE c.eventtype = 1 "
query += "and c.published = 1"
else
query = "SELECT * from jos_insteonevents as c "
query += "WHERE c.from = '#{m['From']}' and c.to = '#{m['To']}' and '#{m['Command']}' LIKE c.command and c.eventtype = 0 "
query += "and c.published = 1"
end
$log.debug(query)
eres = $mysql.query(query)
if eres.num_rows() > 0
eres.each_hash() {|e|
logdata = "Event:#{e['description']}\n"
query = "SELECT * from jos_insteonscheduledetail as c WHERE c.eventid = #{e['id']} and c.published = 1"
$log.debug(query)
sdres = $mysql.query(query)
if sdres.num_rows() > 0
match = true
sdres.each_hash() {|sd|
if match
match,slog = checkSchedule(sd['schedulesid'])
logdata += " Schedule Detail:#{sd['description']}\n #{slog}\n" if match
end
}
else
match = true
end
if match
logdata.split("\n").each { |l|
$log.info(l)
}
sendcommands(e['id'])
end
}
end
end
def datetest
$times = []
$times << Time.parse("2010-11-10 01:00")
$times << Time.parse("2010-11-10 02:00")
$times << Time.parse("2010-11-10 03:00")
$times << Time.parse("2010-11-10 04:00")
$times << Time.parse("2010-11-10 05:00")
$times << Time.parse("2010-11-10 06:00")
$times << Time.parse("2010-11-10 07:00")
$times << Time.parse("2010-11-10 08:00")
$times << Time.parse("2010-11-10 09:00")
$times << Time.parse("2010-11-10 10:00")
$times << Time.parse("2010-11-10 11:00")
$times << Time.parse("2010-11-10 12:00")
$times << Time.parse("2010-11-10 13:00")
$times << Time.parse("2010-11-10 14:00")
$times << Time.parse("2010-11-10 15:00")
$times << Time.parse("2010-11-10 16:00")
$times << Time.parse("2010-11-10 17:00")
$times << Time.parse("2010-11-10 18:00")
$times << Time.parse("2010-11-10 19:00")
$times << Time.parse("2010-11-10 20:00")
$times << Time.parse("2010-11-10 21:00")
$times << Time.parse("2010-11-10 22:00")
$times << Time.parse("2010-11-10 23:00")
$times << Time.parse("2010-11-10 00:00")
query = "SELECT * from jos_insteonschedules as c WHERE c.published = 1"
$log.debug(query)
sdres = $mysql.query(query)
if sdres.num_rows() > 0
sdres.each_hash() {|sd|
$times.each {|t|
$log.info("Schedule #{sd['description']} Time #{t.strftime("%Y/%m/%d %I:%M%p")}")
match=checkSchedule(sd['id'],t)
if match
$log.info("Matched #{sd['description']} \n")
else
$log.info("Missed #{sd['description']} \n")
end
}
}
end
exit
end
sleep(2) #wait for the controller thread to get going
$controller.clearRecvQueue()
$log.info("Sending IM Monitor Mode")
$controller.SendMonitor()
#get status of the lights
query = "SELECT * from jos_insteondevices as c WHERE c.published = 1"
$log.debug(query)
res = $mysql.query(query)
if res.num_rows() > 0
res.each_hash() {|d|
case d['type'][0..1]
when '01', '02'
command = "0262#{d['device']}0F1900"
$log.info("Sending insteon status #{command} delay 0")
$controller.SendPLM(command,desc='Initialize',0)
end
}
end
scheduletime = Time.now
while true
$log.debug("Producer #{producer.status} Webserver #{webserver.status} Main #{Thread.main.status}")
#puts $recvqueue.length()
if producer.status == 'run'
end
if $recvqueue.length() > 0
#$log.info("event")
message = $recvqueue.pop()
parse_buffstatus(message)
execute_message(message)
else
if Time.now > scheduletime + 15
#$log.info("periodic")
execute_message(nil)
scheduletime = Time.now
end
end
sleep(0.1)
end
$log.close
ControllerPLM.rb
require 'timeout'
require 'thread'
require 'logger'
require 'socket'
class HttpServer
def initialize(session, request)
@session = session
@request = request
end
def serve()
if @request =~ /GET .* HTTP.*/
fileName = @request.gsub(/GET \//, '').gsub(/ HTTP.*/, '')
end
fileName = fileName.strip
begin
@session.print "HTTP/1.1 200/OK\r\nServer: Insteon\r\nContent-type: text/plain\r\n\r\n"
@session.write(fileName)
ensure
@session.close
end
return fileName
end
end
class ControllerPLM
def initialize(host, port, username, password,shost, sport, susername, spassword)
@buffer = ''
@host = host
@port = port
@username = username
@password = password
@shost = shost
@sport = sport
@susername = susername
@spassword = spassword
@socket = nil
end
def start
producer = Thread.new {
@socket = TCPSocket.new( @host, @port )
while true
if $sendqueue.length() > 0
message = $sendqueue.pop()
if message['Time'] <= Time.now
WritePLM(message['Path'],message['ErrorDesc'])
else
$sendqueue.push(message)
end
end
check_controller()
sleep(0.2) # don't go to fast or you will over run the PLM
end
}
webserver = Thread.new {
server = TCPServer.new(@shost, @sport)
loop do
session = server.accept
request = session.gets
logStr = "Web request: #{session.peeraddr[2]} (#{session.peeraddr[3]}) #{request.strip}"
$log.info(logStr)
Thread.start(session, request) do |session, request|
command = HttpServer.new(session, request).serve()
if command[0..1] == '02'
$log.info("Sending Web Server command #{command} ")
SendPLM(command,'Web Command')
end
sleep(0.1)
end
end
}
return producer, webserver
end
def recv_controller()
retval = ''
begin
Timeout.timeout 1 do
recv = @socket.recv( 1024 )
retval = recv.unpack('H*').join.upcase
end
rescue Timeout::Error
#$log.error("recv_controller timeout")
retval = ''
end
return retval
end
def check_controller()
@buffer += recv_controller()
if !@buffer.empty?
@buffer = $controller.queue_messages(@buffer)
end
end
def queue_messages(buff)
bs = ''
buff = buff.gsub("0250", ",0250")
buff = buff.gsub("0251", ",0251")
buff = buff.gsub("0252", ",0252")
buff = buff.gsub("0253", ",0253")
buff = buff.gsub("0254", ",0254")
buff = buff.gsub("0255", ",0255")
buff = buff.gsub("0256", ",0256")
buff = buff.gsub("0257", ",0257")
buff = buff.gsub("0258", ",0258")
buff = buff.gsub("0260", ",0260")
buff = buff.gsub("0261", ",0261")
buff = buff.gsub("0262", ",0262")
buff = buff.gsub("0263", ",0263")
buff = buff.gsub("0264", ",0264")
buff = buff.gsub("0265", ",0265")
buff = buff.gsub("0266", ",0266")
buff = buff.gsub("0267", ",0267")
buff = buff.gsub("0268", ",0268")
buff = buff.gsub("0269", ",0269")
buff = buff.gsub("026A", ",026A")
buff = buff.gsub("026B", ",026B")
buff = buff.gsub("026C", ",026C")
buff = buff.gsub("026D", ",026D")
buff = buff.gsub("026E", ",026E")
buff = buff.gsub("026F", ",026F")
buff = buff.gsub("0270", ",0270")
buff = buff.gsub("0271", ",0271")
buff = buff.gsub("0272", ",0272")
buff = buff.gsub("0273", ",0273")
time = Time.new
e = buff.split(',')
e.each_index {|i|
if !e[i].empty?
message = Hash.new
message['Time'] = time
message['Event'] = ''
message['Description'] = ''
message['From'] = ''
message['To'] = ''
message['Flags'] = ''
message['Command'] = ''
message['Extended'] = ''
message['Category'] = ''
message['Subcategory'] = ''
message['Version'] = ''
message['Status'] = ''
message['Linkcode'] = ''
event = e[i][0..3]
case event
when '0250' # received standard message
arr = e[i].scan(/(....)(......)(......)(..)(....)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'Std Message'
message['From'] = arr[0][1]
message['To'] = arr[0][2]
message['Flags'] = arr[0][3]
message['Command'] = arr[0][4]
else
bs = e[i]
end
when '0251' # received extended message
arr = e[i].scan(/(....)(......)(......)(..)(....)(............................)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'Ext Message'
message['From'] = arr[0][1]
message['To'] = arr[0][2]
message['Flags'] = arr[0][3]
message['Command'] = arr[0][4]
message['Extended'] = arr[0][5]
else
bs = e[i]
end
when '0253' # All Linking Completed
arr = e[i].scan(/(....)(..)(..)(......)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 7
message['Event'] = arr[0][0]
message['Description'] = 'All Linking Completed'
message['Linkcode'] = arr[0][1]
message['To'] = arr[0][2]
message['From'] = arr[0][3]
message['Category'] = arr[0][4]
message['Subcateory'] = arr[0][5]
message['Status'] = arr[0][6]
else
bs = e[i]
end
when '0254' # Button Event Report
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Button Event Report'
message['Status'] = arr[0][1]
else
bs = e[i]
end
when '0256' # ALL-Link Cleanup Failure Report
arr = e[i].scan(/(....)(..)(......)/)
if arr.length == 1 and arr[0].length == 3
message['Event'] = arr[0][0]
message['To'] = arr[0][1]
message['From'] = arr[0][2]
message['Description'] = 'ALL-Link Cleanup Failure Report'
else
bs = e[i]
end
when '0257' # All-Link Report
arr = e[i].scan(/(....)(..)(..)(......)(......)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'All-Link Report'
message['From'] = arr[0][3]
message['To'] = arr[0][2]
message['Flags'] = arr[0][1]
message['Command'] = arr[0][4]
else
bs = e[i]
end
when '0258' # ALL-Link Cleanup Status Report
arr = e[i].scan(/(....)(.*)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'All-Link Cleanup'
message['Status'] = arr[0][1]
else
bs = e[i]
end
when '0260' # Get IM Info
arr = e[i].scan(/(....)(......)(..)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'Get IM Info'
message['From'] = arr[0][1]
message['Category'] = arr[0][2]
message['Subcategory'] = arr[0][3]
message['Version'] = arr[0][4]
message['Status'] = arr[0][5]
else
bs = e[i]
end
when '0261' # Scene Command
arr = e[i].scan(/(....)(..)(..)(.*)/)
if arr.length == 1 and arr[0].length == 4
message['Event'] = arr[0][0]
message['Description'] = 'Scene Message'
message['From'] = arr[0][1]
message['Command'] = arr[0][2]
message['Status'] = arr[0][3]
else
bs = e[i]
end
when '0262' # Send Message
case e[i].length
when 46 # extended message
arr = e[i].scan(/(....)(......)(..)(....)(............................)(..)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'Send Message'
message['To'] = arr[0][1]
message['Flags'] = arr[0][2]
message['Command'] = arr[0][3]
message['Extended'] = arr[0][4]
message['Status'] = arr[0][5]
end
when 18 # standard message
arr = e[i].scan(/(....)(......)(..)(....)(..)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'Send Message'
message['To'] = arr[0][1]
message['Flags'] = arr[0][2]
message['Command'] = arr[0][3]
message['Status'] = arr[0][4]
end
else
bs = e[i]
end
when '0265' # Cancel All-linking
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Cancel All-linking'
message['Status'] = arr[0][1]
else
bs = e[i]
end
when '0266' # Set Host Device Category
arr = e[i].scan(/(....)(..)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'Set Host Device Category'
message['Category'] = arr[0][1]
message['Subcategory'] = arr[0][2]
message['Version'] = arr[0][3]
message['Status'] = arr[0][4]
else
bs = e[i]
end
when '0269' # Get First ALL-Link
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Get First ALL-Link'
message['Status'] = arr[0][1]
else
bs = e[i]
end
when '026A' # Get Next ALL-Link
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Get Next ALL-Link'
message['Status'] = arr[0][1]
else
bs = e[i]
end
when '026B' # Set IM Configuration
arr = e[i].scan(/(....)(..)(..)/)
if arr.length == 1 and arr[0].length == 3
message['Event'] = arr[0][0]
message['Command'] = arr[0][1]
message['Description'] = 'Set IM Configuration'
message['Status'] = arr[0][2]
else
bs = e[i]
end
when '026F' # Manage ALL-Link Record
# 026F 20 A2 01 13DED1 00 05 38 06
arr = e[i].scan(/(....)(..)(..)(..)(......)(..)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 9
message['Event'] = arr[0][0]
message['Command'] = arr[0][1]
message['Flags'] = arr[0][2]
message['To'] = arr[0][3]
message['From'] = arr[0][4]
message['Category'] = arr[0][5]
message['Subcategory'] = arr[0][6]
message['Version'] = arr[0][7]
message['Description'] = 'Manage ALL-Link Record'
message['Status'] = arr[0][8]
else
bs = e[i]
end
else
bs = e[i]
end
if !message['Event'].empty?
$recvqueue.push(message)
end
end
}
return bs
end
def ClearSendQueue(device)
tqueue = []
while $sendqueue.length() > 0
message = $sendqueue.pop()
if message['Path'][0..9] == "0262#{device}"
$log.info("Removing Queued command #{message['Path']} #{message['ErrorDesc']} #{message['Time']}")
else
tqueue << message
end
end
tqueue.each {|m|
$sendqueue.push(m)
}
end
def clearRecvQueue()
while $recvqueue.length() > 0
message = $recvqueue.pop()
end
end
def setclock()
end
def sendcommand(device,cmd,delay=0)
path = "0262#{device}0F#{cmd}"
SendPLM(path,'Send Command',delay)
end
def sendExtCommand(device,cmd,delay=0)
path = "0262#{device}1F#{cmd}"
SendPLM(path,'Send Extended Command',delay)
end
def SendGetFirst()
path = "0269"
SendPLM(path,'Send Get First')
end
def SendGetNext()
path = "026A"
SendPLM(path,'Send Get Next')
end
def SendRemoteSetMSB(device,msb)
path = "0262#{device}0F28#{msb}"
SendPLM(path,'Send Remote Set MSB')
end
def SendRemotePeek(device,offset)
path = "0262#{device}0F2B#{offset}"
SendPLM(path,'Send Remote Peek')
end
def SendGetFirst()
path = "0269"
SendPLM(path,'Send Get First')
end
def SendMonitor()
path = "026B40"
SendPLM(path,'Send Monitor')
end
def SendLightStatus(device,delay=10)
path = "0262#{device}0F1900"
SendPLM(path,'Send Light status',delay)
end
def SendIdRequest(device)
path = "0262#{device}0F1000"
SendPLM(path,'Send ID Request')
end
def SendEngineVersion(device)
path = "0262#{device}0F0D00"
SendPLM(path,'Send Engine Version')
end
def SendGetIMinfo()
path = "0260"
SendPLM(path,'Get IM Info')
end
def SendManageLink(record)
path = "026F#{record}"
SendPLM(path,'Send Manage Link')
end
def SendPing(device)
path = "0262#{device}0F1F00"
SendPLM(path,'Send Ping')
end
def ReadAllLinkDatabase(device,address='0000')
d1 = '00' # unused
d2 = '00' # read
d3 = address
#d5 = '01' # read one record
d5 = '00' # read all records
d6 = '00' # unused
d7 = '00' # unused
d8 = '00' # unused
d9 = '00' # unused
d10 = '00' # unused
d11 = '00' # unused
d12 = '00' # unused
d13 = '00' # unused
d14 = '00' # unused
path = "0262#{device}1F2F00#{d1}#{d2}#{d3}#{d5}#{d6}#{d7}#{d8}#{d9}#{d10}#{d11}#{d12}#{d13}#{d14}"
SendPLM(path,'Read All Link Database')
end
def WritePLM(path,desc='Send error')
# This is the HTTP request we send to fetch a file
$log.debug("WritePLM #{path}")
line=''
while !path.empty?
line += path[0..1].hex.chr
path = path[2..path.length]
end
@socket.write line
@socket.flush
#line = ''
#while !path.empty?
# line = path[0..1].hex.chr
# retval = $sp.write(line)
# path = path[2..path.length]
# #print "line #{line.inspect} retval #{retval} path #{path}\n"
#end
rescue
$log.error("#{desc} error #{$!} #{$@[0]} Line: #{$.}")
retval = false
return retval
end
def SendPLM(path,desc='Send error',delay=0)
message = Hash.new
message['Path'] = path
message['ErrorDesc'] = desc
message['Time'] = Time.now + delay
$log.debug("SendPLM Path:#{path} Time now:#{Time.now} Time delay:#{Time.now + delay}")
$sendqueue.push(message)
end
def getIMinfo()
begin
retval = {}
clearRecvQueue()
Timeout.timeout 30 do
SendGetIMinfo()
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0260'
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
retval['Address'] = message['From']
retval['Type'] = "#{message['Category']}#{message['Subcategory']}#{message['Version']}"
end
return retval
end
rescue Timeout::Error
$log.error("getIMinfo timeout...retrying")
retry
end
end
def getRequestID(device)
begin
retval = false
clearRecvQueue()
Timeout.timeout 30 do
SendIdRequest(device)
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0250' and message['From'] == device and message['Flags'].hex & 32 == 0
retval = message['To']
recfound = true
end
end
Thread.pass
end
end
return retval
end
rescue Timeout::Error
$log.error("getRequestID timeout...retrying")
retry
end
end
def getEngineVersion(device)
begin
retval = false
clearRecvQueue()
Timeout.timeout 30 do
SendEngineVersion(device)
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0250' and message['From'] == device
retval = message['Command'][2..3]
recfound = true
end
end
Thread.pass
end
end
return retval
end
rescue Timeout::Error
$log.error("getEngineVersion timeout...retrying")
retry
end
end
def remoteSpiderV1(device)
begin
Timeout.timeout 180 do
linkrecords = []
$remoteDBOffsetMSB = 0x0F # initial offset
$remoteDBOffsetLSB = 0xF8 # initial LSB offset
clearRecvQueue()
SendRemoteSetMSB(device,"%02X"%$remoteDBOffsetMSB)
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device
if message['Status'] != '06'
more = false
end
ackfound = message['Status']
end
end
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0250'
morepeek = true
while morepeek == true
peekrecord = ''
for x in 0..7
SendRemotePeek(device,"%02X"%($remoteDBOffsetLSB + x))
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device
if message['Status'] != '06'
more = false
end
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
peekfound = false
while !peekfound
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0250' and message['From'] == device
peekfound = true
peekrecord = peekrecord + message['Command'][2..3]
end
end
Thread.pass
end
end
end
peekflag = peekrecord[0..1]
peekgroup = peekrecord[2..3]
peekdevice = peekrecord[4..9]
peektype = peekrecord[10..15]
address = "#{"%02X"%$remoteDBOffsetMSB}#{"%02X"%$remoteDBOffsetLSB}"
$log.info("SpiderRemote device #{device} ALDB Flag:#{peekflag} Group:#{peekgroup} Device:#{peekdevice} Type:#{peektype} Address:#{address}")
if (peekflag.hex & 0x02) == 0x00 # High water mark
morepeek = false
end
if (peekflag.hex & 0x80) == 0x80 # Inuse
linkrecords << {'To'=>peekdevice,'From'=>device, 'Group'=>peekgroup, 'Flags' => peekflag,'Linkdata' => peektype,'Address' => address}
end
if $remoteDBOffsetLSB == 0x00
#OffsetMSB needs to be decreased, and LSB needs reset.
$remoteDBOffsetMSB = $remoteDBOffsetMSB - 1
$remoteDBOffsetLSB = 0xFF # the next line will decrease this by 8
end
# decrease DBOffsetLSB by 0x08
$remoteDBOffsetLSB = $remoteDBOffsetLSB - 0x08
end
recfound = true
end
end
end
end
return linkrecords
end
rescue Timeout::Error
$log.error("remoteSpider timeout...retrying")
retry
end
end
def remoteSpider(device)
begin
Timeout.timeout 30 do
linkrecords = []
clearRecvQueue()
nextaddress = '0000'
more = true
while more
ReadAllLinkDatabase(device,nextaddress)
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device
if message['Status'] != '06'
more = false
end
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0251' and message['From'] == device
peekaddress = message['Extended'][4..7]
nextaddress = "%04X"%(message['Extended'][4..7].hex - 8)
peekflag = message['Extended'][10..11]
peekgroup = message['Extended'][12..13]
peekdevice = message['Extended'][14..19]
peektype = message['Extended'][20..25]
$log.info("SpiderRemote device #{device} ALDB Flag:#{peekflag} Group:#{peekgroup} Device:#{peekdevice} Type:#{peektype} Address:#{peekaddress}")
if (peekflag.hex & 0x02) == 0x00 # High water mark
more = false
recfound = true
end
if (peekflag.hex & 0x80) == 0x80 # Inuse
linkrecords << {'To'=>peekdevice,'From'=>device, 'Group'=>peekgroup, 'Flags' => peekflag,'Linkdata' => peektype,'Address' => peekaddress}
end
end
Thread.pass
end
end
end
end
return linkrecords
end
rescue Timeout::Error
$log.error("remoteSpider timeout...retrying")
retry
end
end
def scanController()
begin
Timeout.timeout 30 do
linkrecords = []
clearRecvQueue()
SendGetFirst()
more = true
while more
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0269' or message['Event'] == '026A'
if message['Status'] != '06'
more = false
end
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0257'
linkrecords << {'To'=>message['From'],'From'=>$controllerAddress, 'Group'=>message['To'], 'Flags' => message['Flags'],'Linkdata' => message['Command'],'Type' => message['Command'],'Engine' => '00'}
recfound = true
end
end
Thread.pass
end
end
if more
$controller.SendGetNext()
end
end
return linkrecords
end
rescue Timeout::Error
$log.error("remoteSpider timeout...retrying")
retry
end
end
def ExistsInController(flags,devicegroup,device,linkdata)
begin
retval = false
clearRecvQueue()
Timeout.timeout 30 do
SendManageLink("00#{flags}#{devicegroup}#{device}#{linkdata}")
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '026F'
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
retval = true
else
retval = false
end
return retval
end
rescue Timeout::Error
$log.error("Exists In Controller timeout...retrying")
retry
end
end
def DeleteFromController(flags,devicegroup,device,linkdata)
begin
retval = false
clearRecvQueue()
Timeout.timeout 30 do
SendManageLink("80#{flags}#{devicegroup}#{device}#{linkdata}")
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '026F'
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
retval = true
else
retval = false
end
return retval
end
rescue Timeout::Error
$log.error("Delete From Controller timeout...retrying")
retry
end
end
def AddToController(flags,devicegroup,device,linkdata)
begin
retval = false
clearRecvQueue()
Timeout.timeout 30 do
SendManageLink("20#{flags}#{devicegroup}#{device}#{linkdata}")
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '026F'
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
retval = true
else
retval = false
end
return retval
end
rescue Timeout::Error
$log.error("Add To Controller Controller timeout...retrying")
retry
end
end
def Ping(device)
retries = 0
begin
retval = false
clearRecvQueue()
Timeout.timeout 5 do
SendPing(device)
ackfound = ''
while ackfound != '06' and ackfound != '15'
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0262' and message['To'] == device and message['Command'] == '1F00'
ackfound = message['Status']
end
end
Thread.pass
end
if ackfound == '06'
recfound = false
while recfound == false
if $recvqueue.length() > 0
message = $recvqueue.pop()
if message['Event'] == '0250' and message['From'] == device
retval = true
recfound = true
end
end
Thread.pass
end
end
return retval
end
rescue Timeout::Error
$log.error("Ping timeout...retrying")
retries += 1
if retries == 2
return false
else
retry
end
end
end
end
insteonConfig.rb
$log = Logger.new(STDOUT)
$log.level = Logger::INFO
#$log.level = Logger::DEBUG
$log.datetime_format = "%Y-%m-%d %H:%M:%S"
#Create the location
$long = "36.76"
$lat = "-80.73"
$timezone = 'America/New_York'
#$chost = "127.0.0.1"
$chost = "192.168.5.25" #address of the Smartlinc
#$cport = 20000
$cport = 9761
$cusername = ""
$cpassword = ""
$shost = "127.0.0.1"
$sport = 9091
$susername = ""
$spassword = ""
$tty = '/dev/insteon'
$dbhost = 'localhost'
$dbusername = 'mysqluser'
$dbpassword = 'mysqlpassword'
$dbtable = 'joomla'
$emailserver = ''
$emailfrom = ''
$emailfromname = 'Home'
|
This code allows a usb or serial Insteon PLM to interface to the Insteon 2412n controller code posted here.
#!/usr/bin/env ruby
# serialserver.rb
require 'rubygems'
require 'timeout'
require 'logger'
require 'pp'
require "socket"
require "serialport"
require '/usr/src/insteon/insteonConfig.rb'
include Socket::Constants
$log = Logger.new(STDOUT)
$log.level = Logger::INFO
$log.datetime_format = "%Y-%m-%d %H:%M:%S"
$log.info('Starting...')
$log.info("Starting TCP server on port #{$cport}")
server = TCPServer.new($cport)
while true
catch :socketerror do
rclients= []
wclients= []
eclients= []
$log.info("Opening serialport on device #{$tty}")
sp = SerialPort.new $tty,19200
sp.read_timeout = 100
sp.flow_control = SerialPort::NONE
begin
rclients << server.accept_nonblock
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
IO.select([server])
retry
end
$log.info("TCP connecton on port #{$cport} is accepted")
while true
readable, writeable = IO.select(rclients,wclients,eclients,0.1)
if readable
readable.each do |s|
command = ''
begin
command,sa = s.read_nonblock(1024)
rescue Errno::EAGAIN
IO.select([s])
rescue
$log.error("TCP connecton on port #{$cport} is gone")
s.close
sp.close
throw :socketerror
end
if !command.empty?
retval = sp.write(command)
$log.info "Request: #{command.unpack('H*').join.upcase}"
#sleep(0.1)
end
end
end
r = sp.read
if !r.empty?
$log.info("Response: #{r.unpack('H*').join.upcase}")
rclients[0].write r
end
Thread.pass
end
end
end
This has been update. Please see the update here
The following demonstrates the ability to use the Smarthome Smartlinc network controller, similar to a dedicated PLM.
I wrote this as my first Ruby program. It allows me expand the use of the 2412n to include motion sensor and more advanced timed events.
Insteon.rb Main program
require 'rubygems'
require 'thread'
require 'ice_cube'
require "rexml/document"
require "socket"
include Socket::Constants
require 'Sunrise'
require 'pp'
#Create the location
$long = "36.76"
$lat = "-80.73"
$timezone = 'America/New_York'
$host = "ha-controller.home.local"
$port = 80
$username = "htrn"
$password = "foofoodd"
$buffer = ""
$queue = Queue.new
producer = Thread.new {
def check_controller()
path = "/buffstatus.xml" # The file we want
# This is the HTTP request we send to fetch a file
request = "POST #{path} HTTP/1.1\r\n\r\n"
mr = 2
tbs = ''
while mr > 0
socket = TCPSocket.new($host,$port) # Connect to server
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(request[0..0]) # Send request
socket.flush
socket.write(request[1..-1]) # Send request
socket.flush
response = socket.read # Read complete response
# Split response at first blank line into headers and body
socket.close
header,body = response.split("\r\n\r\n", 2)
if header != nil
m = header.scan(/\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/min)
retval = {'status' => m[0][1].to_i,'content' => body}
else
retval = {'status' => 404,'content' => ''}
end
if retval['status'] == 200
bs = get_buffstatus(retval['content'])
if bs != nil
if tbs != bs
tbs = bs
else
mr = mr - 1
if mr == 0
clear_buffstatus()
queue_messages(bs)
end
end
end
else
puts "Buffstatus error #{retval['status']}"
end
end
return true
end
def queue_messages(buff)
buff = buff.gsub("0250", ",0250")
buff = buff.gsub("0251", ",0251")
buff = buff.gsub("0252", ",0252")
buff = buff.gsub("0253", ",0253")
buff = buff.gsub("0254", ",0254")
buff = buff.gsub("0255", ",0255")
buff = buff.gsub("0256", ",0256")
buff = buff.gsub("0257", ",0257")
buff = buff.gsub("0258", ",0258")
buff = buff.gsub("0260", ",0260")
buff = buff.gsub("0261", ",0261")
buff = buff.gsub("0262", ",0262")
buff = buff.gsub("0263", ",0263")
buff = buff.gsub("0264", ",0264")
buff = buff.gsub("0265", ",0265")
buff = buff.gsub("0266", ",0266")
buff = buff.gsub("0267", ",0267")
buff = buff.gsub("0268", ",0268")
buff = buff.gsub("0269", ",0269")
buff = buff.gsub("026A", ",026A")
buff = buff.gsub("026B", ",026B")
buff = buff.gsub("026C", ",026C")
buff = buff.gsub("026D", ",026D")
buff = buff.gsub("026E", ",026E")
buff = buff.gsub("026F", ",026F")
buff = buff.gsub("0270", ",0270")
buff = buff.gsub("0271", ",0271")
buff = buff.gsub("0272", ",0272")
buff = buff.gsub("0273", ",0273")
time = Time.new
e = buff.split(',')
e.each_index {|i|
if !e[i].empty?
message = Hash.new
message['Time'] = time
message['Event'] = ''
message['Description'] = ''
message['From'] = ''
message['To'] = ''
message['Flags'] = ''
message['Command'] = ''
message['Extended'] = ''
message['Category'] = ''
message['Subcateory'] = ''
message['Version'] = ''
message['Status'] = ''
message['Linkcode'] = ''
event = e[i][0..3]
case event
when '0250' # received standard message
arr = e[i].scan(/(....)(......)(......)(..)(....)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'Std Message'
message['From'] = arr[0][1]
message['To'] = arr[0][2]
message['Flags'] = arr[0][3]
message['Command'] = arr[0][4]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0251' # received extended message
arr = e[i].scan(/(....)(......)(......)(..)(....)(............................)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'Ext Message'
message['From'] = arr[0][1]
message['To'] = arr[0][2]
message['Flags'] = arr[0][3]
message['Command'] = arr[0][4]
message['Extended'] = arr[0][5]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0253' # All Linking Completed
arr = e[i].scan(/(....)(..)(..)(......)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'All Linking Completed'
message['Linkcode'] = arr[0][1]
message['To'] = arr[0][2]
message['From'] = arr[0][3]
message['Category'] = arr[0][4]
message['Subcateory'] = arr[0][5]
message['Status'] = arr[0][6]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0254' # Button Event Report
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Button Event Report'
message['Status'] = arr[0][1]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0257' # All-Link Report
arr = e[i].scan(/(....)(..)(..)(......)(......)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'All-Link Report'
message['From'] = arr[0][3]
message['To'] = arr[0][2]
message['Flags'] = arr[0][1]
message['Command'] = arr[0][4]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0258' # ALL-Link Cleanup Status Report
arr = e[i].scan(/(....)(.*)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'All-Link Cleanup'
message['Status'] = arr[0][1]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0260' # Get IM Info
arr = e[i].scan(/(....)(......)(..)(..)(..)(..)/)
if arr.length == 1 and arr[0].length == 6
message['Event'] = arr[0][0]
message['Description'] = 'Get IM Info'
message['From'] = arr[0][1]
message['Category'] = arr[0][2]
message['Subcateory'] = arr[0][3]
message['Version'] = arr[0][4]
message['Status'] = arr[0][5]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0261' # Scene Command
arr = e[i].scan(/(....)(..)(..)(.*)/)
if arr.length == 1 and arr[0].length == 4
message['Event'] = arr[0][0]
message['Description'] = 'Scene Message'
message['From'] = arr[0][1]
message['Command'] = arr[0][2]
message['Status'] = arr[0][3]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0262' # Send Message
arr = e[i].scan(/(....)(......)(..)(....)(.*)/)
if arr.length == 1 and arr[0].length == 5
message['Event'] = arr[0][0]
message['Description'] = 'Send Message'
message['To'] = arr[0][1]
message['Flags'] = arr[0][2]
message['Command'] = arr[0][3]
message['Status'] = arr[0][4][-2..-1]
if arr[0][2].hex & 16 == 16
message['Extended'] = arr[0][4][0..-3]
end
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0265' # Cancel All-linking
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Cancel All-linking'
message['Status'] = arr[0][1]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '0269' # Get First ALL-Link
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Get First ALL-Link'
message['Status'] = arr[0][1]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
when '026A' # Get Next ALL-Link
arr = e[i].scan(/(....)(..)/)
if arr.length == 1 and arr[0].length == 2
message['Event'] = arr[0][0]
message['Description'] = 'Get Next ALL-Link'
message['Status'] = arr[0][1]
else
message['Event'] = "FF#{event}"
message['Description'] = 'Malformed Message'
message['Command'] = e[i]
end
else
message['Event'] = 'FFFF'
message['Description'] = 'Unknown'
message['Command'] = e[i]
end
$queue.push(message)
end
}
end
def clear_buffstatus()
path = "/1?XB=M=1" # The file we want
# This is the HTTP request we send to fetch a file
request = "POST #{path} HTTP/1.1\r\n\r\n"
socket = TCPSocket.new($host,$port) # Connect to server
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(request[0..0]) # Send request
socket.flush
socket.write(request[1..-1]) # Send request
socket.flush
response = socket.read # Read complete response
socket.close
# Split response at first blank line into headers and body
header,body = response.split("\r\n\r\n", 2)
m = header.scan(/\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/min)
retval = {'status' => m[0][1].to_i,'content' => body}
return retval
rescue Errno::ECONNRESET => e
puts "Clear Buff Timeout error " + e
end
def get_buffstatus(xml)
doc = REXML::Document.new(xml)
doc.elements.each("/response/BS") do |elem|
return elem.text
end
end
while true
check_controller()
Thread.pass
end
}
def setclock()
now = Date.today.strftime("%H:%M")
path = "1?TD=${now}=1=falsefalse"
# This is the HTTP request we send to fetch a file
request = "POST #{path} HTTP/1.1\r\n\r\n"
socket = TCPSocket.new($host,$port) # Connect to server
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(request[0..0]) # Send request
socket.flush
socket.write(request[1..-1]) # Send request
socket.flush
response = socket.read # Read complete response
socket.close
# Split response at first blank line into headers and body
header,body = response.split("\r\n\r\n", 2)
m = header.scan(/\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/min)
retval = {'status' => m[0][1].to_i,'content' => body}
return retval
rescue Errno::ECONNRESET => e
puts "Set Clock Timeout error " + e
end
def sendcommand(device,cmd)
path = "/3?0262#{device}0F#{cmd}=I=3"
# This is the HTTP request we send to fetch a file
request = "POST #{path} HTTP/1.1\r\n\r\n"
socket = TCPSocket.new($host,$port) # Connect to server
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(request[0..0]) # Send request
socket.flush
socket.write(request[1..-1]) # Send request
socket.flush
response = socket.read # Read complete response
socket.close
# Split response at first blank line into headers and body
header,body = response.split("\r\n\r\n", 2)
m = header.scan(/\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/min)
retval = {'status' => m[0][1].to_i,'content' => body}
return retval
rescue Errno::ECONNRESET => e
puts "Send Command Timeout error " + e
end
def parse_buffstatus(e)
print e['Time'].strftime("%Y-%m-%d %H:%M:%S") + " "
print "Event: #{e['Description']}(#{e['Event']}) "
case e['Event']
when '0250' # received standard message
print "From: #{e['From']} "
print "To: #{e['To']} "
print "Flags:[" + parse_flag(e['Flags']) + '] '
print "Command: #{e['Command']} "
when '0251' # received extended message
print "From: #{e['From']} "
print "To: #{e['To']} "
print "Flags:[" + parse_flag(e['Flags']) + '] '
print "Command: #{e['Command']} "
print "Extended: #{e['Extended']} "
when '0253' # ALL-Linking Completed
print "From: #{e['From']} "
print "Group: #{e['To']} "
print "Link Code: "
case e['Linkcode']
when '00'
print "IM is a Responder"
when '01'
print "IM is a Controller"
when 'FF'
print "ALL-Link to the device was deleted"
else
print "Unknown"
end
print "Cat: #{e['Category']} "
print "Sub Cat: #{e['Subcategory']} "
print "Status: #{e['Status']}"
when '0254' # Button Event Report
print "Status: "
case e['Status']
when '02'
print "IM SET Button tapped"
when '03'
print "IM SET Button held"
when '04'
print "IM SET Button released after hold"
when '12'
print "IM Button 2 tapped"
when '13'
print "IM Button 2 held"
when '14'
print "IM Button 2 released after hold"
when '22'
print "IM Button 3 tapped"
when '23'
print "IM Button 3 held"
when '24'
print "IM Button 3 released after hold"
else
print "Unknown"
end
when '0257' # All-Link Report
print "Flags:[" + parse_flag(e['Flags']) + '] '
print "Group: #{e['To']} "
print "Device: #{e['From']} "
print "Data: #{e['Command']} "
when '0258' # ALL-Link Cleanup Status Report
print "Status: #{e['Status']}"
when '0260' # Get IM Info
print "Device: #{e['From']} "
print "Cat: #{e['Category']} "
print "Sub Cat: #{e['Subcategory']} "
print "Version: #{e['Version']} "
print "Status: #{e['Status']}"
when '0261' # Scene Command
print "Scene: #{e['From']} "
print "Command: "
case e['Command']
when '11'
print "On"
when '12'
print "Fast On"
when '13'
print "Off"
when '14'
print "Fast Off"
else
print "Unknown"
end
print " Result: #{e['Status']}"
when '0262' # Send Message
print "To: #{e['To']} "
print "Flags:[" + parse_flag(e['Flags']) + '] '
print "Command: #{e['Command']} "
if e['Flags'].hex & 16 == 16
print e['Extended'] + ' '
end
print "Status: "
case e['Status']
when '06'
print "Ack"
when '15'
print "Nack"
when 'FF'
print "Fail"
else
print "Unknown"
end
when '0265' # Cancel All-linking
print "Status: #{e['Status']} "
when '0269' # Get First ALL-Link
print "Status: #{e['Status']} "
when '026A' # Get Next ALL-Link
print "Status: #{e['Status']} "
else
print "Command: #{e['Command']} "
end
print "\n"
end
def parse_flag(flag)
retval = flag.hex
f = ''
if retval & 128 == 128
f = f + 'B'
else
f = f + 'b'
end
if retval & 64 == 64
f = f + 'G'
else
f = f + 'g'
end
if retval & 32 == 32
f = f + 'A'
else
f = f + 'a'
end
if retval & 16 == 16
f = f + 'E'
else
f = f + 'e'
end
f = f + ' HL:'
if retval & 8 == 8
f = f + '1'
else
f = f + '0'
end
if retval & 4 == 4
f = f + '1'
else
f = f + '0'
end
f = f + ' MH:'
if retval & 2 == 2
f = f + '1'
else
f = f + '0'
end
if retval & 1 == 1
f = f + '1'
else
f = f + '0'
end
return f
end
def daytime()
date = Date.today
calc = SolarEventCalculator.new(date, BigDecimal.new($long), BigDecimal.new($lat))
localSunrise = calc.compute_official_sunrise($timezone)
localSunset = calc.compute_official_sunset($timezone)
now = DateTime.now
now = DateTime.new(now.year, now.month, now.day, now.hour, now.min, now.sec, now.sec_fraction, now.offset)
if now >= localSunrise and now <= localSunset
retval = true
else
retval = false
end
return retval
end
def execute_message(m)
if m['From'] == '141120' and m['To'] == '000001'
print 'Command from Motion Sensor ' + m['Command'] + ' ' + (daytime()?'Day':'Night') + ' '
if m['Command'] = '1101' and daytime() == false
print "Send on to 154AE3"
sendcommand('154AE3', '11FF')
sendcommand('154DE4', '11FF')
end
print "\n"
end
if m['From'] == '13DED1' and m['To'] == '000001'
print 'Command from RemoteLinc ' + m['Command'] + ' '
if m['Command'] == '1100'
print 'Send On to 154AE3'
sendcommand('154AE3', '11FF')
end
if m['Command'] == '1300'
print 'Send Off to 154AE3'
sendcommand('154AE3', '13FF')
end
print "\n"
end
if m['From'] == '13DED1' and m['To'] == '000002'
print 'Command from RemoteLinc ' + m['Command'] + ' '
if m['Command'] == '1100'
print 'Send On to 154DE4'
sendcommand('154DE4', '11FF')
end
if m['Command'] == '1300'
print 'Send Off to 154DE4'
sendcommand('154DE4', '13FF')
end
print "\n"
end
end
producer.abort_on_exception = true
setclockrule = IceCube::Rule.daily
setclockschedule = IceCube::Schedule.new(Time.parse("01:00"), :duration => 2)
setclockschedule.add_recurrence_rule setclockrule
alloffrule = IceCube::Rule.daily
alloffschedule = IceCube::Schedule.new(Time.parse("11:00"), :duration => 2)
alloffschedule.add_recurrence_rule alloffrule
scheduletime = Time.now
while true
#puts "Producer #{producer.status} Timers #{timers.status} Main #{Thread.main.status}"
#puts $queue.length()
if $queue.length() > 0
message = $queue.pop()
#PP.singleline_pp(message)
#print ' '
execute_message(message)
parse_buffstatus(message)
end
if scheduletime + 1 <= Time.now
scheduletime = Time.now
if setclockschedule.occurring_at?(Time.now)
print "Setting clock\n"
setclock()
end
if alloffschedule.occurring_at?(Time.now)
print "All off\n"
sendcommand('154AE3', '13FF')
sendcommand('154DE4', '13FF')
end
end
Thread.pass
end
Sunrise.rb for daytime function
require 'bigdecimal'
require 'date'
require 'tzinfo'
class SolarEventCalculator
@date
@latitude
@longitude
def initialize(date, latitude, longitude)
@date = date
@latitude = latitude
@longitude = longitude
end
def compute_lnghour
lngHour = @longitude / BigDecimal.new("15")
lngHour.round(4)
end
def compute_longitude_hour(isSunrise)
minuend = (isSunrise) ? BigDecimal.new("6") : BigDecimal.new("18")
longHour = @date.yday + ((minuend - compute_lnghour) / BigDecimal.new("24"))
longHour.round(4)
end
def compute_sun_mean_anomaly(longHour)
constant = BigDecimal.new("0.9856")
((longHour * constant) - BigDecimal.new("3.289")).round(4)
end
def compute_sun_true_longitude(meanAnomaly)
mAsRads = degrees_as_rads(meanAnomaly)
sinM = BigDecimal.new(Math.sin(mAsRads.to_f).to_s)
sinTwoM = BigDecimal.new(Math.sin((2 * mAsRads).to_f).to_s)
firstParens = BigDecimal.new("1.916") * sinM
secondParens = BigDecimal.new("0.020") * sinTwoM
trueLong = meanAnomaly + firstParens + secondParens + BigDecimal.new("282.634")
trueLong = put_in_range(trueLong, 0, 360, 360)
trueLong.round(4)
end
def compute_right_ascension(sunTrueLong)
tanL = BigDecimal.new(Math.tan(degrees_as_rads(sunTrueLong).to_f).to_s)
ra = rads_as_degrees(BigDecimal.new(Math.atan(BigDecimal.new("0.91764") * tanL).to_s))
ra = put_in_range(ra, 0, 360, 360)
ra.round(4)
end
def put_ra_in_correct_quadrant(sunTrueLong)
lQuadrant = BigDecimal.new("90") * (sunTrueLong / BigDecimal.new("90")).floor
raQuadrant = BigDecimal.new("90") * (compute_right_ascension(sunTrueLong) / BigDecimal.new("90")).floor
ra = compute_right_ascension(sunTrueLong) + (lQuadrant - raQuadrant)
ra = ra / BigDecimal.new("15")
ra.round(4)
end
def compute_sin_sun_declination(sunTrueLong)
sinL = BigDecimal.new(Math.sin(degrees_as_rads(sunTrueLong).to_f).to_s)
sinDec = sinL * BigDecimal.new("0.39782")
sinDec.round(4)
end
def compute_cosine_sun_declination(sinSunDeclination)
cosDec = BigDecimal.new(Math.cos(Math.asin(sinSunDeclination)).to_s)
cosDec.round(4)
end
def compute_cosine_sun_local_hour(sunTrueLong, zenith)
cosZenith = BigDecimal.new(Math.cos(degrees_as_rads(BigDecimal.new(zenith.to_s))).to_s)
sinLatitude = BigDecimal.new(Math.sin(degrees_as_rads(@latitude)).to_s)
cosLatitude = BigDecimal.new(Math.cos(degrees_as_rads(@latitude)).to_s)
sinSunDeclination = compute_sin_sun_declination(sunTrueLong)
top = cosZenith - (sinSunDeclination * sinLatitude)
bottom = compute_cosine_sun_declination(sinSunDeclination) * cosLatitude
cosLocalHour = top / bottom
cosLocalHour.round(4)
end
def compute_local_hour_angle(cosSunLocalHour, isSunrise)
acosH = BigDecimal.new(Math.acos(cosSunLocalHour).to_s)
acosHDegrees = rads_as_degrees(acosH)
localHourAngle = (isSunrise) ? BigDecimal.new("360") - acosHDegrees : acosHDegrees
localHourAngle = localHourAngle / BigDecimal.new("15")
localHourAngle.round(4)
end
def compute_local_mean_time(sunTrueLong, longHour, t, sunLocalHour)
h = sunLocalHour
ra = put_ra_in_correct_quadrant(sunTrueLong)
parens = BigDecimal.new("0.06571") * t
time = h + ra - parens - BigDecimal.new("6.622")
utcTime = time - longHour
utcTime = put_in_range(utcTime.round(4), 0, 24, 24)
end
def compute_utc_solar_event(zenith, isSunrise)
longHour = compute_lnghour
eventLongHour = compute_longitude_hour(isSunrise)
meanAnomaly = compute_sun_mean_anomaly(eventLongHour)
sunTrueLong = compute_sun_true_longitude(meanAnomaly)
cosineSunLocalHour = compute_cosine_sun_local_hour(sunTrueLong, zenith)
if(cosineSunLocalHour > BigDecimal.new("1") || cosineSunLocalHour < BigDecimal.new("-1"))
return nil
end
sunLocalHour = compute_local_hour_angle(cosineSunLocalHour, isSunrise)
localMeanTime = compute_local_mean_time(sunTrueLong, longHour, eventLongHour, sunLocalHour)
timeParts = localMeanTime.to_s('F').split('.')
mins = BigDecimal.new("." + timeParts[1]) * BigDecimal.new("60")
mins = mins.truncate()
mins = pad_minutes(mins.to_i)
hours = timeParts[0]
Time.utc(@date.year, @date.mon, @date.mday, hours, pad_minutes(mins.to_i))
end
def compute_utc_civil_sunrise
convert_to_datetime(compute_utc_solar_event(96, true))
end
def compute_utc_civil_sunset
convert_to_datetime(compute_utc_solar_event(96, false))
end
def compute_utc_official_sunrise
convert_to_datetime(compute_utc_solar_event(90.8333, true))
end
def compute_utc_official_sunset
convert_to_datetime(compute_utc_solar_event(90.8333, false))
end
def compute_utc_nautical_sunrise
convert_to_datetime(compute_utc_solar_event(102, true))
end
def compute_utc_nautical_sunset
convert_to_datetime(compute_utc_solar_event(102, false))
end
def compute_utc_astronomical_sunrise
convert_to_datetime(compute_utc_solar_event(108, true))
end
def compute_utc_astronomical_sunset
convert_to_datetime(compute_utc_solar_event(108, false))
end
def convert_to_datetime(time)
DateTime.parse("#{@date.strftime}T#{time.hour}:#{time.min}:00+0000") unless time == nil
end
def compute_civil_sunrise(timezone)
put_in_timezone(compute_utc_solar_event(96, true), timezone)
end
def compute_civil_sunset(timezone)
put_in_timezone(compute_utc_solar_event(96, false), timezone)
end
def compute_official_sunrise(timezone)
put_in_timezone(compute_utc_solar_event(90.8333, true), timezone)
end
def compute_official_sunset(timezone)
put_in_timezone(compute_utc_solar_event(90.8333, false), timezone)
end
def compute_nautical_sunrise(timezone)
put_in_timezone(compute_utc_solar_event(102, true), timezone)
end
def compute_nautical_sunset(timezone)
put_in_timezone(compute_utc_solar_event(102, false), timezone)
end
def compute_astronomical_sunrise(timezone)
put_in_timezone(compute_utc_solar_event(108, true), timezone)
end
def compute_astronomical_sunset(timezone)
put_in_timezone(compute_utc_solar_event(108, false), timezone)
end
def put_in_timezone(utcTime, timezone)
tz = TZInfo::Timezone.get(timezone)
# puts "UTCTime #{utcTime}"
local = utcTime + get_utc_offset(timezone)
# puts "LocalTime #{local}"
offset = (get_utc_offset(timezone) / 60 / 60).to_i
offset = (offset > 0) ? "+" + offset.to_s : offset.to_s
timeInZone = DateTime.parse("#{@date.strftime}T#{local.strftime('%H:%M:%S')}#{offset}")
# puts "CALC:timeInZone #{timeInZone}"
timeInZone
end
def get_utc_offset(timezone)
tz = TZInfo::Timezone.get(timezone)
noonUTC = Time.gm(@date.year, @date.mon, @date.mday, 12, 0)
tz.utc_to_local(noonUTC) - noonUTC
end
def pad_minutes(minutes)
if(minutes < 10)
"0" + minutes.to_s
else
minutes
end
end
def put_in_range(number, lower, upper, adjuster)
if number > upper then
number -= adjuster
elsif number < lower then
number += adjuster
else
number
end
end
def degrees_as_rads(degrees)
pi = BigDecimal(Math::PI.to_s)
radian = pi / BigDecimal.new("180")
degrees * radian
end
def rads_as_degrees(radians)
pi = BigDecimal(Math::PI.to_s)
degree = BigDecimal.new("180") / pi
radians * degree
end
end
|