pimatic-mysensors
Version:
MySensors Plugin for pimatic
1,591 lines (1,397 loc) • 73.1 kB
text/coffeescript
module.exports = (env) ->
Promise = env.require 'bluebird'
assert = env.require 'cassert'
_ = env.require 'lodash'
events = env.require 'events'
M = env.matcher
V_TEMP = 0
V_HUM = 1
V_STATUS = 2
V_PERCENTAGE = 3
V_PRESSURE = 4
V_FORECAST = 5
V_RAIN = 6
V_RAINRATE = 7
V_WIND = 8
V_GUST = 9
V_DIRECTION = 10
V_UV = 11
V_WEIGHT = 12
V_DISTANCE = 13
V_IMPEDANCE = 14
V_ARMED = 15
V_TRIPPED = 16
V_WATT = 17
V_KWH = 18
V_SCENE_ON = 19
V_SCENE_OFF = 20
V_HEATER = 21
V_HEATER_SW = 22
V_LIGHT_LEVEL = 23
V_VAR1 = 24
V_VAR2 = 25
V_VAR3 = 26
V_VAR4 = 27
V_VAR5 = 28
V_UP = 29
V_DOWN = 30
V_STOP = 31
V_IR_SEND = 32
V_IR_RECEIVE = 33
V_FLOW = 34
V_VOLUME = 35
V_LOCK_STATUS = 36
V_LEVEL = 37
V_VOLTAGE = 38
V_CURRENT = 39
V_RGB = 40
V_RGBW = 41
V_ID = 42
V_UNIT_PREFIX = 43
V_HVAC_SETPOINT_COOL = 44
V_HVAC_SETPOINT_HEAT = 45
V_HVAC_FLOW_MODE = 46
V_TEXT = 47
V_CUSTOM = 48
V_POSITION = 49
V_IR_RECORD = 50
V_PH = 51
V_ORP = 52
V_EC = 53
ZERO_VALUE = "0"
FIRMWARE_BLOCK_SIZE = 16
BROADCAST_ADDRESS = 255
NODE_SENSOR_ID = 255
C_PRESENTATION = 0
C_SET = 1
C_REQ = 2
C_INTERNAL = 3
C_STREAM = 4
I_BATTERY_LEVEL = 0
I_TIME = 1
I_VERSION = 2
I_ID_REQUEST = 3
I_ID_RESPONSE = 4
I_INCLUSION_MODE = 5
I_CONFIG = 6
I_PING = 7
I_PING_ACK = 8
I_LOG_MESSAGE = 9
I_CHILDREN = 10
I_SKETCH_NAME = 11
I_SKETCH_VERSION = 12
I_REBOOT = 13
I_GATEWAY_READY = 14
I_REQUEST_SIGNING = 15
I_GET_NONCE = 16
I_GET_NONCE_RESPONSE = 17
I_HEARTBEAT = 18
I_PRESENTATION = 19
I_DISCOVER = 20
I_DISCOVER_RESPONSE = 21
I_HEARTBEAT_RESPONSE = 22
I_LOCKED = 23
S_DOOR = 0
S_MOTION = 1
S_SMOKE = 2
S_LIGHT = 3
S_DIMMER = 4
S_COVER = 5
S_TEMP = 6
S_HUM = 7
S_BARO = 8
S_WIND = 9
S_RAIN = 10
S_UV = 11
S_WEIGHT = 12
S_POWER = 13
S_HEATER = 14
S_DISTANCE = 15
S_LIGHT_LEVEL = 16
S_ARDUINO_NODE = 17
S_ARDUINO_REPEATER_NODE = 18
S_LOCK = 19
S_IR = 20
S_WATER = 21
S_AIR_QUALITY = 22
S_CUSTOM = 23
S_DUST = 24
S_SCENE_CONTROLLER = 25
S_RGB_LIGHT = 26
S_RGBW_LIGHT = 27
S_COLOR_SENSOR = 28
S_HVAC = 29
S_MULTIMETER = 30
S_SPRINKLER = 31
S_WATER_LEAK = 32
S_SOUND = 33
S_VIBRATION = 34
S_MOISTURE = 35
S_INFO = 36
S_GAS = 37
S_GPS = 38
S_WATER_QUALITY = 39
ST_FIRMWARE_CONFIG_REQUEST = 0
ST_FIRMWARE_CONFIG_RESPONSE = 1
ST_FIRMWARE_REQUEST = 2
ST_FIRMWARE_RESPONSE = 3
ST_SOUND = 4
ST_IMAGE = 5
P_STRING = 0
P_BYTE = 1
P_INT16 = 2
P_UINT16 = 3
P_LONG32 = 4
P_ULONG32 = 5
P_CUSTOM = 6
P_FLOAT32 = 7
class Board extends events.EventEmitter
constructor: (framework,config) ->
@config = config
@framework = framework
assert @config.time in ["utc", "local"]
assert @config.driver in ["serialport", "ethernet"]
@timeOffset = 0
if @config.time is "local"
@timeOffset = ((new Date()).getTimezoneOffset() * 60 )
env.logger.debug "<- TimeOffset ", @timeOffset
# setup a new driver
switch @config.driver
when "serialport"
SerialPortDriver = require('./serialport')(env)
@driver = new SerialPortDriver(@config.driverOptions)
when "ethernet"
EthernetDriver = require './ethernet'
@driver = new EthernetDriver(@config.driverOptions)
@driver.on('error', (error) =>
env.logger.error error
)
@driver.on('reconnect', (error) =>
@emit('reconnect', error)
)
@driver.on('close', =>
@emit('close')
)
@driver.on("data", (data) =>
@emit "data", data
)
@driver.on("line", (line) =>
@emit "line", line
@_rfReceived(line)
)
connect: (timeout = 2500, retries = 3) ->
return @pendingConnect = @driver.connect(timeout, retries)
disconnect: ->
return @driver.disconnect()
_rfReceived: (data) ->
# decoding message
datas = data.toString().split(";")
sender = parseInt datas[0]
sensor = parseInt datas[1]
command = parseInt datas[2]
ack = parseInt datas[3]
type = parseInt datas[4]
rawpayload = ""
if (datas[5])
rawpayload = datas[5].trim()
switch command
when C_PRESENTATION
if @config.debug
env.logger.debug "<- Presented Node ", datas
@_rfpresent(sender,sensor,type)
when C_SET
@_rfsendtoboard(sender,sensor,type,rawpayload)
when C_REQ
if @config.debug
env.logger.debug "<- request from ", sender, rawpayload
@_rfrequest(sender,sensor,type)
when C_INTERNAL
switch type
when I_BATTERY_LEVEL
if @config.debug
env.logger.debug "<- I_BATTERY_LEVEL ", sender, rawpayload
@_rfsendbatterystat(sender,rawpayload)
when I_TIME
if @config.debug
env.logger.debug "<- I_TIME ", data
@_rfsendTime(sender, sensor)
when I_VERSION
if @config.debug
env.logger.debug "<- I_VERSION ", rawpayload
when I_ID_REQUEST
if @config.debug
env.logger.debug "<- I_ID_REQUEST ", data
@_rfsendNextAvailableSensorId()
when I_ID_RESPONSE
if @config.debug
env.logger.debug "<- I_ID_RESPONSE ", data
when I_INCLUSION_MODE
if @config.debug
env.logger.debug "<- I_INCLUSION_MODE ", data
when I_CONFIG
if @config.debug
env.logger.debug "<- I_CONFIG ", data
@_rfsendConfig(sender)
when I_PING
if @config.debug
env.logger.debug "<- I_PING ", data
when I_PING_ACK
if @config.debug
env.logger.debug "<- I_PING_ACK ", data
when I_LOG_MESSAGE
if @config.debug
env.logger.debug "<- I_LOG_MESSAGE ", data
when I_CHILDREN
if @config.debug
env.logger.debug "<- I_CHILDREN ", data
when I_SKETCH_NAME
#saveSketchName(sender, payload, db);
if @config.debug
env.logger.debug "<- I_SKETCH_NAME ", data
when I_SKETCH_VERSION
#saveSketchVersion(sender, payload, db);
if @config.debug
env.logger.debug "<- I_SKETCH_VERSION ", data
_rfsendTime: (destination,sensor) ->
date = new Date()
payload = Math.floor( ( date.getTime() ) / 1000 ) - @timeOffset
datas =
{
"destination": destination,
"sensor": sensor,
"type" : I_TIME,
"ack" : 0,
"command" : C_INTERNAL,
"value" : payload
}
@_rfWrite( datas)
_rfsendNextAvailableSensorId: ->
newid = false
nextnodeid = @config.startingNodeId
if nextnodeid is null
nextnodeid = 1
else
nextnodeid +=1
while newid is false
newid = not @framework.deviceManager.devicesConfig.some (device, iterator) =>
device.nodeid is nextnodeid
if newid is false
nextnodeid +=1
if newid is true and nextnodeid < 255
datas =
{
"destination": BROADCAST_ADDRESS,
"sensor": NODE_SENSOR_ID,
"type" : I_ID_RESPONSE,
"ack" : 0,
"command" : C_INTERNAL,
"value" : nextnodeid
}
@config.startingNodeId = nextnodeid
@_rfWrite(datas)
@framework.saveConfig()
else
env.logger.error "-> Error assigning next node ID"
_rfrequest: (sender,sensor,type) ->
result = {
"sender": sender,
"sensor": sensor,
"type": type
}
@emit "rfRequest", result
_rfpresent: (sender,sensor,type) ->
result = {
"sender": sender,
"sensor": sensor,
"type" : type
}
@emit "rfPresent", result
_rfsendtoboard: (sender,sensor,type,rawpayload) ->
result = {
"sender": sender,
"sensor": sensor,
"type" : type,
"value" : rawpayload
}
@emit "rfValue", result
_rfsendbatterystat: (sender,rawpayload) ->
result = {
"sender": sender,
"value" : rawpayload
}
@emit "rfbattery", result
_rfsendConfig: (destination) ->
datas = {
"destination": destination,
"sensor": NODE_SENSOR_ID,
"type" : I_CONFIG,
"ack" : 0,
"command" : C_INTERNAL,
"value" : @config.metric
}
@_rfWrite(datas)
_rfWrite: (datas) ->
datas.command ?= C_SET
data = @_rfencode(datas.destination,datas.sensor,datas.command,datas.ack,datas.type,datas.value)
if @config.debug
env.logger.debug "-> Sending ", data
@driver.write(data)
_rfencode: (destination, sensor, command, acknowledge, type, payload) ->
msg = destination.toString(10) + ";" + sensor.toString(10) + ";" + command.toString(10) + ";" + acknowledge.toString(10) + ";" + type.toString(10) + ";";
msg += payload
msg += '\n'
return msg.toString()
Promise.promisifyAll(Board.prototype)
## MySensors class.
class MySensors extends env.plugins.Plugin
init: (app, @framework, @config) =>
@board = new Board(@framework, @config)
@board.connect().then( =>
env.logger.info("Connected to MySensors Gateway.")
)
deviceConfigDef = require("./device-config-schema")
# Discover MySensor nodes
@framework.deviceManager.on('discover', (eventData) =>
@framework.deviceManager.discoverMessage(
'pimatic-mysensors', "Searching for nodes"
)
# Stop searching after configured time
setTimeout(( =>
@board.removeListener("rfPresent", discoverListener)
), eventData.time)
# Received presentation message from the gateway
@board.on("rfPresent", discoverListener = (result) =>
newdevice = true
nodeid = result.sender
sensorid = result.sensor
sensortype = result.type
# Check if device already exists in pimatic
newdevice = not @framework.deviceManager.devicesConfig.some (device, iterator) =>
device.sensorid is sensorid and device.nodeid is nodeid
# Device is a new device and not a battery device
if newdevice
# Temp sensor found
if sensortype is S_TEMP
config = {
class: 'MySensorsDST',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Temp Sensor #{nodeid}.#{sensorid}", config
)
# PIR sensor found
if sensortype is S_MOTION
config = {
class: 'MySensorsPIR',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Motion Sensor #{nodeid}.#{sensorid}", config
)
# Smoke sensor found
if sensortype is S_SMOKE
config = {
class: 'MySensorsPIR',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Smoke Sensor #{nodeid}.#{sensorid}", config
)
# Moisture sensor found
if sensortype is S_MOISTURE
config = {
class: 'MySensorsPIR',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Moisture Sensor #{nodeid}.#{sensorid}", config
)
# Leak sensor found
if sensortype is S_WATER_LEAK
config = {
class: 'MySensorsPIR',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Leak Sensor #{nodeid}.#{sensorid}", config
)
# Contact sensor found
if sensortype is S_DOOR
config = {
class: 'MySensorsButton',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Contact Sensor #{nodeid}.#{sensorid}", config
)
# Shutter found
if sensortype is S_COVER
config = {
class: 'MySensorsShutter',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Shutter #{nodeid}.#{sensorid}", config
)
# Light sensor found
if sensortype is S_LIGHT_LEVEL
config = {
class: 'MySensorsLight',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Light Sensor #{nodeid}.#{sensorid}", config
)
# Lux sensor found
if sensortype is S_LIGHT_LEVEL
config = {
class: 'MySensorsLux',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Lux Sensor #{nodeid}.#{sensorid}", config
)
# kWh sensor found
if sensortype is S_POWER
config = {
class: 'MySensorsPulseMeter',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "kWh Sensor #{nodeid}.#{sensorid}", config
)
# Water sensor found
if sensortype is S_WATER
config = {
class: 'MySensorsWaterMeter',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Water Sensor #{nodeid}.#{sensorid}", config
)
# pH sensor found
if sensortype is S_WATER_QUALITY
config = {
class: 'MySensorsPH',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "pH Sensor #{nodeid}.#{sensorid}", config
)
# Switch found
if sensortype is S_LIGHT or sensortype is S_SPRINKLER
config = {
class: 'MySensorsSwitch',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Switch #{nodeid}.#{sensorid}", config
)
# Dimmer found
if sensortype is S_DIMMER
config = {
class: 'MySensorsDimmer',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Dimmer #{nodeid}.#{sensorid}", config
)
# Distance sensor found
if sensortype is S_DISTANCE
config = {
class: 'MySensorsDistance',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Distance Sensor #{nodeid}.#{sensorid}", config
)
# Gas sensor found
if sensortype is S_AIR_QUALITY
config = {
class: 'MySensorsGas',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "Gas sensor #{nodeid}.#{sensorid}", config
)
# IR sensor found
if sensortype is S_IR
config = {
class: 'MySensorsIR',
nodeid: nodeid,
sensorid: sensorid
}
@framework.deviceManager.discoveredDevice(
'pimatic-mysensors', "IR sensor #{nodeid}.#{sensorid}", config
)
)
)
@framework.ruleManager.addActionProvider(new MySensorsActionProvider @framework,@board, @config)
deviceClasses = [
MySensorsDHT
MySensorsDST
MySensorsBMP
MySensorsPIR
MySensorsSwitch
MySensorsDimmer
MySensorsPulseMeter
MySensorsEnergyMeter
MySensorsWaterMeter
MySensorsPH
MySensorsButton
MySensorsLight
MySensorsLux
MySensorsDistance
MySensorsGas
MySensorsShutter
MySensorsMulti
MySensorsIR
]
for Cl in deviceClasses
do (Cl) =>
@framework.deviceManager.registerDeviceClass(Cl.name, {
configDef: deviceConfigDef[Cl.name]
createCallback: (config,lastState) =>
device = new Cl(config,lastState, @board)
return device
})
# registerDevice for MySensorsBattery device
@framework.deviceManager.registerDeviceClass(MySensorsBattery.name, {
configDef: deviceConfigDef[MySensorsBattery.name]
createCallback: (config,lastState) =>
device = new MySensorsBattery(config,lastState, @board,@framework)
return device
})
class MySensorsDHT extends env.devices.TemperatureSensor
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@_temperatue = lastState?.temperature?.value
@_humidity = lastState?.humidity?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsDHT ", @id, @name
@attributes = {}
@attributes.temperature = {
description: "the measured temperature"
type: "number"
unit: '°C'
acronym: 'T'
}
@attributes.humidity = {
description: "the measured humidity"
type: "number"
unit: '%'
acronym: 'RH'
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid
for sensorid in @config.sensorid
if result.sensor is sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorDHT ", result
if result.type is V_TEMP
#env.logger.debug "temp", result.value
@_temperatue = parseFloat(result.value)
@emit "temperature", @_temperatue
if result.type is V_HUM
#env.logger.debug "humidity", result.value
@_humidity = Math.round(parseFloat(result.value))
@emit "humidity", @_humidity
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getTemperature: -> Promise.resolve @_temperatue
getHumidity: -> Promise.resolve @_humidity
getBattery: -> Promise.resolve @_battery
class MySensorsDST extends env.devices.TemperatureSensor
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@_temperatue = lastState?.temperature?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsDST ", @id, @name
@attributes = {}
@attributes.temperature = {
description: "the measured temperature"
type: "number"
unit: '°C'
acronym: 'T'
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_TEMP and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorDST ", result
@_temperatue = parseFloat(result.value)
@emit "temperature", @_temperatue
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getTemperature: -> Promise.resolve @_temperatue
getBattery: -> Promise.resolve @_battery
class MySensorsBMP extends env.devices.TemperatureSensor
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@_temperatue = lastState?.temperature?.value
@_pressure = lastState?.pressure?.value
@_forecast = lastState?.forecast?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsBMP ", @id, @name
@attributes = {}
@attributes.temperature = {
description: "the measured temperature"
type: "number"
unit: '°C'
acronym: 'T'
}
@attributes.pressure = {
description: "the measured pressure"
type: "number"
unit: 'hPa'
acronym: 'mbar'
}
@attributes.forecast = {
description: "the forecast"
type: "string"
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid
for sensorid in @config.sensorid
if result.sensor is sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorBMP ", result
if result.type is V_TEMP
#env.logger.debug "temp", result.value
@_temperatue = parseInt(result.value)
@emit "temperature", @_temperatue
if result.type is V_PRESSURE
#env.logger.debug "pressure", result.value
@_pressure = parseInt(result.value)
@emit "pressure", @_pressure
if result.type is V_FORECAST
#env.logger.debug "forecast", result.value
@_forecast = result.value
@emit "forecast", @_forecast
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getTemperature: -> Promise.resolve @_temperatue
getPressure: -> Promise.resolve @_pressure
getForecast: -> Promise.resolve @_forecast
getBattery: -> Promise.resolve @_battery
class MySensorsPulseMeter extends env.devices.Device
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@voltage = @config.appliedVoltage
@_watt = lastState?.watt?.value
@_ampere = lastState?.ampere?.value
@_kwh = lastState?.kWh?.value
@_pulsecount = lastState?.pulsecount?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsPulseMeter ", @id, @name
@attributes = {}
@attributes.watt = {
description: "the measured Wattage"
type: "number"
unit: 'W'
acronym: 'Watt'
}
@attributes.pulsecount = {
description: "Measure the Pulse Count"
type: "number"
#unit: ''
hidden: yes
}
@attributes.kWh = {
description: "the measured kWh"
type: "number"
unit: 'kWh'
acronym: 'kWh'
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@attributes.ampere = {
description: "the measured Ampere"
type: "number",
unit: "A"
acronym: 'Ampere'
}
@rfRequestEventHandler = ( (result) =>
if result.sender is @config.nodeid
datas =
{
"destination": @config.nodeid,
"sensor": @config.sensorid,
"type" : V_VAR1,
"value" : @_pulsecount,
"ack" : 1
}
@board._rfWrite(datas)
)
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorsPulseMeter", result
if result.type is V_VAR1
if mySensors.config.debug
env.logger.debug "<- MySensorsPulseMeter V_VAR1"
@_pulsecount = parseInt(result.value)
@emit "pulsecount", @_pulsecount
if result.type is V_WATT
if mySensors.config.debug
env.logger.debug "<- MySensorsPulseMeter V_WATT"
@_watt = parseInt(result.value)
@emit "watt", @_watt
@_ampere = @_watt / @voltage
@emit "ampere", @_ampere
if result.type is V_KWH
if mySensors.config.debug
env.logger.debug "<- MySensorsPulseMeter V_KWH"
@_kwh = parseFloat(result.value)
@emit "kWh", @_kwh
)
@board.on("rfRequest", @rfRequestEventHandler)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfRequest", @rfRequestEventHandler
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getWatt: -> Promise.resolve @_watt
getPulsecount: -> Promise.resolve @_pulsecount
getKWh: -> Promise.resolve @_kwh
getBattery: -> Promise.resolve @_battery
getAmpere: -> Promise.resolve @_ampere
class MySensorsEnergyMeter extends env.devices.Device
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
if mySensors.config.debug
env.logger.debug "MySensorsEnergyMeter ", @id, @name
@kWhlastTime = {}
@attributes = {}
@_watt = {}
for sensorid in @config.sensorid
do(sensorid) =>
attr = "Phase_" + sensorid
@attributes[attr] = {
description: "this measure wattage"
type: "number"
displaySparkline: false
unit: "W"
acronym: attr
}
@kWhlastTime[sensorid] = Date.now()
getter = ( => Promise.resolve @_watt[sensorid] )
@_createGetter( attr, getter)
@_watt[sensorid] = lastState?[attr]?.value
@_kwh = lastState?.kWh?.value or 0
@_totalPower = lastState?.totalPower?.value or 0
@_rate = lastState?.rate?.value or 0
@_battery = lastState?.battery?.value
@attributes.totalPower = {
description: "Total Watt"
type: "number"
unit: 'W'
displaySparkline: false
acronym: 'Total'
}
@attributes.kWh = {
description: "this measure kWh"
type: "number"
unit: 'kWh'
acronym: 'kWh'
}
@attributes.rate = {
description: "Electricity Cost"
type: "number"
unit: ""
displaySparkline: false
acronym: @config.currency
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid
if result.sensor in @config.sensorid and result.type is V_WATT
if mySensors.config.debug
env.logger.debug "<- MySensorsEnergyMeter", result
@calculatePower(result)
)
@_resetWattage = setInterval(( =>
env.logger.debug "<- MySensorsEnergyMeter setinterval"
for sensorid in @config.sensorid
if (new Date()) - @kWhlastTime[sensorid] > @config.resetTime
result = {}
result =
{
sender: @config.nodeid,
sensor: sensorid,
type : V_WATT,
value : 0
}
@calculatePower(result)
), 30000)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
calculatePower:(result) ->
currdate = new Date()
diffTime = currdate - @kWhlastTime[result.sensor]
@_watt[result.sensor] = Math.floor(parseInt(result.value))
calibratedWattage = (@_watt[result.sensor] * (100 - @config.correction)/100)
intKwh = ((calibratedWattage / 1000 ) * ( diffTime ) / 3600000)
@_totalPower = 0
for sensorid in @config.sensorid
@_totalPower += @_watt[sensorid]
@_kwh = @_kwh + intKwh
@kWhlastTime[result.sensor] = currdate
@_rate = @_rate + intKwh * @config.rate
if mySensors.config.debug
env.logger.debug "<- MySensorsEnergyMeter V_KWH", @_kwh , @_rate
@emit "Phase_" + result.sensor, @_watt[result.sensor]
@emit "totalPower" , @_totalPower
@emit "kWh", @_kwh
@emit "rate", @_rate
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
clearInterval(@_resetWattage)
super()
getKWh: -> Promise.resolve @_kwh
getRate: -> Promise.resolve @_rate
getTotalPower: -> Promise.resolve @_totalPower
getBattery: -> Promise.resolve @_battery
class MySensorsWaterMeter extends env.devices.Device
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@_flow = lastState?.flow?.value
@_volume = lastState?.volume?.value
@_pulsecount = lastState?.pulsecount?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsWaterMeter ", @id, @name
@attributes = {}
@attributes.flow = {
description: "the measured water in liter per minute"
type: "number"
unit: 'l/min'
acronym: 'Flow'
}
@attributes.pulsecount = {
description: "Measure the Pulse Count"
type: "number"
#unit: ''
hidden: yes
}
@attributes.volume = {
description: "the measured water in m3"
type: "number"
unit: 'm3'
acronym: 'Total'
}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfRequestEventHandler = ( (result) =>
if result.sender is @config.nodeid
datas =
{
"destination": @config.nodeid,
"sensor": @config.sensorid,
"type" : V_VAR1,
"value" : @_pulsecount,
"ack" : 1
}
@board._rfWrite(datas)
)
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorsWaterMeter", result
if result.type is V_VAR1
if mySensors.config.debug
env.logger.debug "<- MySensorsWaterMeter V_VAR1"
@_pulsecount = parseInt(result.value)
@emit "pulsecount", @_pulsecount
if result.type is V_FLOW
if mySensors.config.debug
env.logger.debug "<- MySensorsWaterMeter V_FLOW"
@_flow = parseInt(result.value)
@emit "flow", @_flow
if result.type is V_VOLUME
if mySensors.config.debug
env.logger.debug "<- MySensorsWaterMeter V_VOLUME"
@_volume = parseFloat(result.value)
@emit "volume", @_volume
)
@board.on("rfRequest", @rfRequestEventHandler)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfRequest", @rfRequestEventHandler
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getFlow: -> Promise.resolve @_flow
getPulsecount: -> Promise.resolve @_pulsecount
getVolume: -> Promise.resolve @_volume
getBattery: -> Promise.resolve @_battery
class MySensorsPH extends env.devices.Device
constructor: (@config,lastState, @board) ->
@id = @config.id
@name = @config.name
@_ph = lastState?.ph?.value
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsPH ", @id, @name
@attributes = {}
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@attributes.ph = {
description: "the measured pH value"
type: "number"
unit: 'pH'
}
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorsPH", result
if result.type is V_PH or result.type is V_VAR1
@_ph = parseFloat(result.value)
@emit "ph", @_ph
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getPh: -> Promise.resolve @_ph
getBattery: -> Promise.resolve @_battery
class MySensorsPIR extends env.devices.PresenceSensor
constructor: (@config, lastState, @board) ->
@id = @config.id
@name = @config.name
@_presence = lastState?.presence?.value or false
@_battery = lastState?.battery?.value
if mySensors.config.debug
env.logger.debug "MySensorsPIR ", @id, @name, @_presence
@addAttribute('battery', {
description: "Battery",
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
})
@['battery'] = ()-> Promise.resolve(@_battery)
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
resetPresence = ( =>
@_setPresence(no)
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_TRIPPED and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorPIR ", result
if result.value is ZERO_VALUE
@_setPresence(no)
else
@_setPresence(yes)
if @config.autoReset is true
clearTimeout(@_resetPresenceTimeout)
@_resetPresenceTimeout = setTimeout(( =>
if @_destroyed then return
@_setPresence(no)
), @config.resetTime)
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
clearTimeout(@_resetPresenceTimeout)
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getPresence: -> Promise.resolve @_presence
getBattery: -> Promise.resolve @_battery
class MySensorsButton extends env.devices.ContactSensor
constructor: (@config,lastState,@board) ->
@id = @config.id
@name = @config.name
@_contact = lastState?.contact?.value or false
if mySensors.config.debug
env.logger.debug "MySensorsButton", @id, @name, @_contact
@attributes = _.cloneDeep @attributes
@attributes.battery = {
description: "Display the battery level of sensor"
type: "number"
displaySparkline: false
unit: "%"
icon:
noText: true
mapping: {
'icon-battery-empty': 0
'icon-battery-fuel-1': [0, 20]
'icon-battery-fuel-2': [20, 40]
'icon-battery-fuel-3': [40, 60]
'icon-battery-fuel-4': [60, 80]
'icon-battery-fuel-5': [80, 100]
'icon-battery-filled': 100
}
hidden: !@config.batterySensor
}
@rfbatteryEventHandler = ( (result) =>
if result.sender is @config.nodeid
unless result.value is null or undefined
# When the battery is to low, battery percentages higher then 100 could be send
if result.value > 100
result.value = 0
@_battery = parseInt(result.value)
@emit "battery", @_battery
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is ( V_TRIPPED or V_STATUS ) and result.sensor is @config.sensorid
if mySensors.config.debug
env.logger.debug "<- MySensorsButton ", result
if result.value is ZERO_VALUE
@_setContact(yes)
else
@_setContact(no)
)
@board.on("rfbattery", @rfbatteryEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
destroy: ->
@board.removeListener "rfbattery", @rfbatteryEventHandler
@board.removeListener "rfValue", @rfValueEventHandler
super()
getBattery: -> Promise.resolve @_battery
class MySensorsSwitch extends env.devices.PowerSwitch
constructor: (@config,lastState,@board) ->
@id = @config.id
@name = @config.name
@_state = lastState?.state?.value
if mySensors.config.debug
env.logger.debug "MySensorsSwitch ", @id, @name, @_state
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_STATUS and result.sensor is @config.sensorid
state = (if parseInt(result.value) is 1 then on else off)
if mySensors.config.debug
env.logger.debug "<- MySensorSwitch ", result
@_setState(state)
)
@rfRequestEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_STATUS
datas =
{
"destination": @config.nodeid,
"sensor": @config.sensorid,
"type" : V_STATUS,
"value" : @_state,
"ack" : 1
}
@board._rfWrite(datas)
)
@board.on("rfValue", @rfValueEventHandler)
@board.on("rfRequest", @rfRequestEventHandler)
super()
changeStateTo: (state) ->
assert state is on or state is off
if state is true then _state = 1 else _state = 0
datas =
{
"destination": @config.nodeid,
"sensor": @config.sensorid,
"type" : V_STATUS,
"value" : _state,
"ack" : 1
}
@board._rfWrite(datas).then ( () =>
@_setState(state)
)
destroy: ->
@board.removeListener "rfValue", @rfValueEventHandler
@board.removeListener "rfRequest", @rfRequestEventHandler
super()
class MySensorsDimmer extends env.devices.DimmerActuator
_lastdimlevel: null
constructor: (@config, lastState, @board) ->
@id = @config.id
@name = @config.name
@_dimlevel = lastState?.dimlevel?.value or 0
@_lastdimlevel = lastState?.lastdimlevel?.value or 100
@_state = lastState?.state?.value or off
@rfRequestEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_PERCENTAGE
datas =
{
"destination": @config.nodeid,
"sensor": @config.sensorid,
"type" : V_PERCENTAGE,
"value" : @_dimlevel,
"ack" : 1
}
@board._rfWrite(datas)
)
@rfValueEventHandler = ( (result) =>
if result.sender is @config.nodeid and result.type is V_PERCENTAGE and result.sensor is @config.sensorid
state = (if parseInt(result.value) is 0 then off else on)
dimlevel = Math.min(Math.max(result.value, 0), 100)
if mySensors.config.debug
env.logger.debug "<- MySensorDimmer ", result
@_setState(state)
@_setDimlevel(dimlevel)
)
@board.on("rfRequest", @rfRequestEventHandler)
@board.on("rfValue", @rfValueEventHandler)
super()
turnOn: -> @changeDimlevelTo(@_lastdimlevel)
changeDimlevelTo: (level) ->
unless @config.forceSend
if @_dimlevel is level then return Promise.resolve true
if level is 0
state = false
unless @_dimlevel is 0
@_lastdimlevel = @_dimlevel
datas =
{