fastled 1.2.33__py3-none-any.whl → 1.4.50__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.
Files changed (58) hide show
  1. fastled/__init__.py +51 -192
  2. fastled/__main__.py +14 -0
  3. fastled/__version__.py +6 -0
  4. fastled/app.py +124 -27
  5. fastled/args.py +124 -0
  6. fastled/assets/localhost-key.pem +28 -0
  7. fastled/assets/localhost.pem +27 -0
  8. fastled/cli.py +10 -2
  9. fastled/cli_test.py +21 -0
  10. fastled/cli_test_interactive.py +21 -0
  11. fastled/client_server.py +334 -55
  12. fastled/compile_server.py +12 -1
  13. fastled/compile_server_impl.py +115 -42
  14. fastled/docker_manager.py +392 -69
  15. fastled/emoji_util.py +27 -0
  16. fastled/filewatcher.py +100 -8
  17. fastled/find_good_connection.py +105 -0
  18. fastled/header_dump.py +63 -0
  19. fastled/install/__init__.py +1 -0
  20. fastled/install/examples_manager.py +62 -0
  21. fastled/install/extension_manager.py +113 -0
  22. fastled/install/main.py +156 -0
  23. fastled/install/project_detection.py +167 -0
  24. fastled/install/test_install.py +373 -0
  25. fastled/install/vscode_config.py +344 -0
  26. fastled/interruptible_http.py +148 -0
  27. fastled/keyboard.py +1 -0
  28. fastled/keyz.py +84 -0
  29. fastled/live_client.py +26 -1
  30. fastled/open_browser.py +133 -89
  31. fastled/parse_args.py +219 -15
  32. fastled/playwright/chrome_extension_downloader.py +207 -0
  33. fastled/playwright/playwright_browser.py +773 -0
  34. fastled/playwright/resize_tracking.py +127 -0
  35. fastled/print_filter.py +52 -0
  36. fastled/project_init.py +20 -13
  37. fastled/select_sketch_directory.py +142 -17
  38. fastled/server_flask.py +487 -0
  39. fastled/server_start.py +21 -0
  40. fastled/settings.py +53 -4
  41. fastled/site/build.py +2 -10
  42. fastled/site/examples.py +10 -0
  43. fastled/sketch.py +129 -7
  44. fastled/string_diff.py +218 -9
  45. fastled/test/examples.py +7 -5
  46. fastled/types.py +22 -2
  47. fastled/util.py +78 -0
  48. fastled/version.py +41 -0
  49. fastled/web_compile.py +401 -218
  50. fastled/zip_files.py +76 -0
  51. {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/METADATA +533 -382
  52. fastled-1.4.50.dist-info/RECORD +60 -0
  53. {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/WHEEL +1 -1
  54. fastled/open_browser2.py +0 -111
  55. fastled-1.2.33.dist-info/RECORD +0 -33
  56. {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/entry_points.txt +0 -0
  57. {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info/licenses}/LICENSE +0 -0
  58. {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,344 @@
1
+ """VSCode configuration generation for FastLED projects."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ def update_launch_json_for_arduino() -> None:
8
+ """Update launch.json with Arduino debugging configuration."""
9
+ launch_json_path = Path.cwd() / ".vscode" / "launch.json"
10
+
11
+ # Default launch configuration
12
+ arduino_config = {
13
+ "name": "🎯 Auto Debug (Smart File Detection)",
14
+ "type": "auto-debug",
15
+ "request": "launch",
16
+ "map": {
17
+ "*.ino": "Arduino: Run .ino with FastLED",
18
+ "*.py": "Python: Current File (UV)",
19
+ },
20
+ }
21
+
22
+ if launch_json_path.exists():
23
+ # Merge with existing
24
+ try:
25
+ with open(launch_json_path, "r") as f:
26
+ data = json.load(f)
27
+ except json.JSONDecodeError:
28
+ data = {"version": "0.2.0", "configurations": []}
29
+ else:
30
+ data = {"version": "0.2.0", "configurations": []}
31
+
32
+ # Check if configuration already exists
33
+ configs = data.get("configurations", [])
34
+ exists = any(cfg.get("name") == arduino_config["name"] for cfg in configs)
35
+
36
+ if not exists:
37
+ configs.insert(0, arduino_config) # Add at the beginning
38
+ data["configurations"] = configs
39
+
40
+ # Write back
41
+ launch_json_path.parent.mkdir(exist_ok=True)
42
+ with open(launch_json_path, "w") as f:
43
+ json.dump(data, f, indent=4)
44
+
45
+ print(f"✅ Updated {launch_json_path}")
46
+
47
+
48
+ def generate_fastled_tasks() -> None:
49
+ """Generate/update tasks.json with FastLED build tasks."""
50
+ tasks_json_path = Path.cwd() / ".vscode" / "tasks.json"
51
+
52
+ # FastLED tasks
53
+ fastled_tasks = [
54
+ {
55
+ "type": "shell",
56
+ "label": "Run FastLED (Debug)",
57
+ "command": "fastled",
58
+ "args": ["${file}", "--debug", "--app"],
59
+ "options": {"cwd": "${workspaceFolder}"},
60
+ "group": {"kind": "build", "isDefault": True},
61
+ "presentation": {
62
+ "echo": True,
63
+ "reveal": "always",
64
+ "focus": True,
65
+ "panel": "new",
66
+ "showReuseMessage": False,
67
+ "clear": True,
68
+ },
69
+ "detail": "Run FastLED with debug mode and app visualization",
70
+ "problemMatcher": [],
71
+ },
72
+ {
73
+ "type": "shell",
74
+ "label": "Run FastLED (Quick)",
75
+ "command": "fastled",
76
+ "args": ["${file}", "--background-update"],
77
+ "options": {"cwd": "${workspaceFolder}"},
78
+ "group": "build",
79
+ "presentation": {
80
+ "echo": True,
81
+ "reveal": "always",
82
+ "focus": True,
83
+ "panel": "new",
84
+ "showReuseMessage": False,
85
+ "clear": True,
86
+ },
87
+ "detail": "Run FastLED with quick background update mode",
88
+ "problemMatcher": [],
89
+ },
90
+ ]
91
+
92
+ if tasks_json_path.exists():
93
+ # Merge with existing
94
+ try:
95
+ with open(tasks_json_path, "r") as f:
96
+ data = json.load(f)
97
+ except json.JSONDecodeError:
98
+ data = {"version": "2.0.0", "tasks": []}
99
+ else:
100
+ data = {"version": "2.0.0", "tasks": []}
101
+
102
+ # Get existing tasks
103
+ existing_tasks = data.get("tasks", [])
104
+ existing_labels = {task.get("label") for task in existing_tasks}
105
+
106
+ # Add new tasks if they don't exist
107
+ for task in fastled_tasks:
108
+ if task["label"] not in existing_labels:
109
+ existing_tasks.append(task)
110
+
111
+ data["tasks"] = existing_tasks
112
+
113
+ # Write back
114
+ tasks_json_path.parent.mkdir(exist_ok=True)
115
+ with open(tasks_json_path, "w") as f:
116
+ json.dump(data, f, indent=4)
117
+
118
+ print(f"✅ Updated {tasks_json_path}")
119
+
120
+
121
+ def update_vscode_settings_for_fastled() -> None:
122
+ """
123
+ 🚨 Repository-only: Apply clangd settings and IntelliSense overrides.
124
+ This should ONLY be called for the actual FastLED repository.
125
+ """
126
+ from .project_detection import is_fastled_repository
127
+
128
+ # Safety check - only apply in actual repository
129
+ if not is_fastled_repository():
130
+ return
131
+ settings_json_path = Path.cwd() / ".vscode" / "settings.json"
132
+
133
+ # FastLED repository-specific settings - updated to match the official FastLED repo
134
+ fastled_settings = {
135
+ # Terminal configuration
136
+ "terminal.integrated.defaultProfile.windows": "Git Bash",
137
+ "terminal.integrated.shellIntegration.enabled": False,
138
+ "terminal.integrated.profiles.windows": {
139
+ "Command Prompt": {"path": "C:\\Windows\\System32\\cmd.exe"},
140
+ "Git Bash": {
141
+ "path": "C:\\Program Files\\Git\\bin\\bash.exe",
142
+ "args": ["--cd=."],
143
+ },
144
+ },
145
+ # File settings
146
+ "files.eol": "\n", # Unix line endings
147
+ "files.autoDetectEol": False, # Prevent VS Code from auto-detecting and changing EOL
148
+ "files.insertFinalNewline": True, # Ensure files end with a newline
149
+ "files.trimFinalNewlines": True, # Remove extra newlines at the end
150
+ "editor.tabSize": 4,
151
+ "editor.insertSpaces": True,
152
+ "editor.detectIndentation": True,
153
+ "editor.formatOnSave": False, # Disabled to prevent conflicts
154
+ # Debugger defaults - ensure C++ debugger is used for C++ files
155
+ "debug.defaultDebuggerType": "cppdbg",
156
+ "debug.toolBarLocation": "docked",
157
+ "debug.console.fontSize": 14,
158
+ "debug.console.lineHeight": 19,
159
+ # Python configuration (using uv as per project rules)
160
+ "python.defaultInterpreterPath": "uv",
161
+ "python.debugger": "debugpy",
162
+ # File associations for debugger
163
+ "[cpp]": {
164
+ "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
165
+ "debug.defaultDebuggerType": "cppdbg",
166
+ },
167
+ "[c]": {
168
+ "editor.defaultFormatter": "ms-vscode.cpptools",
169
+ "debug.defaultDebuggerType": "cppdbg",
170
+ },
171
+ "[ino]": {
172
+ "editor.defaultFormatter": "ms-vscode.cpptools",
173
+ "debug.defaultDebuggerType": "cppdbg",
174
+ },
175
+ # clangd configuration - enhanced
176
+ "clangd.arguments": [
177
+ "--compile-commands-dir=${workspaceFolder}",
178
+ "--clang-tidy",
179
+ "--header-insertion=never",
180
+ "--completion-style=detailed",
181
+ "--function-arg-placeholders=false",
182
+ "--background-index",
183
+ "--pch-storage=memory",
184
+ ],
185
+ "clangd.fallbackFlags": [
186
+ "-std=c++17",
187
+ "-I${workspaceFolder}/src",
188
+ "-I${workspaceFolder}/tests",
189
+ "-Wno-global-constructors",
190
+ ],
191
+ # Disable conflicting IntelliSense to let clangd handle C++ analysis
192
+ "C_Cpp.intelliSenseEngine": "disabled",
193
+ "C_Cpp.autocomplete": "disabled",
194
+ "C_Cpp.errorSquiggles": "disabled",
195
+ "C_Cpp.suggestSnippets": False,
196
+ "C_Cpp.intelliSenseEngineFallback": "disabled",
197
+ "C_Cpp.autocompleteAddParentheses": False,
198
+ "C_Cpp.formatting": "disabled",
199
+ "C_Cpp.vcpkg.enabled": False,
200
+ "C_Cpp.configurationWarnings": "disabled",
201
+ "C_Cpp.intelliSenseCachePath": "",
202
+ "C_Cpp.intelliSenseCacheSize": 0,
203
+ "C_Cpp.intelliSenseUpdateDelay": 0,
204
+ "C_Cpp.workspaceParsingPriority": "lowest",
205
+ "C_Cpp.disabled": True,
206
+ # File associations - comprehensive
207
+ "files.associations": {
208
+ "*.ino": "cpp",
209
+ "*.h": "cpp",
210
+ "*.hpp": "cpp",
211
+ "*.cpp": "cpp",
212
+ "*.c": "c",
213
+ "*.inc": "cpp",
214
+ "*.tcc": "cpp",
215
+ "*.embeddedhtml": "html",
216
+ # Core C++ standard library files
217
+ "compare": "cpp",
218
+ "type_traits": "cpp",
219
+ "cmath": "cpp",
220
+ "limits": "cpp",
221
+ "iostream": "cpp",
222
+ "random": "cpp",
223
+ "functional": "cpp",
224
+ "bit": "cpp",
225
+ "vector": "cpp",
226
+ "array": "cpp",
227
+ "string": "cpp",
228
+ "memory": "cpp",
229
+ "algorithm": "cpp",
230
+ "iterator": "cpp",
231
+ "utility": "cpp",
232
+ "optional": "cpp",
233
+ "variant": "cpp",
234
+ "numeric": "cpp",
235
+ "chrono": "cpp",
236
+ "thread": "cpp",
237
+ "mutex": "cpp",
238
+ "atomic": "cpp",
239
+ "future": "cpp",
240
+ "condition_variable": "cpp",
241
+ },
242
+ # Disable Java language support and popups
243
+ "java.enabled": False,
244
+ "java.jdt.ls.enabled": False,
245
+ "java.compile.nullAnalysis.mode": "disabled",
246
+ "java.configuration.checkProjectSettingsExclusions": False,
247
+ "java.import.gradle.enabled": False,
248
+ "java.import.maven.enabled": False,
249
+ "java.autobuild.enabled": False,
250
+ "java.maxConcurrentBuilds": 0,
251
+ "java.recommendations.enabled": False,
252
+ "java.help.showReleaseNotes": False,
253
+ "redhat.telemetry.enabled": False,
254
+ # Java exclusions
255
+ "java.project.sourcePaths": [],
256
+ "java.project.referencedLibraries": [],
257
+ "files.exclude": {
258
+ "**/.classpath": True,
259
+ "**/.project": True,
260
+ "**/.factorypath": True,
261
+ },
262
+ # Disable PlatformIO auto-detection for .ino files in FastLED project
263
+ "platformio.disableToolchainAutoInstaller": True,
264
+ "platformio-ide.autoRebuildAutocompleteIndex": False,
265
+ "platformio-ide.activateProjectOnTextEditorChange": False,
266
+ "platformio-ide.autoOpenPlatformIOIniFile": False,
267
+ "platformio-ide.autoPreloadEnvTasks": False,
268
+ "platformio-ide.autoCloseSerialMonitor": False,
269
+ "platformio-ide.disablePIOHomeStartup": True,
270
+ # Disable conflicting extensions
271
+ "extensions.ignoreRecommendations": True,
272
+ # Semantic token color customizations for better C++ development
273
+ "editor.semanticTokenColorCustomizations": {
274
+ "rules": {
275
+ # Types (classes, structs, enums) - Teal/Cyan
276
+ "class": "#4EC9B0",
277
+ "struct": "#4EC9B0",
278
+ "type": "#4EC9B0",
279
+ "enum": "#4EC9B0",
280
+ "enumMember": "#B5CEA8",
281
+ "typedef": "#4EC9B0",
282
+ # Variables - Almost pure white for maximum readability
283
+ "variable": "#FAFAFA",
284
+ "variable.local": "#FAFAFA",
285
+ # Parameters - Orange for clear distinction
286
+ "parameter": "#FF8C42",
287
+ "variable.parameter": "#FF8C42",
288
+ # Properties - Light purple/pink
289
+ "property": "#D197D9",
290
+ # Functions and methods - Yellow
291
+ "function": "#DCDCAA",
292
+ "method": "#DCDCAA",
293
+ "function.declaration": "#DCDCAA",
294
+ "method.declaration": "#DCDCAA",
295
+ # Namespaces - Soft blue
296
+ "namespace": "#86C5F7",
297
+ # Constants and readonly - Light green with italic
298
+ "variable.readonly": {"foreground": "#B5CEA8", "fontStyle": "italic"},
299
+ "variable.defaultLibrary": "#B5CEA8",
300
+ # Macros and defines - Muted red/salmon
301
+ "macro": "#E06C75",
302
+ # String literals - Peach/salmon
303
+ "string": "#CE9178",
304
+ # Numbers - Light green
305
+ "number": "#B5CEA8",
306
+ # Keywords - Pink/magenta
307
+ "keyword": "#C586C0",
308
+ # Storage specifiers - Bright magenta/pink
309
+ "keyword.storage": "#FF79C6",
310
+ "storageClass": "#FF79C6",
311
+ # Built-in types - Different from user-defined types
312
+ "type.builtin": "#569CD6",
313
+ "keyword.type": "#569CD6",
314
+ # Comments - Green
315
+ "comment": "#6A9955",
316
+ "comment.documentation": "#6A9955",
317
+ }
318
+ },
319
+ # Inlay hints - Brighter gray for better visibility
320
+ "editor.inlayHints.fontColor": "#808080",
321
+ "editor.inlayHints.background": "#3C3C3C20",
322
+ }
323
+
324
+ if settings_json_path.exists():
325
+ # Merge with existing
326
+ try:
327
+ with open(settings_json_path, "r") as f:
328
+ data = json.load(f)
329
+ except json.JSONDecodeError:
330
+ data = {}
331
+ else:
332
+ data = {}
333
+
334
+ # Update settings
335
+ data.update(fastled_settings)
336
+
337
+ # Write back
338
+ settings_json_path.parent.mkdir(exist_ok=True)
339
+ with open(settings_json_path, "w") as f:
340
+ json.dump(data, f, indent=4)
341
+
342
+ print(
343
+ f"✅ Updated {settings_json_path} with comprehensive FastLED development settings"
344
+ )
@@ -0,0 +1,148 @@
1
+ """
2
+ Interruptible HTTP requests that can be cancelled with Ctrl+C.
3
+
4
+ This module provides cross-platform HTTP request functionality that can be
5
+ interrupted with Ctrl+C by using asyncio cancellation and periodic checks.
6
+ """
7
+
8
+ import asyncio
9
+
10
+ import httpx
11
+
12
+
13
+ class InterruptibleHTTPRequest:
14
+ """A wrapper for making HTTP requests that can be interrupted by Ctrl+C."""
15
+
16
+ def __init__(self):
17
+ self.cancelled = False
18
+
19
+ async def _make_request_async(
20
+ self,
21
+ url: str,
22
+ files: dict,
23
+ headers: dict,
24
+ transport: httpx.HTTPTransport | None = None,
25
+ timeout: float = 240,
26
+ follow_redirects: bool = True,
27
+ ) -> httpx.Response:
28
+ """Make an async HTTP request."""
29
+ # Convert sync transport to async transport if provided
30
+ async_transport = None
31
+ if transport is not None:
32
+ # For IPv4 connections, create async transport with local address
33
+ async_transport = httpx.AsyncHTTPTransport(local_address="0.0.0.0")
34
+
35
+ async with httpx.AsyncClient(
36
+ transport=async_transport,
37
+ timeout=timeout,
38
+ ) as client:
39
+ response = await client.post(
40
+ url,
41
+ follow_redirects=follow_redirects,
42
+ files=files,
43
+ headers=headers,
44
+ )
45
+ return response
46
+
47
+ def make_request_interruptible(
48
+ self,
49
+ url: str,
50
+ files: dict,
51
+ headers: dict,
52
+ transport: httpx.HTTPTransport | None = None,
53
+ timeout: float = 240,
54
+ follow_redirects: bool = True,
55
+ ) -> httpx.Response:
56
+ """Make an HTTP request that can be interrupted by Ctrl+C."""
57
+ try:
58
+ # Create a new event loop if we're not in one
59
+ try:
60
+ loop = asyncio.get_running_loop()
61
+ # We're already in an event loop, use run_in_executor
62
+ return asyncio.run_coroutine_threadsafe(
63
+ self._run_with_keyboard_check(
64
+ url, files, headers, transport, timeout, follow_redirects
65
+ ),
66
+ loop,
67
+ ).result()
68
+ except RuntimeError:
69
+ # No running loop, create one
70
+ return asyncio.run(
71
+ self._run_with_keyboard_check(
72
+ url, files, headers, transport, timeout, follow_redirects
73
+ )
74
+ )
75
+ except KeyboardInterrupt:
76
+ print("\nHTTP request cancelled by user")
77
+ raise
78
+
79
+ async def _run_with_keyboard_check(
80
+ self,
81
+ url: str,
82
+ files: dict,
83
+ headers: dict,
84
+ transport: httpx.HTTPTransport | None = None,
85
+ timeout: float = 240,
86
+ follow_redirects: bool = True,
87
+ ) -> httpx.Response:
88
+ """Run the request with periodic keyboard interrupt checks."""
89
+ task = asyncio.create_task(
90
+ self._make_request_async(
91
+ url, files, headers, transport, timeout, follow_redirects
92
+ )
93
+ )
94
+
95
+ # Poll for keyboard interrupt while waiting for the request
96
+ # This approach allows the task to be cancelled when KeyboardInterrupt
97
+ # is raised in the calling thread
98
+ while not task.done():
99
+ try:
100
+ # Wait for either completion or a short timeout
101
+ response = await asyncio.wait_for(asyncio.shield(task), timeout=0.1)
102
+ return response
103
+ except asyncio.TimeoutError:
104
+ # Continue waiting - the short timeout allows for more responsive
105
+ # cancellation when KeyboardInterrupt is raised
106
+ continue
107
+ except KeyboardInterrupt:
108
+ task.cancel()
109
+ print("\nHTTP request cancelled by user")
110
+ raise
111
+
112
+ return await task
113
+
114
+
115
+ def make_interruptible_post_request(
116
+ url: str,
117
+ files: dict | None = None,
118
+ headers: dict | None = None,
119
+ transport: httpx.HTTPTransport | None = None,
120
+ timeout: float = 240,
121
+ follow_redirects: bool = True,
122
+ ) -> httpx.Response:
123
+ """
124
+ Convenience function to make an interruptible POST request.
125
+
126
+ Args:
127
+ url: The URL to make the request to
128
+ files: Files to upload (optional)
129
+ headers: HTTP headers (optional)
130
+ transport: HTTP transport to use (optional)
131
+ timeout: Request timeout in seconds
132
+ follow_redirects: Whether to follow redirects
133
+
134
+ Returns:
135
+ The HTTP response
136
+
137
+ Raises:
138
+ KeyboardInterrupt: If the request was cancelled by Ctrl+C
139
+ """
140
+ request_handler = InterruptibleHTTPRequest()
141
+ return request_handler.make_request_interruptible(
142
+ url=url,
143
+ files=files or {},
144
+ headers=headers or {},
145
+ transport=transport,
146
+ timeout=timeout,
147
+ follow_redirects=follow_redirects,
148
+ )
fastled/keyboard.py CHANGED
@@ -20,6 +20,7 @@ class SpaceBarWatcher:
20
20
  return True
21
21
  if time.time() - start_time > timeout:
22
22
  return False
23
+ time.sleep(0.1)
23
24
  finally:
24
25
  watcher.stop()
25
26
 
fastled/keyz.py ADDED
@@ -0,0 +1,84 @@
1
+ # This was an experiment for https://localhost
2
+ # Important for audio sampling from sites like Youtube or an app like Audacity.
3
+ # However, pkg_resources does not work in the exe version of this app.
4
+ # For this to work pyinstaller needs special handling (probably a spec-file)
5
+ # in order to properly package up external resources.
6
+
7
+
8
+ from dataclasses import dataclass
9
+
10
+ _ENABLE_SSL_CONFIG = False
11
+
12
+
13
+ @dataclass
14
+ class SslConfig:
15
+ cert: str
16
+ key: str
17
+
18
+
19
+ def get_ssl_config() -> SslConfig | None:
20
+ """Get the keys for the server"""
21
+ if not _ENABLE_SSL_CONFIG:
22
+ return None
23
+ return SslConfig(
24
+ cert=_CERT,
25
+ key=_PRIVATE_KEY,
26
+ )
27
+
28
+
29
+ _PRIVATE_KEY = """-----BEGIN PRIVATE KEY-----
30
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlxbWcpUXPpjqs
31
+ DJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcHS+pkgXnG46g6bUcL/AK5Ba08
32
+ vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh2jSCeOTagNoaHLYEugARkkEu
33
+ 0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU7K+BLWsCiZ308NMpzHF5APZ6
34
+ YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXWyyPG0jwPQyjBot/cNtt8GrsN
35
+ gcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jprHEsw+/UhnG6Qhksm8C/DN9kP
36
+ hselewffAgMBAAECggEARjQ6YTo+Mkvf8WGGbRjLxteJRiBX7lKOD+V7aY2ce06P
37
+ 21LREbbTCm+vljXZN2OnqvJomsjNLCsH21+jaTOIZg5x79LyDn2Au7N8CWdELwVT
38
+ mTbBO2Ql63P4R0UY54onGYNcOeV6z+OX9u7a8L/qYHCxFdHalBZpsfj0gjaQeStJ
39
+ JSnvGjo6tKkwC/nUmX01qEVQgUO1+39WYqCaIWjijZNXt6XiKclEuu1AkL0u6Mpt
40
+ CzvzEDrEA66D0Lvl3Tek9B4O16Oie5anNnNMHigwU9yVc6dI8vDCRSEiz7laPTFK
41
+ xzOCQmqPGClKXkX3U+OvZp/Ss9U26Wpu0AbRKTvzAQKBgQDsMR9NjMpOmUaWkAwl
42
+ 1wlUxsZ9YkRuTy7R3RfIdYWj6Lcoc4/iN0qILFM7xidkHhYTFqnsnP1SQkV6lEHV
43
+ OalYxZu9F2l1rHPc8G5YWh/KOg1rAEI47MVT4iwhA2fw6JLti/rm25AeSTMjSTqu
44
+ ht3146036opcIF3v86oGUrSXDwKBgQD5CsNcwLeUDGXozjq62T8/mTYwd2Tw3aiY
45
+ KaGp+exAW321vYm5SKsMitBMGU2tGFlv5eptSI48h7SCpgnszaexw7yj30KuvqjG
46
+ bBqq/MsKuXHyn2sG0A7MJ6zfk+4l46B45blDJZ+x7xL0dyS4UCU3zUeesgSGo4zK
47
+ ZOspPIQCMQKBgQCk35VuWP1P6IbxyxPvxi/pUeh01gfWyMdyD9fuQrtLM8PHJQQn
48
+ cVlBvU9MxoHwzV+za3qqhNwAc+p0KtHZuiqQoUCZuqIPVpZ6gAtG+YJ/dA6xxrhz
49
+ bDRC3frYALyp2m/WCoTWaiYsPgTIePHRqqt+XbQo+DwlGyL3wSvKxijx2QKBgCb0
50
+ OwioEE70/X/DukX9szn0chh0pHJUiYl7gZD/yadraCdkRUWZC0BD+j7c+lxn4Z1y
51
+ HhAH+E+Zfm+tHwJOTLuufTQ4uMpygh2/TRCPyAaeaSdlLi17n8TpM84o6mg8yZ3/
52
+ eNH68Za4aYOZm0HFL30h++DjwXd534zM6keh8pgRAoGBAKUrsjDGjuSo8l1fi4Cq
53
+ INu/rQop2h/db02zyJP5q7NKhE1nqogeLwwn+2M/LtHQ1nIzZR+rvrLNgt6oWY31
54
+ sPsv8JUfVT8GmdhU9KKmizK6eUu3rWdj2+rJARmuEaPmHcD5O6oJaGU0qadqQP34
55
+ H+enwWmpyZXAIbEu/q63DFhV
56
+ -----END PRIVATE KEY-----"""
57
+
58
+ _CERT = """-----BEGIN _CERTIFICATE-----
59
+ MIIEfTCCAuWgAwIBAgIRAPb7jkLrCuqToG+s3AQYeuUwDQYJKoZIhvcNAQELBQAw
60
+ gakxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE/MD0GA1UECww2REVT
61
+ S1RPUC1JMzcxOERPXFphY2ggVm9yaGllc0BERVNLVE9QLUkzNzE4RE8gKG5pdGVy
62
+ aXMpMUYwRAYDVQQDDD1ta2NlcnQgREVTS1RPUC1JMzcxOERPXFphY2ggVm9yaGll
63
+ c0BERVNLVE9QLUkzNzE4RE8gKG5pdGVyaXMpMB4XDTI1MDQyODAwMzk1MFoXDTI3
64
+ MDcyODAwMzk1MFowajEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
65
+ ZmljYXRlMT8wPQYDVQQLDDZERVNLVE9QLUkzNzE4RE9cWmFjaCBWb3JoaWVzQERF
66
+ U0tUT1AtSTM3MThETyAobml0ZXJpcykwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
67
+ ggEKAoIBAQDlxbWcpUXPpjqsDJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcH
68
+ S+pkgXnG46g6bUcL/AK5Ba08vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh
69
+ 2jSCeOTagNoaHLYEugARkkEu0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU
70
+ 7K+BLWsCiZ308NMpzHF5APZ6YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXW
71
+ yyPG0jwPQyjBot/cNtt8GrsNgcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jpr
72
+ HEsw+/UhnG6Qhksm8C/DN9kPhselewffAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIF
73
+ oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSPBydvhr9wI+FsoW/H
74
+ WK3DbS8IUDAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGB
75
+ AJVrF1yczZaxt+A2AhdeFbJQUR6NzGBTc20YeWF1YzLV5sV3QVumwZLP2M9ggRgd
76
+ xWV0xfwUHobFQk6RIPTADcLKctiurql0cgF4DPnpWVvto9RM00U3AkQcMj3xtKBV
77
+ wUqo83TcbqgL+euudFZ09gGTs9u9AENaZPcMh+rW8DDO92t+EwMI/IfopxVOJGUB
78
+ RSM3yTwV93BMYBuddt8mclzLzPK/1WONfsHU2xEascaHR1tYMOmJN9Vq4o0fzWxo
79
+ a2vI6K0aJqZV/ztdXq3akwLc6/e9hyptHWa0i/022xVCyNWIlnuEhT7ENMPxh6rX
80
+ ZCQCZVnhcSWAyFjggLJql3aSID5fPF8rmN7wWsB/I5pl9qwMR1/THMPrm5aWn1Xj
81
+ xW6PxkSGm73kd57DH7tqm5HTd8eYCbnsFofI9rC7xI6HCfwchKp+YHvIEu/LJ56E
82
+ FLnCZW/orYkHCzWntzxv1bddrw1BwaNR8Q+mu3imRP8fuyXb2UkFkINVVyOOWHuW
83
+ Kw==
84
+ -----END _CERTIFICATE-----"""
fastled/live_client.py CHANGED
@@ -16,13 +16,19 @@ class LiveClient:
16
16
  auto_start: bool = True,
17
17
  auto_updates: bool = True,
18
18
  open_web_browser: bool = True,
19
+ http_port: (
20
+ int | None
21
+ ) = None, # None means auto select a free port. -1 means no server.
19
22
  keep_running: bool = True,
20
23
  build_mode: BuildMode = BuildMode.QUICK,
21
24
  profile: bool = False,
25
+ no_platformio: bool = False,
26
+ enable_https: bool = True, # Enable HTTPS for the local server
22
27
  ) -> None:
23
28
  self.sketch_directory = sketch_directory
24
29
  self.host = host
25
30
  self.open_web_browser = open_web_browser
31
+ self.http_port = http_port
26
32
  self.keep_running = keep_running
27
33
  self.build_mode = build_mode
28
34
  self.profile = profile
@@ -30,6 +36,8 @@ class LiveClient:
30
36
  self.shutdown = threading.Event()
31
37
  self.thread: threading.Thread | None = None
32
38
  self.auto_updates = auto_updates
39
+ self.no_platformio = no_platformio
40
+ self.enable_https = enable_https
33
41
  if auto_start:
34
42
  self.start()
35
43
  if self.auto_updates is False:
@@ -47,18 +55,23 @@ class LiveClient:
47
55
  build_mode=self.build_mode,
48
56
  profile=self.profile,
49
57
  shutdown=self.shutdown,
58
+ http_port=self.http_port,
59
+ no_platformio=self.no_platformio,
60
+ enable_https=self.enable_https,
50
61
  )
51
62
  return rtn
52
63
 
53
64
  def url(self) -> str:
54
65
  """Get the URL of the server."""
66
+ from fastled.settings import SERVER_PORT
67
+
55
68
  if isinstance(self.host, CompileServer):
56
69
  return self.host.url()
57
70
  if self.host is None:
58
71
  import warnings
59
72
 
60
73
  warnings.warn("TODO: use the actual host.")
61
- return "http://localhost:9021"
74
+ return f"http://localhost:{SERVER_PORT}"
62
75
  return self.host
63
76
 
64
77
  @property
@@ -89,3 +102,15 @@ class LiveClient:
89
102
 
90
103
  def __exit__(self, exc_type, exc_value, traceback) -> None:
91
104
  self.finalize()
105
+
106
+ def get_emsdk_headers(self, filepath: Path) -> None:
107
+ """Get EMSDK headers ZIP data from the server and save to filepath."""
108
+ if isinstance(self.host, CompileServer):
109
+ self.host.get_emsdk_headers(filepath)
110
+ else:
111
+ # Handle string host or None case by using web_compile approach
112
+ from fastled.settings import DEFAULT_URL
113
+ from fastled.util import download_emsdk_headers
114
+
115
+ base_url = self.host if isinstance(self.host, str) else DEFAULT_URL
116
+ download_emsdk_headers(base_url, filepath)