smartscreen-driver 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smartscreen_driver/lcd_comm.py +234 -0
- smartscreen_driver/lcd_comm_rev_a.py +225 -0
- smartscreen_driver/lcd_comm_rev_b.py +284 -0
- smartscreen_driver/lcd_comm_rev_c.py +409 -0
- smartscreen_driver/lcd_comm_rev_d.py +194 -0
- smartscreen_driver/lcd_simulated.py +168 -0
- smartscreen_driver/serialize.py +58 -0
- smartscreen_driver-0.2.0.dist-info/METADATA +32 -0
- smartscreen_driver-0.2.0.dist-info/RECORD +11 -0
- smartscreen_driver-0.2.0.dist-info/WHEEL +4 -0
- smartscreen_driver-0.2.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,194 @@
|
|
1
|
+
# turing-smart-screen-python - a Python system monitor and library for USB-C displays like Turing Smart Screen or XuanFang
|
2
|
+
# https://github.com/mathoudebine/turing-smart-screen-python/
|
3
|
+
|
4
|
+
# Copyright (C) 2021-2023 Matthieu Houdebine (mathoudebine)
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
from enum import Enum
|
20
|
+
import logging
|
21
|
+
from typing import Optional, Tuple
|
22
|
+
import queue
|
23
|
+
|
24
|
+
from serial.tools.list_ports import comports
|
25
|
+
from PIL import Image
|
26
|
+
|
27
|
+
from .lcd_comm import LcdComm, Orientation
|
28
|
+
from .serialize import image_to_rgb565, chunked
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
class Command(Enum):
|
34
|
+
GETINFO = bytearray((71, 00, 00, 00))
|
35
|
+
SETORG = bytearray((67, 72, 00, 00)) # Set portrait orientation
|
36
|
+
SET180 = bytearray((67, 71, 00, 00)) # Set reverse portrait orientation
|
37
|
+
SETHF = bytearray(
|
38
|
+
(67, 68, 00, 00)
|
39
|
+
) # Set portrait orientation with horizontal mirroring
|
40
|
+
SETVF = bytearray(
|
41
|
+
(67, 70, 00, 00)
|
42
|
+
) # Set reverse portrait orientation with horizontal mirroring
|
43
|
+
SETBL = bytearray((67, 67)) # Brightness setting
|
44
|
+
DISPCOLOR = bytearray((67, 66)) # Display RGB565 color on whole screen
|
45
|
+
BLOCKWRITE = bytearray((67, 65)) # Send bitmap size
|
46
|
+
INTOPICMODE = bytearray((68, 00, 00, 00)) # Start bitmap transmission
|
47
|
+
OUTPICMODE = bytearray((65, 00, 00, 00)) # End bitmap transmission
|
48
|
+
|
49
|
+
|
50
|
+
# This class is for Kipye Qiye Smart Display 3.5"
|
51
|
+
class LcdCommRevD(LcdComm):
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
com_port: str = "AUTO",
|
55
|
+
display_width: int = 320,
|
56
|
+
display_height: int = 480,
|
57
|
+
update_queue: Optional[queue.Queue] = None,
|
58
|
+
):
|
59
|
+
logger.debug("HW revision: D")
|
60
|
+
LcdComm.__init__(self, com_port, display_width, display_height, update_queue)
|
61
|
+
self.open_serial()
|
62
|
+
|
63
|
+
def __del__(self):
|
64
|
+
self.close_serial()
|
65
|
+
|
66
|
+
@staticmethod
|
67
|
+
def auto_detect_com_port() -> Optional[str]:
|
68
|
+
com_ports = comports()
|
69
|
+
auto_com_port = None
|
70
|
+
|
71
|
+
for com_port in com_ports:
|
72
|
+
if com_port.vid == 0x454D and com_port.pid == 0x4E41:
|
73
|
+
auto_com_port = com_port.device
|
74
|
+
break
|
75
|
+
|
76
|
+
return auto_com_port
|
77
|
+
|
78
|
+
def write_data(self, data: bytearray):
|
79
|
+
LcdComm.write_data(self, data)
|
80
|
+
|
81
|
+
# Empty the input buffer after each write: we don't process acknowledgements the screen sends back
|
82
|
+
self.serial_flush_input()
|
83
|
+
|
84
|
+
def send_command(
|
85
|
+
self,
|
86
|
+
cmd: Command,
|
87
|
+
payload: Optional[bytearray] = None,
|
88
|
+
bypass_queue: bool = False,
|
89
|
+
):
|
90
|
+
message = bytearray(cmd.value)
|
91
|
+
|
92
|
+
if payload:
|
93
|
+
message.extend(payload)
|
94
|
+
|
95
|
+
# If no queue for async requests, or if asked explicitly to do the request sequentially: do request now
|
96
|
+
if not self.update_queue or bypass_queue:
|
97
|
+
self.write_data(message)
|
98
|
+
else:
|
99
|
+
# Lock queue mutex then queue the request
|
100
|
+
with self.update_queue_mutex:
|
101
|
+
self.update_queue.put((self.write_data, [message]))
|
102
|
+
|
103
|
+
def initialize_comm(self):
|
104
|
+
pass
|
105
|
+
|
106
|
+
def reset(self):
|
107
|
+
# HW revision D does not implement a command to reset it: clear display instead
|
108
|
+
self.clear()
|
109
|
+
|
110
|
+
def clear(self):
|
111
|
+
# HW revision D does not implement a Clear command: display a blank image on the whole screen
|
112
|
+
color = 0xFFFF # RGB565 White color
|
113
|
+
color_bytes = bytearray(color.to_bytes(2, "big"))
|
114
|
+
self.send_command(cmd=Command.DISPCOLOR, payload=color_bytes)
|
115
|
+
|
116
|
+
def screen_off(self):
|
117
|
+
# HW revision D does not implement a "ScreenOff" native command: using SetBrightness(0) instead
|
118
|
+
self.set_brightness(0)
|
119
|
+
|
120
|
+
def screen_on(self):
|
121
|
+
# HW revision D does not implement a "ScreenOn" native command: using SetBrightness() instead
|
122
|
+
self.set_brightness()
|
123
|
+
|
124
|
+
def set_brightness(self, level: int = 25):
|
125
|
+
assert 0 <= level <= 100, "Brightness level must be [0-100]"
|
126
|
+
|
127
|
+
# Brightness scales from 0 to 500, with 500 being the brightest and 0 being the darkest.
|
128
|
+
# Convert our brightness % to an absolute value.
|
129
|
+
converted_level = level * 5
|
130
|
+
|
131
|
+
level_bytes = bytearray(converted_level.to_bytes(2, "big"))
|
132
|
+
|
133
|
+
# Send the command twice because sometimes it is not applied...
|
134
|
+
self.send_command(cmd=Command.SETBL, payload=level_bytes)
|
135
|
+
self.send_command(cmd=Command.SETBL, payload=level_bytes)
|
136
|
+
|
137
|
+
def set_orientation(self, orientation: Orientation = Orientation.PORTRAIT):
|
138
|
+
# In revision D, reverse orientations (reverse portrait / reverse landscape) are managed by the display
|
139
|
+
# Basic orientations (portrait / landscape) are software-managed because screen commands only support portrait
|
140
|
+
self.orientation = orientation
|
141
|
+
|
142
|
+
if (
|
143
|
+
self.orientation == Orientation.REVERSE_LANDSCAPE
|
144
|
+
or self.orientation == Orientation.REVERSE_PORTRAIT
|
145
|
+
):
|
146
|
+
self.send_command(cmd=Command.SET180)
|
147
|
+
else:
|
148
|
+
self.send_command(cmd=Command.SETORG)
|
149
|
+
|
150
|
+
def paint(
|
151
|
+
self,
|
152
|
+
image: Image.Image,
|
153
|
+
pos: Tuple[int, int] = (0, 0),
|
154
|
+
):
|
155
|
+
image = self._crop_to_display_bounds(image, pos)
|
156
|
+
image_width, image_height = image.size[0], image.size[1]
|
157
|
+
|
158
|
+
if image_height == 0 or image_width == 0:
|
159
|
+
return
|
160
|
+
|
161
|
+
x, y = pos
|
162
|
+
if (
|
163
|
+
self.orientation == Orientation.PORTRAIT
|
164
|
+
or self.orientation == Orientation.REVERSE_PORTRAIT
|
165
|
+
):
|
166
|
+
(x0, y0) = (x, y)
|
167
|
+
(x1, y1) = (x + image_width - 1, y + image_height - 1)
|
168
|
+
else:
|
169
|
+
# Landscape / reverse landscape orientations are software managed: rotate image -90° and get new coordinates
|
170
|
+
image = image.rotate(270, expand=True)
|
171
|
+
(x0, y0) = (self.display_width - y - image_height, x)
|
172
|
+
(x1, y1) = (self.display_width - y - 1, x + image_width - 1)
|
173
|
+
image_width, image_height = image_height, image_width
|
174
|
+
|
175
|
+
# Send bitmap size
|
176
|
+
image_data = bytearray()
|
177
|
+
image_data += x0.to_bytes(2, "big")
|
178
|
+
image_data += x1.to_bytes(2, "big")
|
179
|
+
image_data += y0.to_bytes(2, "big")
|
180
|
+
image_data += y1.to_bytes(2, "big")
|
181
|
+
self.send_command(cmd=Command.BLOCKWRITE, payload=image_data)
|
182
|
+
|
183
|
+
# Prepare bitmap data transmission
|
184
|
+
self.send_command(Command.INTOPICMODE)
|
185
|
+
|
186
|
+
rgb565be = image_to_rgb565(image, "big")
|
187
|
+
|
188
|
+
# Lock queue mutex then queue all the requests for the image data
|
189
|
+
with self.update_queue_mutex:
|
190
|
+
for chunk in chunked(rgb565be, 63):
|
191
|
+
self.send_line(b"\x50" + chunk)
|
192
|
+
|
193
|
+
# Indicate the complete bitmap has been transmitted
|
194
|
+
self.send_command(Command.OUTPICMODE)
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# turing-smart-screen-python - a Python system monitor and library for USB-C displays like Turing Smart Screen or XuanFang
|
2
|
+
# https://github.com/mathoudebine/turing-smart-screen-python/
|
3
|
+
|
4
|
+
# Copyright (C) 2021-2023 Matthieu Houdebine (mathoudebine)
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
import mimetypes
|
20
|
+
import shutil
|
21
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
22
|
+
from typing import Optional, Tuple
|
23
|
+
import queue
|
24
|
+
import threading
|
25
|
+
import logging
|
26
|
+
|
27
|
+
from PIL import Image
|
28
|
+
|
29
|
+
from .lcd_comm import LcdComm, Orientation
|
30
|
+
|
31
|
+
logger = logging.getLogger(__name__)
|
32
|
+
|
33
|
+
SCREENSHOT_FILE = "screencap.png"
|
34
|
+
WEBSERVER_PORT = 5678
|
35
|
+
|
36
|
+
|
37
|
+
# This webserver offer a blank page displaying simulated screen with auto-refresh
|
38
|
+
class SimulatedLcdWebServer(BaseHTTPRequestHandler):
|
39
|
+
def log_message(self, format, *args):
|
40
|
+
return
|
41
|
+
|
42
|
+
def do_GET(self): # noqa: N802
|
43
|
+
if self.path == "/":
|
44
|
+
self.send_response(200)
|
45
|
+
self.send_header("Content-type", "text/html")
|
46
|
+
self.end_headers()
|
47
|
+
self.wfile.write(
|
48
|
+
bytes('<img src="' + SCREENSHOT_FILE + '" id="myImage" />', "utf-8")
|
49
|
+
)
|
50
|
+
self.wfile.write(bytes("<script>", "utf-8"))
|
51
|
+
self.wfile.write(bytes("setInterval(function() {", "utf-8"))
|
52
|
+
self.wfile.write(
|
53
|
+
bytes(
|
54
|
+
" var myImageElement = document.getElementById('myImage');",
|
55
|
+
"utf-8",
|
56
|
+
)
|
57
|
+
)
|
58
|
+
self.wfile.write(
|
59
|
+
bytes(
|
60
|
+
" myImageElement.src = '"
|
61
|
+
+ SCREENSHOT_FILE
|
62
|
+
+ "?rand=' + Math.random();",
|
63
|
+
"utf-8",
|
64
|
+
)
|
65
|
+
)
|
66
|
+
self.wfile.write(bytes("}, 250);", "utf-8"))
|
67
|
+
self.wfile.write(bytes("</script>", "utf-8"))
|
68
|
+
elif self.path.startswith("/" + SCREENSHOT_FILE):
|
69
|
+
imgfile = open(SCREENSHOT_FILE, "rb").read()
|
70
|
+
mimetype = mimetypes.MimeTypes().guess_type(SCREENSHOT_FILE)[0]
|
71
|
+
self.send_response(200)
|
72
|
+
if mimetype is not None:
|
73
|
+
self.send_header("Content-type", mimetype)
|
74
|
+
self.end_headers()
|
75
|
+
self.wfile.write(imgfile)
|
76
|
+
|
77
|
+
|
78
|
+
# Simulated display: write on a file instead of serial port
|
79
|
+
class LcdSimulated(LcdComm):
|
80
|
+
def __init__(
|
81
|
+
self,
|
82
|
+
com_port: str = "AUTO",
|
83
|
+
display_width: int = 320,
|
84
|
+
display_height: int = 480,
|
85
|
+
update_queue: Optional[queue.Queue] = None,
|
86
|
+
):
|
87
|
+
LcdComm.__init__(self, com_port, display_width, display_height, update_queue)
|
88
|
+
self.screen_image = Image.new(
|
89
|
+
"RGB", (self.width(), self.height()), (255, 255, 255)
|
90
|
+
)
|
91
|
+
self.screen_image.save("tmp", "PNG")
|
92
|
+
shutil.copyfile("tmp", SCREENSHOT_FILE)
|
93
|
+
self.orientation = Orientation.PORTRAIT
|
94
|
+
|
95
|
+
try:
|
96
|
+
self.webServer = HTTPServer(
|
97
|
+
("localhost", WEBSERVER_PORT), SimulatedLcdWebServer
|
98
|
+
)
|
99
|
+
logger.debug(
|
100
|
+
"To see your simulated screen, open http://%s:%d in a browser"
|
101
|
+
% ("localhost", WEBSERVER_PORT)
|
102
|
+
)
|
103
|
+
threading.Thread(target=self.webServer.serve_forever).start()
|
104
|
+
except OSError:
|
105
|
+
logger.error(
|
106
|
+
"Error starting webserver! An instance might already be running on port %d."
|
107
|
+
% WEBSERVER_PORT
|
108
|
+
)
|
109
|
+
|
110
|
+
def __del__(self):
|
111
|
+
self.close_serial()
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def auto_detect_com_port() -> Optional[str]:
|
115
|
+
return None
|
116
|
+
|
117
|
+
def close_serial(self):
|
118
|
+
logger.debug("Shutting down web server")
|
119
|
+
self.webServer.shutdown()
|
120
|
+
|
121
|
+
def initialize_comm(self):
|
122
|
+
pass
|
123
|
+
|
124
|
+
def reset(self):
|
125
|
+
pass
|
126
|
+
|
127
|
+
def clear(self):
|
128
|
+
self.set_orientation(self.orientation)
|
129
|
+
|
130
|
+
def screen_off(self):
|
131
|
+
pass
|
132
|
+
|
133
|
+
def screen_on(self):
|
134
|
+
pass
|
135
|
+
|
136
|
+
def set_brightness(self, level: int = 25):
|
137
|
+
pass
|
138
|
+
|
139
|
+
def set_backplate_led_color(
|
140
|
+
self, led_color: Tuple[int, int, int] = (255, 255, 255)
|
141
|
+
):
|
142
|
+
pass
|
143
|
+
|
144
|
+
def set_orientation(self, orientation: Orientation = Orientation.PORTRAIT):
|
145
|
+
self.orientation = orientation
|
146
|
+
# Just draw the screen again with the new width/height based on orientation
|
147
|
+
with self.update_queue_mutex:
|
148
|
+
self.screen_image = Image.new(
|
149
|
+
"RGB", (self.width(), self.height()), (255, 255, 255)
|
150
|
+
)
|
151
|
+
self.screen_image.save("tmp", "PNG")
|
152
|
+
shutil.copyfile("tmp", SCREENSHOT_FILE)
|
153
|
+
|
154
|
+
def paint(
|
155
|
+
self,
|
156
|
+
image: Image.Image,
|
157
|
+
pos: Tuple[int, int] = (0, 0),
|
158
|
+
):
|
159
|
+
image = self._crop_to_display_bounds(image, pos)
|
160
|
+
image_width, image_height = image.size[0], image.size[1]
|
161
|
+
|
162
|
+
if image_height == 0 or image_width == 0:
|
163
|
+
return
|
164
|
+
|
165
|
+
with self.update_queue_mutex:
|
166
|
+
self.screen_image.paste(image, pos)
|
167
|
+
self.screen_image.save("tmp", "PNG")
|
168
|
+
shutil.copyfile("tmp", SCREENSHOT_FILE)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from typing import Iterator, Literal
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from PIL import Image
|
5
|
+
|
6
|
+
|
7
|
+
def chunked(data: bytes, chunk_size: int) -> Iterator[bytes]:
|
8
|
+
for i in range(0, len(data), chunk_size):
|
9
|
+
yield data[i : i + chunk_size]
|
10
|
+
|
11
|
+
|
12
|
+
def image_to_rgb565(image: Image.Image, endianness: Literal["big", "little"]) -> bytes:
|
13
|
+
if image.mode not in ["RGB", "RGBA"]:
|
14
|
+
# we need the first 3 channels to be R, G and B
|
15
|
+
image = image.convert("RGB")
|
16
|
+
|
17
|
+
rgb = np.asarray(image)
|
18
|
+
|
19
|
+
# flatten the first 2 dimensions (width and height) into a single stream
|
20
|
+
# of RGB pixels
|
21
|
+
rgb = rgb.reshape((image.size[1] * image.size[0], -1))
|
22
|
+
|
23
|
+
# extract R, G, B channels and promote them to 16 bits
|
24
|
+
r = rgb[:, 0].astype(np.uint16)
|
25
|
+
g = rgb[:, 1].astype(np.uint16)
|
26
|
+
b = rgb[:, 2].astype(np.uint16)
|
27
|
+
|
28
|
+
# construct RGB565
|
29
|
+
r = r >> 3
|
30
|
+
g = g >> 2
|
31
|
+
b = b >> 3
|
32
|
+
rgb565 = (r << 11) | (g << 5) | b
|
33
|
+
|
34
|
+
# serialize to the correct endianness
|
35
|
+
if endianness == "big":
|
36
|
+
typ = ">u2"
|
37
|
+
else:
|
38
|
+
typ = "<u2"
|
39
|
+
return rgb565.astype(typ).tobytes()
|
40
|
+
|
41
|
+
|
42
|
+
def image_to_bgr(image: Image.Image) -> bytes:
|
43
|
+
if image.mode not in ["RGB", "RGBA"]:
|
44
|
+
# we need the first 3 channels to be R, G and B
|
45
|
+
image = image.convert("RGB")
|
46
|
+
rgb = np.asarray(image)
|
47
|
+
# same as rgb[:, :, [2, 1, 0]] but faster
|
48
|
+
bgr = np.take(rgb, (2, 1, 0), axis=-1)
|
49
|
+
return bgr.tobytes()
|
50
|
+
|
51
|
+
|
52
|
+
def image_to_bgra(image: Image.Image) -> bytes:
|
53
|
+
if image.mode != "RGBA":
|
54
|
+
image = image.convert("RGBA")
|
55
|
+
rgba = np.asarray(image)
|
56
|
+
# same as rgba[:, :, [2, 1, 0, 3]] but faster
|
57
|
+
bgra = np.take(rgba, (2, 1, 0, 3), axis=-1)
|
58
|
+
return bgra.tobytes()
|
@@ -0,0 +1,32 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: smartscreen-driver
|
3
|
+
Version: 0.2.0
|
4
|
+
Summary: Driver for serial-over-USB displays; library extracted from turing-smart-screen-python
|
5
|
+
Author: Mathieu Houdebine
|
6
|
+
Author-email: Hugo Chargois <hugo.chargois@free.fr>
|
7
|
+
License-File: LICENSE
|
8
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Requires-Python: >=3.9
|
12
|
+
Requires-Dist: numpy>=2.0.2
|
13
|
+
Requires-Dist: pillow>=11.1.0
|
14
|
+
Requires-Dist: pyserial>=3.5
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
17
|
+
# smartscreen-driver
|
18
|
+
|
19
|
+
This package contains drivers for low-cost serial-over-USB displays such as
|
20
|
+
the Turing Smart Screen.
|
21
|
+
|
22
|
+
This library is simply an extract of the driver code from the
|
23
|
+
[turing-smart-screen-python](https://github.com/mathoudebine/turing-smart-screen-python)
|
24
|
+
project, removing all the sensors and UI code and dependencies, fixing coding
|
25
|
+
conventions violations (PEP8 et al) and adding proper Python packaging.
|
26
|
+
|
27
|
+
The usage is straightforward:
|
28
|
+
|
29
|
+
- you open the connection with the correct `LcdCommRevX` depending on your display
|
30
|
+
- you `paint()` (PIL) images to the display
|
31
|
+
|
32
|
+
See `hello_world.py` for an example.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
smartscreen_driver/lcd_comm.py,sha256=AIkdLZfHFHE8I5TV1OocVvf4j8TBFlm_OBITOG4IQZ8,7608
|
2
|
+
smartscreen_driver/lcd_comm_rev_a.py,sha256=vnhd4UaWSVLhxXNBwsKi3f5jcIASBnj2gqQUnePgP7c,8271
|
3
|
+
smartscreen_driver/lcd_comm_rev_b.py,sha256=HJaDXKILtdHpuWiKGA5wa_u6q-zvuWhYrr1zcN4T43s,10896
|
4
|
+
smartscreen_driver/lcd_comm_rev_c.py,sha256=gxaAcX8IdTIq3V_kd_Bb3C10vSYxyguX0FyROsC6M_Q,13795
|
5
|
+
smartscreen_driver/lcd_comm_rev_d.py,sha256=16H_rNbYP66nebAePvZ897FIHjVjIeinLGm7-P2TP_w,7290
|
6
|
+
smartscreen_driver/lcd_simulated.py,sha256=ZI6-fUuFeps7_-0AyrQe2Ygo3dF3qfm8ougw0nNRFhE,5624
|
7
|
+
smartscreen_driver/serialize.py,sha256=xG_CBRutI8q3D3kTsG7dhLA0QYmQ_5LCZr9bfC4pey8,1671
|
8
|
+
smartscreen_driver-0.2.0.dist-info/METADATA,sha256=jlEFh2iP7lE2FXC_KJWOsqL5N2EUWEdOTZ6Sw5d-_HU,1203
|
9
|
+
smartscreen_driver-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
smartscreen_driver-0.2.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
11
|
+
smartscreen_driver-0.2.0.dist-info/RECORD,,
|