Skip to content

Commit 216fc47

Browse files
authored
Merge pull request #2 from mathoudebine/feature/text-display
Feature: add text display and partial refresh of LCD
2 parents 83b275f + 204298d commit 216fc47

15 files changed

+339
-30
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ This is a 3.5" USB-C display that shows as a serial port once connected.
1212
It cannot be seen by the operating system as a monitor but picture can be displayed on it.
1313

1414
A Windows-only software is [available here](https://translate.google.com/translate?sl=auto&u=https://gitee.com/emperg/usblcd/raw/master/dev0/realse.ini) to manage this display.
15-
This software allows creating themes to display your computer sensors on the display, but does not offer a simple way to display custom pictures.
15+
This software allows creating themes to display your computer sensors on the screen, but does not offer a simple way to display custom pictures or text.
1616

1717
This Python script has been created to do some simple operations on this display like :
1818
- **Display custom picture**
19+
- **Display text**
1920
- Clear the screen (blank)
2021
- Turn the screen on/off
2122
- Display soft reset

main.py

Lines changed: 135 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,184 @@
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
17
import struct
8+
from datetime import datetime
29
from time import sleep
310

411
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
613

714
# Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux...
8-
COM_PORT = "/dev/ttyACM1"
15+
COM_PORT = "/dev/ttyACM0"
916
# COM_PORT = "COM5"
1017

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):
1232
byteBuffer = bytearray(6)
1333
byteBuffer[0] = (x >> 2)
1434
byteBuffer[1] = (((x & 3) << 6) + (y >> 4))
1535
byteBuffer[2] = (((y & 15) << 4) + (ex >> 6))
1636
byteBuffer[3] = (((ex & 63) << 2) + (ey >> 8))
1737
byteBuffer[4] = (ey & 255)
18-
byteBuffer[5] = reg
38+
byteBuffer[5] = cmd
1939
ser.write(bytes(byteBuffer))
2040

2141

2242
def Reset(ser):
23-
SendReg(ser, 101, 0, 0, 0, 0)
43+
SendReg(ser, Command.RESET, 0, 0, 0, 0)
2444

2545

2646
def Clear(ser):
27-
SendReg(ser, 102, 0, 0, 0, 0)
47+
SendReg(ser, Command.CLEAR, 0, 0, 0, 0)
2848

2949

3050
def ScreenOff(ser):
31-
SendReg(ser, 108, 0, 0, 0, 0)
51+
SendReg(ser, Command.SCREEN_OFF, 0, 0, 0, 0)
3252

3353

3454
def ScreenOn(ser):
35-
SendReg(ser, 109, 0, 0, 0, 0)
55+
SendReg(ser, Command.SCREEN_ON, 0, 0, 0, 0)
3656

3757

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

4262

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]
4766

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

50-
pix = im.load()
69+
pix = image.load()
70+
line = bytes()
5171
for h in range(image_height):
52-
line = bytes()
5372
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
5876

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:
6187
ser.write(line)
6288

6389
sleep(0.01) # Wait 10 ms after picture display
6490

6591

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+
66127
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)
68142

69143
# Clear screen (blank)
70-
Clear(ser)
144+
Clear(lcd_comm)
71145

72146
# Set brightness to max value
73-
SetBrightness(ser, 0)
147+
SetBrightness(lcd_comm, 0)
74148

75149
# 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

Comments
 (0)