fastled 1.2.56__py3-none-any.whl → 1.2.58__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.
- fastled/__init__.py +1 -1
- fastled/assets/localhost-key.pem +28 -0
- fastled/assets/localhost.pem +27 -0
- fastled/filewatcher.py +52 -47
- fastled/keyz.py +30 -0
- fastled/open_browser.py +47 -61
- fastled/open_browser2.py +63 -77
- fastled/server_fastapi.py +60 -0
- fastled/server_fastapi_cli.py +60 -0
- fastled/settings.py +1 -1
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/METADATA +17 -4
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/RECORD +16 -11
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/WHEEL +1 -1
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.2.56.dist-info → fastled-1.2.58.dist-info}/top_level.txt +0 -0
fastled/__init__.py
CHANGED
@@ -13,7 +13,7 @@ from .types import BuildMode, CompileResult, CompileServerError
|
|
13
13
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
14
14
|
# that has any other change in the repo. Please bump the version as the
|
15
15
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
16
|
-
__version__ = "1.2.
|
16
|
+
__version__ = "1.2.58"
|
17
17
|
|
18
18
|
DOCKER_FILE = (
|
19
19
|
"https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
2
|
+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlxbWcpUXPpjqs
|
3
|
+
DJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcHS+pkgXnG46g6bUcL/AK5Ba08
|
4
|
+
vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh2jSCeOTagNoaHLYEugARkkEu
|
5
|
+
0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU7K+BLWsCiZ308NMpzHF5APZ6
|
6
|
+
YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXWyyPG0jwPQyjBot/cNtt8GrsN
|
7
|
+
gcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jprHEsw+/UhnG6Qhksm8C/DN9kP
|
8
|
+
hselewffAgMBAAECggEARjQ6YTo+Mkvf8WGGbRjLxteJRiBX7lKOD+V7aY2ce06P
|
9
|
+
21LREbbTCm+vljXZN2OnqvJomsjNLCsH21+jaTOIZg5x79LyDn2Au7N8CWdELwVT
|
10
|
+
mTbBO2Ql63P4R0UY54onGYNcOeV6z+OX9u7a8L/qYHCxFdHalBZpsfj0gjaQeStJ
|
11
|
+
JSnvGjo6tKkwC/nUmX01qEVQgUO1+39WYqCaIWjijZNXt6XiKclEuu1AkL0u6Mpt
|
12
|
+
CzvzEDrEA66D0Lvl3Tek9B4O16Oie5anNnNMHigwU9yVc6dI8vDCRSEiz7laPTFK
|
13
|
+
xzOCQmqPGClKXkX3U+OvZp/Ss9U26Wpu0AbRKTvzAQKBgQDsMR9NjMpOmUaWkAwl
|
14
|
+
1wlUxsZ9YkRuTy7R3RfIdYWj6Lcoc4/iN0qILFM7xidkHhYTFqnsnP1SQkV6lEHV
|
15
|
+
OalYxZu9F2l1rHPc8G5YWh/KOg1rAEI47MVT4iwhA2fw6JLti/rm25AeSTMjSTqu
|
16
|
+
ht3146036opcIF3v86oGUrSXDwKBgQD5CsNcwLeUDGXozjq62T8/mTYwd2Tw3aiY
|
17
|
+
KaGp+exAW321vYm5SKsMitBMGU2tGFlv5eptSI48h7SCpgnszaexw7yj30KuvqjG
|
18
|
+
bBqq/MsKuXHyn2sG0A7MJ6zfk+4l46B45blDJZ+x7xL0dyS4UCU3zUeesgSGo4zK
|
19
|
+
ZOspPIQCMQKBgQCk35VuWP1P6IbxyxPvxi/pUeh01gfWyMdyD9fuQrtLM8PHJQQn
|
20
|
+
cVlBvU9MxoHwzV+za3qqhNwAc+p0KtHZuiqQoUCZuqIPVpZ6gAtG+YJ/dA6xxrhz
|
21
|
+
bDRC3frYALyp2m/WCoTWaiYsPgTIePHRqqt+XbQo+DwlGyL3wSvKxijx2QKBgCb0
|
22
|
+
OwioEE70/X/DukX9szn0chh0pHJUiYl7gZD/yadraCdkRUWZC0BD+j7c+lxn4Z1y
|
23
|
+
HhAH+E+Zfm+tHwJOTLuufTQ4uMpygh2/TRCPyAaeaSdlLi17n8TpM84o6mg8yZ3/
|
24
|
+
eNH68Za4aYOZm0HFL30h++DjwXd534zM6keh8pgRAoGBAKUrsjDGjuSo8l1fi4Cq
|
25
|
+
INu/rQop2h/db02zyJP5q7NKhE1nqogeLwwn+2M/LtHQ1nIzZR+rvrLNgt6oWY31
|
26
|
+
sPsv8JUfVT8GmdhU9KKmizK6eUu3rWdj2+rJARmuEaPmHcD5O6oJaGU0qadqQP34
|
27
|
+
H+enwWmpyZXAIbEu/q63DFhV
|
28
|
+
-----END PRIVATE KEY-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEfTCCAuWgAwIBAgIRAPb7jkLrCuqToG+s3AQYeuUwDQYJKoZIhvcNAQELBQAw
|
3
|
+
gakxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE/MD0GA1UECww2REVT
|
4
|
+
S1RPUC1JMzcxOERPXFphY2ggVm9yaGllc0BERVNLVE9QLUkzNzE4RE8gKG5pdGVy
|
5
|
+
aXMpMUYwRAYDVQQDDD1ta2NlcnQgREVTS1RPUC1JMzcxOERPXFphY2ggVm9yaGll
|
6
|
+
c0BERVNLVE9QLUkzNzE4RE8gKG5pdGVyaXMpMB4XDTI1MDQyODAwMzk1MFoXDTI3
|
7
|
+
MDcyODAwMzk1MFowajEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
|
8
|
+
ZmljYXRlMT8wPQYDVQQLDDZERVNLVE9QLUkzNzE4RE9cWmFjaCBWb3JoaWVzQERF
|
9
|
+
U0tUT1AtSTM3MThETyAobml0ZXJpcykwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
10
|
+
ggEKAoIBAQDlxbWcpUXPpjqsDJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcH
|
11
|
+
S+pkgXnG46g6bUcL/AK5Ba08vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh
|
12
|
+
2jSCeOTagNoaHLYEugARkkEu0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU
|
13
|
+
7K+BLWsCiZ308NMpzHF5APZ6YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXW
|
14
|
+
yyPG0jwPQyjBot/cNtt8GrsNgcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jpr
|
15
|
+
HEsw+/UhnG6Qhksm8C/DN9kPhselewffAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIF
|
16
|
+
oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSPBydvhr9wI+FsoW/H
|
17
|
+
WK3DbS8IUDAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGB
|
18
|
+
AJVrF1yczZaxt+A2AhdeFbJQUR6NzGBTc20YeWF1YzLV5sV3QVumwZLP2M9ggRgd
|
19
|
+
xWV0xfwUHobFQk6RIPTADcLKctiurql0cgF4DPnpWVvto9RM00U3AkQcMj3xtKBV
|
20
|
+
wUqo83TcbqgL+euudFZ09gGTs9u9AENaZPcMh+rW8DDO92t+EwMI/IfopxVOJGUB
|
21
|
+
RSM3yTwV93BMYBuddt8mclzLzPK/1WONfsHU2xEascaHR1tYMOmJN9Vq4o0fzWxo
|
22
|
+
a2vI6K0aJqZV/ztdXq3akwLc6/e9hyptHWa0i/022xVCyNWIlnuEhT7ENMPxh6rX
|
23
|
+
ZCQCZVnhcSWAyFjggLJql3aSID5fPF8rmN7wWsB/I5pl9qwMR1/THMPrm5aWn1Xj
|
24
|
+
xW6PxkSGm73kd57DH7tqm5HTd8eYCbnsFofI9rC7xI6HCfwchKp+YHvIEu/LJ56E
|
25
|
+
FLnCZW/orYkHCzWntzxv1bddrw1BwaNR8Q+mu3imRP8fuyXb2UkFkINVVyOOWHuW
|
26
|
+
Kw==
|
27
|
+
-----END CERTIFICATE-----
|
fastled/filewatcher.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""File system watcher implementation using watchdog"""
|
2
2
|
|
3
|
+
import _thread
|
3
4
|
import hashlib
|
4
5
|
import os
|
5
6
|
import queue
|
@@ -229,34 +230,11 @@ class FileWatcherProcess:
|
|
229
230
|
return changed_files
|
230
231
|
|
231
232
|
|
232
|
-
|
233
|
-
# LAST_TIME = 0.0
|
234
|
-
# WATCHED_FILES: list[str] = []
|
235
|
-
|
236
|
-
# def debounced_sketch_filewatcher_get_all_changes() -> list[str]:
|
237
|
-
# nonlocal DEBOUNCE_SECONDS
|
238
|
-
# nonlocal LAST_TIME
|
239
|
-
# nonlocal WATCHED_FILES
|
240
|
-
# current_time = time.time()
|
241
|
-
# new_files = sketch_filewatcher.get_all_changes()
|
242
|
-
# if new_files:
|
243
|
-
# WATCHED_FILES.extend(new_files)
|
244
|
-
# print(f"Changes detected in {new_files}")
|
245
|
-
# LAST_TIME = current_time
|
246
|
-
# return []
|
247
|
-
# diff = current_time - LAST_TIME
|
248
|
-
# if diff > DEBOUNCE_SECONDS and len(WATCHED_FILES) > 0:
|
249
|
-
# LAST_TIME = current_time
|
250
|
-
# WATCHED_FILES, changed_files = [], WATCHED_FILES
|
251
|
-
# changed_files = sorted(list(set(changed_files)))
|
252
|
-
# return changed_files
|
253
|
-
# return []
|
254
|
-
|
255
|
-
|
256
|
-
class DebouncedFileWatcherProcess:
|
233
|
+
class DebouncedFileWatcherProcess(threading.Thread):
|
257
234
|
"""
|
258
235
|
Wraps a FileWatcherProcess to batch rapid-fire change events
|
259
236
|
and only emit them once the debounce interval has passed.
|
237
|
+
Runs in its own thread, polling every 0.1 s.
|
260
238
|
"""
|
261
239
|
|
262
240
|
def __init__(
|
@@ -264,35 +242,62 @@ class DebouncedFileWatcherProcess:
|
|
264
242
|
watcher: FileWatcherProcess,
|
265
243
|
debounce_seconds: float = FILE_CHANGED_DEBOUNCE_SECONDS,
|
266
244
|
) -> None:
|
245
|
+
super().__init__(daemon=True)
|
267
246
|
self.watcher = watcher
|
268
247
|
self.debounce_seconds = debounce_seconds
|
269
|
-
|
270
|
-
|
248
|
+
|
249
|
+
# internal buffer & timing
|
250
|
+
self._buffer: list[str] = []
|
251
|
+
self._last_time: float = 0.0
|
252
|
+
|
253
|
+
# queue of flushed batches
|
254
|
+
self._out_queue: queue.Queue[list[str]] = queue.Queue()
|
255
|
+
self._stop_event = threading.Event()
|
256
|
+
|
257
|
+
# record timestamp of last event for external inspection
|
258
|
+
self.last_event_time: float | None = None
|
259
|
+
|
260
|
+
self.start()
|
261
|
+
|
262
|
+
def run(self) -> None:
|
263
|
+
try:
|
264
|
+
while not self._stop_event.is_set():
|
265
|
+
now = time.time()
|
266
|
+
# non-blocking poll of raw watcher events
|
267
|
+
new = self.watcher.get_all_changes(timeout=0)
|
268
|
+
if new:
|
269
|
+
self._buffer.extend(new)
|
270
|
+
self._last_time = now
|
271
|
+
self.last_event_time = now
|
272
|
+
|
273
|
+
# if buffer exists and debounce interval elapsed, flush it
|
274
|
+
if self._buffer and (now - self._last_time) > self.debounce_seconds:
|
275
|
+
batch = sorted(set(self._buffer))
|
276
|
+
self._buffer.clear()
|
277
|
+
self._last_time = now
|
278
|
+
self._out_queue.put(batch)
|
279
|
+
|
280
|
+
time.sleep(0.1)
|
281
|
+
except KeyboardInterrupt:
|
282
|
+
_thread.interrupt_main()
|
283
|
+
raise
|
271
284
|
|
272
285
|
def get_all_changes(self, timeout: float | None = None) -> list[str]:
|
273
286
|
"""
|
274
|
-
|
275
|
-
|
276
|
-
a sorted, unique list of paths.
|
287
|
+
Return the next debounced batch of paths, or [] if none arrives
|
288
|
+
within `timeout` seconds.
|
277
289
|
"""
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
290
|
+
try:
|
291
|
+
if timeout is None:
|
292
|
+
# non-blocking
|
293
|
+
return self._out_queue.get_nowait()
|
294
|
+
else:
|
295
|
+
return self._out_queue.get(timeout=timeout)
|
296
|
+
except queue.Empty:
|
285
297
|
return []
|
286
298
|
|
287
|
-
# if the window has elapsed, flush
|
288
|
-
if self._watched_files and (now - self._last_time) > self.debounce_seconds:
|
289
|
-
batch = sorted(set(self._watched_files))
|
290
|
-
self._watched_files.clear()
|
291
|
-
self._last_time = now
|
292
|
-
return batch
|
293
|
-
|
294
|
-
return []
|
295
|
-
|
296
299
|
def stop(self) -> None:
|
297
|
-
"""
|
300
|
+
"""Stop the polling thread and tear down the underlying watcher."""
|
301
|
+
self._stop_event.set()
|
302
|
+
self.join()
|
298
303
|
self.watcher.stop()
|
fastled/keyz.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import importlib.resources as pkg_resources
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class SslConfig:
|
8
|
+
certfile: Path
|
9
|
+
keyfile: Path
|
10
|
+
|
11
|
+
|
12
|
+
def get_asset_path(filename: str) -> Path | None:
|
13
|
+
"""Locate a file from the fastled.assets package resources."""
|
14
|
+
try:
|
15
|
+
resource = pkg_resources.files("fastled.assets").joinpath(filename)
|
16
|
+
# Convert to Path for file-system access
|
17
|
+
path = Path(str(resource))
|
18
|
+
return path if path.exists() else None
|
19
|
+
except (ModuleNotFoundError, AttributeError):
|
20
|
+
return None
|
21
|
+
|
22
|
+
|
23
|
+
def get_ssl_config() -> SslConfig:
|
24
|
+
"""Get the keys for the server"""
|
25
|
+
certfile = get_asset_path("localhost-key.pem")
|
26
|
+
keyfile = get_asset_path("localhost.pem")
|
27
|
+
if certfile is None or keyfile is None:
|
28
|
+
raise ValueError("Could not find keys for server")
|
29
|
+
# return certfile, keyfile
|
30
|
+
return SslConfig(certfile=certfile, keyfile=keyfile)
|
fastled/open_browser.py
CHANGED
@@ -5,30 +5,23 @@ import webbrowser
|
|
5
5
|
from multiprocessing import Process
|
6
6
|
from pathlib import Path
|
7
7
|
|
8
|
-
|
8
|
+
from fastled.keyz import get_ssl_config
|
9
9
|
|
10
|
+
DEFAULT_PORT = 8089 # different than live version.
|
10
11
|
PYTHON_EXE = sys.executable
|
12
|
+
SSL_CONFIG = get_ssl_config()
|
13
|
+
|
14
|
+
|
15
|
+
# print(f"SSL Config: {SSL_CONFIG.certfile}, {SSL_CONFIG.keyfile}")
|
11
16
|
|
12
17
|
|
13
18
|
def open_http_server_subprocess(
|
14
|
-
fastled_js: Path,
|
19
|
+
fastled_js: Path,
|
20
|
+
port: int,
|
21
|
+
open_browser: bool,
|
15
22
|
) -> None:
|
16
|
-
"""Start livereload server in the fastled_js directory and return the process"""
|
17
|
-
import shutil
|
18
|
-
|
19
23
|
try:
|
20
|
-
|
21
|
-
cmd = [
|
22
|
-
"live-server",
|
23
|
-
f"--port={port}",
|
24
|
-
"--host=localhost",
|
25
|
-
".",
|
26
|
-
]
|
27
|
-
if not open_browser:
|
28
|
-
cmd.append("--no-browser")
|
29
|
-
subprocess.run(cmd, shell=True, cwd=fastled_js)
|
30
|
-
return
|
31
|
-
|
24
|
+
# Fallback to our Python server
|
32
25
|
cmd = [
|
33
26
|
PYTHON_EXE,
|
34
27
|
"-m",
|
@@ -37,8 +30,23 @@ def open_http_server_subprocess(
|
|
37
30
|
"--port",
|
38
31
|
str(port),
|
39
32
|
]
|
40
|
-
|
41
|
-
|
33
|
+
if open_browser:
|
34
|
+
cmd.append("--open-browser")
|
35
|
+
# Pass SSL flags if available
|
36
|
+
if SSL_CONFIG.certfile and SSL_CONFIG.keyfile:
|
37
|
+
cmd.extend(
|
38
|
+
[
|
39
|
+
"--cert",
|
40
|
+
str(SSL_CONFIG.certfile),
|
41
|
+
"--key",
|
42
|
+
str(SSL_CONFIG.keyfile),
|
43
|
+
]
|
44
|
+
)
|
45
|
+
print(
|
46
|
+
f"Running server on port {port} with certs: {SSL_CONFIG.certfile}, {SSL_CONFIG.keyfile}"
|
47
|
+
)
|
48
|
+
print(f"Command: {subprocess.list2cmdline(cmd)}")
|
49
|
+
# Suppress output
|
42
50
|
subprocess.run(
|
43
51
|
cmd,
|
44
52
|
stdout=subprocess.DEVNULL,
|
@@ -87,54 +95,34 @@ def wait_for_server(port: int, timeout: int = 10) -> None:
|
|
87
95
|
raise TimeoutError("Could not connect to server")
|
88
96
|
|
89
97
|
|
90
|
-
def _background_npm_install_live_server() -> None:
|
91
|
-
import shutil
|
92
|
-
import time
|
93
|
-
|
94
|
-
if shutil.which("npm") is None:
|
95
|
-
return
|
96
|
-
|
97
|
-
if shutil.which("live-server") is not None:
|
98
|
-
return
|
99
|
-
|
100
|
-
time.sleep(3)
|
101
|
-
subprocess.run(
|
102
|
-
["npm", "install", "-g", "live-server"],
|
103
|
-
stdout=subprocess.DEVNULL,
|
104
|
-
stderr=subprocess.DEVNULL,
|
105
|
-
)
|
106
|
-
|
107
|
-
|
108
98
|
def open_browser_process(
|
109
|
-
fastled_js: Path,
|
99
|
+
fastled_js: Path,
|
100
|
+
port: int | None = None,
|
101
|
+
open_browser: bool = True,
|
110
102
|
) -> Process:
|
111
|
-
import shutil
|
112
103
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
raise ValueError(f"Port {port} was specified but in use")
|
117
|
-
else:
|
104
|
+
if port is not None and not is_port_free(port):
|
105
|
+
raise ValueError(f"Port {port} was specified but in use")
|
106
|
+
if port is None:
|
118
107
|
port = find_free_port(DEFAULT_PORT)
|
119
|
-
|
108
|
+
|
109
|
+
proc = Process(
|
120
110
|
target=open_http_server_subprocess,
|
121
|
-
args=(fastled_js, port,
|
111
|
+
args=(fastled_js, port, open_browser),
|
122
112
|
daemon=True,
|
123
113
|
)
|
124
|
-
|
114
|
+
proc.start()
|
125
115
|
wait_for_server(port)
|
126
116
|
if open_browser:
|
127
|
-
print(
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
t.start()
|
137
|
-
return out
|
117
|
+
print(
|
118
|
+
f"Opening browser to http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}"
|
119
|
+
)
|
120
|
+
webbrowser.open(
|
121
|
+
url=f"http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}",
|
122
|
+
new=1,
|
123
|
+
autoraise=True,
|
124
|
+
)
|
125
|
+
return proc
|
138
126
|
|
139
127
|
|
140
128
|
if __name__ == "__main__":
|
@@ -144,9 +132,7 @@ if __name__ == "__main__":
|
|
144
132
|
description="Open a browser to the fastled_js directory"
|
145
133
|
)
|
146
134
|
parser.add_argument(
|
147
|
-
"fastled_js",
|
148
|
-
type=Path,
|
149
|
-
help="Path to the fastled_js directory",
|
135
|
+
"fastled_js", type=Path, help="Path to the fastled_js directory"
|
150
136
|
)
|
151
137
|
parser.add_argument(
|
152
138
|
"--port",
|
fastled/open_browser2.py
CHANGED
@@ -1,90 +1,56 @@
|
|
1
1
|
import argparse
|
2
|
+
import importlib.resources as pkg_resources
|
2
3
|
from pathlib import Path
|
3
4
|
|
4
|
-
from
|
5
|
+
from .server_fastapi_cli import run_fastapi_server_proces
|
5
6
|
|
6
7
|
|
7
|
-
def
|
8
|
-
"""
|
8
|
+
def get_asset_path(filename: str) -> Path | None:
|
9
|
+
"""Locate a file from the fastled.assets package resources."""
|
9
10
|
try:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
response.headers["Content-Type"] = "image/x-icon"
|
45
|
-
if path.endswith(".html"):
|
46
|
-
response.headers["Content-Type"] = "text/html"
|
47
|
-
|
48
|
-
# now also add headers to force no caching
|
49
|
-
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
50
|
-
response.headers["Pragma"] = "no-cache"
|
51
|
-
response.headers["Expires"] = "0"
|
52
|
-
return response
|
53
|
-
|
54
|
-
server = Server(app.wsgi_app)
|
55
|
-
# Watch index.html for changes
|
56
|
-
server.watch(str(fastled_js / "index.html"))
|
57
|
-
# server.watch(str(fastled_js / "index.js"))
|
58
|
-
# server.watch(str(fastled_js / "index.css"))
|
59
|
-
# Start the server
|
60
|
-
server.serve(port=port, debug=True)
|
61
|
-
except KeyboardInterrupt:
|
62
|
-
import _thread
|
63
|
-
|
64
|
-
_thread.interrupt_main()
|
65
|
-
except Exception as e:
|
66
|
-
print(f"Failed to run Flask server: {e}")
|
67
|
-
import _thread
|
68
|
-
|
69
|
-
_thread.interrupt_main()
|
70
|
-
|
71
|
-
|
72
|
-
def run(path: Path, port: int) -> None:
|
73
|
-
"""Run the Flask server."""
|
11
|
+
resource = pkg_resources.files("fastled.assets").joinpath(filename)
|
12
|
+
# Convert to Path for file-system access
|
13
|
+
path = Path(str(resource))
|
14
|
+
return path if path.exists() else None
|
15
|
+
except (ModuleNotFoundError, AttributeError):
|
16
|
+
return None
|
17
|
+
|
18
|
+
|
19
|
+
def _open_browser(url: str) -> None:
|
20
|
+
import webview
|
21
|
+
|
22
|
+
webview.create_window("FastLED", url, width=1280, height=1024)
|
23
|
+
webview.start()
|
24
|
+
|
25
|
+
|
26
|
+
def run(
|
27
|
+
path: Path,
|
28
|
+
port: int,
|
29
|
+
open_browser: bool,
|
30
|
+
certfile: Path | None = None,
|
31
|
+
keyfile: Path | None = None,
|
32
|
+
) -> None:
|
33
|
+
"""Run the server, using package assets if explicit paths are not provided"""
|
34
|
+
# Use package resources if no explicit path
|
35
|
+
if certfile is None:
|
36
|
+
certfile = get_asset_path("localhost.pem")
|
37
|
+
if keyfile is None:
|
38
|
+
keyfile = get_asset_path("localhost-key.pem")
|
39
|
+
|
40
|
+
# _run_flask_server(path, port, certfile, keyfile)
|
41
|
+
# run_fastapi_server_proces(port=port, path=path, certfile=certfile, keyfile=keyfile)
|
42
|
+
proc = run_fastapi_server_proces(port=port, cwd=path)
|
43
|
+
if open_browser:
|
44
|
+
_open_browser(f"http://localhost:{port}/")
|
74
45
|
try:
|
75
|
-
|
76
|
-
import warnings
|
77
|
-
|
78
|
-
warnings.warn("Flask server has stopped")
|
46
|
+
proc.join()
|
79
47
|
except KeyboardInterrupt:
|
80
48
|
import _thread
|
81
49
|
|
82
50
|
_thread.interrupt_main()
|
83
|
-
pass
|
84
51
|
|
85
52
|
|
86
53
|
def parse_args() -> argparse.Namespace:
|
87
|
-
"""Parse the command line arguments."""
|
88
54
|
parser = argparse.ArgumentParser(
|
89
55
|
description="Open a browser to the fastled_js directory"
|
90
56
|
)
|
@@ -95,16 +61,36 @@ def parse_args() -> argparse.Namespace:
|
|
95
61
|
"--port",
|
96
62
|
"-p",
|
97
63
|
type=int,
|
98
|
-
|
99
|
-
help="Port to run the server on (default:
|
64
|
+
default=5500,
|
65
|
+
help="Port to run the server on (default: 5500)",
|
66
|
+
)
|
67
|
+
parser.add_argument(
|
68
|
+
"--cert", type=Path, help="(Optional) Path to SSL certificate (PEM format)"
|
69
|
+
)
|
70
|
+
parser.add_argument(
|
71
|
+
"--key", type=Path, help="(Optional) Path to SSL private key (PEM format)"
|
72
|
+
)
|
73
|
+
parser.add_argument(
|
74
|
+
"--open-browser",
|
75
|
+
action="store_true",
|
100
76
|
)
|
101
77
|
return parser.parse_args()
|
102
78
|
|
103
79
|
|
104
80
|
def main() -> None:
|
105
|
-
"""Main function."""
|
106
81
|
args = parse_args()
|
107
|
-
|
82
|
+
open_browser: bool = args.open_browser
|
83
|
+
fastled_js: Path = args.fastled_js
|
84
|
+
port: int = args.port
|
85
|
+
cert: Path | None = args.cert
|
86
|
+
key: Path | None = args.key
|
87
|
+
run(
|
88
|
+
path=fastled_js,
|
89
|
+
port=port,
|
90
|
+
open_browser=open_browser,
|
91
|
+
certfile=cert,
|
92
|
+
keyfile=key,
|
93
|
+
)
|
108
94
|
|
109
95
|
|
110
96
|
if __name__ == "__main__":
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from fastapi import FastAPI, HTTPException
|
4
|
+
from fastapi.responses import FileResponse
|
5
|
+
|
6
|
+
# your existing MIME mapping, e.g.
|
7
|
+
MAPPING = {
|
8
|
+
"js": "application/javascript",
|
9
|
+
"css": "text/css",
|
10
|
+
"wasm": "application/wasm",
|
11
|
+
"json": "application/json",
|
12
|
+
"png": "image/png",
|
13
|
+
"jpg": "image/jpeg",
|
14
|
+
"jpeg": "image/jpeg",
|
15
|
+
"gif": "image/gif",
|
16
|
+
"svg": "image/svg+xml",
|
17
|
+
"ico": "image/x-icon",
|
18
|
+
"html": "text/html",
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
"""Run FastAPI server with live reload or HTTPS depending on args."""
|
23
|
+
app = FastAPI()
|
24
|
+
base = Path(".")
|
25
|
+
|
26
|
+
|
27
|
+
def no_cache_headers() -> dict[str, str]:
|
28
|
+
return {
|
29
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
30
|
+
"Pragma": "no-cache",
|
31
|
+
"Expires": "0",
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
@app.get("/")
|
36
|
+
async def serve_index():
|
37
|
+
index_path = base / "index.html"
|
38
|
+
if not index_path.exists():
|
39
|
+
raise HTTPException(status_code=404, detail="index.html not found")
|
40
|
+
return FileResponse(
|
41
|
+
index_path,
|
42
|
+
media_type=MAPPING.get("html"),
|
43
|
+
headers=no_cache_headers(),
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
@app.get("/{path:path}")
|
48
|
+
async def serve_files(path: str):
|
49
|
+
file_path = base / path
|
50
|
+
if not file_path.exists() or not file_path.is_file():
|
51
|
+
raise HTTPException(status_code=404, detail=f"{path} not found")
|
52
|
+
|
53
|
+
ext = path.rsplit(".", 1)[-1].lower()
|
54
|
+
media_type = MAPPING.get(ext)
|
55
|
+
|
56
|
+
return FileResponse(
|
57
|
+
file_path,
|
58
|
+
media_type=media_type,
|
59
|
+
headers=no_cache_headers(),
|
60
|
+
)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from multiprocessing import Process
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import uvicorn
|
5
|
+
|
6
|
+
# MAPPING = {
|
7
|
+
# "js": "application/javascript",
|
8
|
+
# "css": "text/css",
|
9
|
+
# "wasm": "application/wasm",
|
10
|
+
# "json": "application/json",
|
11
|
+
# "png": "image/png",
|
12
|
+
# "jpg": "image/jpeg",
|
13
|
+
# "jpeg": "image/jpeg",
|
14
|
+
# "gif": "image/gif",
|
15
|
+
# "svg": "image/svg+xml",
|
16
|
+
# "ico": "image/x-icon",
|
17
|
+
# "html": "text/html",
|
18
|
+
# }
|
19
|
+
|
20
|
+
|
21
|
+
def _run_fastapi_server(
|
22
|
+
port: int,
|
23
|
+
cwd: Path,
|
24
|
+
certfile: Path | None = None,
|
25
|
+
keyfile: Path | None = None,
|
26
|
+
) -> None:
|
27
|
+
# Uvicorn “reload” will watch your Python files for changes.
|
28
|
+
import os
|
29
|
+
|
30
|
+
os.chdir(cwd)
|
31
|
+
uvicorn.run(
|
32
|
+
"fastled.server_fastapi:app",
|
33
|
+
host="127.0.0.1",
|
34
|
+
port=port,
|
35
|
+
reload=True,
|
36
|
+
ssl_certfile=certfile,
|
37
|
+
ssl_keyfile=keyfile,
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
def run_fastapi_server_proces(
|
42
|
+
port: int,
|
43
|
+
cwd: Path | None = None,
|
44
|
+
certfile: Path | None = None,
|
45
|
+
keyfile: Path | None = None,
|
46
|
+
) -> Process:
|
47
|
+
"""Run the FastAPI server in a separate process."""
|
48
|
+
cwd = cwd or Path(".")
|
49
|
+
process = Process(
|
50
|
+
target=_run_fastapi_server,
|
51
|
+
args=(port, cwd, certfile, keyfile),
|
52
|
+
)
|
53
|
+
process.start()
|
54
|
+
return process
|
55
|
+
|
56
|
+
|
57
|
+
if __name__ == "__main__":
|
58
|
+
# Example usage
|
59
|
+
proc = run_fastapi_server_proces(port=8000)
|
60
|
+
proc.join()
|
fastled/settings.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastled
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.58
|
4
4
|
Summary: FastLED Wasm Compiler
|
5
5
|
Home-page: https://github.com/zackees/fastled-wasm
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -19,8 +19,9 @@ Requires-Dist: disklru>=2.0.1
|
|
19
19
|
Requires-Dist: appdirs>=1.4.4
|
20
20
|
Requires-Dist: rapidfuzz>=3.10.1
|
21
21
|
Requires-Dist: progress>=1.6
|
22
|
-
Requires-Dist:
|
23
|
-
Requires-Dist:
|
22
|
+
Requires-Dist: fastapi>=0.115.12
|
23
|
+
Requires-Dist: uvicorn>=0.34.2
|
24
|
+
Requires-Dist: pywebview>=5.4
|
24
25
|
Dynamic: home-page
|
25
26
|
Dynamic: license-file
|
26
27
|
Dynamic: maintainer
|
@@ -295,8 +296,8 @@ A: `delay()` will block `loop()` which blocks the main thread of the browser. Th
|
|
295
296
|
Q: How can I get the compiled size of my FastLED sketch smaller?
|
296
297
|
A: A big chunk of space is being used by unnecessary javascript `emscripten` bundling. The wasm_compiler_settings.py file in the FastLED repo can tweak this.
|
297
298
|
|
298
|
-
# Revisions
|
299
299
|
|
300
|
+
# Revisions
|
300
301
|
* 1.2.31 - Bunch of fixes and ease of use while compiling code in the repo.
|
301
302
|
* 1.2.22 - Prefer to use `live-server` from npm. If npm exists on the system then do a background install of `live-server` for next run.
|
302
303
|
* 1.2.20 - Fixed up path issue for web browser launch for hot reload.
|
@@ -382,3 +383,15 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` bun
|
|
382
383
|
* 1.0.2 - Small bug with new installs.
|
383
384
|
* 1.0.1 - Re-use is no longer the default, due to problems.
|
384
385
|
* 1.0.0 - Initial release.
|
386
|
+
|
387
|
+
|
388
|
+
# TODO
|
389
|
+
* `live-server --https --cert=localhost.pem --key=localhost-key.pem --port=5500`
|
390
|
+
* `live-server --port=8416 --host=localhost . --https --cert=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost-key.pem --key=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost.pem --no-browser`
|
391
|
+
|
392
|
+
|
393
|
+
|
394
|
+
live-server --https --port=8416 --host=localhost . --cert=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost-key.pem --key=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost.pem --no-browser
|
395
|
+
|
396
|
+
|
397
|
+
live-server --https --cert=src/fastled/assets/localhost.pem --key=src/fastled/assets/localhost-key.pem --port=5500
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fastled/__init__.py,sha256=
|
1
|
+
fastled/__init__.py,sha256=NQQ3bWKPtXbIq6lZlbpMUxvUUbYxXAB2rQin9kksZfE,6734
|
2
2
|
fastled/app.py,sha256=Ozn7OJ0rz3CEHsJKMhFLxV7TisRKo4DuFpVhrf5FQNw,3944
|
3
3
|
fastled/cli.py,sha256=FjVr31ht0UPlAcmX-84NwfAGMQHTkrCe4o744jCAxiw,375
|
4
4
|
fastled/cli_test.py,sha256=qJB9yLRFR3OwOwdIWSQ0fQsWLnA37v5pDccufiP_hTs,512
|
@@ -6,17 +6,20 @@ fastled/client_server.py,sha256=Pr_b_406XpB6QLrlRMV3mwM82x25s1bIGFU0O0XA3yk,1493
|
|
6
6
|
fastled/compile_server.py,sha256=ul3eiZNX2wwmInooo3PJC3_kNpdejYVDIo94G3sV9HQ,2941
|
7
7
|
fastled/compile_server_impl.py,sha256=xhC7WBlIfvghgpxAP8VwBeM13NuTA99cI47prODW-IU,9927
|
8
8
|
fastled/docker_manager.py,sha256=VVyfl4HNsSB95nlENuccZFR0qL3Y0ZJgPCFyHKa9A14,31459
|
9
|
-
fastled/filewatcher.py,sha256=
|
9
|
+
fastled/filewatcher.py,sha256=3qS3L7zMQhFuVrkeGn1djsB_cB6x_E2YGJmmQWVAU_w,10033
|
10
10
|
fastled/interactive_srcs.py,sha256=F5nHdJc60xsnmOtnKhngE9JytqGn56PmYw_MVSIX1ac,138
|
11
11
|
fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
|
12
|
+
fastled/keyz.py,sha256=WjfZHtZHEB8wNh1YYZGYwjx1jQV-6wwhCbYn4Zp7xs4,938
|
12
13
|
fastled/live_client.py,sha256=MDauol0mxtXggV1Pv9ahC0Jjg_4wnnV6FjGEtdd9cxU,2763
|
13
|
-
fastled/open_browser.py,sha256=
|
14
|
-
fastled/open_browser2.py,sha256=
|
14
|
+
fastled/open_browser.py,sha256=ls3nJBh61iV95Lq4n0zVhmtQ_xgXjpa5bMVOPRM5ldI,3988
|
15
|
+
fastled/open_browser2.py,sha256=Lle8uVYzLxoy5m8lqRQiBuqTBilrGSGY24azMJ1zVQE,2675
|
15
16
|
fastled/parse_args.py,sha256=xgjxirQVX3FQxHZNVJtQQxTOeSJRQdGUNQ7W33Y880Y,8051
|
16
17
|
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
17
18
|
fastled/project_init.py,sha256=OCIqpGmRMHNQtlXMTfX1x4Sou5WZgJtBNqPhnXbFFLI,4113
|
18
19
|
fastled/select_sketch_directory.py,sha256=TZdCjl1D7YMKjodMTvDRurPcpAmN3x0TcJxffER2NfM,1314
|
19
|
-
fastled/
|
20
|
+
fastled/server_fastapi.py,sha256=BLWERhxXcNxKk7Ub7eLRL1qgXBIgQw24-Zv5vek2X7A,1478
|
21
|
+
fastled/server_fastapi_cli.py,sha256=i-Paq59dFpl-RV1xb7DycTHuDjaMhAo7u_WdVaSUDTY,1347
|
22
|
+
fastled/settings.py,sha256=URgM6ZPlQYF-0ZTEhQCX8isLR6CbmYGwhDX4uXbh-ZI,468
|
20
23
|
fastled/sketch.py,sha256=tHckjDj8P6BI_LWzUFM071a9qcqPs-r-qFWIe50P5Xw,3391
|
21
24
|
fastled/spinner.py,sha256=VHxmvB92P0Z_zYxRajb5HiNmkHHvZ5dG7hKtZltzpcs,867
|
22
25
|
fastled/string_diff.py,sha256=9Q_36W0j7HvTnvfemdq9Ldi5RznGj0duX0Y3sZmVwJI,1730
|
@@ -24,13 +27,15 @@ fastled/types.py,sha256=ZFqHxYIGSeQb9FiImA5KDXZLhmmVSjOrIQDduwxmCZw,4494
|
|
24
27
|
fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
|
25
28
|
fastled/web_compile.py,sha256=H2Tm5kMBaRjqdHqxv7L3-xSeYNHi0k0P-XM0Cn9esOo,10360
|
26
29
|
fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
30
|
+
fastled/assets/localhost-key.pem,sha256=Q-CNO_UoOd8fFNN4ljcnqwUeCMhzTplRjLO2x0pYRlU,1704
|
31
|
+
fastled/assets/localhost.pem,sha256=QTwUtTwjYWbm9m3pHW2IlK2nFZJ8b0pppxPjhgVZqQo,1619
|
27
32
|
fastled/site/build.py,sha256=pjsaeHQQh_Zdh1g5Q78lYlUT7UPDFuNgW5YZkpsPxWc,14436
|
28
33
|
fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
|
29
34
|
fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
|
30
35
|
fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
|
31
|
-
fastled-1.2.
|
32
|
-
fastled-1.2.
|
33
|
-
fastled-1.2.
|
34
|
-
fastled-1.2.
|
35
|
-
fastled-1.2.
|
36
|
-
fastled-1.2.
|
36
|
+
fastled-1.2.58.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
37
|
+
fastled-1.2.58.dist-info/METADATA,sha256=6tslEBTnu9WHa3X4F3dSccKd3PRZLg_9LQwrgsjvUos,22374
|
38
|
+
fastled-1.2.58.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
39
|
+
fastled-1.2.58.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
40
|
+
fastled-1.2.58.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
|
41
|
+
fastled-1.2.58.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|