pimatic-mochad-simple
Version:
Sends and recieve X10 commands and events through a mochad server
218 lines (180 loc) • 7.97 kB
text/coffeescript
# MochadSimple plugin
module.exports = (env) ->
Promise = env.require 'bluebird'
# Require the [cassert library](https://github.com/rhoot/cassert).
assert = env.require 'cassert'
reconnect = require 'reconnect-net'
_ = env.require 'lodash'
# the plugin class
class MochadSimplePlugin extends env.plugins.Plugin
init: (app, @framework, config) ->
env.logger.debug "MochadSimple: init"
@connection = null
host = config.host
port = config.port
env.logger.info(
"MochadSimple: init with mochad server #{host}@port #{port}"
)
@cmdReceivers = []
deviceConfigDef = require("./device-config-schema")
deviceClasses = [
MochadSimpleController,
MochadSimpleSwitch
]
for Cl in deviceClasses
do (Cl) =>
@framework.deviceManager.registerDeviceClass(Cl.name, {
configDef: deviceConfigDef[Cl.name]
createCallback: (deviceConfig) =>
device = new Cl(deviceConfig)
if Cl in [MochadSimpleController]
@cmdReceivers.push device
return device
})
reconnector = reconnect(((conn) ->
# XXX Keep alive does not work [as expected](https://github.com/joyent/node/issues/6194)
conn.setKeepAlive(true, 0)
conn.setNoDelay(true)
conn.on 'data', ((data) ->
lines = data.toString()
env.logger.debug(lines)
@receiveCommandCallback(lines)
).bind(@)
).bind(@)).connect(port, host);
reconnector.on 'connect', ((connection) ->
env.logger.info("(re)Opened connection")
@connection = connection
).bind(@)
reconnector.on 'disconnect', ((err) ->
env.logger.error("Disconnected from #{@host}:#{@port}: " + err)
@connection = null;
).bind(@)
sendCommand: (command) ->
if @connection is null then throw new Error("No connection!")
env.logger.debug("Sending '#{command}'")
@connection.write(command + "\r\n")
receiveCommandCallback: (cmdString) =>
for cmdReceiver in @cmdReceivers
try
handled = cmdReceiver.handleReceivedCmd cmdString
break if handled
catch err
env.logger.error("Error processing recieved comand: " + err, err.stack)
finally
if (!handled)
env.logger.debug "received unhandled command string: #{cmdString}"
# MochadSimpleSwitch sends commands to X.10 devices
class MochadSimpleSwitch extends env.devices.PowerSwitch
constructor: (@config) ->
@id = @config.id
@name = @config.name
@housecode = @config.housecode
@unitcode = @config.unitcode
@protocol = @config.protocol
super()
changeStateTo: (state) ->
return Promise.try( =>
@sendCommand
plugin.sendCommand ("#{@protocol} #{@housecode}#{@unitcode} " + ( if state then "on" else "off" ))
@_setState state
)
.catch((error) -> env.logger.error("Couldn't send command '#{command}': " + error))
destroy: () ->
super()
class MochadSimpleController extends env.devices.Sensor
attributes:
lastX10Message:
description: "Contains the last X10 message recieved"
type: "string"
acronym: "Last X10 Message"
_lastX10Message = "None"
constructor: (@config) ->
@id = @config.id
@name = @config.name
@_lastSeen = {
housecode: "x"
unitcode: "0"
}
super(@config)
destroy: () ->
super()
getLastX10Message : () ->
return Promise.resolve(@_lastX10Message)
handleReceivedCmd: (lines) ->
# Parsing all-units-on/off
# example: 05/22 00:34:04 Rx PL House: P Func: All units on
# example: 05/22 00:34:04 Rx PL House: P Func: All units off
if m = /^\d{2}\/\d{2}\s+(?:\d{2}:){2}\d{2}\s(Rx|Tx)\s+(RF|PL)\s+House:\s+([a-pA-P])\s+Func:\s+All\s+(units|lights)\s+(on|off)$/m.exec(lines)
event = {
protocol: m[2].toLowerCase()
direction: m[1].toLowerCase()
housecode: m[3].toLowerCase()
unitcode: "*" + m[4]
state: m[5].toLowerCase()
}
env.logger.debug("Event: " + JSON.stringify(event))
@_lastX10Message = event.housecode + "-" + event.state
@emit "lastX10Message", @_lastX10Message
env.logger.debug("lastX10Message: " + @_lastX10Message)
# Parsing security remotes / sensors
# example: 07/04 02:07:52 Rx RFSEC Addr: 0xFE Func: Lights_On_SH624
# example: 07/04 02:46:46 07/04 02:46:47 Rx RFSEC Addr: 0xFE Func: Arm_Home_min_SH624
# example: 07/04 02:46:46 07/04 02:46:48 Rx RFSEC Addr: 0xFE Func: Disarm_SH624
# example: 07/04 02:46:46 07/04 02:46:49 Rx RFSEC Addr: 0xFE Func: Arm_Away_min_SH624
# example: 07/04 02:46:46 07/04 02:46:50 Rx RFSEC Addr: 0xFE Func: Disarm_SH624
# example: 07/04 02:46:46 07/04 02:46:51 Rx RFSEC Addr: 0xFE Func: Lights_On_SH624
# example: 07/04 02:46:46 07/04 02:46:52 Rx RFSEC Addr: 0xFE Func: Lights_Off_SH624
if m = /^\d{2}\/\d{2}\s+(?:\d{2}:){2}\d{2}\s(Rx|Tx)\s+(RFSEC)\s+Addr:\s+(0x[0-9a-fA-F]+)(\s+)Func:\s+(.+)$/m.exec(lines)
event = {
protocol: m[2].toLowerCase()
direction: m[1].toLowerCase()
housecode: m[3].toLowerCase()
unitcode: "*" + m[4]
state: m[5].toLowerCase()
}
env.logger.debug("Event: " + JSON.stringify(event))
@_lastX10Message = event.housecode + "-" + event.state
@emit "lastX10Message", @_lastX10Message
env.logger.debug("lastX10Message: " + @_lastX10Message)
# Parsing simple on/off (RF-style)
# 11/30 17:57:12 Tx RF HouseUnit: A10 Func: On
# 11/30 17:57:24 Tx RF HouseUnit: A10 Func: Off
if m = /^\d{2}\/\d{2}\s+(?:\d{2}:){2}\d{2}\s(Rx|Tx)\s+(RF|PL)\s+HouseUnit:\s+([a-pA-P])(\d{1,2})\s+Func:\s+(On|Off)/m.exec(lines)
event = {
protocol: m[2].toLowerCase()
direction: m[1].toLowerCase()
housecode: m[3].toLowerCase()
unitcode: parseInt(m[4], 10)
state: m[5].toLowerCase()
}
env.logger.debug("Event: " + JSON.stringify(event))
@_lastX10Message = event.housecode + event.unitcode + "-" + event.state
@emit "lastX10Message", @_lastX10Message
env.logger.debug("lastX10Message: " + @_lastX10Message)
# Parsing simple on/off (PL-style)
# example: 05/30 20:59:20 Tx PL HouseUnit: P1
# example: 05/30 20:59:20 Tx PL House: P Func: On
# example2: 23:42:03.196 [pimatic-mochad] 09/01 23:42:03 Tx PL HouseUnit: P1
# example2: 23:42:03.196 [pimatic-mochad]>
# example2: 23:42:03.198 [pimatic-mochad] 09/01 23:42:03 Tx PL House: P Func: On
else if m = /^\d{2}\/\d{2}\s+(?:\d{2}:){2}\d{2}\s(?:Rx|Tx)\s+(?:RF|PL)\s+HouseUnit:\s+([a-pA-P])(\d{1,2})/m.exec(lines)
@_lastSeen.housecode = m[1].toLowerCase()
@_lastSeen.unitcode = parseInt(m[2], 10)
env.logger.debug("Event: " + JSON.stringify(@_lastSeen))
if @_lastSeen.housecode and @_lastSeen.unitcode and m = /\d{2}\/\d{2}\s+(?:\d{2}:){2}\d{2}\s(Rx|Tx)\s+(RF|PL)\s+House:\s+([a-pA-P])\s+Func:\s+(On|Off)$/m.exec(lines)
event = {
protocol: m[2].toLowerCase()
direction: m[1].toLowerCase()
housecode: m[3].toLowerCase()
unitcode: null # filled later
state: m[4].toLowerCase()
}
if event.housecode == @_lastSeen.housecode
event.unitcode = @_lastSeen.unitcode
env.logger.debug("Event: " + JSON.stringify(event))
@_lastX10Message = event.housecode + event.unitcode + "-" + event.state
@emit "lastX10Message", @_lastX10Message
env.logger.debug("lastX10Message: " + @_lastX10Message)
return true
plugin = new MochadSimplePlugin
return plugin