From 33794ddd875d265d4664309c61db211128f9bd5a Mon Sep 17 00:00:00 2001 From: Daniel Coutinho <60111446+dcoutinho1328@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:48:24 -0300 Subject: [PATCH 01/11] =?UTF-8?q?feat(compile):=20embed=20JSON=E2=86=92ST?= =?UTF-8?q?=20transpiler,=20retire=20xml2st=20binary=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the in-process JSON-fed transpiler (`generate-st-from-json/`) from openplc-web into the editor's backend and wires it through the shared `CompilerPlatformPort`/`LibraryBuildPort` surface. The bundled `xml2st` subprocess hop and `XmlGenerator` pre-step are no longer invoked during compile or library-build — both run entirely in-process against the renderer's project IR. - Renames `transpileXmlToSt` → `transpileToSt` across both ports. - Editor port impls (`editor-compiler-platform-port`, `desktop-library-build-port`) project the editor's IPC schema-shape payload via the new `fromSchemaShape` adapter. - Pipeline + library-build orchestrator cast the schema-shape payload to `never` at the port call site (matches openplc-web's pattern; each port impl knows the actual shape it receives). - Adds `@xmldom/xmldom@^0.9.10` for the transitional DOM fallback the graphical-POU walker still uses. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 20 +- package.json | 1 + .../editor/compiler/compiler-module.ts | 1 - .../compiler/desktop-library-build-port.ts | 79 +- .../compiler/editor-compiler-platform-port.ts | 70 +- src/backend/shared/compile/pipeline.ts | 46 +- src/backend/shared/library/build-pipeline.ts | 33 +- .../library/library-build-orchestrator.ts | 41 +- .../data/std_block_catalog.json | 15855 ++++++++++++++++ .../generate-st-from-json/from-schema.ts | 207 + .../generate-st-from-json/index.ts | 192 + .../ir-emit/configuration.ts | 248 + .../ir-emit/data-types.ts | 181 + .../ir-emit/pou-graphical.ts | 212 + .../ir-emit/pou-textual.ts | 194 + .../ir-emit/type-text.ts | 54 + .../generate-st-from-json/ir-emit/value.ts | 76 + .../ir-to-plcopen-dom.ts | 418 + .../ld/from-xmlbuilder.ts | 260 + .../generate-st-from-json/ld/modifiers.ts | 83 + .../generate-st-from-json/ld/path_tree.ts | 253 + .../generate-st-from-json/ld/types.ts | 114 + .../generate-st-from-json/ld/walker.ts | 560 + .../generate-st-from-json/ld/walker_state.ts | 102 + .../pou-emission-order.ts | 129 + .../generate-st-from-json/sfc/types.ts | 113 + .../generate-st-from-json/sfc/walker.ts | 422 + .../generate-st-from-json/sfc/walker_state.ts | 79 + .../src/PLCGenerator/block_library.ts | 370 + .../src/PLCGenerator/body_emit.ts | 313 + .../src/PLCGenerator/configuration.ts | 299 + .../src/PLCGenerator/connection_types.ts | 526 + .../src/PLCGenerator/ctn_globals.ts | 210 + .../src/PLCGenerator/data_type.ts | 266 + .../src/PLCGenerator/gen_state.ts | 163 + .../src/PLCGenerator/generate_data_type.ts | 254 + .../src/PLCGenerator/graph_primitives.ts | 181 + .../src/PLCGenerator/interface.ts | 157 + .../src/PLCGenerator/modifiers.ts | 153 + .../src/PLCGenerator/path_tree.ts | 1091 ++ .../src/PLCGenerator/pou_assembly.ts | 275 + .../src/PLCGenerator/program.ts | 227 + .../src/PLCGenerator/sfc.ts | 741 + .../src/PLCGenerator/text_helpers.ts | 93 + .../src/PLCGenerator/type_hierarchy.ts | 96 + .../src/PLCGenerator/type_text.ts | 107 + .../src/PLCGenerator/variable_type.ts | 141 + .../src/plcopen/accessors.ts | 872 + .../src/plcopen/plcopen.ts | 92 + .../src/util/py_compat.ts | 154 + .../src/xmlclass/xsdschema.ts | 140 + .../generate-st-from-json/types.ts | 178 + .../shared/ports/compiler-platform-port.ts | 40 +- .../shared/ports/library-build-port.ts | 18 +- 54 files changed, 27021 insertions(+), 179 deletions(-) create mode 100644 src/backend/shared/transpilers/generate-st-from-json/data/std_block_catalog.json create mode 100644 src/backend/shared/transpilers/generate-st-from-json/from-schema.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/index.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/configuration.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/data-types.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-graphical.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-textual.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/type-text.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-emit/value.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ir-to-plcopen-dom.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/from-xmlbuilder.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/modifiers.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/path_tree.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/types.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/walker.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/ld/walker_state.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/pou-emission-order.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/sfc/types.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/sfc/walker.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/sfc/walker_state.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/block_library.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/body_emit.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/configuration.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/connection_types.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/ctn_globals.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/data_type.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/gen_state.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/generate_data_type.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/graph_primitives.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/interface.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/modifiers.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/path_tree.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/pou_assembly.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/program.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/sfc.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/text_helpers.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/type_hierarchy.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/type_text.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/variable_type.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/plcopen/accessors.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/plcopen/plcopen.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/util/py_compat.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/src/xmlclass/xsdschema.ts create mode 100644 src/backend/shared/transpilers/generate-st-from-json/types.ts diff --git a/package-lock.json b/package-lock.json index ff0d45ce8..8391f00f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@tailwindcss/forms": "^0.5.10", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.2", + "@xmldom/xmldom": "^0.9.10", "@xyflow/react": "^12.0.1", "auto-zustand-selectors-hook": "^2.0.0", "avr8js": "0.20.0", @@ -11037,13 +11038,12 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.6" } }, "node_modules/@xtuc/ieee754": { @@ -24901,6 +24901,16 @@ "node": ">=10.4.0" } }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", diff --git a/package.json b/package.json index b1d671ab7..49a41b64c 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@tailwindcss/forms": "^0.5.10", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.2", + "@xmldom/xmldom": "^0.9.10", "@xyflow/react": "^12.0.1", "auto-zustand-selectors-hook": "^2.0.0", "avr8js": "0.20.0", diff --git a/src/backend/editor/compiler/compiler-module.ts b/src/backend/editor/compiler/compiler-module.ts index c6ccd7bfb..af68e7a3d 100644 --- a/src/backend/editor/compiler/compiler-module.ts +++ b/src/backend/editor/compiler/compiler-module.ts @@ -2977,7 +2977,6 @@ class CompilerModule { // glue the library build needs — every stage decision lives in // the shared orchestrator from here on. const libraryPort = createDesktopLibraryBuildPort({ - transpileXmlToSt: (xmlPath, log, extraArgs) => this.handleTranspileXMLtoST(xmlPath, log, extraArgs), loadEnabledArchives: (names) => mainProcessBridge.loadEnabledArchives(names), runVerificationCompile: ({ projectPath: p, verifyProjectData: v, emit }) => this.runVerificationCompile(p, v as PLCProjectData, mainProcessBridge, (message, logLevel) => diff --git a/src/backend/editor/compiler/desktop-library-build-port.ts b/src/backend/editor/compiler/desktop-library-build-port.ts index f368967d8..450a77637 100644 --- a/src/backend/editor/compiler/desktop-library-build-port.ts +++ b/src/backend/editor/compiler/desktop-library-build-port.ts @@ -7,7 +7,8 @@ * `runLibraryBuildPipeline` cannot perform itself: * * - MD5 hashing (Node `crypto`) - * - xml2st subprocess invocation (the desktop binary) + * - in-process JSON → ST transpilation via + * `backend/shared/transpilers/generate-st-from-json/` * - read / write / delete project files on the local disk * - resolve library-name → `.stlib` archive via the main-process bridge * - drive a verification compile through the editor's existing @@ -17,16 +18,20 @@ * No business logic lives here. The orchestrator owns the build * sequence, cache decisions, error formatting, and the stable * `build/library/*` file names — every byte of that surface is - * shared with the web port impl that lands in a follow-up PR. + * shared with the web port impl. */ -import { createHash, randomUUID } from 'node:crypto' +import { createHash } from 'node:crypto' import * as fs from 'node:fs/promises' -import * as os from 'node:os' import * as path from 'node:path' import { assertPathContained } from '@root/backend/editor/utils/path-containment' -import type { TranspileXmlToStArgs, TranspileXmlToStResult } from '@root/middleware/shared/ports/compiler-platform-port' +import { + fromSchemaShape, + type SchemaProjectData, + transpileToSt as runJsonTranspiler, +} from '@root/backend/shared/transpilers/generate-st-from-json' +import type { TranspileToStArgs, TranspileToStResult } from '@root/middleware/shared/ports/compiler-platform-port' import type { LibraryBuildPort } from '@root/middleware/shared/ports/library-build-port' /** @@ -35,19 +40,6 @@ import type { LibraryBuildPort } from '@root/middleware/shared/ports/library-bui * surface stays narrow + unit-testable. */ export interface DesktopLibraryBuildPortDeps { - /** - * Spawn the bundled `xml2st` binary on the given input path. The - * binary writes `program.st` next to its input; the port reads it - * back from disk after the spawn resolves. Matches the existing - * `CompilerModule.handleTranspileXMLtoST` signature so the - * adapter can pass it through verbatim without a wrapper. - */ - transpileXmlToSt( - xmlPath: string, - log: (chunk: Buffer | string, level?: 'info' | 'error') => void, - extraArgs: readonly string[], - ): Promise - /** * Resolve the names of project-enabled libraries to their parsed * `.stlib` archives. Bundled IEC standard set is included @@ -80,42 +72,31 @@ export function createDesktopLibraryBuildPort(deps: DesktopLibraryBuildPortDeps) return Promise.resolve(createHash('md5').update(input).digest('hex')) }, - async transpileXmlToSt( - args: TranspileXmlToStArgs, + async transpileToSt( + args: TranspileToStArgs, log: (message: string, level: 'info' | 'warning' | 'error') => void, - ): Promise { - // xml2st takes a file path on stdin, so materialise the - // in-memory XML to a unique temp file before spawning. - // Lives in `os.tmpdir()` because the user-visible `plc.xml` - // is written separately by the orchestrator via - // writeBuildFile — the intermediate here exists only for the - // subprocess. - const sessionDir = path.join(os.tmpdir(), `openplc-lib-xml2st-${randomUUID()}`) + ): Promise { try { - await fs.mkdir(sessionDir, { recursive: true }) - const xmlPath = path.join(sessionDir, 'plc.xml') - const programStPath = path.join(sessionDir, 'program.st') - await fs.writeFile(xmlPath, args.xml, 'utf-8') - - await deps.transpileXmlToSt( - xmlPath, - (chunk, level) => log(typeof chunk === 'string' ? chunk : chunk.toString(), level ?? 'info'), - args.xml2stArgs, - ) - - const programSt = await fs.readFile(programStPath, 'utf-8') - return { ok: true, programSt } + // Editor library builds receive the same schema-shape + // project data as `compileProgram` (see `transpileToSt` on + // `editor-compiler-platform-port` for the IPC-shape note). + // The double cast bridges the port's declared port-shape type + // and the actual schema-shape payload at the boundary. + const ir = fromSchemaShape(args.projectData as unknown as SchemaProjectData) + const result = runJsonTranspiler(ir) + if (result.programSt === null || result.errors.length > 0) { + const message = result.errors.join('\n') || 'transpile-from-json failed' + log(message, 'error') + return { ok: false, errors: [{ message, line: 0, column: 0, severity: 'error' }] } + } + for (const warning of result.warnings) { + log(warning, 'info') + } + return { ok: true, programSt: result.programSt } } catch (error) { const message = error instanceof Error ? error.message : String(error) - log(`xml2st failed: ${message}`, 'error') + log(`transpile-from-json failed: ${message}`, 'error') return { ok: false, errors: [{ message, line: 0, column: 0, severity: 'error' }] } - } finally { - // Best-effort cleanup. Leaks aren't fatal (os.tmpdir is - // the OS's responsibility) but tidying after ourselves - // keeps the dev disk clean. - await fs.rm(sessionDir, { recursive: true, force: true }).catch(() => { - /* swallow — the temp dir is the OS's to GC */ - }) } }, diff --git a/src/backend/editor/compiler/editor-compiler-platform-port.ts b/src/backend/editor/compiler/editor-compiler-platform-port.ts index 932df2a1c..36b296d64 100644 --- a/src/backend/editor/compiler/editor-compiler-platform-port.ts +++ b/src/backend/editor/compiler/editor-compiler-platform-port.ts @@ -1,8 +1,8 @@ /** * Editor-side implementation of `CompilerPlatformPort`. * - * Wraps the existing editor handlers (`handleTranspileXMLtoST`, - * `handleCompileArduinoProgram`, etc.) so the shared compile pipeline + * Wraps the existing editor handlers (`handleCompileArduinoProgram`, + * etc.) so the shared compile pipeline * (`backend/shared/compile/pipeline.ts`) can drive editor's compile * flow through the canonical platform-port contract. * @@ -16,10 +16,12 @@ * - Translates the handler's return value back into the port's * canonical result shape * - * No new pipeline logic lives here — only the platform-specific glue - * the editor needs to materialise the in-memory inputs to disk so - * `xml2st` / `arduino-cli` subprocesses can consume them, and to - * read the resulting artefacts back into memory for the pipeline. + * `transpileToSt` no longer needs disk materialisation or a + * subprocess: it projects the project IR via `fromSchemaShape` + * (editor IPC delivers schema-shape data) and runs the in-process + * JSON-fed transpiler + * (`backend/shared/transpilers/generate-st-from-json/`). The old + * `xml2st` binary path has been retired. * * This module is editor-only (lives under `backend/editor/`); the * web platform implements the same port interface separately under @@ -28,6 +30,11 @@ import { deployRuntimeProgram } from '@root/backend/shared/library/deploy-runtime-program' import { probeRuntimeVersion } from '@root/backend/shared/library/probe-runtime-version' +import { + fromSchemaShape, + type SchemaProjectData, + transpileToSt as runJsonTranspiler, +} from '@root/backend/shared/transpilers/generate-st-from-json' import type { CheckRuntimeVersionArgs, CheckRuntimeVersionResult, @@ -40,8 +47,8 @@ import type { PackageVppPluginResult, PlatformDeviceContext, PlatformLog, - TranspileXmlToStArgs, - TranspileXmlToStResult, + TranspileToStArgs, + TranspileToStResult, UploadArduinoBoardArgs, UploadResult, UploadRuntimeV3Args, @@ -152,33 +159,34 @@ export function createEditorCompilerPlatformPort( }, /** - * Spawn the bundled `xml2st` binary to transpile IEC 61131-3 - * XML to ST. The existing `handleTranspileXMLtoST` expects a - * file path (it opens the file via the subprocess's stdin), so - * we materialise the in-memory XML to a temp file first and - * read the produced `program.st` back from disk. + * Project the editor's port-shape data straight to Structured + * Text via the in-process JSON-fed transpiler. No subprocess, + * no disk round-trip — Electron's main process drives the + * walker directly. */ - async transpileXmlToSt(args: TranspileXmlToStArgs, log: PlatformLog): Promise { - const xmlPath = join(context.sourceTargetFolderPath, 'plc.xml') + async transpileToSt(args: TranspileToStArgs, log: PlatformLog): Promise { try { - await fs.mkdir(dirname(xmlPath), { recursive: true }) - await fs.writeFile(xmlPath, args.xml, 'utf-8') - - await handlers.handleTranspileXMLtoST( - xmlPath, - (chunk, level) => { - const message = typeof chunk === 'string' ? chunk : chunk.toString() - log(message, level ?? 'info') - }, - args.xml2stArgs, - ) - - const programStPath = join(context.sourceTargetFolderPath, 'program.st') - const programSt = await fs.readFile(programStPath, 'utf-8') - return { ok: true, programSt } + // Editor IPC delivers the schema-shape project data + // (discriminated-union POUs + singular `configuration`). + // The port's declared `projectData` type is port-shape, but + // the pipeline reaches us with the editor's schema-shape IPC + // payload (matching `compileProgram`'s actual contract). The + // double cast bridges the static mismatch without serialising + // through `unknown` at runtime. + const ir = fromSchemaShape(args.projectData as unknown as SchemaProjectData) + const result = runJsonTranspiler(ir) + if (result.programSt === null || result.errors.length > 0) { + const message = result.errors.join('\n') || 'Failed to generate Structured Text' + log(message, 'error') + return { ok: false, errors: [{ message, line: 0, column: 0, severity: 'error' }] } + } + for (const warning of result.warnings) { + log(warning, 'info') + } + return { ok: true, programSt: result.programSt } } catch (error) { const message = error instanceof Error ? error.message : String(error) - log(`xml2st failed: ${message}`, 'error') + log(`generate-st-from-json failed: ${message}`, 'error') return { ok: false, errors: [{ message, line: 0, column: 0, severity: 'error' }] } } }, diff --git a/src/backend/shared/compile/pipeline.ts b/src/backend/shared/compile/pipeline.ts index e0a4bc6c2..0933b696b 100644 --- a/src/backend/shared/compile/pipeline.ts +++ b/src/backend/shared/compile/pipeline.ts @@ -40,7 +40,6 @@ import type { DevicePin } from '../types/PLC/devices' // (plural `configurations`) and converts at the pipeline entry — see C1 // in the architectural plan. import type { PLCProjectData } from '../types/PLC/open-plc' -import { XmlGenerator } from '../utils/PLC/xml-generator' import { buildCBlocksFromPous, composeFirmwareBundle } from './steps/compose-firmware-bundle' import { generateRuntimeConfs } from './steps/generate-confs' import { generateDefinesContent } from './steps/generate-defines' @@ -398,35 +397,26 @@ async function runCompilePipelineInner( } // --------------------------------------------------------------------- - // Step 1: Generate IEC 61131-3 XML from the project JSON. - // --------------------------------------------------------------------- - emit({ stage: 'xml', message: 'Generating IEC 61131-3 XML...', level: 'info' }) - // XmlGenerator accepts the schema-shape PLCProjectData (singular - // `configuration`). We pass through the preprocessor's output - // which is structurally compatible at runtime — see Step 0's type - // note. - const xmlResult = XmlGenerator(processedData as never, 'old-editor') - if (!xmlResult.ok || !xmlResult.data) { - return bailError(emit, 'xml', `Error generating XML from JSON: ${xmlResult.message}`) - } - const plcXml = xmlResult.data - - // --------------------------------------------------------------------- - // Step 2: Transpile XML to ST via the platform port (xml2st binary - // on editor, HTTP /generate-st on web). + // Step 1: Transpile the project IR straight to Structured Text via + // the platform port. Both adapters (editor + web) route through + // the in-process JSON-fed transpiler (`generate-st-from-json/`), + // so this hop never builds PLCOpen XML. Native STRUCT declarations + // are the only emission mode the transpiler supports — the legacy + // matiec struct→FB rewrite isn't ported, so there are no + // equivalents of the old `xml2stArgs` flags. // --------------------------------------------------------------------- emit({ stage: 'st', message: 'Generating Structured Text...', level: 'info' }) - // `['--keep-structs']` — strucpp parses native `STRUCT` declarations - // and rejects matiec's legacy struct→FB rewrite as a type-vs-instance - // mismatch. Editor's local xml2st always passed `--keep-structs`; - // pre-pipeline this was hardcoded inside its compiler-module while - // the web's compile-service `/generate-st` endpoint ran without it, - // causing structs to compile on the desktop and fail on the web. - // The flag set is now part of the port contract so both adapters - // observe the same tokens — future xml2st flags get appended here - // at this single call site. - const stResult = await port.transpileXmlToSt( - { xml: plcXml, xml2stArgs: ['--keep-structs'] }, + // The pipeline carries the editor's schema-shape `PLCProjectData`, + // but the port's `transpileToSt` is typed against the renderer's + // port-shape (`middleware/shared/ports/types`). The two diverge in + // POU layout (discriminated union vs. flat record) and configuration + // field name (`configuration` vs. `configurations`). Each platform + // port impl knows which shape it actually receives — desktop/editor + // routes through `fromSchemaShape`; web routes through `fromPortShape` + // after converting at the adapter boundary. Casting to `never` here + // erases the structural mismatch without losing runtime fidelity. + const stResult = await port.transpileToSt( + { projectData: processedData as never }, makePlatformLog(emit, 'st'), ) if (!stResult.ok || !stResult.programSt) { diff --git a/src/backend/shared/library/build-pipeline.ts b/src/backend/shared/library/build-pipeline.ts index 5369053e3..622eff878 100644 --- a/src/backend/shared/library/build-pipeline.ts +++ b/src/backend/shared/library/build-pipeline.ts @@ -38,7 +38,6 @@ import type { PLCProject, PLCProjectData } from '@root/backend/shared/types/PLC/open-plc' import { checkPathId } from '@root/backend/shared/utils/path-safety' import { type KnownPou, splitProgramSt } from '@root/backend/shared/utils/PLC/split-program-st' -import { XmlGenerator } from '@root/backend/shared/utils/PLC/xml-generator' import { compileStlib, type CompileStlibError, type CompileStlibSource } from './compile-stlib' @@ -216,24 +215,29 @@ const STUB_SPLIT_FILENAME = `${STUB_PROGRAM_NAME}.st` // --------------------------------------------------------------------------- export interface PrepareXmlResult { - /** Plc.xml content the caller passes to xml2st. */ - xml: string - /** POU list the splitter needs to slice the xml2st output. + /** Stubbed project data — passes directly into the JSON-fed + * transpiler via `port.transpileToSt({ projectData })`. The + * stub adds a synthesised `main` program so the transpiler's + * "requires a main POU" check passes. */ + projectData: PLCProject['data'] + /** POU list the splitter needs to slice the transpiler output. * Includes the stub so the splitter recognises and emits a slice * for it — caller then drops that slice. */ knownPous: KnownPou[] /** Manifest the second stage reads, parsed here so a malformed - * manifest bails BEFORE xml2st runs. */ + * manifest bails BEFORE the transpile step. */ manifest: LibraryBuildManifest } export type PrepareXmlOutcome = PrepareXmlResult | { error: string } /** - * Stage 1. Validates the manifest and produces the XML xml2st - * consumes. Returns `{error}` when the manifest fails validation - * — caller surfaces that as a build error and bails before - * spawning xml2st. + * Stage 1. Validates the manifest and returns the stubbed project + * data the JSON transpiler consumes (plus the POU list the splitter + * needs). The "stub" adds a synthesised `main` program so the + * transpiler's "requires a main POU" guard passes — the caller drops + * the stub's slice from the splitter output downstream. Returns + * `{error}` when the manifest fails validation. */ export function prepareXmlForLibraryBuild(project: PLCProject, manifestJson: string): PrepareXmlOutcome { const parsed = parseLibraryManifest(manifestJson) @@ -242,15 +246,6 @@ export function prepareXmlForLibraryBuild(project: PLCProject, manifestJson: str } const stubbed = stubProgramFor(project) - // `'old-editor'` keeps the XML shape compatible with the bundled - // xml2st binary (the MatIEC-era flavor), which is the same pipeline - // every other library/program build in this repo speaks to. The - // `'codesys'` flavor exists for export-only paths; using it here - // would leave xml2st unable to find the program block. - const xmlRes = XmlGenerator(stubbed.data, 'old-editor') - if (!xmlRes.ok || !xmlRes.data) { - return { error: `XML generation failed: ${xmlRes.message ?? 'unknown error'}` } - } const knownPous: KnownPou[] = stubbed.data.pous.map((p) => ({ name: p.data.name, @@ -258,7 +253,7 @@ export function prepareXmlForLibraryBuild(project: PLCProject, manifestJson: str language: p.data.language, })) - return { xml: xmlRes.data, knownPous, manifest: parsed.manifest } + return { projectData: stubbed.data, knownPous, manifest: parsed.manifest } } // --------------------------------------------------------------------------- diff --git a/src/backend/shared/library/library-build-orchestrator.ts b/src/backend/shared/library/library-build-orchestrator.ts index 44b539383..283adb8dd 100644 --- a/src/backend/shared/library/library-build-orchestrator.ts +++ b/src/backend/shared/library/library-build-orchestrator.ts @@ -15,11 +15,11 @@ * Stages: * * 0. Read `library.json` manifest. - * 1. Validate manifest + generate plc.xml (via shared - * `prepareXmlForLibraryBuild`). - * 2. xml2st → program.st (via `LibraryBuildPort.transpileXmlToSt`). - * The XML and ST live in memory only — no intermediate file - * persistence (see path-constants comment). + * 1. Validate manifest + stub the project for the transpiler + * (via shared `prepareXmlForLibraryBuild`). + * 2. Project IR → program.st (via `LibraryBuildPort.transpileToSt`). + * The ST lives in memory only — no intermediate file persistence + * (see path-constants comment). * 3. Resolve project-enabled library archives + fail on missing * names (one place — feeds BOTH verification and strucpp). * 4. Verification compile against the OpenPLC Simulator target @@ -128,29 +128,30 @@ export async function runLibraryBuildPipeline( if ('error' in stage1) { return fail(emit, stage1.error) } - const { xml, knownPous, manifest } = stage1 + const { projectData: stubbedData, knownPous, manifest } = stage1 emit({ message: `Manifest OK — building "${manifest.name}" v${manifest.version}.`, level: 'info' }) // ------------------------------------------------------------------------- - // Stage 2: xml2st via the shared compiler platform port + // Stage 2: project → ST via the shared compiler platform port. // - // The shared port abstracts the spawn vs HTTP divergence; the - // `--keep-structs` arg mirrors what `runCompilePipeline` passes - // for program builds so manifest type compatibility carries over. - // The XML and the resulting ST live in memory only — no - // intermediate-file persistence, see the path-constants comment. + // The port routes through the in-process JSON-fed transpiler + // (`generate-st-from-json/`). Native STRUCT emission is the only + // mode — no equivalents of the old `--keep-structs` flag exist. + // The resulting ST lives in memory only. // ------------------------------------------------------------------------- - emit({ message: 'Compiling file plc.xml', level: 'info' }) - const transpile = await port.transpileXmlToSt( - { xml, xml2stArgs: ['--keep-structs'] }, - // Forward xml2st's stdout / stderr to the caller — same channel - // the desktop pre-refactor compileLibrary used (`post(message, - // logLevel)`), so the console output stays unchanged. + emit({ message: 'Transpiling project to Structured Text', level: 'info' }) + // `stubbedData` is editor schema-shape; the port's signature is + // port-shape. Each platform port impl knows the actual shape it + // receives (desktop → `fromSchemaShape`; web → `fromPortShape` after + // its adapter converts). See the matching cast site in + // `pipeline.ts` (Step 1) for the same comment. + const transpile = await port.transpileToSt( + { projectData: stubbedData as never }, (message, level) => emit({ message, level }), ) if (!transpile.ok || !transpile.programSt) { - const firstError = transpile.errors?.[0]?.message ?? 'xml2st failed' - return fail(emit, `xml2st failed: ${firstError}`, { libraryName: manifest.name }) + const firstError = transpile.errors?.[0]?.message ?? 'transpile-from-json failed' + return fail(emit, `transpile-from-json failed: ${firstError}`, { libraryName: manifest.name }) } const programSt = transpile.programSt diff --git a/src/backend/shared/transpilers/generate-st-from-json/data/std_block_catalog.json b/src/backend/shared/transpilers/generate-st-from-json/data/std_block_catalog.json new file mode 100644 index 000000000..4a54625a6 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/data/std_block_catalog.json @@ -0,0 +1,15855 @@ +{ + "SR": [ + { + "section": "Standard function blocks", + "infos": { + "name": "SR", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "S1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q1", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The SR bistable is a latch where the Set dominates.", + "usage": "\n (BOOL:S1, BOOL:R) => (BOOL:Q1)" + } + } + ], + "RS": [ + { + "section": "Standard function blocks", + "infos": { + "name": "RS", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "S", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "R1", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q1", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The RS bistable is a latch where the Reset dominates.", + "usage": "\n (BOOL:S, BOOL:R1) => (BOOL:Q1)" + } + } + ], + "SEMA": [ + { + "section": "Standard function blocks", + "infos": { + "name": "SEMA", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CLAIM", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELEASE", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "BUSY", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The semaphore provides a mechanism to allow software elements mutually exclusive access to certain resources.", + "usage": "\n (BOOL:CLAIM, BOOL:RELEASE) => (BOOL:BUSY)" + } + } + ], + "R_TRIG": [ + { + "section": "Standard function blocks", + "infos": { + "name": "R_TRIG", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CLK", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The output produces a single pulse when a rising edge is detected.", + "usage": "\n (BOOL:CLK) => (BOOL:Q)" + } + } + ], + "F_TRIG": [ + { + "section": "Standard function blocks", + "infos": { + "name": "F_TRIG", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CLK", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The output produces a single pulse when a falling edge is detected.", + "usage": "\n (BOOL:CLK) => (BOOL:Q)" + } + } + ], + "CTU": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTU", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "The up-counter can be used to signal when a count has reached a maximum value.", + "usage": "\n (BOOL:CU, BOOL:R, INT:PV) => (BOOL:Q, INT:CV)" + } + } + ], + "CTU_DINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTU_DINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "The up-counter can be used to signal when a count has reached a maximum value.", + "usage": "\n (BOOL:CU, BOOL:R, DINT:PV) => (BOOL:Q, DINT:CV)" + } + } + ], + "CTU_LINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTU_LINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "The up-counter can be used to signal when a count has reached a maximum value.", + "usage": "\n (BOOL:CU, BOOL:R, LINT:PV) => (BOOL:Q, LINT:CV)" + } + } + ], + "CTU_UDINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTU_UDINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "The up-counter can be used to signal when a count has reached a maximum value.", + "usage": "\n (BOOL:CU, BOOL:R, UDINT:PV) => (BOOL:Q, UDINT:CV)" + } + } + ], + "CTU_ULINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTU_ULINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "The up-counter can be used to signal when a count has reached a maximum value.", + "usage": "\n (BOOL:CU, BOOL:R, ULINT:PV) => (BOOL:Q, ULINT:CV)" + } + } + ], + "CTD": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTD", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value.", + "usage": "\n (BOOL:CD, BOOL:LD, INT:PV) => (BOOL:Q, INT:CV)" + } + } + ], + "CTD_DINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTD_DINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value.", + "usage": "\n (BOOL:CD, BOOL:LD, DINT:PV) => (BOOL:Q, DINT:CV)" + } + } + ], + "CTD_LINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTD_LINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value.", + "usage": "\n (BOOL:CD, BOOL:LD, LINT:PV) => (BOOL:Q, LINT:CV)" + } + } + ], + "CTD_UDINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTD_UDINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value.", + "usage": "\n (BOOL:CD, BOOL:LD, UDINT:PV) => (BOOL:Q, UDINT:CV)" + } + } + ], + "CTD_ULINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTD_ULINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value.", + "usage": "\n (BOOL:CD, BOOL:LD, ULINT:PV) => (BOOL:Q, ULINT:CV)" + } + } + ], + "CTUD": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTUD", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "QU", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "QD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "INT", + "qualifier": "none" + }, + { + "name": "CD_T", + "type": "R_TRIG", + "qualifier": "none" + }, + { + "name": "CU_T", + "type": "R_TRIG", + "qualifier": "none" + } + ], + "comment": "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other.", + "usage": "\n (BOOL:CU, BOOL:CD, BOOL:R, BOOL:LD, INT:PV) => (BOOL:QU, BOOL:QD, INT:CV, R_TRIG:CD_T, R_TRIG:CU_T)" + } + } + ], + "CTUD_DINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTUD_DINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "QU", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "QD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "DINT", + "qualifier": "none" + }, + { + "name": "CD_T", + "type": "R_TRIG", + "qualifier": "none" + }, + { + "name": "CU_T", + "type": "R_TRIG", + "qualifier": "none" + } + ], + "comment": "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other.", + "usage": "\n (BOOL:CU, BOOL:CD, BOOL:R, BOOL:LD, DINT:PV) => (BOOL:QU, BOOL:QD, DINT:CV, R_TRIG:CD_T, R_TRIG:CU_T)" + } + } + ], + "CTUD_LINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTUD_LINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "QU", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "QD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "LINT", + "qualifier": "none" + }, + { + "name": "CD_T", + "type": "R_TRIG", + "qualifier": "none" + }, + { + "name": "CU_T", + "type": "R_TRIG", + "qualifier": "none" + } + ], + "comment": "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other.", + "usage": "\n (BOOL:CU, BOOL:CD, BOOL:R, BOOL:LD, LINT:PV) => (BOOL:QU, BOOL:QD, LINT:CV, R_TRIG:CD_T, R_TRIG:CU_T)" + } + } + ], + "CTUD_UDINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTUD_UDINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "QU", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "QD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "UDINT", + "qualifier": "none" + }, + { + "name": "CD_T", + "type": "R_TRIG", + "qualifier": "none" + }, + { + "name": "CU_T", + "type": "R_TRIG", + "qualifier": "none" + } + ], + "comment": "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other.", + "usage": "\n (BOOL:CU, BOOL:CD, BOOL:R, BOOL:LD, UDINT:PV) => (BOOL:QU, BOOL:QD, UDINT:CV, R_TRIG:CD_T, R_TRIG:CU_T)" + } + } + ], + "CTUD_ULINT": [ + { + "section": "Standard function blocks", + "infos": { + "name": "CTUD_ULINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CU", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "CD", + "type": "BOOL", + "qualifier": "rising" + }, + { + "name": "R", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "QU", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "QD", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CV", + "type": "ULINT", + "qualifier": "none" + }, + { + "name": "CD_T", + "type": "R_TRIG", + "qualifier": "none" + }, + { + "name": "CU_T", + "type": "R_TRIG", + "qualifier": "none" + } + ], + "comment": "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other.", + "usage": "\n (BOOL:CU, BOOL:CD, BOOL:R, BOOL:LD, ULINT:PV) => (BOOL:QU, BOOL:QD, ULINT:CV, R_TRIG:CD_T, R_TRIG:CU_T)" + } + } + ], + "TP": [ + { + "section": "Standard function blocks", + "infos": { + "name": "TP", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PT", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "ET", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "The pulse timer can be used to generate output pulses of a given time duration.", + "usage": "\n (BOOL:IN, TIME:PT) => (BOOL:Q, TIME:ET)" + } + } + ], + "TON": [ + { + "section": "Standard function blocks", + "infos": { + "name": "TON", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PT", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "ET", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true.", + "usage": "\n (BOOL:IN, TIME:PT) => (BOOL:Q, TIME:ET)" + } + } + ], + "TOF": [ + { + "section": "Standard function blocks", + "infos": { + "name": "TOF", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PT", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "ET", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "The off-delay timer can be used to delay setting an output false, for fixed period after input goes false.", + "usage": "\n (BOOL:IN, TIME:PT) => (BOOL:Q, TIME:ET)" + } + } + ], + "RTC": [ + { + "section": "Additional function blocks", + "infos": { + "name": "RTC", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PDT", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CDT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on.", + "usage": "\n (BOOL:IN, DT:PDT) => (BOOL:Q, DT:CDT)" + } + } + ], + "INTEGRAL": [ + { + "section": "Additional function blocks", + "infos": { + "name": "INTEGRAL", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "RUN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "R1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "XIN", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "X0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "CYCLE", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "XOUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "The integral function block integrates the value of input XIN over time.", + "usage": "\n (BOOL:RUN, BOOL:R1, REAL:XIN, REAL:X0, TIME:CYCLE) => (BOOL:Q, REAL:XOUT)" + } + } + ], + "DERIVATIVE": [ + { + "section": "Additional function blocks", + "infos": { + "name": "DERIVATIVE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "RUN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "XIN", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "CYCLE", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "XOUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "The derivative function block produces an output XOUT proportional to the rate of change of the input XIN.", + "usage": "\n (BOOL:RUN, REAL:XIN, TIME:CYCLE) => (REAL:XOUT)" + } + } + ], + "PID": [ + { + "section": "Additional function blocks", + "infos": { + "name": "PID", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "AUTO", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PV", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "SP", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "X0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "KP", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TR", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TD", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "CYCLE", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "XOUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control.", + "usage": "\n (BOOL:AUTO, REAL:PV, REAL:SP, REAL:X0, REAL:KP, REAL:TR, REAL:TD, TIME:CYCLE) => (REAL:XOUT)" + } + } + ], + "RAMP": [ + { + "section": "Additional function blocks", + "infos": { + "name": "RAMP", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "RUN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "X0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "X1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TR", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "CYCLE", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "BUSY", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "XOUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "The RAMP function block is modelled on example given in the standard.", + "usage": "\n (BOOL:RUN, REAL:X0, REAL:X1, TIME:TR, TIME:CYCLE) => (BOOL:BUSY, REAL:XOUT)" + } + } + ], + "HYSTERESIS": [ + { + "section": "Additional function blocks", + "infos": { + "name": "HYSTERESIS", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "XIN1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "XIN2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "EPS", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "Q", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2.", + "usage": "\n (REAL:XIN1, REAL:XIN2, REAL:EPS) => (BOOL:Q)" + } + } + ], + "DS18B20": [ + { + "section": "Arduino", + "infos": { + "name": "DS18B20", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "PIN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Reads temperature from one DS18B20 one-wire sensor connected to the pin specified in PIN", + "usage": "\n (SINT:PIN) => (REAL:OUT)" + } + } + ], + "DS18B20_2_OUT": [ + { + "section": "Arduino", + "infos": { + "name": "DS18B20_2_OUT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "PIN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT_0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_1", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Reads temperature from two DS18B20 one-wire sensors. Both sensors must be on the same bus connected to the pin specified in PIN", + "usage": "\n (SINT:PIN) => (REAL:OUT_0, REAL:OUT_1)" + } + } + ], + "DS18B20_3_OUT": [ + { + "section": "Arduino", + "infos": { + "name": "DS18B20_3_OUT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "PIN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT_0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_2", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Reads temperature from three DS18B20 one-wire sensors. All sensors must be on the same bus connected to the pin specified in PIN", + "usage": "\n (SINT:PIN) => (REAL:OUT_0, REAL:OUT_1, REAL:OUT_2)" + } + } + ], + "DS18B20_4_OUT": [ + { + "section": "Arduino", + "infos": { + "name": "DS18B20_4_OUT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "PIN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT_0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_3", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Reads temperature from four DS18B20 one-wire sensors. All sensors must be on the same bus connected to the pin specified in PIN", + "usage": "\n (SINT:PIN) => (REAL:OUT_0, REAL:OUT_1, REAL:OUT_2, REAL:OUT_3)" + } + } + ], + "DS18B20_5_OUT": [ + { + "section": "Arduino", + "infos": { + "name": "DS18B20_5_OUT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "PIN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT_0", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OUT_4", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Reads temperature from five DS18B20 one-wire sensors. All sensors must be on the same bus connected to the pin specified in PIN", + "usage": "\n (SINT:PIN) => (REAL:OUT_0, REAL:OUT_1, REAL:OUT_2, REAL:OUT_3, REAL:OUT_4)" + } + } + ], + "CLOUD_ADD_BOOL": [ + { + "section": "Arduino", + "infos": { + "name": "CLOUD_ADD_BOOL", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "VAR_NAME", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "BOOL_VAR", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Add a BOOL variable to sync with the Arduino Cloud. VAR_NAME must have the same name as the variable set up in the Arduino IoT Cloud", + "usage": "\n (STRING:VAR_NAME, BOOL:BOOL_VAR) => ()" + } + } + ], + "CLOUD_ADD_DINT": [ + { + "section": "Arduino", + "infos": { + "name": "CLOUD_ADD_DINT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "VAR_NAME", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "DINT_VAR", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Add an DINT variable (Arduino int) to sync with the Arduino Cloud. VAR_NAME must have the same name as the variable set up in the Arduino IoT Cloud", + "usage": "\n (STRING:VAR_NAME, DINT:DINT_VAR) => ()" + } + } + ], + "CLOUD_ADD_REAL": [ + { + "section": "Arduino", + "infos": { + "name": "CLOUD_ADD_REAL", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "VAR_NAME", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "REAL_VAR", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Add a REAL variable (Arduino float) to sync with the Arduino Cloud. VAR_NAME must have the same name as the variable set up in the Arduino IoT Cloud", + "usage": "\n (STRING:VAR_NAME, REAL:REAL_VAR) => ()" + } + } + ], + "CLOUD_BEGIN": [ + { + "section": "Arduino", + "infos": { + "name": "CLOUD_BEGIN", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "THING_ID", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "SSID", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "PASS", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Setup and initialize Arduino Cloud communication. Must be called before adding any variables (properties).", + "usage": "\n (STRING:THING_ID, STRING:SSID, STRING:PASS) => ()" + } + } + ], + "PWM_CONTROLLER": [ + { + "section": "Arduino", + "infos": { + "name": "PWM_CONTROLLER", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CHANNEL", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "FREQ", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "DUTY", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Configures the CPU internal PWM peripheral to generate a PWM signal through hardware. If the CPU does not have a PWM peripheral, compiling this block will result in a compilation error. CHANNEL is the PWM channel number. For most Arduino boards that number is the pin number for the PWM capable pin. FREQ is the desired PWM frequency in Hz. DUTY is the PWM duty cycle (between 0 and 100).", + "usage": "\n (SINT:CHANNEL, REAL:FREQ, REAL:DUTY) => (BOOL:SUCCESS)" + } + } + ], + "ARDUINOCAN_CONF": [ + { + "section": "Arduino", + "infos": { + "name": "ARDUINOCAN_CONF", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "EN_PIN", + "type": "WORD", + "qualifier": "none" + }, + { + "name": "BR", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "", + "usage": "\n (WORD:EN_PIN, LINT:BR) => (BOOL:DONE)" + } + } + ], + "ARDUINOCAN_WRITE": [ + { + "section": "Arduino", + "infos": { + "name": "ARDUINOCAN_WRITE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "ID", + "type": "DWORD", + "qualifier": "none" + }, + { + "name": "D0", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D1", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D2", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D3", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D4", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D5", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D6", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "D7", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "", + "usage": "\n (DWORD:ID, USINT:D0, USINT:D1, USINT:D2, USINT:D3, USINT:D4, USINT:D5, USINT:D6, USINT:D7) => (BOOL:DONE)" + } + } + ], + "ARDUINOCAN_WRITE_WORD": [ + { + "section": "Arduino", + "infos": { + "name": "ARDUINOCAN_WRITE_WORD", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "ID", + "type": "DWORD", + "qualifier": "none" + }, + { + "name": "DATA", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "", + "usage": "\n (DWORD:ID, LWORD:DATA) => (BOOL:DONE)" + } + } + ], + "ARDUINOCAN_READ": [ + { + "section": "Arduino", + "infos": { + "name": "ARDUINOCAN_READ", + "type": "functionBlock", + "extensible": false, + "inputs": [], + "outputs": [ + { + "name": "DATA", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "CAN READ", + "usage": "\n () => (LWORD:DATA)" + } + } + ], + "STM32CAN_CONF": [ + { + "section": "Microver", + "infos": { + "name": "STM32CAN_CONF", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CONF", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "BR", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "", + "usage": "\n (BOOL:CONF, LINT:BR) => (BOOL:DONE)" + } + } + ], + "STM32CAN_WRITE": [ + { + "section": "Microver", + "infos": { + "name": "STM32CAN_WRITE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "EN_PIN", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "CH", + "type": "USINT", + "qualifier": "none" + }, + { + "name": "ID", + "type": "DWORD", + "qualifier": "none" + }, + { + "name": "D0", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D1", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D2", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D3", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D4", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D5", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D6", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D7", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "", + "usage": "\n (BOOL:EN_PIN, USINT:CH, DWORD:ID, BYTE:D0, BYTE:D1, BYTE:D2, BYTE:D3, BYTE:D4, BYTE:D5, BYTE:D6, BYTE:D7) => (BOOL:DONE)" + } + } + ], + "STM32CAN_READ": [ + { + "section": "Microver", + "infos": { + "name": "STM32CAN_READ", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "EN_PIN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "DONE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "ID", + "type": "DWORD", + "qualifier": "none" + }, + { + "name": "D0", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D1", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D2", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D3", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D4", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D5", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D6", + "type": "BYTE", + "qualifier": "none" + }, + { + "name": "D7", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "CAN READ", + "usage": "\n (BOOL:EN_PIN) => (BOOL:DONE, DWORD:ID, BYTE:D0, BYTE:D1, BYTE:D2, BYTE:D3, BYTE:D4, BYTE:D5, BYTE:D6, BYTE:D7)" + } + } + ], + "TCP_CONNECT": [ + { + "section": "Communication", + "infos": { + "name": "TCP_CONNECT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CONNECT", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "IP_ADDRESS", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "PORT", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SOCKET_ID", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Connect to a remote TCP server when CONNECT is TRUE. Upon success, this block returns the connection ID on SOCKET_ID. If SOCKET_ID is less than zero, then the connection was not successfull", + "usage": "\n (BOOL:CONNECT, STRING:IP_ADDRESS, INT:PORT) => (INT:SOCKET_ID)" + } + } + ], + "TCP_SEND": [ + { + "section": "Communication", + "infos": { + "name": "TCP_SEND", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SEND", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "SOCKET_ID", + "type": "INT", + "qualifier": "none" + }, + { + "name": "MSG", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "BYTES_SENT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Send a message to a remote device using TCP/IP when SEND is TRUE. SOCKET_ID must receive a connection ID from a successfull connection using the TCP_Connect block. BYTES_SENT returns the number of bytes sent to the remote device. If BYTES_SENT is less than zero then an error occurred while trying to send the message", + "usage": "\n (BOOL:SEND, INT:SOCKET_ID, STRING:MSG) => (INT:BYTES_SENT)" + } + } + ], + "TCP_RECEIVE": [ + { + "section": "Communication", + "infos": { + "name": "TCP_RECEIVE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "RECEIVE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "SOCKET_ID", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "BYTES_RECEIVED", + "type": "INT", + "qualifier": "none" + }, + { + "name": "MSG", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Send a message to a remote device using TCP/IP when SEND is TRUE. SOCKET_ID must receive a connection ID from a successfull connection using the TCP_Connect block. BYTES_RECEIVED returns the number of bytes received from the remote device. MSG is a String containing the message received", + "usage": "\n (BOOL:RECEIVE, INT:SOCKET_ID) => (INT:BYTES_RECEIVED, STRING:MSG)" + } + } + ], + "TCP_CLOSE": [ + { + "section": "Communication", + "infos": { + "name": "TCP_CLOSE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CLOSE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "SOCKET_ID", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Close the TCP connection with the remote server. If SUCCESS is less than zero, then the connection was not successfully closed, or the connection does not exist anymore.", + "usage": "\n (BOOL:CLOSE, INT:SOCKET_ID) => (INT:SUCCESS)" + } + } + ], + "P1AM_INIT": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1AM_INIT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "INIT", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Initialize P1AM Modules and return the number of initialized modules on SUCCESS. If SUCCESS is zero, an error has occurred, or there aren't any modules on the bus", + "usage": "\n (BOOL:INIT) => (SINT:SUCCESS)" + } + } + ], + "P1_16CDR": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_16CDR", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "O1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O8", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I8", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs and update all outputs from P1-16CDR module. Also works with P1-15CDD1 and P1-15CDD2", + "usage": "\n (SINT:SLOT, BOOL:O1, BOOL:O2, BOOL:O3, BOOL:O4, BOOL:O5, BOOL:O6, BOOL:O7, BOOL:O8) => (BOOL:I1, BOOL:I2, BOOL:I3, BOOL:I4, BOOL:I5, BOOL:I6, BOOL:I7, BOOL:I8)" + } + } + ], + "P1_08N": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_08N", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I8", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs from P1-08Nxx modules. Compatible with P1-08NA, P1-08ND3, P1-08NE3 and P1-08SIM", + "usage": "\n (SINT:SLOT) => (BOOL:I1, BOOL:I2, BOOL:I3, BOOL:I4, BOOL:I5, BOOL:I6, BOOL:I7, BOOL:I8)" + } + } + ], + "P1_16N": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_16N", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I9", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I10", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I11", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I12", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I13", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I14", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I15", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I16", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs from P1-16Nxx modules. Compatible with P1-16ND3 and P1-16NE3", + "usage": "\n (SINT:SLOT) => (BOOL:I1, BOOL:I2, BOOL:I3, BOOL:I4, BOOL:I5, BOOL:I6, BOOL:I7, BOOL:I8, BOOL:I9, BOOL:I10, BOOL:I11, BOOL:I12, BOOL:I13, BOOL:I14, BOOL:I15, BOOL:I16)" + } + } + ], + "P1_08T": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_08T", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "O1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O8", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Set all outputs on P1-08Txx modules. Compatible with P1-08TA, P1-08TD1, P1-08TD2 and P1-08TRS", + "usage": "\n (SINT:SLOT, BOOL:O1, BOOL:O2, BOOL:O3, BOOL:O4, BOOL:O5, BOOL:O6, BOOL:O7, BOOL:O8) => ()" + } + } + ], + "P1_16TR": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_16TR", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "O1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O9", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O10", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O11", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O12", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O13", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O14", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O15", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O16", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Set all outputs on P1-16TR modules. Also compatible with P1-15TD1 and P1-15TD2", + "usage": "\n (SINT:SLOT, BOOL:O1, BOOL:O2, BOOL:O3, BOOL:O4, BOOL:O5, BOOL:O6, BOOL:O7, BOOL:O8, BOOL:O9, BOOL:O10, BOOL:O11, BOOL:O12, BOOL:O13, BOOL:O14, BOOL:O15, BOOL:O16) => ()" + } + } + ], + "P1_04AD": [ + { + "section": "P1AM Modules", + "infos": { + "name": "P1_04AD", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SLOT", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "I2", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "I3", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "I4", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Get all analog inputs from P1-04ADxx modules. Compatible with P1-04AD, P1-04ADL-1 and P1-04ADL-2", + "usage": "\n (SINT:SLOT) => (UINT:I1, UINT:I2, UINT:I3, UINT:I4)" + } + } + ], + "MQTT_RECEIVE": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_RECEIVE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "RECEIVE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TOPIC", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "RECEIVED", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MESSAGE", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Receive MQTT messages for a particular TOPIC when RECEIVE is active. You must subscribe to a topic first before you can start receiving messages for that particular topic. Once a message is received, RECEIVED output is triggered, and MESSAGE will contain the received message as a STRING.", + "usage": "\n (BOOL:RECEIVE, STRING:TOPIC) => (BOOL:RECEIVED, STRING:MESSAGE)" + } + } + ], + "MQTT_SEND": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_SEND", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SEND", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TOPIC", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "MESSAGE", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Sends a MESSAGE to a particular TOPIC when SEND input is triggered. Keep in mind that SEND is not configured as a rising edge input, which means that MQTT_SEND will continuously send messages every scan cycle while SEND is TRUE. If the message was sent without errors, SUCCESS will be TRUE.", + "usage": "\n (BOOL:SEND, STRING:TOPIC, STRING:MESSAGE) => (BOOL:SUCCESS)" + } + } + ], + "MQTT_CONNECT": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_CONNECT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CONNECT", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "BROKER", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "PORT", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Connect to a BROKER at a given PORT when CONNECT is triggered. If a successfull connection is made, SUCCESS is set to TRUE", + "usage": "\n (BOOL:CONNECT, STRING:BROKER, UINT:PORT) => (BOOL:SUCCESS)" + } + } + ], + "MQTT_CONNECT_AUTH": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_CONNECT_AUTH", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "CONNECT", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "BROKER", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "PORT", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "USER", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "PASSWORD", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Connect to an authenticated BROKER at a given PORT using the credentials from USER and PASSWORD when CONNECT is triggered. If a successfull connection is made, SUCCESS is set to TRUE", + "usage": "\n (BOOL:CONNECT, STRING:BROKER, UINT:PORT, STRING:USER, STRING:PASSWORD) => (BOOL:SUCCESS)" + } + } + ], + "MQTT_SUBSCRIBE": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_SUBSCRIBE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "SUBSCRIBE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TOPIC", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Subscribe to a given TOPIC when SUBSCRIBE input is triggered. Upon a successfull subscription, SUCCESS is set to TRUE. Keep in mind that once you subscribe to a topic, OpenPLC will start receiving messages sent to that topic and storing them in a message pool. You must use the MQTT_RECEIVE block to retrieve messages from the pool and free up space to receive more messages. The maximum pool size is currently limited to 10 messages. If you let messages accumulate in the pool you will start loosing messages once the pool is full.", + "usage": "\n (BOOL:SUBSCRIBE, STRING:TOPIC) => (BOOL:SUCCESS)" + } + } + ], + "MQTT_UNSUBSCRIBE": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_UNSUBSCRIBE", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "UNSUBSCRIBE", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TOPIC", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Unsubscribe to a given TOPIC when UNSUBSCRIBE input is triggered. Upon a successfull unsubscription, SUCCESS is set to TRUE. Keep in mind that once you unsubscribe to a topic, OpenPLC will stop storing messages sent to that topic in the message pool. However, messages received previously and not captured with a MQTT_RECEIVE block will remain in the pool using up pool space.", + "usage": "\n (BOOL:UNSUBSCRIBE, STRING:TOPIC) => (BOOL:SUCCESS)" + } + } + ], + "MQTT_DISCONNECT": [ + { + "section": "MQTT", + "infos": { + "name": "MQTT_DISCONNECT", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "DISCONNECT", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Disconnects from the current broker when DISCONNECT is set to TRUE. Upon a successfull disconnection, SUCCESS is set to TRUE.", + "usage": "\n (BOOL:DISCONNECT) => (BOOL:SUCCESS)" + } + } + ], + "SM_8RELAY": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_8RELAY", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "O1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O8", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Update all outputs from 8-relays card", + "usage": "\n (SINT:STACK, BOOL:O1, BOOL:O2, BOOL:O3, BOOL:O4, BOOL:O5, BOOL:O6, BOOL:O7, BOOL:O8) => ()" + } + } + ], + "SM_16RELAY": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_16RELAY", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "O1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O9", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O10", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O11", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O12", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O13", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O14", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O15", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "O16", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Update all outputs from 16-relays card", + "usage": "\n (SINT:STACK, BOOL:O1, BOOL:O2, BOOL:O3, BOOL:O4, BOOL:O5, BOOL:O6, BOOL:O7, BOOL:O8, BOOL:O9, BOOL:O10, BOOL:O11, BOOL:O12, BOOL:O13, BOOL:O14, BOOL:O15, BOOL:O16) => ()" + } + } + ], + "SM_8DIN": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_8DIN", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I8", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs from Sequent microsystems 8 HV Inputs modules", + "usage": "\n (SINT:STACK) => (BOOL:I1, BOOL:I2, BOOL:I3, BOOL:I4, BOOL:I5, BOOL:I6, BOOL:I7, BOOL:I8)" + } + } + ], + "SM_16DIN": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_16DIN", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "I1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I9", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I10", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I11", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I12", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I13", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I14", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I15", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I16", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs from Sequent Microsystems 16 digital inputs modules.", + "usage": "\n (SINT:STACK) => (BOOL:I1, BOOL:I2, BOOL:I3, BOOL:I4, BOOL:I5, BOOL:I6, BOOL:I7, BOOL:I8, BOOL:I9, BOOL:I10, BOOL:I11, BOOL:I12, BOOL:I13, BOOL:I14, BOOL:I15, BOOL:I16)" + } + } + ], + "SM_4REL4IN": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_4REL4IN", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "RELAY1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY4", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OPTO1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "AC_OPTO1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "AC_OPTO2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "AC_OPTO3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "AC_OPTO4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "PWM1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "PWM2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "PWM3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "PWM4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "FREQ1", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "FREQ2", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "FREQ3", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "FREQ4", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "BUTTON", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Get all inputs from and set all outputs to SM_4REL4IN modules", + "usage": "\n (SINT:STACK, BOOL:RELAY1, BOOL:RELAY2, BOOL:RELAY3, BOOL:RELAY4) => (BOOL:OPTO1, BOOL:OPTO2, BOOL:OPTO3, BOOL:OPTO4, BOOL:AC_OPTO1, BOOL:AC_OPTO2, BOOL:AC_OPTO3, BOOL:AC_OPTO4, REAL:PWM1, REAL:PWM2, REAL:PWM3, REAL:PWM4, UINT:FREQ1, UINT:FREQ2, UINT:FREQ3, UINT:FREQ4, BOOL:BUTTON)" + } + } + ], + "SM_INDUSTRIAL": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_INDUSTRIAL", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "LED1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "Q0_10V1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q4_20MA1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q4_20MA2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q4_20MA3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q4_20MA4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD4", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OPTO1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "I0_10V1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I0_10V2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I0_10V3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I0_10V4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I4_20MA1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I4_20MA2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I4_20MA3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "I4_20MA4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T4", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Get all inpust and set all outputs of a Sequent Microsystems Industrial Automation card", + "usage": "\n (SINT:STACK, BOOL:LED1, BOOL:LED2, BOOL:LED3, BOOL:LED4, REAL:Q0_10V1, REAL:Q0_10V2, REAL:Q0_10V3, REAL:Q0_10V4, REAL:Q4_20MA1, REAL:Q4_20MA2, REAL:Q4_20MA3, REAL:Q4_20MA4, REAL:QOD1, REAL:QOD2, REAL:QOD3, REAL:QOD4) => (BOOL:OPTO1, BOOL:OPTO2, BOOL:OPTO3, BOOL:OPTO4, REAL:I0_10V1, REAL:I0_10V2, REAL:I0_10V3, REAL:I0_10V4, REAL:I4_20MA1, REAL:I4_20MA2, REAL:I4_20MA3, REAL:I4_20MA4, REAL:OWB_T1, REAL:OWB_T2, REAL:OWB_T3, REAL:OWB_T4)" + } + } + ], + "SM_RTD": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_RTD", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "TEMP1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP5", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP6", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP7", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "TEMP8", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Get all temperature inputs from SM_RTD module as REAL values in deg Celsious", + "usage": "\n (SINT:STACK) => (REAL:TEMP1, REAL:TEMP2, REAL:TEMP3, REAL:TEMP4, REAL:TEMP5, REAL:TEMP6, REAL:TEMP7, REAL:TEMP8)" + } + } + ], + "SM_BAS": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_BAS", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "TRIAC1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TRIAC2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TRIAC3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "TRIAC4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "LED4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "IN1_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN2_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN3_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN4_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN5_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN6_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN7_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "IN8_T", + "type": "UINT", + "qualifier": "none" + }, + { + "name": "Q0_10V1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V4", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "UNIV1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV5", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV6", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV7", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "UNIV8", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "DRY_C1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "DRY_C8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OWB_T1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T4", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Get all inpust and set all outputs of a Sequent Microsystems Building Automation card", + "usage": "\n (SINT:STACK, BOOL:TRIAC1, BOOL:TRIAC2, BOOL:TRIAC3, BOOL:TRIAC4, BOOL:LED1, BOOL:LED2, BOOL:LED3, BOOL:LED4, UINT:IN1_T, UINT:IN2_T, UINT:IN3_T, UINT:IN4_T, UINT:IN5_T, UINT:IN6_T, UINT:IN7_T, UINT:IN8_T, REAL:Q0_10V1, REAL:Q0_10V2, REAL:Q0_10V3, REAL:Q0_10V4) => (REAL:UNIV1, REAL:UNIV2, REAL:UNIV3, REAL:UNIV4, REAL:UNIV5, REAL:UNIV6, REAL:UNIV7, REAL:UNIV8, BOOL:DRY_C1, BOOL:DRY_C2, BOOL:DRY_C3, BOOL:DRY_C4, BOOL:DRY_C5, BOOL:DRY_C6, BOOL:DRY_C7, BOOL:DRY_C8, REAL:OWB_T1, REAL:OWB_T2, REAL:OWB_T3, REAL:OWB_T4)" + } + } + ], + "SM_HOME": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_HOME", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "RELAY1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "RELAY8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "Q0_10V1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "Q0_10V4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "QOD4", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OPTO1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OPTO8", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "ADC1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC4", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC5", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC6", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC7", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "ADC8", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T1", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T2", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T3", + "type": "REAL", + "qualifier": "none" + }, + { + "name": "OWB_T4", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Get all inpust and set all outputs of a Sequent Microsystems Home Automation card", + "usage": "\n (SINT:STACK, BOOL:RELAY1, BOOL:RELAY2, BOOL:RELAY3, BOOL:RELAY4, BOOL:RELAY5, BOOL:RELAY6, BOOL:RELAY7, BOOL:RELAY8, REAL:Q0_10V1, REAL:Q0_10V2, REAL:Q0_10V3, REAL:Q0_10V4, REAL:QOD1, REAL:QOD2, REAL:QOD3, REAL:QOD4) => (BOOL:OPTO1, BOOL:OPTO2, BOOL:OPTO3, BOOL:OPTO4, BOOL:OPTO5, BOOL:OPTO6, BOOL:OPTO7, BOOL:OPTO8, REAL:ADC1, REAL:ADC2, REAL:ADC3, REAL:ADC4, REAL:ADC5, REAL:ADC6, REAL:ADC7, REAL:ADC8, REAL:OWB_T1, REAL:OWB_T2, REAL:OWB_T3, REAL:OWB_T4)" + } + } + ], + "SM_8MOSFET": [ + { + "section": "Sequent Microsystems Modules", + "infos": { + "name": "SM_8MOSFET", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "STACK", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "MOS1", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS2", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS3", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS4", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS5", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS6", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS7", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "MOS8", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [], + "comment": "Update all outputs from 8-mosfets card", + "usage": "\n (SINT:STACK, BOOL:MOS1, BOOL:MOS2, BOOL:MOS3, BOOL:MOS4, BOOL:MOS5, BOOL:MOS6, BOOL:MOS7, BOOL:MOS8) => ()" + } + } + ], + "ADC_CONFIG": [ + { + "section": "Jaguar", + "infos": { + "name": "ADC_CONFIG", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "ADC_CH", + "type": "SINT", + "qualifier": "none" + }, + { + "name": "ADC_TYPE", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "SUCCESS", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Configures the analog channel inputs on the Jaguar board. ADC_CH must be beween 0 - 3. ADC_TYPE must be between 0 - 3, where 0 = unipolar 10V, 1 = bipolar 10V, 2 = unipolar 5V, and 3 = bipolar 5V. Upon successfull configuration of the ADC, SUCCESS is set to TRUE.", + "usage": "\n (SINT:ADC_CH, SINT:ADC_TYPE) => (BOOL:SUCCESS)" + } + } + ], + "ROTARY_SWITCH": [ + { + "section": "SL-RP4", + "infos": { + "name": "ROTARY_SWITCH", + "type": "functionBlock", + "extensible": false, + "inputs": [ + { + "name": "READ", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "ERROR", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Reads the rotary switch position on SL-RP4 when the READ input is triggered. If ERROR is TRUE then an error occurred while trying to read the rotary switch. If ERROR is FALSE, the switch position value will be available on output OUT", + "usage": "\n (BOOL:READ) => (BOOL:ERROR, INT:OUT)" + } + } + ], + "BOOL_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (SINT:OUT)" + } + } + ], + "BOOL_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (INT:OUT)" + } + } + ], + "BOOL_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (DINT:OUT)" + } + } + ], + "BOOL_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (LINT:OUT)" + } + } + ], + "BOOL_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (USINT:OUT)" + } + } + ], + "BOOL_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (UINT:OUT)" + } + } + ], + "BOOL_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (UDINT:OUT)" + } + } + ], + "BOOL_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (ULINT:OUT)" + } + } + ], + "BOOL_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (REAL:OUT)" + } + } + ], + "BOOL_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (LREAL:OUT)" + } + } + ], + "BOOL_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (TIME:OUT)" + } + } + ], + "BOOL_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (DATE:OUT)" + } + } + ], + "BOOL_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (TOD:OUT)" + } + } + ], + "BOOL_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (DT:OUT)" + } + } + ], + "BOOL_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (STRING:OUT)" + } + } + ], + "BOOL_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (BYTE:OUT)" + } + } + ], + "BOOL_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (WORD:OUT)" + } + } + ], + "BOOL_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (DWORD:OUT)" + } + } + ], + "BOOL_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BOOL_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BOOL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BOOL:IN) => (LWORD:OUT)" + } + } + ], + "SINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (BOOL:OUT)" + } + } + ], + "SINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (INT:OUT)" + } + } + ], + "SINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (DINT:OUT)" + } + } + ], + "SINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (LINT:OUT)" + } + } + ], + "SINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (USINT:OUT)" + } + } + ], + "SINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (UINT:OUT)" + } + } + ], + "SINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (UDINT:OUT)" + } + } + ], + "SINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (ULINT:OUT)" + } + } + ], + "SINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (REAL:OUT)" + } + } + ], + "SINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (LREAL:OUT)" + } + } + ], + "SINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (TIME:OUT)" + } + } + ], + "SINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (DATE:OUT)" + } + } + ], + "SINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (TOD:OUT)" + } + } + ], + "SINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (DT:OUT)" + } + } + ], + "SINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (STRING:OUT)" + } + } + ], + "SINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (BYTE:OUT)" + } + } + ], + "SINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (WORD:OUT)" + } + } + ], + "SINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (DWORD:OUT)" + } + } + ], + "SINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "SINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "SINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (SINT:IN) => (LWORD:OUT)" + } + } + ], + "INT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (BOOL:OUT)" + } + } + ], + "INT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (SINT:OUT)" + } + } + ], + "INT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (DINT:OUT)" + } + } + ], + "INT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (LINT:OUT)" + } + } + ], + "INT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (USINT:OUT)" + } + } + ], + "INT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (UINT:OUT)" + } + } + ], + "INT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (UDINT:OUT)" + } + } + ], + "INT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (ULINT:OUT)" + } + } + ], + "INT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (REAL:OUT)" + } + } + ], + "INT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (LREAL:OUT)" + } + } + ], + "INT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (TIME:OUT)" + } + } + ], + "INT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (DATE:OUT)" + } + } + ], + "INT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (TOD:OUT)" + } + } + ], + "INT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (DT:OUT)" + } + } + ], + "INT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (STRING:OUT)" + } + } + ], + "INT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (BYTE:OUT)" + } + } + ], + "INT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (WORD:OUT)" + } + } + ], + "INT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (DWORD:OUT)" + } + } + ], + "INT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "INT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (INT:IN) => (LWORD:OUT)" + } + } + ], + "DINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (BOOL:OUT)" + } + } + ], + "DINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (SINT:OUT)" + } + } + ], + "DINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (INT:OUT)" + } + } + ], + "DINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (LINT:OUT)" + } + } + ], + "DINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (USINT:OUT)" + } + } + ], + "DINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (UINT:OUT)" + } + } + ], + "DINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (UDINT:OUT)" + } + } + ], + "DINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (ULINT:OUT)" + } + } + ], + "DINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (REAL:OUT)" + } + } + ], + "DINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (LREAL:OUT)" + } + } + ], + "DINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (TIME:OUT)" + } + } + ], + "DINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (DATE:OUT)" + } + } + ], + "DINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (TOD:OUT)" + } + } + ], + "DINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (DT:OUT)" + } + } + ], + "DINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (STRING:OUT)" + } + } + ], + "DINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (BYTE:OUT)" + } + } + ], + "DINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (WORD:OUT)" + } + } + ], + "DINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (DWORD:OUT)" + } + } + ], + "DINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DINT:IN) => (LWORD:OUT)" + } + } + ], + "LINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (BOOL:OUT)" + } + } + ], + "LINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (SINT:OUT)" + } + } + ], + "LINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (INT:OUT)" + } + } + ], + "LINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (DINT:OUT)" + } + } + ], + "LINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (USINT:OUT)" + } + } + ], + "LINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (UINT:OUT)" + } + } + ], + "LINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (UDINT:OUT)" + } + } + ], + "LINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (ULINT:OUT)" + } + } + ], + "LINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (REAL:OUT)" + } + } + ], + "LINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (LREAL:OUT)" + } + } + ], + "LINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (TIME:OUT)" + } + } + ], + "LINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (DATE:OUT)" + } + } + ], + "LINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (TOD:OUT)" + } + } + ], + "LINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (DT:OUT)" + } + } + ], + "LINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (STRING:OUT)" + } + } + ], + "LINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (BYTE:OUT)" + } + } + ], + "LINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (WORD:OUT)" + } + } + ], + "LINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (DWORD:OUT)" + } + } + ], + "LINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LINT:IN) => (LWORD:OUT)" + } + } + ], + "USINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (BOOL:OUT)" + } + } + ], + "USINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (SINT:OUT)" + } + } + ], + "USINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (INT:OUT)" + } + } + ], + "USINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (DINT:OUT)" + } + } + ], + "USINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (LINT:OUT)" + } + } + ], + "USINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (UINT:OUT)" + } + } + ], + "USINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (UDINT:OUT)" + } + } + ], + "USINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (ULINT:OUT)" + } + } + ], + "USINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (REAL:OUT)" + } + } + ], + "USINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (LREAL:OUT)" + } + } + ], + "USINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (TIME:OUT)" + } + } + ], + "USINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (DATE:OUT)" + } + } + ], + "USINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (TOD:OUT)" + } + } + ], + "USINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (DT:OUT)" + } + } + ], + "USINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (STRING:OUT)" + } + } + ], + "USINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (BYTE:OUT)" + } + } + ], + "USINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (WORD:OUT)" + } + } + ], + "USINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (DWORD:OUT)" + } + } + ], + "USINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (USINT:IN) => (LWORD:OUT)" + } + } + ], + "UINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (BOOL:OUT)" + } + } + ], + "UINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (SINT:OUT)" + } + } + ], + "UINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (INT:OUT)" + } + } + ], + "UINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (DINT:OUT)" + } + } + ], + "UINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (LINT:OUT)" + } + } + ], + "UINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (USINT:OUT)" + } + } + ], + "UINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (UDINT:OUT)" + } + } + ], + "UINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (ULINT:OUT)" + } + } + ], + "UINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (REAL:OUT)" + } + } + ], + "UINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (LREAL:OUT)" + } + } + ], + "UINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (TIME:OUT)" + } + } + ], + "UINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (DATE:OUT)" + } + } + ], + "UINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (TOD:OUT)" + } + } + ], + "UINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (DT:OUT)" + } + } + ], + "UINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (STRING:OUT)" + } + } + ], + "UINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (BYTE:OUT)" + } + } + ], + "UINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (WORD:OUT)" + } + } + ], + "UINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (DWORD:OUT)" + } + } + ], + "UINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UINT:IN) => (LWORD:OUT)" + } + } + ], + "UDINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (BOOL:OUT)" + } + } + ], + "UDINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (SINT:OUT)" + } + } + ], + "UDINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (INT:OUT)" + } + } + ], + "UDINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (DINT:OUT)" + } + } + ], + "UDINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (LINT:OUT)" + } + } + ], + "UDINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (USINT:OUT)" + } + } + ], + "UDINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (UINT:OUT)" + } + } + ], + "UDINT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (ULINT:OUT)" + } + } + ], + "UDINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (REAL:OUT)" + } + } + ], + "UDINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (LREAL:OUT)" + } + } + ], + "UDINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (TIME:OUT)" + } + } + ], + "UDINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (DATE:OUT)" + } + } + ], + "UDINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (TOD:OUT)" + } + } + ], + "UDINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (DT:OUT)" + } + } + ], + "UDINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (STRING:OUT)" + } + } + ], + "UDINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (BYTE:OUT)" + } + } + ], + "UDINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (WORD:OUT)" + } + } + ], + "UDINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (DWORD:OUT)" + } + } + ], + "UDINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (UDINT:IN) => (LWORD:OUT)" + } + } + ], + "ULINT_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (BOOL:OUT)" + } + } + ], + "ULINT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (SINT:OUT)" + } + } + ], + "ULINT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (INT:OUT)" + } + } + ], + "ULINT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (DINT:OUT)" + } + } + ], + "ULINT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (LINT:OUT)" + } + } + ], + "ULINT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (USINT:OUT)" + } + } + ], + "ULINT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (UINT:OUT)" + } + } + ], + "ULINT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (UDINT:OUT)" + } + } + ], + "ULINT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (REAL:OUT)" + } + } + ], + "ULINT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (LREAL:OUT)" + } + } + ], + "ULINT_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (TIME:OUT)" + } + } + ], + "ULINT_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (DATE:OUT)" + } + } + ], + "ULINT_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (TOD:OUT)" + } + } + ], + "ULINT_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (DT:OUT)" + } + } + ], + "ULINT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (STRING:OUT)" + } + } + ], + "ULINT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (BYTE:OUT)" + } + } + ], + "ULINT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (WORD:OUT)" + } + } + ], + "ULINT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (DWORD:OUT)" + } + } + ], + "ULINT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (ULINT:IN) => (LWORD:OUT)" + } + } + ], + "REAL_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (BOOL:OUT)" + } + } + ], + "REAL_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (SINT:OUT)" + } + } + ], + "REAL_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (INT:OUT)" + } + } + ], + "REAL_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (DINT:OUT)" + } + } + ], + "REAL_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (LINT:OUT)" + } + } + ], + "REAL_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (USINT:OUT)" + } + } + ], + "REAL_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (UINT:OUT)" + } + } + ], + "REAL_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (UDINT:OUT)" + } + } + ], + "REAL_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (ULINT:OUT)" + } + } + ], + "REAL_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (LREAL:OUT)" + } + } + ], + "REAL_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (TIME:OUT)" + } + } + ], + "REAL_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (DATE:OUT)" + } + } + ], + "REAL_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (TOD:OUT)" + } + } + ], + "REAL_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (DT:OUT)" + } + } + ], + "REAL_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (STRING:OUT)" + } + } + ], + "REAL_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (BYTE:OUT)" + } + } + ], + "REAL_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (WORD:OUT)" + } + } + ], + "REAL_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (DWORD:OUT)" + } + } + ], + "REAL_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "REAL_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (REAL:IN) => (LWORD:OUT)" + } + } + ], + "LREAL_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (BOOL:OUT)" + } + } + ], + "LREAL_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (SINT:OUT)" + } + } + ], + "LREAL_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (INT:OUT)" + } + } + ], + "LREAL_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (DINT:OUT)" + } + } + ], + "LREAL_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (LINT:OUT)" + } + } + ], + "LREAL_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (USINT:OUT)" + } + } + ], + "LREAL_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (UINT:OUT)" + } + } + ], + "LREAL_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (UDINT:OUT)" + } + } + ], + "LREAL_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (ULINT:OUT)" + } + } + ], + "LREAL_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (REAL:OUT)" + } + } + ], + "LREAL_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (TIME:OUT)" + } + } + ], + "LREAL_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (DATE:OUT)" + } + } + ], + "LREAL_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (TOD:OUT)" + } + } + ], + "LREAL_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (DT:OUT)" + } + } + ], + "LREAL_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (STRING:OUT)" + } + } + ], + "LREAL_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (BYTE:OUT)" + } + } + ], + "LREAL_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (WORD:OUT)" + } + } + ], + "LREAL_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (DWORD:OUT)" + } + } + ], + "LREAL_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LREAL_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LREAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LREAL:IN) => (LWORD:OUT)" + } + } + ], + "TIME_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (SINT:OUT)" + } + } + ], + "TIME_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (INT:OUT)" + } + } + ], + "TIME_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (DINT:OUT)" + } + } + ], + "TIME_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (LINT:OUT)" + } + } + ], + "TIME_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (USINT:OUT)" + } + } + ], + "TIME_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (UINT:OUT)" + } + } + ], + "TIME_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (UDINT:OUT)" + } + } + ], + "TIME_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (ULINT:OUT)" + } + } + ], + "TIME_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (REAL:OUT)" + } + } + ], + "TIME_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (LREAL:OUT)" + } + } + ], + "TIME_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (STRING:OUT)" + } + } + ], + "TIME_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (BYTE:OUT)" + } + } + ], + "TIME_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (WORD:OUT)" + } + } + ], + "TIME_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (DWORD:OUT)" + } + } + ], + "TIME_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TIME_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TIME:IN) => (LWORD:OUT)" + } + } + ], + "DATE_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (SINT:OUT)" + } + } + ], + "DATE_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (INT:OUT)" + } + } + ], + "DATE_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (DINT:OUT)" + } + } + ], + "DATE_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (LINT:OUT)" + } + } + ], + "DATE_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (USINT:OUT)" + } + } + ], + "DATE_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (UINT:OUT)" + } + } + ], + "DATE_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (UDINT:OUT)" + } + } + ], + "DATE_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (ULINT:OUT)" + } + } + ], + "DATE_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (REAL:OUT)" + } + } + ], + "DATE_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (LREAL:OUT)" + } + } + ], + "DATE_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (STRING:OUT)" + } + } + ], + "DATE_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (BYTE:OUT)" + } + } + ], + "DATE_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (WORD:OUT)" + } + } + ], + "DATE_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (DWORD:OUT)" + } + } + ], + "DATE_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DATE:IN) => (LWORD:OUT)" + } + } + ], + "TOD_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (SINT:OUT)" + } + } + ], + "TOD_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (INT:OUT)" + } + } + ], + "TOD_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (DINT:OUT)" + } + } + ], + "TOD_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (LINT:OUT)" + } + } + ], + "TOD_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (USINT:OUT)" + } + } + ], + "TOD_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (UINT:OUT)" + } + } + ], + "TOD_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (UDINT:OUT)" + } + } + ], + "TOD_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (ULINT:OUT)" + } + } + ], + "TOD_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (REAL:OUT)" + } + } + ], + "TOD_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (LREAL:OUT)" + } + } + ], + "TOD_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (STRING:OUT)" + } + } + ], + "TOD_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (BYTE:OUT)" + } + } + ], + "TOD_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (WORD:OUT)" + } + } + ], + "TOD_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (DWORD:OUT)" + } + } + ], + "TOD_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "TOD_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (TOD:IN) => (LWORD:OUT)" + } + } + ], + "DT_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (SINT:OUT)" + } + } + ], + "DT_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (INT:OUT)" + } + } + ], + "DT_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (DINT:OUT)" + } + } + ], + "DT_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (LINT:OUT)" + } + } + ], + "DT_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (USINT:OUT)" + } + } + ], + "DT_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (UINT:OUT)" + } + } + ], + "DT_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (UDINT:OUT)" + } + } + ], + "DT_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (ULINT:OUT)" + } + } + ], + "DT_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (REAL:OUT)" + } + } + ], + "DT_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (LREAL:OUT)" + } + } + ], + "DT_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (STRING:OUT)" + } + } + ], + "DT_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (BYTE:OUT)" + } + } + ], + "DT_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (WORD:OUT)" + } + } + ], + "DT_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (DWORD:OUT)" + } + } + ], + "DT_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DT_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DT:IN) => (LWORD:OUT)" + } + } + ], + "STRING_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (BOOL:OUT)" + } + } + ], + "STRING_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (SINT:OUT)" + } + } + ], + "STRING_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (INT:OUT)" + } + } + ], + "STRING_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (DINT:OUT)" + } + } + ], + "STRING_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (LINT:OUT)" + } + } + ], + "STRING_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (USINT:OUT)" + } + } + ], + "STRING_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (UINT:OUT)" + } + } + ], + "STRING_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (UDINT:OUT)" + } + } + ], + "STRING_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (ULINT:OUT)" + } + } + ], + "STRING_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (REAL:OUT)" + } + } + ], + "STRING_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (LREAL:OUT)" + } + } + ], + "STRING_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (TIME:OUT)" + } + } + ], + "STRING_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (DATE:OUT)" + } + } + ], + "STRING_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (TOD:OUT)" + } + } + ], + "STRING_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (DT:OUT)" + } + } + ], + "STRING_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (BYTE:OUT)" + } + } + ], + "STRING_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (WORD:OUT)" + } + } + ], + "STRING_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (DWORD:OUT)" + } + } + ], + "STRING_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "STRING_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (STRING:IN) => (LWORD:OUT)" + } + } + ], + "BYTE_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (BOOL:OUT)" + } + } + ], + "BYTE_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (SINT:OUT)" + } + } + ], + "BYTE_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (INT:OUT)" + } + } + ], + "BYTE_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (DINT:OUT)" + } + } + ], + "BYTE_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (LINT:OUT)" + } + } + ], + "BYTE_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (USINT:OUT)" + } + } + ], + "BYTE_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (UINT:OUT)" + } + } + ], + "BYTE_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (UDINT:OUT)" + } + } + ], + "BYTE_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (ULINT:OUT)" + } + } + ], + "BYTE_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (REAL:OUT)" + } + } + ], + "BYTE_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (LREAL:OUT)" + } + } + ], + "BYTE_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (TIME:OUT)" + } + } + ], + "BYTE_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (DATE:OUT)" + } + } + ], + "BYTE_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (TOD:OUT)" + } + } + ], + "BYTE_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (DT:OUT)" + } + } + ], + "BYTE_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (STRING:OUT)" + } + } + ], + "BYTE_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (WORD:OUT)" + } + } + ], + "BYTE_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (DWORD:OUT)" + } + } + ], + "BYTE_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "BYTE_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (BYTE:IN) => (LWORD:OUT)" + } + } + ], + "WORD_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (BOOL:OUT)" + } + } + ], + "WORD_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (SINT:OUT)" + } + } + ], + "WORD_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (INT:OUT)" + } + } + ], + "WORD_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (DINT:OUT)" + } + } + ], + "WORD_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (LINT:OUT)" + } + } + ], + "WORD_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (USINT:OUT)" + } + } + ], + "WORD_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (UINT:OUT)" + } + } + ], + "WORD_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (UDINT:OUT)" + } + } + ], + "WORD_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (ULINT:OUT)" + } + } + ], + "WORD_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (REAL:OUT)" + } + } + ], + "WORD_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (LREAL:OUT)" + } + } + ], + "WORD_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (TIME:OUT)" + } + } + ], + "WORD_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (DATE:OUT)" + } + } + ], + "WORD_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (TOD:OUT)" + } + } + ], + "WORD_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (DT:OUT)" + } + } + ], + "WORD_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (STRING:OUT)" + } + } + ], + "WORD_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (BYTE:OUT)" + } + } + ], + "WORD_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (DWORD:OUT)" + } + } + ], + "WORD_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "WORD_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (WORD:IN) => (LWORD:OUT)" + } + } + ], + "DWORD_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (BOOL:OUT)" + } + } + ], + "DWORD_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (SINT:OUT)" + } + } + ], + "DWORD_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (INT:OUT)" + } + } + ], + "DWORD_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (DINT:OUT)" + } + } + ], + "DWORD_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (LINT:OUT)" + } + } + ], + "DWORD_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (USINT:OUT)" + } + } + ], + "DWORD_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (UINT:OUT)" + } + } + ], + "DWORD_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (UDINT:OUT)" + } + } + ], + "DWORD_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (ULINT:OUT)" + } + } + ], + "DWORD_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (REAL:OUT)" + } + } + ], + "DWORD_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (LREAL:OUT)" + } + } + ], + "DWORD_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (TIME:OUT)" + } + } + ], + "DWORD_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (DATE:OUT)" + } + } + ], + "DWORD_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (TOD:OUT)" + } + } + ], + "DWORD_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (DT:OUT)" + } + } + ], + "DWORD_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (STRING:OUT)" + } + } + ], + "DWORD_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (BYTE:OUT)" + } + } + ], + "DWORD_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (WORD:OUT)" + } + } + ], + "DWORD_TO_LWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "DWORD_TO_LWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (DWORD:IN) => (LWORD:OUT)" + } + } + ], + "LWORD_TO_BOOL": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_BOOL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (BOOL:OUT)" + } + } + ], + "LWORD_TO_SINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_SINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "SINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (SINT:OUT)" + } + } + ], + "LWORD_TO_INT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_INT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (INT:OUT)" + } + } + ], + "LWORD_TO_DINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_DINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (DINT:OUT)" + } + } + ], + "LWORD_TO_LINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_LINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (LINT:OUT)" + } + } + ], + "LWORD_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (USINT:OUT)" + } + } + ], + "LWORD_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (UINT:OUT)" + } + } + ], + "LWORD_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (UDINT:OUT)" + } + } + ], + "LWORD_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (ULINT:OUT)" + } + } + ], + "LWORD_TO_REAL": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_REAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "REAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (REAL:OUT)" + } + } + ], + "LWORD_TO_LREAL": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_LREAL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LREAL", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (LREAL:OUT)" + } + } + ], + "LWORD_TO_TIME": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (TIME:OUT)" + } + } + ], + "LWORD_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (DATE:OUT)" + } + } + ], + "LWORD_TO_TOD": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (TOD:OUT)" + } + } + ], + "LWORD_TO_DT": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (DT:OUT)" + } + } + ], + "LWORD_TO_STRING": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_STRING", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (STRING:OUT)" + } + } + ], + "LWORD_TO_BYTE": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_BYTE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (BYTE:OUT)" + } + } + ], + "LWORD_TO_WORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_WORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (WORD:OUT)" + } + } + ], + "LWORD_TO_DWORD": [ + { + "section": "Type conversion", + "infos": { + "name": "LWORD_TO_DWORD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Data type conversion", + "usage": "\n (LWORD:IN) => (DWORD:OUT)" + } + } + ], + "TRUNC": [ + { + "section": "Type conversion", + "infos": { + "name": "TRUNC", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "comment": "Rounding up/down", + "usage": "\n (ANY_REAL:IN) => (ANY_INT:OUT)" + } + } + ], + "BCD_TO_USINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BCD_TO_USINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "BYTE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "USINT", + "qualifier": "none" + } + ], + "comment": "Conversion from BCD", + "usage": "\n (BYTE:IN) => (USINT:OUT)" + } + } + ], + "BCD_TO_UINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BCD_TO_UINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "WORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UINT", + "qualifier": "none" + } + ], + "comment": "Conversion from BCD", + "usage": "\n (WORD:IN) => (UINT:OUT)" + } + } + ], + "BCD_TO_UDINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BCD_TO_UDINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "UDINT", + "qualifier": "none" + } + ], + "comment": "Conversion from BCD", + "usage": "\n (DWORD:IN) => (UDINT:OUT)" + } + } + ], + "BCD_TO_ULINT": [ + { + "section": "Type conversion", + "infos": { + "name": "BCD_TO_ULINT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "LWORD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ULINT", + "qualifier": "none" + } + ], + "comment": "Conversion from BCD", + "usage": "\n (LWORD:IN) => (ULINT:OUT)" + } + } + ], + "USINT_TO_BCD": [ + { + "section": "Type conversion", + "infos": { + "name": "USINT_TO_BCD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "USINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BYTE", + "qualifier": "none" + } + ], + "comment": "Conversion to BCD", + "usage": "\n (USINT:IN) => (BYTE:OUT)" + } + } + ], + "UINT_TO_BCD": [ + { + "section": "Type conversion", + "infos": { + "name": "UINT_TO_BCD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "WORD", + "qualifier": "none" + } + ], + "comment": "Conversion to BCD", + "usage": "\n (UINT:IN) => (WORD:OUT)" + } + } + ], + "UDINT_TO_BCD": [ + { + "section": "Type conversion", + "infos": { + "name": "UDINT_TO_BCD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "UDINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DWORD", + "qualifier": "none" + } + ], + "comment": "Conversion to BCD", + "usage": "\n (UDINT:IN) => (DWORD:OUT)" + } + } + ], + "ULINT_TO_BCD": [ + { + "section": "Type conversion", + "infos": { + "name": "ULINT_TO_BCD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ULINT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "LWORD", + "qualifier": "none" + } + ], + "comment": "Conversion to BCD", + "usage": "\n (ULINT:IN) => (LWORD:OUT)" + } + } + ], + "DATE_AND_TIME_TO_TIME_OF_DAY": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_AND_TIME_TO_TIME_OF_DAY", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Conversion to time-of-day", + "usage": "\n (DT:IN) => (TOD:OUT)" + } + } + ], + "DATE_AND_TIME_TO_DATE": [ + { + "section": "Type conversion", + "infos": { + "name": "DATE_AND_TIME_TO_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DATE", + "qualifier": "none" + } + ], + "comment": "Conversion to date", + "usage": "\n (DT:IN) => (DATE:OUT)" + } + } + ], + "ABS": [ + { + "section": "Numerical", + "infos": { + "name": "ABS", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "comment": "Absolute number", + "usage": "\n (ANY_NUM:IN) => (ANY_NUM:OUT)" + } + } + ], + "SQRT": [ + { + "section": "Numerical", + "infos": { + "name": "SQRT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Square root (base 2)", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "LN": [ + { + "section": "Numerical", + "infos": { + "name": "LN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Natural logarithm", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "LOG": [ + { + "section": "Numerical", + "infos": { + "name": "LOG", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Logarithm to base 10", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "EXP": [ + { + "section": "Numerical", + "infos": { + "name": "EXP", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Exponentiation", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "SIN": [ + { + "section": "Numerical", + "infos": { + "name": "SIN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Sine", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "COS": [ + { + "section": "Numerical", + "infos": { + "name": "COS", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Cosine", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "TAN": [ + { + "section": "Numerical", + "infos": { + "name": "TAN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Tangent", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "ASIN": [ + { + "section": "Numerical", + "infos": { + "name": "ASIN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Arc sine", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "ACOS": [ + { + "section": "Numerical", + "infos": { + "name": "ACOS", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Arc cosine", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "ATAN": [ + { + "section": "Numerical", + "infos": { + "name": "ATAN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Arc tangent", + "usage": "\n (ANY_REAL:IN) => (ANY_REAL:OUT)" + } + } + ], + "ADD": [ + { + "section": "Arithmetic", + "infos": { + "name": "ADD", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY_NUM", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "comment": "Addition", + "usage": "\n (ANY_NUM:IN1, ANY_NUM:IN2) => (ANY_NUM:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "ADD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time addition", + "usage": "\n (TIME:IN1, TIME:IN2) => (TIME:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "ADD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Time-of-day addition", + "usage": "\n (TOD:IN1, TIME:IN2) => (TOD:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "ADD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Date addition", + "usage": "\n (DT:IN1, TIME:IN2) => (DT:OUT)" + } + } + ], + "MUL": [ + { + "section": "Arithmetic", + "infos": { + "name": "MUL", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY_NUM", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "comment": "Multiplication", + "usage": "\n (ANY_NUM:IN1, ANY_NUM:IN2) => (ANY_NUM:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "MUL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time multiplication", + "usage": "\n (TIME:IN1, ANY_NUM:IN2) => (TIME:OUT)" + } + } + ], + "SUB": [ + { + "section": "Arithmetic", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "ANY_NUM", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "comment": "Subtraction", + "usage": "\n (ANY_NUM:IN1, ANY_NUM:IN2) => (ANY_NUM:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time subtraction", + "usage": "\n (TIME:IN1, TIME:IN2) => (TIME:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DATE", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Date subtraction", + "usage": "\n (DATE:IN1, DATE:IN2) => (TIME:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Time-of-day subtraction", + "usage": "\n (TOD:IN1, TIME:IN2) => (TOD:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time-of-day subtraction", + "usage": "\n (TOD:IN1, TOD:IN2) => (TIME:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Date and time subtraction", + "usage": "\n (DT:IN1, TIME:IN2) => (DT:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "SUB", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Date and time subtraction", + "usage": "\n (DT:IN1, DT:IN2) => (TIME:OUT)" + } + } + ], + "DIV": [ + { + "section": "Arithmetic", + "infos": { + "name": "DIV", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "ANY_NUM", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "comment": "Division", + "usage": "\n (ANY_NUM:IN1, ANY_NUM:IN2) => (ANY_NUM:OUT)" + } + }, + { + "section": "Time", + "infos": { + "name": "DIV", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time division", + "usage": "\n (TIME:IN1, ANY_NUM:IN2) => (TIME:OUT)" + } + } + ], + "MOD": [ + { + "section": "Arithmetic", + "infos": { + "name": "MOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "ANY_INT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "comment": "Remainder (modulo)", + "usage": "\n (ANY_INT:IN1, ANY_INT:IN2) => (ANY_INT:OUT)" + } + } + ], + "EXPT": [ + { + "section": "Arithmetic", + "infos": { + "name": "EXPT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "ANY_REAL", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_REAL", + "qualifier": "none" + } + ], + "comment": "Exponent", + "usage": "\n (ANY_REAL:IN1, ANY_NUM:IN2) => (ANY_REAL:OUT)" + } + } + ], + "MOVE": [ + { + "section": "Arithmetic", + "infos": { + "name": "MOVE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Assignment", + "usage": "\n (ANY:IN) => (ANY:OUT)" + } + } + ], + "ADD_TIME": [ + { + "section": "Time", + "infos": { + "name": "ADD_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time addition", + "usage": "\n (TIME:IN1, TIME:IN2) => (TIME:OUT)" + } + } + ], + "ADD_TOD_TIME": [ + { + "section": "Time", + "infos": { + "name": "ADD_TOD_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Time-of-day addition", + "usage": "\n (TOD:IN1, TIME:IN2) => (TOD:OUT)" + } + } + ], + "ADD_DT_TIME": [ + { + "section": "Time", + "infos": { + "name": "ADD_DT_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Date addition", + "usage": "\n (DT:IN1, TIME:IN2) => (DT:OUT)" + } + } + ], + "MULTIME": [ + { + "section": "Time", + "infos": { + "name": "MULTIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time multiplication", + "usage": "\n (TIME:IN1, ANY_NUM:IN2) => (TIME:OUT)" + } + } + ], + "SUB_TIME": [ + { + "section": "Time", + "infos": { + "name": "SUB_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time subtraction", + "usage": "\n (TIME:IN1, TIME:IN2) => (TIME:OUT)" + } + } + ], + "SUB_DATE_DATE": [ + { + "section": "Time", + "infos": { + "name": "SUB_DATE_DATE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DATE", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "DATE", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Date subtraction", + "usage": "\n (DATE:IN1, DATE:IN2) => (TIME:OUT)" + } + } + ], + "SUB_TOD_TIME": [ + { + "section": "Time", + "infos": { + "name": "SUB_TOD_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TOD", + "qualifier": "none" + } + ], + "comment": "Time-of-day subtraction", + "usage": "\n (TOD:IN1, TIME:IN2) => (TOD:OUT)" + } + } + ], + "SUB_TOD_TOD": [ + { + "section": "Time", + "infos": { + "name": "SUB_TOD_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TOD", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time-of-day subtraction", + "usage": "\n (TOD:IN1, TOD:IN2) => (TIME:OUT)" + } + } + ], + "SUB_DT_TIME": [ + { + "section": "Time", + "infos": { + "name": "SUB_DT_TIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TIME", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Date and time subtraction", + "usage": "\n (DT:IN1, TIME:IN2) => (DT:OUT)" + } + } + ], + "SUB_DT_DT": [ + { + "section": "Time", + "infos": { + "name": "SUB_DT_DT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "DT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Date and time subtraction", + "usage": "\n (DT:IN1, DT:IN2) => (TIME:OUT)" + } + } + ], + "DIVTIME": [ + { + "section": "Time", + "infos": { + "name": "DIVTIME", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "TIME", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_NUM", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "TIME", + "qualifier": "none" + } + ], + "comment": "Time division", + "usage": "\n (TIME:IN1, ANY_NUM:IN2) => (TIME:OUT)" + } + } + ], + "SHL": [ + { + "section": "Bit-shift", + "infos": { + "name": "SHL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_BIT", + "qualifier": "none" + }, + { + "name": "N", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Shift left", + "usage": "\n (ANY_BIT:IN, ANY_INT:N) => (ANY_BIT:OUT)" + } + } + ], + "SHR": [ + { + "section": "Bit-shift", + "infos": { + "name": "SHR", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_BIT", + "qualifier": "none" + }, + { + "name": "N", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Shift right", + "usage": "\n (ANY_BIT:IN, ANY_INT:N) => (ANY_BIT:OUT)" + } + } + ], + "ROR": [ + { + "section": "Bit-shift", + "infos": { + "name": "ROR", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_NBIT", + "qualifier": "none" + }, + { + "name": "N", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NBIT", + "qualifier": "none" + } + ], + "comment": "Rotate right", + "usage": "\n (ANY_NBIT:IN, ANY_INT:N) => (ANY_NBIT:OUT)" + } + } + ], + "ROL": [ + { + "section": "Bit-shift", + "infos": { + "name": "ROL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_NBIT", + "qualifier": "none" + }, + { + "name": "N", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_NBIT", + "qualifier": "none" + } + ], + "comment": "Rotate left", + "usage": "\n (ANY_NBIT:IN, ANY_INT:N) => (ANY_NBIT:OUT)" + } + } + ], + "AND": [ + { + "section": "Bitwise", + "infos": { + "name": "AND", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY_BIT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Bitwise AND", + "usage": "\n (ANY_BIT:IN1, ANY_BIT:IN2) => (ANY_BIT:OUT)" + } + } + ], + "OR": [ + { + "section": "Bitwise", + "infos": { + "name": "OR", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY_BIT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Bitwise OR", + "usage": "\n (ANY_BIT:IN1, ANY_BIT:IN2) => (ANY_BIT:OUT)" + } + } + ], + "XOR": [ + { + "section": "Bitwise", + "infos": { + "name": "XOR", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY_BIT", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Bitwise XOR", + "usage": "\n (ANY_BIT:IN1, ANY_BIT:IN2) => (ANY_BIT:OUT)" + } + } + ], + "NOT": [ + { + "section": "Bitwise", + "infos": { + "name": "NOT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY_BIT", + "qualifier": "none" + } + ], + "comment": "Bitwise inverting", + "usage": "\n (ANY_BIT:IN) => (ANY_BIT:OUT)" + } + } + ], + "SEL": [ + { + "section": "Selection", + "infos": { + "name": "SEL", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "G", + "type": "BOOL", + "qualifier": "none" + }, + { + "name": "IN0", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Binary selection (1 of 2)", + "usage": "\n (BOOL:G, ANY:IN0, ANY:IN1) => (ANY:OUT)" + } + } + ], + "MAX": [ + { + "section": "Selection", + "infos": { + "name": "MAX", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Maximum", + "usage": "\n (ANY:IN1, ANY:IN2) => (ANY:OUT)" + } + } + ], + "MIN": [ + { + "section": "Selection", + "infos": { + "name": "MIN", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Minimum", + "usage": "\n (ANY:IN1, ANY:IN2) => (ANY:OUT)" + } + } + ], + "LIMIT": [ + { + "section": "Selection", + "infos": { + "name": "LIMIT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "MN", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "MX", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Limitation", + "usage": "\n (ANY:MN, ANY:IN, ANY:MX) => (ANY:OUT)" + } + } + ], + "MUX": [ + { + "section": "Selection", + "infos": { + "name": "MUX", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "K", + "type": "ANY_INT", + "qualifier": "none" + }, + { + "name": "IN0", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "ANY", + "qualifier": "none" + } + ], + "comment": "Multiplexer (select 1 of N)", + "usage": "\n (ANY_INT:K, ANY:IN0, ANY:IN1) => (ANY:OUT)" + } + } + ], + "GT": [ + { + "section": "Comparison", + "infos": { + "name": "GT", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Greater than", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "GE": [ + { + "section": "Comparison", + "infos": { + "name": "GE", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Greater than or equal to", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "EQ": [ + { + "section": "Comparison", + "infos": { + "name": "EQ", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Equal to", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "LT": [ + { + "section": "Comparison", + "infos": { + "name": "LT", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Less than", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "LE": [ + { + "section": "Comparison", + "infos": { + "name": "LE", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Less than or equal to", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "NE": [ + { + "section": "Comparison", + "infos": { + "name": "NE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "ANY", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "ANY", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "BOOL", + "qualifier": "none" + } + ], + "comment": "Not equal to", + "usage": "\n (ANY:IN1, ANY:IN2) => (BOOL:OUT)" + } + } + ], + "LEN": [ + { + "section": "Character string", + "infos": { + "name": "LEN", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Length of string", + "usage": "\n (STRING:IN) => (INT:OUT)" + } + } + ], + "LEFT": [ + { + "section": "Character string", + "infos": { + "name": "LEFT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "L", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "string left of", + "usage": "\n (STRING:IN, ANY_INT:L) => (STRING:OUT)" + } + } + ], + "RIGHT": [ + { + "section": "Character string", + "infos": { + "name": "RIGHT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "L", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "string right of", + "usage": "\n (STRING:IN, ANY_INT:L) => (STRING:OUT)" + } + } + ], + "MID": [ + { + "section": "Character string", + "infos": { + "name": "MID", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "L", + "type": "ANY_INT", + "qualifier": "none" + }, + { + "name": "P", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "string from the middle", + "usage": "\n (STRING:IN, ANY_INT:L, ANY_INT:P) => (STRING:OUT)" + } + } + ], + "CONCAT": [ + { + "section": "Character string", + "infos": { + "name": "CONCAT", + "type": "function", + "extensible": true, + "inputs": [ + { + "name": "IN1", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Concatenation", + "usage": "\n (STRING:IN1, STRING:IN2) => (STRING:OUT)" + } + } + ], + "CONCAT_DATE_TOD": [ + { + "section": "Character string", + "infos": { + "name": "CONCAT_DATE_TOD", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "DATE", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "TOD", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "DT", + "qualifier": "none" + } + ], + "comment": "Time concatenation", + "usage": "\n (DATE:IN1, TOD:IN2) => (DT:OUT)" + } + } + ], + "INSERT": [ + { + "section": "Character string", + "infos": { + "name": "INSERT", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "P", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Insertion (into)", + "usage": "\n (STRING:IN1, STRING:IN2, ANY_INT:P) => (STRING:OUT)" + } + } + ], + "DELETE": [ + { + "section": "Character string", + "infos": { + "name": "DELETE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "L", + "type": "ANY_INT", + "qualifier": "none" + }, + { + "name": "P", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Deletion (within)", + "usage": "\n (STRING:IN, ANY_INT:L, ANY_INT:P) => (STRING:OUT)" + } + } + ], + "REPLACE": [ + { + "section": "Character string", + "infos": { + "name": "REPLACE", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "L", + "type": "ANY_INT", + "qualifier": "none" + }, + { + "name": "P", + "type": "ANY_INT", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "STRING", + "qualifier": "none" + } + ], + "comment": "Replacement (within)", + "usage": "\n (STRING:IN1, STRING:IN2, ANY_INT:L, ANY_INT:P) => (STRING:OUT)" + } + } + ], + "FIND": [ + { + "section": "Character string", + "infos": { + "name": "FIND", + "type": "function", + "extensible": false, + "inputs": [ + { + "name": "IN1", + "type": "STRING", + "qualifier": "none" + }, + { + "name": "IN2", + "type": "STRING", + "qualifier": "none" + } + ], + "outputs": [ + { + "name": "OUT", + "type": "INT", + "qualifier": "none" + } + ], + "comment": "Find position", + "usage": "\n (STRING:IN1, STRING:IN2) => (INT:OUT)" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/shared/transpilers/generate-st-from-json/from-schema.ts b/src/backend/shared/transpilers/generate-st-from-json/from-schema.ts new file mode 100644 index 000000000..5722e2608 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/from-schema.ts @@ -0,0 +1,207 @@ +/** + * Project the editor's IPC schema-shape `PLCProjectData` + * (`backend/shared/types/PLC/open-plc.ts`, z.infer of + * `PLCProjectDataSchema`) into the transpiler's minimal IR. + * + * Lives alongside the transpiler because both types come from + * `backend/shared/` — no layer violation. The port-shape adapter + * lives outside (under `middleware/adapters/`) because port-shape + * types are middleware-scoped. + */ + +import { fbdToXml } from '../../../../frontend/utils/PLC/xml-generator/old-editor/language/fbd-xml' +import { ladderToXml } from '../../../../frontend/utils/PLC/xml-generator/old-editor/language/ladder-xml' +import type { PLCProjectData as SchemaPLCProjectData } from '../../types/PLC/open-plc' +import { xmlObjectToLdBody } from './ld/from-xmlbuilder' +import type { + TranspileBody, + TranspileDataType, + TranspileInstance, + TranspilePou, + TranspileProject, + TranspileTask, + TranspileVariable, + TranspileVariableType, +} from './types' + +export type SchemaProjectData = SchemaPLCProjectData + +export function fromSchemaShape(data: SchemaPLCProjectData): TranspileProject { + return { + pous: data.pous.map(projectPou), + dataTypes: (data.dataTypes ?? []).map(projectDataType), + configuration: { + tasks: (data.configuration?.resource?.tasks ?? []).map(projectTask), + instances: (data.configuration?.resource?.instances ?? []).map(projectInstance), + globalVariables: (data.configuration?.resource?.globalVariables ?? []).map((v) => + projectVariable(v as SchemaVariable), + ), + }, + } +} + +type SchemaPou = SchemaPLCProjectData['pous'][number] +type SchemaVariable = NonNullable[number] +type SchemaDataType = NonNullable[number] +type SchemaStructureVariable = Extract['variable'][number] +type SchemaTask = SchemaPLCProjectData['configuration']['resource']['tasks'][number] +type SchemaInstance = SchemaPLCProjectData['configuration']['resource']['instances'][number] +type SchemaBody = SchemaPou['data']['body'] + +function projectPou(pou: SchemaPou): TranspilePou { + const variables = (pou.data.variables ?? []).map(projectVariable) + return { + name: pou.data.name, + pouType: pou.type, + documentation: pou.data.documentation ?? '', + interface: { + variables, + ...(pou.type === 'function' + ? { returnType: stringifyReturnType(pou.data.returnType) } + : {}), + }, + body: projectBody(pou.data.body), + } +} + +function projectBody(body: SchemaBody): TranspileBody { + switch (body.language) { + case 'st': + case 'il': + case 'python': + case 'cpp': + return { language: body.language, value: body.value } + case 'ld': { + const xmlBody = ladderToXml( + body.value.rungs as Parameters[0], + ) as unknown as Record + return { language: 'ld', xmlBody, ldBody: xmlObjectToLdBody(xmlBody) } + } + case 'fbd': { + const xmlBody = fbdToXml( + body.value.rung as Parameters[0], + ) as unknown as Record + return { language: 'fbd', xmlBody, ldBody: xmlObjectToLdBody(xmlBody) } + } + case 'sfc': + // Schema's SFC body is currently typed as `string`; fall back + // to ST passthrough until the SFC pipeline ports JSON-native. + return { language: 'st', value: body.value } + default: { + const unreachable: never = body + throw new Error(`Unknown body language: ${JSON.stringify(unreachable)}`) + } + } +} + +function projectVariable(v: SchemaVariable): TranspileVariable { + return { + name: v.name, + type: projectVariableType(v.type), + ...(v.class !== undefined ? { class: normalizeVarClass(v.class) } : {}), + ...(v.location !== undefined && v.location !== '' ? { location: v.location } : {}), + ...(v.initialValue !== undefined && v.initialValue !== null && v.initialValue !== '' + ? { initialValue: v.initialValue } + : {}), + ...(v.documentation !== undefined && v.documentation !== '' + ? { documentation: v.documentation } + : {}), + } +} + +function normalizeVarClass(cls: NonNullable): TranspileVariable['class'] { + // Schema's variable class includes 'global'; the IR doesn't (global + // vars live under configuration.globalVariables, not in a POU + // interface). Collapse to 'local' for the projection — matches the + // port-shape adapter's normalisation. + if (cls === 'global') return 'local' + return cls +} + +function projectStructureVariable(v: SchemaStructureVariable): TranspileVariable { + const initial = v.initialValue?.simpleValue?.value + return { + name: v.name, + type: projectStructureVariableType(v.type), + ...(initial !== undefined && initial !== '' ? { initialValue: initial } : {}), + } +} + +function projectVariableType(type: SchemaVariable['type']): TranspileVariableType { + if (type.definition === 'array') { + return { + definition: 'array', + data: { + dimensions: type.data.dimensions.map((d) => ({ dimension: d.dimension })), + baseType: { value: type.data.baseType.value }, + }, + } + } + if (type.definition === 'derived' || type.definition === 'user-data-type') { + return { definition: type.definition, value: type.value } + } + return { definition: 'base-type', value: type.value } +} + +function projectStructureVariableType(type: SchemaStructureVariable['type']): TranspileVariableType { + if (type.definition === 'array') { + return { + definition: 'array', + data: { + dimensions: type.data.dimensions.map((d) => ({ dimension: d.dimension })), + baseType: { value: type.data.baseType.value }, + }, + } + } + if (type.definition === 'derived' || type.definition === 'user-data-type') { + return { definition: type.definition, value: type.value } + } + return { definition: 'base-type', value: type.value } +} + +function projectDataType(dt: SchemaDataType): TranspileDataType { + if (dt.derivation === 'array') { + return { + name: dt.name, + derivation: 'array', + dimensions: dt.dimensions.map((d) => ({ dimension: d.dimension })), + baseType: { value: dt.baseType.value }, + ...(dt.initialValue ? { initialValue: dt.initialValue } : {}), + } + } + if (dt.derivation === 'enumerated') { + return { + name: dt.name, + derivation: 'enumerated', + values: dt.values.map((v) => ({ description: v.description })), + ...(dt.initialValue ? { initialValue: dt.initialValue } : {}), + } + } + // structure + return { + name: dt.name, + derivation: 'structure', + variable: dt.variable.map(projectStructureVariable), + } +} + +function projectTask(task: SchemaTask): TranspileTask { + return { + name: task.name, + priority: task.priority, + triggering: task.triggering, + ...(task.triggering === 'Cyclic' ? { interval: task.interval } : { single: task.interval }), + } +} + +function projectInstance(inst: SchemaInstance): TranspileInstance { + return { + name: inst.name, + program: inst.program, + ...(inst.task ? { task: inst.task } : {}), + } +} + +function stringifyReturnType(returnType: unknown): string { + return typeof returnType === 'string' ? returnType : 'BOOL' +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/index.ts b/src/backend/shared/transpilers/generate-st-from-json/index.ts new file mode 100644 index 000000000..9bd56e71b --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/index.ts @@ -0,0 +1,192 @@ +/** + * PLCOpen JSON → Structured Text transpiler. + * + * Phase 1 dispatch: + * - Data types, configuration, textual POUs (ST / IL / Python / C++) + * emit DIRECTLY from the IR via `ir-emit/*`. No DOM round-trip. + * - Graphical POUs (LD / FBD / SFC) still use the DOM-based walker + * from the XML-fed sibling (`src/PLCGenerator/`) — for those we + * synthesise a minimum-viable PLCOpen project DOM containing + * just the data types + POUs the graphical walker needs to do + * its type-inference (`computeConnectionTypes`) and block-library + * lookups, then run `generateProgram` against the POU element. + * + * Subsequent phases (2-4) replace the LD / FBD / SFC walkers with + * JSON-native ones, at which point `ir-to-plcopen-xml.ts` and the + * whole `src/` subdirectory get deleted. + * + * Callers project their own project shape into `TranspileProject` + * via `fromSchemaShape` (defined here, against the schema-shape + * `PLCProjectData` the editor's IPC delivers) — see `from-schema.ts`. + */ + +import { + type GenerateConfigurationOptions, + generateConfigurations, +} from './ir-emit/configuration' +import { generateDataTypes } from './ir-emit/data-types' +import { generateGraphicalPou } from './ir-emit/pou-graphical' +import { generateTextualPou } from './ir-emit/pou-textual' +import { irToPlcOpenDom } from './ir-to-plcopen-dom' +import { buildPouEmissionOrder } from './pou-emission-order' +import { generateProgram } from './src/PLCGenerator/pou_assembly' +import { getbody, getcontent, getname, getpous } from './src/plcopen/accessors' +import type { ProjectTree } from './src/plcopen/plcopen' +import type { TranspileProject } from './types' + +export { fromSchemaShape, type SchemaProjectData } from './from-schema' +export type { + ConfigurationExtraVariablesProvider, + CtnGlobalEntry, + CtnGlobalVarTuple, +} from './src/PLCGenerator/ctn_globals' +export type { + TranspileBody, + TranspileBodyLanguage, + TranspileDataType, + TranspileInstance, + TranspilePou, + TranspilePouInterface, + TranspilePouKind, + TranspileProject, + TranspileTask, + TranspileVariable, + TranspileVariableClass, + TranspileVariableType, +} from './types' + +export type TranspileOptions = GenerateConfigurationOptions + +export interface TranspileResult { + /** Concatenated Structured Text, or `null` if no POU compiled. */ + programSt: string | null + /** Names of POUs that compiled successfully, in emission order. */ + pouNames: string[] + /** Non-fatal diagnostics (skipped empty bodies, missing libraries, …). */ + warnings: string[] + /** + * Per-POU compile errors (and any project-level load error). Empty when + * every POU compiled cleanly. + */ + errors: string[] +} + +const TEXTUAL_LANGUAGES = new Set(['st', 'il', 'python', 'cpp']) + +/** + * Walk a `TranspileProject` and emit Structured Text for every data + * type, POU, and configuration. Pure function — no I/O, no + * network, safe to call in the browser / Web Worker. + * + * Throws synchronously only on internal invariant failures; per-POU + * compile errors land in `result.errors` and the rest of the + * program still emits. + */ +export function transpileToSt( + project: TranspileProject, + options: TranspileOptions = {}, +): TranspileResult { + const errors: string[] = [] + const warnings: string[] = [] + const pouNames: string[] = [] + const pieces: string[] = [] + + // Lazily build a DOM project IFF we need it for graphical POUs. + // Phase 2: built directly via DOMImplementation — no XML string + // serialise / parse cycle. + let domCache: ProjectTree | null = null + const ensureDom = (): ProjectTree | null => { + if (domCache) return domCache + try { + domCache = irToPlcOpenDom(project) as unknown as ProjectTree + return domCache + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + errors.push(`Failed to build project DOM for graphical walker: ${msg}`) + return null + } + } + + // TYPE … END_TYPE — emit IR-native. + for (const [text] of generateDataTypes(project)) { + pieces.push(text) + } + + // Per-POU emission. Textual bodies use the IR-native emitter; + // graphical bodies still use the DOM-based walker against a + // synthesised project DOM. + // + // Iteration order mirrors python's lazy / on-reference scheme + // (`PLCGenerator.GeneratePouProgram`, PLCGenerator.py:302): + // dependencies (POUs whose names appear inside another POU's body + // or as a derived-type variable declaration) are emitted BEFORE + // their dependents. See `pou_emission_order.ts`. + const orderedPous = buildPouEmissionOrder(project.pous) + for (const pou of orderedPous) { + try { + if (TEXTUAL_LANGUAGES.has(pou.body.language)) { + const chunks = generateTextualPou(pou, project) + pieces.push(chunks.map((c) => c[0]).join('')) + pouNames.push(pou.name) + continue + } + // Graphical POU. When the adapter populated `ldBody` (LD/FBD + // only — SFC still flows through the DOM walker), use the + // JSON-native walker. Otherwise fall back to the DOM walker. + if ( + (pou.body.language === 'ld' || pou.body.language === 'fbd') && + 'ldBody' in pou.body && + pou.body.ldBody !== undefined + ) { + const chunks = generateGraphicalPou(pou, pou.body.ldBody, project) + pieces.push(chunks.map((c) => c[0]).join('')) + pouNames.push(pou.name) + continue + } + const tree = ensureDom() + if (!tree) { + errors.push(`POU "${pou.name}" (${pou.body.language} body): DOM build failed for graphical walker`) + continue + } + const domPou = findDomPou(tree, pou.name) + if (!domPou) { + errors.push(`POU "${pou.name}" not found in synthesised DOM`) + continue + } + const bodies = getbody(domPou) + if (bodies.length === 0) { + warnings.push(`POU "${pou.name}" has no ; skipped`) + continue + } + const content = getcontent(bodies[0]) + if (!content) { + warnings.push(`POU "${pou.name}" has empty body content; skipped`) + continue + } + const chunks = generateProgram(domPou, { project: tree }) + pieces.push(chunks.map((c) => c[0]).join('')) + pouNames.push(pou.name) + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + errors.push(`POU "${pou.name}" (${pou.body.language} body): ${msg}`) + } + } + + // Trailing CONFIGURATION block — emit IR-native. + for (const [text] of generateConfigurations(project, options)) { + pieces.push(text) + } + + if (pouNames.length === 0 && errors.length > 0) { + return { programSt: null, pouNames, warnings, errors } + } + + return { programSt: pieces.join(''), pouNames, warnings, errors } +} + +function findDomPou(tree: ProjectTree, name: string) { + for (const pou of getpous(tree)) { + if (getname(pou) === name) return pou + } + return null +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/configuration.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/configuration.ts new file mode 100644 index 000000000..f776a42b5 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/configuration.ts @@ -0,0 +1,248 @@ +/** + * IR-native `CONFIGURATION … END_CONFIGURATION` block emitter. + * + * JSON-direct port of `src/PLCGenerator/configuration.ts` — walks + * `TranspileProject.configuration` instead of the parsed DOM, but + * emits byte-identical chunks. Mirrors Python's + * `ProgramGenerator.GenerateConfiguration` + `GenerateResource` + * (PLCGenerator.py:334-628). + * + * The IR carries exactly one configuration with one resource, named + * `Config0` / `Res0` — same hardcoded names the XML-based emitter + * produced. Global vars are emitted under the configuration block + * (not under the resource), matching `irToPlcOpenXml`'s layout. + * + * CTN-globals provider is honoured: `kind: 'variable'` entries + * (tuple-shape globals) are appended after the IR's + * `globalVariables`. `kind: 'varlist'` entries (raw DOM `` + * elements) are silently ignored on the IR-native path — they only + * come from the legacy DOM-injection flow which has no caller after + * Phase 1. + */ + +import type { ConfigurationExtraVariablesProvider } from '../src/PLCGenerator/ctn_globals' +import type { ProgramChunk } from '../src/PLCGenerator/program' +import { + computeConfigurationName, + computeConfigurationResourceName, +} from '../src/PLCGenerator/text_helpers' +import type { TranspileProject, TranspileVariable } from '../types' +import { declaredTypeName, getTypeAsText } from './type-text' +import { computeValue } from './value' + +export interface GenerateConfigurationOptions { + /** + * Beremiz CTN-injected globals provider. Mirrors + * `Controler.GetConfigurationExtraVariables` (PLCControler.py:1248). + */ + extraVarsProvider?: ConfigurationExtraVariablesProvider | null +} + +const CONFIG_NAME = 'Config0' +const RESOURCE_NAME = 'Res0' + +/** + * Emit the trailing `\nCONFIGURATION … END_CONFIGURATION\n` block. + * Returns an empty array when the IR carries no tasks / instances / + * globals (caller may still want the keyword shell — Python always + * emits the block; we mirror that). + */ +export function generateConfigurations( + project: TranspileProject, + options: GenerateConfigurationOptions = {}, +): ProgramChunk[] { + const configTagname = computeConfigurationName(CONFIG_NAME) + const resourceTagname = computeConfigurationResourceName(CONFIG_NAME, RESOURCE_NAME) + + const out: ProgramChunk[] = [] + + out.push(['\nCONFIGURATION ', []]) + out.push([CONFIG_NAME, [configTagname, 'name']]) + out.push(['\n', []]) + + // Configuration-level global variables. IR-shape globals first, + // then any CTN-injected tuple-shape globals. + emitGlobalVarList( + out, + collectConfigGlobals(project, options.extraVarsProvider), + configTagname, + /*indent=*/ ' ', + /*varIndent=*/ ' ', + project, + ) + + // RESOURCE block. The IR has exactly one resource (`Res0`). + out.push(['\n RESOURCE ', []]) + out.push([RESOURCE_NAME, [resourceTagname, 'name']]) + out.push([' ON PLC\n', []]) + + // Resource-scope globals are not in the IR today (Python supports + // them; we don't surface a field for them yet). Skipping the + // resource-level VAR_GLOBAL emit matches `irToPlcOpenXml`'s + // current shape. + + // Tasks. + project.configuration.tasks.forEach((task, taskNumber) => { + out.push([' TASK ', []]) + out.push([task.name, [resourceTagname, 'task', taskNumber, 'name']]) + out.push(['(', []]) + + if (task.triggering !== 'Cyclic') { + const single = task.single ?? '' + if (single.length === 0) { + throw new Error( + `Source signal has to be defined for single task '${task.name}' in resource '${CONFIG_NAME}.${RESOURCE_NAME}'.`, + ) + } + const snglkw = single.startsWith('[') && single.endsWith(']') ? 'MULTI' : 'SINGLE' + out.push([`${snglkw} := `, []]) + out.push([single, [resourceTagname, 'task', taskNumber, 'single']]) + out.push([',', []]) + } + + if (task.interval !== undefined) { + out.push(['INTERVAL := ', []]) + out.push([task.interval, [resourceTagname, 'task', taskNumber, 'interval']]) + out.push([',', []]) + } + + out.push(['PRIORITY := ', []]) + out.push([`${task.priority}`, [resourceTagname, 'task', taskNumber, 'priority']]) + out.push([');\n', []]) + }) + + // PROGRAM bindings — first the task-bound instances (in task + // iteration order, then instance order within each task), then the + // task-less instances directly under the resource. + let instanceNumber = 0 + for (const task of project.configuration.tasks) { + for (const instance of project.configuration.instances) { + if (instance.task !== task.name) continue + out.push([' PROGRAM ', []]) + out.push([instance.name, [resourceTagname, 'instance', instanceNumber, 'name']]) + out.push([' WITH ', []]) + out.push([task.name, [resourceTagname, 'instance', instanceNumber, 'task']]) + out.push([' : ', []]) + out.push([instance.program, [resourceTagname, 'instance', instanceNumber, 'type']]) + out.push([';\n', []]) + instanceNumber++ + } + } + for (const instance of project.configuration.instances) { + if (instance.task !== undefined && instance.task !== '') continue + out.push([' PROGRAM ', []]) + out.push([instance.name, [resourceTagname, 'instance', instanceNumber, 'name']]) + out.push([' : ', []]) + out.push([instance.program, [resourceTagname, 'instance', instanceNumber, 'type']]) + out.push([';\n', []]) + instanceNumber++ + } + + out.push([' END_RESOURCE\n', []]) + out.push(['END_CONFIGURATION\n', []]) + return out +} + +/* ────────────────────── helpers ─────────────────────────────────────────── */ + +function collectConfigGlobals( + project: TranspileProject, + provider: ConfigurationExtraVariablesProvider | null | undefined, +): TranspileVariable[] { + const out: TranspileVariable[] = [...project.configuration.globalVariables] + if (!provider) return out + const entries = provider() + for (const entry of entries) { + if (entry.kind !== 'variable') continue + const tuple = entry.variable + out.push({ + name: tuple.name, + type: tupleTypeToIr(tuple.type), + ...(tuple.initial ? { initialValue: tuple.initial } : {}), + }) + } + return out +} + +function tupleTypeToIr(typeName: string): TranspileVariable['type'] { + // CTN-globals tuples carry the type as a bare string. IEC base + // types resolve to `base-type`; anything else becomes a derived + // reference — mirrors PLCControler.py:1258-1273. + const upper = typeName.toUpperCase() + const elementaryTypes = new Set([ + 'BOOL', + 'SINT', + 'INT', + 'DINT', + 'LINT', + 'USINT', + 'UINT', + 'UDINT', + 'ULINT', + 'REAL', + 'LREAL', + 'TIME', + 'DATE', + 'TOD', + 'DT', + 'STRING', + 'WSTRING', + 'BYTE', + 'WORD', + 'DWORD', + 'LWORD', + ]) + if (elementaryTypes.has(upper)) { + return { definition: 'base-type', value: upper } + } + return { definition: 'derived', value: typeName } +} + +function emitGlobalVarList( + out: ProgramChunk[], + variables: TranspileVariable[], + tagname: string, + indent: string, + _varIndent: string, + project: TranspileProject, +): void { + if (variables.length === 0) return + + const variableType = 'var_local' + const range: [number, number] = [0, variables.length] + + out.push([`${indent}VAR_GLOBAL`, []]) + // CONSTANT / RETAIN / NON_RETAIN modifiers come from the + // wrapper in the DOM path; the IR doesn't surface + // per-list modifiers today (only the bare variable list). + void range + void tagname + out.push(['\n', []]) + + variables.forEach((variable, idx) => { + out.push([_varIndent, []]) + out.push([variable.name, [tagname, variableType, idx, 'name']]) + out.push([' ', []]) + + if (variable.location) { + out.push(['AT ', []]) + out.push([variable.location, [tagname, variableType, idx, 'location']]) + out.push([' ', []]) + } + + out.push([': ', []]) + out.push([getTypeAsText(variable), [tagname, variableType, idx, 'type']]) + + if (variable.initialValue !== undefined && variable.initialValue !== '') { + const declaredType = declaredTypeName(variable) + out.push([' := ', []]) + out.push([ + computeValue(project, variable.initialValue, declaredType), + [tagname, variableType, idx, 'initial value'], + ]) + } + out.push([';\n', []]) + }) + + out.push([`${indent}END_VAR\n`, []]) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/data-types.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/data-types.ts new file mode 100644 index 000000000..b7b0d5bf6 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/data-types.ts @@ -0,0 +1,181 @@ +/** + * IR-native `TYPE … END_TYPE` block emitter. + * + * JSON-direct port of `generate_data_type.ts` — walks + * `TranspileProject.dataTypes` instead of the parsed DOM, but emits + * byte-identical chunks (verified by the comparison helper). + * + * The recursive emission order (a child data type referencing + * another forces the dependency to emit first) mirrors + * `ProgramGenerator.GenerateDataType` (PLCGenerator.py:152-299) line + * for line. Only difference from the DOM version: no `subrange` + * branch — the IR's `TranspileDataType` union doesn't carry + * subranges (matches the schema at `backend/shared/types/PLC/open-plc.ts`). + */ + +import { ComputeDataTypeName } from '../src/PLCGenerator/data_type' +import type { ProgramChunk } from '../src/PLCGenerator/program' +import type { + TranspileDataType, + TranspileProject, + TranspileVariable, + TranspileVariableType, +} from '../types' +import { computeValue } from './value' + +interface DataTypeState { + project: TranspileProject + out: ProgramChunk[] + byName: Map + computed: Map +} + +export function generateDataTypes(project: TranspileProject): ProgramChunk[] { + if (project.dataTypes.length === 0) return [] + + const state: DataTypeState = { + project, + out: [], + byName: new Map(project.dataTypes.map((dt) => [dt.name, dt])), + computed: new Map(project.dataTypes.map((dt) => [dt.name, false])), + } + + const program: ProgramChunk[] = [] + program.push(['TYPE\n', []]) + for (const name of state.computed.keys()) { + generateDataType(state, name) + } + program.push(...state.out) + program.push(['END_TYPE\n\n', []]) + return program +} + +function generateDataType(state: DataTypeState, datatypeName: string): void { + // Mirror PLCGenerator.py:154 — dict.get(name, True). Unknown names + // (e.g. POU references) get True back, skipping emission. + if (state.computed.get(datatypeName) !== false) return + state.computed.set(datatypeName, true) + + const dt = state.byName.get(datatypeName) + if (!dt) return + + const tagname = ComputeDataTypeName(dt.name) + const chunks: ProgramChunk[] = [ + [' ', []], + [dt.name, [tagname, 'name']], + [' : ', []], + ] + + if (dt.derivation === 'directly-derived') { + // Branch on whether the base is elementary or another data type. + const base = dt.baseType + if (state.byName.has(base)) { + generateDataType(state, base) + chunks.push([base, [tagname, 'base']]) + } else { + // Elementary IEC base — emit uppercased to match the DOM + // walker (which does `baseTypeKind.toUpperCase()` for the + // elementary branch at PLCGenerator.py:286). + chunks.push([base.toUpperCase(), [tagname, 'base']]) + } + } else if (dt.derivation === 'enumerated') { + chunks.push(['(', []]) + dt.values.forEach((value, i) => { + if (i > 0) chunks.push([', ', []]) + chunks.push([value.description, [tagname, 'value', i]]) + }) + chunks.push([')', []]) + } else if (dt.derivation === 'array') { + const baseTypeName = resolveArrayBaseName(dt.baseType, state) + chunks.push(['ARRAY [', []]) + dt.dimensions.forEach((dimension, i) => { + if (i > 0) chunks.push([',', []]) + const [lower, upper] = dimension.dimension.split('..') + chunks.push( + [`${lower}`, [tagname, 'range', i, 'lower']], + ['..', []], + [`${upper}`, [tagname, 'range', i, 'upper']], + ) + }) + chunks.push(['] OF ', []], [baseTypeName, [tagname, 'base']]) + } else if (dt.derivation === 'structure') { + chunks.push(['STRUCT', []]) + dt.variable.forEach((variable, i) => { + const elementtypeName = resolveStructFieldType(variable, state) + chunks.push( + ['\n ', []], + [variable.name, [tagname, 'struct', i, 'name']], + [' : ', []], + [elementtypeName, [tagname, 'struct', i, 'type']], + ) + if (variable.initialValue !== undefined && variable.initialValue !== '') { + chunks.push( + [' := ', []], + [ + computeValue(state.project, variable.initialValue, elementtypeName), + [tagname, 'struct', i, 'initial value'], + ], + ) + } + chunks.push([';', []]) + }) + chunks.push(['\n END_STRUCT', []]) + } + + if (dt.initialValue !== undefined && dt.initialValue !== '') { + chunks.push( + [' := ', []], + [ + computeValue(state.project, dt.initialValue, datatypeName), + [tagname, 'initial value'], + ], + ) + } + chunks.push([';\n', []]) + + state.out.push(...chunks) +} + +function resolveArrayBaseName( + baseType: string | { value: string }, + state: DataTypeState, +): string { + const name = typeof baseType === 'string' ? baseType : baseType.value + if (state.byName.has(name)) { + generateDataType(state, name) + return name + } + return name.toUpperCase() +} + +function resolveStructFieldType( + variable: TranspileVariable, + state: DataTypeState, +): string { + const type = variable.type + if (type.definition === 'derived' || type.definition === 'user-data-type') { + if (state.byName.has(type.value)) generateDataType(state, type.value) + return type.value + } + if (type.definition === 'array') { + const baseName = resolveArrayBaseName(type.data.baseType, state) + const dimensions = type.data.dimensions.map((d) => d.dimension).join(',') + return `ARRAY [${dimensions}] OF ${baseName}` + } + return formatBaseType(type) +} + +function formatBaseType(type: TranspileVariableType): string { + if (type.definition === 'base-type') { + return type.value.toUpperCase() + } + if (type.definition === 'array') { + const baseName = + typeof type.data.baseType === 'string' + ? type.data.baseType + : type.data.baseType.value + const dimensions = type.data.dimensions.map((d) => d.dimension).join(',') + return `ARRAY [${dimensions}] OF ${baseName.toUpperCase()}` + } + return type.value +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-graphical.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-graphical.ts new file mode 100644 index 000000000..b1a3aed6d --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-graphical.ts @@ -0,0 +1,212 @@ +/** + * IR-native graphical-POU emitter — LD / FBD bodies. + * + * Drives the JSON-native walker (`ld/walker.ts`) for the body + * content, then wraps it with the POU's signature + VAR sections + + * END. Trigger variables synthesised during the walk + * (`R_TRIG1`, `F_TRIG1`, …) get appended to the trailing `VAR` + * section before assembly so the declaration order matches what the + * existing DOM walker produces. + */ + +import { emitLdBody } from '../ld/walker' +import { newWalkerState } from '../ld/walker_state' +import type { LdBody } from '../ld/types' +import { resolveBlockType } from '../src/PLCGenerator/block_library' +import { PLC_BASE_TYPES } from '../src/PLCGenerator/ctn_globals' +import type { ProgramChunk } from '../src/PLCGenerator/program' +import { computePouName } from '../src/PLCGenerator/text_helpers' +import { varTypeNames } from '../src/PLCGenerator/type_text' +import type { + TranspilePou, + TranspileProject, + TranspileVariable, + TranspileVariableClass, +} from '../types' +import { declaredTypeName, getTypeAsText } from './type-text' +import { computeValue } from './value' + +interface InterfaceEntry { + keyword: string + vars: TranspileVariable[] +} + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +/** + * Emit a complete LD/FBD POU (signature → VAR sections → body → + * closing keyword). Mirrors `pou-textual.generateTextualPou` for + * the wrapping, with the body coming from `emitLdBody` instead of + * a raw string passthrough. + */ +export function generateGraphicalPou( + pou: TranspilePou, + ldBody: LdBody, + project: TranspileProject, + indent = 2, +): ProgramChunk[] { + const tagName = computePouName(pou.name) + const kindKeyword = ({ + program: 'PROGRAM', + function: 'FUNCTION', + 'function-block': 'FUNCTION_BLOCK', + } as Record)[pou.pouType] + + // Pre-populate the walker's declaredVars set so trigger-var + // synthesis (`R_TRIG1`, …) avoids collisions with the POU's + // existing interface entries. + const declaredVars = new Set() + for (const v of pou.interface?.variables ?? []) declaredVars.add(v.name) + + const walkerState = newWalkerState(tagName, ldBody, declaredVars) + walkerState.currentIndent = ' '.repeat(indent) + emitLdBody(walkerState) + + // Compose the final POU chunk stream now that the walker has + // accumulated the body + any trigger vars. + const program: ProgramChunk[] = [] + program.push([`${kindKeyword} `, []]) + program.push([pou.name, [tagName, 'name']]) + if (pou.pouType === 'function') { + const returnType = (pou.interface.returnType ?? 'BOOL').toUpperCase() + program.push([' : ', []]) + program.push([returnType, [tagName, 'return']]) + } + program.push(['\n', []]) + + // Resolve `ANY` placeholders in the synthesised function-output + // temps: + // 1. User-defined project functions → declared `interface.returnType`. + // 2. Standard catalog functions (ADD, MUL, NOT, AND, …) → catalog's + // formal output `type`. Generic groups (`ANY_BIT`, `ANY_NUM`, + // …) collapse to `BOOL`, which matches the corpus where these + // operators are always Boolean rung logic. A future + // computeConnectionTypes port will narrow these properly. + const resolvedTempVars = walkerState.functionTempVars.map((tv) => { + if (tv.type !== 'ANY') return tv + const referenced = project.pous.find((p) => p.name === tv.originBlockTypeName) + if (referenced && referenced.pouType === 'function' && referenced.interface.returnType) { + return { ...tv, type: referenced.interface.returnType } + } + const stdResolved = resolveBlockType(null, tv.originBlockTypeName) + if (stdResolved) { + const outPort = stdResolved.infos.outputs.find( + (o) => o.name === tv.originFormalParameter, + ) + if (outPort) { + const t = outPort.type + const collapsed = t.startsWith('ANY') ? 'BOOL' : t + return { ...tv, type: collapsed } + } + } + return tv + }) + + const iface = computeInterface( + pou.interface?.variables ?? [], + walkerState.triggerVars, + resolvedTempVars, + ) + for (const entry of iface) { + const variableType = locationCategory(entry.keyword) + program.push([` ${entry.keyword}`, []]) + program.push(['\n', []]) + entry.vars.forEach((v, varNumber) => { + program.push([' ', []]) + program.push([v.name, [tagName, variableType, varNumber, 'name']]) + program.push([' ', []]) + if (v.location) { + program.push(['AT ', []]) + program.push([v.location, [tagName, variableType, varNumber, 'location']]) + program.push([' ', []]) + } + const typeText = getTypeAsText(v) + program.push([': ', []]) + program.push([typeText, [tagName, variableType, varNumber, 'type']]) + if (v.initialValue !== undefined && v.initialValue !== '') { + const declared = declaredTypeName(v) + program.push([' := ', []]) + program.push([ + computeValue(project, v.initialValue, declared), + [tagName, variableType, varNumber, 'initial value'], + ]) + } + program.push([';\n', []]) + }) + program.push([' END_VAR\n', []]) + } + program.push(['\n', []]) + program.push(...walkerState.program) + program.push([`END_${kindKeyword}\n\n`, []]) + return program +} + +/* ────────────────────────── helpers ─────────────────────────────────────── */ + +function computeInterface( + variables: TranspileVariable[], + triggerVars: { name: string; type: 'R_TRIG' | 'F_TRIG' }[], + functionTempVars: { + name: string + type: string + originBlockTypeName: string + originFormalParameter: string + }[], +): InterfaceEntry[] { + const classToKeyword: Record = { + input: varTypeNames.inputVars, + output: varTypeNames.outputVars, + inOut: varTypeNames.inOutVars, + external: varTypeNames.externalVars, + local: varTypeNames.localVars, + temp: varTypeNames.tempVars, + } + // Group by keyword, preserving IR insertion order. + const grouped = new Map() + for (const v of variables) { + const keyword = classToKeyword[v.class ?? 'local'] ?? varTypeNames.localVars + const bucket = grouped.get(keyword) ?? [] + bucket.push(v) + grouped.set(keyword, bucket) + } + // Append synthesised trigger vars + function-call output temps to + // the trailing VAR (local) bucket so they appear after the user's + // declared locals — same order the DOM walker produces. + if (triggerVars.length > 0 || functionTempVars.length > 0) { + const localKeyword = varTypeNames.localVars + const localBucket = grouped.get(localKeyword) ?? [] + for (const t of triggerVars) { + localBucket.push({ + name: t.name, + type: { definition: 'derived', value: t.type }, + class: 'local', + }) + } + for (const t of functionTempVars) { + const isElementary = PLC_BASE_TYPES.has(t.type.toUpperCase()) + localBucket.push({ + name: t.name, + type: isElementary + ? { definition: 'base-type', value: t.type.toUpperCase() } + : { definition: 'derived', value: t.type }, + class: 'local', + }) + } + grouped.set(localKeyword, localBucket) + } + const out: InterfaceEntry[] = [] + for (const [keyword, vars] of grouped) { + out.push({ keyword, vars }) + } + return out +} + +const ERROR_VAR_TYPES: Record = { + VAR_INPUT: 'var_input', + VAR_OUTPUT: 'var_output', + VAR_INOUT: 'var_inout', +} + +function locationCategory(keyword: string): string { + return ERROR_VAR_TYPES[keyword] ?? 'var_local' +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-textual.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-textual.ts new file mode 100644 index 000000000..92f6c432e --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/pou-textual.ts @@ -0,0 +1,194 @@ +/** + * IR-native textual-POU emitter — ST / IL / Python / C++. + * + * Skips the DOM round-trip entirely: walks `TranspilePou` directly + * and emits the same chunk shape `pou_assembly.generateProgram` + * produces for textual bodies. Byte-identical with the XML-fed + * transpiler (verified by the comparison helper). + * + * Graphical POUs (LD / FBD / SFC) still go through the DOM-based + * walker in `src/PLCGenerator/pou_assembly.ts` — see `index.ts`'s + * dispatch. + */ + +import { PLC_BASE_TYPES } from '../src/PLCGenerator/ctn_globals' +import type { ProgramChunk } from '../src/PLCGenerator/program' +import { reIndentText } from '../src/PLCGenerator/text_helpers' +import { computePouName } from '../src/PLCGenerator/text_helpers' +import { varTypeNames } from '../src/PLCGenerator/type_text' +import type { + TranspilePou, + TranspileProject, + TranspileVariable, + TranspileVariableClass, +} from '../types' +import { declaredTypeName, getTypeAsText } from './type-text' +import { computeValue } from './value' + +/** + * Mirror python's `` rendering rule: elementary IEC + * types come out uppercased (`BOOL`, `INT`, …), user-defined + * derived types keep their declared case (`Irrigation_State`). + * `interface.computeReturnType` in the XML-fed transpiler does the + * equivalent by branching on the `` local tag. + */ +function formatReturnType(returnType: string | undefined): string { + if (returnType === undefined || returnType === '') return 'BOOL' + return PLC_BASE_TYPES.has(returnType.toUpperCase()) + ? returnType.toUpperCase() + : returnType +} + +interface InterfaceEntry { + keyword: string + located: boolean + vars: TranspileVariable[] +} + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +/** + * Emit a complete textual-body POU (signature → VAR sections → body + * text → closing keyword). Mirrors + * `PouProgramGenerator.GenerateProgram` (PLCGenerator.py:2414) for the + * textual body path. + * + * Throws `Error` when the POU has no interface or no body — same + * guards Python uses. + */ +export function generateTextualPou( + pou: TranspilePou, + project: TranspileProject, + indent = 2, +): ProgramChunk[] { + const tagName = computePouName(pou.name) + const kindKeyword = ({ + program: 'PROGRAM', + function: 'FUNCTION', + 'function-block': 'FUNCTION_BLOCK', + } as Record)[pou.pouType] + + const program: ProgramChunk[] = [] + program.push([`${kindKeyword} `, []]) + program.push([pou.name, [tagName, 'name']]) + + if (pou.pouType === 'function') { + program.push([' : ', []]) + program.push([formatReturnType(pou.interface.returnType), [tagName, 'return']]) + } + program.push(['\n', []]) + + const iface = computeInterface(pou.interface.variables) + if (iface.length === 0) { + throw new Error(`No variable defined in "${pou.name}" POU`) + } + + let varNumber = 0 + for (const entry of iface) { + const variableType = locationCategory(entry.keyword) + program.push([` ${entry.keyword}`, []]) + program.push(['\n', []]) + + for (const v of entry.vars) { + program.push([' ', []]) + program.push([v.name, [tagName, variableType, varNumber, 'name']]) + program.push([' ', []]) + + if (v.location) { + program.push(['AT ', []]) + program.push([v.location, [tagName, variableType, varNumber, 'location']]) + program.push([' ', []]) + } + + const typeText = getTypeAsText(v) + program.push([': ', []]) + program.push([typeText, [tagName, variableType, varNumber, 'type']]) + + if (v.initialValue !== undefined && v.initialValue !== '') { + const declared = declaredTypeName(v) + program.push([' := ', []]) + program.push([ + computeValue(project, v.initialValue, declared), + [tagName, variableType, varNumber, 'initial value'], + ]) + } + program.push([';\n', []]) + varNumber++ + } + program.push([' END_VAR\n', []]) + } + + program.push(['\n', []]) + + // Body: raw textual source, re-indented to `indent` spaces. For ST + // / IL / Python / C++ the IR's `value` is already a string. + if ( + pou.body.language !== 'st' && + pou.body.language !== 'il' && + pou.body.language !== 'python' && + pou.body.language !== 'cpp' + ) { + throw new Error(`generateTextualPou called with non-textual body "${pou.body.language}"`) + } + const bodyText = pou.body.value + if (bodyText.length === 0) { + throw new Error(`No body defined in "${pou.name}" POU`) + } + program.push([reIndentText(bodyText, indent), [tagName, 'body', indent]]) + + program.push([`END_${kindKeyword}\n\n`, []]) + return program +} + +/* ────────────────────── helpers ─────────────────────────────────────────── */ + +function computeInterface(variables: TranspileVariable[]): InterfaceEntry[] { + // Bucket variables by class, mirroring the order Python's varlist + // iteration produces. `classToKeyword` is keyed on IR class names + // (which match the PLCOpen ``/``/… local + // tags after lower-casing the leading section); look those up + // through `varTypeNames` so the keyword strings stay identical to + // the DOM emitter. + const classToKeyword: Record = { + input: varTypeNames.inputVars, + output: varTypeNames.outputVars, + inOut: varTypeNames.inOutVars, + external: varTypeNames.externalVars, + local: varTypeNames.localVars, + temp: varTypeNames.tempVars, + } + + const grouped = new Map() + // Maintain insertion order matching the IR's variable order. + for (const v of variables) { + const keyword = classToKeyword[v.class ?? 'local'] ?? varTypeNames.localVars + let bucket = grouped.get(keyword) + if (!bucket) { + bucket = { located: [], unlocated: [] } + grouped.set(keyword, bucket) + } + if (v.location) bucket.located.push(v) + else bucket.unlocated.push(v) + } + + const out: InterfaceEntry[] = [] + for (const [keyword, bucket] of grouped) { + if (bucket.unlocated.length > 0) { + out.push({ keyword, located: false, vars: bucket.unlocated }) + } + if (bucket.located.length > 0) { + out.push({ keyword, located: true, vars: bucket.located }) + } + } + return out +} + +const ERROR_VAR_TYPES: Record = { + VAR_INPUT: 'var_input', + VAR_OUTPUT: 'var_output', + VAR_INOUT: 'var_inout', +} + +function locationCategory(keyword: string): string { + return ERROR_VAR_TYPES[keyword] ?? 'var_local' +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/type-text.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/type-text.ts new file mode 100644 index 000000000..e6bd760f7 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/type-text.ts @@ -0,0 +1,54 @@ +/** + * IR-native `gettypeAsText` — mirrors the DOM helper + * (`plcopen.py:1100` and `src/PLCGenerator/type_text.ts`) but reads + * the variable's type definition directly off the IR. + */ + +import type { TranspileVariable, TranspileVariableType } from '../types' + +/** + * Textual representation of a `TranspileVariable`'s declared type: + * - `derived` / `user-data-type` → the referenced name as-is. + * - `base-type` → uppercased (`'BOOL'`, `'INT'`, …). Lowercase + * `string`/`wstring` are uppercased into `STRING`/`WSTRING`. + * - `array` → `ARRAY [a..b, …] OF basetype`. + */ +export function getTypeAsText(variable: TranspileVariable): string { + return formatType(variable.type) +} + +function formatType(type: TranspileVariableType): string { + if (type.definition === 'derived' || type.definition === 'user-data-type') { + return type.value + } + if (type.definition === 'base-type') { + return type.value.toUpperCase() + } + // array — explicit guard so editor's stricter narrowing keeps the + // `data` property in scope. Web's tsconfig accepted the fall- + // through; editor's doesn't. + if (type.definition !== 'array') return '' + const baseName = + typeof type.data.baseType === 'string' + ? type.data.baseType + : type.data.baseType.value + const dims = type.data.dimensions.map((d) => d.dimension).join(',') + return `ARRAY [${dims}] OF ${baseName.toUpperCase()}` +} + +/** + * For `computeValue`'s quote-wrapping check we need the type name + * users actually wrote in the type field — but for the derived case + * the DOM helper returned the derived name directly (not "ARRAY […]" + * even if the derived type happens to be an array). Mirrors the + * `interface.ts:resolveDeclaredType` quirk. + */ +export function declaredTypeName(variable: TranspileVariable): string { + if ( + variable.type.definition === 'derived' || + variable.type.definition === 'user-data-type' + ) { + return variable.type.value + } + return getTypeAsText(variable) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-emit/value.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/value.ts new file mode 100644 index 000000000..c5cc3fc7a --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-emit/value.ts @@ -0,0 +1,76 @@ +/** + * IR-native `computeValue` — wraps STRING/WSTRING initial values in + * quotes when the user didn't already. Mirrors + * `ProgramGenerator.ComputeValue` (PLCGenerator.py:135) without going + * through DOM accessors; the project's data-type alias chain is + * walked directly off the IR's `dataTypes` array. + */ + +import { TypeHierarchy } from '../src/PLCGenerator/type_hierarchy' +import type { TranspileDataType, TranspileProject } from '../types' + +/** + * Walk `typename` down through `project.dataTypes` aliases until an + * elementary IEC type is reached. Returns `null` if the chain + * dead-ends (unknown alias, struct/enum leaves where the elementary + * base is intentionally absent). + * + * Identical behaviour to `pou_assembly.getBaseType`, just reading the + * IR map instead of the DOM project. + */ +function getBaseType(typename: string, dataTypeIndex: Map): string | null { + // Elementary types short-circuit (the existing DOM helper does the same). + if (typename in TypeHierarchy) return typename + + const dt = dataTypeIndex.get(typename) + if (!dt) return null + + if (dt.derivation === 'array') { + return getBaseType( + typeof dt.baseType === 'string' ? dt.baseType : dt.baseType.value, + dataTypeIndex, + ) + } + if (dt.derivation === 'directly-derived') { + return getBaseType(dt.baseType, dataTypeIndex) + } + // Struct / enum leaves: Python's GetDataTypeBaseType returns the + // type name itself. We return the same so STRING/WSTRING quote + // gating doesn't fire on them. + return typename +} + +/** + * Resolve a variable's underlying elementary base type from the + * IR. Used by initial-value quote wrapping + * (`'foo'` for STRING, `"foo"` for WSTRING). Returns `null` when + * the chain doesn't terminate at an elementary type. + */ +export function resolveBaseType( + typename: string, + project: TranspileProject, +): string | null { + const index = new Map() + for (const dt of project.dataTypes) index.set(dt.name, dt) + return getBaseType(typename, index) +} + +/** + * Same as the DOM helper at `pou_assembly.computeValue` — wrap + * STRING / WSTRING initial values in quotes when the source text + * doesn't already carry them. + */ +export function computeValue( + project: TranspileProject, + value: string, + varType: string, +): string { + const baseType = resolveBaseType(varType, project) + if (baseType === 'STRING' && !value.startsWith("'") && !value.endsWith("'")) { + return `'${value}'` + } + if (baseType === 'WSTRING' && !value.startsWith('"') && !value.endsWith('"')) { + return `"${value}"` + } + return value +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ir-to-plcopen-dom.ts b/src/backend/shared/transpilers/generate-st-from-json/ir-to-plcopen-dom.ts new file mode 100644 index 000000000..b5360ccd6 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ir-to-plcopen-dom.ts @@ -0,0 +1,418 @@ +/** + * Build a PLCOpen `Document` directly from the IR — no XML string + * round-trip. Replaces the Phase 0 `irToPlcOpenXml` (xmlbuilder2 + * serialise) + `LoadProjectXML` (xmldom parse) pair with one + * DOM-construction pass using `xmldom`'s `DOMImplementation`. + * + * Only used when the project contains graphical POUs (LD / FBD / + * SFC) — purely textual projects skip DOM construction entirely via + * the IR-native emitters in `ir-emit/`. The DOM produced here gets + * walked by the existing accessors in `src/plcopen/accessors.ts`, + * which expect the standard PLCOpen tag shape this builder emits. + * + * The IR's graphical bodies carry `xmlBody` — an xmlbuilder2 plain + * object (`{ body: { LD: {…} } }` etc.) the adapters built via + * `ladderToXml` / `fbdToXml`. `objectToDom` walks that object and + * materialises matching DOM elements; the xmlbuilder2 conventions + * (`'@attr'` for attributes, `'$'` for CDATA-style text, arrays for + * repeated children) map straight to DOM API calls. + */ + +import { DOMImplementation } from '@xmldom/xmldom' +import type { Document, Element } from '@xmldom/xmldom' + +import type { + TranspileBody, + TranspileDataType, + TranspilePou, + TranspilePouKind, + TranspileProject, + TranspileTask, + TranspileVariable, + TranspileVariableType, +} from './types' + +/* ─────────────────────────── namespaces / constants ─────────────────────── */ + +const TC6_NS = 'http://www.plcopen.org/xml/tc6_0201' +const XHTML_NS = 'http://www.w3.org/1999/xhtml' + +const BASE_PLC_TYPES: ReadonlySet = new Set([ + 'BOOL', + 'SINT', + 'INT', + 'DINT', + 'LINT', + 'USINT', + 'UINT', + 'UDINT', + 'ULINT', + 'REAL', + 'LREAL', + 'TIME', + 'DATE', + 'TOD', + 'DT', + 'STRING', + 'WSTRING', + 'BYTE', + 'WORD', + 'DWORD', + 'LWORD', +]) + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +export function irToPlcOpenDom(project: TranspileProject): Document { + const doc = new DOMImplementation().createDocument(TC6_NS, 'project', null) + const root = doc.documentElement + if (!root) throw new Error('irToPlcOpenDom: DOMImplementation returned a document with no root element') + + root.setAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema-instance') + root.setAttribute('xmlns:xhtml', XHTML_NS) + root.setAttribute('xmlns:ns1', TC6_NS) + + const now = new Date().toISOString() + appendChildEl(doc, root, 'fileHeader', { + companyName: 'Unknown', + productName: 'Unnamed', + productVersion: '1', + creationDateTime: now, + }) + const contentHeader = appendChildEl(doc, root, 'contentHeader', { + name: 'Unnamed', + modificationDateTime: now, + }) + const coordinateInfo = appendChildEl(doc, contentHeader, 'coordinateInfo') + for (const lang of ['fbd', 'ld', 'sfc']) { + const langEl = appendChildEl(doc, coordinateInfo, lang) + appendChildEl(doc, langEl, 'scaling', { x: '16', y: '16' }) + } + + // + const types = appendChildEl(doc, root, 'types') + const dataTypesEl = appendChildEl(doc, types, 'dataTypes') + for (const dt of project.dataTypes) { + appendDataType(doc, dataTypesEl, dt) + } + const pousEl = appendChildEl(doc, types, 'pous') + for (const pou of project.pous) { + appendPou(doc, pousEl, pou) + } + + // + const instances = appendChildEl(doc, root, 'instances') + const configurations = appendChildEl(doc, instances, 'configurations') + const configuration = appendChildEl(doc, configurations, 'configuration', { name: 'Config0' }) + const resource = appendChildEl(doc, configuration, 'resource', { name: 'Res0' }) + for (const task of project.configuration.tasks) { + appendTask(doc, resource, task, project.configuration.instances) + } + if (project.configuration.globalVariables.length > 0) { + const globalVars = appendChildEl(doc, configuration, 'globalVars') + for (const v of project.configuration.globalVariables) { + appendVariable(doc, globalVars, v) + } + } + + return doc +} + +/* ─────────────────────────── POU emission ───────────────────────────────── */ + +function appendPou(doc: Document, parent: Element, pou: TranspilePou): void { + const pouEl = appendChildEl(doc, parent, 'pou', { + name: pou.name, + pouType: pouTypeToXml(pou.pouType), + }) + appendInterface(doc, pouEl, pou) + appendBody(doc, pouEl, pou.body) + const documentation = appendChildEl(doc, pouEl, 'documentation') + appendXhtmlP(doc, documentation, pou.documentation || ' ') +} + +function pouTypeToXml(pouType: TranspilePouKind): string { + return pouType === 'function-block' ? 'functionBlock' : pouType +} + +function appendInterface(doc: Document, parent: Element, pou: TranspilePou): void { + const variables = pou.interface?.variables ?? [] + const returnType = pou.pouType === 'function' ? pou.interface?.returnType : undefined + if (variables.length === 0 && !returnType) return + + const interfaceEl = appendChildEl(doc, parent, 'interface') + if (returnType) { + const returnEl = appendChildEl(doc, interfaceEl, 'returnType') + appendTypeLeaf(doc, returnEl, returnType) + } + + const classToTag: Record = { + input: 'inputVars', + output: 'outputVars', + inOut: 'inOutVars', + external: 'externalVars', + local: 'localVars', + temp: 'tempVars', + } + const groups = new Map() + for (const v of variables) { + const tag = classToTag[v.class ?? 'local'] + if (!tag) continue + const bucket = groups.get(tag) ?? [] + bucket.push(v) + groups.set(tag, bucket) + } + for (const [tag, bucket] of groups) { + const groupEl = appendChildEl(doc, interfaceEl, tag) + for (const v of bucket) { + appendVariable(doc, groupEl, v) + } + } +} + +function appendBody(doc: Document, parent: Element, body: TranspileBody): void { + switch (body.language) { + case 'st': + case 'python': + case 'cpp': { + const bodyEl = appendChildEl(doc, parent, 'body') + const stEl = appendChildEl(doc, bodyEl, 'ST') + appendXhtmlP(doc, stEl, body.value) + return + } + case 'il': { + const bodyEl = appendChildEl(doc, parent, 'body') + const ilEl = appendChildEl(doc, bodyEl, 'IL') + appendXhtmlP(doc, ilEl, body.value) + return + } + case 'ld': + case 'fbd': + case 'sfc': + // Graphical body: the adapter pre-rendered the + // `{ body: { LD|FBD|SFC: {…} } }` xmlbuilder2 plain object. + // Materialise it into DOM directly. + objectToDom(doc, parent, body.xmlBody) + return + } +} + +/* ─────────────────────────── variable emission ──────────────────────────── */ + +function appendVariable(doc: Document, parent: Element, v: TranspileVariable): void { + const variableEl = appendChildEl(doc, parent, 'variable', { name: v.name }) + if (v.location) variableEl.setAttribute('address', v.location) + + const typeEl = appendChildEl(doc, variableEl, 'type') + appendTypeContent(doc, typeEl, v.type) + + if (v.initialValue !== undefined && v.initialValue !== '') { + const ivEl = appendChildEl(doc, variableEl, 'initialValue') + appendChildEl(doc, ivEl, 'simpleValue', { value: v.initialValue }) + } + + if (v.documentation) { + const docEl = appendChildEl(doc, variableEl, 'documentation') + appendXhtmlP(doc, docEl, v.documentation) + } +} + +function appendTypeContent(doc: Document, parent: Element, type: TranspileVariableType): void { + if (type.definition === 'array') { + const arrayEl = appendChildEl(doc, parent, 'array') + for (const d of type.data.dimensions) { + const [lower, upper] = d.dimension.split('..') + appendChildEl(doc, arrayEl, 'dimension', { lower, upper }) + } + const baseTypeEl = appendChildEl(doc, arrayEl, 'baseType') + const baseName = + typeof type.data.baseType === 'string' ? type.data.baseType : type.data.baseType.value + appendTypeLeaf(doc, baseTypeEl, baseName) + return + } + if (type.definition === 'derived' || type.definition === 'user-data-type') { + appendChildEl(doc, parent, 'derived', { name: type.value }) + return + } + // base-type + appendTypeLeaf(doc, parent, type.value) +} + +/** + * Emit a single elementary IEC type element (``, ``, + * `` lower-cased, …) or a `` wrapper + * when the typename isn't elementary. + */ +function appendTypeLeaf(doc: Document, parent: Element, typeName: string): void { + const normalized = typeName.trim() + const upper = normalized.toUpperCase() + if (BASE_PLC_TYPES.has(upper)) { + const tag = upper === 'STRING' ? 'string' : upper === 'WSTRING' ? 'wstring' : upper + appendChildEl(doc, parent, tag) + return + } + appendChildEl(doc, parent, 'derived', { name: normalized }) +} + +/* ─────────────────────────── data-type emission ─────────────────────────── */ + +function appendDataType(doc: Document, parent: Element, dt: TranspileDataType): void { + const dtEl = appendChildEl(doc, parent, 'dataType', { name: dt.name }) + + if (dt.derivation === 'array') { + const baseTypeEl = appendChildEl(doc, dtEl, 'baseType') + const arrayEl = appendChildEl(doc, baseTypeEl, 'array') + for (const d of dt.dimensions) { + const [lower, upper] = d.dimension.split('..') + appendChildEl(doc, arrayEl, 'dimension', { lower, upper }) + } + const innerBaseEl = appendChildEl(doc, arrayEl, 'baseType') + appendTypeLeaf(doc, innerBaseEl, typeof dt.baseType === 'string' ? dt.baseType : dt.baseType.value) + if (dt.initialValue) { + const ivEl = appendChildEl(doc, dtEl, 'initialValue') + appendChildEl(doc, ivEl, 'simpleValue', { value: dt.initialValue }) + } + return + } + if (dt.derivation === 'enumerated') { + const baseTypeEl = appendChildEl(doc, dtEl, 'baseType') + const enumEl = appendChildEl(doc, baseTypeEl, 'enum') + const valuesEl = appendChildEl(doc, enumEl, 'values') + for (const v of dt.values) { + appendChildEl(doc, valuesEl, 'value', { name: v.description }) + } + if (dt.initialValue) { + const ivEl = appendChildEl(doc, dtEl, 'initialValue') + appendChildEl(doc, ivEl, 'simpleValue', { value: dt.initialValue }) + } + return + } + if (dt.derivation === 'structure') { + const baseTypeEl = appendChildEl(doc, dtEl, 'baseType') + const structEl = appendChildEl(doc, baseTypeEl, 'struct') + for (const v of dt.variable) { + const fieldEl = appendChildEl(doc, structEl, 'variable', { name: v.name }) + const typeEl = appendChildEl(doc, fieldEl, 'type') + appendTypeContent(doc, typeEl, v.type) + if (v.initialValue !== undefined && v.initialValue !== '') { + const ivEl = appendChildEl(doc, fieldEl, 'initialValue') + appendChildEl(doc, ivEl, 'simpleValue', { value: v.initialValue }) + } + } + return + } + // directly-derived + const baseTypeEl = appendChildEl(doc, dtEl, 'baseType') + appendTypeLeaf(doc, baseTypeEl, dt.baseType) + if (dt.initialValue) { + const ivEl = appendChildEl(doc, dtEl, 'initialValue') + appendChildEl(doc, ivEl, 'simpleValue', { value: dt.initialValue }) + } +} + +/* ─────────────────────────── configuration emission ─────────────────────── */ + +function appendTask( + doc: Document, + parent: Element, + task: TranspileTask, + allInstances: TranspileProject['configuration']['instances'], +): void { + const attrs: Record = { + name: task.name, + priority: String(task.priority), + } + if (task.triggering === 'Cyclic') { + attrs.interval = task.interval ?? '' + } else { + attrs.single = task.single ?? '' + } + const taskEl = appendChildEl(doc, parent, 'task', attrs) + for (const inst of allInstances) { + if (inst.task !== task.name) continue + appendChildEl(doc, taskEl, 'pouInstance', { name: inst.name, typeName: inst.program }) + } +} + +/* ─────────────────────────── generic object→DOM ─────────────────────────── */ + +/** + * Walk an xmlbuilder2 plain object and materialise matching DOM + * elements under `parent`. Conventions mirrored: + * + * - Key prefixed `'@'` → attribute on `parent`. + * - Key `'$'` → text content on `parent` (used by xmlbuilder2 to + * emit CDATA-like body strings). + * - Key with array value → emit one child element per array entry, + * all named after the key. + * - Key with object value → emit one child element named after the + * key and recurse into its content. + * - Key with `xhtml:` namespace prefix → element placed under the + * XHTML namespace. + * - Anything else (string, number, boolean) → emit a child element + * with `textContent = String(value)`. + * + * Handles every shape `ladderToXml` and `fbdToXml` produce. + */ +function objectToDom(doc: Document, parent: Element, obj: unknown): void { + if (obj === null || typeof obj !== 'object') return + for (const [key, value] of Object.entries(obj as Record)) { + if (key.startsWith('@')) { + parent.setAttribute(key.slice(1), String(value)) + continue + } + if (key === '$' || key === '#text') { + parent.appendChild(doc.createTextNode(String(value))) + continue + } + if (Array.isArray(value)) { + for (const item of value) { + const child = createNamedElement(doc, key) + if (item !== null && typeof item === 'object') { + objectToDom(doc, child, item) + } else if (item !== undefined) { + child.appendChild(doc.createTextNode(String(item))) + } + parent.appendChild(child) + } + continue + } + const child = createNamedElement(doc, key) + if (value !== null && typeof value === 'object') { + objectToDom(doc, child, value) + } else if (value !== undefined && value !== '') { + child.appendChild(doc.createTextNode(String(value))) + } + parent.appendChild(child) + } +} + +function createNamedElement(doc: Document, key: string): Element { + // xhtml:p → XHTML namespace; otherwise TC6. + if (key.startsWith('xhtml:')) return doc.createElementNS(XHTML_NS, key) + return doc.createElementNS(TC6_NS, key) +} + +/* ─────────────────────────── helpers ────────────────────────────────────── */ + +function appendChildEl( + doc: Document, + parent: Element, + name: string, + attrs?: Record, +): Element { + const el = doc.createElementNS(TC6_NS, name) + if (attrs) { + for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v) + } + parent.appendChild(el) + return el +} + +function appendXhtmlP(doc: Document, parent: Element, text: string): void { + const p = doc.createElementNS(XHTML_NS, 'xhtml:p') + // The DOM walker reads textContent off ``; CDATA vs text + // node makes no observable difference for the consumer. + p.appendChild(doc.createTextNode(text)) + parent.appendChild(p) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/from-xmlbuilder.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/from-xmlbuilder.ts new file mode 100644 index 000000000..257be4e85 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/from-xmlbuilder.ts @@ -0,0 +1,260 @@ +/** + * Convert an xmlbuilder2 plain-object body subtree (the shape + * `ladderToXml` / `fbdToXml` in `frontend/utils/PLC/xml-generator/` + * produce) into the JSON-native `LdBody` IR consumed by the LD/FBD + * walker. + * + * Conventions mirrored: + * - `'@attr'` keys → attribute values + * - `key: {…}` → single child element + * - `key: [{…}, {…}]` → repeated child elements + * + * Lives in openplc-web because the production adapter calls + * `ladderToXml(rungs)` first (the existing React Flow → XML path) + * then runs this converter to get the IR the worker expects. + * Phase 4 (post-this-integration) replaces the two-step + * ladderToXml + xmlbuilder→IR with a single React Flow → IR + * converter, dropping ladder-xml.ts as a dependency. + */ + +import type { Connection, LdBody, LdInstance, Position } from './types' + +type Obj = Record + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +/** + * Accepts the wrapper object `ladderToXml` or `fbdToXml` returns + * (`{ body: { LD: {…} } }` or `{ body: { FBD: {…} } }`) and walks + * the inner body content, materialising one `LdInstance` per child + * element. + */ +export function xmlObjectToLdBody(wrapper: Record): LdBody { + const body = asObject(wrapper.body) + if (!body) return { instances: [] } + const inner = asObject(body.LD) ?? asObject(body.FBD) + if (!inner) return { instances: [] } + + const instances: LdInstance[] = [] + for (const [tag, value] of Object.entries(inner)) { + if (tag.startsWith('@') || tag === '$') continue + for (const child of asElementArray(value)) { + const inst = elementToInstance(tag, child) + if (inst) instances.push(inst) + } + } + return { instances } +} + +/* ─────────────────────────── per-tag parsers ────────────────────────────── */ + +function elementToInstance(tag: string, el: Obj): LdInstance | null { + const localId = getAttrInt(el, 'localId') + if (localId === undefined) return null + const position = getPosition(el) + + if (tag === 'leftPowerRail') { + return { kind: 'leftPowerRail', localId, position } + } + if (tag === 'rightPowerRail') { + return { + kind: 'rightPowerRail', + localId, + position, + connections: collectConnections(el), + } + } + if (tag === 'contact') { + return { + kind: 'contact', + localId, + position, + variable: getTextChild(el, 'variable'), + modifier: buildContactModifier(el), + connections: collectConnections(el), + } + } + if (tag === 'coil') { + const eoid = getAttrInt(el, 'executionOrderId') + const result: LdInstance = { + kind: 'coil', + localId, + position, + variable: getTextChild(el, 'variable'), + modifier: buildCoilModifier(el), + connections: collectConnections(el), + } + if (eoid !== undefined) (result as { executionOrderId?: number }).executionOrderId = eoid + return result + } + if (tag === 'block') { + const inputsEl = asObject(el.inputVariables) + const outputsEl = asObject(el.outputVariables) + const inOutsEl = asObject(el.inOutVariables) + const inputs = readBlockVariables(inputsEl) + const outputs = readBlockVariables(outputsEl).map((v) => ({ formalParameter: v.formalParameter })) + const inOuts = readBlockVariables(inOutsEl) + const eoid = getAttrInt(el, 'executionOrderId') + const instName = getAttrString(el, 'instanceName') + const result: LdInstance = { + kind: 'block', + localId, + position, + typeName: getAttrString(el, 'typeName') ?? '', + inputs, + outputs, + inOuts, + } + if (instName !== undefined) (result as { instanceName?: string }).instanceName = instName + if (eoid !== undefined) (result as { executionOrderId?: number }).executionOrderId = eoid + return result + } + if (tag === 'inVariable') { + return { + kind: 'inVariable', + localId, + position, + expression: getTextChild(el, 'expression'), + } + } + if (tag === 'outVariable') { + const eoid = getAttrInt(el, 'executionOrderId') + const result: LdInstance = { + kind: 'outVariable', + localId, + position, + expression: getTextChild(el, 'expression'), + connections: collectConnections(el), + } + if (eoid !== undefined) (result as { executionOrderId?: number }).executionOrderId = eoid + return result + } + if (tag === 'inOutVariable') { + return { + kind: 'inOutVariable', + localId, + position, + expression: getTextChild(el, 'expression'), + connections: collectConnections(el), + } + } + if (tag === 'connector') { + return { + kind: 'connector', + localId, + position, + name: getAttrString(el, 'name') ?? '', + connections: collectConnections(el), + } + } + if (tag === 'continuation') { + return { + kind: 'continuation', + localId, + position, + name: getAttrString(el, 'name') ?? '', + } + } + return null +} + +/* ─────────────────────────── helpers ────────────────────────────────────── */ + +function asObject(v: unknown): Obj | null { + if (v && typeof v === 'object' && !Array.isArray(v)) return v as Obj + return null +} + +function asElementArray(v: unknown): Obj[] { + if (Array.isArray(v)) { + return v.filter((item): item is Obj => item !== null && typeof item === 'object' && !Array.isArray(item)) + } + if (v && typeof v === 'object') return [v as Obj] + return [] +} + +function getAttrString(el: Obj, name: string): string | undefined { + const v = el[`@${name}`] + if (v === undefined || v === null) return undefined + return String(v) +} + +function getAttrInt(el: Obj, name: string): number | undefined { + const s = getAttrString(el, name) + if (s === undefined) return undefined + const n = Number.parseInt(s, 10) + return Number.isFinite(n) ? n : undefined +} + +function getAttrBool(el: Obj, name: string): boolean { + return getAttrString(el, name) === 'true' +} + +function getTextChild(el: Obj, key: string): string { + const v = el[key] + if (typeof v === 'string') return v + if (typeof v === 'number') return String(v) + if (v && typeof v === 'object') { + // xmlbuilder2 uses `'$'` for text content; tolerate it. + const obj = v as Obj + const dollar = obj.$ ?? obj['#text'] + if (typeof dollar === 'string') return dollar + } + return '' +} + +function getPosition(el: Obj): Position { + const pos = asObject(el.position) + if (!pos) return { x: 0, y: 0 } + return { x: getAttrInt(pos, 'x') ?? 0, y: getAttrInt(pos, 'y') ?? 0 } +} + +function collectConnections(el: Obj): Connection[] { + // `connectionPointIn` can be a single object (most instances) or + // an array (rightPowerRail / coil with multiple parallel inputs). + const cpIns = asElementArray(el.connectionPointIn) + const out: Connection[] = [] + for (const cpIn of cpIns) { + for (const conn of asElementArray(cpIn.connection)) { + const ref = getAttrInt(conn, 'refLocalId') + if (ref === undefined) continue + const fp = getAttrString(conn, 'formalParameter') + const result: Connection = { refLocalId: ref } + if (fp !== undefined) result.refFormalParameter = fp + out.push(result) + } + } + return out +} + +function buildContactModifier(el: Obj): { negated?: boolean; edge?: 'rising' | 'falling' } { + const mod: { negated?: boolean; edge?: 'rising' | 'falling' } = {} + if (getAttrBool(el, 'negated')) mod.negated = true + const edge = getAttrString(el, 'edge') + if (edge === 'rising' || edge === 'falling') mod.edge = edge + return mod +} + +function buildCoilModifier( + el: Obj, +): { negated?: boolean; storage?: 'set' | 'reset'; edge?: 'rising' | 'falling' } { + const mod: { negated?: boolean; storage?: 'set' | 'reset'; edge?: 'rising' | 'falling' } = {} + if (getAttrBool(el, 'negated')) mod.negated = true + const storage = getAttrString(el, 'storage') + if (storage === 'set' || storage === 'reset') mod.storage = storage + const edge = getAttrString(el, 'edge') + if (edge === 'rising' || edge === 'falling') mod.edge = edge + return mod +} + +function readBlockVariables(wrap: Obj | null): { formalParameter: string; connections: Connection[] }[] { + if (!wrap) return [] + const out: { formalParameter: string; connections: Connection[] }[] = [] + for (const v of asElementArray(wrap.variable)) { + out.push({ + formalParameter: getAttrString(v, 'formalParameter') ?? '', + connections: collectConnections(v), + }) + } + return out +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/modifiers.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/modifiers.ts new file mode 100644 index 000000000..23b8852ab --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/modifiers.ts @@ -0,0 +1,83 @@ +/** + * Modifier extraction for contacts and coils. + * + * Mirrors `src/PLCGenerator/modifiers.ts` exactly — three modifier + * kinds, byte-faithful emission: + * + * - `negated` → wrap expression in `NOT(...)` + * - `storage` (`set` / `reset`) → side-effect on the trailing + * program chunks; return a short "TRUE; END_IF" / "FALSE; END_IF" + * reference the caller substitutes + * - `edge` (`rising` / `falling`) → synthesize an `R_TRIG_N` / + * `F_TRIG_N` instance variable, emit its `(CLK := expr);` + * invocation, return `.Q` reference + * + * State carried via `WalkerState` (defined in `walker.ts`). + */ + +import type { Location, ProgramChunk } from './path_tree' +import type { ContactModifier, CoilModifier } from './types' +import type { WalkerState } from './walker_state' + +export function extractModifier( + state: WalkerState, + modifier: ContactModifier | CoilModifier, + expression: ProgramChunk[], + varInfo: Location, +): ProgramChunk[] { + if (modifier.negated) { + return [['NOT(', [...varInfo, 'negated']], ...expression, [')', []]] + } + if ('storage' in modifier) { + const storage = modifier.storage + if (storage === 'set' || storage === 'reset') { + state.program.push([`${state.currentIndent}IF `, [...varInfo, storage]]) + state.program.push(...expression) + state.program.push([' THEN\n ', []]) + const value = storage === 'set' ? 'TRUE' : 'FALSE' + return [[`${value}; (*${storage}*)\n${state.currentIndent}END_IF`, []]] + } + } + if (modifier.edge === 'rising') { + return addTrigger(state, 'R_TRIG', expression, [...varInfo, 'rising']) + } + if (modifier.edge === 'falling') { + return addTrigger(state, 'F_TRIG', expression, [...varInfo, 'falling']) + } + return expression +} + +/** Inject a `R_TRIG`/`F_TRIG` instance var into the POU's interface + * and emit its `(CLK := expr);` invocation. Returns `[`.Q`]` + * for the caller to substitute. */ +function addTrigger( + state: WalkerState, + edge: 'R_TRIG' | 'F_TRIG', + expression: ProgramChunk[], + varInfo: Location, +): ProgramChunk[] { + ensureTriggerVarSection(state) + let i = 1 + let name = `${edge}${i}` + while (state.declaredVars.has(name)) { + i++ + name = `${edge}${i}` + } + state.declaredVars.add(name) + state.triggerVars.push({ name, type: edge }) + + state.program.push([state.currentIndent, []]) + state.program.push([name, varInfo]) + state.program.push(['(CLK := ', []]) + state.program.push(...expression) + state.program.push([');\n', []]) + + return [[`${name}.Q`, varInfo]] +} + +function ensureTriggerVarSection(state: WalkerState): void { + // Trigger vars accumulate into `state.triggerVars`; the caller + // flushes them into the POU's `VAR` block before final assembly. + // No-op here beyond ensuring the array exists (always does). + void state +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/path_tree.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/path_tree.ts new file mode 100644 index 000000000..f76337e7f --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/path_tree.ts @@ -0,0 +1,253 @@ +/** + * Path-tree data model + algorithms — the LD/FBD core that turns a + * graph walk into ST chunks. + * + * Mirrors `src/PLCGenerator/path_tree.ts` line-for-line but reads + * from the JSON `LdBody` IR instead of `@xmldom` `Element`s. The + * algorithm itself (factorization, ComputePaths, pythonRepr-stable + * keying) is pure JS and is preserved verbatim from the DOM walker. + * + * Chunks emitted here are the same `[text, location]` tuples the + * existing transpiler produces, so downstream assembly stays + * compatible. + */ + +/* ─────────────────────────── chunk model ────────────────────────────────── */ + +export type LocationAtom = string | number | readonly (string | number)[] +export type Location = readonly LocationAtom[] +export type ProgramChunk = readonly [text: string, location: Location] + +/* ─────────────────────────── path nodes ─────────────────────────────────── */ + +/** + * One node in the path tree. Same shape as the DOM walker's + * `PathNode`; algorithms below are byte-faithful ports. + */ +export type PathNode = + | { readonly kind: 'true' } + | { readonly kind: 'leaf'; readonly chunks: readonly ProgramChunk[] } + | { readonly kind: 'and'; readonly children: readonly PathNode[] } + | { readonly kind: 'or'; readonly children: readonly PathNode[] } + +export const TRUE_NODE: PathNode = { kind: 'true' } + +export function leafNode(chunks: ProgramChunk[]): PathNode { + return { kind: 'leaf', chunks } +} + +/* ─────────────────────────── pythonRepr ─────────────────────────────────── */ + +/** + * Stable structural key for a PathNode — used by `factorizePaths` to + * detect common terms. Identical to the Python `repr` output the + * original xml2st pipeline keys on. + */ +export function pythonReprNode(node: PathNode): string { + switch (node.kind) { + case 'true': + return 'None' + case 'leaf': + return pythonReprChunks(node.chunks) + case 'and': + return `[${node.children.map(pythonReprNode).join(', ')}]` + case 'or': + if (node.children.length === 0) return '()' + if (node.children.length === 1) return `(${pythonReprNode(node.children[0])},)` + return `(${node.children.map(pythonReprNode).join(', ')})` + } +} + +export function pythonReprChunks(chunks: readonly ProgramChunk[]): string { + return `[${chunks.map(pythonReprChunk).join(', ')}]` +} + +function pythonReprChunk(chunk: ProgramChunk): string { + const [text, location] = chunk + return `(${pythonReprString(text)}, ${pythonReprLocation(location)})` +} + +function pythonReprLocation(loc: Location): string { + if (loc.length === 0) return '()' + if (loc.length === 1) return `(${pythonReprPrimitive(loc[0])},)` + return `(${loc.map(pythonReprPrimitive).join(', ')})` +} + +function pythonReprPrimitive(v: LocationAtom): string { + if (Array.isArray(v)) { + if (v.length === 0) return '()' + if (v.length === 1) return `(${pythonReprPrimitive(v[0])},)` + return `(${v.map(pythonReprPrimitive).join(', ')})` + } + if (typeof v === 'number') { + return Number.isInteger(v) ? v.toString(10) : v.toString() + } + return pythonReprString(v as string) +} + +export function pythonReprString(s: string): string { + // Python prefers single quotes; switches to double quotes if the + // string contains a `'` and not a `"`. + const hasSingle = s.includes("'") + const hasDouble = s.includes('"') + const quote = hasSingle && !hasDouble ? '"' : "'" + let out = '' + for (const ch of s) { + const code = ch.codePointAt(0) ?? 0 + if (ch === quote) out += `\\${ch}` + else if (ch === '\\') out += '\\\\' + else if (ch === '\n') out += '\\n' + else if (ch === '\r') out += '\\r' + else if (ch === '\t') out += '\\t' + else if (code < 0x20 || code === 0x7f) out += `\\x${code.toString(16).padStart(2, '0')}` + else out += ch + } + return `${quote}${out}${quote}` +} + +/* ─────────────────────────── factorizePaths ─────────────────────────────── */ + +/** + * Boolean simplification — `(A AND B) OR (A AND C)` → `A AND (B OR C)`. + * + * Strategy mirrors `FactorizePaths` (PLCGenerator.py:1429): + * 1. Sort the paths by their Python `repr`. + * 2. For each pair, find common head/tail prefixes/suffixes between + * adjacent AND nodes. + * 3. Replace with `[common_head, OR(unique_middles), common_tail]`. + */ +export function factorizePaths(paths: readonly PathNode[]): PathNode[] { + if (paths.length <= 1) return [...paths] + + // Sort by stable repr key — same order Python produces. + const sorted = pythonStableSort(paths) + + // Walk sorted list, group adjacent paths sharing head AND. + const out: PathNode[] = [] + let i = 0 + while (i < sorted.length) { + let j = i + 1 + // Find run of paths that share the same head/tail with sorted[i]. + while (j < sorted.length && canFactor(sorted[i], sorted[j])) j++ + if (j - i === 1) { + out.push(sorted[i]) + i = j + continue + } + // Build the factored shape. + const group = sorted.slice(i, j) + out.push(factorGroup(group)) + i = j + } + return out +} + +/** Stable Python-style sort by `pythonReprNode` key. */ +export function pythonStableSort(nodes: readonly PathNode[]): PathNode[] { + return [...nodes].sort((a, b) => { + const ka = pythonReprNode(a) + const kb = pythonReprNode(b) + return ka < kb ? -1 : ka > kb ? 1 : 0 + }) +} + +/** Two paths can be factored if they share at least one head OR tail leaf. */ +function canFactor(a: PathNode, b: PathNode): boolean { + const aList = andChildren(a) + const bList = andChildren(b) + if (aList.length === 0 || bList.length === 0) return false + return ( + pythonReprNode(aList[0]) === pythonReprNode(bList[0]) || + pythonReprNode(aList[aList.length - 1]) === pythonReprNode(bList[bList.length - 1]) + ) +} + +function andChildren(node: PathNode): readonly PathNode[] { + return node.kind === 'and' ? node.children : [node] +} + +function factorGroup(group: readonly PathNode[]): PathNode { + // Find common head length and common tail length. + const lists = group.map(andChildren) + let headLen = 0 + outerHead: while (true) { + const ref = lists[0][headLen] + if (ref === undefined) break + const refKey = pythonReprNode(ref) + for (let k = 1; k < lists.length; k++) { + if (lists[k][headLen] === undefined) break outerHead + if (pythonReprNode(lists[k][headLen]) !== refKey) break outerHead + } + headLen++ + } + let tailLen = 0 + outerTail: while (true) { + const minLeft = Math.min(...lists.map((l) => l.length - headLen)) + if (tailLen >= minLeft) break + const ref = lists[0][lists[0].length - 1 - tailLen] + const refKey = pythonReprNode(ref) + for (let k = 1; k < lists.length; k++) { + if (pythonReprNode(lists[k][lists[k].length - 1 - tailLen]) !== refKey) break outerTail + } + tailLen++ + } + + const head = lists[0].slice(0, headLen) + const tail = headLen + tailLen >= lists[0].length ? [] : lists[0].slice(lists[0].length - tailLen) + const middles: PathNode[] = lists.map((l) => { + const mid = l.slice(headLen, l.length - tailLen) + if (mid.length === 0) return TRUE_NODE + if (mid.length === 1) return mid[0] + return { kind: 'and', children: mid } + }) + + const orNode: PathNode = { kind: 'or', children: pythonStableSort(middles) } + const allChildren: PathNode[] = [...head, orNode, ...tail] + if (allChildren.length === 1) return allChildren[0] + return { kind: 'and', children: allChildren } +} + +/* ─────────────────────────── computePaths ───────────────────────────────── */ + +/** + * Turn a `PathNode` into its ST expression chunks. + * + * `first=true` at the outermost call suppresses the parenthesis + * around the top-level OR — matches Python's `first` flag. + */ +export function computePaths(node: PathNode, first = false): ProgramChunk[] { + switch (node.kind) { + case 'true': + return [['TRUE', []]] + case 'leaf': + return [...node.chunks] + case 'and': + return joinChunkLists( + node.children.map((c) => computePaths(c)), + [[' AND ', []]], + ) + case 'or': { + const inner = joinChunkLists( + node.children.map((c) => computePaths(c)), + [[' OR ', []]], + ) + if (first) return inner + const out: ProgramChunk[] = [['(', []]] + out.push(...inner) + out.push([')', []]) + return out + } + } +} + +function joinChunkLists( + groups: readonly (readonly ProgramChunk[])[], + separator: readonly ProgramChunk[], +): ProgramChunk[] { + const out: ProgramChunk[] = [] + for (let i = 0; i < groups.length; i++) { + if (i > 0) out.push(...separator) + out.push(...groups[i]) + } + return out +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/types.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/types.ts new file mode 100644 index 000000000..797704e06 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/types.ts @@ -0,0 +1,114 @@ +/** + * Plain-JSON intermediate representation for an LD body. + * + * Mirrors what the existing DOM-based walker reads off the parsed + * `` subtree, but as a structured-clonable JS object so the + * walker stops touching `@xmldom`. Both the openplc-web React Flow + * adapter and the test-side XML→IR converter target this same shape + * so the walker is verified against the same fixtures the DOM + * walker uses today. + */ + +export interface Position { + x: number + y: number +} + +/** An incoming wire on a `connectionPointIn`. */ +export interface Connection { + refLocalId: number + /** Disambiguates which output of a multi-output upstream block the + * connection reads (e.g. `"Q"` / `"ET"` on a TON). */ + refFormalParameter?: string +} + +export interface ContactModifier { + negated?: boolean + edge?: 'rising' | 'falling' +} + +export interface CoilModifier { + negated?: boolean + storage?: 'set' | 'reset' + edge?: 'rising' | 'falling' +} + +export interface BlockInputVar { + formalParameter: string + connections: Connection[] +} + +export interface BlockOutputVar { + formalParameter: string +} + +export type LdInstance = + | { kind: 'leftPowerRail'; localId: number; position: Position } + | { kind: 'rightPowerRail'; localId: number; position: Position; connections: Connection[] } + | { + kind: 'contact' + localId: number + position: Position + variable: string + modifier: ContactModifier + connections: Connection[] + } + | { + kind: 'coil' + localId: number + position: Position + variable: string + modifier: CoilModifier + connections: Connection[] + executionOrderId?: number + } + | { + kind: 'block' + localId: number + position: Position + typeName: string + instanceName?: string + executionOrderId?: number + inputs: BlockInputVar[] + outputs: BlockOutputVar[] + inOuts: BlockInputVar[] + } + | { + kind: 'inVariable' + localId: number + position: Position + expression: string + } + | { + kind: 'outVariable' + localId: number + position: Position + expression: string + connections: Connection[] + executionOrderId?: number + } + | { + kind: 'inOutVariable' + localId: number + position: Position + expression: string + connections: Connection[] + } + | { kind: 'connector'; localId: number; position: Position; name: string; connections: Connection[] } + | { kind: 'continuation'; localId: number; position: Position; name: string } + +export interface LdBody { + instances: LdInstance[] +} + +/** O(1) lookup convenience: build once at the start of a walk. */ +export function indexById(body: LdBody): Map { + const m = new Map() + for (const inst of body.instances) m.set(inst.localId, inst) + return m +} + +/** Stable narrowing predicate for the dispatch. */ +export function instanceKind(inst: LdInstance): LdInstance['kind'] { + return inst.kind +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/walker.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/walker.ts new file mode 100644 index 000000000..86892aba7 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/walker.ts @@ -0,0 +1,560 @@ +/** + * LD body walker — JSON-native port of + * `src/PLCGenerator/path_tree.ts:generatePaths` plus the per-sink + * dispatch in `src/PLCGenerator/body_emit.ts`. + * + * Input: an `LdBody` IR. + * Output: `state.program` populated with the same `ProgramChunk` + * stream the DOM walker emits, byte-faithful against the fixture + * corpus (verified by `tests/json_walker_corpus.test.ts`). + * + * Scope (incremental): + * - LeftPowerRail / RightPowerRail + * - Contact (with negated / edge modifiers) + * - Coil (with negated / set / reset modifiers) + * - InVariable / OutVariable / InOutVariable + * - Block (function-block instance with formalParameter wiring) + * + * Not yet ported (Phase 3 follow-up): + * - Connector / Continuation (FBD; only marginally relevant to LD) + * - Permissive block-type synthesis for unknown block types + * - EN/ENO gating in cascaded blocks (LD case #21 shows the + * pattern; needs additional plumbing through `computeBlockCall`) + */ + +import { extractModifier } from './modifiers' +import { + computePaths, + factorizePaths, + type Location, + leafNode, + type PathNode, + type ProgramChunk, + TRUE_NODE, +} from './path_tree' +import type { Connection, LdInstance } from './types' +import type { WalkerState } from './walker_state' + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +/** + * Walk the LD body and accumulate ST chunks in `state.program`. + * + * Iteration order: + * 1. Sinks with explicit `executionOrderId` ascending. + * 2. Sinks without execution order, sorted by Y first (10-unit + * tolerance), then X. + * + * Sinks = coils + outVariables + standalone block calls (blocks not + * consumed by any downstream node). + */ +export function emitLdBody(state: WalkerState): void { + // Mirror body_emit.ts dispatch: two buckets keyed on whether the + // instance has a non-zero `executionOrderId`. + // - ordered: emitted first, sorted by executionOrderId ascending. + // Recursion launched from these sinks runs with `order=true`, + // which suppresses eager emission of upstream blocks (they + // emit at their own iteration step). + // - others: emitted after, sorted by position (Y first 10-unit + // tolerance, then X). `order=false` so recursion eagerly + // emits any not-yet-emitted upstream block. + const ordered: LdInstance[] = [] + const others: LdInstance[] = [] + + for (const inst of state.body.instances) { + if (inst.kind !== 'outVariable' && inst.kind !== 'inOutVariable' && + inst.kind !== 'coil' && inst.kind !== 'block' && inst.kind !== 'connector') { + continue + } + const eoid = + 'executionOrderId' in inst ? (inst.executionOrderId ?? 0) : 0 + if (eoid > 0) ordered.push(inst) + else others.push(inst) + } + + ordered.sort((a, b) => { + const ao = 'executionOrderId' in a ? (a.executionOrderId ?? 0) : 0 + const bo = 'executionOrderId' in b ? (b.executionOrderId ?? 0) : 0 + return ao - bo + }) + others.sort(comparePosition) + + for (const inst of ordered) dispatchInstance(state, inst, /*order=*/ true) + for (const inst of others) dispatchInstance(state, inst, /*order=*/ false) +} + +function dispatchInstance(state: WalkerState, inst: LdInstance, order: boolean): void { + if (inst.kind === 'coil') return emitCoil(state, inst, order) + if (inst.kind === 'outVariable') return emitOutVariable(state, inst, order) + if (inst.kind === 'inOutVariable') return emitInOutVariable(state, inst, order) + if (inst.kind === 'block') return emitStandaloneBlockCall(state, inst, order) + if (inst.kind === 'connector') return emitConnector(state, inst) +} + +/** + * Connector iteration step. Walks the connector's upstream and + * caches the resulting expression under the connector's `name`. + * Continuation visits later look up the cached chunks. + */ +function emitConnector( + state: WalkerState, + conn: Extract, +): void { + if (state.connectorExprs.has(conn.name)) return + const paths = generatePaths(state, conn.connections, false) + if (paths.length === 0) return + state.connectorExprs.set(conn.name, pathsToChunks(paths)) +} + +/* ─────────────────────────── position sort ──────────────────────────────── */ + +function comparePosition(a: LdInstance, b: LdInstance): number { + // Mirror PLCGenerator.py:89 — sort by Y (10-unit tolerance) then X. + const ax = Math.trunc(a.position.x) + const ay = Math.trunc(a.position.y) + const bx = Math.trunc(b.position.x) + const by = Math.trunc(b.position.y) + if (Math.abs(ay - by) >= 10) return ay - by + return ax - bx +} + +/* ─────────────────────────── per-sink emitters ──────────────────────────── */ + +function emitCoil( + state: WalkerState, + coil: Extract, + _order: boolean, +): void { + void _order + // Top-level sinks always invoke generatePaths with order=false — + // matches `body_emit.emitCoil` / `emitOutVariable` not threading the + // sink's own eoid into computeExpression. Order suppression only + // applies inside block→block recursion (see `buildInputArgs`). + const paths = generatePaths(state, coil.connections, false) + if (paths.length === 0) { + state.warnings.push(`Coil "${coil.variable}" must be connected.`) + return + } + const expr = pathsToChunks(paths) + const coilInfo: Location = [state.tagName, 'coil', coil.localId] + const modified = extractModifier(state, coil.modifier, expr, coilInfo) + state.program.push([state.currentIndent, []]) + state.program.push([coil.variable, [...coilInfo, 'reference']]) + state.program.push([' := ', []]) + state.program.push(...modified) + state.program.push([';\n', []]) +} + +function emitOutVariable( + state: WalkerState, + ov: Extract, + _order: boolean, +): void { + void _order + const paths = generatePaths(state, ov.connections, false) + if (paths.length === 0) return + const expr = pathsToChunks(paths) + const info: Location = [state.tagName, 'io_variable', ov.localId, 'expression'] + + // ENO gating — wrap the assignment in `IF THEN ... END_IF;` + // when the upstream block has its EN input wired (mirrors + // PLCGenerator.py:1243 `GetUsedEno`). + const enoVar = getUsedEno(state, ov.connections) + if (enoVar !== null) { + state.program.push([`${state.currentIndent}IF ${enoVar}`, []]) + state.program.push([' THEN\n ', []]) + state.currentIndent += ' ' + } + + state.program.push([state.currentIndent, []]) + state.program.push([ov.expression, info]) + state.program.push([' := ', []]) + state.program.push(...expr) + state.program.push([';\n', []]) + + if (enoVar !== null) { + state.currentIndent = state.currentIndent.slice(0, -2) + state.program.push([`${state.currentIndent}END_IF;\n`, []]) + } +} + +/** + * If the single upstream connection of an outVariable points at a + * block with an `EN` input wired, return the `ENO` reference that + * gates downstream assignment. Mirrors PLCGenerator.py:1243. + */ +function getUsedEno(state: WalkerState, connections: readonly Connection[]): string | null { + if (connections.length !== 1) return null + const next = state.byId.get(connections[0].refLocalId) + if (!next || next.kind !== 'block') return null + for (const input of next.inputs) { + if (input.formalParameter !== 'EN') continue + if (input.connections.length === 0) return null + if (next.instanceName !== undefined) { + return `${next.instanceName}.ENO` + } + return `_TMP_${next.typeName}${next.localId}_ENO` + } + return null +} + +function emitInOutVariable( + state: WalkerState, + iov: Extract, + _order: boolean, +): void { + void _order + const paths = generatePaths(state, iov.connections, false) + if (paths.length === 0) return + const expr = pathsToChunks(paths) + const info: Location = [state.tagName, 'io_variable', iov.localId, 'expression'] + state.program.push([state.currentIndent, []]) + state.program.push([iov.expression, info]) + state.program.push([' := ', []]) + state.program.push(...expr) + state.program.push([';\n', []]) +} + +function emitStandaloneBlockCall( + state: WalkerState, + blk: Extract, + _order: boolean, +): void { + void _order + // A block reached via the body iteration is unconditionally emitted + // (idempotent via emittedBlocks). This is the hook that lets + // cascaded blocks emit in their iteration order even when their + // outputs feed downstream consumers. + if (blk.instanceName !== undefined) { + emitFunctionBlockCall(state, blk) + } else { + emitFunctionCall(state, blk) + } +} + +/* ─────────────────────────── generatePaths (LD walk) ────────────────────── */ + +/** + * Walk backward from a set of `Connection`s through the LD graph, + * building a `PathNode[]` (lists for series, tuples for parallels). + * Mirrors `src/PLCGenerator/path_tree.ts:generatePaths`. + */ +export function generatePaths( + state: WalkerState, + connections: readonly Connection[], + order = false, +): PathNode[] { + const paths: PathNode[] = [] + for (const conn of connections) { + const next = state.byId.get(conn.refLocalId) + if (!next) continue + const node = visit(state, next, conn, order) + if (node !== undefined) paths.push(node) + } + return paths +} + +function visit( + state: WalkerState, + inst: LdInstance, + conn: Connection, + order: boolean, +): PathNode | undefined { + switch (inst.kind) { + case 'leftPowerRail': + return TRUE_NODE + + case 'inVariable': + case 'inOutVariable': + return leafNode([ + [ + inst.expression, + [state.tagName, 'io_variable', inst.localId, 'expression'], + ], + ]) + + case 'block': + return visitBlockOutput(state, inst, conn, order) + + case 'contact': + return visitContact(state, inst, order) + + case 'coil': { + // In LD, a coil's wire passes THROUGH it — downstream nodes + // (a block input wired to the same signal that drives this + // coil) can tap off it. Walk back through `inst.connections` + // to find whatever drives the coil and return that as the + // path. Mirrors `path_tree.ts:Coil` branch (PLCGenerator.py). + const upstream = generatePaths(state, inst.connections, order) + if (upstream.length === 0) return undefined + if (upstream.length === 1) return upstream[0] + const factored = factorizePaths(upstream) + if (factored.length === 1) return factored[0] + return { kind: 'or', children: factored } + } + + case 'continuation': { + // Look up the cached connector expression by name. If the + // matching connector hasn't been iterated yet, attempt to + // resolve it eagerly so the continuation can produce a value + // even when the connector appears later in iteration order. + const cached = state.connectorExprs.get(inst.name) + if (cached) return leafNode([...cached]) + const connector = findConnectorByName(state, inst.name) + if (connector) { + emitConnector(state, connector) + const resolved = state.connectorExprs.get(inst.name) + if (resolved) return leafNode([...resolved]) + } + state.warnings.push(`continuation "${inst.name}" has no matching connector`) + return undefined + } + + case 'connector': + case 'rightPowerRail': + case 'outVariable': + return undefined + } +} + +function findConnectorByName( + state: WalkerState, + name: string, +): Extract | null { + for (const inst of state.body.instances) { + if (inst.kind === 'connector' && inst.name === name) return inst + } + return null +} + +function visitContact( + state: WalkerState, + inst: Extract, + order: boolean, +): PathNode { + const contactInfo: Location = [state.tagName, 'contact', inst.localId] + const variableChunks: ProgramChunk[] = [ + [inst.variable, [...contactInfo, 'reference']], + ] + const variableLeaf = leafNode(extractModifier(state, inst.modifier, variableChunks, contactInfo)) + + const upstream = generatePaths(state, inst.connections, order) + if (upstream.length === 0) { + state.warnings.push(`Contact "${inst.variable}" must be connected.`) + return variableLeaf + } + if (upstream.length === 1) { + const only = upstream[0] + if (only.kind === 'true') return variableLeaf + if (only.kind === 'and') { + return { kind: 'and', children: [variableLeaf, ...only.children] } + } + return { kind: 'and', children: [variableLeaf, only] } + } + const factored = factorizePaths(upstream) + if (factored.length > 1) { + return { + kind: 'and', + children: [variableLeaf, { kind: 'or', children: factored }], + } + } + const tail = factored[0] + const tailChildren = tail.kind === 'and' ? tail.children : [tail] + return { kind: 'and', children: [variableLeaf, ...tailChildren] } +} + +/* ─────────────────────────── block emission ─────────────────────────────── */ + +/** + * Build the chunk sequence for one block-output read. Side effect: + * emits the block call statement into `state.program` if not yet + * emitted (function-block path) or pre-computes the temp var + * assignment (function path). + * + * Returns the leaf naming `instance.formalParameter` (e.g. + * `TON0.Q`) for FBs or `_TMP__` for functions. + */ +function visitBlockOutput( + state: WalkerState, + block: Extract, + conn: Connection, + order: boolean, +): PathNode { + const isFunction = block.instanceName === undefined + if (isFunction) { + const out = conn.refFormalParameter ?? block.outputs[0]?.formalParameter ?? 'OUT' + // When walking from an ordered sink, suppress eager emission — + // the upstream block emits at its own iteration step (and + // registers its temps then). Otherwise emit now (idempotent + // via `emittedBlocks`). + if (!order) emitFunctionCall(state, block) + const tempName = `_TMP_${block.typeName}${block.localId}_${out}` + return leafNode([ + [tempName, [state.tagName, 'block', block.localId, 'output', out]], + ]) + } + if (!order) emitFunctionBlockCall(state, block) + const instName = block.instanceName! + const out = conn.refFormalParameter ?? block.outputs[0]?.formalParameter ?? 'OUT' + return leafNode([ + [`${instName}.${out}`, [state.tagName, 'block', block.localId, 'output', out]], + ]) +} + +/** + * Function-block path: emit `instance(IN := …, PT := …);` once per + * block (idempotent across multiple output reads). Downstream + * readers access outputs as `instance.`. + */ +function emitFunctionBlockCall( + state: WalkerState, + block: Extract, +): void { + if (state.emittedBlocks.has(block.localId)) return + state.emittedBlocks.add(block.localId) + + const instName = block.instanceName! + const info: Location = [state.tagName, 'block', block.localId] + const parts = buildInputArgs(state, block) + + state.program.push([state.currentIndent, []]) + state.program.push([instName, [...info, 'instance']]) + state.program.push(['(', []]) + for (let i = 0; i < parts.length; i++) { + if (i > 0) state.program.push([', ', []]) + state.program.push(...parts[i]) + } + state.program.push([');\n', []]) +} + +/** + * Function path: emit + * + * _TMP__ := (arg1, arg2, …); + * + * — once per block (dedup via `emittedBlocks`). Mirrors python's + * `generateBlock` function branch (PLCGenerator.py:1494-1644; TS + * reference `xml-to-st/src/PLCGenerator/path_tree.ts:606-826`). + * + * **Argument syntax** — named (`PARAM := expr`) when the block has + * more than one declared output OR not every declared input is + * wired; positional otherwise. Two-output functions (with `ENO`) + * are the common driver of named-arg form. + * + * **Outputs** — every declared output gets a synthesised + * `_TMP__` temp, registered in + * `state.functionTempVars` so the driver can append them to the + * POU's VAR section. Among the registered temps, the "primary" + * output (empty formal parameter, or `OUT`, or a single output) is + * placed on the LHS of the call; other outputs (notably `ENO`) are + * passed as extra `param => tempName` named args inside the call. + */ +function emitFunctionCall( + state: WalkerState, + block: Extract, +): void { + if (state.emittedBlocks.has(block.localId)) return + state.emittedBlocks.add(block.localId) + + const info: Location = [state.tagName, 'block', block.localId] + const allInputConnected = block.inputs.every( + (inp) => inp.connections.length > 0, + ) + const useNamedArgs = block.outputs.length > 1 || !allInputConnected + + // Build input arg parts. + const recurseOrdered = + 'executionOrderId' in block && (block.executionOrderId ?? 0) > 0 + const parts: ProgramChunk[][] = [] + for (const input of block.inputs) { + const upstream = generatePaths(state, input.connections, recurseOrdered) + if (upstream.length === 0) continue + const inputExpr = pathsToChunks(upstream) + if (useNamedArgs) { + parts.push([[`${input.formalParameter} := `, []], ...inputExpr]) + } else { + parts.push(inputExpr) + } + } + + // Register every output's temp var; pick the primary; append + // non-primary outputs as `param => tempName` extras to `parts`. + let primaryName: string | null = null + let primaryFormal = '' + let primaryIdx = 0 + for (let i = 0; i < block.outputs.length; i++) { + const out = block.outputs[i] + const tempName = `_TMP_${block.typeName}${block.localId}_${out.formalParameter}` + const tempType = out.formalParameter === 'ENO' ? 'BOOL' : 'ANY' + state.functionTempVars.push({ + name: tempName, + type: tempType, + originBlockTypeName: block.typeName, + originFormalParameter: out.formalParameter, + }) + const isPrimary = + block.outputs.length === 1 || + out.formalParameter === '' || + out.formalParameter === 'OUT' + if (isPrimary && primaryName === null) { + primaryName = tempName + primaryFormal = out.formalParameter + primaryIdx = i + } else { + parts.push([ + [out.formalParameter, [...info, 'output', i]], + [` => ${tempName}`, []], + ]) + } + } + + // No primary output → nothing meaningful to emit. Corpus never + // triggers this (standard functions always have `OUT` or unnamed). + if (primaryName === null) return + + state.program.push([state.currentIndent, []]) + state.program.push([primaryName, [...info, 'output', primaryIdx]]) + state.program.push([' := ', []]) + state.program.push([block.typeName, [...info, 'type']]) + state.program.push(['(', []]) + for (let i = 0; i < parts.length; i++) { + if (i > 0) state.program.push([', ', []]) + state.program.push(...parts[i]) + } + state.program.push([');\n', []]) + void primaryFormal +} + +/** Build `IN := upstream_expr` chunks for every wired input on a + * function-block instance. Used by `emitFunctionBlockCall`. + * `recurseOrdered` is true when the block's own executionOrderId + * is > 0 — propagated to upstream walks so cascaded ordered blocks + * don't eagerly emit each other. */ +function buildInputArgs( + state: WalkerState, + block: Extract, +): ProgramChunk[][] { + const recurseOrdered = + 'executionOrderId' in block && (block.executionOrderId ?? 0) > 0 + const parts: ProgramChunk[][] = [] + for (const input of block.inputs) { + const upstream = generatePaths(state, input.connections, recurseOrdered) + if (upstream.length === 0) continue + const inputExpr = pathsToChunks(upstream) + const chunk: ProgramChunk[] = [[`${input.formalParameter} := `, []]] + chunk.push(...inputExpr) + parts.push(chunk) + } + return parts +} + + +/* ─────────────────────────── helpers ────────────────────────────────────── */ + +function pathsToChunks(paths: PathNode[]): ProgramChunk[] { + if (paths.length === 0) return [['TRUE', []]] + if (paths.length === 1) return computePaths(paths[0], /*first=*/ true) + const factored = factorizePaths(paths) + if (factored.length === 1) return computePaths(factored[0], /*first=*/ true) + return computePaths({ kind: 'or', children: factored }, /*first=*/ true) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/ld/walker_state.ts b/src/backend/shared/transpilers/generate-st-from-json/ld/walker_state.ts new file mode 100644 index 000000000..6948e1b0b --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/ld/walker_state.ts @@ -0,0 +1,102 @@ +/** + * Mutable state carried through the LD walker. + * + * Replaces `src/PLCGenerator/gen_state.ts` `GenState` for the + * JSON-native walker. Only the fields the LD walker actually + * touches are surfaced here; FBD/SFC ports add their own. + */ + +import type { LdBody, LdInstance } from './types' +import type { ProgramChunk } from './path_tree' + +export interface TriggerVar { + /** `R_TRIG1`, `R_TRIG2`, … or `F_TRIG1`, `F_TRIG2`, … */ + name: string + type: 'R_TRIG' | 'F_TRIG' +} + +/** + * A `_TMP__` variable synthesised + * during a function-call emission to hold one of the function's + * outputs (`OUT`, `ENO`, or any other extra named output). Mirrors + * the `ensureFreshVarSection` + `iface.vars.push` side effect in + * `xml-to-st/src/PLCGenerator/path_tree.ts:789-798`. The caller is + * responsible for emitting these into the POU's VAR section after + * the walk completes. + * + * `type` is `BOOL` for `ENO`, or `'ANY'` for outputs whose type + * isn't statically known by the walker (the driver runs a separate + * type-inference pass against the standard block catalog to resolve + * these — see `xml-to-st/src/PLCGenerator/connection_types.ts`). + */ +export interface FunctionTempVar { + name: string + /** `'BOOL'` for ENO temps; `'ANY'` for primary OUT and other + * outputs (the driver resolves these against `project.pous` / + * the standard block catalog using `originBlockTypeName`). */ + type: string + /** Block's `typeName` (e.g. `'Eq_State'`, `'ADD'`). Lets the + * driver replace `'ANY'` with the function's actual return + * type. */ + originBlockTypeName: string + /** Which output of the block this temp represents (`'OUT'`, + * `'ENO'`, or any other formal parameter on multi-output + * functions). */ + originFormalParameter: string +} + +export interface WalkerState { + /** POU name (used as the first element of every location tuple). */ + tagName: string + /** The LD body being walked, plus an id index built once on entry. */ + body: LdBody + byId: Map + /** Accumulating ST chunk stream (the equivalent of `GenState.program`). */ + program: ProgramChunk[] + /** Current indent string — top-level is `' '` (2 spaces). */ + currentIndent: string + /** Names already declared in the POU interface — used to avoid + * trigger-var name clashes. Caller pre-populates from the POU's + * declared variables before the walk starts. */ + declaredVars: Set + /** Trigger vars synthesised during the walk; caller appends them + * to the POU's VAR section after the walk completes. */ + triggerVars: TriggerVar[] + /** Block local-ids (FB-instance OR function) whose call statement + * has already been emitted — prevents double emission across + * multiple output reads of the same block. Mirrors python's + * `state.computedBlocks[block] = True` flag (PLCGenerator.py:1467). */ + emittedBlocks: Set + /** `_TMP__` variables + * synthesised during function-call emission. The caller appends + * these to the POU's VAR section after the walk. */ + functionTempVars: FunctionTempVar[] + /** Connector → upstream chunks lookup, populated during + * `` iteration and consumed by `` + * resolution. Keyed by `name` attribute. */ + connectorExprs: Map + /** Warnings the walker raises that don't abort emission. */ + warnings: string[] +} + +export function newWalkerState( + tagName: string, + body: LdBody, + declaredVars: Set, +): WalkerState { + const byId = new Map() + for (const inst of body.instances) byId.set(inst.localId, inst) + return { + tagName, + body, + byId, + program: [], + currentIndent: ' ', + declaredVars, + triggerVars: [], + emittedBlocks: new Set(), + functionTempVars: [], + connectorExprs: new Map(), + warnings: [], + } +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/pou-emission-order.ts b/src/backend/shared/transpilers/generate-st-from-json/pou-emission-order.ts new file mode 100644 index 000000000..15db13207 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/pou-emission-order.ts @@ -0,0 +1,129 @@ +/** + * Project-level POU emission order, mirroring python's lazy / + * on-reference scheme. + * + * Python's `PLCGenerator.GeneratePouProgram(pou_name)` is recursive + * (PLCGenerator.py:302): + * + * 1. Iterate `project.getpous()` in source order. + * 2. For each POU, mark it computed BEFORE descending so cycles + * don't infinite-loop. + * 3. While generating the POU's body, every block / variable + * reference that resolves to another project POU triggers + * `GeneratePouProgram(dep)` to emit that dependency to the + * shared `self.Program` list FIRST. + * + * Net effect: a depth-first post-order traversal where dependencies + * are emitted ahead of dependents. This module recreates that order + * up-front so the driver loop is a single linear walk — no callback + * threading required through the per-POU generator. + * + * Dependency extraction (mirrors PLCGenerator.py call sites): + * - `block.typeName` for blocks in `LdBody.instances` + * (PLCGenerator.py:1384, 1827). + * - Variable `type` of `derived` / `user-data-type` definitions + * matching a project POU name (FB instance variables, + * PLCGenerator.py:900). + * - Textual body text scanned with python's identifier regex + * `(?:^|[^0-9^A-Z])NAME(?:$|[^0-9^A-Z])` against UPPER(body) + * (PLCGenerator.py:327-331 `GeneratePouProgramInText`). + */ + +import type { TranspilePou, TranspileProject } from './types' + +export function buildPouEmissionOrder( + pous: readonly TranspilePou[], +): TranspilePou[] { + const byName = new Map() + for (const p of pous) byName.set(p.name, p) + const pouNames = new Set(byName.keys()) + + const emitted = new Set() + const order: TranspilePou[] = [] + + const visit = (name: string): void => { + if (emitted.has(name)) return + emitted.add(name) // mark BEFORE recursion (cycle-safe; matches python) + const pou = byName.get(name) + if (!pou) return + for (const dep of extractPouDeps(pou, pouNames)) visit(dep) + order.push(pou) + } + + for (const p of pous) visit(p.name) + return order +} + +function extractPouDeps( + pou: TranspilePou, + pouNames: ReadonlySet, +): string[] { + const deps = new Set() + + // Variable references: a `derived` / `user-data-type` variable + // whose type name matches another project POU is an FB instance + // declaration — emit the FB definition first. + for (const v of pou.interface?.variables ?? []) { + const def = v.type.definition + if (def === 'derived' || def === 'user-data-type') { + const tname = (v.type as { value: string }).value + if (tname !== pou.name && pouNames.has(tname)) deps.add(tname) + } + } + + // Body references. + if ( + pou.body.language === 'ld' || + pou.body.language === 'fbd' || + pou.body.language === 'sfc' + ) { + if (pou.body.ldBody) { + for (const inst of pou.body.ldBody.instances) { + if ( + inst.kind === 'block' && + inst.typeName !== pou.name && + pouNames.has(inst.typeName) + ) { + deps.add(inst.typeName) + } + } + } + // xmlBody-only graphical case: SFC and FBD/LD lacking ldBody. + // The DOM walker the driver falls back to handles its own + // recursion via `GeneratePouProgram` in the inner generator — + // this pre-pass only orders POUs whose deps we can see from + // the IR. Missing deps surface here as out-of-order emission, + // not as missing emission, which is acceptable while the + // ldBody/SFC IR coverage gap closes. + } else if ( + pou.body.language === 'st' || + pou.body.language === 'il' || + pou.body.language === 'python' || + pou.body.language === 'cpp' + ) { + // Textual body — port of python's `GeneratePouProgramInText`. + // Explicit narrowing because editor's tsconfig doesn't infer + // `body.value` from the negation of the graphical-language list + // above (web's tsconfig does). + const upper = pou.body.value.toUpperCase() + for (const name of pouNames) { + if (name === pou.name) continue + const re = new RegExp( + `(?:^|[^0-9^A-Z])${escapeRegex(name.toUpperCase())}(?:$|[^0-9^A-Z])`, + ) + if (re.test(upper)) deps.add(name) + } + } + + return [...deps] +} + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +/* Re-export for callers that want to query just the order, e.g. + * for diagnostics or alternative walk schemes. */ +export function emissionOrder(project: TranspileProject): string[] { + return buildPouEmissionOrder(project.pous).map((p) => p.name) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/sfc/types.ts b/src/backend/shared/transpilers/generate-st-from-json/sfc/types.ts new file mode 100644 index 000000000..f6446e5a9 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/sfc/types.ts @@ -0,0 +1,113 @@ +/** + * Plain-JSON IR for an SFC body — steps, transitions, jumps, + * action blocks, divergences. Drives the JSON-native walker the + * same way `ld/types.ts` drives the LD/FBD walker. + * + * Sub-POUs (transition reference bodies, action reference bodies) + * are carried alongside the main body so the walker can resolve + * `` without touching DOM. + */ + +export interface Position { + x: number + y: number +} + +export interface Connection { + refLocalId: number +} + +/** + * One action entry inside an ``. Mirrors what + * `getactions` produces for the DOM walker. + */ +export interface SfcActionEntry { + qualifier: string + duration?: string + indicator?: string + /** `'reference'` — `value` is an action sub-POU name (looked up in + * `SfcBody.actionSubPous`). `'inline'` — `value` IS the inline + * ST text (rare; xml2st xsd-validation rejects it on the + * `` shape, so we keep the discriminator but no + * fixture exercises this case yet). */ + type: 'reference' | 'inline' + value: string +} + +/** + * Transition condition — three variants mirroring PLCOpen's + * `` content choice. + */ +export type SfcTransitionCondition = + | { kind: 'inline'; bodyLang: 'ST' | 'IL'; value: string } + | { kind: 'reference'; name: string } + | { kind: 'connection'; connections: Connection[] } + +export type SfcInstance = + | { + kind: 'step' + localId: number + position: Position + name: string + initial: boolean + /** Incoming transition wires (``). Empty + * for the initial step. */ + fromConnections: Connection[] + } + | { + kind: 'transition' + localId: number + position: Position + priority?: number + /** Wires from the upstream step (or divergence). */ + fromConnections: Connection[] + condition: SfcTransitionCondition + } + | { + kind: 'jumpStep' + localId: number + position: Position + targetName: string + fromConnections: Connection[] + } + | { + kind: 'actionBlock' + localId: number + position: Position + /** Single connection back to the step this action block decorates. */ + fromConnections: Connection[] + actions: SfcActionEntry[] + } + | { + kind: 'selectionDivergence' + localId: number + position: Position + fromConnections: Connection[] + } + | { + kind: 'selectionConvergence' + localId: number + position: Position + fromConnections: Connection[] + } + | { + kind: 'simultaneousDivergence' + localId: number + position: Position + fromConnections: Connection[] + } + | { + kind: 'simultaneousConvergence' + localId: number + position: Position + fromConnections: Connection[] + } + +export interface SfcBody { + instances: SfcInstance[] + /** Action sub-POU bodies keyed by `@name`. Looked up when an + * actionBlock entry's `type === 'reference'`. */ + actionSubPous: Map + /** Transition sub-POU bodies keyed by `@name`. */ + transitionSubPous: Map +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/sfc/walker.ts b/src/backend/shared/transpilers/generate-st-from-json/sfc/walker.ts new file mode 100644 index 000000000..1f4c22ea4 --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/sfc/walker.ts @@ -0,0 +1,422 @@ +/** + * SFC body walker — JSON-native port of + * `src/PLCGenerator/sfc.ts:emitSfcBody`. + * + * Two-pass algorithm: + * + * 1. Pre-pass: walk the body, populate steps / transitions / + * jumps / actionBlock entries. Resolve each step's + * predecessor transitions by walking back through divergences / + * convergences (depth-bounded by the graph itself). + * + * 2. Emit pass: starting from each `initial` step, recursively + * emit `INITIAL_STEP` / `STEP` blocks followed by their + * outgoing transitions and the destination steps they target. + * Action sub-POUs are emitted lazily as referenced. + * + * Output matches the existing DOM walker byte-for-byte (verified + * against the json_walker SFC corpus). + */ + +import type { ProgramChunk } from '../ld/path_tree' +import type { + Connection, + SfcActionEntry, + SfcInstance, + SfcTransitionCondition, +} from './types' +import type { SfcWalkerState } from './walker_state' + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +export function emitSfcBody(state: SfcWalkerState): void { + // Pre-pass: index all instances by localId for fast lookup, then + // register each step / transition / jump / actionBlock. + registerInstances(state) + + // Emit each initial step's reachable subgraph. The recursive + // computeSfcStep deletes from `state.sfcSteps` so a back-jump + // doesn't re-emit the same step. + for (const stepName of state.initialSteps) { + computeSfcStep(state, stepName) + } + + // Any actions still in `state.sfcActions` after the step walk are + // referenced ones we haven't emitted yet — they should have been + // visited via `computeSfcStep`'s action-iteration, so the leftover + // case is unusual. Mirroring Python: leftover actions don't + // emit on their own. +} + +/* ─────────────────────────── pre-pass ───────────────────────────────────── */ + +function registerInstances(state: SfcWalkerState): void { + for (const inst of state.body.instances) { + state.byId.set(inst.localId, inst) + } + for (const inst of state.body.instances) { + if (inst.kind === 'step') registerStep(state, inst) + else if (inst.kind === 'jumpStep') registerJump(state, inst) + else if (inst.kind === 'actionBlock') registerActionBlock(state, inst) + } +} + +function registerStep(state: SfcWalkerState, step: Extract): void { + if (state.sfcSteps.has(step.name)) return + if (step.initial) state.initialSteps.push(step.name) + state.sfcSteps.set(step.name, { + id: step.localId, + initial: step.initial, + transitions: [], + actions: [], + }) + + // Resolve incoming transitions by walking back through the step's + // would-be predecessors. Steps don't carry connections directly + // in our IR — we discover incoming transitions by looking at every + // transition's `to` destinations. Mirror the DOM walker's + // `resolveStepPredecessorTransitions`. + for (const candidate of state.body.instances) { + if (candidate.kind !== 'transition' && candidate.kind !== 'jumpStep') continue + if (candidate.kind === 'jumpStep') continue + if (transitionLeadsTo(state, candidate, step.localId)) { + registerTransition(state, candidate) + state.sfcTransitions.get(candidate.localId)?.to.push([ + [step.name, [state.tagName, 'transition', candidate.localId, 'to', step.localId]], + ]) + } + } +} + +function registerJump(state: SfcWalkerState, jump: Extract): void { + // jumpStep is a sink — find every transition pointing AT this + // jumpStep and add its `targetName` to that transition's `to`. + for (const candidate of state.body.instances) { + if (candidate.kind !== 'transition') continue + if (transitionLeadsTo(state, candidate, jump.localId)) { + registerTransition(state, candidate) + state.sfcTransitions.get(candidate.localId)?.to.push([ + [jump.targetName, [state.tagName, 'jump', jump.localId, 'target']], + ]) + } + } +} + +function registerTransition( + state: SfcWalkerState, + transition: Extract, +): void { + if (state.sfcTransitions.has(transition.localId)) return + + // Walk back from the transition's fromConnections through any + // divergences / convergences until we hit step(s). The result is + // the `from` list (step names). + const fromSteps = walkBackToSteps(state, transition.fromConnections) + + const fromLocations: ProgramChunk[][] = fromSteps.map((name) => [ + [name, [state.tagName, 'transition', transition.localId, 'from']], + ]) + + state.sfcTransitions.set(transition.localId, { + id: transition.localId, + ...(transition.priority !== undefined ? { priority: transition.priority } : {}), + condition: transition.condition, + from: fromLocations, + to: [], + simultaneous: false, + }) + + // Hook this transition onto each predecessor step's outgoing list. + for (const stepName of fromSteps) { + const stepInfos = state.sfcSteps.get(stepName) + if (stepInfos) stepInfos.transitions.push(transition.localId) + } +} + +/** + * Walk back from `connections` through divergences/convergences, + * collecting the upstream step names. When the upstream is a + * `simultaneousDivergence`, recurse through its own + * `fromConnections` — semantically the simultaneous transition's + * `from` set is the single step driving the divergence. + */ +function walkBackToSteps(state: SfcWalkerState, connections: readonly Connection[]): string[] { + const out: string[] = [] + for (const conn of connections) { + const upstream = state.byId.get(conn.refLocalId) + if (!upstream) continue + if (upstream.kind === 'step') { + out.push(upstream.name) + } else if ( + upstream.kind === 'selectionDivergence' || + upstream.kind === 'simultaneousDivergence' || + upstream.kind === 'selectionConvergence' || + upstream.kind === 'simultaneousConvergence' + ) { + out.push(...walkBackToSteps(state, upstream.fromConnections)) + } + } + return out +} + +/** + * Forward reachability: does following `transition.toConnections` + * (which we discover by finding which downstream instance points at + * the transition) eventually reach `stepLocalId`? + * + * We walk through divergences/convergences from the transition's + * downstream consumers until we find the target step. + */ +function transitionLeadsTo( + state: SfcWalkerState, + transition: Extract, + stepLocalId: number, +): boolean { + // Find every instance that has this transition in its + // fromConnections — those are the transition's downstream + // consumers. + for (const candidate of state.body.instances) { + const consumers = consumerConnectionsOf(candidate) + if (!consumers) continue + if (!consumers.some((c) => c.refLocalId === transition.localId)) continue + + if (candidate.kind === 'step' && candidate.localId === stepLocalId) return true + if (candidate.kind === 'jumpStep' && candidate.localId === stepLocalId) return true + if ( + candidate.kind === 'selectionDivergence' || + candidate.kind === 'simultaneousDivergence' || + candidate.kind === 'selectionConvergence' || + candidate.kind === 'simultaneousConvergence' + ) { + // Recurse through this divergence — its downstream consumers + // continue the chain. + if (consumersReach(state, candidate.localId, stepLocalId)) return true + } + } + return false +} + +function consumersReach( + state: SfcWalkerState, + fromLocalId: number, + stepLocalId: number, +): boolean { + for (const candidate of state.body.instances) { + const consumers = consumerConnectionsOf(candidate) + if (!consumers) continue + if (!consumers.some((c) => c.refLocalId === fromLocalId)) continue + + if (candidate.kind === 'step' && candidate.localId === stepLocalId) return true + if (candidate.kind === 'jumpStep' && candidate.localId === stepLocalId) return true + if ( + candidate.kind === 'selectionDivergence' || + candidate.kind === 'simultaneousDivergence' || + candidate.kind === 'selectionConvergence' || + candidate.kind === 'simultaneousConvergence' + ) { + if (consumersReach(state, candidate.localId, stepLocalId)) return true + } + } + return false +} + +function consumerConnectionsOf(inst: SfcInstance): readonly Connection[] | null { + if ( + inst.kind === 'step' || + inst.kind === 'transition' || + inst.kind === 'jumpStep' || + inst.kind === 'actionBlock' || + inst.kind === 'selectionDivergence' || + inst.kind === 'selectionConvergence' || + inst.kind === 'simultaneousDivergence' || + inst.kind === 'simultaneousConvergence' + ) { + return inst.fromConnections + } + return null +} + +function registerActionBlock( + state: SfcWalkerState, + block: Extract, +): void { + // Each actionBlock has exactly one upstream step. Walk back + // through any divergences (rare for actionBlocks) to find it. + const stepNames = walkBackToSteps(state, block.fromConnections) + if (stepNames.length === 0) return + const stepName = stepNames[0] + + block.actions.forEach((action, num) => { + const stepInfos = state.sfcSteps.get(stepName) + if (!stepInfos) return + stepInfos.actions.push({ + id: block.localId, + num, + qualifier: action.qualifier, + ...(action.duration !== undefined ? { duration: action.duration } : {}), + ...(action.indicator !== undefined ? { indicator: action.indicator } : {}), + content: action.value, + }) + if (action.type === 'reference') { + registerActionDef(state, action) + } + // Inline actions are exotic; the existing TS DOM walker doesn't + // produce a separate ACTION block for them either — the qualifier + // line in the STEP body is the only emission. + }) +} + +function registerActionDef(state: SfcWalkerState, action: SfcActionEntry): void { + if (state.sfcActions.has(action.value)) return + const subPou = state.body.actionSubPous.get(action.value) + if (!subPou) return + const location: ProgramChunk['1'] = [state.tagName, 'action', action.value, 'name'] + const content: ProgramChunk[] = [ + [`${state.currentIndent} `, []], + [subPou.value, [state.tagName, 'action', action.value, 'body']], + [subPou.value.endsWith('\n') ? '' : '\n', []], + ] + state.sfcActions.set(action.value, { location, content }) +} + +/* ─────────────────────────── emit pass ──────────────────────────────────── */ + +function computeSfcStep(state: SfcWalkerState, stepName: string): void { + const stepInfos = state.sfcSteps.get(stepName) + if (!stepInfos) return + state.sfcSteps.delete(stepName) + + state.program.push([state.currentIndent, []]) + if (stepInfos.initial) state.program.push(['INITIAL_', []]) + state.program.push(['STEP ', []]) + state.program.push([stepName, [state.tagName, 'step', stepInfos.id, 'name']]) + state.program.push([':\n', []]) + + const referencedActions: string[] = [] + state.currentIndent += ' ' + for (const actionInfos of stepInfos.actions) { + referencedActions.push(actionInfos.content) + state.program.push([state.currentIndent, []]) + state.program.push([ + actionInfos.content, + [state.tagName, 'action_block', actionInfos.id, 'action', actionInfos.num, 'reference'], + ]) + state.program.push(['(', []]) + state.program.push([ + actionInfos.qualifier, + [state.tagName, 'action_block', actionInfos.id, 'action', actionInfos.num, 'qualifier'], + ]) + if (actionInfos.duration !== undefined) { + state.program.push([', ', []]) + state.program.push([ + actionInfos.duration, + [state.tagName, 'action_block', actionInfos.id, 'action', actionInfos.num, 'duration'], + ]) + } + if (actionInfos.indicator !== undefined) { + state.program.push([', ', []]) + state.program.push([ + actionInfos.indicator, + [state.tagName, 'action_block', actionInfos.id, 'action', actionInfos.num, 'indicator'], + ]) + } + state.program.push([');\n', []]) + } + state.currentIndent = state.currentIndent.slice(0, -2) + state.program.push([`${state.currentIndent}END_STEP\n\n`, []]) + + for (const actionName of referencedActions) { + computeSfcAction(state, actionName) + } + for (const transitionId of stepInfos.transitions) { + computeSfcTransition(state, transitionId) + } +} + +function computeSfcAction(state: SfcWalkerState, actionName: string): void { + const entry = state.sfcActions.get(actionName) + if (!entry) return + state.sfcActions.delete(actionName) + state.program.push([`${state.currentIndent}ACTION `, []]) + state.program.push([actionName, entry.location]) + state.program.push([':\n', []]) + state.program.push(...entry.content) + state.program.push([`${state.currentIndent}END_ACTION\n\n`, []]) +} + +function computeSfcTransition(state: SfcWalkerState, transitionId: number): void { + const infos = state.sfcTransitions.get(transitionId) + if (!infos) return + state.sfcTransitions.delete(transitionId) + + state.program.push([`${state.currentIndent}TRANSITION`, []]) + if (infos.priority !== undefined) { + state.program.push([` (PRIORITY := ${infos.priority})`, [state.tagName, 'transition', transitionId, 'priority']]) + } + state.program.push([' FROM ', []]) + // Multiple from-steps means simultaneous convergence — render as + // `(X, Y)`. + if (infos.from.length === 1) { + state.program.push(...infos.from[0]) + } else { + state.program.push(['(', []]) + for (let i = 0; i < infos.from.length; i++) { + if (i > 0) state.program.push([', ', []]) + state.program.push(...infos.from[i]) + } + state.program.push([')', []]) + } + state.program.push([' TO ', []]) + // Multiple to-steps means simultaneous divergence — render as + // `(X, Y)` (existing DOM emitter convention). + if (infos.to.length === 1) { + state.program.push(...infos.to[0]) + } else { + state.program.push(['(', []]) + for (let i = 0; i < infos.to.length; i++) { + if (i > 0) state.program.push([', ', []]) + state.program.push(...infos.to[i]) + } + state.program.push([')', []]) + } + emitTransitionCondition(state, transitionId, infos.condition) + state.program.push([`${state.currentIndent}END_TRANSITION\n\n`, []]) + + // Recurse into destination steps. + for (const toGroup of infos.to) { + for (const [stepName] of toGroup) { + computeSfcStep(state, stepName as string) + } + } +} + +function emitTransitionCondition( + state: SfcWalkerState, + transitionId: number, + condition: SfcTransitionCondition, +): void { + state.program.push(['\n', []]) + state.currentIndent += ' ' + if (condition.kind === 'inline') { + state.program.push([state.currentIndent, []]) + state.program.push([':= ', []]) + state.program.push([ + condition.value, + [state.tagName, 'transition', transitionId, 'inline'], + ]) + state.program.push([';\n', []]) + } else if (condition.kind === 'reference') { + const subPou = state.body.transitionSubPous.get(condition.name) + if (subPou) { + state.program.push([state.currentIndent, []]) + state.program.push([ + subPou.value, + [state.tagName, 'transition', transitionId, 'reference', condition.name], + ]) + // Sub-POU bodies may or may not end with a newline. Add one + // if missing so END_TRANSITION lands on its own line. + if (!subPou.value.endsWith('\n')) state.program.push(['\n', []]) + } + } + state.currentIndent = state.currentIndent.slice(0, -2) +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/sfc/walker_state.ts b/src/backend/shared/transpilers/generate-st-from-json/sfc/walker_state.ts new file mode 100644 index 000000000..815b1d84c --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/sfc/walker_state.ts @@ -0,0 +1,79 @@ +/** + * Mutable state carried through the SFC walker. + * + * Mirrors the relevant subset of `GenState` the existing DOM walker + * uses for SFC emission: per-name step / transition / action maps, + * an initial-step queue, and the accumulating program chunk stream. + */ + +import type { ProgramChunk } from '../ld/path_tree' +import type { SfcBody, SfcInstance, SfcTransitionCondition } from './types' + +export interface SfcStepActionEntry { + id: number + num: number + qualifier: string + duration?: string + indicator?: string + /** For `` actions, the sub-POU name; for `` + * actions, the inline ST text (one-line). */ + content: string +} + +export interface SfcStepInfos { + id: number + initial: boolean + /** localIds of outgoing transitions. */ + transitions: number[] + actions: SfcStepActionEntry[] +} + +export interface SfcTransitionInfos { + id: number + priority?: number + condition: SfcTransitionCondition + /** Each element is one chunk-list representing an upstream step + * name. When length > 1 we render as `(X, Y)` (simultaneous + * convergence). */ + from: ProgramChunk[][] + /** Each element is one chunk-list representing a downstream step + * name. When length > 1 we render as `(X, Y)` (simultaneous + * divergence). */ + to: ProgramChunk[][] + /** When true, both `from` and `to` are treated as parenthesised + * groups (simultaneous semantics). */ + simultaneous: boolean +} + +export interface SfcActionInfos { + location: ProgramChunk['1'] + content: ProgramChunk[] +} + +export interface SfcWalkerState { + tagName: string + body: SfcBody + byId: Map + program: ProgramChunk[] + currentIndent: string + sfcSteps: Map + sfcTransitions: Map + sfcActions: Map + initialSteps: string[] + warnings: string[] +} + +export function newSfcWalkerState(tagName: string, body: SfcBody): SfcWalkerState { + return { + tagName, + body, + byId: new Map(), + program: [], + currentIndent: ' ', + sfcSteps: new Map(), + sfcTransitions: new Map(), + sfcActions: new Map(), + initialSteps: [], + warnings: [], + } +} diff --git a/src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/block_library.ts b/src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/block_library.ts new file mode 100644 index 000000000..ca4169f0f --- /dev/null +++ b/src/backend/shared/transpilers/generate-st-from-json/src/PLCGenerator/block_library.ts @@ -0,0 +1,370 @@ +/** + * Block-library resolution. + * + * Phase 2d.1 — project-local POU resolution + permissive synthesis fallback. + * + * The full Python pipeline (PLCControler.GetBlockType, PLCGenerator.py:1288) + * resolves block types across three sources: + * 1. 10 TC6 function-block library XMLs (TON, TOF, R_TRIG, F_TRIG, …) + * 2. iec_std.csv (ADD, SUB, AND, OR, GT, EQ, … with overload signatures) + * 3. The project's own POU definitions + * + * Porting (1) and (2) is its own deferred effort (Phase 2d.2): it requires + * shipping ~MB of XML/CSV data in the bundle and re-implementing overload + * resolution. For now we cover (3) — every block type the corpus references + * that *isn't* a project-local POU falls through to + * `synthesizePermissiveBlockInfos`, matching `xml2st`'s permissive policy + * (strucpp does real type-checking downstream). + * + * This divergence is recorded in `fixtures/INVENTORY.md`. + */ + +import { + getcontentOfType, + getdescription, + getformalParameter, + getinputVariables, + getinstanceName, + getinterface, + getinterfaceVarLists, + getname, + getoutputVariables, + getpou, + getpouType, + getreturnType, + gettype, + gettypeName, + getvariable, +} from '../plcopen/accessors' +import type { ProjectTree } from '../plcopen/plcopen' +import { + childElements, + type Element, + getLocalTag, + isElement, +} from '../xmlclass/xsdschema' + +/* ───────────────────────────── data model ────────────────────────────────── */ + +/** + * `(name, type, qualifier)` triple. Mirrors Python's tuple inside + * `block_infos["inputs"]` / `["outputs"]`. Qualifier is always `"none"` for + * project-local POUs and synthesized permissive entries. + */ +export interface BlockIO { + name: string + type: string + qualifier: 'none' | 'negated' | 'rising' | 'falling' +} + +/** + * Shape of Python's `block_infos` dict, identical to `getblockInfos()` and + * `SynthesizePermissiveBlockInfos` returns. + */ +export interface BlockInfos { + name: string + type: 'function' | 'functionBlock' | 'program' | string + extensible: boolean + inputs: BlockIO[] + outputs: BlockIO[] + comment: string + usage: string +} + +/* ─────────────────────── type-info helper ────────────────────────────────── */ + +/** + * Extract a textual IEC type from a `` (or ``) wrapper. + * Mirrors `_getvariableTypeinfos` (plcopen/plcopen.py:1469). + * + * Behavior: + * - `` → `"X"` + * - elementary (``, ``, `