Skip to content

Commit 46720b8

Browse files
committed
Update README, split main.py in 2 versions for supported HW revisions A & B/flagship
1 parent e9b24dc commit 46720b8

File tree

3 files changed

+260
-53
lines changed

3 files changed

+260
-53
lines changed

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,41 @@ This project is an open-source alternative software, not the USBMonitor.exe orig
1717
A simple Python manager for "Turing Smart Screen" 3.5" IPS USB-C (UART) display, also known as :
1818
- Turing USB35INCHIPS / USB35INCHIPSV2
1919
- 3.5 Inch Mini Screen
20-
- [3.5 Inch 320*480 Mini Capacitive Touch Screen IPS Module](https://www.aliexpress.com/item/1005002505149293.html)
20+
- [3.5 Inch 320*480 Mini Capacitive Touch Screen IPS Module](https://www.aliexpress.com/item/1005003723773653.html)
2121

22-
Operating systems supported : macOS, Windows, Linux (incl. Raspberry Pi) and all OS that support Python3.7
23-
22+
## Hardware
2423
<img src="res/pics/smart-screen-3.webp" width="500"/>
2524

26-
This is a 3.5" USB-C display that shows as a serial port once connected.
27-
It cannot be seen by the operating system as a monitor but picture can be displayed on it.
25+
The Turing Smart Screen is a 3.5" USB-C display that shows as a serial port once connected.
26+
It cannot be seen by the operating system as a monitor but pictures can be displayed on it.
2827

29-
A Windows-only software is [available in Chinese](https://lgb123-1253504678.cos.ap-beijing.myqcloud.com/35inch.rar) or [in English](https://lgb123-1253504678.cos.ap-beijing.myqcloud.com/35inchENG.rar) to manage this display.
28+
There is 3 hardware revisions of the screen: [how to identify my version?](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions) Version B and "flagship" use the same protocol.
29+
A [Windows-only software is available](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Vendor-apps) is provided by the vendor to manage this display.
3030
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.
3131

3232
## Features
3333
This Python script can do some simple operations on the Turing display like :
3434
- **Display custom picture**
3535
- **Display text**
3636
- **Display progress bar**
37-
- Clear the screen (blank)
38-
- Turn the screen on/off
39-
- Display soft reset
37+
- Clear the screen (blank) - HW version A only
38+
- Turn the screen on/off - HW version A only
39+
- Display soft reset - HW version A only
4040
- Set brightness
4141

4242
Not yet implemented:
4343
- Screen rotation
4444

45+
Operating systems supported : macOS, Windows, Linux (incl. Raspberry Pi) and all OS that support Python3.7
46+
4547
## Getting started
4648
_Python knowledges recommended._
47-
Download the `main.py` file from this project
48-
Download and install latest Python 3.x (min. 3.7) for your OS: https://www.python.org/downloads/
49+
Download this project by cloning it or using the [Releases sections](https://github.com/mathoudebine/turing-smart-screen-python/releases)
50+
Download and install the latest Python 3.x (min. 3.7) for your OS: https://www.python.org/downloads/
4951
Plug your Turing display to your computer (install the drivers if on Windows)
50-
Open the `main.py` file and edit the [`COM_PORT`](https://github.com/mathoudebine/turing-smart-screen-python/blob/deb0a60b772f2c5acef377f13b959632ca649f9f/main.py#L15) variable to the port used by the display
51-
Open a terminal and run `python3 main.py` or `py -3 main.py` depending on your OS
52+
[Identify your hardware revision (version A or version B/flagship)](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions)
53+
Open the `mainVersionA.py` or `mainVersionB.py` file and edit the [`COM_PORT`](https://github.com/mathoudebine/turing-smart-screen-python/blob/deb0a60b772f2c5acef377f13b959632ca649f9f/main.py#L15) variable to the port used by the display
54+
Open a terminal and run `python3 mainVersionA.py / mainVersionB.py` or `py -3 mainVersionA.py / mainVersionB.py` depending on your OS
5255
You should see animated content on your Turing display!
5356

54-
You can then edit the `main.py` file to change the content displayed, or use this file as a Python module for your personal Python project
57+
You can then edit the `mainVersionA.py / mainVersionB.py` file to change the content displayed, or use this file as a Python module for your personal Python project

mainVersionA.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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
7+
import struct
8+
from datetime import datetime
9+
from time import sleep
10+
11+
import serial # Install pyserial : pip install pyserial
12+
from PIL import Image, ImageDraw, ImageFont # Install PIL or Pillow
13+
14+
# Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux...
15+
COM_PORT = "/dev/ttyACM0"
16+
# COM_PORT = "COM5"
17+
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: serial.Serial, cmd: int, x: int, y: int, ex: int, ey: int):
32+
byteBuffer = bytearray(6)
33+
byteBuffer[0] = (x >> 2)
34+
byteBuffer[1] = (((x & 3) << 6) + (y >> 4))
35+
byteBuffer[2] = (((y & 15) << 4) + (ex >> 6))
36+
byteBuffer[3] = (((ex & 63) << 2) + (ey >> 8))
37+
byteBuffer[4] = (ey & 255)
38+
byteBuffer[5] = cmd
39+
ser.write(bytes(byteBuffer))
40+
41+
42+
def Reset(ser: serial.Serial):
43+
SendReg(ser, Command.RESET, 0, 0, 0, 0)
44+
45+
46+
def Clear(ser: serial.Serial):
47+
SendReg(ser, Command.CLEAR, 0, 0, 0, 0)
48+
49+
50+
def ScreenOff(ser: serial.Serial):
51+
SendReg(ser, Command.SCREEN_OFF, 0, 0, 0, 0)
52+
53+
54+
def ScreenOn(ser: serial.Serial):
55+
SendReg(ser, Command.SCREEN_ON, 0, 0, 0, 0)
56+
57+
58+
def SetBrightness(ser: serial.Serial, level: int):
59+
# Level : 0 (brightest) - 255 (darkest)
60+
assert 255 >= level >= 0, 'Brightness level must be [0-255]'
61+
SendReg(ser, Command.SET_BRIGHTNESS, level, 0, 0, 0)
62+
63+
64+
def DisplayPILImage(ser: serial.Serial, image: Image, x: int, y: int):
65+
image_height = image.size[1]
66+
image_width = image.size[0]
67+
68+
assert image_height > 0, 'Image width must be > 0'
69+
assert image_width > 0, 'Image height must be > 0'
70+
71+
SendReg(ser, Command.DISPLAY_BITMAP, x, y, x + image_width - 1, y + image_height - 1)
72+
73+
pix = image.load()
74+
line = bytes()
75+
for h in range(image_height):
76+
for w in range(image_width):
77+
R = pix[w, h][0] >> 3
78+
G = pix[w, h][1] >> 2
79+
B = pix[w, h][2] >> 3
80+
81+
rgb = (R << 11) | (G << 5) | B
82+
line += struct.pack('H', rgb)
83+
84+
# Send image data by multiple of DISPLAY_WIDTH bytes
85+
if len(line) >= DISPLAY_WIDTH * 8:
86+
ser.write(line)
87+
line = bytes()
88+
89+
# Write last line if needed
90+
if len(line) > 0:
91+
ser.write(line)
92+
93+
sleep(0.01) # Wait 10 ms after picture display
94+
95+
96+
def DisplayBitmap(ser: serial.Serial, bitmap_path: str, x=0, y=0):
97+
image = Image.open(bitmap_path)
98+
DisplayPILImage(ser, image, x, y)
99+
100+
101+
def DisplayText(ser: serial.Serial, text: str, x=0, y=0,
102+
font="roboto/Roboto-Regular.ttf",
103+
font_size=20,
104+
font_color=(0, 0, 0),
105+
background_color=(255, 255, 255),
106+
background_image: str = None):
107+
# Convert text to bitmap using PIL and display it
108+
# Provide the background image path to display text with transparent background
109+
110+
assert len(text) > 0, 'Text must not be empty'
111+
assert font_size > 0, "Font size must be > 0"
112+
113+
if background_image is None:
114+
# A text bitmap is created with max width/height by default : text with solid background
115+
text_image = Image.new('RGB', (DISPLAY_WIDTH, DISPLAY_HEIGHT), background_color)
116+
else:
117+
# The text bitmap is created from provided background image : text with transparent background
118+
text_image = Image.open(background_image)
119+
120+
# Draw text with specified color & font (also crop if text overflows display)
121+
font = ImageFont.truetype("./res/fonts/" + font, font_size)
122+
d = ImageDraw.Draw(text_image)
123+
d.text((x, y), text, font=font, fill=font_color)
124+
125+
# Crop text bitmap to keep only the text
126+
left, top, text_width, text_height = d.textbbox((0,0), text, font=font)
127+
text_image = text_image.crop(box=(x, y, min(x + text_width, DISPLAY_WIDTH), min(y + text_height, DISPLAY_HEIGHT)))
128+
129+
DisplayPILImage(ser, text_image, x, y)
130+
131+
132+
def DisplayProgressBar(ser: serial.Serial, x: int, y: int, width: int, height: int, min_value=0, max_value=100,
133+
value=50,
134+
bar_color=(0, 0, 0),
135+
bar_outline=True,
136+
background_color=(255, 255, 255),
137+
background_image: str = None):
138+
# Generate a progress bar and display it
139+
# Provide the background image path to display progress bar with transparent background
140+
141+
assert x + width <= DISPLAY_WIDTH, 'Progress bar width exceeds display width'
142+
assert y + height <= DISPLAY_HEIGHT, 'Progress bar height exceeds display height'
143+
assert min_value <= value <= max_value, 'Progress bar value shall be between min and max'
144+
145+
if background_image is None:
146+
# A bitmap is created with solid background
147+
bar_image = Image.new('RGB', (width, height), background_color)
148+
else:
149+
# A bitmap is created from provided background image
150+
bar_image = Image.open(background_image)
151+
152+
# Crop bitmap to keep only the progress bar background
153+
bar_image = bar_image.crop(box=(x, y, x + width, y + height))
154+
155+
# Draw progress bar
156+
bar_filled_width = value / (max_value - min_value) * width
157+
draw = ImageDraw.Draw(bar_image)
158+
draw.rectangle([0, 0, bar_filled_width-1, height-1], fill=bar_color, outline=bar_color)
159+
160+
if bar_outline:
161+
# Draw outline
162+
draw.rectangle([0, 0, width-1, height-1], fill=None, outline=bar_color)
163+
164+
DisplayPILImage(ser, bar_image, x, y)
165+
166+
167+
stop = False
168+
169+
if __name__ == "__main__":
170+
171+
def sighandler(signum, frame):
172+
global stop
173+
stop = True
174+
175+
176+
# Set the signal handlers, to send a complete frame to the LCD before exit
177+
signal.signal(signal.SIGINT, sighandler)
178+
signal.signal(signal.SIGTERM, sighandler)
179+
is_posix = os.name == 'posix'
180+
if is_posix:
181+
signal.signal(signal.SIGQUIT, sighandler)
182+
183+
# Do not change COM port settings unless you know what you are doing
184+
lcd_comm = serial.Serial(COM_PORT, 115200, timeout=1, rtscts=1)
185+
186+
# Clear screen (blank)
187+
Clear(lcd_comm)
188+
189+
# Set brightness to max value
190+
SetBrightness(lcd_comm, 0)
191+
192+
# Display sample picture
193+
DisplayBitmap(lcd_comm, "res/example.png")
194+
195+
# Display sample text
196+
DisplayText(lcd_comm, "Basic text", 50, 100)
197+
198+
# Display custom text with solid background
199+
DisplayText(lcd_comm, "Custom italic text", 5, 150,
200+
font="roboto/Roboto-Italic.ttf",
201+
font_size=30,
202+
font_color=(0, 0, 255),
203+
background_color=(255, 255, 0))
204+
205+
# Display custom text with transparent background
206+
DisplayText(lcd_comm, "Transparent bold text", 5, 300,
207+
font="geforce/GeForce-Bold.ttf",
208+
font_size=30,
209+
font_color=(255, 255, 255),
210+
background_image="res/example.png")
211+
212+
# Display text that overflows
213+
DisplayText(lcd_comm, "Text overflow!", 5, 430,
214+
font="roboto/Roboto-Bold.ttf",
215+
font_size=60,
216+
font_color=(255, 255, 255),
217+
background_image="res/example.png")
218+
219+
# Display the current time and some progress bars as fast as possible
220+
bar_value = 0
221+
while not stop:
222+
DisplayText(lcd_comm, str(datetime.now().time()), 160, 2,
223+
font="roboto/Roboto-Bold.ttf",
224+
font_size=20,
225+
font_color=(255, 0, 0),
226+
background_image="res/example.png")
227+
228+
DisplayProgressBar(lcd_comm, 10, 40,
229+
width=140, height=30,
230+
min_value=0, max_value=100, value=bar_value,
231+
bar_color=(255, 255, 0), bar_outline=True,
232+
background_image="res/example.png")
233+
234+
DisplayProgressBar(lcd_comm, 160, 40,
235+
width=140, height=30,
236+
min_value=0, max_value=19, value=bar_value % 20,
237+
bar_color=(0, 255, 0), bar_outline=False,
238+
background_image="res/example.png")
239+
240+
bar_value = (bar_value + 2) % 101
241+
242+
lcd_comm.close()

main.py renamed to mainVersionB.py

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from PIL import Image, ImageDraw, ImageFont # Install PIL or Pillow
1313

1414
# Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux...
15-
COM_PORT = "/dev/ttyACM0"
15+
# COM_PORT = "/dev/ttyACM0"
1616
# COM_PORT = "COM5"
1717
# MacOS COM port:
1818
COM_PORT = '/dev/cu.usbmodem2017_2_251'
@@ -28,14 +28,6 @@ class TuringError(Exception):
2828

2929

3030
class Command:
31-
# Old protocol (6 byte packets, command in final byte)
32-
#RESET = 101
33-
#CLEAR = 102
34-
#SCREEN_OFF = 108
35-
#SCREEN_ON = 109
36-
#SET_BRIGHTNESS = 110
37-
#DISPLAY_BITMAP = 197
38-
3931
# New protocol (10 byte packets, framed with the command, 8 data bytes inside)
4032
HELLO = 0xCA
4133
ORIENTATION = 0xCB
@@ -49,17 +41,6 @@ class Command:
4941
SET_BRIGHTNESS = 0xCE
5042

5143

52-
def SendReg(ser: serial.Serial, cmd: int, x: int, y: int, ex: int, ey: int):
53-
byteBuffer = bytearray(6)
54-
byteBuffer[0] = (x >> 2)
55-
byteBuffer[1] = (((x & 3) << 6) + (y >> 4))
56-
byteBuffer[2] = (((y & 15) << 4) + (ex >> 6))
57-
byteBuffer[3] = (((ex & 63) << 2) + (ey >> 8))
58-
byteBuffer[4] = (ey & 255)
59-
byteBuffer[5] = cmd
60-
ser.write(bytes(byteBuffer))
61-
62-
6344
def SendCommand(ser: serial.Serial, cmd: int, payload=None):
6445
if payload is None:
6546
payload = [0] * 8
@@ -100,13 +81,6 @@ def Hello(ser: serial.Serial):
10081
# based on how I'd do it.
10182

10283

103-
def Clear(ser: serial.Serial):
104-
# Unknown what command this is
105-
print("Clear unknown")
106-
# Cannot find a 'clear' command
107-
#SendReg(ser, Command.CLEAR, 0, 0, 0, 0)
108-
109-
11084
def Orientation(ser: serial.Serial, state: int):
11185
print("Orientation: %r" % (state,))
11286
SendCommand(ser, Command.ORIENTATION, payload=[state])
@@ -120,18 +94,6 @@ def SetLighting(ser: serial.Serial, red: int, green: int, blue: int):
12094
SendCommand(ser, Command.LIGHTING, payload=[red, green, blue])
12195

12296

123-
def ScreenOff(ser: serial.Serial):
124-
print("Screen off unknown")
125-
# Cannot find a 'screen off' command
126-
#SendReg(ser, Command.SCREEN_OFF, 0, 0, 0, 0)
127-
128-
129-
def ScreenOn(ser: serial.Serial):
130-
print("Screen on unknown")
131-
# Cannot find a 'screen on' command
132-
#SendReg(ser, Command.SCREEN_ON, 0, 0, 0, 0)
133-
134-
13597
def SetBrightness(ser: serial.Serial, level: int):
13698
# Level : 0 (brightest) - 255 (darkest)
13799
assert 255 >= level >= 0, 'Brightness level must be [0-255]'

0 commit comments

Comments
 (0)