From 69e074f697d922bf19179da98b9568ae5835a1fd Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 27 Feb 2026 11:31:06 -0500 Subject: [PATCH 1/4] feat: add real-time non-BOOL value display on FBD and LD diagrams Show live debug values (INT, REAL, STRING, etc.) in green badges next to variable nodes during debugging, similar to CODESYS. BOOL variables are excluded since they already have dedicated color indicators. - Add shared DebugValueBadge component for both FBD and LD editors - Integrate badge into FBD variable nodes (right/left/below positioning) - Integrate badge into LD variable nodes (right/left positioning) - Poll all variables of the active POU (not just BOOLs) so non-BOOL values are available for display; tab switching naturally updates the polling set Co-Authored-By: Claude Opus 4.6 --- .../graphical-editor/debug-value-badge.tsx | 52 +++++++++++++++++++ .../_atoms/graphical-editor/fbd/variable.tsx | 11 ++++ .../graphical-editor/ladder/variable.tsx | 10 ++++ src/renderer/screens/workspace-screen.tsx | 21 ++++++++ 4 files changed, 94 insertions(+) create mode 100644 src/renderer/components/_atoms/graphical-editor/debug-value-badge.tsx diff --git a/src/renderer/components/_atoms/graphical-editor/debug-value-badge.tsx b/src/renderer/components/_atoms/graphical-editor/debug-value-badge.tsx new file mode 100644 index 000000000..5d4b08d9f --- /dev/null +++ b/src/renderer/components/_atoms/graphical-editor/debug-value-badge.tsx @@ -0,0 +1,52 @@ +import { useOpenPLCStore } from '@root/renderer/store' +import { cn } from '@root/utils' + +type DebugValueBadgeProps = { + compositeKey: string + variableType: string | undefined + position?: 'right' | 'left' | 'below' +} + +/** + * Displays a real-time debug value badge next to graphical editor nodes. + * Shows the current polled value for non-BOOL variables when the debugger is active. + * BOOL variables are skipped since they already have dedicated color indicators. + * + * Designed to be used by both FBD and LD variable/block nodes. + */ +const DebugValueBadge = ({ compositeKey, variableType, position = 'right' }: DebugValueBadgeProps) => { + const { + workspace: { debugVariableValues }, + } = useOpenPLCStore() + + if (!variableType || variableType.toUpperCase() === 'BOOL') { + return null + } + + const value = debugVariableValues.get(compositeKey) + if (value === undefined) { + return null + } + + const positionClasses: Record = { + right: 'left-full ml-1 top-1/2 -translate-y-1/2', + left: 'right-full mr-1 top-1/2 -translate-y-1/2', + below: 'top-full mt-0.5 left-1/2 -translate-x-1/2', + } + + return ( +
+ {value} +
+ ) +} + +export { DebugValueBadge } +export type { DebugValueBadgeProps } diff --git a/src/renderer/components/_atoms/graphical-editor/fbd/variable.tsx b/src/renderer/components/_atoms/graphical-editor/fbd/variable.tsx index 32533906c..8ed36568d 100644 --- a/src/renderer/components/_atoms/graphical-editor/fbd/variable.tsx +++ b/src/renderer/components/_atoms/graphical-editor/fbd/variable.tsx @@ -20,6 +20,7 @@ import { Modal, ModalContent, ModalTitle } from '../../../_molecules/modal' import { HighlightedTextArea } from '../../highlighted-textarea' import { Label } from '../../label' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip' +import { DebugValueBadge } from '../debug-value-badge' import { BlockVariant } from '../types/block' import { validateVariableType } from '../utils' import { FBDBlockAutoComplete } from './autocomplete' @@ -656,6 +657,16 @@ const VariableElement = (block: VariableProps) => { )} + {isDebuggerVisible && isAVariable && ( + + )} + {isDebuggerVisible && contextMenuPosition && ( diff --git a/src/renderer/components/_atoms/graphical-editor/ladder/variable.tsx b/src/renderer/components/_atoms/graphical-editor/ladder/variable.tsx index 94c547070..c4b5cbf6d 100644 --- a/src/renderer/components/_atoms/graphical-editor/ladder/variable.tsx +++ b/src/renderer/components/_atoms/graphical-editor/ladder/variable.tsx @@ -20,6 +20,7 @@ import { useEffect, useRef, useState } from 'react' import { Modal, ModalContent, ModalTitle } from '../../../_molecules/modal' import { HighlightedTextArea } from '../../highlighted-textarea' import { Label } from '../../label' +import { DebugValueBadge } from '../debug-value-badge' import { getVariableByName, validateVariableType } from '../utils' import { VariablesBlockAutoComplete } from './autocomplete' import { BlockNodeData, BlockVariant, LadderBlockConnectedVariables } from './block' @@ -455,6 +456,7 @@ const VariableElement = (block: VariableProps) => { return ( <>
@@ -519,6 +521,14 @@ const VariableElement = (block: VariableProps) => {
)} + {isDebuggerVisible && isAVariable && ( + + )} + {isDebuggerVisible && contextMenuPosition && ( diff --git a/src/renderer/screens/workspace-screen.tsx b/src/renderer/screens/workspace-screen.tsx index 34cd1d8d4..dc8fad8b6 100644 --- a/src/renderer/screens/workspace-screen.tsx +++ b/src/renderer/screens/workspace-screen.tsx @@ -1386,6 +1386,27 @@ const WorkspaceScreen = () => { } } + // Poll all variables of the active POU so non-BOOL values can be displayed on the diagram. + // This adds every variable registered in variableInfoMapRef that belongs to the current POU, + // enabling the DebugValueBadge components to show real-time values for INT, REAL, etc. + if (currentPou) { + const activePouName = currentPou.data.name + Array.from(variableInfoMapRef.current.entries()).forEach(([_, varInfos]) => { + for (const varInfo of varInfos) { + if (varInfo.pouName !== activePouName) continue + // For FB POUs, only add variables that match the selected instance context + if (currentPou.type === 'function-block') { + const compositeKey = makeCompositeKeyForCurrentPou(varInfo.variable.name) + if (compositeKey) { + debugVariableKeys.add(compositeKey) + } + } else { + debugVariableKeys.add(`${varInfo.pouName}:${varInfo.variable.name}`) + } + } + }) + } + // Forced variables must also be polled so their current value appears in the debugger panel const { workspace: { debugForcedVariables: currentForcedVars }, From c533d4dbc2a31d8ddfd553c9651586177d4a7daf Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 27 Feb 2026 12:27:32 -0500 Subject: [PATCH 2/4] feat: add block output debug badges and fix _TMP_ registration for all base types - Register all base-type function outputs (not just BOOL) so non-BOOL _TMP_ variables like ADD's INT output get polled during debugging - Create shared BlockOutputDebugBadges component used by both FBD and LD block nodes to show real-time values next to output connectors - Skip badge rendering for outputs connected to variable nodes to avoid duplicate badges (LD uses connectedVariables, FBD uses edge analysis) Co-Authored-By: Claude Opus 4.6 --- .../block-output-debug-badges.tsx | 80 ++++++++++++++++++ .../_atoms/graphical-editor/fbd/block.tsx | 30 ++++++- .../_atoms/graphical-editor/ladder/block.tsx | 26 +++++- src/renderer/screens/workspace-screen.tsx | 82 ++++++++++++++----- 4 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 src/renderer/components/_atoms/graphical-editor/block-output-debug-badges.tsx diff --git a/src/renderer/components/_atoms/graphical-editor/block-output-debug-badges.tsx b/src/renderer/components/_atoms/graphical-editor/block-output-debug-badges.tsx new file mode 100644 index 000000000..b91db721d --- /dev/null +++ b/src/renderer/components/_atoms/graphical-editor/block-output-debug-badges.tsx @@ -0,0 +1,80 @@ +import { useDebugCompositeKey } from '@root/renderer/hooks/use-debug-composite-key' +import { useOpenPLCStore } from '@root/renderer/store' + +import { DebugValueBadge } from './debug-value-badge' + +type BlockOutputDebugBadgesProps = { + blockType: string + blockName: string + blockVariableName: string + numericId: string + outputVariables: Array<{ name: string; class: string; type: { definition: string; value: string } }> + connectorStartY: number + connectorOffsetY: number + blockWidth: number + /** Output names that already have a variable node connected showing its own badge. */ + connectedOutputNames?: Set +} + +/** + * Renders debug value badges next to each output connector of a block node. + * Works for both function blocks (using instance-qualified names) and + * functions (using _TMP_ variable names). Shared between FBD and LD. + * + * Outputs that are connected to a variable node are skipped since the + * variable node already displays its own badge (avoids double badges). + */ +const BlockOutputDebugBadges = ({ + blockType, + blockName, + blockVariableName, + numericId, + outputVariables, + connectorStartY, + connectorOffsetY, + blockWidth, + connectedOutputNames, +}: BlockOutputDebugBadgesProps) => { + const { + workspace: { isDebuggerVisible }, + } = useOpenPLCStore() + const getCompositeKey = useDebugCompositeKey() + + if (!isDebuggerVisible || blockType === 'generic') { + return null + } + + const outputs = outputVariables.filter((v) => v.class === 'output' || v.class === 'inOut') + + return ( + <> + {outputs.map((outputVar, index) => { + if (connectedOutputNames?.has(outputVar.name)) { + return null + } + + let compositeKey: string + if (blockType === 'function-block') { + compositeKey = getCompositeKey(`${blockVariableName}.${outputVar.name}`) + } else { + compositeKey = getCompositeKey(`_TMP_${blockName.toUpperCase()}${numericId}_${outputVar.name}`) + } + + return ( +
+ +
+ ) + })} + + ) +} + +export { BlockOutputDebugBadges } diff --git a/src/renderer/components/_atoms/graphical-editor/fbd/block.tsx b/src/renderer/components/_atoms/graphical-editor/fbd/block.tsx index e3fda6b51..ac2e66baa 100644 --- a/src/renderer/components/_atoms/graphical-editor/fbd/block.tsx +++ b/src/renderer/components/_atoms/graphical-editor/fbd/block.tsx @@ -6,11 +6,12 @@ import type { PLCVariable } from '@root/types/PLC' import { cn, generateNumericUUID } from '@root/utils' import { newGraphicalEditorNodeID } from '@root/utils/new-graphical-editor-node-id' import { Node, NodeProps, Position } from '@xyflow/react' -import { FocusEvent, useEffect, useRef, useState } from 'react' +import { FocusEvent, useEffect, useMemo, useRef, useState } from 'react' import { HighlightedTextArea } from '../../highlighted-textarea' import { InputWithRef } from '../../input' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip' +import { BlockOutputDebugBadges } from '../block-output-debug-badges' import { BlockVariant } from '../types/block' import { getBlockDocumentation, getVariableRestrictionType } from '../utils' import { buildHandle, CustomHandle } from './handle' @@ -352,10 +353,24 @@ export const Block = (block: BlockProps) => { const [wrongVariable, setWrongVariable] = useState(false) const [hoveringBlock, setHoveringBlock] = useState(false) - const { variables } = getFBDPouVariablesRungNodeAndEdges(editor, pous, fbdFlows, { + const { variables, rung } = getFBDPouVariablesRungNodeAndEdges(editor, pous, fbdFlows, { nodeId: id ?? '', }) + // Outputs connected to variable nodes already show their own badge — skip those + const connectedOutputNames = useMemo(() => { + const names = new Set() + if (!rung) return names + const outgoingEdges = rung.edges.filter((e) => e.source === id) + for (const edge of outgoingEdges) { + const targetNode = rung.nodes.find((n) => n.id === edge.target) + if (targetNode && typeof targetNode.type === 'string' && targetNode.type.includes('variable')) { + if (edge.sourceHandle) names.add(edge.sourceHandle) + } + } + return names + }, [rung, id]) + const inputVariableRef = useRef< HTMLTextAreaElement & { blur: ({ submit }: { submit?: boolean }) => void @@ -807,6 +822,17 @@ export const Block = (block: BlockProps) => { {data.handles.map((handle, index) => ( ))} + ) } diff --git a/src/renderer/components/_atoms/graphical-editor/ladder/block.tsx b/src/renderer/components/_atoms/graphical-editor/ladder/block.tsx index fcdc30480..90319c45c 100644 --- a/src/renderer/components/_atoms/graphical-editor/ladder/block.tsx +++ b/src/renderer/components/_atoms/graphical-editor/ladder/block.tsx @@ -11,11 +11,12 @@ import type { VariableReference } from '@root/types/PLC/variable-reference' import { cn, generateNumericUUID } from '@root/utils' import { newGraphicalEditorNodeID } from '@root/utils/new-graphical-editor-node-id' import { Node, NodeProps, Position } from '@xyflow/react' -import { FocusEvent, useEffect, useRef, useState } from 'react' +import { FocusEvent, useEffect, useMemo, useRef, useState } from 'react' import { HighlightedTextArea } from '../../highlighted-textarea' import { InputWithRef } from '../../input' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip' +import { BlockOutputDebugBadges } from '../block-output-debug-badges' import { BlockVariant as newBlockVariant } from '../types/block' import { getBlockDocumentation, getVariableRestrictionType, validateVariableType } from '../utils' import { buildHandle, CustomHandle } from './handle' @@ -433,6 +434,18 @@ export const Block = (block: BlockProps) => { nodeId: id, }) + const connectedOutputNames = useMemo(() => { + const names = new Set() + if (data.connectedVariables) { + for (const cv of data.connectedVariables) { + if (cv.type === 'output' && cv.variable) { + names.add(cv.handleId) + } + } + } + return names + }, [data.connectedVariables]) + const inputVariableRef = useRef< HTMLTextAreaElement & { blur: ({ submit }: { submit?: boolean }) => void @@ -896,6 +909,17 @@ export const Block = (block: BlockProps) => { {data.handles.map((handle, index) => ( ))} + ) } diff --git a/src/renderer/screens/workspace-screen.tsx b/src/renderer/screens/workspace-screen.tsx index dc8fad8b6..73d545dd2 100644 --- a/src/renderer/screens/workspace-screen.tsx +++ b/src/renderer/screens/workspace-screen.tsx @@ -640,7 +640,7 @@ const WorkspaceScreen = () => { } }) - // Register _TMP_ variables for function BOOL outputs so they get polled + // Register _TMP_ variables for function base-type outputs so they get polled const registerFunctionTempOutputs = (nodes: Array<{ type?: string; data: object }>) => { nodes.forEach((node) => { if (node.type !== 'block') return @@ -662,25 +662,22 @@ const WorkspaceScreen = () => { const numericId = blockData.numericId if (!numericId) return - let boolOutputs = blockData.variant.variables.filter( - (v) => - (v.class === 'output' || v.class === 'inOut') && - v.type.definition === 'base-type' && - v.type.value.toUpperCase() === 'BOOL', + let baseTypeOutputs = blockData.variant.variables.filter( + (v) => (v.class === 'output' || v.class === 'inOut') && v.type.definition === 'base-type', ) const hasExecutionControl = blockData.executionControl || false if (hasExecutionControl) { - const hasENO = boolOutputs.some((v) => v.name.toUpperCase() === 'ENO') + const hasENO = baseTypeOutputs.some((v) => v.name.toUpperCase() === 'ENO') if (!hasENO) { - boolOutputs = [ - ...boolOutputs, + baseTypeOutputs = [ + ...baseTypeOutputs, { name: 'ENO', class: 'output', type: { definition: 'base-type', value: 'BOOL' } }, ] } } - boolOutputs.forEach((outputVar) => { + baseTypeOutputs.forEach((outputVar) => { const index = getIndexFromMapWithFallback( debugVariableIndexes, programInstance.name, @@ -693,7 +690,30 @@ const WorkspaceScreen = () => { pouName: pou.data.name, variable: { name: tempVarName, - type: { definition: 'base-type', value: 'bool' }, + type: { + definition: 'base-type', + value: outputVar.type.value.toLowerCase() as + | 'bool' + | 'int' + | 'real' + | 'time' + | 'string' + | 'date' + | 'sint' + | 'dint' + | 'lint' + | 'usint' + | 'uint' + | 'udint' + | 'ulint' + | 'lreal' + | 'tod' + | 'dt' + | 'byte' + | 'word' + | 'dword' + | 'lword', + }, class: 'local', location: '', documentation: '', @@ -835,26 +855,23 @@ const WorkspaceScreen = () => { const numericId = blockData.numericId if (!numericId) return - let boolOutputs = blockData.variant.variables.filter( - (v) => - (v.class === 'output' || v.class === 'inOut') && - v.type.definition === 'base-type' && - v.type.value.toUpperCase() === 'BOOL', + let baseTypeOutputs = blockData.variant.variables.filter( + (v) => (v.class === 'output' || v.class === 'inOut') && v.type.definition === 'base-type', ) // Add ENO if execution control is enabled const hasExecutionControl = blockData.executionControl || false if (hasExecutionControl) { - const hasENO = boolOutputs.some((v) => v.name.toUpperCase() === 'ENO') + const hasENO = baseTypeOutputs.some((v) => v.name.toUpperCase() === 'ENO') if (!hasENO) { - boolOutputs = [ - ...boolOutputs, + baseTypeOutputs = [ + ...baseTypeOutputs, { name: 'ENO', class: 'output', type: { definition: 'base-type', value: 'BOOL' } }, ] } } - boolOutputs.forEach((outputVar) => { + baseTypeOutputs.forEach((outputVar) => { // Debug path uses the full nested path: // RES0__INSTANCE0.FB_B0.FB_A0._TMP_EQ_STATE7415072_ENO // Use fallback to try both FB-style and struct-style paths @@ -871,7 +888,30 @@ const WorkspaceScreen = () => { pouName: programPouName, variable: { name: tempVarName, - type: { definition: 'base-type', value: 'bool' }, + type: { + definition: 'base-type', + value: outputVar.type.value.toLowerCase() as + | 'bool' + | 'int' + | 'real' + | 'time' + | 'string' + | 'date' + | 'sint' + | 'dint' + | 'lint' + | 'usint' + | 'uint' + | 'udint' + | 'ulint' + | 'lreal' + | 'tod' + | 'dt' + | 'byte' + | 'word' + | 'dword' + | 'lword', + }, class: 'local', location: '', documentation: '', From 6a06cd5090ef5570ef692777a3beb2318f204860 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 27 Feb 2026 12:56:04 -0500 Subject: [PATCH 3/4] refactor: extract PLCBaseTypesLowercase type to replace duplicated inline union casts Co-Authored-By: Claude Opus 4.6 --- src/renderer/screens/workspace-screen.tsx | 45 ++--------------------- src/types/PLC/units/base-types.ts | 3 +- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/src/renderer/screens/workspace-screen.tsx b/src/renderer/screens/workspace-screen.tsx index 73d545dd2..a7d37c8c3 100644 --- a/src/renderer/screens/workspace-screen.tsx +++ b/src/renderer/screens/workspace-screen.tsx @@ -4,6 +4,7 @@ import { PlcLogsFilters } from '@components/_organisms/plc-logs/filters' import * as Tabs from '@radix-ui/react-tabs' import { useRuntimePolling } from '@root/renderer/hooks/use-runtime-polling' import { DebugTreeNode } from '@root/types/debugger' +import type { PLCBaseTypesLowercase } from '@root/types/PLC/units/base-types' // Note: Logs polling is now handled by useRuntimePolling hook import { cn, isOpenPLCRuntimeTarget, isSimulatorTarget } from '@root/utils' import { @@ -692,27 +693,7 @@ const WorkspaceScreen = () => { name: tempVarName, type: { definition: 'base-type', - value: outputVar.type.value.toLowerCase() as - | 'bool' - | 'int' - | 'real' - | 'time' - | 'string' - | 'date' - | 'sint' - | 'dint' - | 'lint' - | 'usint' - | 'uint' - | 'udint' - | 'ulint' - | 'lreal' - | 'tod' - | 'dt' - | 'byte' - | 'word' - | 'dword' - | 'lword', + value: outputVar.type.value.toLowerCase() as PLCBaseTypesLowercase, }, class: 'local', location: '', @@ -890,27 +871,7 @@ const WorkspaceScreen = () => { name: tempVarName, type: { definition: 'base-type', - value: outputVar.type.value.toLowerCase() as - | 'bool' - | 'int' - | 'real' - | 'time' - | 'string' - | 'date' - | 'sint' - | 'dint' - | 'lint' - | 'usint' - | 'uint' - | 'udint' - | 'ulint' - | 'lreal' - | 'tod' - | 'dt' - | 'byte' - | 'word' - | 'dword' - | 'lword', + value: outputVar.type.value.toLowerCase() as PLCBaseTypesLowercase, }, class: 'local', location: '', diff --git a/src/types/PLC/units/base-types.ts b/src/types/PLC/units/base-types.ts index bf3144ca6..e3f0b7cee 100644 --- a/src/types/PLC/units/base-types.ts +++ b/src/types/PLC/units/base-types.ts @@ -4,5 +4,6 @@ import { z } from 'zod' const PLCBaseTypesSchema = z.enum(baseTypes) type PLCBaseTypes = z.infer +type PLCBaseTypesLowercase = Lowercase -export { PLCBaseTypes, PLCBaseTypesSchema } +export { PLCBaseTypes, PLCBaseTypesLowercase, PLCBaseTypesSchema } From aabaab625fe9a6a2f69b8479a7870e55e79d7c41 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 27 Feb 2026 13:11:52 -0500 Subject: [PATCH 4/4] fix: resolve FB instance variable polling using correct program name and instance path The non-BOOL polling loop compared varInfo.pouName (the hosting program, e.g. "main") against currentPou.data.name (the FB type name, e.g. "IRRIGATION_CONTROLLER"), so all FB variables were silently skipped. It also passed the already-qualified variable name into makeCompositeKeyForCurrentPou, which would double-prefix the instance path. Fix follows the proven BOOL polling pattern: resolve the selected FB instance context, then filter by programName + instance path prefix. Co-Authored-By: Claude Opus 4.6 --- src/renderer/screens/workspace-screen.tsx | 43 ++++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/renderer/screens/workspace-screen.tsx b/src/renderer/screens/workspace-screen.tsx index a7d37c8c3..33fdcac97 100644 --- a/src/renderer/screens/workspace-screen.tsx +++ b/src/renderer/screens/workspace-screen.tsx @@ -1391,21 +1391,38 @@ const WorkspaceScreen = () => { // This adds every variable registered in variableInfoMapRef that belongs to the current POU, // enabling the DebugValueBadge components to show real-time values for INT, REAL, etc. if (currentPou) { - const activePouName = currentPou.data.name - Array.from(variableInfoMapRef.current.entries()).forEach(([_, varInfos]) => { - for (const varInfo of varInfos) { - if (varInfo.pouName !== activePouName) continue - // For FB POUs, only add variables that match the selected instance context - if (currentPou.type === 'function-block') { - const compositeKey = makeCompositeKeyForCurrentPou(varInfo.variable.name) - if (compositeKey) { - debugVariableKeys.add(compositeKey) - } - } else { - debugVariableKeys.add(`${varInfo.pouName}:${varInfo.variable.name}`) + if (currentPou.type === 'function-block') { + // For FB POUs, resolve the selected instance context and match variables by + // program name + instance path prefix (same pattern used for BOOL polling above). + const fbTypeKey = currentPou.data.name.toUpperCase() + const selectedKey = fbSelectedInstance.get(fbTypeKey) + if (selectedKey) { + const instances = fbDebugInstances.get(fbTypeKey) || [] + const selectedInstance = instances.find((inst) => inst.key === selectedKey) + if (selectedInstance) { + const instancePrefix = `${selectedInstance.fbVariableName}.` + Array.from(variableInfoMapRef.current.values()).forEach((varInfos) => { + for (const varInfo of varInfos) { + if ( + varInfo.pouName === selectedInstance.programName && + varInfo.variable.name.startsWith(instancePrefix) + ) { + debugVariableKeys.add(`${varInfo.pouName}:${varInfo.variable.name}`) + } + } + }) } } - }) + } else { + const activePouName = currentPou.data.name + Array.from(variableInfoMapRef.current.values()).forEach((varInfos) => { + for (const varInfo of varInfos) { + if (varInfo.pouName === activePouName) { + debugVariableKeys.add(`${varInfo.pouName}:${varInfo.variable.name}`) + } + } + }) + } } // Forced variables must also be polled so their current value appears in the debugger panel