smartscreen-driver 0.2.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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,,
|