Twinkly - Enhance integration with scenes (movie/color) [Python PoC examples included]

Hi,

For the Twinkly integration, the possibility to select scenes and set colors would be a very valuable enhancement. I know it’s not that easy to achieve with the way how Twinkly is built - but I found out that is quite feasible.

It took me quite some time to understand how Twinkly manages its scenes. Finally I managed to control everything - not only the color mode but also the movies/effects.

The challenge with Twinkly is obviously that the scenes (movies) are only selectively transmitted from the app to the control unit. However, all movies that are in the playlist are available on the control unit. Those are then also easily controllable via API. For the colors it is even easier, because only the RGB code has to be sent.

Please find some sample Python scripts attached:

Choose a movie (needs to be added to the playlist before)

import requests
import secrets

host = '192.168.0.xx'

path = '/xled/v1'
challenge = secrets.token_hex(nbytes=16)
movie_name = input("Please type movie name:\n")

# Login
endpoint = 'login'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge': 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='}
res = requests.post(url, json=myobj)
json = res.json()
if json['code'] != 1000:
	print(json)
authToken = json['authentication_token']
challengeResponse = json['challenge-response']

# Verify
endpoint = 'verify'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge-response': challengeResponse}
res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

# Get movies
endpoint = 'movies'
url = 'http://'+host+path+'/'+endpoint
res = requests.get(url, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

movie_id = -1
for i in json['movies']:
    if i['name'] == movie_name:
        movie_id = i['id']
        break

# Set movie
if movie_id >= 0:
	endpoint = 'movies/current'
	url = 'http://'+host+path+'/'+endpoint
	myobj = {"id":movie_id}
	res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
	json = res.json()
	if json['code'] != 1000:
		print(json)
	else:
		print('Movie with name "'+movie_name+'" enabled!')	
else:
	print('Movie with name "'+movie_name+'" not found!')

Set color

import requests
import secrets

host = '192.168.0.xx'

path = '/xled/v1'
challenge = secrets.token_hex(nbytes=16)

# Login
endpoint = 'login'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge': challenge}
res = requests.post(url, json=myobj)
json = res.json()
if json['code'] != 1000:
	print(json)
authToken = json['authentication_token']
challengeResponse = json['challenge-response']

# Verify
endpoint = 'verify'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge-response': challengeResponse}
res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

# Enable color mode
endpoint = 'led/mode'
url = 'http://'+host+path+'/'+endpoint
myobj = {"mode":"color"}
res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

# Set color
print(json)
url = 'http://192.168.0.66/xled/v1/led/color'
myobj = {"hue":0,"saturation":0,"value":0,"white":255,"red":0,"green":0,"blue":0}
res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

Furthermore I managed to upload a full movie to the control unit. But here you have to “sniff” all the octet-stream movie data and its json meta data from the network first. It works, but can hardly be integrated in Home Assistant I guess…

import requests
import secrets

host = '192.168.0.xx'

movie_json = {"leds_per_frame":400,"unique_id":"B68D6EA6-5E34-42A6-9402-05F8EA159ABB","frames_number": 94,"descriptor_type": "rgbw_raw","fps": 12,"name": "Snowflakes"}
movie_stream_file = './snowflakes.stream'
#movie_json = {"name":"Gradient","descriptor_type":"rgbw_raw","frames_number":120,"fps":12,"unique_id":"623A34E2-BD1A-453D-9B86-9E5BD1E52D48","leds_per_frame":400}
#movie_stream_file = './gradient.stream'

path = '/xled/v1'
challenge = secrets.token_hex(nbytes=16)

# Login
endpoint = 'login'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge': challenge}
res = requests.post(url, json=myobj)
json = res.json()
if json['code'] != 1000:
	print(json)
authToken = json['authentication_token']
challengeResponse = json['challenge-response']

# Verify
endpoint = 'verify'
url = 'http://'+host+path+'/'+endpoint
myobj = {'challenge-response': challengeResponse}
res = requests.post(url, json=myobj, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

# New movie
endpoint = 'movies/new'
url = 'http://'+host+path+'/'+endpoint
res = requests.post(url, json=movie_json, headers={'X-Auth-Token':authToken})
json = res.json()
if json['code'] != 1000:
	print(json)

# Upload movie
endpoint = 'movies/full'
url = 'http://'+host+path+'/'+endpoint
with open(movie_stream_file, 'rb') as f:
    data = f.read()
dataLen = len(data)
res = requests.post(url, data=data, headers={'X-Auth-Token':authToken,'Content-Type':'application/octet-stream','Content-Length':str(dataLen)})
json = res.json()
if json['code'] != 1000:
	print(json)

It would be nifty if there were some ‘controller’ you could run in HA to act as a Govee light, which could be used to intercept these streams and save them to be used in automations. Would definitely love to see some support even if basic for animation/scenes as that pretty much the entire point of Twinkly products. :crossed_fingers:

1 Like

I would love this too. Just a simple toggle to trigger the playlist rather than a static colour would be amazing!

Control of Movies/Effects has been added to the Twinkly integration and is available in Home Assistant 2022.12 (currently in Beta).

2 Likes

Amazing, I can’t wait to try it out!