Skip to content

feat: simulator stubs for hardware-dependent blocks#630

Merged
thiagoralves merged 2 commits into
developmentfrom
feat/simulator-hardware-stubs
Feb 26, 2026
Merged

feat: simulator stubs for hardware-dependent blocks#630
thiagoralves merged 2 commits into
developmentfrom
feat/simulator-hardware-stubs

Conversation

@thiagoralves
Copy link
Copy Markdown
Contributor

@thiagoralves thiagoralves commented Feb 26, 2026

Summary

  • Adds no-op stub implementations for all 51 hardware-dependent functions (P1AM, MQTT, DS18B20, CAN, Sequent Microsystems, Arduino Cloud, TCP) so the simulator can compile programs that use these blocks
  • Routes arduino_libs.h to simulator_stubs.c when SIMULATOR_MODE is defined, bypassing real hardware module includes that require platform-specific headers
  • Bumps matiec to v4.0.11 which changes the #ifndef ARDUINO guard in iec_std_FB.h to #if !defined(ARDUINO) || defined(SIMULATOR_MODE), allowing TCP block type definitions to be included in simulator builds

Test plan

  • Compile a P1AM project for the simulator — succeeds where it previously failed
  • Compile a project using MQTT blocks for the simulator — succeeds
  • Compile a project using TCP blocks for the simulator — succeeds
  • Compile a project with no hardware blocks for the simulator — regression check passes
  • Compile a project for a real hardware target (e.g. P1AM-100) — regression check, should use real modules
  • Run the simulator with hardware-block program — blocks silently no-op

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced a preprocessing step that better handles Python/C++ POUs and improves simulator behavior.
    • Simulator-aware UI/flow: simulator targets now enable relevant options previously restricted.
  • Refactor

    • Simplified compile/debug flow to use the new preprocessing pipeline, improving consistency and validation.
  • Chores

    • Updated matiec to version 4.0.11

Allow the simulator to compile and run programs that use hardware-specific
blocks (P1AM, MQTT, DS18B20, CAN, Sequent Microsystems, Arduino Cloud, TCP)
by providing no-op stub implementations. Previously these blocks failed to
compile for the simulator because their module files include platform-specific
headers unavailable on AVR.

- Add simulator_stubs.c with 51 no-op function stubs covering all hardware
  modules plus TCP communication functions
- Route arduino_libs.h to stubs when SIMULATOR_MODE is defined, bypassing
  real hardware module includes
- Bump matiec to v4.0.11 which allows communication.h (TCP block type
  definitions) to be included in simulator builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 26, 2026

Walkthrough

Multiple changes introduce a new PLC preprocessing pipeline, make simulator-aware gating across UI components, and update a single dependency version in binary-versions.json.

Changes

Cohort / File(s) Summary
Version update
binary-versions.json
Bumped matiec version from v4.0.10 to v4.0.11.
Preprocessing utility (new)
src/utils/PLC/preprocess-pous.ts
Added a new preprocessing pipeline for PLC POUs handling Python and C++ POUs, validation, logging, and returning {projectData, validationFailed}; exports preprocessPous and ProjectDataWithCpp.
Compilation / Debug UI
src/renderer/components/_organisms/workspace-activity-bar/default.tsx
Replaced ad-hoc Python/C++ handling with preprocessPous flow, removed previous C++/Python-specific transforms/types, changed compile/debug flows to use preprocessed project data and validation flag; significant control-flow and type surface changes.
Element creation UI
src/renderer/components/_features/[workspace]/create-element/element-card/index.tsx
Added simulator awareness via isSimulatorTarget; broadened gating for server/remote-device UI and adjusted Python-blocking logic to consider simulator mode.
Board configuration UI
src/renderer/components/_features/[workspace]/editor/device/configuration/board.tsx
Added isTargetSimulator check and updated pending-board-change gating to include simulator targets when evaluating server/remote-device constraints.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ActivityBar as WorkspaceActivityBar
    participant Preproc as PreprocessService
    participant Compiler
    participant Debugger
    participant Board as Board/Simulator

    User->>ActivityBar: Start Compile / Start Debug
    ActivityBar->>Preproc: preprocessPous(projectData, isSimulator, log)
    Preproc-->>ActivityBar: { projectData: processedProjectData, validationFailed }
    alt validationFailed == true
        ActivityBar->>User: surface validation error / abort flow
    else validationFailed == false
        ActivityBar->>Compiler: compile(processedProjectData)
        Compiler-->>ActivityBar: compilation result
        alt Debug requested
            ActivityBar->>Debugger: connectAndActivateDebugger(processedProjectData, boardContext)
            Debugger->>Board: attach / start debug session
            Board-->>Debugger: runtime responses
            Debugger-->>ActivityBar: debug events/status
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

feature, enhancement

Suggested reviewers

  • vmleroy

Poem

🐰 A preprocess hop, I skip and I chew,
I stitch Python, C++, into ST anew,
Simulator ears perk, servers nod in tune,
A version bumped, the code hums—hop, hooray, moon! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides a clear summary of changes and a comprehensive test plan, but lacks the structured template format with required sections like References, DOD checklist, and test coverage metrics. Add formal sections following the repository template: References section with issue/Jira links, explicit test coverage percentage, and complete DOD checklist items with explicit pass/fail status for each criterion.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding simulator stub implementations for hardware-dependent blocks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/simulator-hardware-stubs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…device creation

Extract duplicated Python + C++ POU preprocessing into a shared
preprocessPous() utility. For the simulator target, Python function
blocks compile as empty ST stubs instead of generating the full
Python runtime wrapper (which requires POSIX shared memory unavailable
on AVR). Unlock server and remote-device creation UI for the simulator
target alongside OpenPLC Runtime v4, and suppress the board-switch
warning modal when switching to the simulator with existing servers
or remote devices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/renderer/components/_features/[workspace]/create-element/element-card/index.tsx (3)

411-419: User-facing message should mention simulator support.

The gating logic now uses allowServersAndRemoteDevices (which includes simulator), but the disabled message still only references "OpenPLC Runtime v4 targets". This could confuse users who selected a non-v4, non-simulator target.

Consider updating the message to reflect that both Runtime v4 and Simulator support this feature:

📝 Suggested message update
                {!allowServersAndRemoteDevices ? (
                  <div className='flex flex-col gap-2 py-2'>
                    <p className='text-sm text-neutral-700 dark:text-neutral-300'>
-                      Server configuration is only available for OpenPLC Runtime v4 targets.
+                      Server configuration is only available for OpenPLC Runtime v4 or Simulator targets.
                    </p>
                    <p className='text-xs text-neutral-500 dark:text-neutral-400'>
-                      Please select OpenPLC Runtime v4 in the Device Configuration to enable this feature.
+                      Please select OpenPLC Runtime v4 or Simulator in the Device Configuration to enable this feature.
                    </p>
                  </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/components/_features/`[workspace]/create-element/element-card/index.tsx
around lines 411 - 419, The user-facing message shown when
allowServersAndRemoteDevices is false only references "OpenPLC Runtime v4
targets" and should also mention Simulator support; update the copy inside the
JSX block that renders when !allowServersAndRemoteDevices (the div containing
the two <p> elements) to say that Server configuration is available for OpenPLC
Runtime v4 and Simulator targets and instruct the user to select Runtime v4 or
Simulator in Device Configuration to enable the feature—locate the message text
near the allowServersAndRemoteDevices conditional and replace the two
paragraphs' strings accordingly.

746-748: Redundant condition—isSimulator and isArduinoTarget are mutually exclusive.

Based on the utility functions in src/utils/device.ts, isArduinoTarget checks compiler === 'arduino-cli' and isSimulatorTarget checks compiler === 'simulator'. Since these are mutually exclusive, isArduinoTarget && !isSimulator is logically equivalent to just isArduinoTarget.

The code is functionally correct, but the !isSimulator check is unnecessary given the current implementation. If this is intentional defensive coding for potential future changes where a board could be both, consider adding a comment explaining the intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/components/_features/`[workspace]/create-element/element-card/index.tsx
around lines 746 - 748, The condition creating isPythonBlockedForArduino is
using a redundant check: remove the unnecessary "!isSimulator" and set it to
const isPythonBlockedForArduino = lang.value === 'Python' && isArduinoTarget; if
the original extra check was intentional for future defensive reasons, instead
add a short comment above the declaration explaining why both checks are
present; refer to the isPythonBlockedForArduino, isArduinoTarget and isSimulator
symbols to locate the change.

546-554: Same messaging inconsistency for remote device section.

Similar to the server section, the disabled message should mention simulator support for consistency.

📝 Suggested message update
                {!allowServersAndRemoteDevices ? (
                  <div className='flex flex-col gap-2 py-2'>
                    <p className='text-sm text-neutral-700 dark:text-neutral-300'>
-                      Remote device configuration is only available for OpenPLC Runtime v4 targets.
+                      Remote device configuration is only available for OpenPLC Runtime v4 or Simulator targets.
                    </p>
                    <p className='text-xs text-neutral-500 dark:text-neutral-400'>
-                      Please select OpenPLC Runtime v4 in the Device Configuration to enable this feature.
+                      Please select OpenPLC Runtime v4 or Simulator in the Device Configuration to enable this feature.
                    </p>
                  </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/components/_features/`[workspace]/create-element/element-card/index.tsx
around lines 546 - 554, Update the disabled remote device message in
element-card/index.tsx (the JSX block guarded by the
allowServersAndRemoteDevices check) to match the server-section wording by
explicitly mentioning simulator support; modify the two <p> strings inside the
!allowServersAndRemoteDevices branch so they say that remote device
configuration is only available for OpenPLC Runtime v4 targets (and the
simulator) and instruct the user to select OpenPLC Runtime v4 in Device
Configuration to enable the feature, mirroring the server messaging for
consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/components/_organisms/workspace-activity-bar/default.tsx`:
- Around line 580-592: The upload fallback path still uses raw projectData
causing preprocessing to be skipped; update the upload/MD5-mismatch compile call
to pass the already preprocessedProjectData from preprocessPous instead of
projectData so Python/C++ preprocessing runs consistently—locate where the MD5
mismatch/upload compile is invoked (the counterpart to
window.bridge.runDebugCompilation) and replace its projectData argument with
processedProjectData, preserving the same validationFailed handling and
consoleActions logging flow from preprocessPous.

In `@src/utils/PLC/preprocess-pous.ts`:
- Around line 63-77: The early preprocessing result from
applyEarlyCommentWrapping is being discarded when Python POUs exist because
addPythonLocalVariables is called with projectData instead of
processedProjectData; change the call to
addPythonLocalVariables(processedProjectData) (and ensure any downstream uses
refer to processedProjectData) so the wrapped ST/IL comments produced by
applyEarlyCommentWrapping are preserved when processing pythonPous.
- Around line 96-111: The map over processedProjectData.pous currently skips
conversion when processedPythonCodes[pythonIndex] is falsy (e.g. empty string),
causing misalignment; change the truthy check around
processedPythonCodes[pythonIndex] in the pou.data.body.language === 'python'
branch to explicitly test for undefined (e.g. processedPythonCodes[pythonIndex]
!== undefined or typeof processedPythonCodes[pythonIndex] !== 'undefined') so
empty/valid transformed code is still used by generateSTCode, and only increment
pythonIndex when the value is defined; references: processedProjectData.pous,
PLCPou, pou.data.body.language, processedPythonCodes[pythonIndex],
generateSTCode, pythonIndex.

---

Nitpick comments:
In
`@src/renderer/components/_features/`[workspace]/create-element/element-card/index.tsx:
- Around line 411-419: The user-facing message shown when
allowServersAndRemoteDevices is false only references "OpenPLC Runtime v4
targets" and should also mention Simulator support; update the copy inside the
JSX block that renders when !allowServersAndRemoteDevices (the div containing
the two <p> elements) to say that Server configuration is available for OpenPLC
Runtime v4 and Simulator targets and instruct the user to select Runtime v4 or
Simulator in Device Configuration to enable the feature—locate the message text
near the allowServersAndRemoteDevices conditional and replace the two
paragraphs' strings accordingly.
- Around line 746-748: The condition creating isPythonBlockedForArduino is using
a redundant check: remove the unnecessary "!isSimulator" and set it to const
isPythonBlockedForArduino = lang.value === 'Python' && isArduinoTarget; if the
original extra check was intentional for future defensive reasons, instead add a
short comment above the declaration explaining why both checks are present;
refer to the isPythonBlockedForArduino, isArduinoTarget and isSimulator symbols
to locate the change.
- Around line 546-554: Update the disabled remote device message in
element-card/index.tsx (the JSX block guarded by the
allowServersAndRemoteDevices check) to match the server-section wording by
explicitly mentioning simulator support; modify the two <p> strings inside the
!allowServersAndRemoteDevices branch so they say that remote device
configuration is only available for OpenPLC Runtime v4 targets (and the
simulator) and instruct the user to select OpenPLC Runtime v4 in Device
Configuration to enable the feature, mirroring the server messaging for
consistency.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52cbfb5 and 2f8bfcf.

📒 Files selected for processing (4)
  • src/renderer/components/_features/[workspace]/create-element/element-card/index.tsx
  • src/renderer/components/_features/[workspace]/editor/device/configuration/board.tsx
  • src/renderer/components/_organisms/workspace-activity-bar/default.tsx
  • src/utils/PLC/preprocess-pous.ts

Comment on lines +580 to 592
const { projectData: processedProjectData, validationFailed } = preprocessPous(
projectData,
isSimulatorTarget(currentBoardInfo),
(level, message) => consoleActions.addLog({ id: crypto.randomUUID(), level, message }),
)
if (validationFailed) {
setIsDebuggerProcessing(false)
return
}

window.bridge.runDebugCompilation(
[projectPath, boardTarget, processedProjectData as ProjectDataWithCpp],
[projectPath, boardTarget, processedProjectData],
(data: { logLevel?: 'info' | 'error' | 'warning'; message: string | Buffer; closePort?: boolean }) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use preprocessed project data in every compile path, including MD5 mismatch upload.

This new flow preprocesses for debug compilation, but the upload fallback still sends raw projectData (Line 899). That can bypass Python/C++ preprocessing in that path.

💡 Suggested fix (outside this hunk, at the upload compile call)
-          window.bridge.runCompileProgram(
-            [projectPath, boardTarget, boardCore, false, projectData, targetIpAddress ?? '', runtimeJwtToken],
+          const { projectData: uploadProjectData, validationFailed: uploadValidationFailed } = preprocessPous(
+            projectData,
+            isSimulatorTarget(availableBoards.get(boardTarget)),
+            (level, message) => consoleActions.addLog({ id: crypto.randomUUID(), level, message }),
+          )
+          if (uploadValidationFailed) {
+            setIsDebuggerProcessing(false)
+            return
+          }
+
+          window.bridge.runCompileProgram(
+            [projectPath, boardTarget, boardCore, false, uploadProjectData, targetIpAddress ?? '', runtimeJwtToken],
             (data: {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/_organisms/workspace-activity-bar/default.tsx` around
lines 580 - 592, The upload fallback path still uses raw projectData causing
preprocessing to be skipped; update the upload/MD5-mismatch compile call to pass
the already preprocessedProjectData from preprocessPous instead of projectData
so Python/C++ preprocessing runs consistently—locate where the MD5
mismatch/upload compile is invoked (the counterpart to
window.bridge.runDebugCompilation) and replace its projectData argument with
processedProjectData, preserving the same validationFailed handling and
consoleActions logging flow from preprocessPous.

Comment on lines +63 to +77
let processedProjectData: PLCProjectData = applyEarlyCommentWrapping(projectData)

// --- Python processing ---
const hasPythonCode = projectData.pous.some((pou: PLCPou) => pou.data.body.language === 'python')

if (hasPythonCode) {
const pythonPous = projectData.pous.filter((pou: PLCPou) => pou.data.body.language === 'python')

pythonPous.forEach((pou) => {
log('info', `Found Python POU: "${pou.data.name}" (${pou.type})`)
})

log('info', `Processing ${pythonPous.length} Python POU(s)...`)

processedProjectData = addPythonLocalVariables(projectData)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve early preprocessing when Python POUs are present.

Line 77 rebuilds from projectData instead of processedProjectData, so wrapped ST/IL comments from Line 63 are lost whenever Python POUs exist.

💡 Suggested fix
-    processedProjectData = addPythonLocalVariables(projectData)
+    processedProjectData = addPythonLocalVariables(processedProjectData)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let processedProjectData: PLCProjectData = applyEarlyCommentWrapping(projectData)
// --- Python processing ---
const hasPythonCode = projectData.pous.some((pou: PLCPou) => pou.data.body.language === 'python')
if (hasPythonCode) {
const pythonPous = projectData.pous.filter((pou: PLCPou) => pou.data.body.language === 'python')
pythonPous.forEach((pou) => {
log('info', `Found Python POU: "${pou.data.name}" (${pou.type})`)
})
log('info', `Processing ${pythonPous.length} Python POU(s)...`)
processedProjectData = addPythonLocalVariables(projectData)
let processedProjectData: PLCProjectData = applyEarlyCommentWrapping(projectData)
// --- Python processing ---
const hasPythonCode = projectData.pous.some((pou: PLCPou) => pou.data.body.language === 'python')
if (hasPythonCode) {
const pythonPous = projectData.pous.filter((pou: PLCPou) => pou.data.body.language === 'python')
pythonPous.forEach((pou) => {
log('info', `Found Python POU: "${pou.data.name}" (${pou.type})`)
})
log('info', `Processing ${pythonPous.length} Python POU(s)...`)
processedProjectData = addPythonLocalVariables(processedProjectData)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/PLC/preprocess-pous.ts` around lines 63 - 77, The early
preprocessing result from applyEarlyCommentWrapping is being discarded when
Python POUs exist because addPythonLocalVariables is called with projectData
instead of processedProjectData; change the call to
addPythonLocalVariables(processedProjectData) (and ensure any downstream uses
refer to processedProjectData) so the wrapped ST/IL comments produced by
applyEarlyCommentWrapping are preserved when processing pythonPous.

Comment on lines +96 to +111
processedProjectData.pous = processedProjectData.pous.map((pou: PLCPou) => {
if (pou.data.body.language === 'python') {
if (processedPythonCodes[pythonIndex]) {
const stCode = generateSTCode({
pouName: pou.data.name,
allVariables: pou.data.variables,
processedPythonCode: processedPythonCodes[pythonIndex],
})

pou.data.body = {
language: 'st',
value: stCode,
}

pythonIndex++
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid truthy checks for indexed transformed Python code.

Line 98 treats '' as missing. That can skip conversion and leave later Python POUs misaligned/unprocessed.

💡 Suggested fix
       let pythonIndex = 0
       processedProjectData.pous = processedProjectData.pous.map((pou: PLCPou) => {
         if (pou.data.body.language === 'python') {
-          if (processedPythonCodes[pythonIndex]) {
+          const processedPythonCode = processedPythonCodes[pythonIndex]
+          pythonIndex++
+          if (processedPythonCode !== undefined) {
             const stCode = generateSTCode({
               pouName: pou.data.name,
               allVariables: pou.data.variables,
-              processedPythonCode: processedPythonCodes[pythonIndex],
+              processedPythonCode,
             })
 
             pou.data.body = {
               language: 'st',
               value: stCode,
             }
-
-            pythonIndex++
+          } else {
+            log('error', `Missing transformed Python code for "${pou.data.name}"`)
           }
         }
         return pou
       })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/PLC/preprocess-pous.ts` around lines 96 - 111, The map over
processedProjectData.pous currently skips conversion when
processedPythonCodes[pythonIndex] is falsy (e.g. empty string), causing
misalignment; change the truthy check around processedPythonCodes[pythonIndex]
in the pou.data.body.language === 'python' branch to explicitly test for
undefined (e.g. processedPythonCodes[pythonIndex] !== undefined or typeof
processedPythonCodes[pythonIndex] !== 'undefined') so empty/valid transformed
code is still used by generateSTCode, and only increment pythonIndex when the
value is defined; references: processedProjectData.pous, PLCPou,
pou.data.body.language, processedPythonCodes[pythonIndex], generateSTCode,
pythonIndex.

@thiagoralves thiagoralves merged commit 26b402d into development Feb 26, 2026
13 checks passed
@thiagoralves thiagoralves deleted the feat/simulator-hardware-stubs branch February 26, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant