|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# A simple Python manager for "Turing Smart Screen" 3.5" IPS USB-C display |
| 3 | +# https://github.com/mathoudebine/turing-smart-screen-python |
| 4 | + |
| 5 | +import os |
| 6 | +import signal |
1 | 7 | import struct |
| 8 | +from datetime import datetime |
2 | 9 | from time import sleep |
3 | 10 |
|
4 | 11 | import serial # Install pyserial : pip install pyserial |
5 | | -from PIL import Image # Install PIL or Pillow |
| 12 | +from PIL import Image, ImageDraw, ImageFont # Install PIL or Pillow |
6 | 13 |
|
7 | 14 | # Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux... |
8 | | -COM_PORT = "/dev/ttyACM1" |
| 15 | +COM_PORT = "/dev/ttyACM0" |
9 | 16 | # COM_PORT = "COM5" |
10 | 17 |
|
11 | | -def SendReg(ser, reg, x, y, ex, ey): |
| 18 | +DISPLAY_WIDTH = 320 |
| 19 | +DISPLAY_HEIGHT = 480 |
| 20 | + |
| 21 | + |
| 22 | +class Command: |
| 23 | + RESET = 101 |
| 24 | + CLEAR = 102 |
| 25 | + SCREEN_OFF = 108 |
| 26 | + SCREEN_ON = 109 |
| 27 | + SET_BRIGHTNESS = 110 |
| 28 | + DISPLAY_BITMAP = 197 |
| 29 | + |
| 30 | + |
| 31 | +def SendReg(ser, cmd, x, y, ex, ey): |
12 | 32 | byteBuffer = bytearray(6) |
13 | 33 | byteBuffer[0] = (x >> 2) |
14 | 34 | byteBuffer[1] = (((x & 3) << 6) + (y >> 4)) |
15 | 35 | byteBuffer[2] = (((y & 15) << 4) + (ex >> 6)) |
16 | 36 | byteBuffer[3] = (((ex & 63) << 2) + (ey >> 8)) |
17 | 37 | byteBuffer[4] = (ey & 255) |
18 | | - byteBuffer[5] = reg |
| 38 | + byteBuffer[5] = cmd |
19 | 39 | ser.write(bytes(byteBuffer)) |
20 | 40 |
|
21 | 41 |
|
22 | 42 | def Reset(ser): |
23 | | - SendReg(ser, 101, 0, 0, 0, 0) |
| 43 | + SendReg(ser, Command.RESET, 0, 0, 0, 0) |
24 | 44 |
|
25 | 45 |
|
26 | 46 | def Clear(ser): |
27 | | - SendReg(ser, 102, 0, 0, 0, 0) |
| 47 | + SendReg(ser, Command.CLEAR, 0, 0, 0, 0) |
28 | 48 |
|
29 | 49 |
|
30 | 50 | def ScreenOff(ser): |
31 | | - SendReg(ser, 108, 0, 0, 0, 0) |
| 51 | + SendReg(ser, Command.SCREEN_OFF, 0, 0, 0, 0) |
32 | 52 |
|
33 | 53 |
|
34 | 54 | def ScreenOn(ser): |
35 | | - SendReg(ser, 109, 0, 0, 0, 0) |
| 55 | + SendReg(ser, Command.SCREEN_ON, 0, 0, 0, 0) |
36 | 56 |
|
37 | 57 |
|
38 | 58 | def SetBrightness(ser, level): |
39 | | - # Level : 0 (bright) - 255 (darkest) |
40 | | - SendReg(ser, 110, level, 0, 0, 0) |
| 59 | + # Level : 0 (brightest) - 255 (darkest) |
| 60 | + SendReg(ser, Command.SET_BRIGHTNESS, level, 0, 0, 0) |
41 | 61 |
|
42 | 62 |
|
43 | | -def PrintImage(ser, image): |
44 | | - im = Image.open(image) |
45 | | - image_height = im.size[1] |
46 | | - image_width = im.size[0] |
| 63 | +def DisplayPILImage(ser, image, x, y): |
| 64 | + image_height = image.size[1] |
| 65 | + image_width = image.size[0] |
47 | 66 |
|
48 | | - SendReg(ser, 197, 0, 0, image_width - 1, image_height - 1) |
| 67 | + SendReg(ser, Command.DISPLAY_BITMAP, x, y, x + image_width - 1, y + image_height - 1) |
49 | 68 |
|
50 | | - pix = im.load() |
| 69 | + pix = image.load() |
| 70 | + line = bytes() |
51 | 71 | for h in range(image_height): |
52 | | - line = bytes() |
53 | 72 | for w in range(image_width): |
54 | | - if w < image_width: |
55 | | - R = pix[w, h][0] >> 3 |
56 | | - G = pix[w, h][1] >> 2 |
57 | | - B = pix[w, h][2] >> 3 |
| 73 | + R = pix[w, h][0] >> 3 |
| 74 | + G = pix[w, h][1] >> 2 |
| 75 | + B = pix[w, h][2] >> 3 |
58 | 76 |
|
59 | | - rgb = (R << 11) | (G << 5) | B |
60 | | - line += struct.pack('H', rgb) |
| 77 | + rgb = (R << 11) | (G << 5) | B |
| 78 | + line += struct.pack('H', rgb) |
| 79 | + |
| 80 | + # Send image data by multiple of DISPLAY_WIDTH bytes |
| 81 | + if len(line) >= DISPLAY_WIDTH*4: |
| 82 | + ser.write(line) |
| 83 | + line = bytes() |
| 84 | + |
| 85 | + # Write last line if needed |
| 86 | + if len(line) > 0: |
61 | 87 | ser.write(line) |
62 | 88 |
|
63 | 89 | sleep(0.01) # Wait 10 ms after picture display |
64 | 90 |
|
65 | 91 |
|
| 92 | +def DisplayBitmap(ser, bitmap_path, x=0, y=0): |
| 93 | + image = Image.open(bitmap_path) |
| 94 | + DisplayPILImage(ser, image, x, y) |
| 95 | + |
| 96 | + |
| 97 | +def DisplayText(ser, text, x=0, y=0, |
| 98 | + font="roboto/Roboto-Regular.ttf", |
| 99 | + font_size=20, |
| 100 | + font_color=(0, 0, 0), |
| 101 | + background_color=(255, 255, 255), |
| 102 | + background_image=None): |
| 103 | + # Convert text to bitmap using PIL and display it |
| 104 | + # Provide the background image path to display text with transparent background |
| 105 | + |
| 106 | + if background_image is None: |
| 107 | + # A text bitmap is created with max width/height by default : text with solid background |
| 108 | + text_image = Image.new('RGB', (DISPLAY_WIDTH, DISPLAY_HEIGHT), background_color) |
| 109 | + else: |
| 110 | + # The text bitmap is created from provided background image : text with transparent background |
| 111 | + text_image = Image.open(background_image) |
| 112 | + |
| 113 | + # Draw text with specified color & font |
| 114 | + font = ImageFont.truetype("./res/fonts/" + font, font_size) |
| 115 | + d = ImageDraw.Draw(text_image) |
| 116 | + d.text((x, y), text, font=font, fill=font_color) |
| 117 | + |
| 118 | + # Crop text bitmap to keep only the text |
| 119 | + text_width, text_height = d.textsize(text, font=font) |
| 120 | + text_image = text_image.crop(box=(x, y, min(x + text_width, DISPLAY_WIDTH), min(y + text_height, DISPLAY_HEIGHT))) |
| 121 | + |
| 122 | + DisplayPILImage(ser, text_image, x, y) |
| 123 | + |
| 124 | + |
| 125 | +stop = False |
| 126 | + |
66 | 127 | if __name__ == "__main__": |
67 | | - ser = serial.Serial(COM_PORT, 115200, timeout=1, rtscts=1) |
| 128 | + |
| 129 | + def sighandler(signum, frame): |
| 130 | + global stop |
| 131 | + stop = True |
| 132 | + |
| 133 | + # Set the signal handlers, to send a complete frame to the LCD before exit |
| 134 | + signal.signal(signal.SIGINT, sighandler) |
| 135 | + signal.signal(signal.SIGTERM, sighandler) |
| 136 | + is_posix = os.name == 'posix' |
| 137 | + if is_posix: |
| 138 | + signal.signal(signal.SIGQUIT, sighandler) |
| 139 | + |
| 140 | + # Do not change COM port settings unless you know what you are doing |
| 141 | + lcd_comm = serial.Serial(COM_PORT, 115200, timeout=1, rtscts=1) |
68 | 142 |
|
69 | 143 | # Clear screen (blank) |
70 | | - Clear(ser) |
| 144 | + Clear(lcd_comm) |
71 | 145 |
|
72 | 146 | # Set brightness to max value |
73 | | - SetBrightness(ser, 0) |
| 147 | + SetBrightness(lcd_comm, 0) |
74 | 148 |
|
75 | 149 | # Display sample picture |
76 | | - PrintImage(ser, "res/example.png") |
77 | | - |
78 | | - ser.close() |
| 150 | + DisplayBitmap(lcd_comm, "res/example.png") |
| 151 | + |
| 152 | + # Display sample text |
| 153 | + DisplayText(lcd_comm, "Basic text", 50, 100) |
| 154 | + |
| 155 | + # Display custom text with solid background |
| 156 | + DisplayText(lcd_comm, "Custom italic text", 5, 150, |
| 157 | + font="roboto/Roboto-Italic.ttf", |
| 158 | + font_size=30, |
| 159 | + font_color=(0, 0, 255), |
| 160 | + background_color=(255, 255, 0)) |
| 161 | + |
| 162 | + # Display custom text with transparent background |
| 163 | + DisplayText(lcd_comm, "Transparent bold text", 5, 300, |
| 164 | + font="roboto/Roboto-Bold.ttf", |
| 165 | + font_size=30, |
| 166 | + font_color=(255, 255, 255), |
| 167 | + background_image="res/example.png") |
| 168 | + |
| 169 | + # Display text that overflows |
| 170 | + DisplayText(lcd_comm, "Text overflow!", 5, 430, |
| 171 | + font="roboto/Roboto-Bold.ttf", |
| 172 | + font_size=60, |
| 173 | + font_color=(255, 255, 255), |
| 174 | + background_image="res/example.png") |
| 175 | + |
| 176 | + # Display the current time as fast as possible |
| 177 | + while not stop: |
| 178 | + DisplayText(lcd_comm, str(datetime.now().time()), 160, 2, |
| 179 | + font="roboto/Roboto-Bold.ttf", |
| 180 | + font_size=20, |
| 181 | + font_color=(255, 0, 0), |
| 182 | + background_image="res/example.png") |
| 183 | + |
| 184 | + lcd_comm.close() |
0 commit comments