diff --git a/Salus.lua b/Salus.lua index 32121b1..2c9ebad 100644 --- a/Salus.lua +++ b/Salus.lua @@ -9,6 +9,7 @@ function Salus:new(config) self.pass = config:getPassword() self.device_id = config:getDeviceID() self.token = Globals:get('salus_token', '') + self.token_time = tonumber(Globals:get('salus_token_time', 0)) self.http = HTTPClient:new({ baseUrl = 'https://eu.salusconnect.io' }) @@ -358,7 +359,7 @@ function Salus:auth(callback) Salus:setToken('') end local success = function(response) - QuickApp:debug(json.encode(response)) + -- QuickApp:debug(json.encode(response)) if response.status > 299 then fail(response) return @@ -384,16 +385,29 @@ end function Salus:setToken(token) self.token = token + self.token_time = os.time(os.date("!*t")) Globals:set('salus_token', token) + Globals:set('salus_token_time', self.token_time) end function Salus:getToken() + if not self:checkTokenTime() then + self:setToken('') + return '' + end if string.len(self.token) > 10 then return self.token elseif string.len(Globals:get('salus_token', '')) > 10 then return Globals:get('salus_token', '') end - return nil + return '' +end + +function Salus:checkTokenTime() + if self.token_time < 1 then + self.token_time = tonumber(Globals:get('salus_token_time', 0)) + end + return self.token_time > 0 and os.time(os.date("!*t")) - self.token_time < 43200 end function Salus:translateBatteryLevel(batteryLevel) diff --git a/Salus_It600.fqa b/Salus_It600.fqa index 671d45b..6c3bced 100644 --- a/Salus_It600.fqa +++ b/Salus_It600.fqa @@ -126,7 +126,7 @@ "name": "main", "isMain": true, "isOpen": true, - "content": "--[[\nSalus IT600 thermostats integration v 1.1.0\n@author ikubicki\n]]\n\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.salus = Salus:new(self.config)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self:trace('')\n self:trace(string.format(self.i18n:get('name'), self.name))\n self:updateProperty('manufacturer', 'Salus')\n self:updateProperty('model', 'IT600')\n self.childrenIds = {}\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n\n self:updateProperty(\"supportedThermostatModes\", {\"Off\", \"Heat\", \"Auto\"})\n self:updateProperty(\"heatingThermostatSetpointCapabilitiesMax\", 35)\n self:updateProperty(\"heatingThermostatSetpointCapabilitiesMin\", 10)\n\n self:updateView(\"label1\", \"text\", string.format(self.i18n:get('name'), self.name))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n\n self:initChildDevices({\n [\"com.fibaro.temperatureSensor\"] = SalusChildDevice,\n [\"com.fibaro.humiditySensor\"] = SalusChildDevice,\n [\"com.fibaro.binarySwitch\"] = SalusChildDevice,\n })\n for id, device in pairs(self.childDevices) do\n self.childrenIds[device.type] = id\n end\n\n if string.len(self.config:getDeviceID()) > 10 then\n if self.childrenIds[\"com.fibaro.temperatureSensor\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Temperature',\n type = \"com.fibaro.temperatureSensor\",\n }, SalusChildDevice)\n end\n if self.childrenIds[\"com.fibaro.humiditySensor\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Humidity',\n type = \"com.fibaro.humiditySensor\",\n }, SalusChildDevice)\n end\n if self.childrenIds[\"com.fibaro.binarySwitch\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Heating',\n type = \"com.fibaro.binarySwitch\",\n deviceRole = 'Valve',\n isLight = false,\n }, SalusChildDevice)\n end\n self:run()\n else \n self:updateView(\"label1\", \"text\", self.i18n:get('not-configured'))\n end\nend\n\nfunction QuickApp:setThermostatMode(mode)\n self:updateProperty(\"thermostatMode\", mode)\n local holdtype = 0\n if mode == 'Off' then\n holdtype = 7\n elseif mode == 'Heat' then\n holdtype = 2\n end\n local setHoldtypeCallback = function(response)\n self:pullDataFromCloud()\n end\n self.salus:setHoldtype(holdtype, setHoldtypeCallback)\nend\n\nfunction QuickApp:setHeatingThermostatSetpoint(value) \n self:updateProperty(\"heatingThermostatSetpoint\", value)\n local setHeatingSetpointCallback = function(response)\n self:pullDataFromCloud()\n end\n self.salus:setHeatingSetpoint(value, setHeatingSetpointCallback)\nend\n\nfunction QuickApp:refreshEvent(event)\n self:updateView(\"label\", \"text\", self.i18n:get('refreshing'))\n self:pullDataFromCloud()\nend\n\nfunction QuickApp:run()\n self:pullDataFromCloud()\n local interval = self.config:getInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullDataFromCloud()\n local getPropertiesCallback = function(properties)\n -- QuickApp:debug(json.encode(properties))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n if self.childrenIds[\"com.fibaro.temperatureSensor\"] ~= nil then\n self.childDevices[self.childrenIds[\"com.fibaro.temperatureSensor\"]]:setValue(properties.temperature)\n end\n if self.childrenIds[\"com.fibaro.humiditySensor\"] ~= nil then\n self.childDevices[self.childrenIds[\"com.fibaro.humiditySensor\"]]:setValue(properties.humidity)\n end\n if self.childrenIds[\"com.fibaro.binarySwitch\"] ~= nil then\n local isRunningValue = 0\n if properties.running and properties.running > 0 then\n isRunningValue = 1\n end\n self.childDevices[self.childrenIds[\"com.fibaro.binarySwitch\"]]:setValue(isRunningValue > 0)\n end\n local mode = 'Auto' -- 0 or 1\n if properties.holdtype == 2 then\n mode = 'Heat'\n elseif properties.holdtype == 7 then\n mode = 'Off'\n end\n self:updateProperty(\"thermostatMode\", mode)\n self:updateProperty(\"heatingThermostatSetpoint\", properties.heatingSetpoint)\n self:updateView(\"label1\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n \n if properties.battery ~= nil then\n self:updateProperty(\"batteryLevel\", Salus:translateBatteryLevel(properties.battery))\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\n \"quickApp\", \"battery\", \"heatingThermostatSetpoint\", \"thermostatMode\"\n }})\n end\n else\n\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\n \"quickApp\", \"power\", \"heatingThermostatSetpoint\", \"thermostatMode\"\n }})\n end\n end\n end\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n self.salus:getProperties(getPropertiesCallback)\nend\n\nfunction QuickApp:searchEvent(param)\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(gateways)\n -- QuickApp:debug(json.encode(gateways))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n -- printing results\n for _, gateway in pairs(gateways) do\n QuickApp:trace(string.format(self.i18n:get('search-row-gateway'), gateway.name, gateway.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-gateway-devices'), #gateway.devices))\n for __, device in ipairs(gateway.devices) do\n QuickApp:trace(string.format(self.i18n:get('search-row-device'), device.name, device.id, device.model))\n end\n end\n self:updateView(\"label2\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n self.salus:searchDevices(searchDevicesCallback)\nend\n" + "content": "--[[\nSalus IT600 thermostats integration v 1.1.1\n@author ikubicki\n]]\n\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.salus = Salus:new(self.config)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self:trace('')\n self:trace(string.format(self.i18n:get('name'), self.name))\n self:updateProperty('manufacturer', 'Salus')\n self:updateProperty('model', 'IT600')\n self.childrenIds = {}\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n\n self:updateProperty(\"supportedThermostatModes\", {\"Off\", \"Heat\", \"Auto\"})\n self:updateProperty(\"heatingThermostatSetpointCapabilitiesMax\", 35)\n self:updateProperty(\"heatingThermostatSetpointCapabilitiesMin\", 10)\n\n self:updateView(\"label1\", \"text\", string.format(self.i18n:get('name'), self.name))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n\n self:initChildDevices({\n [\"com.fibaro.temperatureSensor\"] = SalusChildDevice,\n [\"com.fibaro.humiditySensor\"] = SalusChildDevice,\n [\"com.fibaro.binarySwitch\"] = SalusChildDevice,\n })\n for id, device in pairs(self.childDevices) do\n self.childrenIds[device.type] = id\n end\n\n if string.len(self.config:getDeviceID()) > 10 then\n if self.childrenIds[\"com.fibaro.temperatureSensor\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Temperature',\n type = \"com.fibaro.temperatureSensor\",\n }, SalusChildDevice)\n end\n if self.childrenIds[\"com.fibaro.humiditySensor\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Humidity',\n type = \"com.fibaro.humiditySensor\",\n }, SalusChildDevice)\n end\n if self.childrenIds[\"com.fibaro.binarySwitch\"] == nil then\n local child = self:createChildDevice({\n name = self.name .. ' Heating',\n type = \"com.fibaro.binarySwitch\",\n deviceRole = 'Valve',\n isLight = false,\n }, SalusChildDevice)\n end\n self:run()\n else \n self:updateView(\"label1\", \"text\", self.i18n:get('not-configured'))\n end\nend\n\nfunction QuickApp:setThermostatMode(mode)\n self:updateProperty(\"thermostatMode\", mode)\n local holdtype = 0\n if mode == 'Off' then\n holdtype = 7\n elseif mode == 'Heat' then\n holdtype = 2\n end\n local setHoldtypeCallback = function(response)\n self:pullDataFromCloud()\n end\n self.salus:setHoldtype(holdtype, setHoldtypeCallback)\nend\n\nfunction QuickApp:setHeatingThermostatSetpoint(value) \n self:updateProperty(\"heatingThermostatSetpoint\", value)\n local setHeatingSetpointCallback = function(response)\n self:pullDataFromCloud()\n end\n self.salus:setHeatingSetpoint(value, setHeatingSetpointCallback)\nend\n\nfunction QuickApp:refreshEvent(event)\n self:updateView(\"label\", \"text\", self.i18n:get('refreshing'))\n self:pullDataFromCloud()\nend\n\nfunction QuickApp:run()\n self:pullDataFromCloud()\n local interval = self.config:getInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullDataFromCloud()\n local getPropertiesCallback = function(properties)\n -- QuickApp:debug(json.encode(properties))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n if self.childrenIds[\"com.fibaro.temperatureSensor\"] ~= nil then\n self.childDevices[self.childrenIds[\"com.fibaro.temperatureSensor\"]]:setValue(properties.temperature)\n end\n if self.childrenIds[\"com.fibaro.humiditySensor\"] ~= nil then\n self.childDevices[self.childrenIds[\"com.fibaro.humiditySensor\"]]:setValue(properties.humidity)\n end\n if self.childrenIds[\"com.fibaro.binarySwitch\"] ~= nil then\n local isRunningValue = 0\n if properties.running and properties.running > 0 then\n isRunningValue = 1\n end\n self.childDevices[self.childrenIds[\"com.fibaro.binarySwitch\"]]:setValue(isRunningValue > 0)\n end\n local mode = 'Auto' -- 0 or 1\n if properties.holdtype == 2 then\n mode = 'Heat'\n elseif properties.holdtype == 7 then\n mode = 'Off'\n end\n self:updateProperty(\"thermostatMode\", mode)\n self:updateProperty(\"heatingThermostatSetpoint\", properties.heatingSetpoint)\n self:updateView(\"label1\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n \n if properties.battery ~= nil then\n self:updateProperty(\"batteryLevel\", Salus:translateBatteryLevel(properties.battery))\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\n \"quickApp\", \"battery\", \"heatingThermostatSetpoint\", \"thermostatMode\"\n }})\n end\n else\n\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\n \"quickApp\", \"power\", \"heatingThermostatSetpoint\", \"thermostatMode\"\n }})\n end\n end\n end\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n self.salus:getProperties(getPropertiesCallback)\nend\n\nfunction QuickApp:searchEvent(param)\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(gateways)\n -- QuickApp:debug(json.encode(gateways))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n -- printing results\n for _, gateway in pairs(gateways) do\n QuickApp:trace(string.format(self.i18n:get('search-row-gateway'), gateway.name, gateway.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-gateway-devices'), #gateway.devices))\n for __, device in ipairs(gateway.devices) do\n QuickApp:trace(string.format(self.i18n:get('search-row-device'), device.name, device.id, device.model))\n end\n end\n self:updateView(\"label2\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n self.salus:searchDevices(searchDevicesCallback)\nend\n" }, { "name": "Config", @@ -156,7 +156,7 @@ "name": "Salus", "isMain": false, "isOpen": false, - "content": "--[[\nSalus IT600 SDK\n@author ikubicki\n]]\nclass 'Salus'\n\nfunction Salus:new(config)\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.device_id = config:getDeviceID()\n self.token = Globals:get('salus_token', '')\n self.http = HTTPClient:new({\n baseUrl = 'https://eu.salusconnect.io'\n })\n return self\nend\n\nfunction Salus:getProperties(callback)\n local properties = {}\n local batteryLevelCallback = function(response)\n properties[\"battery\"] = response.value\n callback(properties)\n end\n local holdtypeCallback = function(response)\n properties[\"holdtype\"] = response.value\n Salus:batteryLevel(batteryLevelCallback)\n end\n local runningCallback = function(response)\n properties[\"running\"] = response.value\n Salus:holdtype(holdtypeCallback)\n end\n local humidityCallback = function(response)\n properties[\"humidity\"] = response.value\n Salus:running(runningCallback)\n end\n local heatingSetpointCallback = function(response)\n properties[\"heatingSetpoint\"] = response.value / 100\n Salus:humidity(humidityCallback)\n end\n local temperatureCallback = function(response)\n properties[\"temperature\"] = response.value / 100\n Salus:heatingSetpoint(heatingSetpointCallback)\n end\n local authCallback = function(response)\n Salus:temperature(temperatureCallback)\n end\n Salus:auth(authCallback)\nend\n\nfunction Salus:searchDevices(callback)\n local buildGateway = function(data) \n return {\n id = data.dsn,\n name = data.product_name,\n ip = data.lan_ip,\n devices = {}\n }\n end\n local buildDevice = function(data)\n return {\n id = data.dsn,\n name = data.product_name,\n model = data.oem_model,\n }\n end\n local listDevicesCallback = function(response)\n QuickApp:debug('OK');\n local gateways = {}\n -- gateways\n for _, d in ipairs(response) do\n if d.device.device_type == 'Gateway' then\n gateways[d.device.dsn] = buildGateway(d.device)\n end\n end\n -- devices\n for _, d in ipairs(response) do\n if d.device.dsn ~= d.device.product_name and d.device.device_type == 'Node' and gateways[d.device.gateway_dsn] ~= nil then\n table.insert(gateways[d.device.gateway_dsn].devices, buildDevice(d.device))\n end\n end\n callback(gateways)\n end\n local authCallback = function(response)\n Salus:listDevices(listDevicesCallback)\n end\n Salus:auth(authCallback)\nend\n\nfunction Salus:batteryLevel(callback, attempt)\n if attempt == nil then\n attempt = 1\n end\n local fail = function(response)\n if response.status == 404 then\n return callback({})\n end\n QuickApp:error('Unable to pull battery level')\n Salus:setToken('')\n -- QuickApp:debug(json.encode(response))\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:batteryLevel - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:batteryLevel(callback, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:BatteryLevel.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:temperature(callback, attempt)\n if attempt == nil then\n attempt = 1\n end\n local fail = function(response)\n QuickApp:error('Unable to pull temperature')\n Salus:setToken('')\n QuickApp:debug(json.encode(response))\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:temperature - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:temperature(callback, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:LocalTemperature_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:heatingSetpoint(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull heating setpoint')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:HeatingSetpoint_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:setHeatingSetpoint(heatingSetpoint, callback)\n local fail = function(response)\n QuickApp:error('Unable to update heatingSetpoint')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SetHeatingSetpoint_x100/datapoints.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken(),\n [\"Content-Type\"] = \"application/json\",\n }\n local data = {\n datapoint = {\n value = heatingSetpoint * 100\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:humidity(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull humidity')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SunnySetpoint_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:running(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull mode')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:RunningState.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:holdtype(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull mode')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:HoldType.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:setHoldtype(holdtype, callback)\n local fail = function(response)\n QuickApp:error('Unable to update holdtype')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SetHoldType/datapoints.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken(),\n [\"Content-Type\"] = \"application/json\",\n }\n local data = {\n datapoint = {\n value = holdtype\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:listDevices(callback, fail, attempt)\n if attempt == nil then\n attempt = 1\n end\n if fail == nil then\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n Salus:setToken('')\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:listDevices - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:listDevices(callback, nil, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data)\n end\n end\n local url = \"/apiv1/devices.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:auth(callback)\n if string.len(self.token) > 1 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n Salus:setToken('')\n end\n local success = function(response)\n QuickApp:debug(json.encode(response))\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n Salus:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n local url = \"/users/sign_in.json\"\n local headers = {\n [\"Content-Type\"] = \"application/json\"\n }\n local data = {\n user = {\n email = self.user,\n password = self.pass,\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:setToken(token)\n self.token = token\n Globals:set('salus_token', token)\nend\n\nfunction Salus:getToken()\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('salus_token', '')) > 10 then\n return Globals:get('salus_token', '')\n end\n return nil\nend\n\nfunction Salus:translateBatteryLevel(batteryLevel)\n if batteryLevel > 4 then return 100 end;\n if batteryLevel == 4 then return 75 end;\n if batteryLevel == 3 then return 50 end;\n if batteryLevel == 2 then return 25 end;\n if batteryLevel > 2 then return 0 end;\nend" + "content": "--[[\nSalus IT600 SDK\n@author ikubicki\n]]\nclass 'Salus'\n\nfunction Salus:new(config)\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.device_id = config:getDeviceID()\n self.token = Globals:get('salus_token', '')\n self.token_time = tonumber(Globals:get('salus_token_time', 0))\n self.http = HTTPClient:new({\n baseUrl = 'https://eu.salusconnect.io'\n })\n return self\nend\n\nfunction Salus:getProperties(callback)\n local properties = {}\n local batteryLevelCallback = function(response)\n properties[\"battery\"] = response.value\n callback(properties)\n end\n local holdtypeCallback = function(response)\n properties[\"holdtype\"] = response.value\n Salus:batteryLevel(batteryLevelCallback)\n end\n local runningCallback = function(response)\n properties[\"running\"] = response.value\n Salus:holdtype(holdtypeCallback)\n end\n local humidityCallback = function(response)\n properties[\"humidity\"] = response.value\n Salus:running(runningCallback)\n end\n local heatingSetpointCallback = function(response)\n properties[\"heatingSetpoint\"] = response.value / 100\n Salus:humidity(humidityCallback)\n end\n local temperatureCallback = function(response)\n properties[\"temperature\"] = response.value / 100\n Salus:heatingSetpoint(heatingSetpointCallback)\n end\n local authCallback = function(response)\n Salus:temperature(temperatureCallback)\n end\n Salus:auth(authCallback)\nend\n\nfunction Salus:searchDevices(callback)\n local buildGateway = function(data) \n return {\n id = data.dsn,\n name = data.product_name,\n ip = data.lan_ip,\n devices = {}\n }\n end\n local buildDevice = function(data)\n return {\n id = data.dsn,\n name = data.product_name,\n model = data.oem_model,\n }\n end\n local listDevicesCallback = function(response)\n QuickApp:debug('OK');\n local gateways = {}\n -- gateways\n for _, d in ipairs(response) do\n if d.device.device_type == 'Gateway' then\n gateways[d.device.dsn] = buildGateway(d.device)\n end\n end\n -- devices\n for _, d in ipairs(response) do\n if d.device.dsn ~= d.device.product_name and d.device.device_type == 'Node' and gateways[d.device.gateway_dsn] ~= nil then\n table.insert(gateways[d.device.gateway_dsn].devices, buildDevice(d.device))\n end\n end\n callback(gateways)\n end\n local authCallback = function(response)\n Salus:listDevices(listDevicesCallback)\n end\n Salus:auth(authCallback)\nend\n\nfunction Salus:batteryLevel(callback, attempt)\n if attempt == nil then\n attempt = 1\n end\n local fail = function(response)\n if response.status == 404 then\n return callback({})\n end\n QuickApp:error('Unable to pull battery level')\n Salus:setToken('')\n -- QuickApp:debug(json.encode(response))\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:batteryLevel - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:batteryLevel(callback, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:BatteryLevel.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:temperature(callback, attempt)\n if attempt == nil then\n attempt = 1\n end\n local fail = function(response)\n QuickApp:error('Unable to pull temperature')\n Salus:setToken('')\n QuickApp:debug(json.encode(response))\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:temperature - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:temperature(callback, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:LocalTemperature_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:heatingSetpoint(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull heating setpoint')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:HeatingSetpoint_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:setHeatingSetpoint(heatingSetpoint, callback)\n local fail = function(response)\n QuickApp:error('Unable to update heatingSetpoint')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SetHeatingSetpoint_x100/datapoints.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken(),\n [\"Content-Type\"] = \"application/json\",\n }\n local data = {\n datapoint = {\n value = heatingSetpoint * 100\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:humidity(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull humidity')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SunnySetpoint_x100.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:running(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull mode')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:RunningState.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:holdtype(callback)\n local fail = function(response)\n QuickApp:error('Unable to pull mode')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:HoldType.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:setHoldtype(holdtype, callback)\n local fail = function(response)\n QuickApp:error('Unable to update holdtype')\n Salus:setToken('')\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data.property)\n end\n end\n local url = \"/apiv1/dsns/\" .. self.device_id .. \"/properties/ep_9:sIT600TH:SetHoldType/datapoints.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken(),\n [\"Content-Type\"] = \"application/json\",\n }\n local data = {\n datapoint = {\n value = holdtype\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:listDevices(callback, fail, attempt)\n if attempt == nil then\n attempt = 1\n end\n if fail == nil then\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n Salus:setToken('')\n \n if attempt < 2 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Salus:listDevices - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:listDevices(callback, nil, attempt)\n end\n Salus:auth(authCallback)\n end)\n end\n end\n end\n local success = function(response)\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n if callback ~= nil then\n callback(data)\n end\n end\n local url = \"/apiv1/devices.json\"\n local headers = {\n Authorization = \"Bearer \" .. Salus:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Salus:auth(callback)\n if string.len(self.token) > 1 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n Salus:setToken('')\n end\n local success = function(response)\n -- QuickApp:debug(json.encode(response))\n if response.status > 299 then\n fail(response)\n return\n end\n local data = json.decode(response.data)\n Salus:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n local url = \"/users/sign_in.json\"\n local headers = {\n [\"Content-Type\"] = \"application/json\"\n }\n local data = {\n user = {\n email = self.user,\n password = self.pass,\n }\n }\n self.http:post(url, data, success, fail, headers)\nend\n\nfunction Salus:setToken(token)\n self.token = token\n self.token_time = os.time(os.date(\"!*t\"))\n Globals:set('salus_token', token)\n Globals:set('salus_token_time', self.token_time)\nend\n\nfunction Salus:getToken()\n if not self:checkTokenTime() then\n self:setToken('')\n return ''\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('salus_token', '')) > 10 then\n return Globals:get('salus_token', '')\n end\n return ''\nend\n\nfunction Salus:checkTokenTime()\n if self.token_time < 1 then\n self.token_time = tonumber(Globals:get('salus_token_time', 0))\n end\n return self.token_time > 0 and os.time(os.date(\"!*t\")) - self.token_time < 43200\nend\n\nfunction Salus:translateBatteryLevel(batteryLevel)\n if batteryLevel > 4 then return 100 end;\n if batteryLevel == 4 then return 75 end;\n if batteryLevel == 3 then return 50 end;\n if batteryLevel == 2 then return 25 end;\n if batteryLevel > 2 then return 0 end;\nend" }, { "name": "SalusChildDevice", diff --git a/main.lua b/main.lua index 05fce7c..ada0fee 100644 --- a/main.lua +++ b/main.lua @@ -1,5 +1,5 @@ --[[ -Salus IT600 thermostats integration v 1.1.0 +Salus IT600 thermostats integration v 1.1.1 @author ikubicki ]]