if not modules then modules = { } end modules ['util-sig-imp-runner'] = { version = 1.002, comment = "companion to util-sig.lua", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- conbee (stand alone on machine, done) -- hue (in local hue network, will do) -- fritz (only when i have a test setup) -- ... (only when i have a test setup) local function rgbtohsv(r,g,b) local offset, maximum, other_1, other_2 local hue = 0 local saturation = 0 local brightness = 0 if r >= g and r >= b then offset, maximum, other_1, other_2 = 0, r, g, b elseif g >= r and g >= b then offset, maximum, other_1, other_2 = 2, g, b, r else offset, maximum, other_1, other_2 = 4, b, r, g end if maximum == 0 then --- else local minimum = other_1 < other_2 and other_1 or other_2 if maximum == minimum then brightness = maximum else local delta = maximum - minimum hue = math.round((offset + (other_1-other_2)/delta)*60 * (65536/360)) saturation = math.round(256*(delta/maximum)) - 1 brightness = maximum end end return { hue = hue, saturation = saturation, brightness = brightness, } end local function rgbtoxyz(r,g,b) r = 100 * ((r > 0.04045) and ((r + 0.055)/1.055)^2.4 or (r / 12.92)) g = 100 * ((g > 0.04045) and ((g + 0.055)/1.055)^2.4 or (g / 12.92)) b = 100 * ((b > 0.04045) and ((b + 0.055)/1.055)^2.4 or (b / 12.92)) return { x = r * 0.4124 + g * 0.3576 + b * 0.1805, y = r * 0.2126 + g * 0.7152 + b * 0.0722, z = r * 0.0193 + g * 0.1192 + b * 0.9505, } end -- tasklist /V /FO CSV -- last column Window Title -- We use a Phoscon Conbee II usb stick as zigbee hub with 4 Hue color lamps and -- also two Ikea switches to turn them off. -- get an access token: -- -- curl -X POST -i "http://127.0.0.1/api" --data "{ \"devicetype\" : \"password\" }" -- -- local u = "XXXXXXXXXX" -- todo: switches local next, dofile, type = next, dofile, type local format, gsub, find = string.format, string.gsub, string.find local jsontostring = utilities.json.tostring local jsontolua = utilities.json.tolua local report = utilities.signals.report or logs.reporter("signal") local state = utilities.signals.loadstate() local url = "http://127.0.0.1/" local token = "XXXXXXXXXX" local lamps = { 1, 2, 3, 4 } local alarms = { } --maybe local trace = environment.arguments.verbose local protocol = "hue" if state then local server = state.servers[state.usage.server or "default"] if server then url = server.url or url token = server.token or token protocol = server.protocol or protocol end if state.signals and state.signals.runner then lamps = state.signals.runner.lamps or lamps end url = gsub(url,"/+$","") end url = file.join(url,"api",token) local how = { blue = { 0, 0, 1 }, yellow = { 1, 1, 0 }, red = { 1, 0, 0 }, green = { 0, 1, 0 }, white = { 1, 1, 1 }, orange = { 1, .65, 0 }, off = { 0, 0, 0 }, reset = { 0, 0, 0 }, on = { 0, 0, 0 }, } for key, value in next, how do local hsv = rgbtohsv(value[1],value[2],value[3]) local xyz = rgbtoxyz(value[1],value[2],value[3]) local sum = xyz.x + xyz.y + xyz.z local nop = rgbtoxyz(1,1,1) local som = nop.x + nop.y + nop.z how[key] = { hue = hsv.hue, saturation = hsv.saturation, brightness = hsv.brightness, x = xyz.x, y = xyz.y, z = xyz.z, xz = sum == 0 and nop.x/som or xyz.x/sum, yz = sum == 0 and nop.y/som or xyz.y/sum, } end local states = { reset = how.reset, off = how.off, on = how.on, busy = how.blue, error = how.red, done = how.green, maxruns = how.orange, problem = how.orange, finished = how.yellow, } -- local states = { -- reset = how.off, -- busy = how.blue, -- error = how.red, -- done = how.yellow, -- maxruns = how.orange, -- problem = how.orange, -- finished = how.green, -- } local curl = false local fetch = false local name = resolvers.findfile("libs-imp-curl.lmt") or "" local curl = name ~= "" and dofile(name) -- todo: protocol = hue | deconz -- print(curl) if curl and curl.getversion and curl.getversion() then fetch = function(t) local kind = t.kind local result = "unsupported method" local detail = nil if kind == "put" then result, detail = curl.fetch { url = t.url, postfields = t.data, customrequest = "PUT", sslverifypeer = false, sslverifyhost = false, timeout = 1, } elseif kind == "get" then result, detail = curl.fetch { url = t.url, sslverifypeer = false, sslverifyhost = false, timeout = 1, } elseif kind == "delete" then result, detail = curl.fetch { url = t.url, customrequest = "DELETE", sslverifypeer = false, sslverifyhost = false, timeout = 1, } print(t.url,detail) elseif kind == "post" then result, detail = curl.fetch { url = t.url, postfields = t.data, customrequest = "POST", sslverifypeer = false, sslverifyhost = false, timeout = 1, } end -- local t = type(result) == "string" and jsontolua(result) -- return type(t) == "table" and t or result -- print(detail) return result end else -- local trigger = trace and os.execute or os.resultof local trigger = os.execute local getfmt = [[curl -k -s -i "%s"]] local putfmt = [[curl -k -s -X PUT -i "%s" --data "%s"]] local postfmt = [[curl -k -s -X POST -i "%s" --data "%s"]] local deletefmt = [[curl -k -s -X DELETE -i "%s"]] fetch = function(t) local kind = t.kind local result = "unsupported method" if kind == "put" then local dat = gsub(t.data,'"','\\"') local cmd = format(putfmt,t.url,dat) result = trigger(cmd) elseif kind == "get" then local cmd = format(putfmt,t.url) result = trigger(cmd) elseif kind == "delete" then local cmd = format(deletefmt,t.url) result = trigger(cmd) elseif kind == "post" then local dat = gsub(t.data,'"','\\"') local cmd = format(deletefmt,t.url,dat) result = trigger(cmd) end -- local t = type(result) == "string" and jsontolua(result) -- return type(t) == "table" and t or result return result end end local function action(state,url,data,time,kind) statistics.starttiming("signals") local result = fetch { url = url, data = data, kind = kind or "put", } if trace then if type(result) == "table" then result = table.serialized(result,false) end report("result: %s",tostring(result)) end statistics.stoptiming("signals") local seconds = statistics.elapsedseconds("signals") if time and seconds and seconds ~= "" then report("last state %a, total time spent: %s",state,seconds) end return result end local enable = false local disable = false local wipe = false local prune = false if protocol == "hue" or protocol == "deconz" then enable = function(state,first,last) local spec = states[state] or states.reset if spec then local lampdata = jsontostring { on = true, bri = 255, -- normally okay -- sat = spec.saturation, -- hue = spec.hue, -- needed for hive lamp xy = { spec.xz, spec.yz }, } local plugdata = jsontostring { on = true, } for lamp=first,last do local valid = lamps[lamp] if valid then local url = format( "%s/lights/%i/state", url, valid < 0 and -valid or valid ) action( state, url, valid < 0 and plugdate or lampdata, lamp==last ) end end end end disable = function(state,first,last) local spec = states[state] or states.reset if spec then local lampdata = jsontostring { on = false, bri = 255, -- normally okay -- sat = spec.saturation, -- hue = spec.hue, -- needed for hive lamp xy = { spec.xz, spec.yz }, } local plugdata = jsontostring { on = false, } for lamp=first,last do local valid = lamps[lamp] if valid then local url = format( "%s/lights/%i/state", url, valid < 0 and -valid or valid ) action( state, url, valid < 0 and plugdate or lampdata, lamp==last ) end end end end -- This is only for Hue (where we can actually run out of rules). This is -- for configuring the hub, not for users. prune = protocol == "hue" and function(state,id,max) -- Only for the administrator, removes all rules. We check the state again. if state == "prune" then id = tonumber(id) max = tonumber(max) if id or max then local first = id or 1 local last = id or max for i=first,last do -- -- DELETE /rules/ResetState3 -- local wipeurl = format( "%s/rules/%i", url, i ) action( "prune", wipeurl, "", false, "delete" ) end end end end or false wipe = protocol == "hue" and function(state,lamp,minutes) -- Only for the administrator, sets a timer. We check the state again. if state == "wipe" and type(lamp) == "number" then local spec = states.reset if spec then local wipe = jsontostring { name = format("ResetState%i",lamp), conditions = { { address = format("/lights/%i/state/on",lamp), operator = "eq", value = "true", }, { address = format("/lights/%i/state/on",lamp), operator = "ddx", value = format("PT00:%02i:00",tonumber(minutes) or 5) } }, actions = { { address = format("/lights/%i/state",lamp), method = "PUT", body = { on = false, bri = 255, xy = { spec.xz, spec.yz }, } } } } -- -- POST /rules/Reset_State_3 -- local wipeurl = format( "%s/rules", url ) local result = action( "wipe", wipeurl, wipe, lamp==last, "post" ) if type(result) == "string" then result = jsontolua(result) if result then for i=1,#result do local r = result[i] if type(r) == "table" and r.success then return r.success.id end end end end end end end or false end if not enable then report("unknown protocol %a",protocol) end local function trigger(state,run,all) if state == "wipe" then if wipe then return wipe("wipe",run,all) end elseif state == "prune" then if prune then prune("prune",run,all) end else local noflamps = #lamps local usedrun = tonumber(run) or 0 if type(run) == "boolean" then all = run usedrun = 0 end if usedrun == 0 then all = true usedrun = noflamps elseif usedrun > noflamps then usedrun = noflamps end if state == "reset" or state == "off" then disable(state,1,usedrun) elseif all then enable(state,1,noflamps) elseif state == "finished" then enable("finished",1,usedrun) else enable(state,usedrun,usedrun) end end end -- if no server set then quit return { name = "runner", trigger = trigger, report = report, }