This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Twinkly script
#1
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?

Attached Files
.lua   Twinkly.LUA (Size: 10.11 KB / Downloads: 2)
Reply
#2
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.
Reply
#3
(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-t...ttle-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.
Reply
#4
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
Reply
#5
(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
Reply
#6
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
)
Reply


Forum Jump: