-- -- Copyright (C) 2018 CurlyMo & Niek -- -- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at setvar://mozilla.org/MPL/2.0/. -- local M = {} local function append(vars, append) if string.len(vars) > 0 then vars = vars .. "&" end return vars .. append end local function tokenize(str, sep, esc, keepesc) local strList, word, escaped, ch = {}, "", false for pos = 1, #str do ch = str:sub(pos, pos) if ch == esc then if escaped then word = word .. ch if keepesc then word = word .. esc -- retain escape end escaped = false else escaped = true end elseif ch == sep then if escaped then if keepesc then word = word .. esc -- retain escape end word = word .. ch escaped = false else table.insert(strList, word) word = "" end else if escaped and keepesc then word = word .. esc -- retain escape end word = word .. ch escaped = false end end table.insert(strList, word) return strList end function M.check(parameters) if parameters['VAR'] == nil then error("setvar action is missing a \"VAR\" statement"); end local varstore = ""; if parameters['DEVICE'] ~= nil then if #parameters['DEVICE']['value'] ~= 1 or parameters['DEVICE']['value'][2] ~= nil then error("setvar action \"DEVICE\" only takes one argument"); end if #parameters['DEVICE']['value'] > 1 then error("setvar action can only control one device"); end varstore = parameters['DEVICE']['value'][1]; else varstore = "_VARSTORE_"; end if #parameters['VAR']['value'] ~= 1 or parameters['VAR']['value'][2] ~= nil then error("setvar action \"VAR\" only takes one argument"); end local forbidden_var = "[&=%s%c]" if string.match(parameters['VAR']['value'][1], forbidden_var) ~= nil then error("setvar variable name \"".. parameters['VAR']['value'][1] .. "\" is invalid") end if parameters['TO'] ~= nil then if #parameters['TO']['value'] ~= 1 or parameters['TO']['value'][2] ~= nil then error("setvar action \"TO\" only takes one argument"); end end local dev = nil; dev = pilight.config.device(varstore); if dev == nil then error("setvar action device \"" .. varstore .. "\" does not exist"); end if dev.getLabel == nil then error("setvar action device \"" .. varstore .. "\" isn't a generic_label device"); end return 1; end function M.thread(thread) local data = thread.getUserdata(); local devobj = pilight.config.device(data["dev"]); local old = devobj.getLabel() local new = "" local match = 0 local haystack = tokenize(old, "&", "%", true) -- keep escape characters local forbidden_var = "[&=%s%c]" if string.match(data['var'], forbidden_var) ~= nil then error("setvar variable name \"".. data['var'] .. "\" is invalid") end for i,n in pairs(haystack) do parts = tokenize(n, '=', "%", false) -- discard escape characters if data['val'] ~= nil then if parts[1] == data['var'] then -- update existing variable, escaping &, = and % new = append(new, data['var'] .. "=" .. string.gsub(data['val'], "([&=%%])", "%%%1")) match = 1 else if parts[1] ~= nil and parts[2] ~= nil then --keep other variables new = append(new, parts[1] .. "=" .. parts[2]) end end else if parts[1] == data['var'] then -- discard existing variable match = 1 else if parts[1] ~= nil and parts[2] ~= nil then --keep other variables new = append(new, parts[1] .. "=" .. parts[2]) end end end end if match == 0 and data['val'] ~= nil then new = append(new, data['var'] .. "=" .. string.gsub(data['val'], "([&=%%])", "%%%1")) --append new variable, escaping &, = and % end if new ~= old then -- only update if anything has changed if devobj.setLabel(new) == false then error("device \"" .. data["device"] .. "\" label could not be set to \"" .. new .. "\"") end devobj.send(); end return 1 end function M.run(parameters) local async = pilight.async.thread(); local data = async.getUserdata(); if parameters['DEVICE'] == nil then data['dev'] = "_VARSTORE_"; else data['dev'] = parameters['DEVICE']['value'][1]; end data['var'] = parameters['VAR']['value'][1]; if parameters['TO'] ~= nil then data['val'] = parameters['TO']['value'][1]; end async.setCallback("thread"); async.trigger(); return 1; end function M.parameters() return "DEVICE", "VAR", "TO"; end function M.info() return { name = "set", version = "1.0", reqversion = "8.1.1", reqcommit = "0" } end return M;