From 5b1a6606fb4b22cdd6b1e215b66e21d74e298fd8 Mon Sep 17 00:00:00 2001 From: Irek Kubicki Date: Sat, 11 Nov 2023 12:37:24 +0100 Subject: [PATCH] feat: refresh token authentication Adds support for refresh token based authentication --- Config.lua | 5 ++++ Netatmo.lua | 16 +++++++++++++ Netatmo_Unified_Sensor-CO2.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Humidity.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Noise.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Pressure.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Rain.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Temperature.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor-Wind.fqa | 33 +++++++++++--------------- Netatmo_Unified_Sensor.fqa | 31 ++++++++++-------------- README.md | 16 +++++++++---- main.lua | 3 ++- 12 files changed, 146 insertions(+), 156 deletions(-) diff --git a/Config.lua b/Config.lua index dd6bffb..8490f30 100644 --- a/Config.lua +++ b/Config.lua @@ -53,6 +53,10 @@ function Config:setAccessToken(token) self.token = token end +function Config:getRefreshToken() + return self.rtoken +end + function Config:getTimeoutInterval() return tonumber(self.interval) * 60000 end @@ -76,6 +80,7 @@ function Config:init() self.type = tostring(self.app:getVariable('Type')) self.interval = self.app:getVariable('Interval') self.token = self.app:getVariable('AccessToken') + self.rtoken = self.app:getVariable('RefreshToken') local storedClientID = Globals:get('netatmo_client_id') local storedClientSecret = Globals:get('netatmo_client_secret') diff --git a/Netatmo.lua b/Netatmo.lua index 1b31cd7..e2f2387 100644 --- a/Netatmo.lua +++ b/Netatmo.lua @@ -14,6 +14,7 @@ function Netatmo:new(config) self.module_id = config:getModuleID() self.access_token = config:getAccessToken() self.token = Globals:get('netatmo_atoken', '') + self.refresh_token = config:getRefreshToken() self.http = HTTPClient:new({}) return self end @@ -209,6 +210,13 @@ function Netatmo:auth(callback) end return end + if string.len(self.access_token) > 10 then + if callback ~= nil then + Netatmo:setToken(self.access_token) + callback({}) + end + return + end local data = { ["grant_type"] = 'password', ["scope"] = 'read_station', @@ -217,6 +225,14 @@ function Netatmo:auth(callback) ["username"] = self.user, ["password"] = self.pass, } + if string.len(self.refresh_token) > 10 then + data = { + ["grant_type"] = 'refresh_token', + ["refresh_token"] = self.refresh_token, + ["client_id"] = self.client_id, + ["client_secret"] = self.client_secret, + } + end local fail = function(response) QuickApp:error('Unable to authenticate') if self.access_token == self.token then diff --git a/Netatmo_Unified_Sensor-CO2.fqa b/Netatmo_Unified_Sensor-CO2.fqa index ebc21cc..9617487 100644 --- a/Netatmo_Unified_Sensor-CO2.fqa +++ b/Netatmo_Unified_Sensor-CO2.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro CO2 sensor", "type": "com.fibaro.multilevelSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,14 +109,14 @@ "value": "" }, { - "name": "Username", - "type": "string", + "name": "RefreshToken", + "type": "password", "value": "" }, { - "name": "Password", - "type": "password", - "value": "" + "name": "Type", + "type": "string", + "value": "CO2" }, { "name": "ModuleID", @@ -127,11 +127,6 @@ "name": "DeviceID", "type": "string", "value": "" - }, - { - "name": "Type", - "type": "string", - "value": "CO2" } ], "typeTemplateInitialized": true @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Humidity.fqa b/Netatmo_Unified_Sensor-Humidity.fqa index f99f5e5..540a297 100644 --- a/Netatmo_Unified_Sensor-Humidity.fqa +++ b/Netatmo_Unified_Sensor-Humidity.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Humidity sensor", "type": "com.fibaro.humiditySensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,27 +109,22 @@ "value": "" }, { - "name": "Username", - "type": "string", - "value": "" - }, - { - "name": "Password", + "name": "RefreshToken", "type": "password", "value": "" }, { - "name": "ModuleID", + "name": "Type", "type": "string", - "value": "" + "value": "Humidity" }, { - "name": "DeviceID", + "name": "ModuleID", "type": "string", "value": "" }, { - "name": "Type", + "name": "DeviceID", "type": "string", "value": "" } @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Noise.fqa b/Netatmo_Unified_Sensor-Noise.fqa index 4f475e9..9851d71 100644 --- a/Netatmo_Unified_Sensor-Noise.fqa +++ b/Netatmo_Unified_Sensor-Noise.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Noise sensor", "type": "com.fibaro.multilevelSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,14 +109,14 @@ "value": "" }, { - "name": "Username", - "type": "string", + "name": "RefreshToken", + "type": "password", "value": "" }, { - "name": "Password", - "type": "password", - "value": "" + "name": "Type", + "type": "string", + "value": "Noise" }, { "name": "ModuleID", @@ -127,11 +127,6 @@ "name": "DeviceID", "type": "string", "value": "" - }, - { - "name": "Type", - "type": "string", - "value": "Noise" } ], "typeTemplateInitialized": true @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Pressure.fqa b/Netatmo_Unified_Sensor-Pressure.fqa index 4fb3996..b6872b3 100644 --- a/Netatmo_Unified_Sensor-Pressure.fqa +++ b/Netatmo_Unified_Sensor-Pressure.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Pressure sensor", "type": "com.fibaro.multilevelSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,14 +109,14 @@ "value": "" }, { - "name": "Username", - "type": "string", + "name": "RefreshToken", + "type": "password", "value": "" }, { - "name": "Password", - "type": "password", - "value": "" + "name": "Type", + "type": "string", + "value": "Pressure" }, { "name": "ModuleID", @@ -127,11 +127,6 @@ "name": "DeviceID", "type": "string", "value": "" - }, - { - "name": "Type", - "type": "string", - "value": "Pressure" } ], "typeTemplateInitialized": true @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Rain.fqa b/Netatmo_Unified_Sensor-Rain.fqa index 0fa6f69..089b4e9 100644 --- a/Netatmo_Unified_Sensor-Rain.fqa +++ b/Netatmo_Unified_Sensor-Rain.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Rain sensor", "type": "com.fibaro.rainSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,27 +109,22 @@ "value": "" }, { - "name": "Username", - "type": "string", - "value": "" - }, - { - "name": "Password", + "name": "RefreshToken", "type": "password", "value": "" }, { - "name": "ModuleID", + "name": "Type", "type": "string", - "value": "" + "value": "Rain" }, { - "name": "DeviceID", + "name": "ModuleID", "type": "string", "value": "" }, { - "name": "Type", + "name": "DeviceID", "type": "string", "value": "" } @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Temperature.fqa b/Netatmo_Unified_Sensor-Temperature.fqa index 395c9ef..92953c3 100644 --- a/Netatmo_Unified_Sensor-Temperature.fqa +++ b/Netatmo_Unified_Sensor-Temperature.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Temperature sensor", "type": "com.fibaro.temperatureSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,27 +109,22 @@ "value": "" }, { - "name": "Username", - "type": "string", - "value": "" - }, - { - "name": "Password", + "name": "RefreshToken", "type": "password", "value": "" }, { - "name": "ModuleID", + "name": "Type", "type": "string", - "value": "" + "value": "Temperature" }, { - "name": "DeviceID", + "name": "ModuleID", "type": "string", "value": "" }, { - "name": "Type", + "name": "DeviceID", "type": "string", "value": "" } @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor-Wind.fqa b/Netatmo_Unified_Sensor-Wind.fqa index 50d2033..34cda05 100644 --- a/Netatmo_Unified_Sensor-Wind.fqa +++ b/Netatmo_Unified_Sensor-Wind.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Wind sensor", "type": "com.fibaro.windSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,27 +109,22 @@ "value": "" }, { - "name": "Username", - "type": "string", - "value": "" - }, - { - "name": "Password", + "name": "RefreshToken", "type": "password", "value": "" }, { - "name": "ModuleID", + "name": "Type", "type": "string", - "value": "" + "value": "Wind" }, { - "name": "DeviceID", + "name": "ModuleID", "type": "string", "value": "" }, { - "name": "Type", + "name": "DeviceID", "type": "string", "value": "" } @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/Netatmo_Unified_Sensor.fqa b/Netatmo_Unified_Sensor.fqa index fc56f77..8ce9e51 100644 --- a/Netatmo_Unified_Sensor.fqa +++ b/Netatmo_Unified_Sensor.fqa @@ -1,5 +1,5 @@ { - "name": "Netatmo Single Sensor", + "name": "Fibaro Wind sensor", "type": "com.fibaro.multilevelSensor", "apiVersion": "1.2", "initialProperties": { @@ -10,7 +10,7 @@ "style": { "height": "0" }, - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" }, "sections": { "items": [ @@ -81,7 +81,7 @@ } }, "head": { - "title": "UnifiedNetatmo" + "title": "fibaro-unified-sensor" } } }, @@ -109,27 +109,22 @@ "value": "" }, { - "name": "Username", - "type": "string", - "value": "" - }, - { - "name": "Password", + "name": "RefreshToken", "type": "password", "value": "" }, { - "name": "ModuleID", + "name": "Type", "type": "string", "value": "" }, { - "name": "DeviceID", + "name": "ModuleID", "type": "string", "value": "" }, { - "name": "Type", + "name": "DeviceID", "type": "string", "value": "" } @@ -140,8 +135,8 @@ { "name": "main", "isMain": true, - "isOpen": true, - "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" + "isOpen": false, + "content": "--[[\nNetatmo Unified Sensor\n@author ikubicki\n@version 1.1.0\n]]\nfunction QuickApp:onInit()\n self.config = Config:new(self)\n self.i18n = i18n:new(api.get(\"/settings/info\").defaultLanguage)\n self.netatmo = Netatmo:new(self.config)\n self:trace('')\n self:trace('Netatmo Unified Sensor - ' .. self:getTypeLabel())\n self:updateProperty('manufacturer', 'Netatmo')\n self:updateProperty('model', 'Weather Station')\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n \n self.interfaces = api.get(\"/devices/\" .. self.id).interfaces\n self:run()\nend\n\nfunction QuickApp:run()\n self:pullNetatmoData()\n local interval = self.config:getTimeoutInterval()\n if (interval > 0) then\n fibaro.setTimeout(interval, function() self:run() end)\n end\nend\n\nfunction QuickApp:pullNetatmoData()\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refreshing'))\n local getSensorDataCallback = function(sensorData)\n local property = self:getProperty()\n self:updateProperty(\"value\", sensorData.data[property])\n if sensorData.battery ~= nil then\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"battery\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"battery\"} })\n end\n else\n self:updateProperty(\"batteryLevel\", sensorData.battery)\n if not utils:contains(self.interfaces, \"power\") then\n api.put(\"/devices/\" .. self.id, { interfaces = {\"quickApp\", \"power\"} })\n end\n end\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('last-update'), os.date('%Y-%m-%d %H:%M:%S')))\n self:updateView(\"button2_2\", \"text\", self.i18n:get('refresh'))\n end\n local types = self:getType()\n local id = self.config:getModuleID()\n self.netatmo:getSensorData(types, id, getSensorDataCallback)\nend\n\nfunction QuickApp:refreshEvent()\n self:pullNetatmoData()\nend\n\nfunction QuickApp:searchEvent()\n self:debug(self.i18n:get('searching-devices'))\n self:updateView(\"button2_1\", \"text\", self.i18n:get('searching-devices'))\n local searchDevicesCallback = function(stations)\n -- QuickApp:debug(json.encode(stations))\n -- printing results\n for _, station in pairs(stations) do\n QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))\n QuickApp:trace(string.format(self.i18n:get('search-row-station-modules'), #station.modules))\n for __, module in ipairs(station.modules) do\n QuickApp:trace(string.format(self.i18n:get('search-row-module'), module.name, module.id, module.type))\n QuickApp:trace(string.format(self.i18n:get('search-row-module_types'), table.concat(module.data_type, ', ')))\n end\n end\n self:updateView(\"button2_1\", \"text\", self.i18n:get('search-devices'))\n self:updateView(\"label\", \"text\", string.format(self.i18n:get('check-logs'), 'QUICKAPP' .. self.id))\n end\n local types = self:getType()\n self.netatmo:searchDevices(types, searchDevicesCallback)\nend\n\nfunction QuickApp:getType()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" or self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n self:updateProperty(\"unit\", \"km/h\")\n return { \"NAModule2\" } \n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n self:updateProperty(\"unit\", \"mm/h\")\n return { \"NAModule3\" } \n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return { \"NAMain\", \"NAModule1\", \"NAModule4\" } \n end\n if self.config:getDeviceType() == \"Pressure\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"Noise\" then\n return { \"NAMain\" }\n end\n if self.config:getDeviceType() == \"CO2\" then\n return { \"NAMain\", \"NAModule4\" } \n end\nend\n\nfunction QuickApp:getTypeLabel()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"Wind\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"Gusts\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n\nfunction QuickApp:getProperty()\n if self.type == \"com.fibaro.temperatureSensor\" or self.config:getDeviceType() == \"Temperature\" then \n return \"Temperature\"\n end\n if self.config:getDeviceType() == \"Gusts\" or self.config:getDeviceType() == \"Gust\" then \n return \"max_wind_str\"\n -- return \"GustStrength\"\n end\n if self.type == \"com.fibaro.windSensor\" or self.config:getDeviceType() == \"Wind\" then\n return \"WindStrength\"\n end\n if self.config:getDeviceType() == \"Rain1h\" or self.config:getDeviceType() == \"Rain1\" then \n return \"sum_rain_1\"\n end\n if self.config:getDeviceType() == \"Rain24h\" or self.config:getDeviceType() == \"Rain24\" then \n return \"sum_rain_24\"\n end\n if self.type == \"com.fibaro.rainSensor\" or self.config:getDeviceType() == \"Rain\" then \n return \"Rain\"\n end\n if self.type == \"com.fibaro.humiditySensor\" or self.config:getDeviceType() == \"Humidity\" then \n return \"Humidity\"\n end\n if self.config:getDeviceType() == \"Pressure\" then\n return \"Pressure\"\n end\n if self.config:getDeviceType() == \"Noise\" then\n return \"Noise\"\n end\n if self.config:getDeviceType() == \"CO2\" then\n return \"CO2\"\n end\nend\n" }, { "name": "Globals", @@ -152,8 +147,8 @@ { "name": "Config", "isMain": false, - "isOpen": true, - "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" + "isOpen": false, + "content": "--[[\nConfiguration handler\n@author ikubicki\n]]\nclass 'Config'\n\nfunction Config:new(app)\n self.app = app\n self:init()\n return self\nend\n\nfunction Config:getClientID()\n return self.clientID\nend\n\nfunction Config:getClientSecret()\n return self.clientSecret\nend\n\nfunction Config:getUsername()\n return self.username\nend\n\nfunction Config:getPassword()\n return self.password\nend\n\nfunction Config:getDeviceID()\n return self.deviceID\nend\n\nfunction Config:setDeviceID(deviceID)\n self.deviceID = deviceID\n self.app:setVariable('DeviceID', deviceID)\nend\n\nfunction Config:getModuleID()\n return self.moduleID\nend\n\nfunction Config:setModuleID(moduleID)\n self.moduleID = moduleID\n self.app:setVariable('ModuleID', moduleID)\nend\n\nfunction Config:getAccessToken()\n return self.token\nend\n\nfunction Config:setAccessToken(token)\n self.app:setVariable(\"AccessToken\", token)\n self.token = token\nend\n\nfunction Config:getRefreshToken()\n return self.rtoken\nend\n\nfunction Config:getTimeoutInterval()\n return tonumber(self.interval) * 60000\nend\n\nfunction Config:getDeviceType()\n return self.type\nend\n\n--[[\nThis function takes variables and sets as global variables if those are not set already.\nThis way, adding other devices might be optional and leaves option for users, \nwhat they want to add into HC3 virtual devices.\n]]\nfunction Config:init()\n self.clientID = self.app:getVariable('ClientID')\n self.clientSecret = self.app:getVariable('ClientSecret')\n self.username = self.app:getVariable('Username')\n self.password = self.app:getVariable('Password')\n self.deviceID = tostring(self.app:getVariable('DeviceID'))\n self.moduleID = tostring(self.app:getVariable('ModuleID'))\n self.type = tostring(self.app:getVariable('Type'))\n self.interval = self.app:getVariable('Interval')\n self.token = self.app:getVariable('AccessToken')\n self.rtoken = self.app:getVariable('RefreshToken')\n\n local storedClientID = Globals:get('netatmo_client_id')\n local storedClientSecret = Globals:get('netatmo_client_secret')\n local storedUsername = Globals:get('netatmo_username')\n local storedPassword = Globals:get('netatmo_password')\n local storedInterval = Globals:get('netatmo_interval')\n\n -- handling clientID\n if string.len(self.clientID) < 4 and string.len(storedClientID) > 3 then\n self.app:setVariable(\"ClientID\", storedClientID)\n self.clientID = storedClientID\n elseif (storedClientID == nil and self.clientID) then -- or storedClientID ~= self.clientID then\n Globals:set('netatmo_client_id', self.clientID)\n end\n -- handling client secret\n if string.len(self.clientSecret) < 4 and string.len(storedClientSecret) > 3 then\n self.app:setVariable(\"ClientSecret\", storedClientSecret)\n self.clientSecret = storedClientSecret\n elseif (storedClientSecret == nil and self.clientSecret) then -- or storedClientSecret ~= self.clientSecret then\n Globals:set('netatmo_client_secret', self.clientSecret)\n end\n -- handling username\n if string.len(self.username) < 4 and string.len(storedUsername) > 3 then\n self.app:setVariable(\"Username\", storedUsername)\n self.username = storedUsername\n elseif (storedUsername == nil and self.username) then -- or storedUsername ~= self.username then\n Globals:set('netatmo_username', self.username)\n end\n -- handling password\n if string.len(self.password) < 4 and string.len(storedPassword) > 3 then\n self.app:setVariable(\"Password\", storedPassword)\n self.password = storedPassword\n elseif (storedPassword == nil and self.password) then -- or storedPassword ~= self.password then\n Globals:set('netatmo_password', self.password)\n end\n -- handling interval\n if not self.interval or self.interval == \"\" then\n if storedInterval and storedInterval ~= \"\" then\n self.app:setVariable(\"Interval\", storedInterval)\n self.interval = storedInterval\n else\n self.interval = \"5\"\n end\n end\n if (storedInterval == \"\" and self.interval ~= \"\") then -- or storedInterval ~= self.interval then\n Globals:set('netatmo_interval', self.interval)\n end\nend" }, { "name": "HTTPClient", @@ -170,8 +165,8 @@ { "name": "Netatmo", "isMain": false, - "isOpen": true, - "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" + "isOpen": false, + "content": "--[[\nNetatmo SDK\n@author ikubicki\n]]\nclass 'Netatmo'\n\nfunction Netatmo:new(config)\n self.config = config\n self.user = config:getUsername()\n self.pass = config:getPassword()\n self.client_id = config:getClientID()\n self.client_secret = config:getClientSecret()\n self.device_id = config:getDeviceID()\n self.module_id = config:getModuleID()\n self.access_token = config:getAccessToken()\n self.token = Globals:get('netatmo_atoken', '')\n self.refresh_token = config:getRefreshToken()\n self.http = HTTPClient:new({})\n return self\nend\n\nfunction Netatmo:searchDevices(types, callback)\n if #types < 1 then types = nil end\n local buildModule = function(module)\n return {\n id = module._id,\n name = module.module_name,\n type = module.type,\n data_type = module.data_type,\n }\n end\n local buildStation = function(data)\n local station = {\n id = data._id,\n home_id = data.home_id,\n name = data.station_name,\n modules = {},\n }\n table.insert(station.modules, buildModule(data))\n return station\n end\n local getStationsDataCallback = function(devices)\n local stations = {}\n for _, device in ipairs(devices) do\n local station = buildStation(device)\n local add = false\n for _, module in ipairs(device.modules) do\n if not types or utils:contains(types, module.type) then\n table.insert(station.modules, buildModule(module))\n add = true\n end\n end\n if add == true then\n table.insert(stations, station)\n end\n end\n if callback ~= nil then\n callback(stations)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getSensorData(types, moduleID, callback)\n if callback == nil then\n callback = function() end\n end\n local getStationsDataCallback = function(devices)\n for _, device in ipairs(devices) do\n if device._id == moduleID then\n return callback({\n id = device._id,\n type = device.type,\n data = device.dashboard_data,\n name = device.module_name,\n battery = nil,\n dead = device.reachable ~= true,\n })\n end\n for _, module in ipairs(device.modules) do\n if module._id == moduleID then\n return callback({\n id = module._id,\n type = module.type,\n data = module.dashboard_data,\n name = module.module_name,\n battery = math.ceil((tonumber(module.battery_vp) - 4300) / 17),\n dead = module.reachable,\n })\n end\n end\n end\n if callback ~= nil then\n callback({})\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n local searchDevicesCallback = function(stations)\n if self.device_id == \"\" then\n -- QuickApp:debug(json.encode(stations))\n QuickApp:trace('Assigning DeviceID: ' .. stations[1].id)\n self:setDeviceID(stations[1].id)\n end\n if moduleID == nil or string.len(moduleID) < 10 then\n moduleID = \"\"\n for _, module in pairs(stations[1].modules) do\n if utils:contains(types, module.type) then\n moduleID = module.id\n end\n end\n if moduleID ~= nil then\n QuickApp:trace('Assigning ModuleID: ' .. moduleID)\n self:setModuleID(moduleID)\n end\n end\n if string.len(moduleID) > 10 then\n self:auth(authCallback)\n elseif (callback ~= nil) then\n callback({})\n end\n end\n if string.len(moduleID) < 10 or self.device_id == \"\" then\n self:searchDevices(types, searchDevicesCallback)\n else\n searchDevicesCallback({{modules = {{id = moduleID}}}})\n end\nend\n\nfunction Netatmo:getWeatherData(callback)\n local getStationsDataCallback = function(devices)\n local device = devices[1]\n local weatherData = {\n _id = device._id,\n temp = tonumber(device.dashboard_data[\"Temperature\"]),\n humi = tonumber(device.dashboard_data[\"Humidity\"]),\n rain = 0,\n wind = 0, \n }\n for _, module in pairs(device.modules) do\n if module.type == \"NAModule1\" then\n weatherData.temp = tonumber(module.dashboard_data.Temperature)\n weatherData.humi = tonumber(module.dashboard_data.Humidity)\n end\n if module.type == \"NAModule2\" then\n weatherData.wind = tonumber(module.dashboard_data.WindStrength)\n end\n if module.type == \"NAModule3\" then\n weatherData.rain = tonumber(module.dashboard_data.Rain)\n end\n end\n if callback ~= nil then\n callback(weatherData)\n end\n end\n local authCallback = function(response)\n self:getStationsData(getStationsDataCallback)\n end\n self:auth(authCallback)\nend\n\nfunction Netatmo:getStationsData(callback, attempt)\n if attempt == nil then\n attempt = 0\n end\n local fail = function(response)\n QuickApp:error('Unable to pull devices')\n -- QuickApp:debug(json.encode(response.data))\n Netatmo:setToken('')\n if attempt < 3 then\n attempt = attempt + 1\n fibaro.setTimeout(3000, function()\n QuickApp:debug('Netatmo:getStationData - Retry attempt #' .. attempt)\n local authCallback = function(response)\n self:getStationsData(callback, attempt)\n end\n Netatmo: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.body.devices)\n end\n end\n local url = 'https://api.netatmo.com/api/getstationsdata'\n if string.len(self.device_id) > 1 then\n url = url .. '?device_id=' .. self.device_id\n end\n local headers = {\n Authorization = \"Bearer \" .. self:getToken()\n }\n self.http:get(url, success, fail, headers)\nend\n\nfunction Netatmo:auth(callback)\n if string.len(self:getToken()) > 10 then\n -- QuickApp:debug('Already authenticated')\n if callback ~= nil then\n callback({})\n end\n return\n end\n if string.len(self.access_token) > 10 then\n if callback ~= nil then\n Netatmo:setToken(self.access_token)\n callback({})\n end\n return\n end\n local data = {\n [\"grant_type\"] = 'password',\n [\"scope\"] = 'read_station',\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n [\"username\"] = self.user,\n [\"password\"] = self.pass,\n }\n if string.len(self.refresh_token) > 10 then\n data = {\n [\"grant_type\"] = 'refresh_token',\n [\"refresh_token\"] = self.refresh_token,\n [\"client_id\"] = self.client_id,\n [\"client_secret\"] = self.client_secret,\n }\n end\n local fail = function(response)\n QuickApp:error('Unable to authenticate')\n if self.access_token == self.token then\n QuickApp:error('Removing configured AccessToken')\n self.config:setAccessToken('')\n end\n Netatmo: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 Netatmo:setToken(data.access_token)\n if callback ~= nil then\n callback(data)\n end\n end\n self.http:postForm('https://api.netatmo.net/oauth2/token', data, success, fail)\nend\n\nfunction Netatmo:setDeviceID(deviceID)\n self.device_id = deviceID\n self.config:setDeviceID(deviceID)\nend\n\nfunction Netatmo:setModuleID(moduleID)\n self.module_id = moduleID\n self.config:setModuleID(moduleID)\nend\n\nfunction Netatmo:setToken(token)\n self.token = token\n Globals:set('netatmo_atoken', token)\nend\n\nfunction Netatmo:getToken()\n if not self.token and self.access_token ~= nil then\n self.token = self.access_token\n end\n if string.len(self.token) > 10 then\n return self.token\n elseif string.len(Globals:get('netatmo_atoken', '')) > 10 then\n return Globals:get('netatmo_atoken', '')\n end\n return \"\"\nend" }, { "name": "utils", diff --git a/README.md b/README.md index 0ff507f..b73a139 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ Data updates every 5 minutes by default. `ClientSecret` - Netatmo client secret -`Username` - Netatmo username - -`Password` - Netatmo password +`RefreshToken` - Refresh token ### Optional values @@ -28,6 +26,16 @@ Other sensors (because they are using generic type - Multilevel sensor type) all `AccessToken` - Allows to set own access token and bypass credentials authentication. +## Installation + +To acquire required parameters, you need to go to Netatmo Connect site and create new application. Once that's done, you will be able to get client ID and client secret. To get refresh token, you need to use a Token generator (section below you get client id and client secret). +From generated token you need to use a Refresh Token value. +This should allow you to run quick application in your Fibaro Home Center device. + ## Integration -This quick application integrates with other Netatmo dedicated quick apps for devices. It will automatically populate configuration to new virtual Netatmo devices. \ No newline at end of file +This quick application integrates with other Netatmo dedicated quick apps for devices. It will automatically populate configuration to new virtual Netatmo devices. + +## Support + +Due to horrible user experience with Fibaro Marketplace, for better communication I recommend to contact with me through GitHub or create an issue in the repository. \ No newline at end of file diff --git a/main.lua b/main.lua index 0d799c1..f42dd7d 100644 --- a/main.lua +++ b/main.lua @@ -1,6 +1,7 @@ --[[ Netatmo Unified Sensor @author ikubicki +@version 1.1.0 ]] function QuickApp:onInit() self.config = Config:new(self) @@ -56,7 +57,7 @@ function QuickApp:searchEvent() self:debug(self.i18n:get('searching-devices')) self:updateView("button2_1", "text", self.i18n:get('searching-devices')) local searchDevicesCallback = function(stations) - QuickApp:debug(json.encode(stations)) + -- QuickApp:debug(json.encode(stations)) -- printing results for _, station in pairs(stations) do QuickApp:trace(string.format(self.i18n:get('search-row-station'), station.name, station.id))