Hi All,
I own a Tidbyt display - https://tidbyt.com/
There is a dev platform and an api. I decided to go with the api. It basically accepts a post request with an image (it can be animated).
I wanted a way to send a message to it. So some googling later… I came up with a local API, to use the Tidbyt API, from home assistant to send a message. Mind you I am NOT a developer, my code is based off knowledge of python and other examples online. I did it simply because I have never built an API before and I LOVE tinkering and finding unnecessary projects for myself.
It would be a heck of a lot easier if the Tidbyt API had an improvement to do what my api is doing or BETTER YET… home assistant integration (others request) that allowed sending both an image and text
My Python script does this:
- Flask API to accept a POST call with 2 parameters: Text and Color
- Logic to figure out the size of the text, text wrapping, determining if color is valid and assigning a default value if not
- PIL library used to build the image based on the text and color from API with a black background
- Base 64 encoded
- Sent off to Tidbyt API on Flask API POST
Config in home assistant:
tidbyt:
url: http://[LOCAL IP HERE]:5000/msg?text={{text}}&color={{color}}
method: POST
Script in home assistant:
service: rest_command.tidbyt
data:
text: '{{ states(''input_text.tidbyt_text'') }}'
color: '{{ states(''input_select.tidbyt_color'') }}'
Obviously the heart of the operation is my python code. Keep in mind this is running on my windows machine that stays on, just as a POC. I figured I would post it here and maybe it would encourage a proper solution.
I do not provide an installation ID in my code, so all messages are just temporary messages that are temporarily inserted into the cycle and disappear. But they can be inserted into the cycle of applets being shown on the device.
from flask import Flask, abort, jsonify, request, make_response, url_for
from PIL import Image, ImageDraw, ImageFont
from string import ascii_letters
import textwrap
from colour import Color
import base64
from io import BytesIO
import http.client
def pil_base64(image):
img_buffer = BytesIO()
image.save(img_buffer, format='gif')
byte_data = img_buffer.getvalue()
base64_str = base64.b64encode(byte_data)
return base64_str
def determine_text_size(text: str):
if len(text) < 5:
size=20
elif len(text) < 6:
size=17
elif len(text) < 7:
size=15
elif len(text) < 8:
size=12
elif len(text) < 9:
size=11
elif len(text) < 10:
size=10
elif len(text) < 12:
size=9
elif len(text) < 14:
size=15
elif len(text) < 16:
size=12
elif len(text) < 18:
size=11
elif len(text) < 20:
size=10
elif len(text) <= 34 :
size=9
elif len(text) > 34:
size=14
return size
def check_color(color):
if color is None:
color='red'
elif color =='':
color='red'
try:
# Converting 'deep sky blue' to 'deepskyblue'
color = color.replace(" ", "")
Color(color)
# if everything goes fine then return True
return color
except ValueError: # The color code was not found
return "red"
def create_image(text: str, size: int, color: str):
# Create Image
img = Image.new('RGB', (64, 32), color = 'black')
# Load custom font
font = ImageFont.truetype(font='SourceCodePro-Bold.ttf', size=size)
# Create DrawText object
draw = ImageDraw.Draw(im=img)
# Calculate the average length of a single character of our font.
avg_char_width = sum(font.getsize(char)[0] for char in ascii_letters) / len(ascii_letters)
# Translate this average length into a character count
max_char_count = int(img.size[0] / avg_char_width)
# Create a wrapped text object using scaled character count
text = textwrap.fill(text=text, width=max_char_count)
## Add text to the image
draw.text(xy=(img.size[0]/2, img.size[1] / 2 - .5), text=text, font=font, fill= color, anchor='mm')
return img
app = Flask(__name__)
@app.errorhandler(400)
def bad_request(error):
return make_response(jsonify( { 'error': 'Bad request' } ), 400)
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify( { 'error': 'Not found' } ), 404)
@app.route('/msg', methods = ['POST'])
def data():
text = request.args.get('text')
if len(text) > 34:
text='TOO BIG'
size=determine_text_size(text)
color = request.args.get('color')
color = check_color(color)
img = create_image(text, size, color)
#img.show()
#encode image
image_base64=pil_base64(img)
image_encoded=image_base64.decode('utf-8')
#API: token
headers = {
'Content-Type': "application/json",
'Authorization': "Bearer [TOKEN HERE]"
}
#API: Image-base64 encoded, installationID if you want to save applet, background deploy
payload = "{\n\"image\":\"" + image_encoded +"\",\n \"installationID\": \"\",\n \"background\": false\n}"
#API: Set address
conn = http.client.HTTPSConnection("api.tidbyt.com")
#API: Post to Tidbyt API DeviceID, payload and header
conn.request("POST", "/v0/devices/[DEVICEID HERE]/push", payload, headers)
#API: Response captured
res = conn.getresponse()
data = res.read()
#Local API Response
return jsonify({"color":color,"size":size,"Text":text,"Tidbyt-reply":data.decode("utf-8")})
if __name__ == '__main__':
app.run('[LOCAL IP HERE]',debug=True)