extensions/generic/generic.lua
Rama master · réplica de lectura del repositorio original.
local extension = ...
local logger = extension:getLogger()
local File = require('jls.io.File')
local json = require('jls.util.json')
local Map = require('jls.util.Map')
local strings = require('jls.util.strings')
local tables = require('jls.util.tables')
local Thing = require('lha.Thing')
local function isEmpty(value)
return value == nil or value == '' or (type(value) == 'table' and next(value) == nil)
end
local function firstOf(...)
local l = select('#', ...)
if l > 0 then
local values = {...}
for i = 1, l do
local value = values[i]
if not isEmpty(value) then
return value
end
end
end
end
local METADATA_KEYS = {'type', '@type', 'unit', 'title', 'description', 'enum', 'minimum', 'maximum', 'readOnly', 'writeOnly', 'configuration'}
local METADATA_DEFAUT_VALUES = {
readOnly = false,
writeOnly = false,
configuration = false,
}
local PROPERTY_TYPE_BY_TYPE = {
boolean_RO = 'BooleanProperty',
boolean = 'OnOffProperty',
number = 'LevelProperty',
string = 'StringProperty',
}
local CAPABILITY_BY_TYPE = {
boolean_RO = 'BinarySensor',
boolean = 'OnOffSwitch',
number_RO = 'MultiLevelSensor',
number = 'MultiLevelSwitch',
}
local function createThing(thingConfig)
local typeSet = Map:new()
typeSet:add('GenericThing')
local thing = Thing:new(firstOf(thingConfig.title, 'Generic Thing'), firstOf(thingConfig.description, 'Generic Thing'))
for _, propertyConfig in ipairs(thingConfig.properties) do
local name = firstOf(propertyConfig.name, 'value')
local primitiveType = tostring(propertyConfig.type)
-- guess the semantic type
local adaptedType = primitiveType
if propertyConfig.type == 'integer' then
adaptedType = 'number'
end
if propertyConfig.readOnly then
adaptedType = adaptedType..'_RO'
end
local semanticType = PROPERTY_TYPE_BY_TYPE[adaptedType] or PROPERTY_TYPE_BY_TYPE[primitiveType] or 'GenericProperty'
-- filter property metadata values
local metadata = {}
for _, key in ipairs(METADATA_KEYS) do
local value = propertyConfig[key]
if not isEmpty(value) and value ~= METADATA_DEFAUT_VALUES[key] then
metadata[key] = value
end
end
thing:addProperty(name, Map.assign({
title = name,
['@type'] = semanticType,
description = semanticType
}, metadata))
-- guess thing type based on property type
local capability = CAPABILITY_BY_TYPE[adaptedType] or CAPABILITY_BY_TYPE[primitiveType]
if capability then
typeSet:add(capability)
end
end
local tType = thingConfig['@type']
if isEmpty(tType) then
tType = typeSet:skeys()
end
thing.type = tType
return thing
end
local thingTable
local valuesFile
local function saveValues()
if thingTable and valuesFile then
local data = json.stringify(thingTable, 2)
valuesFile:write(data)
end
end
local function setThingPropertyValue(thing, name, value)
local property = thing:getProperty(name)
if property and value ~= nil then
if property:isWritable() then
local path = thing.thingId..'/'..name
local prev = tables.setPath(thingTable, path, value)
if value ~= prev then
extension:putTimer('save', saveValues, 1000)
end
thing:updatePropertyValue(name, value)
end
end
end
local function createThings(things, thingsByKey)
if type(things) == 'table' then
for _, thingConfig in ipairs(things) do
if not isEmpty(thingConfig.properties) then
if thingConfig.id == '' or type(thingConfig.id) ~= 'string' then
local configuration = extension:getConfiguration()
local lastId = configuration.lastId
if math.type(lastId) == 'integer' then
lastId = lastId + 1
else
lastId = 1
end
configuration.lastId = lastId
thingConfig.id = strings.formatInteger(lastId, 64)
end
local thing = thingsByKey[thingConfig.id]
if thing then
if thingConfig.save and thing.thingId then
local values = thingTable[thing.thingId]
if values then
for name, value in pairs(values) do
local property = thing:getProperty(name)
if property then
property:setValue(value)
end
end
end
thing.setPropertyValue = setThingPropertyValue
end
else
local discoveredThing = createThing(thingConfig)
if discoveredThing then
if logger:isLoggable(logger.FINEST) then
logger:finest('discovered thing "%s": %T', thingConfig.id, discoveredThing:asThingDescription())
end
extension:discoverThing(thingConfig.id, discoveredThing)
end
end
end
end
end
end
extension:subscribeEvent('startup', function()
thingTable = {}
local engine = extension:getEngine()
valuesFile = File:new(engine:getWorkDirectory(), 'generic-things.json')
if valuesFile:isFile() then
local status, t = pcall(json.decode, valuesFile:readAll())
if status then
thingTable = t
else
logger:warn('Fail to load generic thing values, %s', t)
end
end
end)
extension:subscribeEvent('things', function()
logger:info('Looking for generic things')
local configuration = extension:getConfiguration()
extension:cleanDiscoveredThings()
local thingsByKey = extension:getThingsByDiscoveryKey()
createThings(configuration.basicThings, thingsByKey)
createThings(configuration.things, thingsByKey)
createThings(configuration.list, thingsByKey)
end)