class ContactSensor extends Sensor
_contact: undefined
attributes:
contact:
description: "state of the contact"
type: t.boolean
labels: ['closed', 'opened']
template: "contact"
_setContact: (value) ->
if @_contact is value then return
@_contact = value
@emit 'contact', value
getContact: -> Promise.resolve(@_contact)
upperCaseFirst = (string) ->
unless string.length is 0
string[0].toUpperCase() + string.slice(1)
else ""
class ButtonsDevice extends Device
attributes:
button:
description: "The last pressed button"
type: t.string
actions:
buttonPressed:
params:
buttonId:
type: t.string
description: "Press a button"
template: "buttons"
_lastPressedButton: null
constructor: (@config)->
@id = config.id
@name = config.name
super()
getButton: -> Promise.resolve(@_lastPressedButton)
buttonPressed: (buttonId) ->
for b in @config.buttons
if b.id is buttonId
@_lastPressedButton = b.id
@emit 'button', b.id
return
throw new Error("No button with the id #{buttonId} found")
class VariablesDevice extends Device
constructor: (@config, @framework) ->
@id = config.id
@name = config.name
@_vars = @framework.variableManager
@_exprChangeListeners = []
@attributes = {}
for variable in @config.variables
do (variable) =>
name = variable.name
info = @_vars.parseVariableExpression(variable.expression)
@attributes[name] = {
description: name
label: "$#{name}"
type: (
switch info.datatype
when "string" then t.string
when "numeric" then t.number
else assert false
)
}
evaluate = ( =>
(
switch info.datatype
when "numeric" then @_vars.evaluateNumericExpression(info.tokens)
when "string" then @_vars.evaluateStringExpression(info.tokens)
else assert false
).then( (val) =>
if val isnt @_attributesMeta[name].value
@emit name, val
return val
)
)
@_createGetter(name, evaluate)
@_vars.notifyOnChange(info.tokens, evaluate)
@_exprChangeListeners.push evaluate
super()
destroy: ->
@_vars.cancelNotifyOnChange(cl) for cl in @_exprChangeListeners
super()
class DeviceManager extends events.EventEmitter
devices: {}
deviceClasses: {}
constructor: (@framework, @devicesConfig) ->
registerDeviceClass: (className, {configDef, createCallback, prepareConfig}) ->
assert typeof className is "string"
assert typeof configDef is "object"
assert typeof createCallback is "function"
assert(if prepareConfig? then typeof prepareConfig is "function" else true)
assert typeof configDef.properties is "object"
configDef.properties.id = {
description: "the id for the device"
type: "string"
}
configDef.properties.name = {
description: "the name for the device"
type: "string"
}
configDef.properties.class = {
description: "the class to use for the device"
type: "string"
}
@deviceClasses[className] = {
prepareConfig
configDef
createCallback
}
updateDeviceOrder: (deviceOrder) ->
assert deviceOrder? and Array.isArray deviceOrder
@framework.config.devices = @devicesConfig = _.sortBy(@devicesConfig, (device) =>
index = deviceOrder.indexOf device.id
return if index is -1 then 99999 else index # push it to the end if not found
)
@framework.saveConfig()
@framework._emitDeviceOrderChanged(deviceOrder)
return deviceOrder
registerDevice: (device) ->
assert device?
assert device instanceof env.devices.Device
assert device._constructorCalled
if @devices[device.id]?
throw new assert.AssertionError("dublicate device id \"#{device.id}\"")
unless device.id.match /^[a-z0-9\-_]+$/i
env.logger.warn """
The id of #{device.id} contains a non alphanumeric letter or symbol.
This could lead to errors.
"""
for reservedWord in ["and", "or", "then"]
if device.name.indexOf(" and ") isnt -1
env.logger.warn """
Name of device "#{device.id}" contains an "#{reservedWord}".
This could lead to errors in rules.
"""
env.logger.info "new device \"#{device.name}\"..."
@devices[device.id]=device
for attrName, attr of device.attributes
do (attrName, attr) =>
device.on(attrName, onChange = (value) =>
@framework._emitDeviceAttributeEvent(device, attrName, attr, new Date(), value)
)
device.afterRegister()
@framework._emitDeviceAdded(device)
return device
_loadDevice: (deviceConfig) ->
classInfo = @deviceClasses[deviceConfig.class]
unless classInfo?
throw new Error("Unknown device class \"#{deviceConfig.class}\"")
warnings = []
classInfo.prepareConfig(deviceConfig) if classInfo.prepareConfig?
@framework._validateConfig(
deviceConfig,
classInfo.configDef,
"config of device #{deviceConfig.id}"
)
declapi.checkConfig(classInfo.configDef.properties, deviceConfig, warnings)
for w in warnings
env.logger.warn("Device configuration of #{deviceConfig.id}: #{w}")
deviceConfig = declapi.enhanceJsonSchemaWithDefaults(classInfo.configDef, deviceConfig)
device = classInfo.createCallback(deviceConfig)
assert deviceConfig is device.config
return @registerDevice(device)
loadDevices: ->
for deviceConfig in @devicesConfig
classInfo = @deviceClasses[deviceConfig.class]
if classInfo?
try
@_loadDevice(deviceConfig)
catch e
env.logger.error("Error loading device #{deviceConfig.id}: #{e.message}")
env.logger.debug(e.stack)
else
env.logger.warn(
"no plugin found for device \"#{deviceConfig.id}\" of class \"#{deviceConfig.class}\"!"
)
return
getDeviceById: (id) -> @devices[id]
getDevices: -> (device for id, device of @devices)
getDeviceClasses: -> (className for className of @deviceClasses)
getDeviceConfigSchema: (className)-> @deviceClasses[className]?.configDef
addDeviceByConfig: (deviceConfig) ->
assert deviceConfig.id?
assert deviceConfig.class?
if @isDeviceInConfig(deviceConfig.id)
throw new Error(
"A device with the id \"#{deviceConfig.id}\" is already in the config."
)
device = @_loadDevice(deviceConfig)
@addDeviceToConfig(deviceConfig)
return device
updateDeviceByConfig: (deviceConfig) ->
throw new Error("The Operation isn't supported yet.")
removeDevice: (deviceId) ->
device = @getDeviceById(deviceId)
unless device? then return
@framework._emitDeviceRemoved(device)
device.emit 'remove'
_.remove(@devicesConfig, {deviceId: deviceId})
@emit 'deviceRemoved'
@framework.saveConfig()
device.destroy()
return device
addDeviceToConfig: (deviceConfig) ->
assert deviceConfig.id?
assert deviceConfig.class?
Devices