PINIO: fix operand order, add pwmOnPin() to JS transpiler, unify pinioSet#2587
PINIO: fix operand order, add pwmOnPin() to JS transpiler, unify pinioSet#2587sensei-hacker wants to merge 75 commits intoiNavFlight:maintenance-10.xfrom
Conversation
Implement persistent last save directory feature that remembers the directory used in file save dialogs and defaults to it in future save operations. Changes: - Modified dialog.js showSaveDialog wrapper to store and retrieve last directory - Uses electron-store via electronAPI.storeGet/storeSet - Automatically extracts directory from selected file path - Sets as defaultPath for subsequent saves if no path specified - Cross-platform path handling (supports both / and \ separators) Benefits: - Reduces user friction when saving multiple files - Persists across app restarts - Works for all save operations (blackbox logs, diffs, configs, etc.) - No UI changes required - transparent to user - Backwards compatible - gracefully handles no saved directory Closes part of UX improvements for file management.
Move save directory logic to main process IPC handler where it belongs. The implementation now: - Stores the last used directory in electron-store - Combines saved directory with provided filename using path.join() - Handles edge cases: filename-only, full paths, no saved directory - Preserves directory on cancel (only saves on successful file selection) This fixes the issue where the implementation in renderer process was never called.
The firmware flasher was unable to match boards with multiple underscores in their names (like TBS_LUCID_H7_WING) because the old code only replaced the first underscore with a space, creating a mismatch between dictionary keys and lookup values. Changes: - Add normalizeTargetName() function to convert hyphens to underscores - Fix parseFilename() to replace ALL underscores/hyphens with spaces for display - Key releases dictionary by normalized raw_target instead of modified target - Normalize all lookups to use consistent underscore format - Support both underscore and hyphen filename variants This fixes board selection for targets like TBS_LUCID_H7_WING and enables support for hyphen-based filenames as a workaround (TBS-LUCID-H7-WING).
Eliminates 6 of 7 normalizeTargetName() calls by normalizing target names once at the data source (parseFilename/parseDevFilename functions) instead of repeatedly at consumption points. Changes: - Rename raw_target to target_id for semantic clarity - Add normalization in parseDevFilename() and parseFilename() - Remove redundant normalizeTargetName() calls throughout (6 eliminated) - Keep one necessary call for FC.CONFIG.target (external data source) Benefits: - Single normalization per filename parse (better performance) - Impossible to forget normalization (always available in result.target_id) - Clearer code intent and improved maintainability - Net reduction of 4 lines (17 insertions, 21 deletions)
- Simplify normalizeTargetName() comment to "Allow hyphens due to 9.0.0 patch" - Remove "Normalized: ..." comments since function name is self-explanatory - Keep "Display: ..." comments for clarity on user-facing strings
Implement user-friendly GPS configuration presets for M8/M9/M10 modules: - Add GPS preset dropdown with Auto-detect, Manual, M8, M9, M10, and M10-highperf options - Add GPS update rate input field (previously not exposed in UI) - Extend MSP_GPSSTATISTICS to parse hwVersion for auto-detection - Preset configuration disables (but shows) settings when active - Add preset info box explaining each preset's characteristics Presets configured per manager feedback: - M8: 4 constellations @ 8Hz (conservative) - M9: 4 constellations @ 10Hz (hardware limit: 16 sats) - M10: 4 constellations @ 6Hz (default CPU clock safe rate) - M10-highperf: 4 constellations @ 10Hz (for high-perf clock users) - Manual: Full user control (default for existing users) Auto-detection uses FC.GPS_DATA.hwVersion from MSP_GPSSTATISTICS (firmware extension pending - see documentation). Translation strings added for en locale. Related: GPS optimization research based on Jetrell's M10 testing data
…ection - Add GPS_PRESETS object with 7 preset options - Split M9 into Precision (5Hz, 32 sats) and Sport (10Hz, 16 sats) modes - Update M10 presets (3 const @ 8Hz, 4 const @ 10Hz for high-perf) - Add detectGPSPreset() to map hwVersion to preset - Add applyGPSPreset() to apply constellation/rate settings - Extend MSPHelper to parse hwVersion from MSP_GPSSTATISTICS (backward compatible) - Update HTML dropdown with new preset options Research: M9 hardware limits to 16 satellites at >=10Hz, 32 satellites at <10Hz See: claude/developer/docs/gps/m9-16-satellite-limitation-official.md
When navigating away from GPS tab and back, the preset dropdown was resetting to "Manual Settings" every time, even if the GPS module had been auto-detected. Changes: - Check if GPS data (hwVersion) is already available on tab init - If available and valid (>0), auto-detect and apply preset - If not available, fall back to manual mode as before This preserves the auto-detected preset when switching between tabs, providing a smoother user experience. Related: GPS preset UI feature
Addresses Qodo code review suggestion to ensure state consistency
when programmatically setting GPS configuration values.
When presets change constellation checkboxes and rate values, we now
trigger their 'change' events. This follows the pattern used in other
tabs (firmware_flasher.js, pid_tuning.js, sensors.js) and ensures:
- Save button state updates correctly
- Any change event handlers fire properly
- Configuration tracking remains consistent
Without .trigger('change'), programmatic updates bypass event handlers
that may be listening for user changes to these controls.
Problem: The decompiler was eagerly decompiling both operandA and operandB for all action operations, even when operandB was unused. This caused incorrect type-specific validation warnings for operations like SET_PROFILE (operation 42) which only uses operandA. Example: SET_PROFILE with operandB type=6 (PID), value=1700 (garbage data) would trigger: "Invalid PID operand value 1700. Valid range is 0-3." Solution: Added operation-specific operand handling with two categories: - operandAOnlyOperations: Operations that only use operandA (skip operandB decompilation) - noOperandOperations: Operations that use no operands (skip both) Additionally, added version detection warnings for unexpected operands: - Warns when unused operands have non-zero type or value - Helps detect firmware/configurator version mismatches - Shows operation name, type, and value for debugging Example warning: "Unexpected operand B to Set Profile operation (type=6, value=1700). This may indicate a firmware version mismatch." Benefits: - Prevents type-specific validation errors on garbage data in unused operands - Preserves validation for operations that legitimately use PID/other operands - Provides version detection when firmware adds new operand usage
Organizes the 24 configuration tabs into 8 collapsible accordion groups for improved navigation and reduced visual clutter. Groups: - Setup & Configuration (5 tabs) - Flight Control (5 tabs) - Tuning (3 tabs) - Navigation & Mission (2 tabs) - Sensors & Peripherals (3 tabs) - Data Logging (2 tabs) - Programming (2 tabs) - Tools (2 tabs) Implementation: - Added accordion group structure to index.html - Added accordion styling to main.css (preserves INAV cyan theme) - Added expand/collapse JavaScript to configurator_main.js - Added English translation keys for navigation groups - First group (Setup & Configuration) expands by default No new dependencies required - uses existing jQuery and CSS transitions.
Adds an icon-only toggle button at the bottom of the navigation menu to expand or collapse all accordion groups. Features: - SVG icon button that swaps between expand (double chevron down) and collapse (double chevron up) states - Clicking expands all 8 navigation groups or collapses to first group only - Preference persists across configurator restarts via electron-store - When all groups expanded, headers become minimal cyan divider lines to maximize vertical space - Icon-only design saves space - all 24 tabs fit without scrolling - Smooth CSS transitions for icon swap and header state changes Implementation: - Toggle button in index.html with both expand/collapse SVG icons - JavaScript in configurator_main.js handles toggle logic and persistence - CSS in main.css for compact headers when expanded and icon styling - Translation keys in locale/en/messages.json for accessibility Addresses need for quick access to all tabs while maintaining visual organization of accordion groups.
Adds WCAG-compliant accessibility features to accordion navigation: HTML changes: - Added role="button" to all group headers for screen reader compatibility - Added tabindex="0" to make headers keyboard-navigable - Added aria-expanded="false" initial state to indicate collapsed groups JavaScript changes: - Updates aria-expanded dynamically when groups expand/collapse - Added keyboard event handler for Enter and Space keys - Ensures aria-expanded is set correctly on initialization - Updates aria-expanded when using expand/collapse all button - Fixed localization attribute on JavaScript Programming tab (i18n → data-i18n) Benefits: - Screen readers announce group headers as interactive buttons - Keyboard users can Tab to headers and activate with Enter/Space - Assistive technology correctly reports expanded/collapsed state - Meets WCAG 2.1 Level AA compliance for keyboard navigation Addresses qodo-merge code review suggestions for accessibility.
Augment decompiler to track which Logic Conditions map to which lines in generated JavaScript code. This enables real-time highlighting of active conditions in the JavaScript Programming tab. Key features: - 3-pass mapping algorithm handles simple, compound, and nested conditions - Compound conditions (a && b) correctly map all sub-LCs to same line - Activator chains propagate line numbers to child LCs - Hoisted variables and sticky/timer patterns tracked - Returns lcToLineMapping in decompile() result Part of Feature 1: Active LC Highlighting (Phase 1/5)
Implement real-time status polling and gutter decoration updates in the JavaScript Programming tab to show which Logic Conditions are currently active/true. Key features: - Status polling every 100ms using MSPChainer - updateActiveHighlighting() filters true LCs and applies gutter markers - clearActiveHighlighting() when code is dirty (unsaved changes) - Proper cleanup on tab switch/disconnect - isDirty check prevents highlighting stale code Part of Feature 1: Active LC Highlighting (Phase 2/5)
Implement CSS and Monaco editor configuration for green checkmark gutter markers that indicate active/true Logic Conditions. Key changes: - Add .lc-active-true CSS class with SVG green checkmark icon - Add Monaco gutter margin background styling - Enable glyphMargin: true in Monaco editor options - Set up proper cursor and sizing for gutter decorations Part of Feature 1: Active LC Highlighting (Phase 3/5)
Fix 'Cannot read properties of null' error in updateActiveHighlighting() by adding proper null checks for FC.LOGIC_CONDITIONS_STATUS and FC.LOGIC_CONDITIONS before accessing .get(). This prevents errors during tab initialization when FC data is still loading.
The onChange handler was firing after setValue() during load, causing isDirty to be set to true even after we cleared it. This prevented the highlighting from ever appearing because updateActiveHighlighting() returns early when isDirty is true. Fixed by using setTimeout() to clear isDirty after the setValue() change event has been processed.
Previous approach tried to find LCs by parsing the generated code for /* LC X */ comments, but normal if-statements don't have these comments. New approach: - Track LC index and line content during code generation - After adding boilerplate, search for the tracked lines in final code - Map LC index to actual line numbers in final output This ensures all if-statements and their associated LCs get mapped correctly for the active highlighting feature.
Track what's being searched for and what's being found to diagnose why the LC-to-line mapping is empty.
This will help us diagnose why the green checkmarks aren't appearing by logging: - When the function is called - If isDirty is blocking it - What the LC-to-line mapping is - If FC data is available - Which LCs are TRUE - What lines should be highlighted - If decorations are created
FC.LOGIC_CONDITIONS_STATUS.get() requires a condition index parameter and returns a single value. We need getAll() to get the entire status array. This was causing 'FC data is null' errors and preventing any highlighting from appearing.
This completes the active LC highlighting feature with two major improvements: 1. FALSE condition indicators: Gray hollow circles (○) display when conditions evaluate to FALSE, complementing the existing green checkmarks for TRUE conditions. Mixed states (TRUE and FALSE on same line) show green checkmark. 2. Transpiler-side line tracking: LC-to-line mappings are now generated during transpilation, providing immediate visual feedback after "Transpile" or "Save to FC" without requiring "Load from FC" roundtrip. Correctly adjusts for auto-added import statement offset. Technical changes: - Add lineOffset tracking in codegen to account for prepended import lines - Store currentSourceLine context during statement generation - Return lcToLineMapping in transpiler result - Update tab to use transpiler mapping immediately after transpile/save - Change polling interval from 100ms to 500ms to reduce MSP load (2Hz vs 10Hz) - Reorder checks: verify FC data availability before isDirty state - Clean up excessive debug logging for production readiness Testing: - Verified circles appear on correct lines after transpile - Tested TRUE/FALSE/MIXED states display properly - Confirmed decompiler mapping replaces transpiler mapping on load - Reviewed with inav-code-review agent - all critical issues resolved
Fixes all 6 medium-priority issues identified by qodo-merge bot: 1. Clear stale decorations when loading code from FC 2. Clear mapping/decorations when loading default code 3. Add in-flight guard to prevent overlapping MSP polling requests 4. Remove duplicate intervals before adding new one 5. Improve type safety with Array.isArray() checks for MSP data Code organization improvements: - Extract highlighting logic to new module js/transpiler/lc_highlighting.js - Reduce tab file from 928 to 893 lines (-35 lines) - Create 5 testable, pure functions: categorizeLCsByStatus(), mapLCsToLines(), createMonacoDecorations(), applyDecorations(), clearDecorations() - Better separation of concerns (tab orchestrates, module implements) - Improved code maintainability and testability
Implement visual indicator showing when JavaScript editor content matches flight controller storage vs when changes exist locally. Key features: - Save button disabled when isDirty=false (code matches FC) - Save button enabled when isDirty=true (unsaved changes) - updateSaveButtonState() called at all isDirty change points - Uses existing .disabled CSS styling for visual feedback Part of Feature 2: Code Sync Status
…ing tab Feature 2 - Code Sync Status: - Disable Save button when editor matches FC (isDirty = false) - Enable Save button when code is modified (isDirty = true) - Uses existing .disabled CSS class with pointer-events: none Feature 3 - Gvar Display: - Display live non-zero global variable values as inline hints - Format: // gvar[index] = value - Position at end of line where gvar is referenced - Deduplicate - show each gvar only once at first occurrence - Auto-update at 500ms intervals via MSP polling - Implemented using Monaco Content Widgets API Implementation details: - Created js/transpiler/gvar_display.js module for gvar functionality - Added updateSaveButtonState() to manage Save button state - Added updateGvarDisplay() for real-time gvar value display - Integrated with existing MSPChainer for efficient polling - CSS styling: opacity 0.65, italic, subtle gray color
After code review, added critical error handling to prevent silent failures: - Monaco widget operations now wrapped in try-catch blocks - MSP chainer exit point updates protected with error handling - Fixed cleanup bug (gvarDecorations -> gvarWidgets) - Added console logging for debugging widget failures These changes ensure the gvar display feature degrades gracefully instead of breaking the polling loop if Monaco operations fail.
…NavFlight#2536) * Fix JS Programming decompiler: render childless boolean conditions The decompiler was skipping boolean conditions with no children, assuming they were all "helper conditions" used as operands elsewhere. However, some childless conditions (like flight parameter checks with activators) need to be rendered for external reference (e.g., Global Functions, OSD). Changed decompileTree() to only skip childless conditions that cannot be externally referenced. Conditions that could be referenced externally are now rendered as empty if blocks with a comment. Fixes: Logic condition 23 (airspeed < 1111 with activator) was completely missing from decompiled output. Added comprehensive regression test suite with 7 test cases covering childless boolean conditions, activators, and external reference scenarios. All existing tests pass (27/27 test suites). * Fix decompiler GVAR hoisting bug The decompiler was hoisting expressions that read from GVARs to the top of the decompiled code (as const declarations), causing them to use OLD GVAR values instead of NEW values set by earlier logic conditions. Root Cause: ActivatorHoistingManager.identifyHoistedVars() didn't check if an LC reads from GVARs before hoisting to global scope. GVAR values are dynamic and can be set at runtime, so hoisting breaks execution order. Fix: - Added readsFromGvar() method to detect GVAR reads (recursively checking LC operands for transitive dependencies) - Skip hoisting for any LC that reads from GVARs - Expressions using GVARs now render inline, preserving execution order Example (BEFORE - broken): const cond1 = (gvar[7] * 1000 / 45) - 500; // Uses OLD value if (rc[8].low) { gvar[7] = 0; } // Sets NEW value gvar[6] = cond1; // Wrong\! Example (AFTER - correct): if (rc[8].low) { gvar[7] = 0; } // Sets value first gvar[6] = (gvar[7] * 1000 / 45) - 500; // Uses NEW value Testing: - Added comprehensive regression test suite (3 test cases) - Tests verify GVAR assignments occur before GVAR usage - All existing tests pass (27/28 suites) Files modified: - js/transpiler/transpiler/activator_hoisting.js - js/transpiler/transpiler/tests/gvar_hoisting_order.test.cjs (new) - js/transpiler/transpiler/tests/run_gvar_hoisting_tests.cjs (new) * Refine GVAR hoisting fix: only prevent when GVAR is written The initial GVAR hoisting fix was too aggressive - it prevented hoisting ANY expression that read from GVARs, even when those GVARs were never written. This caused the "duplicate let declarations" test to fail. Refinement: - Added findWrittenGvars() to track which GVARs are actually being SET - Only prevent hoisting if reading from a GVAR that is WRITTEN - GVARs that are only READ can be safely hoisted This allows safe optimizations while still preventing the original bug. Test Results: - All 28 test suites now pass (was 27/28) - GVAR hoisting regression tests: PASS - Duplicate let declarations test: PASS (fixed) - User's original conditions: PASS Example distinction: - gvar[5] only read → hoisting ALLOWED (safe optimization) - gvar[7] written by LCs → hoisting PREVENTED (prevents bug) * Add DELAY to stateful operations + implement bot suggestions 1. Exclude DELAY from hoisting (like STICKY/TIMER) - DELAY maintains state (timeout, flags) across loop iterations - Should not be hoisted to prevent incorrect evaluation timing - Updated: identifyHoistedVars, activatorChainHasSticky, findStickyInActivatorChain 2. Add defensive check in decompileTree (Qodo suggestion #1) - Prevent crashes from malformed trees with missing node.lc or node.children - Adds robustness for edge cases 3. Use enums in test file (Qodo suggestion #2 - partial) - Import OPERATION and OPERAND_TYPE constants - Replace magic numbers with named constants (first test case) - Improves test maintainability Test Results: - All 28 test suites pass - No regressions introduced * Exclude EDGE from hoisting (stateful operation) EDGE maintains state (timeout, flags) across loop iterations like STICKY/TIMER/DELAY, so it should not be hoisted. Changes: 1. Added EDGE to stateful operations exclusion in identifyHoistedVars() 2. Added EDGE to activatorChainHasSticky() check 3. Added EDGE to findStickyInActivatorChain() for scope detection 4. Removed EDGE from complexOps list (now excluded earlier) 5. Updated edge_activator_in_sticky test to accept both patterns: - Hoisted: const cond1 = ... ? edge(...) : 0 - Inline: on: () => (... ? edge(...) : 0) Both patterns correctly preserve the activator relationship. Test Results: - All 28 test suites pass - edge_activator_in_sticky test now accepts inline pattern * Add comprehensive hoisting documentation to activator_hoisting.js Added detailed header comment explaining: - What hoisting is and why we do it - Hoisting criteria (all conditions that must be met) - Excluded operations (stateful, actions, GVAR dependencies) - GVAR dependency tracking and why it matters - Execution order preservation mechanisms - Example of hoisted code This helps developers understand the hoisting logic without needing to read through the implementation.
Fixes JavaScript ReferenceError where code was calling GUI_control.prototype.log() instead of GUI.log(). GUI_control is a constructor function, while GUI is the exported singleton instance that should be used throughout the codebase. Changed 4 instances in 3 files: - tabs/magnetometer.js:653 - Magnetometer 3D initialization - tabs/firmware_flasher.js:829 - Firmware flasher error logging - js/serial_backend.js:348,416 - Serial connection error logging All other tabs correctly use GUI.log(). This fix brings these files in line with the established pattern. Fixes #11xxx (magnetometer tab initialization error)
Add automatic fallback from hardware to software WebGL rendering with graceful degradation when WebGL is completely unavailable. Changes: - Add tryCreateWebGLContext() helper with 3-tier fallback - Try hardware WebGL, then software WebGL, then graceful disable - Show user-friendly messages when 3D unavailable - Provide no-op functions to prevent crashes - Add console logging for WebGL rendering method Fixes crashes when WebGL unavailable in Setup and Magnetometer tabs. Improves UX for users with GPU driver issues or running in VMs. Files modified: - tabs/setup.js: Robust WebGL detection - tabs/magnetometer.js: Robust WebGL detection
Fixes the "fs is not defined" ReferenceError when saving mission files from the Mission Control tab. **Problem:** - Legacy code was using Node.js fs.writeFile() directly from renderer - fs module is not accessible in Electron's renderer process - Users got error: "ReferenceError: fs is not defined" at line 4050 **Solution:** - Replace fs.writeFile() with window.electronAPI.writeFile() - Use Promise-based API (.then) instead of callback pattern - Move success messages inside .then() block to ensure they execute after file write completes **Testing:** - Configurator builds without errors - Mission file save dialog opens correctly - Files save successfully to selected location - No console errors Follows the same pattern used in cli.js and other tabs.
The OSD tab only needs to know which Logic Conditions are enabled to populate dropdown selectors - it never inspects LC content. Replace the heavyweight loadLogicConditions() call (which fetches up to 64 individual LCs via MSP) with a single MSP2_INAV_LOGIC_CONDITIONS_CONFIGURED request that returns an 8-byte bitmask. This also fixes iNavFlight#2552 (LC dropdowns showing "Logic 0") by moving the mask fetch into the OSD.reload chain so data is available before createCustomElements() builds the UI.
Use unsigned right-shift (>>>) for bitmask checks to avoid sign-extension issues when bit 31 is set. Use MSP.promise() with .catch() so done() is always called even if the mask fetch fails.
Prepare for 9.0.1 patch release.
Built from firmware commit d44f2cf6e6 (maintenance-9.x tip). Linux x64 built locally for glibc 2.34 compatibility (Ubuntu 22.04+). All other platforms from CI nightly v9.0.1-20260214.201.
…L binaries Bump configurator version to 9.0.1
- Add null safety to normalizeTargetName() for failed MSP connections - Quote CSS attribute value in jQuery selector to prevent breakage - Show display name (spaces) instead of target_id (underscores) in firmware version dropdown placeholder - De-duplicate release entries when both hyphen and underscore variants of the same target exist in a release
…asher-target-matching Fix firmware flasher board matching for multi-underscore targets
…rsion-9.0.2 Bump configurator version to 9.0.2
…configured-mask OSD: use LC configured mask
…ave-directory Remember last save directory across file operations
…set-ui Add GPS preset UI with auto-detection and M9 Precision/Sport modes
…ing-decompiler-warning Decompiler: improve handling of garbage input
…on-navigation Feature: Accordion navigation for tab organization
…ramming-lc-highlighting JavaScript Programming tab: Add true/false indicators to aid debugging
…ramming-debug-tools-2-3-from-feature-1 JavaScript Programming: Add Code Sync Status and Gvar Display
…-ble-device-chooser-issue-2361 Improve BLE device chooser with sorting and filtering
…ip-ui-redesign-2b LED Strip UI - Improved Layout
- outputMapping: detect PINIO outputs via specialLabels (firmware-assigned USER number) instead of timerOverrides cross-referencing; USER number derived directly from specialLabels value (2=USER1, 3=USER2, etc.) - mixer: label PINIO timer output mode as "PINIO / DUTY CYCLE" - logicConditions: rename condition 52 from "LED Pin PWM" to "PINIO PWM"; enable second operand (operandB = duty cycle, operandA = channel index) - transpiler: rename LED_PIN_PWM to PINIO_PWM throughout; update decompiler handler for two-operand pinioPwm(channel, duty) semantics
Fix operand order for backward compatibility: operandA=duty, operandB=pin (0=LED pin, 1=USER1, 2=USER2, ...). Old LED_PIN_PWM configs with operandA=duty and operandB=0 continue to work unchanged. Rename condition label to "PWM on pin" to reflect operand order. Add pwmOnPin(duty, pin) to JavaScript transpiler with full round-trip support (compile JS->LC and decompile LC->JS). Function name mirrors the "PWM on pin" label: duty first, then pin. Also fix transformBodyStatement to dispatch CallExpression nodes, which was silently dropping function calls in if-statement bodies. Add round-trip tests: compile, decompile, and JS->LC->JS for LED pin, USER1, and USER2.
|
Branch Targeting SuggestionYou've targeted the
If This is an automated suggestion to help route contributions to the appropriate branch. |




Summary
operandA=duty,operandB=pin(0=LED pin, 1=USER1, 2=USER2, ...). OldLED_PIN_PWMconfigs with operandA as duty and operandB=0 continue to work unchanged.inav.override.pwmOnPin(duty, pin)to the JavaScript transpiler with full compile (JS→LC) and decompile (LC→JS) support. Function name mirrors the label: duty first, then pin.transformBodyStatementto dispatchCallExpressionnodes — previously, function calls insideifbodies were silently dropped.Changes
js/logicConditionOperators.js— rename condition 52 label to "PWM on pin"js/transpiler/transpiler/parser.js— recognizeinav.override.pwmOnPin(duty, pin); fixCallExpressiondispatch intransformBodyStatementjs/transpiler/transpiler/codegen.js— handlePinioPwmAST node → emitPINIO_PWMLC with operandA=duty, operandB=pinjs/transpiler/transpiler/action_decompiler.js— decompile toinav.override.pwmOnPin(duty, pin)js/transpiler/transpiler/tests/pinio_pwm.test.cjs— round-trip tests (compile, decompile, JS→LC→JS) for LED pin, USER1, USER2js/transpiler/transpiler/tests/run_pinio_pwm_tests.cjs— test runnerTesting
run_all_tests.cjs)pinioPwmround-trip tests pass covering compile, decompile, and full round-trip for LED pin (pin=0), USER1 (pin=1), and USER2 (pin=2)LED_PIN_PWMconfigs with operandA=duty, operandB=0 correctly target the LED pinCode Review
Reviewed for correctness of operand mapping, transpiler round-trip consistency, and backward compatibility.