Skip to content

Commit 06e882d

Browse files
committed
Add comments, enable screen on/off using brightness
1 parent 9f58e5f commit 06e882d

File tree

2 files changed

+77
-58
lines changed

2 files changed

+77
-58
lines changed

library/display.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __init__(self):
8787
self.lcd = LcdCommRevC(com_port=config.CONFIG_DATA['config']['COM_PORT'],
8888
update_queue=config.update_queue, display_width=width, display_height=height)
8989
elif config.CONFIG_DATA["display"]["REVISION"] == "C_USB":
90-
# Because of issue with Turing rev. C size auto-detection, manually configure screen width/height from theme
90+
# On all USB models, manually configure screen width/height from theme
9191
self.lcd = LcdCommRevCUSB(display_width=width, display_height=height)
9292
elif config.CONFIG_DATA["display"]["REVISION"] == "D":
9393
self.lcd = LcdCommRevD(com_port=config.CONFIG_DATA['config']['COM_PORT'],

library/lcd/lcd_comm_rev_c_usb.py

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
1+
import platform
2+
import queue
3+
import struct
4+
import subprocess
5+
import time
16
from io import BytesIO
2-
from PIL import Image
7+
from pathlib import Path
8+
from typing import Optional
9+
310
import usb.core
411
import usb.util
5-
import struct
6-
import time
7-
import argparse
812
from Crypto.Cipher import DES
913
from PIL import Image
10-
import time
11-
import subprocess
12-
from pathlib import Path
13-
import platform
14+
1415
from library.lcd.lcd_comm import Orientation, LcdComm
15-
from library.lcd.serialize import image_to_BGRA, image_to_BGR, chunked
16-
from library.log import logger
17-
from typing import Optional, Tuple
18-
import queue
1916

2017
VENDOR_ID = 0x1cbe
2118
PRODUCT_ID = 0x0088
2219

20+
2321
def build_command_packet_header(a0: int) -> bytearray:
2422
packet = bytearray(500)
2523
packet[0] = a0
@@ -29,12 +27,14 @@ def build_command_packet_header(a0: int) -> bytearray:
2927
packet[4:8] = struct.pack('<I', timestamp)
3028
return packet
3129

30+
3231
def encrypt_with_des(key: bytes, data: bytes) -> bytes:
3332
cipher = DES.new(key, DES.MODE_CBC, key)
3433
padded_len = (len(data) + 7) // 8 * 8
3534
padded_data = data.ljust(padded_len, b'\x00')
3635
return cipher.encrypt(padded_data)
3736

37+
3838
def encrypt_command_packet(data: bytearray) -> bytearray:
3939
des_key = b'slv3tuzx'
4040
encrypted = encrypt_with_des(des_key, data)
@@ -44,6 +44,7 @@ def encrypt_command_packet(data: bytearray) -> bytearray:
4444
final_packet[511] = 26
4545
return final_packet
4646

47+
4748
def find_usb_device():
4849
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
4950
if dev is None:
@@ -63,6 +64,7 @@ def find_usb_device():
6364

6465
return dev
6566

67+
6668
def read_flush(ep_in, max_attempts=5):
6769
"""
6870
Flush the USB IN endpoint by reading available data until timeout or max attempts reached.
@@ -74,24 +76,27 @@ def read_flush(ep_in, max_attempts=5):
7476
if e.errno == 110 or e.args[0] == 'Operation timed out':
7577
break
7678
else:
77-
#print("Flush read error:", e)
79+
# print("Flush read error:", e)
7880
break
7981

82+
8083
def write_to_device(dev, data, timeout=2000):
8184
cfg = dev.get_active_configuration()
8285
intf = usb.util.find_descriptor(cfg, bInterfaceNumber=0)
8386
if intf is None:
8487
raise RuntimeError("USB interface 0 not found")
85-
ep_out = usb.util.find_descriptor(intf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
86-
ep_in = usb.util.find_descriptor(intf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
88+
ep_out = usb.util.find_descriptor(intf, custom_match=lambda e: usb.util.endpoint_direction(
89+
e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
90+
ep_in = usb.util.find_descriptor(intf, custom_match=lambda e: usb.util.endpoint_direction(
91+
e.bEndpointAddress) == usb.util.ENDPOINT_IN)
8792
assert ep_out is not None and ep_in is not None, "Could not find USB endpoints"
88-
93+
8994
try:
9095
ep_out.write(data, timeout)
9196
except usb.core.USBError as e:
9297
print("USB write error:", e)
9398
return None
94-
99+
95100
try:
96101
response = ep_in.read(512, timeout)
97102
read_flush(ep_in)
@@ -100,39 +105,46 @@ def write_to_device(dev, data, timeout=2000):
100105
print("USB read error:", e)
101106
return None
102107

108+
103109
def delay_sync(dev):
104110
send_sync_command(dev)
105111
time.sleep(0.2)
106112

113+
107114
def send_sync_command(dev):
108115
print("Sending Sync Command (ID 10)...")
109116
cmd_packet = build_command_packet_header(10)
110117
return write_to_device(dev, encrypt_command_packet(cmd_packet))
111118

119+
112120
def send_restart_device_command(dev):
113121
print("Sending Restart Command (ID 11)...")
114122
return write_to_device(dev, encrypt_command_packet(build_command_packet_header(11)))
115123

124+
116125
def send_brightness_command(dev, brightness: int):
117126
print(f"Sending Brightness Command (ID 14)...")
118127
print(f" Brightness = {brightness}")
119128
cmd_packet = build_command_packet_header(14)
120129
cmd_packet[8] = brightness
121130
return write_to_device(dev, encrypt_command_packet(cmd_packet))
122131

132+
123133
def send_frame_rate_command(dev, frame_rate: int):
124134
print(f"Sending Frame Rate Command (ID 15)...")
125135
print(f" Frame Rate = {frame_rate}")
126136
cmd_packet = build_command_packet_header(15)
127137
cmd_packet[8] = frame_rate
128138
return write_to_device(dev, encrypt_command_packet(cmd_packet))
129139

140+
130141
def format_bytes(val):
131142
if val > 1024 * 1024:
132143
return f"{val / (1024 * 1024):.2f} GB"
133144
else:
134145
return f"{val / 1024:.2f} MB"
135146

147+
136148
def send_refresh_storage_command(dev):
137149
print("Sending Refresh Storage Command (ID 100)...")
138150
response = write_to_device(dev, encrypt_command_packet(build_command_packet_header(100)))
@@ -145,6 +157,7 @@ def send_refresh_storage_command(dev):
145157
print(f" Card Used = {used}")
146158
print(f" Card Valid = {valid}")
147159

160+
148161
def send_save_settings_command(dev, brightness=0, startup=0, reserved=0, rotation=0, sleep=0, offline=0):
149162
print("Sending Save Settings Command (ID 125)...")
150163
print(f" Brightness: {brightness}")
@@ -162,6 +175,7 @@ def send_save_settings_command(dev, brightness=0, startup=0, reserved=0, rotatio
162175
cmd_packet[13] = offline
163176
return write_to_device(dev, encrypt_command_packet(cmd_packet))
164177

178+
165179
def send_image(dev, png_data: bytes):
166180
img_size = len(png_data)
167181

@@ -174,19 +188,17 @@ def send_image(dev, png_data: bytes):
174188
full_payload = encrypt_command_packet(cmd_packet) + png_data
175189
return write_to_device(dev, full_payload)
176190

191+
177192
def clear_image(dev):
178-
img_data = bytearray([
179-
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
180-
0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x07, 0x80, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, 0xf0, 0x84,
181-
0xf5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
182-
0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00,
183-
0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7,
184-
0x6f, 0xa8, 0x64, 0x00, 0x00, 0x0e, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x78, 0x5e, 0xed, 0xc1, 0x01,
185-
0x0d, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf7, 0x4f, 0x6d, 0x0f, 0x07, 0x14, 0x00, 0x00, 0x00, 0x00,
186-
] + [0x00] * 3568 + [
187-
0x00, 0xf0, 0x66, 0x4a, 0xc8, 0x00, 0x01, 0x11, 0x9d, 0x82, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x49,
188-
0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
189-
])
193+
img_data = bytearray(
194+
[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00,
195+
0x01, 0xe0, 0x00, 0x00, 0x07, 0x80, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, 0xf0, 0x84, 0xf5, 0x00, 0x00, 0x00,
196+
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41,
197+
0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
198+
0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, 0x00, 0x0e, 0x0c, 0x49, 0x44, 0x41,
199+
0x54, 0x78, 0x5e, 0xed, 0xc1, 0x01, 0x0d, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf7, 0x4f, 0x6d, 0x0f, 0x07, 0x14,
200+
0x00, 0x00, 0x00, 0x00, ] + [0x00] * 3568 + [0x00, 0xf0, 0x66, 0x4a, 0xc8, 0x00, 0x01, 0x11, 0x9d, 0x82,
201+
0x0a, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82])
190202
img_size = len(img_data)
191203
print(f" Chunk Size: {img_size} bytes")
192204

@@ -199,6 +211,7 @@ def clear_image(dev):
199211
full_payload = encrypt_command_packet(cmd_packet) + img_data
200212
return write_to_device(dev, full_payload)
201213

214+
202215
def delay(dev, rst):
203216
time.sleep(0.05)
204217
print("Sending Delay Command (ID 122)...")
@@ -207,20 +220,19 @@ def delay(dev, rst):
207220
if response and response[8] > rst:
208221
delay(dev, rst)
209222

223+
210224
def extract_h264_from_mp4(mp4_path: str):
211225
input_path = Path(mp4_path)
212226
if not input_path.exists():
213227
raise FileNotFoundError(f"Input file not found: {input_path}")
214228

215229
output_path = input_path.with_suffix(".h264")
216-
230+
217231
if output_path.exists():
218232
print(f"{output_path.name} already exists. Skipping extraction.")
219233
return output_path
220234

221-
cmd = [
222-
"ffmpeg",
223-
"-y", # overwrite without asking
235+
cmd = ["ffmpeg", "-y", # overwrite without asking
224236
"-i", str(input_path), # input file
225237
"-c:v", "copy", # copy video stream
226238
"-bsf:v", "h264_mp4toannexb", # convert to Annex-B
@@ -232,21 +244,22 @@ def extract_h264_from_mp4(mp4_path: str):
232244
print(f"Extracting H.264 from {input_path.name}...")
233245
subprocess.run(cmd, check=True)
234246
print(f"Done. Saved as {output_path.name}")
235-
return output_path
247+
return output_path
248+
236249

237250
def send_video(dev, video_path, loop=False):
238251
output_path = extract_h264_from_mp4(video_path)
239252
write_to_device(dev, encrypt_command_packet(build_command_packet_header(111)))
240253
write_to_device(dev, encrypt_command_packet(build_command_packet_header(112)))
241254
write_to_device(dev, encrypt_command_packet(build_command_packet_header(13)))
242-
send_brightness_command(dev, 32) #14
255+
send_brightness_command(dev, 32) # 14
243256
write_to_device(dev, encrypt_command_packet(build_command_packet_header(41)))
244-
clear_image(dev) #102, 3703
245-
send_frame_rate_command(dev, 25) #15
257+
clear_image(dev) # 102, 3703
258+
send_frame_rate_command(dev, 25) # 15
246259
# send_image(dev, './102_25011_payload.png') #102, 25011
247260
print("Sending Send Video Command (ID 121)...")
248261
try:
249-
while(True):
262+
while (True):
250263
with open(output_path, 'rb') as f:
251264
while True:
252265
data = f.read(202752)
@@ -274,34 +287,41 @@ def send_video(dev, video_path, loop=False):
274287
finally:
275288
write_to_device(dev, encrypt_command_packet(build_command_packet_header(123)))
276289

290+
291+
# This class is for Turing Smart Screen newer models (5.2" / 8" / 8.8" HW rev 1.x / 9.2")
277292
class LcdCommRevCUSB(LcdComm):
278-
def __init__(self, com_port: str = "USB", display_width: int = 480, display_height: int = 1920,
293+
def __init__(self, com_port: str = "AUTO", display_width: int = 480, display_height: int = 1920,
279294
update_queue: Optional[queue.Queue] = None):
280295
super().__init__(com_port, display_width, display_height, update_queue)
281296
self.dev = find_usb_device()
282-
self.image_parts = {}
283-
297+
# Store the current screen state as an image that will be continuously updated and sent
298+
self.current_state = Image.new("RGBA", (self.get_width(), self.get_height()), (0, 0, 0, 255))
299+
284300
def auto_detect_com_port(self):
285301
pass
286302

287303
def InitializeComm(self):
288304
send_sync_command(self.dev)
289305

290306
def Reset(self):
307+
# Do not enable the reset command for now on Turing USB models
291308
# send_restart_device_command(self.dev)
292309
pass
293310

294311
def Clear(self):
295312
clear_image(self.dev)
296313

297314
def ScreenOff(self):
298-
pass
315+
# Turing USB models do not implement a "screen off" command (that we know of): use SetBrightness(0) instead
316+
self.Clear()
317+
self.SetBrightness(0)
299318

300319
def ScreenOn(self):
301-
pass
320+
# Turing USB models do not implement a "screen off" command (that we know of): using SetBrightness() instead
321+
self.SetBrightness()
302322

303-
def SetBrightness(self, level: int):
304-
assert 0 <= level <= 100, 'Brightness must be 0~100'
323+
def SetBrightness(self, level: int = 25):
324+
assert 0 <= level <= 100, 'Brightness level must be [0-100]'
305325
converted = int(level / 100 * 102)
306326
send_brightness_command(self.dev, converted)
307327

@@ -322,23 +342,22 @@ def DisplayPILImage(self, image: Image.Image, x: int = 0, y: int = 0, image_widt
322342
if image_width != image.size[0] or image_height != image.size[1]:
323343
image = image.crop((0, 0, image_width, image_height))
324344

325-
self.image_parts[(x, y)] = image
326-
base_image = Image.new("RGBA", (self.get_width(), self.get_height()), (0, 0, 0, 0))
327-
328-
for (x, y), part in self.image_parts.items():
329-
base_image.paste(part, (x, y))
345+
# Paste new image over existing screen state
346+
self.current_state.paste(image, (x, y))
330347

348+
# Rotate image before sending to screen: all images sent to the screen are in portrait mode
331349
if self.orientation == Orientation.LANDSCAPE:
332-
base_image = base_image.transpose(Image.Transpose.ROTATE_270)
350+
base_image = self.current_state.transpose(Image.Transpose.ROTATE_270)
333351
elif self.orientation == Orientation.REVERSE_LANDSCAPE:
334-
base_image = base_image.transpose(Image.Transpose.ROTATE_90)
352+
base_image = self.current_state.transpose(Image.Transpose.ROTATE_90)
335353
elif self.orientation == Orientation.PORTRAIT:
336-
base_image = base_image.transpose(Image.Transpose.ROTATE_180)
337-
elif self.orientation == Orientation.REVERSE_PORTRAIT:
338-
pass
354+
base_image = self.current_state.transpose(Image.Transpose.ROTATE_180)
355+
else: # Orientation.REVERSE_PORTRAIT is initial screen orientation
356+
base_image = self.current_state
339357

358+
# Save as PNG format with headers
340359
buffer = BytesIO()
341360
base_image.save(buffer, format="PNG")
342-
png_data = buffer.getvalue()
343361

344-
send_image(self.dev, png_data)
362+
# Send PNG data
363+
send_image(self.dev, buffer.getvalue())

0 commit comments

Comments
 (0)