LogicMachine Forum
Twinkly script - Printable Version

+- LogicMachine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Twinkly script (/showthread.php?tid=3049)



Twinkly script - gjniewenhuijse - 07.12.2020

I like to connect my Twinkly christmas leds to the LM.

I found a LUA example for Fibaro (see attached Twinkly.LUA), but i can't get the example working.

My code:
Code:
--[[ documentation api: https://xled-docs.readthedocs.io/en/latest/rest_api.html mqtt: https://xled-docs.readthedocs.io/en/latest/msqtt_api.html --]] -- init if not init then   require('socket.http')   require('ltn12')   require('json')   socket.http.TIMEOUT = 5   host = "192.168.x.xx"     authToken = nil   init = true end local api = {     base = "/xled/v1/",     endpoints = {         login = "login",         verify = "verify",         mode = "led/mode",         deviceName = "device_name",         reset = "led/reset",         movieConfig = "led/movie/config",         movie = "led/movie/full",         gestalt = "gestalt",         brightness = "led/out/brightness"     } } function getTwinklyResponseData(endPoint, body, content_type)     local content_type = content_type or "application/json"     local url = "http://" .. host .. api.base .. api.endpoints[endPoint]   local method = "GET"   if body ~= nil then     method = "POST"     if type(body) == "table" and content_type == "application/json" then       body = json.encode(body)     end   end   local response_body = {}   local payload = body   log('input', url, method, payload)   local res, code, response_headers, status = socket.http.request     {       url = url,       method = method,       headers =       {         ["Content-Type"] = content_type,         ["X-Auth-Token"] = authToken       },       source = ltn12.source.string(payload),       sink = ltn12.sink.table(response_body)     }   log(res, code, response_headers, status)   log('payload', payload)   log('response_body', response_body)   if res and code == 200 then     return true   else     return false   end end log('start') if getTwinklyResponseData("gestalt") then   getTwinklyResponseData("login",{challenge = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\a"}) end log('einde')

my data response from "gestalt" looks right with the correct device information, but the "login" gives as result:
Code:
* arg: 1   * number: 1 * arg: 2   * number: 200 * arg: 3   * table:    ["server"]     * string: esp-httpd/0.5    ["content-type"]     * string: application/json    ["connection"]     * string: close * arg: 4   * string: HTTP/1.1 200 OK
thats looks also right, but my response_data is: 
Code:
* arg: 1   * string: response_body * arg: 2   * table:    [1]     * string: {"code":1106}
and thats not right i think.

Who can help me?


RE: Twinkly script - admin - 07.12.2020

The challenge = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\a" part seems incorrect, try the one from the docs:
{challenge = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="}

You can also try MQTT instead of HTTP, the integration seems easier for MQTT.


RE: Twinkly script - gjniewenhuijse - 07.12.2020

(07.12.2020, 10:02)admin Wrote: The challenge = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\a" part seems incorrect, try the one from the docs:
{challenge = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="}

same code 1106 response

-- i see this on https://labs.f-secure.com/blog/twinkly-twinkly-little-star/
Once the application knows the IP address of the lights, it authenticates with them, receives an authentication token and retrieves information about the device.

The authentication process, although a good idea, is flawed. First, the application makes a POST request to the endpoint '/xled/v1/login' with a base64 encoded 32 bit random number. The lights respond with an authentication token, how long it will be valid for, and a base64 encoded response to the challenge. This response is based on the random challenge number, the MAC address of the lights and a shared secret. The phone application sets the authentication token as a HTTP header and sends the received challenge response back to the lights on the endpoint '/xled/v1/verify'. This finalises the authentication allowing for authenticated endpoints to be called.


RE: Twinkly script - admin - 07.12.2020

You are missing the Content-Length header for the POST request. This should help:
Code:
function getTwinklyResponseData(endPoint, body, content_type)   local content_type = content_type or "application/json"   local url = "http://" .. host .. api.base .. api.endpoints[endPoint]   local method = "GET"   local cl   if body ~= nil then     method = "POST"     if type(body) == "table" and content_type == "application/json" then       body = json.encode(body)     end     cl = #body   end   local response_body = {}   local res, code, response_headers, status = socket.http.request({     url = url,     method = method,     headers =     {       ["Content-Type"] = content_type,       ["Content-Length"] = cl,       ["X-Auth-Token"] = authToken     },     source = ltn12.source.string(body),     sink = ltn12.sink.table(response_body)   })   if res then     return code == 200   end end



RE: Twinkly script - gjniewenhuijse - 07.12.2020

(07.12.2020, 11:14)admin Wrote: You are missing the Content-Length header for the POST request. This should help:
Code:
function getTwinklyResponseData(endPoint, body, content_type)   local content_type = content_type or "application/json"   local url = "http://" .. host .. api.base .. api.endpoints[endPoint]   local method = "GET"   local cl   if body ~= nil then     method = "POST"     if type(body) == "table" and content_type == "application/json" then       body = json.encode(body)     end     cl = #body   end   local response_body = {}   local res, code, response_headers, status = socket.http.request({     url = url,     method = method,     headers =     {       ["Content-Type"] = content_type,       ["Content-Length"] = cl,       ["X-Auth-Token"] = authToken     },     source = ltn12.source.string(body),     sink = ltn12.sink.table(response_body)   })   if res then     return code == 200   end end

yes, thanks


RE: Twinkly script - gjniewenhuijse - 07.12.2020

Thanks admin for all the work. Here my first working version
Code:
--[[ documentation api: https://xled-docs.readthedocs.io/en/latest/rest_api.html mqtt: https://xled-docs.readthedocs.io/en/latest/msqtt_api.html fibaro example: https://forum.fibaro.com/topic/52767-qa-twinkly-lights-on-off/ --]] host = "xxx.xxx.x.xx"  -- enter your Twinkly ip -- debuggen debug = false function debugLog(iLog)   if debug then log(iLog) end end -- init if not init then   require('socket.http')   require('ltn12')   require('json')   socket.http.TIMEOUT = 5   challenceToken = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="   authToken = nil   init = true end local api = {     base = "/xled/v1/",     endpoints = {         login = "login",         verify = "verify",         mode = "led/mode",         deviceName = "device_name",         reset = "led/reset",         movieConfig = "led/movie/config",         movie = "led/movie/full",         gestalt = "gestalt",         brightness = "led/out/brightness"     } } function getTwinklyResponseData(endPoint, body, func, content_type)     debugLog(endPoint)       content_type = content_type or "application/json"     local url = "http://" .. host .. api.base .. api.endpoints[endPoint]     local method = "GET"       local cl       if body ~= nil then         method = "POST"         if type(body) == "table" and content_type == "application/json" then             body = json.encode(body)         end             cl = #body     end       debugLog({url, method, content_type, cl, authToken, body})       local response_body = {}     local res, code, response_headers, status = socket.http.request({       url = url,       method = method,       headers =       {         ["Content-Type"] = content_type,         ["Content-Length"] = cl,         ["X-Auth-Token"] = authToken       },       source = ltn12.source.string(body),       sink = ltn12.sink.table(response_body)     })       debugLog({res, code, response_headers, status, response_body})       if res then         if code == 200 then             func(json.decode(table.concat(response_body)))          else             func(nil, response_body)         end       else         func(nil, {error = code})       end end function connect(func_succ, func_err)   getTwinklyResponseData("gestalt", nil,   function(gestalt, err)       if err == nil then           getTwinklyResponseData(               "login",               {challenge = challenceToken},               function(login, err)                   if err == nil then                       authToken = login.authentication_token                       getTwinklyResponseData(                           "verify",                           {},                           function(verify, err)                               if err == nil then                                   func_succ(gestalt)                               else                                   if func_err then                                       func_err(err)                                   else                                       debugLog(json.encode(err))                                   end                               end                           end                       )                   else                       if func_err then                           func_err(err)                       else                           debugLog(json.encode(err))                       end                   end               end           )       else           if func_err then               func_err(err)           else               debugLog(json.encode(err))           end       end   end   ) end function setMode(mode, func)   if authToken == nil then       debugLog("Device is not intialized!")   end   getTwinklyResponseData(     "mode",     {mode = mode},     function(response, err)       if err == nil then           if func then             func(response)         end       else           debugLog(json.encode(err))       end     end   ) end function getMode(func)   if authToken == nil then       debugLog("Device is not intialized!")   end   getTwinklyResponseData(     "mode",     nil,     function(response, err)       if err == nil then         if func then             func(response)         end       else           debugLog(json.encode(err))       end     end   ) end function setBrightness(value, func)   if authToken == nil then       debugLog("Device is not intialized!")   end   getTwinklyResponseData(       "brightness",     {mode = "enabled", type = "A", value = value},     function(response, err)       if err == nil then         if func then             func(response)         end       else           debugLog(json.encode(err))       end     end   ) end function getBrightness(func)   if authToken == nil then       debugLog("Device is not intialized!")   end   getTwinklyResponseData(     "brightness",     nil,     function(response, err)       if err == nil then         if func then             func(response)         end       else           debugLog(json.encode(err))       end     end   ) end ------------------ some function tests ------------------ -- set mode connect(   function(data)     setMode(       "off", -- off, movie       function(data)         debugLog(json.encode(data))        end     )   end ) -- get current mode connect(   function(data)     getMode(       function(data)         debugLog(json.encode(data))        end       )   end ) -- set brightness connect(   function(data)     setBrightness(       10,       function(data)           debugLog(json.encode(data))       end     )   end ) -- get current brightness connect(   function(data)     getBrightness(       function(data)         debugLog(json.encode(data))        end     )   end )