pimatic-sensibo
Version:
Pimatic Plugin for Sensibo
333 lines (297 loc) • 12.4 kB
text/coffeescript
# #Sensibo plugin
module.exports = (env) ->
Promise = env.require 'bluebird'
types = env.require('decl-api').types
_ = env.require 'lodash'
commons = require('pimatic-plugin-commons')(env)
rest = require('restler-promise')(Promise)
url = require 'url'
deviceConfigTemplates =
"SensiboControl":
name: "Sensibo"
class: "SensiboControl"
"SensiboFanControl":
name: "Sensibo"
class: "SensiboFanControl"
"SensiboSensor":
name: "Sensibo"
class: "SensiboSensor"
# ###SensiboPlugin class
class SensiboPlugin extends env.plugins.Plugin
# ####init()
# The `init` function is called by the framework to ask your plugin to initialise.
#
# #####params:
# * `app` is the [express] instance the framework is using.
# * `framework` the framework itself
# * `config` the properties the user specified as config for your plugin in the `plugins`
# section of the config.json file
#
#
init: (app, , ) =>
# register devices
deviceConfigDef = require("./device-config-schema")
= commons.base @, 'SensiboPlugin'
= .baseUrl
= .apiKey
= {
timeout: 1000 * .normalize .timeout ? .__proto__.timeout, 5, 86400
}
.deviceManager.registerDeviceClass("SensiboSensor", {
configDef: deviceConfigDef.SensiboSensor,
createCallback: (config, lastState) =>
return new SensiboSensor config, @, lastState
})
.deviceManager.registerDeviceClass("SensiboControl", {
configDef: deviceConfigDef.SensiboControl,
createCallback: (config, lastState) =>
return new SensiboControl config, @, lastState
})
.deviceManager.registerDeviceClass("SensiboFanControl", {
configDef: deviceConfigDef.SensiboFanControl,
createCallback: (config, lastState) =>
return new SensiboFanControl config, @, lastState
})
.deviceManager.on('discover', (eventData) =>
.deviceManager.discoverMessage 'pimatic-sensibo', 'Searching for devices'
.then( (podIds) =>
id=null
for podUid in podIds
for own templateName of deviceConfigTemplates
configTemplate = deviceConfigTemplates[templateName]
id = .generateDeviceId , "sensibo", id
if id?
config = _.cloneDeep
class: configTemplate.class
id: id
name: "#{configTemplate.name} #{id.substr(1 + id.indexOf '-')}"
podUid: podUid
.deviceManager.discoveredDevice(
'pimatic-sensibo', "#{config.name}", config
)
).catch (errorMessage) =>
.deviceManager.discoverMessage 'pimatic-sensibo', errorMessage
)
getPods: () ->
urlObject = url.parse , false, true
urlObject.pathname = urlObject.pathname, "users/me/pods"
urlObject.query =
apiKey: "#{@apiKey}"
.info url.format(urlObject).replace(/apiKey=[^&]+/i, "apiKey=XXX");
rest.get(url.format(urlObject), ).then((result) =>
.info "response:", result.data
data = result.data
if data.status is 'success' and _.isArray data.result
podIds = []
for pod in data.result
podIds.push pod.id || pod.name
return Promise.resolve podIds
).catch (errorResult) =>
.rejectWithErrorString Promise.reject, errorResult.error ? errorResult, "Unable to query pods"
addToUrlPath: (baseUrlString, path) ->
return baseUrlString.replace(/\/$/,"") + '/' + path.replace(/^\//,"")
class SensiboSensor extends env.devices.TemperatureSensor
attributes:
temperature:
description: "Temperature"
type: types.number
unit: '°C'
acronym: 'T'
humidity:
description: "Relative humidity"
type: types.number
unit: '%'
acronym: 'RH'
constructor: (, , ) ->
= .config.debug ? false
= commons.base @, .class unless ?
.debug "Device Initialization"
= .id
= .name
=
.info .replace(/apiKey=[^&]+/i, "apiKey=XXX");
= .options
intervalSeconds = (.interval or (.config.interval ? .config.__proto__.interval))
= 1000 * .normalize intervalSeconds, 10, 86400
= lastState?.temperature?.value or null
= lastState?.humidity?.value or null
super()
destroy: () ->
.cancelUpdate()
super()
_setHumidity: (value) ->
= value
'humidity', value
_createServiceUrl: ->
urlObject = url.parse .baseUrl, false, true
urlObject.pathname = .addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/measurements"
urlObject.query =
apiKey: "#{@plugin.apiKey}"
fields: "temperature,humidity"
return url.format urlObject
_requestUpdate: ->
rest.get(, ).then((result) =>
.info "response:", result.data
data = result.data
if data.status is 'success' and _.isArray data.result
data.result[0].humidity
data.result[0].temperature
).catch((errorResult) =>
.error "Unable to get status values of device: ", errorResult.error ? errorResult
).finally () =>
.scheduleUpdate ,
getHumidity: -> Promise.resolve
class SensiboFanControl extends env.devices.ButtonsDevice
constructor: (, , lastState) ->
= .id
= .name
= commons.base @, .class
for b in .buttons
b.text = b.id unless b.text?
=
=
= .options
super
destroy: () ->
super()
_createServiceUrl: (queryParams) ->
urlObject = url.parse .baseUrl, false, true
urlObject.pathname = .addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/acStates"
urlObject.query =
apiKey: "#{@plugin.apiKey}"
if queryParams?
for own k, v of queryParams
urlObject.query[k] = v
return url.format urlObject
_getValues: ->
new Promise (resolve, reject) =>
rest.get(, ).then((result) =>
.info "response:", result.data
data = result.data
if data.status is 'success' and _.isArray data.result
.info "data:", data.result
acState = data.result[0].acState
resolve(acState)
else
reject new Error "Invalid response status: #{data.status}"
).catch (errorResult) =>
reject errorResult.error ? errorResult
buttonPressed: (buttonId) ->
for b in .buttons
if b.id is buttonId
return .then (acState) =>
acState.fanLevel = b.id
data =
acState: acState
rest.postJson(, data, ).then((result) =>
.debug "post response:", result.data
= b.id
'button', b.id
Promise.resolve()
).catch((errorResult) =>
.error "Unable to change status values of device: " + errorResult.error ? errorResult, errorResult.response || ''
Promise.reject(errorResult.error ? errorResult)
)
throw new Error("No button with the id #{buttonId} found")
class SensiboControl extends env.devices.PowerSwitch
attributes:
state:
description: "Current State"
type: types.boolean
labels: ['on', 'off']
targetTemperature:
description: "Target Temperature"
type: types.number
unit: '°C'
acronym: 'Tt'
fanLevel:
description: "The current fan level"
type: types.string
acronym: "fan"
mode:
description: "The current mode of operation"
type: types.string
acronym: "mode"
# Initialize device by reading entity definition from middleware
constructor: (, , ) ->
= .config.debug ? false
= commons.base @, .class unless ?
.debug "Device Initialization"
= .id
= .name
=
=
.debug .replace(/apiKey=[^&]+/i, "apiKey=XXX");
= .options
intervalSeconds = (.interval or (.config.interval ? .config.__proto__.interval))
= 1000 * .normalize intervalSeconds, 10, 86400
= lastState?.fanLevel?.value or 'low'
= lastState?.mode?.value or 'fan'
= lastState?.targetTemperature?.value or 20.0
super()
destroy: () ->
.cancelUpdate()
super()
_createServiceUrl: (queryParams) ->
urlObject = url.parse .baseUrl, false, true
urlObject.pathname = .addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/acStates"
urlObject.query =
apiKey: "#{@plugin.apiKey}"
if queryParams?
for own k, v of queryParams
urlObject.query[k] = v
return url.format urlObject
_getValues: ->
new Promise (resolve, reject) =>
rest.get(, ).then((result) =>
.debug "response:", result.data
data = result.data
if data.status is 'success' and _.isArray data.result
.debug "data:", data.result
acState = data.result[0].acState
.setAttribute "fanLevel", acState.fanLevel
.setAttribute "mode", acState.mode
.setAttribute "targetTemperature", acState.targetTemperature
acState.on
resolve()
else
reject new Error "Invalid response status: #{data.status}"
).catch (errorResult) =>
reject errorResult.error ? errorResult
_requestUpdate: ->
.catch((error) =>
.error "Unable to get status values of device: " + error
).finally () =>
.scheduleUpdate ,
getFanLevel: -> Promise.resolve
getMode: -> Promise.resolve
getTargetTemperature: -> Promise.resolve
changeStateTo: (newState) ->
.then =>
data =
acState:
on: newState
mode:
fanLevel:
targetTemperature:
rest.postJson(, data, ).then((result) =>
.debug "post response:", result.data
newState
Promise.resolve()
).catch((errorResult) =>
.error "Unable to change status values of device: " + errorResult.error ? errorResult, errorResult.response || ''
Promise.reject(errorResult.error ? errorResult)
)
# ###Finally
# Create a instance of my plugin
myPlugin = new SensiboPlugin()
# and return it to the framework.
return myPlugin