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
)
|