-- // This program is free software: you can redistribute it and/or modify -- // it under the condition that it is for private or home useage and -- // this whole comment is reproduced in the source code file. -- // Commercial utilisation is not authorized without the appropriate -- // written agreement from amg0 / alexis . mermet @ gmail . com -- // This program is distributed in the hope that it will be useful, -- // but WITHOUT ANY WARRANTY; without even the implied warranty of -- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . local MSG_CLASS = "ALTUI" local ALTUI_SERVICE = "urn:upnp-org:serviceId:altui1" local devicetype = "urn:schemas-upnp-org:device:altui:1" local DEBUG_MODE = false local version = "v1.02" local UI7_JSON_FILE= "D_ALTUI_UI7.json" local json = require("L_ALTUIjson") local mime = require("mime") local socket = require("socket") local http = require("socket.http") local https = require ("ssl.https") local ltn12 = require("ltn12") local tmpprefix = "/tmp/altui_" -- prefix for tmp files hostname = "" --calling a function from HTTP in the device context --http://192.168.1.5/port_3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunLua&DeviceNum=81&Code=getMapUrl(81) ------------------------------------------------ -- Debug -- ------------------------------------------------ local function log(text, level) luup.log(string.format("%s: %s", MSG_CLASS, text), (level or 50)) end local function debug(text) if (DEBUG_MODE) then log("debug: " .. text) end end local function warning(stuff) log("warning: " .. stuff, 2) end local function error(stuff) log("erreur: " .. stuff, 1) end local function dumpString(str) for i=1,str:len() do debug(string.format("i:%d c:%d char:%s",i,str:byte(i),str:sub(i,i) )) end end function string.starts(String,Start) return string.sub(String,1,string.len(Start))==Start end local function isempty(s) return s == nil or s == '' end function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end function setDebugMode(lul_device,newDebugMode) lul_device = tonumber(lul_device) newDebugMode = tonumber(newDebugMode) or 0 log(string.format("setDebugMode(%d,%d)",lul_device,newDebugMode)) luup.variable_set(ALTUI_SERVICE, "Debug", newDebugMode, lul_device) if (newDebugMode==1) then DEBUG_MODE=true else DEBUG_MODE=false end end ---code from lolodomo DNLA plugin local function xml_decode(val) return val:gsub("&", '&') :gsub("<", '<') :gsub(">", '>') :gsub(""", '"') :gsub("'", "'") :gsub("<", "<") :gsub(">", ">") :gsub(""", '"') :gsub("'", "'") :gsub("&", "&") end ---code from lolodomo DNLA plugin local function xml_encode(val) return val:gsub("&", "&") :gsub("<", "<") :gsub(">", ">") :gsub('"', """) :gsub("'", "'") end function url_decode(str) str = string.gsub (str, "+", " ") str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end) str = string.gsub (str, "\r\n", "\n") return str end function findALTUIDevice() for k,v in pairs(luup.devices) do if( v.device_type == devicetype ) then return k end end return -1 end function proxyGet(lul_device,newUrl,resultName) debug(string.format("proxyGet lul_device:%d newUrl:%s",lul_device,newUrl)) local httpcode,data = luup.inet.wget(newUrl,10) if (httpcode~=0) then error(string.format("failed to connect to url:%s, http.request returned %d", newUrl,httpcode)) return 0,""; end debug(string.format("success httpcode:%s",httpcode)) debug(string.format("data:%s",data)) return 1,data end -- {"devices":{},"scenes":{"scenes_57":{"timers":[],"triggers":[{"name":"Below 1km","enabled":1,"template":"2","device":"94","arguments":[{"id":"1","value":"1"}],"LastEval":1,"last_run":1437377298}],"groups":[{"delay":0,"actions":[]}],"name":"Alexis 1km","lua":"--- message\nlocal current = os.time()\nlocal message = \"\\nBelow 1km. \\n Heure:\" .. os.date(\"%c\",current) .. \"\\n\"\npushingbox_notify( message )\nreturn true","id":57,"room":"11","modeStatus":"1,2,3,4","paused":0,"favorite":false,"altuiid":"0-57","last_run":1437376224,"Timestamp":1437377258}},"sections":{},"rooms":{},"InstalledPlugins":[],"PluginSettings":[],"users":{}} json -- {"devices":{},"scenes":{"scenes_57":{"timers":[],"triggers":[{"name":"Below 1km","enabled":1,"template":"2","device":"94","arguments":[{"id":"1","value":"1"}],"LastEval":1,"last_run":1437379023}],"groups":[{"delay":0,"actions":[]}],"name":"Alexis 1km","lua":"--- message\nlocal current = os.time()\nlocal message = \"\\nBelow 1km. \\n Heure:\" .. os.date(\"%c\",current) .. \"\\n\"\npushingbox_notify( message )\nreturn true","id":57,"room":"11","modeStatus":"1,2,3,4","paused":1,"favorite":false,"altuiid":"0-57","Timestamp":1437378982,"last_run":1437379024}},"sections":{},"rooms":{},"InstalledPlugins":[],"PluginSettings":[],"users":{}} json -- -- WARNING the SOAPACTION header requires to be inside double quotes -- otherwise it RETURNS http500 -- function proxySoap(lul_device,newUrl,soapaction,envelop,body) debug(string.format("proxySoap lul_device:%d soapaction:%s",lul_device,soapaction)) debug(string.format("body:%s",body)) local mybody = string.format(envelop,xml_encode(body)) -- local mybody=" {"devices":{},"scenes":{"scenes_57":{"timers":[],"triggers":[{"name":"Below 1km","enabled":1,"template":"2","device":"94","arguments":[{"id":"1","value":"1"}],"LastEval":1,"last_run":1437379682}],"groups":[{"delay":0,"actions":[]}],"name":"Alexis 1km","lua":"--- message\nlocal current = os.time()\nlocal message = \"\\nBelow 1km. \\n Heure:\" .. os.date(\"%c\",current) .. \"\\n\"\npushingbox_notify( message )\nreturn true","id":57,"room":"11","modeStatus":"1,2,3,4","paused":0,"favorite":false,"altuiid":"0-57","last_run":1437379024,"Timestamp":1437379040}},"sections":{},"rooms":{},"InstalledPlugins":[],"PluginSettings":[],"users":{}} json " debug(string.format("mybody:%s",mybody)) local result = {} local request, code = http.request({ method="POST", url = newUrl, source= ltn12.source.string(mybody), headers = { -- ["Host"]="192.168.1.5", ["Connection"]= "keep-alive", ["Content-Length"] = mybody:len(), -- ["Origin"]="http://192.168.1.5", -- ["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36", ["Content-Type"] = "text/xml;charset=UTF-8", ["Accept"]="text/plain, */*; q=0.01", -- ["X-Requested-With"]="XMLHttpRequest", ["Accept-Encoding"]="gzip, deflate", ["Accept-Language"]= "fr,fr-FR;q=0.8,en;q=0.6,en-US;q=0.4", ["SOAPACTION"]='"urn:schemas-micasaverde-org:service:HomeAutomationGateway:1#' .. soapaction..'"' }, sink = ltn12.sink.table(result) }) -- fail to connect if (request==nil) then error(string.format("failed to connect to %s, http.request returned nil", newUrl)) return 0,"" elseif (code==401) then warning(string.format("Access requires a user/password: %d", code)) return 0,"" elseif (code~=200) then warning(string.format("http.request returned a bad code: %d", code)) return 0,"" end -- everything looks good local data = table.concat(result) debug(string.format("request:%s",request)) debug(string.format("code:%s",code)) return 1,data end -- -- code from lolodomo in DNLA plugin -- local function table2XML(value) --debug(string.format("table2XML(%s)",json.encode(value))) local result = "" if (value == nil) then return result end -- -- Convert all the Number, Boolean and Table objects, and escape all the string -- values in the XML output stream -- -- If value table has an entry OrderedArgs, we consider that all the values -- are set in this special entry and we bypass all the other values of the value table. -- In this particular case, this entry itself is a table of strings, each element of -- the table following the format "parameter=value" -- for e, v in pairs(value) do if (v == nil) then result = result .. string.format("<%s />", e) elseif (type(v) == "table") then result = result .. table2XML(v) elseif (type(v) == "number") then result = result .. string.format("<%s>%.0f", e, v, e) elseif (type(v) == "boolean") then result = result .. string.format("<%s>%s", e, (v and "1" or "0"), e) else result = result .. string.format("<%s>%s", e, xml_encode(v), e) end end return result end ------------------------------------------------ -- Check UI7 ------------------------------------------------ local function checkVersion(lul_device) local ui7Check = luup.variable_get(ALTUI_SERVICE, "UI7Check", lul_device) or "" if ui7Check == "" then luup.variable_set(ALTUI_SERVICE, "UI7Check", "false", lul_device) ui7Check = "false" end if( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false") then luup.variable_set(ALTUI_SERVICE, "UI7Check", "true", lul_device) luup.attr_set("device_json", UI7_JSON_FILE, lul_device) luup.reload() end end local function getIP() -- local stdout = io.popen("GetNetworkState.sh ip_wan") -- local ip = stdout:read("*a") -- stdout:close() -- return ip local mySocket = socket.udp () mySocket:setpeername ("42.42.42.42", "424242") -- arbitrary IP/PORT local ip = mySocket:getsockname () mySocket: close() return ip or "127.0.0.1" end local function getSysinfo(ip) --http://192.168.1.5/cgi-bin/cmh/sysinfo.sh log(string.format("getSysinfo(%s)",ip)) local url=string.format("http://%s/cgi-bin/cmh/sysinfo.sh",ip) local timeout = 30 local httpcode,content = luup.inet.wget(url,timeout) if (httpcode==0) then local obj = json.decode(content) debug("sysinfo="..content) return obj end return nil end ------------------------------------------------ -- Tasks ------------------------------------------------ local taskHandle = -1 local TASK_ERROR = 2 local TASK_ERROR_PERM = -2 local TASK_SUCCESS = 4 local TASK_BUSY = 1 -- -- Has to be "non-local" in order for MiOS to call it :( -- local function task(text, mode) if (mode == TASK_ERROR_PERM) then error(text) elseif (mode ~= TASK_SUCCESS) then warning(text) else log(text) end if (mode == TASK_ERROR_PERM) then taskHandle = luup.task(text, TASK_ERROR, MSG_CLASS, taskHandle) else taskHandle = luup.task(text, mode, MSG_CLASS, taskHandle) -- Clear the previous error, since they're all transient if (mode ~= TASK_SUCCESS) then luup.call_delay("clearTask", 15, "", false) end end end function clearTask() task("Clearing...", TASK_SUCCESS) end function UserMessage(text, mode) mode = (mode or TASK_ERROR) task(text,mode) end ------------------------------------------------ -- LUA Utils ------------------------------------------------ function string:split(sep) -- from http://lua-users.org/wiki/SplitJoin local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end function string:template(variables) return (self:gsub('@(.-)@', function (key) return tostring(variables[key] or '') end)) end function string:trim() return self:match "^%s*(.-)%s*$" end ------------------------------------------------ -- VERA Device Utils ------------------------------------------------ -- example: iterateTbl( t , luup.log ) local function forEach( tbl, func, param ) for k,v in pairs(tbl) do func(k,v,param) end end function tablelength(T) local count = 0 if (T~=nil) then for _ in pairs(T) do count = count + 1 end end return count end function inTable(tbl, item) for key, value in pairs(tbl) do if value == item then return key end end return false end local function getParent(lul_device) return luup.devices[lul_device].device_num_parent end local function getAltID(lul_device) return luup.devices[lul_device].id end ----------------------------------- -- from a altid, find a child device -- returns 2 values -- a) the index === the device ID -- b) the device itself luup.devices[id] ----------------------------------- local function findChild( lul_parent, altid ) debug(string.format("findChild(%s,%s)",lul_parent,altid)) for k,v in pairs(luup.devices) do if( getParent(k)==lul_parent) then if( v.id==altid) then return k,v end end end return nil,nil end local function forEachChildren(parent, func, param ) --debug(string.format("forEachChildren(%s,func,%s)",parent,param)) for k,v in pairs(luup.devices) do if( getParent(k)==parent) then func(k, param) end end end local function getForEachChildren(parent, func, param ) --debug(string.format("forEachChildren(%s,func,%s)",parent,param)) local result = {} for k,v in pairs(luup.devices) do if( getParent(k)==parent) then result[#result+1] = func(k, param) end end return result end ------------------------------------------------ -- Device Properties Utils ------------------------------------------------ local function getSetVariable(serviceId, name, deviceId, default) local curValue = luup.variable_get(serviceId, name, deviceId) if (curValue == nil) then curValue = default luup.variable_set(serviceId, name, curValue, deviceId) end return curValue end local function getSetVariableIfEmpty(serviceId, name, deviceId, default) local curValue = luup.variable_get(serviceId, name, deviceId) if (curValue == nil) or (curValue:trim() == "") then curValue = default luup.variable_set(serviceId, name, curValue, deviceId) end return curValue end local function setVariableIfChanged(serviceId, name, value, deviceId) debug(string.format("setVariableIfChanged(%s,%s,%s,%s)",serviceId, name, value, deviceId)) local curValue = luup.variable_get(serviceId, name, deviceId) or "" value = value or "" if (tostring(curValue)~=tostring(value)) then luup.variable_set(serviceId, name, value, deviceId) end end local function setAttrIfChanged(name, value, deviceId) debug(string.format("setAttrIfChanged(%s,%s,%s)",name, value, deviceId)) local curValue = luup.attr_get(name, deviceId) if ((value ~= curValue) or (curValue == nil)) then luup.attr_set(name, value, deviceId) return true end return value end local function run_scene(id) debug(string.format("run_scene(%s)",id or "nil")) local resultCode, resultString, job, returnArguments = luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", {SceneNum = tostring(id)}, 0) return resultCode, resultString, job, returnArguments end ------------------------------------------------ -- HOUSE MODE ------------------------------------------------ -- 1 = Home -- 2 = Away -- 3 = Night -- 4 = Vacation local HModes = { "Home", "Away", "Night", "Vacation" ,"Unknown" } local function setHouseMode( newmode ) debug(string.format("HouseMode, setHouseMode( %s )",newmode)) newmode = tonumber(newmode) if (newmode>=1) and (newmode<=4) then debug("SetHouseMode to "..newmode) luup.call_action('urn:micasaverde-com:serviceId:HomeAutomationGateway1', 'SetHouseMode', { Mode=newmode }, 0) end end local function getMode() debug("HouseMode, getMode()") -- local url_req = "http://" .. getIP() .. ":3480/data_request?id=variableget&DeviceNum=0&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&Variable=Mode" local url_req = "http://127.0.0.1:3480/data_request?id=variableget&DeviceNum=0&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&Variable=Mode" local req_status, req_result = luup.inet.wget(url_req) -- ISSUE WITH THIS CODE=> ONLY WORKS WITHIN GLOBAL SCOPE LUA, not in PLUGIN context -- debug("calling getMode()...") -- local req_result = luup.attr_get("Mode") -- debug("getMode() = "..req_result) req_result = tonumber( req_result or (#HModes+1) ) log(string.format("HouseMode, getMode() returns: %s, %s",req_result or "", HModes[req_result])) return req_result , HModes[req_result] end ------------------------------------------------ -- Get user_data ------------------------------------------------ local function getFirstUserData() local url_req = "http://127.0.0.1:3480/data_request?id=user_data&output_format=json" local req_status, req_result = luup.inet.wget(url_req) if (req_status~=0) then debug(string.format("getScriptContent(%s) failed, returns: %s",filename,req_status)) return "" end return req_result end ------------------------------------------------ -- Get File ( uncompress & return content ) ------------------------------------------------ local function getScriptContent( filename ) log("getScriptContent("..filename..")") local url_req = "http://127.0.0.1:3480/"..filename local req_status, req_result = luup.inet.wget(url_req) -- debug(string.format("getScriptContent(%s) returns: %s",filename,req_result)) if (req_status~=0) then debug(string.format("getScriptContent(%s) failed, returns: %s",filename,req_status)) return "" end return req_result end local function getDataFor( deviceID,name ) debug("getDataFor("..name..")") local name = "Data_"..name name = name:gsub(" ", "+") -- spaces are replaced by '+' local num = 0 local var = nil local result = "" -- search for all "Data_xxx_nnn" variables and concatenate them -- debug("reading "..name.."_"..num) var = luup.variable_get(ALTUI_SERVICE, name.."_"..num, deviceID) or "" -- debug("var =("..var..")") while( var ~= "") do num = num+1 result = result .. var -- debug("reading "..name.."_"..num) var = luup.variable_get(ALTUI_SERVICE, name.."_"..num, deviceID) or "" -- debug("var =("..var..")") end if (result=="") then return nil end debug("returning "..result) return result end ------------------------------------------------------------------------------------------------ -- Http handlers : Communication FROM ALTUI -- http://192.168.1.5:3480/data_request?id=lr_ALTUI_Handler&command=xxx -- recommended settings in ALTUI: PATH = /data_request?id=lr_ALTUI_Handler&mac=$M&deviceID=114 ------------------------------------------------------------------------------------------------ function switch( command, actiontable) -- check if it is in the table, otherwise call default if ( actiontable[command]~=nil ) then return actiontable[command] end log("ALTUI_Handler:Unknown command received:"..command.." was called. Default function") return actiontable["default"] end local htmlLocalScripts = [[ ]] -- local htmlScripts = [[ ]] -- local htmlStyle = [[ ]] local defaultBootstrapPath = "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" local htmlLocalCSSlinks = [[ ]] local htmlCSSlinks = [[ ]] local htmlLayout = [[ @csslinks@ @style@ VERA AltUI @mandatory_scripts@ @optional_scripts@
]] ------------------------------------------------ -- Watch Management ------------------------------------------------ local remoteWatches={} local registeredWatches = {} -- service#variable#deviceid#sceneid#lua_expr#blockly xml;service#variable#deviceid#sceneid#lua_expr#blockly xml local function setWatchParams(service,variable,deviceid,sceneid,lua,xml) return string.format("%s#%s#%s#%s#%s#%s",service,variable,deviceid,sceneid,lua,xml or '') end local function getWatchParams(str) local params = str:split("#") return params[1],params[2],params[3],tonumber(params[4]),params[5],params[6] end local function setRemoteWatchParams(service,variable,devid,ctrlid,ipaddr) return string.format("%s#%s#%s#%s#%s",service,variable,devid,ctrlid,ipaddr) end --local service,variable,devid,ctrlid,ipaddr = getRemoteWatchParams(str) local function getRemoteWatchParams(str) local params = str:split("#") return params[1],params[2],params[3],params[4],params[5] end -- service#variable#deviceid#provider#data line which is a template sprintf string for params -- urn:micasaverde-com:serviceId:SceneController1#LastSceneID#208#thingspeak#61186#U1F7T31MHB5O8HZI#key=U1F7T31MHB5O8HZI&field1=0#graphic url local function setPushParams(service,variable,deviceid,provider,channelid,readkey,data,url) return string.format("%s#%s#%s#%s#%s#%s#%s#%s",service,variable,deviceid,provider,channelid,readkey,data,url) end local function getPushParams(str) local params = str:split("#") return params[1],params[2],params[3],params[4],params[5],params[6],params[7],params[8] or "" end local function saveRemoteWatch(lul_device,service,variable,devid,ctrlid,ipaddr) debug(string.format("saveRemoteWatch(%s,%s,%s,%s,%s,%s)",lul_device,service,variable,devid,ctrlid,ipaddr)) local watchline = setRemoteWatchParams(service,variable,devid,ctrlid,ipaddr) local variableWatch = getSetVariable(ALTUI_SERVICE, "RemoteVariablesToWatch", lul_device, "") local bFound=false; for k,v in pairs(variableWatch:split(';')) do if (watchline==v) then bFound = true; end end if (bFound==false) then variableWatch = variableWatch .. ";" .. watchline luup.variable_set(ALTUI_SERVICE, "RemoteVariablesToWatch", variableWatch, lul_device) end end local function setRemoteWatch(service,variable,devid,ctrlid,ipaddr) debug(string.format("setRemoteWatch(%s,%s,%s,%s,%s)",service,variable,devid,ctrlid,ipaddr)) -- devid = tonumber(devid) if (remoteWatches[devid]==nil) then remoteWatches[devid]={} end if (remoteWatches[devid][service]==nil) then remoteWatches[devid][service]={} end if (remoteWatches[devid][service][variable]==nil) then remoteWatches[devid][service][variable]={ ["ctrlid"] = ctrlid, ["ipaddr"] = ipaddr } return true end return false end -- http://192.168.1.16/port_3480/data_request?id=lr_ALTUI_Handler&command=addRemoteWatch&device=112&variable=Status&service=urn:upnp-org:serviceId:SwitchPower1&data=192.168.1.16 -- http://192.168.1.5/port_3480/data_request?id=lr_ALTUI_Handler&command=addRemoteWatch&device=42&variable=Status&service=urn:upnp-org:serviceId:SwitchPower1&data=192.168.1.16 local function delRemoteWatch(lul_device,service,variable,devid,ctrlid,ipaddr) debug(string.format("delRemoteWatch(%s,%s,%s,%s,%s,%s)",lul_device,service,variable,devid,ctrlid,ipaddr)) local watchline = setRemoteWatchParams(service,variable,devid,ctrlid,ipaddr) local variableWatch = getSetVariable(ALTUI_SERVICE, "RemoteVariablesToWatch", lul_device, "") local toKeep = {} for k,v in pairs(variableWatch:split(';')) do if (v ~= watchline) then table.insert(toKeep,v) end end luup.variable_set(ALTUI_SERVICE, "RemoteVariablesToWatch", table.concat(toKeep,";"), lul_device) return "ok" end local function addRemoteWatch(lul_device,service,variable,devid,ctrlid,ipaddr) debug(string.format("addRemoteWatch(%s,%s,%s,%s,%s,%s)",lul_device,service,variable,devid,ctrlid,ipaddr)) if (setRemoteWatch(service,variable,devid,ctrlid,ipaddr)==true) then local parts = devid:split("-"); luup.variable_watch("remoteVariableWatchCallback", service,variable,tonumber(parts[2])) saveRemoteWatch(lul_device,service,variable,devid,ctrlid,ipaddr) else debug("Ignoring duplicate remote watch") end return "ok" end function findWatch( devid, service, variable ) local watch = nil devid = tostring(devid) debug(string.format("findWatch(%s,%s,%s)",devid, service, variable)) debug(string.format("registeredWatches: %s",json.encode(registeredWatches))) if (registeredWatches[devid] ~= nil) and (registeredWatches[devid][service] ~= nil) and (registeredWatches[devid][service][variable] ~= nil) then return registeredWatches[devid][service][variable] end warning(string.format("findWatch(%s,%s,%s) did not find a match",devid, service, variable)) return nil end function remoteVariableWatchCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) debug(string.format("remoteVariableWatchCallback(%s,%s,%s,%s,%s)",lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)) debug(string.format("remoteWatches:%s",json.encode(remoteWatches))) local altuiid = "0-"..lul_device local watch = remoteWatches[altuiid][lul_service][lul_variable] if (watch~=nil) then local url = string.format("http://%s/port_3480/data_request?id=lr_ALTUI_Handler&command=remoteWatchCB&service=%s&variable=%s&device=%d&old=%s&new=%s&ctrlid=%s", watch["ipaddr"], lul_service, lul_variable, lul_device, lul_value_old, lul_value_new, watch["ctrlid"] ) debug(string.format("calling url:%s",url)) local httpcode,data = luup.inet.wget(url,10) if (httpcode~=0) then error(string.format("failed to connect to url:%s, http.request returned %d", url,httpcode)) return 0 end debug(string.format("success httpcode:%s data:%s",httpcode,data)) return 1 else warning("ignoring watch CB because of unknown watch") end return 0 end function watchTimerCB(lul_data) debug(string.format("watchTimerCB(%s)",lul_data)) local tbl = lul_data:split('#') -- if the timer was cancelled, ignore local watch = findWatch( tbl[1], tbl[2], tbl[3] ) local expr = tbl[4] local tbl_expr = watch['Expressions'][expr] for k,v in pairs(watch['Expressions'][expr]) do if (v["PendingTimer"] == nil) then warning(string.format("Ignoring timer callback, timer was cancelled for index:%s expression %s",tostring(k),expr)) else -- otherwise cancel it now watch['Expressions'][expr][k]["PendingTimer"] = nil -- if we are here , nobody cancelled the timer so it is assumed the condition is true local scene = watch['Expressions'][expr][k]["SceneID"] local res = run_scene(scene) if (res==-1) then error(string.format("Failed to run the scene %s",scene)) end end end debug(string.format("updated watches %s",json.encode(registeredWatches))) end function myALTUI_LuaRunHandler(lul_request, lul_parameters, lul_outputformat) -- local oldlog = _G.log -- _G.log = luup.log -- local olddebug = _G.debug -- _G.debug = luup.log -- local oldwarning = _G.warning -- _G.warning = luup.log -- log('myALTUI_LuaRunHandler: request is: '..tostring(lul_request)) -- log('myALTUI_LuaRunHandler: parameters is: '..json.encode(lul_parameters)) -- log('myALTUI_LuaRunHandler: outputformat is: '..json.encode(lul_outputformat)) -- debug("hostname="..hostname) -- if (hostname=="") then -- hostname = getIP() -- debug("now hostname="..hostname) -- end -- local lua = lul_parameters["lua"] -- code,result,output = runLua(deviceID,lua) -- local res = string.format("%d||%s||%s",code,json.encode(result),output); -- _G.log = oldlog -- _G.debug = olddebug -- _G.warning = oldwarning res="1||all is ok||all is ok" return res, "text/plain" end function myALTUI_Handler(lul_request, lul_parameters, lul_outputformat) log('ALTUI_Handler: request is: '..tostring(lul_request)) log('ALTUI_Handler: parameters is: '..json.encode(lul_parameters)) log('ALTUI_Handler: outputformat is: '..json.encode(lul_outputformat)) local lul_html = ""; -- empty return by default local mime_type = ""; debug("hostname="..hostname) if (hostname=="") then hostname = getIP() debug("now hostname="..hostname) end -- find a parameter called "command" if ( lul_parameters["command"] ~= nil ) then command =lul_parameters["command"] else debug("ALTUI_Handler:no command specified, taking default") command ="default" end local deviceID = tonumber(lul_parameters["DeviceNum"] or findALTUIDevice() ) -- switch table local action = { -- ["isregistered"] = -- function(params) -- local success,response = isRegisteredSmartPhone(deviceID) -- local result = (success==true) and (response=="0") -- return json.encode( result ) -- end, ["home"] = function(params) local result = luup.variable_get(ALTUI_SERVICE, "PluginConfig", deviceID) local tbl = json.decode(result) tbl ["info"] = { ["ui7Check"] = luup.variable_get(ALTUI_SERVICE, "UI7Check", deviceID) or "", ["debug"] = DEBUG_MODE, ["PluginVersion"] = luup.variable_get(ALTUI_SERVICE, "Version", deviceID) or "", ["RemoteAccess"] = luup.variable_get(ALTUI_SERVICE, "RemoteAccess", deviceID) or "" } -- preload necessary scripts : optimization for remote access -- without this, ALTUI just dynamically load the script but it seems to take long time sometime local scripts = {} local loaded = {} local styles = {} local idx= 1 -- scripts[idx] = "J_ALTUI_utils.js" -- loaded[scripts[idx]]=true -- idx = idx+1 local lang="" if ( (lul_parameters["lang"]~=nil) and (lul_parameters["lang"]~="en") ) then lang = string.sub( (lul_parameters["lang"].." "),1,2) scripts[idx] = "J_ALTUI_loc_"..lang..".js" loaded[scripts[idx]]=true idx = idx+1 end for k,v in pairs(tbl) do if (v["ScriptFile"] ~= nil) then if (loaded[v["ScriptFile"]]~=true) then scripts[idx] = v["ScriptFile"] loaded[v["ScriptFile"]]=true idx = idx + 1 end if (v["StyleFunc"] ~= nil) then styles[v["ScriptFile"]] = v["StyleFunc"] end end end if (lang=="") then lang="en" end for k,v in pairs({"J_ALTUI_jquery.ui.touch-punch.min.js","J_ALTUI_b_blockly_compressed.js","J_ALTUI_b_blocks_compressed.js","J_ALTUI_b_"..lang..".js","J_ALTUI_b_javascript_compressed.js","J_ALTUI_b_lua_compressed.js"}) do scripts[idx] = v loaded[scripts[idx]]=true idx = idx+1 end -- scripts[idx] = "J_ALTUI_verabox.js" -- loaded[scripts[idx]]=true -- idx = idx+1 -- scripts[idx] = "J_ALTUI_multibox.js" -- loaded[scripts[idx]]=true -- idx = idx+1 -- scripts[idx] = "J_ALTUI_uimgr.js" -- loaded[scripts[idx]]=true -- idx = idx+1 local optional_scripts="" for i = 1,#scripts do local str = getScriptContent(scripts[i]) if (styles[scripts[i]] ~= nil) then str = str .. "_loadStyle('"..styles[scripts[i]].."');" end optional_scripts = optional_scripts .. string.format( "", scripts[i], "//\n" ) end local pagelist = getDataFor( deviceID, "CustomPages" ) or "{}" if (pagelist=="[]") then pagelist="{}" end local custompages_tbl = json.decode( pagelist ) local result_tbl ={} for k,v in pairs(custompages_tbl) do local data = getDataFor( deviceID, v ) table.insert( result_tbl , data ) end -- local custompages = luup.variable_get(ALTUI_SERVICE, "CustomPages", deviceID) or "[]" -- custompages = string.gsub(custompages,"'","\\x27") -- custompages = string.gsub(custompages,"\"","\\x22") local serverOptions= getSetVariable(ALTUI_SERVICE, "ServerOptions", deviceID, "") local localcdn = getSetVariable(ALTUI_SERVICE, "LocalCDN", deviceID, "") local localbootstrap = getSetVariable(ALTUI_SERVICE, "LocalBootstrap", deviceID, "") if (localbootstrap == "") then localbootstrap=defaultBootstrapPath end local variables={} variables["hostname"] = hostname variables["localcdn"] = localcdn variables["localbootstrap"] = localbootstrap variables["devicetypes"] = json.encode(tbl) variables["custompages"] = "["..table.concat(result_tbl, ",").."]" variables["ThemeCSS"] = luup.variable_get(ALTUI_SERVICE, "ThemeCSS", deviceID) or "" variables["ServerOptions"] = serverOptions variables["style"] = htmlStyle variables["mydeviceid"] = deviceID variables["extracontroller"] = getSetVariable(ALTUI_SERVICE, "ExtraController", deviceID, "") -- variables["firstuserdata"] = "{}" variables["firstuserdata"] = getFirstUserData() -- ( json.encode( getFirstUserData() ) -- :gsub("'", "\'") ) if (localcdn ~= "") then variables["csslinks"] = htmlLocalCSSlinks:template(variables) variables["mandatory_scripts"] = htmlLocalScripts:template(variables) else variables["csslinks"] = htmlCSSlinks:template(variables) variables["mandatory_scripts"] = htmlScripts end -- " becomes \x22 variables["optional_scripts"] = optional_scripts return htmlLayout:template(variables),"text/html" end, ["save_data"] = function(params) local name = lul_parameters["name"] local npage = lul_parameters["npage"] local data = lul_parameters["data"] debug(string.format("ALTUI_Handler: save_data( name:%s npage:%s)",name,npage)) local variablename = "Data_"..name.."_"..npage if (data=="") then debug(string.format("ALTUI_Handler: save_data( ) - Empty data",name,npage)) luup.variable_set(ALTUI_SERVICE, variablename, "", deviceID) return "ok", "text/plain" else debug(string.format("ALTUI_Handler: save_data( ) - Not Empty data",name,npage)) data = url_decode( data ) debug(string.format("ALTUI_Handler: save_data( ) - url decoded",name,npage)) luup.variable_set(ALTUI_SERVICE, variablename, data, deviceID) debug(string.format("ALTUI_Handler: save_data( ) - returns:%s",data)) return data, "text/plain" end end, ["clear_data"] = function(params) local name = lul_parameters["name"] local npage = lul_parameters["npage"] local variablename = "Data_"..name.."_"..npage -- cleanup all found data until we find local var = luup.variable_get(ALTUI_SERVICE, variablename, deviceID) while ((var ~= nil) and (var ~="" )) do luup.variable_set(ALTUI_SERVICE, variablename, "", deviceID) npage = npage + 1 variablename = "Data_"..name.."_"..npage var = luup.variable_get(ALTUI_SERVICE, variablename, deviceID) end return "ok", "text/plain" end, -- ["run_lua"] = -- function(params) -- local lua = lul_parameters["lua"] -- code,result,output = runLua(deviceID,lua) -- local res = string.format("%d||%s||%s",code,json.encode(result),output); -- return res, "text/plain" -- end, ["proxysoap"] = function(params) local newUrl = lul_parameters["newUrl"] local soapaction = lul_parameters["action"] local envelop= lul_parameters["envelop"] local body = lul_parameters["body"] code,result = proxySoap(deviceID,newUrl,soapaction,envelop,body) local res = string.format("%d,%s",code,result); return res, "text/plain" end, ["proxyget"] = function(params) local newUrl = lul_parameters["newUrl"] local resultName = lul_parameters["resultName"] code,result = proxyGet(deviceID,newUrl,resultName) local res = string.format("%d,%s",code,result); return res, "text/plain" end, ["delWatch"] = function(params) -- primary controller beeing called to set a watch local res = delWatch( deviceID, lul_parameters["service"], lul_parameters["variable"], lul_parameters["device"], tonumber(lul_parameters["scene"]), lul_parameters["expression"], lul_parameters["xml"], lul_parameters["provider"], lul_parameters["channelid"], lul_parameters["readkey"], lul_parameters["data"], lul_parameters["graphicurl"] ) return res, "text/plain" end, ["addWatch"] = function(params) -- primary controller beeing called to set a watch local res = addWatch( deviceID, lul_parameters["service"], lul_parameters["variable"], lul_parameters["device"], tonumber(lul_parameters["scene"]), lul_parameters["expression"], lul_parameters["xml"], lul_parameters["provider"], lul_parameters["channelid"], lul_parameters["readkey"], lul_parameters["data"], lul_parameters["graphicurl"] ) return res, "text/plain" end, ["addRemoteWatch"] = function(params) -- primary controller calling the secondary one to set a watch local device = "0-"..lul_parameters["device"] -- we receive a VERA device ID, we make a ALTUI ID local service = lul_parameters["service"] local variable = lul_parameters["variable"] local ctrlid = lul_parameters["ctrlid"] local ipaddr = lul_parameters["ipaddr"] local res = addRemoteWatch(deviceID,service,variable,device,ctrlid,ipaddr) return res, "text/plain" end, ["delRemoteWatch"] = function(params) -- primary controller calling the secondary one to set a watch local device = "0-"..lul_parameters["device"] -- we receive a VERA device ID, we make a ALTUI ID local service = lul_parameters["service"] local variable = lul_parameters["variable"] local ctrlid = lul_parameters["ctrlid"] local ipaddr = lul_parameters["ipaddr"] local res = delRemoteWatch(deviceID,service,variable,device,ctrlid,ipaddr) return res, "text/plain" end, ["remoteWatchCB"] = -- secondary controller calling back the primary controller with a watch result function(params) local lul_device = lul_parameters["device"] local lul_service = lul_parameters["service"] local lul_variable = lul_parameters["variable"] local lul_value_old = lul_parameters["old"] local lul_value_new = lul_parameters["new"] local ctrlid = lul_parameters["ctrlid"] variableWatchCallbackFromRemote(ctrlid, lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) return "ok", "text/plain" end, ["readtmp"] = function(params) -- local command = lul_parameters["oscommand"] -- local handle = io.popen(command) -- local result = handle:read("*a") -- handle:close() local filename = url_decode( lul_parameters["filename"] ) debug("opening file") local file = io.open(tmpprefix..filename,'r') local result = '' if file~=nil then result = "1,"..file:read("*a") file:close() else result = "0," end debug("returning result") return result , "text/plain" -- return json.encode( {success=(response==0 or response==true), result=result} ) , "application/json" end, ["oscommand"] = function(params) local resultcode="" local result = "" local command = url_decode( lul_parameters["oscommand"] ) local file = io.popen(command) if file then result = file:read("*a") file:close() resultcode = "1," else resultcode = "0," end -- local result = handle:read("*a") -- handle:close() -- local command = url_decode( lul_parameters["oscommand"] ) .. '> /tmp/oscommand.log' -- local response = os.execute(command) -- local file = io.open('/tmp/oscommand.log','r') -- local result = file:read("*a") -- local resultcode = "" -- file:close() -- if (response==0 or response==true) then -- resultcode="1," -- else -- resultcode="0," -- end return resultcode..result , "text/plain" end, -- return json.encode( {success=(response==0 or response==true), result=result} ) , "application/json" ["rooms"] = function(params) return json.encode( luup.rooms ) , "application/json" end, ["devices"] = function(params) return json.encode( luup.devices ) , "application/json" end, ["scenes"] = function(params) return json.encode( luup.scenes ), "application/json" end, ["image"] = function(params) local default_img="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAASJSURBVHja7Jp7aI5RHMff123CMOYyMmFY5LZYI5umFmHhD2pyyYzYkju5hCWX0jZKM9rEkCy5tJostxWRIteZe5FLyW2Y68z35Pfq9Os8z573eT3vu9fOr76d5zzn8jyf59x+57yvu6amxlWfrIGrnpkG1sAaWANrYA2sgTWwBnbKGnmT2e12/7MHb8vOaYhgEJQA9YN6Qj2g5lCoSFu4eNF1K3V5sx9o5M+vC0jxvCRoKjQOalmnW9gH0BYI5kKLoE5B06Vttug8KBMKqyX7S+g+9Ab6SGHwAAN2MIICqL9BlifQMegcdAHj9X1QtjBAxcy2BNoENWbJ1VARtAO6BMiaoO7SgG2C4AA0SZF8CFoDyMf/xRgGbCsExVA8S3oEzQJomUG5AQgSoSFQNNSZlqZ4q8uS34Hx0s0MYA+KSQsv/pHlD0eQQctTVFC1MDkQRQrYtQDdoOgFa6F0qGmwdun10Fh2Lx2wOxnseAS7ofZGDhP0DHoAVUJvnQB2e+OWcdcSEKMRnGTZlgN2K+sBWdACRZXfoBPQYeg8ytmC9IrBLjB5T+VQFynLXrz0TDZrC5gJrKrv0HYoG/lf+dpq/vKlMxnsbRqbcsuqYC9B0wH6MGi2h4CJRDCfjT+x9HyR7mUpYIXDkRAoWF9aeBXzovIAcUX6IBMVYzYTedZb+JghCCIo+gFl3gV00sILtcalGHchdPsr1B0v9lJaeiqgjlLRXKRnmED2QpAGjYH6iEdJyeJZp6FCEarcUW8Y7HTpKRKssD0eWLLVDPYqbQtVoGFQAX2gZVBfBuuiuoSDUgpdRv4Yf4/haSxewDyodLZZSMUH+a6AFXDCdUxVQBpZrJj0UHamX4DxoDb0UI/dAsw1KZ5KfrDH9iP9pqKe3mLdhSJtvLNY6vbYhfa2hRNZmRKWPoPFtxhMSkehcJb0ArpRi2THJA91DXR6lo5j8dMSSFeacDx2Ea17T1HHQpbPRSccscj/3KR3tUVwl7V0LjTMyRaOZnG5O49gacUGrbtUUe8KM1iyHKgduzcUdSY62cK9pOvXzPftx/JeUJRPUnRl8dEO03L3t8VRd7X0oUYpJkuPpdAxkSPAHaTrpyytG4uXK8onKO7FsAM74YWJQ4EqyWffZfJO8U526VA27mRrK13/NPCQult4xmyUrZLiG6GuJvmjnOzS8oa+QnG6USZ5XyprVkv9wiM7L3XlOOaz+8zgVWYzXxhp+Raq+GSSJjb/K9kEl2/BKfkRkEM8i3bfJC0NH61SioufYdawPJsVK0V5XQY+S742t32ALWU95jWC4+yIKFpRtszx/bAPVqaY3V+RM2Lm0rYkJ0NlhX4707J5eDCHLTPF1PJmNhJKVtwvQU8YW2d/LiXLJydiOMWTDWBqs0oLM3jAu7QYm78QTHb9+UXCromZOcXOzzYB+csDHRiMoMMBb004NMmoo8RfBwD/Cvo57XTWQZ8tFjsi3E6UPeW3My0njDYOU+hMS/jWEZL7egc6Q4cJqu2mcwfx/4Pp/2lpYA2sgTWwBtbAGlgDO2W/BRgADRV6RjlErQoAAAAASUVORK5CYII=" local imgpath = lul_parameters["path"] -- get the extension local i = string.find(imgpath,".",-5) debug("find last dot at position i="..i) i=i+2 debug("i="..i) local extension = imgpath:sub( i ) debug("extension="..extension) -- build the local IO pathname i= string.find(imgpath,"/cmh") local webpath = imgpath:sub( i ) -- build physical file name local physicalpath = "/www"..webpath; log( string.format("extension:%s webpath:%s physicalpath:%s",extension,webpath,physicalpath) ) -- read the file debug(string.format("opening %s",physicalpath)) local file = io.open(physicalpath, "r") if (file==nil) then log("opening ".. physicalpath .." returns nil, returning default image") return default_img end local content = file:read("*all") file:close() debug(string.format("closing %s",physicalpath)) -- encode in B64 local b64 = mime.b64(content) debug(string.format("b64 %s",b64)) return string.format("data:image/%s;base64,%s",extension,b64) , "image/"..extension --return "data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7" end, ["devicetypes"] = function(params) local result = luup.variable_get(ALTUI_SERVICE, "PluginConfig", deviceID) local tbl = json.decode(result) tbl ["info"] = { ["debug"] = DEBUG_MODE, ["ui7Check"] = luup.variable_get(ALTUI_SERVICE, "UI7Check", deviceID) or "", ["PluginVersion"] = luup.variable_get(ALTUI_SERVICE, "Version", deviceID) or "", ["RemoteAccess"] = luup.variable_get(ALTUI_SERVICE, "RemoteAccess", deviceID) or "" } return json.encode(tbl), "application/json" end, -- ["set_attribute"] = -- function(params) -- local attr = lul_parameters["attr"] -- local value = lul_parameters["value"] -- local devid = lul_parameters["devid"] -- luup.attr_set(attr , value, devid) -- return "ok" -- end, ["default"] = function(params) return "not successful", "text/plain" end } -- actual call lul_html , mime_type = switch(command,action)(lul_parameters) debug(string.format("lul_html:%s",lul_html or "")) return (lul_html or "") , mime_type end ------------------------------------------------ -- RESET ALTUI COnfig ------------------------------------------------ local function getDefaultConfig() local tbl = {} tbl["urn:schemas-upnp-org:device:BinaryLight:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawBinaryLight", ["StyleFunc"]="ALTUI_PluginDisplays.getStyle", -- ["ControlPanelFunc"]="ALTUI_PluginDisplays.drawBinLightControlPanel", } tbl["urn:schemas-upnp-org:device:RGBController:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawBinaryLight", } tbl["urn:antor-fr:device:SamsungTVRemote:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawBinaryLight", } tbl["urn:schemas-micasaverde-com:device:DoorLock:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDoorLock", } tbl["urn:schemas-micasaverde-com:device:DoorSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDoorSensor", } tbl["urn:schemas-micasaverde-com:device:TemperatureSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawTempSensor", } tbl["urn:schemas-upnp-org:device:Heater:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawHeater", } tbl["urn:schemas-upnp-org:device:HVAC_ZoneThermostat:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawZoneThermostat", } tbl["urn:schemas-micasaverde-com:device:HumiditySensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawHumidity", } tbl["urn:schemas-micasaverde-com:device:LightSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawLight", } tbl["urn:schemas-cd-jackson-com:device:DataMine:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDataMine", } tbl["urn:schemas-a-lurker-com:device:InfoViewer:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawInfoViewer", } tbl["urn:demo-micasaverde-com:device:weather:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawWeather", ["DeviceIconFunc"]="ALTUI_PluginDisplays.drawWeatherIcon", } tbl["urn:schemas-upnp-org:device:DimmableLight:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDimmable", } tbl["urn:schemas-upnp-org:device:DimmableRGBLight:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDimmableRGB", } tbl["urn:schemas-upnp-org:device:DimmableRGBLight:2"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDimmableRGB", } tbl["urn:schemas-micasaverde-com:device:MotionSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawMotion", } tbl["urn:schemas-micasaverde-com:device:SmokeSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawSmoke", } tbl["urn:schemas-micasaverde-com:device:WindowCovering:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawWindowCover", } tbl["urn:schemas-micasaverde-com:device:PowerMeter:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawPowerMeter", } tbl["urn:schemas-micasaverde-com:device:PowerMeter:2"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawPowerMeter", } tbl["urn:schemas-upnp-org:device:VSwitch:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawVswitch", } tbl["urn:schemas-upnp-org:device:DigitalSecurityCamera:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawCamera", } tbl["urn:schemas-upnp-org:device:DigitalSecurityCamera:2"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawCamera", } tbl["urn:schemas-upnp-org:device:cplus:1"]= { ["ScriptFile"]="J_ALTUI_iphone.js", ["DeviceDrawFunc"]="ALTUI_IPhoneLocator.drawCanalplus", ["ControlPanelFunc"]="ALTUI_IPhoneLocator.drawCanaplusControlPanel" } tbl["urn:schemas-upnp-org:device:altui:1"]= { ["ScriptFile"]="J_ALTUI_iphone.js", ["DeviceDrawFunc"]="ALTUI_IPhoneLocator.drawAltUI", } tbl["urn:schemas-futzle-com:device:holidayvirtualswitch:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawVacation", } tbl["urn:schemas-futzle-com:device:CountdownTimer:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawCountDown", } tbl["urn:schemas-upnp-org:device:IPhoneLocator:1"]= { ["ScriptFile"]="J_ALTUI_iphone.js", ["DeviceDrawFunc"]="ALTUI_IPhoneLocator.drawIPhone", ["StyleFunc"]="ALTUI_IPhoneLocator.getStyle", -- ["ControlPanelFunc"]="ALTUI_IPhoneLocator.drawControlPanel", } tbl["urn:schemas-upnp-org:device:IPX800:1"]= { ["ScriptFile"]="J_ALTUI_iphone.js", ["DeviceDrawFunc"]="ALTUI_IPhoneLocator.drawIPX" -- ["ControlPanelFunc"]="ALTUI_IPhoneLocator.drawControlPanel", } tbl["urn:schemas-rts-services-com:device:ProgramLogicEG:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawPLEG", -- ["ControlPanelFunc"]="ALTUI_IPhoneLocator.drawControlPanel", } tbl["urn:schemas-utz-com:device:GCal:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawGCal" } tbl["urn:schemas-futzle-com:device:CombinationSwitch:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawCombinationSwitch" } tbl["urn:schemas-rts-services-com:device:DayTime:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawDayTime" } tbl["urn:schemas-micasaverde-com:device:Sonos:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawSonos" } tbl["urn:schemas-cd-jackson-com:device:SystemMonitor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawSysMonitor" } tbl["urn:richardgreen:device:VeraAlert:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawVeraAlerts" } tbl["urn:schemas-micasaverde-com:device:TempLeakSensor:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawTempLeak" } tbl["urn:schemas-upnp-org:device:VContainer:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawMultiString" } tbl["urn:schemas-futzle-com:device:UPnPProxy:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawPnPProxy" } tbl["urn:schemas-rts-services-com:device:ProgramLogicTS:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawProgLogicTimerSwitch" } tbl["urn:schemas-arduino-cc:device:arduino:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawMySensors" } tbl["urn:schemas-dcineco-com:device:MSwitch:1"]= { ["ScriptFile"]="J_ALTUI_plugins.js", ["DeviceDrawFunc"]="ALTUI_PluginDisplays.drawMultiswitch" } return tbl end function resetDevice(lul_device,norepeat) lul_device = tonumber(lul_device) log(string.format("resetDevice(%d,%s)",lul_device, tostring(norepeat or "nil"))) -- reset the config local tbl = getDefaultConfig() local default = json.encode( tbl ) setVariableIfChanged(ALTUI_SERVICE, "PluginConfig", default, lul_device) debug(string.format("Reseting ALTUI config to %s",default)) end function registerPlugin(lul_device,newDeviceType,newScriptFile,newDeviceDrawFunc,newStyleFunc,newDeviceIconFunc,newControlPanelFunc) newScriptFile = newScriptFile or "" newDeviceDrawFunc = newDeviceDrawFunc or "" newStyleFunc = newStyleFunc or "" newDeviceIconFunc = newDeviceIconFunc or "" newControlPanelFunc = newControlPanelFunc or "" log(string.format("registerPlugin(%d,%s,%s,%s,%s,%s,%s)",lul_device,newDeviceType,newScriptFile,newDeviceDrawFunc,newStyleFunc,newDeviceIconFunc,newControlPanelFunc)) if (newDeviceType ~= "") then local tbljson = getSetVariable(ALTUI_SERVICE, "PluginConfig", lul_device, json.encode( getDefaultConfig() ) ) local tbl = json.decode(tbljson) if (tbl[newDeviceType] == nil) then tbl[newDeviceType]={} end for k,v in pairs({ ["ScriptFile"]=newScriptFile,["DeviceDrawFunc"]=newDeviceDrawFunc,["StyleFunc"]=newStyleFunc,["DeviceIconFunc"]=newDeviceIconFunc,["ControlPanelFunc"]=newControlPanelFunc}) do if (v~="") then tbl[newDeviceType][k]=v end end setVariableIfChanged(ALTUI_SERVICE, "PluginConfig", json.encode( tbl ), lul_device) else debug("Ignored, empty device type") end end ------------------------------------------------ -- User Level API functions for watches ------------------------------------------------ function trueSince(cond,delay) delay = delay or 0 debug(string.format("sinceWatch(%s,%d)",tostring(cond),delay)) return cond,delay end function midnight(timestamp) local t = os.date('*t',timestamp) t.hour=0 t.min=0 t.sec=0 return os.time(t) end function timeOf(timestamp) local t2= midnight(timestamp) return os.difftime(timestamp,t2) end function _evaluateUserExpression(lul_device, lul_service, lul_variable,old,new,lastupdate,expr) debug(string.format("_evaluateUserExpression(%s,%s,%s,%s,%s,%s,%s)",lul_device, lul_service, lul_variable,old,new,tostring(lastupdate),expr)) local results = {} local code = [[ return function(lul_device, lul_service, lul_variable, expr) local old='%s' local new='%s' local lastupdate=%s local now=os.time() local results= {%s} -- eventually returns 2 results, cond and delay return results end ]] code = string.format(code,old,new,lastupdate,expr) local f,msg = loadstring(code) if (f==nil) then error(string.format("loadstring %s failed to compile, msg=%s",code,msg)) else local func = f() -- call it results = func(lul_device, lul_service, lul_variable,expr) debug(string.format("Evaluation of user watch expression returned: %s",json.encode(results))) end return results end --https://api.thingspeak.com/update?key=U1F7T31MHB5O8HZI&field1=0 function sendValueToStorage(watch,lul_device, lul_service, lul_variable,old, new, lastupdate) debug(string.format("sendValueToStorage(%s,%s,%s,%s,%s,%s)",lul_device, lul_service, lul_variable,old, new, lastupdate)) for provider,v in pairs(watch['DataProviders']) do local n = tablelength(watch['DataProviders'][provider]) for i=1,n do local template = v[i]['Data'] if (isempty(template==nil)==false) then local data = string.format(template,new) local response_body = {} debug(string.format("Provider:%s Url:%s",provider,data)) local response, status, headers = https.request{ method="POST", url="https://api.thingspeak.com/update", headers = { ["Content-Type"] = "application/x-www-form-urlencoded", ["Content-Length"] = string.len(data), -- ["X-THINGSPEAKAPIKEY"] = api_write_key }, source = ltn12.source.string(data), sink = ltn12.sink.table(response_body) } debug("https Response=" .. json.encode({res=response,sta=status,hea=headers}) ) return response or 0 end end end return 0 end function evaluateExpression(lul_device, lul_service, lul_variable,expr,old, new, lastupdate, exp_index, scene) debug(string.format("evaluateExpression(%s,%s,%s,%s,%s,%s,%s,%s,%s)",lul_device, lul_service, lul_variable,expr,old, new, tostring(lastupdate),exp_index,scene)) local watch = findWatch( lul_device, lul_service, lul_variable ) if (watch==nil) then return end local results = _evaluateUserExpression(lul_device, lul_service, lul_variable,old,new,lastupdate,expr) local res,delay = results[1], results[2] or nil -- if it evaluates as FALSE , do not do anything & cancel timer if (res==nil or res==false or tonumber(res)==0) then debug(string.format("ignoring watch trigger, loadstring returned %s",tostring(res))) -- cancelling the timer for that expression as the condition is false now before the timer expired watch['Expressions'][expr][exp_index]["PendingTimer"] = nil else -- if it evaluates as TRUE, if (delay ~=nil ) then -- if it is a defered response, if (watch['Expressions'][expr][exp_index]["PendingTimer"]==nil) then -- if new timer local tbl = {lul_device, lul_service, lul_variable,expr} local timer = luup.call_delay("watchTimerCB",delay, table.concat(tbl, "#") ) or 1 if (timer==0) then debug("preparing timer watchTimerCB with delay "..delay) watch['Expressions'][expr][exp_index]["PendingTimer"]=1 else error("luup.call_delay failed !") watch['Expressions'][expr][exp_index]["PendingTimer"]=nil end else -- already a running timer, still true, do nothing wait for the timer debug("already a running timer, still true, do nothing wait for the timer") end else -- if it is a immediate response, then run the scene if (scene ~= -1 ) then local scene_res = run_scene(scene) if (scene_res==-1) then error(string.format("Failed to run the scene %s",scene)) end end end end debug(string.format("evaluateExpression() returns %s",tostring(res))) return res end function _internalVariableWatchCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) debug(string.format("_internalVariableWatchCallback(%s,%s,%s,old:'%s',new:'%s')",lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)) local watch = findWatch( lul_device, lul_service, lul_variable ) if (watch==nil) or (watch['Expressions']==nil )then warning(string.format("ignoring unexpected watch callback, variableWatchCallback(%s,%s,%s,old:'%s',new:'%s')",lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)) return else watch["LastOld"] = lul_value_old watch["LastNew"] = lul_value_new watch["LastUpdate"] = os.time() debug(string.format("-----> evaluateExpression() %s",json.encode(watch['Expressions'] ))) for k,v in pairs(watch['Expressions'] or {}) do -- watch['Expressions'][k] is a table of object { ["LastEval"] = nil, ["SceneID"] = scene } -- v is an object for exp_index,target in ipairs(watch['Expressions'][k]) do watch['Expressions'][k][exp_index]["LastEval"] = evaluateExpression(lul_device, lul_service, lul_variable,k,lul_value_old, lul_value_new, watch["LastUpdate"], exp_index, target["SceneID"]) debug(string.format(">>>evaluated %s, index:%s LastEval:%s",k,exp_index,tostring(watch['Expressions'][k][exp_index]["LastEval"]))) end end debug(string.format("-----> DataProviders() %s",json.encode(watch['DataProviders']))) for k,v in pairs(watch['DataProviders'] or {}) do debug(string.format("Data Provider watch k:%s v:%s",k,json.encode(v))) sendValueToStorage(watch,lul_device, lul_service, lul_variable,lul_value_old, lul_value_new, watch["LastUpdate"]) end end debug(string.format("registeredWatches: %s",json.encode(registeredWatches))) end function variableWatchCallbackFromRemote(ctrlid, lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) lul_device = tostring(ctrlid).."-"..lul_device _internalVariableWatchCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) end function variableWatchCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) lul_device = "0-"..lul_device _internalVariableWatchCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) end function _addWatch( service, variable, devid, scene, expression, xml, provider, channelid, readkey, data, graphicurl ) debug(string.format("_addWatch(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",service, variable, devid, scene, expression, xml or "", provider or "", channelid or "", readkey or "", data or "", graphicurl or "")) local result = 1 devidstr = tostring(devid) -- to inssure it is not a indexed array , but hash table local parts = devidstr:split("-") if (parts[2]==nil) then devidstr = "0-"..devidstr parts = devidstr:split("-") end local bDuplicateWatch = false if (registeredWatches[devidstr] == nil) then registeredWatches[devidstr]={} end if (registeredWatches[devidstr][service] == nil) then registeredWatches[devidstr][service]={} end if (registeredWatches[devidstr][service][variable] == nil) then registeredWatches[devidstr][service][variable] = { ["LastOld"] = nil, ["LastNew"] = nil, ["LastUpdate"] = nil } else -- a watch was already there bDuplicateWatch = true end if (registeredWatches[devidstr][service][variable]['Expressions'] == nil) then registeredWatches[devidstr][service][variable]['Expressions']={} end if (registeredWatches[devidstr][service][variable]['Expressions'][expression] == nil) then registeredWatches[devidstr][service][variable]['Expressions'][expression] = {} end local n = tablelength(registeredWatches[devidstr][service][variable]['Expressions'][expression]) if (scene==-1) then registeredWatches[devidstr][service][variable]['Expressions'][expression][1]= { ["LastEval"] = nil, ["SceneID"] = scene } if (provider=="thingspeak") and ( isempty(data)==false ) then if (registeredWatches[devidstr][service][variable]['DataProviders'] == nil) then registeredWatches[devidstr][service][variable]['DataProviders']={} end if (registeredWatches[devidstr][service][variable]['DataProviders'][provider] == nil) then registeredWatches[devidstr][service][variable]['DataProviders'][provider]={} end local n2 = tablelength(registeredWatches[devidstr][service][variable]['DataProviders'][provider]) local bFound = false for i=1,n2 do if (registeredWatches[devidstr][service][variable]['DataProviders'][provider][i]['Data']==data) then bFound = true end end if (bFound==false) then registeredWatches[devidstr][service][variable]['DataProviders'][provider][n2+1] = { ['Data']=data } end else warning(string.format("Unknown data push provider:%s data:%s",provider or"", data or "")) return 0 end else local bFound = false for i=1,n do if (registeredWatches[devidstr][service][variable]['Expressions'][expression][i]["SceneID"] == scene ) then bFound = true end end if (bFound==false) then registeredWatches[devidstr][service][variable]['Expressions'][expression][n+1]= { ["LastEval"] = nil, ["SceneID"] = scene } end end if (bDuplicateWatch==true) then debug(string.format("Ignoring luup.variable_watch for duplicate watch for %s-%s",service,variable)) result = 0 else if (parts[1]=="0") then -- Master Controller luup.variable_watch("variableWatchCallback", service,variable,tonumber(parts[2])) else -- Secondary Controller local extraController= getSetVariable(ALTUI_SERVICE, "ExtraController", lul_device, "") local controllers = extraController:split(",") local ipaddr = controllers [ tonumber(parts[1]) ]:trim() local url = string.format("http://%s/port_3480/data_request?id=lr_ALTUI_Handler&command=addRemoteWatch&device=%s&variable=%s&service=%s&ctrlid=%s&ipaddr=%s", ipaddr, -- remote ctrl ip addr parts[2], -- pure vera device id on remote controller variable, service, parts[1], -- controller id for ALTUI getIP() -- local IP address for callback ) debug(string.format("Calling url to set remote watch. url:%s",url)) local httpcode,data = luup.inet.wget(url,10) if (httpcode~=0) then error(string.format("failed to connect to url:%s, http.request returned %d", url,httpcode)) return 0 end debug(string.format("success httpcode:%s data:%s",httpcode,data)) end end debug(string.format("registeredWatches: %s",json.encode(registeredWatches))) return result end function addWatch( lul_device, service, variable, deviceid, sceneid, expression, xml, provider, channelid, readkey, data, graphicurl ) debug(string.format("addWatch(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",lul_device, service, variable, deviceid, sceneid, expression, xml or "", provider or "", channelid or "", readkey or "", data or "", graphicurl or "")) -- 1/ Add Watch in database graphicurl = graphicurl or "" local newwatch = _addWatch( service, variable, deviceid, sceneid, expression, xml, provider, channelid, readkey, data, graphicurl ) -- 2/ Add Watch in persistence list ( device variable ) if (sceneid ~=-1) then -- classic watch local watchline = setWatchParams(service,variable,deviceid,sceneid,expression,xml) debug(string.format("searching if watchline already exists: %s",watchline)) local variableWatch = getSetVariable(ALTUI_SERVICE, "VariablesToWatch", lul_device, "") local bFound = false; for k,v in pairs(variableWatch:split(';')) do local wservice,wvariable,wdevice,wscene,wexpression,wxml = getWatchParams(v) if (service==wservice) and (variable==wvariable) and (deviceid==wdevice) and (sceneid==wscene) and (expression==wexpression) and (xml==wxml) then bFound = true; end end if (bFound==false) then debug(string.format("no, adding watchline %s",watchline)) variableWatch = variableWatch .. ";" .. watchline luup.variable_set(ALTUI_SERVICE, "VariablesToWatch", variableWatch, lul_device) else debug(string.format("yes, found an existing watchline")) end else -- data push watch local watchline = setPushParams(service,variable,deviceid,provider,channelid,readkey,data,graphicurl) debug(string.format("searching if watchline already exists: %s",watchline)) local variableWatch= getSetVariable(ALTUI_SERVICE, "VariablesToSend", lul_device, "") local bFound = false; for k,v in pairs(variableWatch:split(';')) do local wservice,wvariable,wdevice,wprovider,wchannelid,wreadkey,wdata,wgraphicurl = getPushParams(v) if (service==wservice) and (variable==wvariable) and (deviceid==wdevice) and (provider==wprovider) and (channelid==wchannelid) and (readkey==wreadkey) and (data==wdata) and (graphicurl==wgraphicurl) then bFound = true; end end if (bFound==false) then debug(string.format("no, adding watchline %s",watchline)) variableWatch = variableWatch .. ";" .. watchline luup.variable_set(ALTUI_SERVICE, "VariablesToSend", variableWatch, lul_device) end end return tostring(newwatch) end function _delWatch(service, variable, deviceid, sceneid, expression, xml, provider, channelid, readkey, data, graphicurl ) debug(string.format("_delWatch(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",service, variable, deviceid, sceneid, expression, xml or "", provider or "", channelid or "", readkey or "", data or "", graphicurl or "")) devidstr = tostring(deviceid) -- to inssure it is not a indexed array , but hash table local removed=0 local parts = devidstr:split("-") if (parts[2]==nil) then devidstr = "0-"..devidstr parts = devidstr:split("-") end -- registeredWatches[devidstr][service][variable]['Expressions'][expression][n+1] -- local n = tablelength(registeredWatches[devidstr][service][variable]['Expressions'][expression]) if (registeredWatches[devidstr] ~= nil) and (registeredWatches[devidstr][service] ~= nil) and (registeredWatches[devidstr][service][variable] ~= nil) and (registeredWatches[devidstr][service][variable]['Expressions'][expression] ~= nil) then if (sceneid==-1) then -- watch for push -- if (registeredWatches[devidstr][service][variable]['DataProviders']DataProviders[i]['Data']==data) then local n = tablelength(registeredWatches[devidstr][service][variable]['DataProviders'][provider]) for i=n,1,-1 do if (registeredWatches[devidstr][service][variable]['DataProviders'][provider][i]['Data']==data) then table.remove(registeredWatches[devidstr][service][variable]['DataProviders'][provider],i) removed = removed +1 end end if (removed == n) then registeredWatches[devidstr][service][variable]['DataProviders'][provider]=nil end else -- watch for scene local n = tablelength(registeredWatches[devidstr][service][variable]['Expressions'][expression]) for i=n,1,-1 do if (registeredWatches[devidstr][service][variable]['Expressions'][expression][i]["SceneID"] == sceneid) then table.remove(registeredWatches[devidstr][service][variable]['Expressions'][expression], i) removed = removed +1 end end if (removed == n) then registeredWatches[devidstr][service][variable]['Expressions'][expression]=nil end end if (removed==0) then warning("did not removed anything") end end if (parts[1] ~= "0") then -- Remote Watch local extraController= getSetVariable(ALTUI_SERVICE, "ExtraController", lul_device, "") local controllers = extraController:split(",") local ipaddr = controllers [ tonumber(parts[1]) ]:trim() local url = string.format("http://%s/port_3480/data_request?id=lr_ALTUI_Handler&command=delRemoteWatch&device=%s&variable=%s&service=%s&ctrlid=%s&ipaddr=%s", ipaddr, -- remote ctrl ip addr parts[2], -- pure vera device id on remote controller variable, service, parts[1], -- controller id for ALTUI getIP() -- local IP address for callback ) debug(string.format("Calling url to delete remote watch. url:%s",url)) local httpcode,data = luup.inet.wget(url,10) if (httpcode~=0) then error(string.format("failed to connect to url:%s, http.request returned %d", url,httpcode)) return 0 end debug(string.format("success httpcode:%s data:%s",httpcode,data)) end return removed end function delWatch( lul_device, service, variable, deviceid, sceneid, expression, xml, provider, channelid, readkey, data, graphicurl ) debug(string.format("delWatch(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",lul_device, service, variable, deviceid, sceneid, expression, xml or "", provider or "", channelid or "", readkey or "", data or "", graphicurl or "")) -- remove from DB and call the remote controller to remove the watch too graphicurl = graphicurl or "" local removed = _delWatch(service, variable, deviceid, sceneid, expression, xml, provider, channelid, readkey, data, graphicurl ) -- remove from persistent list if (sceneid ~=-1) then -- classic watch local watchline = setWatchParams(service,variable,deviceid,sceneid,expression,xml) debug(string.format("Watch to delete: %s",watchline)) local variableWatch = getSetVariable(ALTUI_SERVICE, "VariablesToWatch", lul_device, "") local toKeep = {} for k,v in pairs(variableWatch:split(';')) do local wservice,wvariable,wdevice,wscene,wexpression,wxml = getWatchParams(v) if (service~=wservice) or (variable~=wvariable) or (deviceid~=wdevice) or (sceneid~=wscene) or (expression~=wexpression) or (xml~=wxml) then debug(string.format("Keeping this watch: %s",v)) table.insert(toKeep, v) end end luup.variable_set(ALTUI_SERVICE, "VariablesToWatch", table.concat(toKeep,";"), lul_device) else -- data push watch local watchline = setPushParams(service,variable,deviceid,provider,channelid,readkey,data,graphicurl) debug(string.format("Watch to delete: %s",watchline)) local variableWatch= getSetVariable(ALTUI_SERVICE, "VariablesToSend", lul_device, "") local toKeep = {} for k,v in pairs(variableWatch:split(';')) do local wservice,wvariable,wdevice,wprovider,wchannelid,wreadkey,wdata,wgraphicurl = getPushParams(v) if not((service==wservice) and (variable==wvariable) and (deviceid==wdevice) and (provider==wprovider) and (channelid==wchannelid) and (readkey==wreadkey) and (data==wdata) and (graphicurl==wgraphicurl)) then debug(string.format("Keeping this watch: %s",v)) table.insert(toKeep, v) end end luup.variable_set(ALTUI_SERVICE, "VariablesToSend", table.concat(toKeep,";"), lul_device) end debug(string.format("registeredWatches: %s",json.encode(registeredWatches))) debug(string.format("delWatch() returns: %d",removed)) return tostring(removed) end function fixVariableWatches( lul_device ) debug(string.format("fixVariableWatches(%s)",lul_device )) debug(string.format("fixVariableWatches(%s)",lul_device )) local strings = { ["VariablesToWatch"]=getSetVariable(ALTUI_SERVICE, "VariablesToWatch", lul_device, ""), ["VariablesToSend"]=getSetVariable(ALTUI_SERVICE, "VariablesToSend", lul_device, ""), ["RemoteVariablesToWatch"]=getSetVariable(ALTUI_SERVICE, "RemoteVariablesToWatch", lul_device, "") } for k,v in pairs(strings) do local watches = v:split(";") for k2,v2 in pairs(watches) do local parts = v2:split('#') local deviceid = parts[3] if (string.find(deviceid,"-") == nil) then parts[3] = "0-"..deviceid end watches[k2] = table.concat(parts,"#") end luup.variable_set(ALTUI_SERVICE, k, table.concat(watches,";"), lul_device) end end function initVariableWatches( lul_device ) debug(string.format("initVariableWatches(%s)",lul_device )) local variableWatchString = getSetVariable(ALTUI_SERVICE, "VariablesToWatch", lul_device, "") -- service#variable#deviceid#sceneid;service#variable#deviceid#sceneid local dataPushString= getSetVariable(ALTUI_SERVICE, "VariablesToSend", lul_device, "") -- service#variable#deviceid#providername; ... local remoteVariableWatch = getSetVariable(ALTUI_SERVICE, "RemoteVariablesToWatch", lul_device, "") local watches = variableWatchString:split(";") -- local toKeep = {} for k,v in pairs(watches) do if (v~="") then local service,variable,device,scene,expression,xml = getWatchParams(v) _addWatch( service,variable,device,scene,expression ) -- table.insert(toKeep, setWatchParams(service,variable,device,scene,expression,xml or "") ) end end -- luup.variable_set(ALTUI_SERVICE, "VariablesToWatch", table.concat(toKeep,";"), lul_device) -- urn:micasaverde-com:serviceId:SceneController1#LastSceneID#208#thingspeak#key=U1F7T31MHB5O8HZI&field1=0 watches = dataPushString:split(";") -- toKeep = {} for k,v in pairs(watches) do local service,variable,device,provider,channelid,readkey,data,graphicurl = getPushParams(v) _addWatch( service, variable, device, -1, "true", "", provider, channelid, readkey, data,graphicurl ) -- table.insert(toKeep, setPushParams(service,variable,device,provider,channelid,readkey,data,graphicurl or "") ) end -- luup.variable_set(ALTUI_SERVICE, "VariablesToSend", table.concat(toKeep,";"), lul_device) watches = remoteVariableWatch:split(";") -- toKeep = {} for k,v in pairs(watches) do local service,variable,device,ctrlid,ipaddr = getRemoteWatchParams(v) addRemoteWatch(lul_device,service,variable,device,ctrlid,ipaddr) end -- luup.variable_set(ALTUI_SERVICE, "RemoteVariablesToWatch", table.concat(toKeep,";"), lul_device) end ------------------------------------------------ -- THINGSPEAK integration ------------------------------------------------ -- data : https://thingspeak.com/docs/channels#api_keys function sendToDataStorage(api_write_key,data) require('ltn12') local socket = require("socket") local http = require("socket.http") local base_url = "http://api.thingspeak.com/update" local method = "POST" local response_body = {} local response, status, header = http.request{ method = method, url = base_url, headers = { ["Content-Type"] = "application/x-www-form-urlencoded", ["Content-Length"] = string.len(data), ["X-THINGSPEAKAPIKEY"] = api_write_key }, source = ltn12.source.string(data), sink = ltn12.sink.table(response_body) } return response end ------------------------------------------------ -- STARTUP Sequence ------------------------------------------------ function registerHandlers() luup.register_handler("myALTUI_Handler","ALTUI_Handler") -- luup.register_handler('ALTUI_LuaRunHandler','ALTUI_LuaRunHandler') local code = [[ -- local altuijson = require("L_ALTUIjson") local printResult = {} local function myPrint (...) local arg = {} for i = 1, select("#", ...) do local x = select(i, ...) arg[i] = tostring(x) end table.insert (printResult, table.concat (arg, " \t")) end local function pretty (Lua, cfg) -- 2015.11.29 @akbooer cfg = type (cfg) == "table" and cfg or {} local tab = (' '):rep (cfg.tab or 2) -- for line indent local enc = {} -- set of tables currently being encoded (to avoid infinite self-reference loop) local con = table.concat local function ctrl(y) return ("\\%03d"): format (y:byte ()) end -- deal with escapes, etc. local function str_obj(x) return con {'"', x:gsub ("[\001-\031]", ctrl), '"'} end local function brk_idx(x) return con {'[', x, ']'} end local function str_idx(x) return x:match "^[%a_][%w_]*$" and x or brk_idx(str_obj (x)) end local function fmt (options, x) return (options [type(x)] or tostring) (x) end local function val (x, depth, name) local function tbl_obj (x) local idx, its, y = {}, {}, {(x[1] or x[2]) and true} if enc[x] then return enc[x] end enc[x] = name -- start encoding this table for i in pairs(x) do idx[#idx+1] = i; y[i] = true if (type(i) == "number") and x[i+2] then y[i+1] = true end end table.sort (idx, function (a,b) return tostring(a) < tostring (b) end) for i in ipairs (y) do its[i] = val (x[i], depth+1, con {name,'[',i,']'}); y[i]=nil end -- contiguous numeric indices if #its > 0 then its = {con (its, ',')} end local n = 0 for _,j in ipairs (idx) do -- discontiguous numeric or any string indices if y[j] then n = n + 1 local fmt_idx = fmt ({number = brk_idx, string = str_idx}, (j)) its[#its+1] = fmt_idx .." = ".. val (x[j], depth+1, name..'.'..fmt_idx) end end local crlf, spc1, spc2 = '','','' if n > 1 then crlf = '\n'.. tab:rep(depth); spc1 = crlf; spc2 = crlf: sub (1,-1 - #tab) end -- indents enc [x] = nil -- finish encoding this table return con {'{', spc1, con {con (its, ','..crlf) }, spc2, '}'} end return fmt ({table = tbl_obj, string = str_obj}, x) end for a,b in pairs (_G) do if type (b) == "table" then enc[b] = a end; end return val(Lua, 1, cfg.name or '_') end function ALTUI_LuaRunHandler(lul_request, lul_parameters, lul_outputformat) local lua = lul_parameters["lua"] luup.log(string.format("ALTUI: runLua(%s)",lua),50) -- prepare print result and override print function printResult = {} -- prepare execution local errcode = 0 local f,results = loadstring(lua) if (f==nil) then luup.log(string.format("ALTUI: loadstring %s failed to compile, msg=%s",lua,results),1) else setfenv (f, setmetatable ({print=myPrint, pretty=pretty}, {__index = _G, __newindex = _G})) local ok ok, results = pcall (f) -- call it luup.log(string.format("ALTUI: Evaluation of lua code returned: %s",tostring(results)),50) errcode=1 end printResult = table.concat (printResult, "\n") return string.format("%d||%s||%s",errcode,tostring(results),printResult); end luup.register_handler('ALTUI_LuaRunHandler','ALTUI_LuaRunHandler') ]] local url = require "socket.url" local req = "http://127.0.0.1:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunLua&Code=" -- code = "require 'L_ALTUI_LuaRunHandler'\n" req = req .. url.escape(code) local httpcode,content = luup.inet.wget(req) return httpcode end function startupDeferred(lul_device) lul_device = tonumber(lul_device) log("startupDeferred, called on behalf of device:"..lul_device) local debugmode = getSetVariable(ALTUI_SERVICE, "Debug", lul_device, "0") local oldversion = getSetVariable(ALTUI_SERVICE, "Version", lul_device, version) local present = getSetVariable(ALTUI_SERVICE,"Present", lul_device, 0) local remoteurl =getSetVariable(ALTUI_SERVICE,"RemoteAccess", lul_device, "https://vera-ui.strongcubedfitness.com/Veralogin.php") local localurl = getSetVariableIfEmpty(ALTUI_SERVICE,"LocalHome", lul_device, "/port_3480/data_request?id=lr_ALTUI_Handler&command=home") local css = getSetVariable(ALTUI_SERVICE,"ThemeCSS", lul_device, "") local extraController= getSetVariable(ALTUI_SERVICE, "ExtraController", lul_device, "") local serverOptions= getSetVariable(ALTUI_SERVICE, "ServerOptions", lul_device, "") local localcdn = getSetVariable(ALTUI_SERVICE, "LocalCDN", lul_device, "") local localbootstrap = getSetVariable(ALTUI_SERVICE, "LocalBootstrap", lul_device, "") if (localbootstrap == "") then localbootstrap=defaultBootstrapPath else -- verify this starts by ../ to make sure it works for remote access if (string.starts(localbootstrap,"../") == false) then if (string.starts(localbootstrap,"/") == false) then localbootstrap = ".."..localbootstrap else localbootstrap = "../"..localbootstrap end luup.variable_set(ALTUI_SERVICE, "LocalBootstrap", localbootstrap, lul_device) end end -- clean tmp area from our files -- os.execute('rm /tmp/altui_*'); if (debugmode=="1") then DEBUG_MODE = true UserMessage("Enabling debug mode for device:"..lul_device,TASK_BUSY) end local major,minor = 0,0 local tbl={} if (oldversion~=nil) then major,minor = string.match(oldversion,"v(%d+)%.(%d+)") major,minor = tonumber(major),tonumber(minor) debug ("Plugin version: "..version.." Device's Version is major:"..major.." minor:"..minor) newmajor,newminor = string.match(version,"v(%d+)%.(%d+)") newmajor,newminor = tonumber(newmajor),tonumber(newminor) -- init the configuration table with a valid default if needed local defconfigjson = json.encode( getDefaultConfig() ) local config = getSetVariable(ALTUI_SERVICE, "PluginConfig", lul_device, defconfigjson ) -- force the default in case of upgrade if ( (newmajor>major) or ( (newmajor==major) and (newminor>minor) ) ) then log ("Version upgrade => Reseting Plugin config to default") setVariableIfChanged(ALTUI_SERVICE, "PluginConfig", defconfigjson, lul_device) if (newmajor==1) and (newminor==1) then setVariableIfChanged(ALTUI_SERVICE, "ServerOptions", "[]", lul_device) end end luup.variable_set(ALTUI_SERVICE, "Version", version, lul_device) end -- init watches -- init data storages fixVariableWatches( lul_device ) initVariableWatches( lul_device) -- NOTHING to start if( luup.version_branch == 1 and luup.version_major == 7) then luup.set_failure(0,lul_device) -- should be 0 in UI7 else luup.set_failure(false,lul_device) -- should be 0 in UI7 end registerHandlers() log("startup completed") end function initstatus(lul_device) lul_device = tonumber(lul_device) log("initstatus("..lul_device..") starting version: "..version) checkVersion(lul_device) hostname = getIP() local delay = 1 -- delaying first refresh by x seconds debug("initstatus("..lul_device..") startup for Root device, delay:"..delay) -- http://192.168.1.5:3480/data_request?id=lr_IPX800_Handler luup.call_delay("startupDeferred", delay, tostring(lul_device)) end -- do not delete, last line must be a CR according to MCV wiki page