Skip to content

Commit 9abc4a2

Browse files
committed
Add E_PaintBrush.sc DMX/Network: store in layers and status control
Doc updates Live scripts ========== - Add E_PaintBrush.sc Backend ======= - Live scripts: add drawLine3D and blur2D and sqrt, file Update: no requestUIUpdate - LayerManager: check for old style presets - DMX in / Network in: store layers in virtualChannels - Network in/out: add status
1 parent 865590f commit 9abc4a2

13 files changed

Lines changed: 469 additions & 104 deletions

File tree

CLAUDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,11 @@ MoonLight periodically merges upstream ESP32-sveltekit changes. To keep merges c
161161
**Memory** — On S3/P4 with PSRAM, `operator new` is overridden in `main.cpp` to prefer PSRAM for allocations above a threshold. On ESP32-D0 (no PSRAM), heap is very tight; watch flash usage too (target <3MB firmware).
162162

163163
**Logging** — Use `EXT_LOGD` / `EXT_LOGI` / `EXT_LOGW` / `EXT_LOGE` (not `Serial.printf` directly in module/node code). Tag constants: `ML_TAG` (MoonLight), `MB_TAG` (MoonBase).
164+
165+
## Additional Reference Documents
166+
167+
Supplementary guidance for AI assistants lives in `/misc/instructions/`:
168+
169+
- **`MoonLight.md`** — project philosophy, architectural goals, future roadmap, and broader context not covered above.
170+
- **`GEMINI.md`** — structural overview of the upstream [ESP32-sveltekit](https://github.com/theelims/ESP32-sveltekit) repo (services, file layout, key dependencies). Useful when touching upstream-derived code.
171+
- **`svelte.instructions.md`** — Svelte 5 development guidelines. Applies **only** to moonbase-specific frontend files (`src/routes/moonbase/`, `src/lib/components/moonbase/`, `moonbase_utilities.ts`, `moonbase_models.ts`). Do not apply runes patterns or reformatting to upstream files.

docs/moonlight/drivers.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ Sends pixel data over the network to LED controllers and DMX fixtures. Supports
113113
* **FPS Limiter**: Maximum frames per second sent. Art-Net spec recommends ~44 FPS; higher rates (up to ~130 FPS tested) work with most controllers.
114114
* **Universe size**: Channels per universe (max 512). Match the setting on your controller.
115115
* **Used channels** *(read-only)*: Channels actually used per universe after rounding down to a whole number of lights (e.g. 510 for RGB at 512-channel universes). Always at least one light's worth of channels — if **Universe size** is set smaller than the channels per light, one full light is still included per universe.
116-
* **#Outputs per IP**: Number of physical outputs per controller. When all outputs for one IP are filled, sending continues on the next IP.
117-
* **Universes per output**: How many universes each output handles, determining the maximum lights per output.
116+
* **#Outputs per IP**: Number of physical outputs per controller. When all outputs for one IP are filled, sending continues on the next IP. E.g. the Club Lights 12 Pro Artnet Controller (see below) has 12 outputs.
117+
* **Universes per output**: How many universes each output handles, determining the maximum lights per output. (check total universes)
118118
* **Total universes** *(read-only)*: Universes required to transmit all lights.
119-
* **Channels per output**: Channel budget per output.
119+
* **Channels per output**: Channel budget per output. Make sure it matches the number of lights per output (check total channels)
120120
* **Total channels** *(read-only)*: Total channels sent across all outputs and IPs.
121121

122122
!!! tip "Controller settings"

docs/moonlight/effects-tutorial.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,6 @@ void loop() {
266266
}
267267
```
268268

269-
... watchdog due to expensive inoise8 function, only on cube, not on panel ...
270-
271269
Each slice through the cube looks like a shifting 2D noise field. The z offset in the noise coordinates makes adjacent layers look related but distinct — like a 3D fire or fog.
272270

273271
![E_Noise3D](../media/moonlight/effects/E_Noise3D.gif)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// E_PaintBrush.sc - Paintbrush
2+
// Audio-reactive animated lines driven by beatsin8 and audio bands.
3+
// Works in 2D (drawLine) and 3D (drawLine3D) based on the layer depth.
4+
// oscillatorOffset: speed of oscillation (1-16)
5+
// numLines: number of simultaneous lines (1-40)
6+
// fadeRate: trail fade speed (0=no fade, 128=fast)
7+
// original idea: @TroyHacks
8+
9+
uint8_t oscillatorOffset = 4;
10+
uint8_t numLines = 20;
11+
uint8_t fadeRate = 40;
12+
13+
void setup() {
14+
addControl(&oscillatorOffset, "oscillatorOffset", "slider", 1, 16);
15+
addControl(&numLines, "numLines", "slider", 1, 40);
16+
addControl(&fadeRate, "fadeRate", "slider", 0, 128);
17+
}
18+
19+
uint8_t hue = 0;
20+
21+
void loop() {
22+
hue++;
23+
fadeToBlackBy(fadeRate);
24+
25+
for (uint8_t i = 0; i < numLines; i++) {
26+
uint8_t bin = i * 16 / numLines;
27+
uint8_t band = bands[bin];
28+
29+
uint8_t x1 = beatsin8(oscillatorOffset * 1 + band / 16, 0, width - 1, band, 0);
30+
uint8_t x2 = beatsin8(oscillatorOffset * 2 + band / 16, 0, width - 1, band, 0);
31+
uint8_t y1 = beatsin8(oscillatorOffset * 3 + band / 16, 0, height - 1, band, 0);
32+
uint8_t y2 = beatsin8(oscillatorOffset * 4 + band / 16, 0, height - 1, band, 0);
33+
34+
CRGB color = ColorFromPalette(i * 255 / numLines + hue, 255);
35+
36+
if (depth > 1) {
37+
uint8_t z1 = beatsin8(oscillatorOffset * 5 + band / 16, 0, depth - 1, band, 0);
38+
uint8_t z2 = beatsin8(oscillatorOffset * 6 + band / 16, 0, depth - 1, band, 0);
39+
float dx = x2 - x1;
40+
float dy = y2 - y1;
41+
float dz = z2 - z1;
42+
float len = sqrt(dx * dx + dy * dy + dz * dz);
43+
if (len > 1) drawLine3D(x1, y1, z1, x2, y2, z2, color);
44+
} else {
45+
float dx = x2 - x1;
46+
float dy = y2 - y1;
47+
float len = sqrt(dx * dx + dy * dy);
48+
if (len > 1) drawLine(x1, y1, x2, y2, color);
49+
}
50+
}
51+
}

misc/instructions/MoonLight.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@
3030
- MoonLight should run on all ESP32 devices. Mainly used and tested now on ESP32-D0 (standard dev MCU’s), ESP32-S3 and ESP32-P4. Running MoonLight on ESP32-D0 is a challenge as heap is pretty much used up, and Flash size is limited. We currently use a 3MB flash partition so 4MB boards cannot do OTA at the moment. In the future we plan to implement a minimal OTA partition (as investigated at Tasmota/ESPHome …), so also small flash size boards (4MB) can do OTA.
3131
- Optimize on the critical process: running effects / modifiers (producers) and displaying them on leds (direct via pins on the board using led drivers or via Art-Net over the network). So code for the critical process should compile to minimal assembler code and checks on code should be minimal. (Example use int instead of unsigned int as no need to control if it is negative if the developer should ensure it is possible, or do not check if a const char * is null as it’s the developer responsibility to make sure this is the case). In short: the code should not be resilient to developer mistakes, the developer must solve the mistakes.
3232
- Non critical processes can run in reasonable time, in general 20ms response time for UI is more than enough.
33-
- See also GIMINI.md in /misc/parking for info about upstream sveltekit
33+
- See also GEMINI.md in /misc/instructions for info about upstream sveltekit
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
description: 'Svelte 5 and SvelteKit development standards and best practices for component-based user interfaces and full-stack applications'
3+
applyTo: '**/*.svelte, **/*.ts, **/*.js, **/*.css, **/*.scss, **/*.json'
4+
---
5+
6+
(original https://github.com/github/awesome-copilot/blob/main/instructions/svelte.instructions.md)
7+
8+
# Svelte 5 and SvelteKit Development Instructions
9+
10+
Instructions for building high-quality Svelte 5 and SvelteKit applications with modern runes-based reactivity, TypeScript, and performance optimization.
11+
12+
## MoonLight-Specific Constraints
13+
14+
> **Important:** MoonLight periodically merges upstream [ESP32-sveltekit](https://github.com/theelims/ESP32-sveltekit) changes. Upstream files use Svelte stores (not runes) and must **not** be reformatted or modernised to avoid merge conflicts.
15+
>
16+
> Apply these guidelines **only** to MoonLight-specific files:
17+
> - `interface/src/routes/moonbase/`
18+
> - `interface/src/lib/components/moonbase/`
19+
> - `interface/src/lib/stores/moonbase_utilities.ts`
20+
> - `interface/src/lib/types/moonbase_models.ts`
21+
>
22+
> Do **not** migrate upstream files to runes, reformat them, or change their store patterns.
23+
24+
## Project Context
25+
- Svelte 5.x with runes system ($state, $derived, $effect, $props, $bindable) — moonbase-specific files only
26+
- SvelteKit for full-stack applications with file-based routing
27+
- TypeScript for type safety and better developer experience
28+
- Component-scoped styling with CSS custom properties
29+
- Progressive enhancement and performance-first approach
30+
- Modern build tooling (Vite) with optimizations
31+
32+
## Core Concepts
33+
34+
### Architecture
35+
- Use Svelte 5 runes system for all reactivity instead of legacy stores
36+
- Organize components by feature or domain for scalability
37+
- Separate presentation components from logic-heavy components
38+
- Extract reusable logic into composable functions
39+
- Implement proper component composition with slots and snippets
40+
- Use SvelteKit's file-based routing with proper load functions
41+
42+
### Component Design
43+
- Follow single responsibility principle for components
44+
- Use `<script lang="ts">` with runes syntax as default
45+
- Keep components small and focused on one concern
46+
- Implement proper prop validation with TypeScript annotations
47+
- Use `{#snippet}` blocks for reusable template logic within components
48+
- Use slots for component composition and content projection
49+
- Pass `children` snippet for flexible parent-child composition
50+
- Design components to be testable and reusable
51+
52+
## Reactivity and State
53+
54+
### Svelte 5 Runes System
55+
- Use `$state()` for reactive local state management
56+
- Implement `$derived()` for computed values and expensive calculations
57+
- Use `$derived.by()` for complex computations beyond simple expressions
58+
- Use `$effect()` sparingly - prefer `$derived` or function bindings for state sync
59+
- Implement `$effect.pre()` for running code before DOM updates
60+
- Use `untrack()` to prevent infinite loops when reading/writing same state in effects
61+
- Define component props with `$props()` and destructuring with TypeScript annotations
62+
- Use `$bindable()` for two-way data binding between components
63+
- Migrate from legacy stores to runes for better performance
64+
- Override derived values directly for optimistic UI patterns (Svelte 5.25+)
65+
66+
### State Management
67+
- Use `$state()` for local component state
68+
- Implement type-safe context with `createContext()` helper over raw `setContext`/`getContext`
69+
- Use context API for sharing reactive state down component trees
70+
- Avoid global `$state` modules for SSR - use context to prevent cross-request data leaks
71+
- Use SvelteKit stores for global application state when needed
72+
- Keep state normalized for complex data structures
73+
- Prefer `$derived()` over `$effect()` for computed values
74+
- Implement proper state persistence for client-side data
75+
76+
### Effect Best Practices
77+
- **Avoid** using `$effect()` to synchronize state - use `$derived()` instead
78+
- **Do** use `$effect()` for side effects: analytics, logging, DOM manipulation
79+
- **Do** return cleanup functions from effects for proper teardown
80+
- Use `$effect.pre()` when code must run before DOM updates (e.g., scroll position)
81+
- Use `$effect.root()` for manually controlled effects outside component lifecycle
82+
- Use `untrack()` to read state without creating dependencies in effects
83+
- Remember: async code in effects doesn't track dependencies after `await`
84+
85+
## SvelteKit Patterns
86+
87+
### Routing and Layouts
88+
- Use `+page.svelte` for page components with proper SEO
89+
- Implement `+layout.svelte` for shared layouts and navigation
90+
- Handle routing with SvelteKit's file-based system
91+
92+
### Data Loading and Mutations
93+
- Use `+page.server.ts` for server-side data loading and API calls
94+
- Implement form actions in `+page.server.ts` for data mutations
95+
- Use `+server.ts` for API endpoints and server-side logic
96+
- Use SvelteKit's load functions for server-side and universal data fetching
97+
- Implement proper loading, error, and success states
98+
- Handle streaming data with promises in server load functions
99+
- Use `invalidate()` and `invalidateAll()` for cache management
100+
- Implement optimistic updates for better user experience
101+
- Handle offline scenarios and network errors gracefully
102+
103+
### Forms and Validation
104+
- Use SvelteKit's form actions for server-side form handling
105+
- Implement progressive enhancement with `use:enhance`
106+
- Use `bind:value` for controlled form inputs
107+
- Validate data both client-side and server-side
108+
- Handle file uploads and complex form scenarios
109+
- Implement proper accessibility with labels and ARIA attributes
110+
111+
## UI and Styling
112+
113+
### Styling
114+
- Use component-scoped styles with `<style>` blocks
115+
- Implement CSS custom properties for theming and design systems
116+
- Use `class:` directive for conditional styling
117+
- Follow BEM or utility-first CSS conventions
118+
- Implement responsive design with mobile-first approach
119+
- Use `:global()` sparingly for truly global styles
120+
121+
### Transitions and Animations
122+
- Use `transition:` directive for enter/exit animations (fade, slide, scale, fly)
123+
- Use `in:` and `out:` for separate enter/exit transitions
124+
- Implement `animate:` directive with `flip` for smooth list reordering
125+
- Create custom transitions for branded motion design
126+
- Use `|local` modifier to trigger transitions only on direct changes
127+
- Combine transitions with keyed `{#each}` blocks for list animations
128+
129+
## TypeScript and Tooling
130+
131+
### TypeScript Integration
132+
- Enable strict mode in `tsconfig.json` for maximum type safety
133+
- Annotate props with TypeScript: `let { name }: { name: string } = $props()`
134+
- Type event handlers, refs, and SvelteKit's generated types
135+
- Use generic types for reusable components
136+
- Leverage `$types.ts` files generated by SvelteKit
137+
- Implement proper type checking with `svelte-check`
138+
- Use type inference where possible to reduce boilerplate
139+
140+
### Development Tools
141+
- Use ESLint with eslint-plugin-svelte and Prettier for code consistency
142+
- Use Svelte DevTools for debugging and performance analysis
143+
- Keep dependencies up to date and audit for security vulnerabilities
144+
- Document complex components and logic with JSDoc
145+
- Follow Svelte's naming conventions (PascalCase for components, camelCase for functions)
146+
147+
## Production Readiness
148+
149+
### Performance Optimization
150+
- Use keyed `{#each}` blocks for efficient list rendering
151+
- Implement lazy loading with dynamic imports and `<svelte:component>`
152+
- Use `$derived()` for expensive computations to avoid unnecessary recalculations
153+
- Use `$derived.by()` for complex derived values that require multiple statements
154+
- Avoid `$effect()` for derived state - it's less efficient than `$derived()`
155+
- Leverage SvelteKit's automatic code splitting and preloading
156+
- Optimize bundle size with tree shaking and proper imports
157+
- Profile with Svelte DevTools to identify performance bottlenecks
158+
- Use `$effect.tracking()` in abstractions to conditionally create reactive listeners
159+
160+
### Error Handling
161+
- Implement `+error.svelte` pages for route-level error boundaries
162+
- Use try/catch blocks in load functions and form actions
163+
- Provide meaningful error messages and fallback UI
164+
- Log errors appropriately for debugging and monitoring
165+
- Handle validation errors in forms with proper user feedback
166+
- Use SvelteKit's `error()` and `redirect()` helpers for proper responses
167+
- Track pending promises with `$effect.pending()` for loading states
168+
169+
### Testing
170+
- Write unit tests for components using Vitest and Testing Library
171+
- Test component behavior, not implementation details
172+
- Use Playwright for end-to-end testing of user workflows
173+
- Mock SvelteKit's load functions and stores appropriately
174+
- Test form actions and API endpoints thoroughly
175+
- Implement accessibility testing with axe-core
176+
177+
### Security
178+
- Sanitize user inputs to prevent XSS attacks
179+
- Use `@html` directive carefully and validate HTML content
180+
- Implement proper CSRF protection with SvelteKit
181+
- Validate and sanitize data in load functions and form actions
182+
- Use HTTPS for all external API calls and production deployments
183+
- Store sensitive data securely with proper session management
184+
185+
### Accessibility
186+
- Use semantic HTML elements and proper heading hierarchy
187+
- Implement keyboard navigation for all interactive elements
188+
- Provide proper ARIA labels and descriptions
189+
- Ensure color contrast meets WCAG guidelines
190+
- Test with screen readers and accessibility tools
191+
- Implement focus management for dynamic content
192+
193+
### Deployment
194+
- Use environment variables for configuration across different deployment stages
195+
- Implement proper SEO with SvelteKit's meta tags and structured data
196+
- Deploy with appropriate SvelteKit adapter based on hosting platform
197+
198+
## Implementation Process
199+
1. Initialize SvelteKit project with TypeScript and desired adapters
200+
2. Set up project structure with proper folder organization
201+
3. Define TypeScript interfaces and component props
202+
4. Implement core components with Svelte 5 runes
203+
5. Add routing, layouts, and navigation with SvelteKit
204+
6. Implement data loading and form handling
205+
7. Add styling system with custom properties and responsive design
206+
8. Implement error handling and loading states
207+
9. Add comprehensive testing coverage
208+
10. Optimize performance and bundle size
209+
11. Ensure accessibility compliance
210+
12. Deploy with appropriate SvelteKit adapter
211+
212+
## Common Patterns
213+
- Renderless components with slots for flexible UI composition
214+
- Custom actions (`use:` directives) for cross-cutting concerns and DOM manipulation
215+
- `{#snippet}` blocks for reusable template logic within components
216+
- Type-safe context with `createContext()` for component tree state sharing
217+
- Progressive enhancement for forms and interactive features with `use:enhance`
218+
- Server-side rendering with client-side hydration for optimal performance
219+
- Function bindings (`bind:value={() => value, setValue}`) for two-way binding
220+
- Avoid `$effect()` for state synchronization - use `$derived()` or callbacks instead

platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ build_flags =
5757
-D BUILD_TARGET=\"$PIOENV\"
5858
-D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
5959
-D APP_VERSION=\"0.9.1\" ; semver compatible version string
60-
-D APP_DATE=\"20260402\" ; 🌙
60+
-D APP_DATE=\"20260403\" ; 🌙
6161

6262
-D PLATFORM_VERSION=\"pioarduino-55.03.37\" ; 🌙 make sure it matches with above plaftform
6363

src/MoonBase/LiveScriptNode.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,11 @@ static void _setPalEntryHSV(uint8_t index, uint8_t h, uint8_t s, uint8_t v) { if
7575
static void _setHSV(uint16_t indexV, uint8_t h, uint8_t s, uint8_t v) { currentNode()->layer->setRGB(indexV, CHSV(h, s, v)); }
7676
static void _setHSVXY(int x, int y, uint8_t h, uint8_t s, uint8_t v) { currentNode()->layer->setRGB(Coord3D{x, y}, CHSV(h, s, v)); }
7777

78-
// 2D drawing
78+
// 2D/3D drawing
7979
static void _drawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, CRGB color) { currentNode()->layer->drawLine(x0, y0, x1, y1, color); }
80+
static void _drawLine3D(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2, uint8_t y2, uint8_t z2, CRGB color) { currentNode()->layer->drawLine3D(x1, y1, z1, x2, y2, z2, color); }
8081
static void _drawCircle(int cx, int cy, uint8_t radius, CRGB color) { currentNode()->layer->drawCircle(cx, cy, radius, color, false); }
82+
static void _blur2d(uint8_t blur_amount) { currentNode()->layer->blur2d(blur_amount); }
8183

8284
// time of day
8385
static uint8_t _lsHour = 0;
@@ -181,6 +183,7 @@ void LiveScriptNode::setup() {
181183
addExternal("uint8_t inoise8(uint16_t,uint16_t,uint16_t)", (void*)(uint8_t (*)(uint16_t, uint16_t, uint16_t))inoise8);
182184
addExternal("uint8_t beatsin8(uint16_t,uint8_t,uint8_t,uint32_t,uint8_t)", (void*)beatsin8);
183185
addExternal("uint8_t beatsin16(uint16_t,uint16_t,uint16_t,uint32_t,uint8_t)", (void*)beatsin16);
186+
addExternal("float sqrt(float)", (void*)(float (*)(float))sqrtf);
184187
addExternal("float hypot(float,float)", (void*)(float (*)(float, float))hypot);
185188
addExternal("uint8_t beat8(uint16_t,uint32_t)", (void*)(uint8_t (*)(uint16_t, uint32_t))beat8); // saw wave
186189
addExternal("uint8_t triangle8(uint8_t)", (void*)triangle8);
@@ -229,9 +232,11 @@ void LiveScriptNode::setup() {
229232
addExternal("int gravityY", &sharedData.gravity.y);
230233
addExternal("int gravityZ", &sharedData.gravity.z);
231234

232-
// 2D drawing
235+
// 2D/3D drawing
233236
addExternal("void drawLine(uint8_t,uint8_t,uint8_t,uint8_t,CRGB)", (void*)_drawLine);
237+
addExternal("void drawLine3D(uint8_t,uint8_t,uint8_t,uint8_t,uint8_t,uint8_t,CRGB)", (void*)_drawLine3D);
234238
addExternal("void drawCircle(int,int,uint8_t,CRGB)", (void*)_drawCircle);
239+
addExternal("void blur2d(uint8_t)", (void*)_blur2d);
235240

236241
// time of day
237242
_updateTime();

0 commit comments

Comments
 (0)