1+ import platform
2+ import queue
3+ import struct
4+ import subprocess
5+ import time
16from io import BytesIO
2- from PIL import Image
7+ from pathlib import Path
8+ from typing import Optional
9+
310import usb .core
411import usb .util
5- import struct
6- import time
7- import argparse
812from Crypto .Cipher import DES
913from PIL import Image
10- import time
11- import subprocess
12- from pathlib import Path
13- import platform
14+
1415from 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
2017VENDOR_ID = 0x1cbe
2118PRODUCT_ID = 0x0088
2219
20+
2321def 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+
3231def 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+
3838def 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+
4748def 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+
6668def 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+
8083def 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+
103109def delay_sync (dev ):
104110 send_sync_command (dev )
105111 time .sleep (0.2 )
106112
113+
107114def 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+
112120def 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+
116125def 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+
123133def 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+
130141def 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+
136148def 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+
148161def 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+
165179def 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+
177192def 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+
202215def 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+
210224def 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
237250def 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")
277292class 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