UNPKG

pimatic-mysensors

Version:
1,591 lines (1,397 loc) 73.1 kB
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 = {