diff --git a/.gitignore b/.gitignore index a33d9088b..ff41e9cd4 100644 --- a/.gitignore +++ b/.gitignore @@ -51,11 +51,6 @@ resources/st-compiler/**.spec # External tool binaries (downloaded by scripts/download-binaries.ts) # arduino-cli stays committed since we don't own its releases -resources/bin/**/xml2st -resources/bin/**/xml2st.exe -resources/bin/**/xml2st/ -resources/bin/.binary-metadata.json -resources/bin/**/.binary-metadata.json resources/strucpp/ # Playwright diff --git a/binary-versions.json b/binary-versions.json index eebbe5bfa..46c0e540d 100644 --- a/binary-versions.json +++ b/binary-versions.json @@ -1,10 +1,6 @@ { - "xml2st": { - "version": "v4.0.5", - "repository": "Autonomy-Logic/xml2st" - }, "strucpp": { "version": "v0.5.5", "repository": "Autonomy-Logic/STruCpp" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index baa953c61..a943f91e2 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 1c7274e2d..9d58123f7 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/scripts/download-binaries.ts b/scripts/download-binaries.ts index b0318c183..f9504d48d 100644 --- a/scripts/download-binaries.ts +++ b/scripts/download-binaries.ts @@ -1,115 +1,35 @@ /** - * Download external tool binaries (xml2st, strucpp) from GitHub Releases. + * Download external tool binaries (strucpp) from GitHub Releases. * * Usage: - * ts-node scripts/download-binaries.ts [--platform ] [--arch ] [--force] + * ts-node scripts/download-binaries.ts [--force] * - * Defaults to the current platform/arch. Use --force to re-download even if cached. + * Use --force to re-install even if cached. + * + * The legacy `xml2st` binary download path was removed when the + * editor migrated to the in-process JSON → ST transpiler + * (`backend/shared/transpilers/generate-st-from-json/`). */ import { execSync } from 'child_process' import fs from 'fs' import path from 'path' -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - interface ToolEntry { version: string repository: string } interface BinaryVersions { - xml2st: ToolEntry strucpp: ToolEntry } -interface CacheMetadata { - xml2st: string - platform: string - arch: string -} - -type Platform = 'darwin' | 'linux' | 'win32' -type Arch = 'x64' | 'arm64' - -// --------------------------------------------------------------------------- -// Paths -// --------------------------------------------------------------------------- - const ROOT_DIR = path.resolve(__dirname, '..') const VERSIONS_FILE = path.join(ROOT_DIR, 'binary-versions.json') const RESOURCES_DIR = path.join(ROOT_DIR, 'resources') -function binDir(platform: Platform, arch: Arch): string { - return path.join(RESOURCES_DIR, 'bin', platform, arch) -} - -function cacheFile(platform: Platform, arch: Arch): string { - return path.join(binDir(platform, arch), '.binary-metadata.json') -} - -// --------------------------------------------------------------------------- -// CLI argument parsing -// --------------------------------------------------------------------------- - -function parseArgs(): { platform: Platform; arch: Arch; force: boolean } { - const args = process.argv.slice(2) - let platform = process.platform as string - let arch = process.arch as string - let force = false - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--platform' && args[i + 1]) { - platform = args[++i] - } else if (args[i] === '--arch' && args[i + 1]) { - arch = args[++i] - } else if (args[i] === '--force') { - force = true - } - } - - if (!['darwin', 'linux', 'win32'].includes(platform)) { - console.error(`Unsupported platform: ${platform}`) - process.exit(1) - } - if (!['x64', 'arm64'].includes(arch)) { - console.error(`Unsupported architecture: ${arch}`) - process.exit(1) - } - - return { platform: platform as Platform, arch: arch as Arch, force } -} - -// --------------------------------------------------------------------------- -// Cache check -// --------------------------------------------------------------------------- - -function getCachedMetadata(platform: Platform, arch: Arch): CacheMetadata | null { - const file = cacheFile(platform, arch) - if (!fs.existsSync(file)) return null - - try { - return JSON.parse(fs.readFileSync(file, 'utf-8')) as CacheMetadata - } catch { - return null - } -} - -function needsXml2st(versions: BinaryVersions, cached: CacheMetadata | null, platform: Platform, arch: Arch): boolean { - const dir = binDir(platform, arch) - const isWindows = platform === 'win32' - const isDarwin = platform === 'darwin' - - const xml2stPath = isDarwin - ? path.join(dir, 'xml2st', 'xml2st') - : path.join(dir, isWindows ? 'xml2st.exe' : 'xml2st') - - if (!fs.existsSync(xml2stPath)) return true - if (!cached || cached.xml2st !== versions.xml2st.version) return true - - return false +function parseArgs(): { force: boolean } { + return { force: process.argv.slice(2).includes('--force') } } function needsStrucpp(versions: BinaryVersions): boolean { @@ -126,24 +46,9 @@ function needsStrucpp(versions: BinaryVersions): boolean { } catch { return true } - return false } -function writeCache(versions: BinaryVersions, platform: Platform, arch: Arch): void { - const data: CacheMetadata = { - xml2st: versions.xml2st.version, - platform, - arch, - } - fs.mkdirSync(path.dirname(cacheFile(platform, arch)), { recursive: true }) - fs.writeFileSync(cacheFile(platform, arch), JSON.stringify(data, null, 2) + '\n') -} - -// --------------------------------------------------------------------------- -// Download helpers -// --------------------------------------------------------------------------- - async function downloadToFile(url: string, dest: string): Promise { const response = await fetch(url, { redirect: 'follow' }) if (!response.ok) { @@ -153,101 +58,12 @@ async function downloadToFile(url: string, dest: string): Promise { fs.writeFileSync(dest, new Uint8Array(arrayBuffer)) } -function extractTarGz(archive: string, destDir: string): void { - fs.mkdirSync(destDir, { recursive: true }) - execSync(`tar xzf "${archive}" -C "${destDir}"`, { stdio: 'pipe' }) -} - -function extractZip(archive: string, destDir: string): void { - fs.mkdirSync(destDir, { recursive: true }) - execSync(`tar xf "${archive}" -C "${destDir}"`, { stdio: 'pipe' }) -} - function rmrf(p: string): void { if (fs.existsSync(p)) { fs.rmSync(p, { recursive: true, force: true }) } } -function copyRecursive(src: string, dest: string): void { - fs.mkdirSync(dest, { recursive: true }) - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - const srcPath = path.join(src, entry.name) - const destPath = path.join(dest, entry.name) - if (entry.isSymbolicLink()) { - const linkTarget = fs.readlinkSync(srcPath) - if (fs.existsSync(destPath)) fs.rmSync(destPath, { force: true }) - fs.symlinkSync(linkTarget, destPath) - } else if (entry.isDirectory()) { - copyRecursive(srcPath, destPath) - } else { - fs.copyFileSync(srcPath, destPath) - } - } -} - -// --------------------------------------------------------------------------- -// xml2st download and extraction -// --------------------------------------------------------------------------- - -async function downloadXml2st( - tool: ToolEntry, - platform: Platform, - arch: Arch, - targetBinDir: string, -): Promise { - const isWindows = platform === 'win32' - const isDarwin = platform === 'darwin' - const ext = isWindows ? 'zip' : 'tar.gz' - const url = `https://github.com/${tool.repository}/releases/download/${tool.version}/xml2st-${platform}-${arch}.${ext}` - - console.log(` Downloading xml2st ${tool.version} for ${platform}-${arch}...`) - const tmpDir = fs.mkdtempSync(path.join(RESOURCES_DIR, '.tmp-xml2st-')) - - try { - const archivePath = path.join(tmpDir, `xml2st.${ext}`) - await downloadToFile(url, archivePath) - - const extractDir = path.join(tmpDir, 'extracted') - if (isWindows) { - extractZip(archivePath, extractDir) - } else { - extractTarGz(archivePath, extractDir) - } - - // Archive contains xml2st/ directory - const extractedToolDir = path.join(extractDir, 'xml2st') - - if (isDarwin) { - // macOS: xml2st is a directory with _internal/ — copy as-is - const destDir = path.join(targetBinDir, 'xml2st') - rmrf(destDir) - copyRecursive(extractedToolDir, destDir) - fs.chmodSync(path.join(destDir, 'xml2st'), 0o755) - } else { - // Linux/Windows: single executable - const exeName = isWindows ? 'xml2st.exe' : 'xml2st' - const srcFile = path.join(extractedToolDir, exeName) - const destFile = path.join(targetBinDir, exeName) - rmrf(destFile) - // Also remove any leftover directory from a previous macOS-style install - rmrf(path.join(targetBinDir, 'xml2st')) - fs.copyFileSync(srcFile, destFile) - if (!isWindows) { - fs.chmodSync(destFile, 0o755) - } - } - - console.log(` xml2st ${tool.version} installed.`) - } finally { - rmrf(tmpDir) - } -} - -// --------------------------------------------------------------------------- -// strucpp download and extraction -// --------------------------------------------------------------------------- - async function downloadStrucpp(tool: ToolEntry): Promise { // The npm tarball is platform-independent (pure TypeScript + C++ headers) const version = tool.version.replace(/^v/, '') @@ -284,12 +100,8 @@ async function downloadStrucpp(tool: ToolEntry): Promise { } } -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - async function main(): Promise { - const { platform, arch, force } = parseArgs() + const { force } = parseArgs() if (!fs.existsSync(VERSIONS_FILE)) { console.error(`binary-versions.json not found at ${VERSIONS_FILE}`) @@ -298,34 +110,15 @@ async function main(): Promise { const versions: BinaryVersions = JSON.parse(fs.readFileSync(VERSIONS_FILE, 'utf-8')) - console.log(`[download-binaries] platform=${platform} arch=${arch} force=${force}`) + console.log(`[download-binaries] force=${force}`) - const targetBinDir = binDir(platform, arch) - fs.mkdirSync(targetBinDir, { recursive: true }) - - const cached = force ? null : getCachedMetadata(platform, arch) - const downloadXml2stNeeded = force || needsXml2st(versions, cached, platform, arch) - const downloadStrucppNeeded = force || needsStrucpp(versions) - - if (!downloadXml2stNeeded && !downloadStrucppNeeded) { - console.log(`[download-binaries] All tools up to date, skipping.`) - return - } - - if (downloadXml2stNeeded) { - await downloadXml2st(versions.xml2st, platform, arch, targetBinDir) - } else { - console.log(` xml2st ${versions.xml2st.version} already installed, skipping.`) - } - - // strucpp is platform-independent — only download once regardless of platform/arch - if (downloadStrucppNeeded) { - await downloadStrucpp(versions.strucpp) - } else { + if (!force && !needsStrucpp(versions)) { console.log(` strucpp ${versions.strucpp.version} already installed, skipping.`) + console.log(`[download-binaries] Done.`) + return } - writeCache(versions, platform, arch) + await downloadStrucpp(versions.strucpp) console.log(`[download-binaries] Done.`) } diff --git a/src/backend/editor/compiler/__tests__/editor-compiler-platform-port.test.ts b/src/backend/editor/compiler/__tests__/editor-compiler-platform-port.test.ts index ab4b96b40..590b5a0a7 100644 --- a/src/backend/editor/compiler/__tests__/editor-compiler-platform-port.test.ts +++ b/src/backend/editor/compiler/__tests__/editor-compiler-platform-port.test.ts @@ -129,7 +129,6 @@ describe('findHexInCompilationPath', () => { describe('createEditorCompilerPlatformPort', () => { function makeHandlers(overrides?: Partial): EditorCompilerHandlers { return { - handleTranspileXMLtoST: jest.fn(), handleCompileArduinoProgram: jest.fn(), handleUploadProgram: jest.fn(), handleCoreInstallation: jest.fn(), @@ -229,64 +228,6 @@ describe('createEditorCompilerPlatformPort', () => { expect(log).toHaveBeenCalledWith(expect.stringContaining('lib install failed'), 'warning') }) - // ---- transpileXmlToSt — xml2stArgs forwarding (STRUCT drift regression) ---- - - it('transpileXmlToSt forwards args.xml2stArgs to handleTranspileXMLtoST verbatim', async () => { - // Regression guard for the editor/web STRUCT drift bug: the - // shared pipeline owns the xml2st flag set as an array of CLI - // tokens, and the editor adapter must thread that array into - // handleTranspileXMLtoST as the third positional arg — the - // handler then splices it straight into the spawned xml2st argv. - // Editor's local xml2st is trusted, so the adapter passes the - // array through verbatim (no filtering). - const handleTranspileXMLtoST = jest - .fn< - ReturnType, - Parameters - >() - .mockResolvedValue({ success: true, data: '' }) - const tmp = mkdtempSync(join(tmpdir(), 'xml2st-args-')) - try { - const port = createEditorCompilerPlatformPort( - makeHandlers({ handleTranspileXMLtoST }), - makeContext({ sourceTargetFolderPath: tmp }), - ) - // The handler stub never produces a program.st, so the readFile - // after the spawn-equivalent step throws — that's fine, we only - // care about the xml2stArgs argument forwarded to the handler. - await port.transpileXmlToSt({ xml: '', xml2stArgs: ['--keep-structs'] }, () => undefined) - expect(handleTranspileXMLtoST).toHaveBeenCalledTimes(1) - const callArgs = handleTranspileXMLtoST.mock.calls[0]! - expect(callArgs[2]).toEqual(['--keep-structs']) - } finally { - rmSync(tmp, { recursive: true, force: true }) - } - }) - - it('transpileXmlToSt forwards an empty xml2stArgs array verbatim', async () => { - // The adapter must not "helpfully" inject defaults when the - // pipeline asked for nothing — that would be the exact kind of - // silent drift the shared port contract exists to prevent. - const handleTranspileXMLtoST = jest - .fn< - ReturnType, - Parameters - >() - .mockResolvedValue({ success: true, data: '' }) - const tmp = mkdtempSync(join(tmpdir(), 'xml2st-empty-args-')) - try { - const port = createEditorCompilerPlatformPort( - makeHandlers({ handleTranspileXMLtoST }), - makeContext({ sourceTargetFolderPath: tmp }), - ) - await port.transpileXmlToSt({ xml: '', xml2stArgs: [] }, () => undefined) - const callArgs = handleTranspileXMLtoST.mock.calls[0]! - expect(callArgs[2]).toEqual([]) - } finally { - rmSync(tmp, { recursive: true, force: true }) - } - }) - // ---- uploadArduinoBoard — port wiring (regression for issue #5) ---- it('uploadArduinoBoard forwards args.port to the handler as communicationPort', async () => { diff --git a/src/backend/editor/compiler/compiler-module.spec.ts b/src/backend/editor/compiler/compiler-module.spec.ts index 6340249b1..7cea25be6 100644 --- a/src/backend/editor/compiler/compiler-module.spec.ts +++ b/src/backend/editor/compiler/compiler-module.spec.ts @@ -113,7 +113,6 @@ describe('CompilerModule', () => { expect(typeof compilerModule.arduinoCliBinaryPath).toBe('string') expect(typeof compilerModule.arduinoCliConfigurationFilePath).toBe('string') expect(Array.isArray(compilerModule.arduinoCliBaseParameters)).toBe(true) - expect(typeof compilerModule.xml2stBinaryPath).toBe('string') expect(typeof compilerModule.strucppRuntimeDir).toBe('string') }) diff --git a/src/backend/editor/compiler/compiler-module.ts b/src/backend/editor/compiler/compiler-module.ts index c6ccd7bfb..8793d3045 100644 --- a/src/backend/editor/compiler/compiler-module.ts +++ b/src/backend/editor/compiler/compiler-module.ts @@ -26,6 +26,11 @@ import { runLibraryBuildPipeline } from '@root/backend/shared/library/library-bu import { buildKnownPous, emitCompileErrorEvents } from '@root/backend/shared/library/program-build-helpers' import { runProgramBuildPipeline } from '@root/backend/shared/library/program-build-pipeline' import { loadStrucpp } from '@root/backend/shared/library/strucpp-runtime' +import { + fromSchemaShape, + type SchemaProjectData, + transpileToSt as runJsonTranspiler, +} from '@root/backend/shared/transpilers/st-transpiler' import type { KnownPou } from '@root/backend/shared/utils/PLC/split-program-st' /** @@ -159,8 +164,6 @@ class CompilerModule { arduinoCliConfigurationFilePath: string arduinoCliBaseParameters: string[] - xml2stBinaryPath: string - strucppRuntimeDir: string // Memoised arduino-cli `--show-properties=expanded` output keyed by FQBN. @@ -215,8 +218,6 @@ class CompilerModule { // INFO: We use this approach because some commands can receive additional parameters as a string array. this.arduinoCliBaseParameters = ['--config-file', this.arduinoCliConfigurationFilePath] - this.xml2stBinaryPath = this.#constructXml2stBinaryPath() - this.strucppRuntimeDir = this.#constructStrucppRuntimeDir() } @@ -343,10 +344,6 @@ class CompilerModule { return join(this.binaryDirectoryPath, 'arduino-cli') } - #constructXml2stBinaryPath(): string { - return join(this.binaryDirectoryPath, 'xml2st', CompilerModule.HOST_PLATFORM === 'darwin' ? 'xml2st' : '') - } - #constructStrucppRuntimeDir(): string { // strucpp's runtime headers (`src/runtime/include/`) live in two // places depending on whether we're running dev or a packaged app: @@ -417,14 +414,6 @@ class CompilerModule { } } - #executeXml2st(args: string[]) { - let xml2stBinaryPath = this.xml2stBinaryPath - if (CompilerModule.HOST_PLATFORM === 'win32') { - xml2stBinaryPath += '.exe' - } - return spawn(xml2stBinaryPath, args) - } - #executeArduinoCliCommand(args: string[]) { let arduinoCliBinaryPath = this.arduinoCliBinaryPath if (CompilerModule.HOST_PLATFORM === 'win32') { @@ -852,46 +841,6 @@ class CompilerModule { }) } - async handleTranspileXMLtoST( - generatedXMLFilePath: string, - handleOutputData: (chunk: Buffer | string, logLevel?: 'info' | 'error') => void, - extraXml2stArgs: readonly string[], - ) { - return new Promise>((resolve, reject) => { - // `extraXml2stArgs` comes from the shared pipeline's - // `TranspileXmlToStArgs.xml2stArgs` — the single source of truth - // for xml2st flag semantics across editor and web. Editor passes - // them through verbatim (trusted local binary); web's adapter - // filters against its known-args allowlist before sending to the - // compile-service. Strucpp targets currently pass - // `['--keep-structs']` (native STRUCT declarations vs matiec's - // legacy struct→FB rewrite); future flags appear here as the - // pipeline opts into them. - const executeCommand = this.#executeXml2st(['--generate-st', generatedXMLFilePath, ...extraXml2stArgs]) - - let stderrData = '' - - // INFO: We use the xml2st command to transpile the XML file to ST. - executeCommand.stdout?.on('data', (data: Buffer) => { - handleOutputData(data) - }) - executeCommand.stderr?.on('data', (data: Buffer) => { - stderrData += data.toString() - }) - - executeCommand.on('close', (code) => { - if (code === 0) { - handleOutputData(`ST file generated at: ${generatedXMLFilePath.replace('plc.xml', 'program.st')}`, 'info') - resolve({ - success: true, - }) - } else { - reject(new Error(`xml2st process exited with code ${code}\n${stderrData}`)) - } - }) - }) - } - async handleCompileSTtoCpp( sourceTargetFolderPath: string, handleOutputData: (chunk: Buffer | string, logLevel?: 'info' | 'error', compileError?: StrucppCompileError) => void, @@ -2596,7 +2545,6 @@ class CompilerModule { // --- Build the editor's CompilerPlatformPort implementation --- const platformPort = createEditorCompilerPlatformPort( { - handleTranspileXMLtoST: this.handleTranspileXMLtoST.bind(this), handleCompileArduinoProgram: this.handleCompileArduinoProgram.bind(this), handleUploadProgram: this.handleUploadProgram.bind(this), handleCoreInstallation: this.handleCoreInstallation.bind(this), @@ -2794,34 +2742,34 @@ class CompilerModule { return } + // JSON → ST in-process via `st-transpiler` — replaces the + // legacy XmlGenerator + xml2st-subprocess hop the program-compile + // pipeline already retired. Mirrors what + // `editor-compiler-platform-port.transpileToSt` does for the + // shared pipeline path, scoped down to the debug compile here. try { - const generateXMLResult = await this.handleGenerateXMLfromJSON(sourceTargetFolderPath, projectData) - _mainProcessPort.postMessage({ - logLevel: 'info', - message: `Generated XML from JSON at: ${generateXMLResult.data?.xmlPath as string}`, - }) - } catch (error) { - _mainProcessPort.postMessage({ - logLevel: 'error', - message: `Error generating XML from JSON: ${error as string}\nStopping debug compilation process.`, - }) - _mainProcessPort.close() - return - } - - const generatedXMLFilePath = join(sourceTargetFolderPath, 'plc.xml') - try { - await this.handleTranspileXMLtoST( - generatedXMLFilePath, - (data, logLevel) => { - _mainProcessPort.postMessage({ logLevel, message: data }) - }, - ['--keep-structs'], - ) + const ir = fromSchemaShape(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' + _mainProcessPort.postMessage({ + logLevel: 'error', + message: `${message}\nStopping debug compilation process.`, + }) + _mainProcessPort.close() + return + } + for (const warning of result.warnings) { + _mainProcessPort.postMessage({ logLevel: 'info', message: warning }) + } + await mkdir(sourceTargetFolderPath, { recursive: true }) + const programStPath = join(sourceTargetFolderPath, 'program.st') + await writeFile(programStPath, result.programSt, 'utf-8') + _mainProcessPort.postMessage({ logLevel: 'info', message: `ST file generated at: ${programStPath}` }) } catch (error) { _mainProcessPort.postMessage({ logLevel: 'error', - message: `Error transpiling XML to ST: ${error as string}\nStopping debug compilation process.`, + message: `Error transpiling JSON to ST: ${getErrorMessage(error)}\nStopping debug compilation process.`, }) _mainProcessPort.close() return @@ -2977,7 +2925,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..442ae0e59 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/st-transpiler/` * - 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/st-transpiler' +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 7419391e0..b95cde697 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/st-transpiler/`). 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/st-transpiler' import type { CheckRuntimeVersionArgs, CheckRuntimeVersionResult, @@ -40,8 +47,8 @@ import type { PackageVppPluginResult, PlatformDeviceContext, PlatformLog, - TranspileXmlToStArgs, - TranspileXmlToStResult, + TranspileToStArgs, + TranspileToStResult, UploadArduinoBoardArgs, UploadResult, UploadRuntimeV3Args, @@ -60,7 +67,6 @@ import type { CompilerModule } from './compiler-module' * file-watching, etc.). */ export interface EditorCompilerHandlers { - handleTranspileXMLtoST: CompilerModule['handleTranspileXMLtoST'] handleCompileArduinoProgram: CompilerModule['handleCompileArduinoProgram'] handleUploadProgram: CompilerModule['handleUploadProgram'] handleCoreInstallation: CompilerModule['handleCoreInstallation'] @@ -152,33 +158,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(`st-transpiler failed: ${message}`, 'error') return { ok: false, errors: [{ message, line: 0, column: 0, severity: 'error' }] } } }, diff --git a/src/backend/editor/hardware/hardware-module.ts b/src/backend/editor/hardware/hardware-module.ts index d5072bec3..c51b361d2 100644 --- a/src/backend/editor/hardware/hardware-module.ts +++ b/src/backend/editor/hardware/hardware-module.ts @@ -1,11 +1,10 @@ -import { exec } from 'node:child_process' import { existsSync } from 'node:fs' import { readFile } from 'node:fs/promises' import { join, resolve as pathResolve, sep as pathSep } from 'node:path' -import { promisify } from 'node:util' import { app as electronApp } from 'electron' import { produce } from 'immer' +import { SerialPort as NodeSerialPort } from 'serialport' import { readHalsFile } from '../../shared/firmware/hals-loader' import { type BoardBuildInfo, BoardInfoResolver } from '../../shared/hardware/board-info-resolver' @@ -95,46 +94,20 @@ class HardwareModule { // ++ ============================= Getters ================================ ++ async getAvailableSerialPorts(): Promise { - let xml2stBinaryPath = join( - this.binaryDirectoryPath, - 'xml2st', - HardwareModule.HOST_PLATFORM === 'darwin' ? 'xml2st' : '', - ) - if (HardwareModule.HOST_PLATFORM === 'win32') { - xml2stBinaryPath += '.exe' - } - const executeCommand = promisify(exec) - + // Native `serialport` package replaces the legacy `xml2st + // --list-ports` subprocess (xml2st was retired when the JSON + // transpiler landed in-process; see + // `editor-compiler-platform-port.transpileToSt`). `NodeSerialPort.list()` + // returns each port's `path` plus optional vendor metadata; map + // it onto the `{name, address}` shape the renderer expects. try { - const { stdout, stderr } = await executeCommand(`"${xml2stBinaryPath}" --list-ports`) - - if (stderr) { - logger.warn(`xml2st stderr output: ${stderr}`) - } - - let normalizedOutputString: SerialPort[] = [{ name: '', address: 'fallback' }] - - if (stdout) { - try { - const parsedOutput = JSON.parse(stdout) as { - ports: { - name: string - address: string - }[] - } - normalizedOutputString = parsedOutput.ports.map((port) => ({ - name: port.name ?? port.address, - address: port.address, - })) - } catch (parseError: unknown) { - logger.error(`Failed to parse xml2st output: ${String(parseError)}`) - return [] - } - } - - return normalizedOutputString - } catch (execError: unknown) { - logger.error(`Failed to execute xml2st: ${String(execError)}`) + const ports = await NodeSerialPort.list() + return ports.map((port) => ({ + name: port.manufacturer ?? port.path, + address: port.path, + })) + } catch (error: unknown) { + logger.error(`Failed to enumerate serial ports: ${String(error)}`) return [] } } diff --git a/src/backend/shared/compile/pipeline.ts b/src/backend/shared/compile/pipeline.ts index 9cdf36d1b..c38ac949e 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,37 +397,25 @@ 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 (`st-transpiler/`), + // 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'] }, - makePlatformLog(emit, 'st'), - ) + // 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) { if (stResult.errors && stResult.errors.length > 0) { emitCompileErrorEvents( 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..f7ffa991a 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,29 @@ 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 + // (`st-transpiler/`). 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. - (message, level) => emit({ message, level }), + 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/st-transpiler/core/modifier-types.ts b/src/backend/shared/transpilers/st-transpiler/core/modifier-types.ts new file mode 100644 index 000000000..572cc54f0 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/core/modifier-types.ts @@ -0,0 +1,16 @@ +/** + * Contact / coil modifier shapes. Shared by the walker (which derives + * them from React Flow `data.variant` via `narrow.ts`) and by + * `extractModifier` (which translates them into ST emission steps). + */ + +export interface ContactModifier { + negated?: boolean + edge?: 'rising' | 'falling' +} + +export interface CoilModifier { + negated?: boolean + storage?: 'set' | 'reset' + edge?: 'rising' | 'falling' +} diff --git a/src/backend/shared/transpilers/st-transpiler/core/modifiers.ts b/src/backend/shared/transpilers/st-transpiler/core/modifiers.ts new file mode 100644 index 000000000..c38db6d2b --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/core/modifiers.ts @@ -0,0 +1,97 @@ +/** + * 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 { CoilModifier, ContactModifier } from './modifier-types' +import type { Location, ProgramChunk } from './path-tree' +import type { TriggerVar } from './trigger-var' + +/** + * Subset of `WalkerState` that the modifier helpers actually read or + * mutate. Lets the React Flow walker (which carries its own state + * shape without `body` / `connectorExprs`) share this module without + * type casts. `WalkerState` itself satisfies `ModifierState` + * structurally — no migration needed for the legacy walker. + */ +export interface ModifierState { + program: ProgramChunk[] + currentIndent: string + declaredVars: Set + triggerVars: TriggerVar[] +} + +export function extractModifier( + state: ModifierState, + 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: ModifierState, + 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: ModifierState): 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/st-transpiler/core/path-tree.ts b/src/backend/shared/transpilers/st-transpiler/core/path-tree.ts new file mode 100644 index 000000000..f76337e7f --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/core/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/st-transpiler/core/trigger-var.ts b/src/backend/shared/transpilers/st-transpiler/core/trigger-var.ts new file mode 100644 index 000000000..b09329aa1 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/core/trigger-var.ts @@ -0,0 +1,12 @@ +/** + * Edge-trigger instance variable (`R_TRIG1`, `F_TRIG1`, …) synthesised + * by `extractModifier` when a contact/coil carries a rising/falling + * edge modifier. The walker accumulates these in its state, the wrap + * declares them in the trailing local `VAR` section. + */ + +export interface TriggerVar { + /** `R_TRIG1`, `R_TRIG2`, … or `F_TRIG1`, `F_TRIG2`, … */ + name: string + type: 'R_TRIG' | 'F_TRIG' +} diff --git a/src/backend/shared/transpilers/st-transpiler/data/std_block_catalog.json b/src/backend/shared/transpilers/st-transpiler/data/std_block_catalog.json new file mode 100644 index 000000000..daf0e5474 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/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)" + } + } + ] +} diff --git a/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts b/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts new file mode 100644 index 000000000..74a63278d --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts @@ -0,0 +1,245 @@ +/** + * 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 '../helpers/ctn-globals' +import type { ProgramChunk } from '../helpers/program' +import { computeConfigurationName, computeConfigurationResourceName } from '../helpers/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/st-transpiler/emit/data-types.ts b/src/backend/shared/transpilers/st-transpiler/emit/data-types.ts new file mode 100644 index 000000000..af3d2c122 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/data-types.ts @@ -0,0 +1,161 @@ +/** + * 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 '../helpers/data-type' +import type { ProgramChunk } from '../helpers/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/st-transpiler/emit/pou-graphical.ts b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts new file mode 100644 index 000000000..a6fca3957 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts @@ -0,0 +1,174 @@ +/** + * IR-native graphical-POU emitter — LD / FBD bodies. + * + * Drives the React Flow walker (`../walker/`) for + * the body content, then wraps it with the POU's signature + VAR + * sections + END. Trigger variables and function-call output temps + * synthesised during the walk (`R_TRIG1`, `_TMP__OUT`, …) + * get appended to the trailing `VAR` section before assembly so the + * declaration order matches what the python oracle produces. + */ + +import { resolveBlockType } from '../helpers/block-library' +import { PLC_BASE_TYPES } from '../helpers/ctn-globals' +import type { ProgramChunk } from '../helpers/program' +import { computePouName } from '../helpers/text-helpers' +import { varTypeNames } from '../helpers/type-text' +import type { TranspilePou, TranspileProject, TranspileVariable, TranspileVariableClass } from '../types' +import { emitFbdBody } from '../walker/fbd' +import type { SyntheticVar } from '../walker/ld' +import { emitLdBody } from '../walker/ld' +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 the React Flow walker. + */ +export function generateGraphicalPou(pou: TranspilePou, project: TranspileProject): ProgramChunk[] { + const tagName = computePouName(pou.name) + const kindKeyword = ( + { + program: 'PROGRAM', + function: 'FUNCTION', + 'function-block': 'FUNCTION_BLOCK', + } as Record + )[pou.pouType] + + if (pou.body.language !== 'ld' && pou.body.language !== 'fbd') { + throw new Error(`generateGraphicalPou called with non-graphical body: ${pou.body.language}`) + } + const emitted = pou.body.language === 'ld' ? emitLdBody(pou.body.value) : emitFbdBody(pou.body.value) + + // Compose the final POU chunk stream now that the walker has + // emitted the body bytes + any synthetic 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 resolvedSyntheticVars = emitted.syntheticVars.map((sv) => { + if (sv.type !== 'ANY' || sv.originBlockTypeName === undefined) return sv + const referenced = project.pous.find((p) => p.name === sv.originBlockTypeName) + if (referenced && referenced.pouType === 'function' && referenced.interface.returnType) { + return { ...sv, type: referenced.interface.returnType } + } + const stdResolved = resolveBlockType(sv.originBlockTypeName) + if (stdResolved) { + const outPort = stdResolved.infos.outputs.find((o) => o.name === sv.originFormalParameter) + if (outPort) { + const collapsed = outPort.type.startsWith('ANY') ? 'BOOL' : outPort.type + return { ...sv, type: collapsed } + } + } + return sv + }) + + const iface = computeInterface(pou.interface?.variables ?? [], resolvedSyntheticVars) + 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([emitted.bodySt, []]) + program.push([`END_${kindKeyword}\n\n`, []]) + return program +} + +/* ────────────────────────── helpers ─────────────────────────────────────── */ + +function computeInterface(variables: TranspileVariable[], syntheticVars: SyntheticVar[]): 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 python oracle produces. + if (syntheticVars.length > 0) { + const localKeyword = varTypeNames.localVars + const localBucket = grouped.get(localKeyword) ?? [] + for (const sv of syntheticVars) { + const isElementary = PLC_BASE_TYPES.has(sv.type.toUpperCase()) + localBucket.push({ + name: sv.name, + type: isElementary + ? { definition: 'base-type', value: sv.type.toUpperCase() } + : { definition: 'derived', value: sv.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/st-transpiler/emit/pou-textual.ts b/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts new file mode 100644 index 000000000..b57700d27 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts @@ -0,0 +1,185 @@ +/** + * 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 '../helpers/ctn-globals' +import type { ProgramChunk } from '../helpers/program' +import { reIndentText } from '../helpers/text-helpers' +import { computePouName } from '../helpers/text-helpers' +import { varTypeNames } from '../helpers/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/st-transpiler/emit/type-text.ts b/src/backend/shared/transpilers/st-transpiler/emit/type-text.ts new file mode 100644 index 000000000..d4b1f3645 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/type-text.ts @@ -0,0 +1,48 @@ +/** + * 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/st-transpiler/emit/value.ts b/src/backend/shared/transpilers/st-transpiler/emit/value.ts new file mode 100644 index 000000000..b936b9508 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/emit/value.ts @@ -0,0 +1,66 @@ +/** + * 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 '../helpers/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/st-transpiler/from-schema.ts b/src/backend/shared/transpilers/st-transpiler/from-schema.ts new file mode 100644 index 000000000..5a78d6435 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/from-schema.ts @@ -0,0 +1,249 @@ +/** + * 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 type { PLCProjectData as SchemaPLCProjectData } from '@root/backend/shared/types/PLC/open-plc' + +import type { + TranspileBody, + TranspileDataType, + TranspileInstance, + TranspilePou, + TranspileProject, + TranspileTask, + TranspileVariable, + TranspileVariableType, +} from './types' +import type { RFFbdBody } from './walker/fbd' +import type { RFBody, RFEdge, RFNode, RFRung } from './walker/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': + return { language: 'ld', value: projectLdBody(body.value.rungs) } + case 'fbd': + return { language: 'fbd', value: projectFbdBody(body.value.rung) } + case 'sfc': + throw new Error('SFC support is under development') + default: { + const unreachable: never = body + throw new Error(`Unknown body language: ${JSON.stringify(unreachable)}`) + } + } +} + +/* ─── graphical body projection ──────────────────────────────────── */ + +type SchemaLdValue = Extract['value'] +type SchemaFbdValue = Extract['value'] +type SchemaRung = SchemaLdValue['rungs'][number] +type SchemaFbdRung = SchemaFbdValue['rung'] +type SchemaNode = SchemaRung['nodes'][number] +type SchemaEdge = SchemaRung['edges'][number] + +function projectLdBody(rungs: readonly SchemaRung[]): RFBody { + return { rungs: rungs.map(projectRung) } +} + +function projectFbdBody(rung: SchemaFbdRung): RFFbdBody { + return { + rung: { + comment: rung.comment, + nodes: rung.nodes.map(projectNode), + edges: rung.edges.map(projectEdge), + }, + } +} + +function projectRung(rung: SchemaRung): RFRung { + return { + id: rung.id, + comment: rung.comment, + reactFlowViewport: rung.reactFlowViewport, + nodes: rung.nodes.map(projectNode), + edges: rung.edges.map(projectEdge), + } +} + +function projectNode(n: SchemaNode): RFNode { + return { + id: n.id, + type: n.type, + position: n.position, + data: isRecord(n.data) ? n.data : {}, + } +} + +function projectEdge(e: SchemaEdge): RFEdge { + return { + id: e.id, + source: e.source, + target: e.target, + sourceHandle: e.sourceHandle, + targetHandle: e.targetHandle, + } +} + +function isRecord(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v) +} + +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/st-transpiler/helpers/block-library.ts b/src/backend/shared/transpilers/st-transpiler/helpers/block-library.ts new file mode 100644 index 000000000..e98d51ae6 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/block-library.ts @@ -0,0 +1,88 @@ +/** + * Standard block-library resolution against the pre-built catalog. + * + * The full python pipeline resolves block types from three sources — + * TC6 function-block library XMLs, `iec_std.csv` overloads, and + * project-local POUs. The first two are baked into + * `data/std_block_catalog.json` at build time + * (`tools/build_std_catalog.py`); the third is intentionally dropped + * here because the only caller (`emit/pou-graphical.ts`) resolves + * project POUs separately via `project.pous.find(...)`. + * + * Overload behaviour mirrors the python oracle's display mode: when + * a name has multiple catalog entries (ADD, GT, …), the result has + * all I/O collapsed to `'ANY'`. The wrap then narrows via its own + * type-resolution pass. + */ + +import stdCatalog from '../data/std_block_catalog.json' + +export interface BlockIO { + name: string + type: string + qualifier: 'none' | 'negated' | 'rising' | 'falling' +} + +export interface BlockInfos { + name: string + type: 'function' | 'functionBlock' | 'program' | string + extensible: boolean + inputs: BlockIO[] + outputs: BlockIO[] + comment: string + usage: string +} + +export interface BlockResolution { + source: 'standard' + infos: BlockInfos +} + +interface CatalogEntry { + section: string + infos: BlockInfos +} + +const CATALOG: ReadonlyMap = (() => { + const map = new Map() + for (const [name, entries] of Object.entries(stdCatalog as Record)) { + map.set(name, entries) + } + return map +})() + +/** + * Look the block name up in the standard catalog. Single match → + * return its infos. Multiple matches → return the first entry with + * all I/O types collapsed to `'ANY'` (the wrap re-narrows). No + * match → `null`. + */ +export function resolveBlockType(typename: string): BlockResolution | null { + const entries = CATALOG.get(typename) ?? [] + let result: BlockInfos | null = null + for (const entry of entries) { + if (result !== null) return { source: 'standard', infos: collapseToAny(result) } + result = cloneBlockInfos(entry.infos) + } + return result === null ? null : { source: 'standard', infos: result } +} + +function cloneBlockInfos(infos: BlockInfos): BlockInfos { + return { + name: infos.name, + type: infos.type, + extensible: infos.extensible, + inputs: infos.inputs.map((i) => ({ ...i })), + outputs: infos.outputs.map((o) => ({ ...o })), + comment: infos.comment, + usage: infos.usage, + } +} + +function collapseToAny(infos: BlockInfos): BlockInfos { + return { + ...infos, + inputs: infos.inputs.map((i) => ({ ...i, type: 'ANY' })), + outputs: infos.outputs.map((o) => ({ ...o, type: 'ANY' })), + } +} diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts b/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts new file mode 100644 index 000000000..90539a74a --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts @@ -0,0 +1,56 @@ +/** + * Configuration-level globals injected by host plugins (Beremiz CTN + * mechanism). Mirrors `Controler.GetConfigurationExtraVariables` + * (PLCControler.py:1248-1285): a provider callback returns either + * pre-built `varlist` entries or `(name, type, initial)` tuples that + * the configuration emitter folds into the resource's `VAR_GLOBAL` + * sections. + * + * In a standalone xml2st build the provider is stubbed to `[]`, so + * observable behaviour against the python oracle is unchanged — the + * shape is exported so editor/web hosts wiring plugins can plug in. + */ + +/** + * IEC 61131-3 elementary base types — mirrors `Controler.GetBaseTypes()` + * (derived from `TypeHierarchy_list` in `plcopen/definitions.py:84`). + * Used to decide whether a synthesised global's type is elementary + * (emit verbatim) or derived (emit as a type reference). + * + * `WSTRING` is intentionally absent — matches python's `# TODO` + * comment at `definitions.py:118`. + */ +export const PLC_BASE_TYPES: ReadonlySet = new Set([ + 'BOOL', + 'SINT', + 'INT', + 'DINT', + 'LINT', + 'USINT', + 'UINT', + 'UDINT', + 'ULINT', + 'REAL', + 'LREAL', + 'TIME', + 'DATE', + 'TOD', + 'DT', + 'STRING', + 'BYTE', + 'WORD', + 'DWORD', + 'LWORD', +]) + +export interface CtnGlobalVarTuple { + name: string + /** Elementary type name (`'BOOL'`, `'INT'`, …) or a derived-type name. */ + type: string + /** ST source for the initial value (`''` omits `:= …`). */ + initial?: string +} + +export type CtnGlobalEntry = { kind: 'variable'; variable: CtnGlobalVarTuple } + +export type ConfigurationExtraVariablesProvider = () => CtnGlobalEntry[] diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/data-type.ts b/src/backend/shared/transpilers/st-transpiler/helpers/data-type.ts new file mode 100644 index 000000000..3fa3bbf12 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/data-type.ts @@ -0,0 +1,10 @@ +/** + * `"D::" + name` — data-type-tagged identifier used as the first + * field of Program-chunk location tuples for `TYPE … END_TYPE` body + * fragments. Mirrors `ComputeDataTypeName` + * (`plcopen/types_enums.py:142`). + */ + +export function ComputeDataTypeName(datatype: string): string { + return `D::${datatype}` +} diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/program.ts b/src/backend/shared/transpilers/st-transpiler/helpers/program.ts new file mode 100644 index 000000000..fda860d82 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/program.ts @@ -0,0 +1,13 @@ +/** + * Chunk model — the unit the transpiler accumulates and then joins + * into the final ST source. + * + * A chunk is `[text, location]`: the text fragment to emit, plus a + * variable-arity tuple identifying where the fragment came from + * (POU tag, region, index, …) so the editor can navigate back to + * source from cursor positions in the generated ST. + */ + +export type LocationAtom = string | number | readonly (string | number)[] +export type Location = readonly LocationAtom[] +export type ProgramChunk = readonly [text: string, location: Location] diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts b/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts new file mode 100644 index 000000000..1125d5fea --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts @@ -0,0 +1,93 @@ +/** + * Text-manipulation helpers used by the body dispatcher and downstream phases. + * + * Mirrors: + * - `ReIndentText` (PLCGenerator.py:66) + * - `ComputePouName` (plcopen/types_enums.py:112) + * - `ComputePouTransitionName` (plcopen/types_enums.py:117) + * - `ComputePouActionName` (plcopen/types_enums.py:122) + * - `ComputeConfigurationName` (plcopen/types_enums.py:127) + */ + +/** + * Reindent every line of `text` to `nbSpaces` leading spaces. + * + * Behavior (exact port of `PLCGenerator.py:66-86`): + * 1. Split on newlines. + * 2. Find the first non-blank line (`.strip() != ""`). + * 3. Count its leading spaces. + * 4. Build an `indent` string of `max(nbSpaces - leadingSpaces, 0)` spaces. + * 5. For every line, prepend `indent` (but emit empty lines as just `"\n"`). + * 6. Always append `"\n"` after each non-empty line. + * + * The function returns `""` when given an entirely blank input. + */ +export function reIndentText(text: string, nbSpaces: number): string { + let compute = '' + const lines = pySplitLines(text) + if (lines.length === 0) return compute + + let lineNum = 0 + while (lineNum < lines.length && lines[lineNum].trim().length === 0) { + lineNum++ + } + if (lineNum >= lines.length) return compute + + let spaces = 0 + const firstNonBlank = lines[lineNum] + while (spaces < firstNonBlank.length && firstNonBlank.charAt(spaces) === ' ') { + spaces++ + } + let indent = '' + for (let i = spaces; i < nbSpaces; i++) indent += ' ' + + for (const line of lines) { + if (line !== '') { + compute += `${indent}${line}\n` + } else { + compute += '\n' + } + } + return compute +} + +/** + * Mirror of Python's `str.splitlines()` for the line separators we encounter. + * + * Key difference from `String.prototype.split('\n')`: Python drops the final + * empty element when the string ends with a separator. PLCOpen-loaded text + * is normalized to `\n` by lxml, so we only need to handle that form here. + */ +function pySplitLines(text: string): string[] { + if (text.length === 0) return [] + const lines = text.split('\n') + if (lines[lines.length - 1] === '') lines.pop() + return lines +} + +/** `"P::" + name` — POU-tagged identifier used as the first field of a + * Program-chunk location tuple. */ +export function computePouName(name: string): string { + return `P::${name}` +} + +/** `"T::" + pou + "::" + transition`. */ +export function computePouTransitionName(pou: string, transition: string): string { + return `T::${pou}::${transition}` +} + +/** `"A::" + pou + "::" + action`. */ +export function computePouActionName(pou: string, action: string): string { + return `A::${pou}::${action}` +} + +/** `"C::" + name`. */ +export function computeConfigurationName(name: string): string { + return `C::${name}` +} + +/** `"R::" + config + "::" + resource` + * (`plcopen/types_enums.py:132`). */ +export function computeConfigurationResourceName(config: string, resource: string): string { + return `R::${config}::${resource}` +} diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts b/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts new file mode 100644 index 000000000..87c3c017b --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts @@ -0,0 +1,96 @@ +/** + * IEC 61131-3 type hierarchy + compatibility predicate. + * + * Mirrors ``plcopen/definitions.py:84-119`` (`TypeHierarchy_list`), + * ``plcopen/structures.py:36-48`` (`IsOfType`), and + * ``plcopen/structures.py:51-59`` (`GetSubTypes`). + * + * The hierarchy is a tree rooted at `"ANY"` with each concrete IEC type + * (`"INT"`, `"BOOL"`, `"REAL"`, …) attached under its ANY-prefixed + * meta-parent. `isOfType(child, ancestor)` is true when walking parent + * pointers from `child` eventually reaches `ancestor`. + * + * This is used by `GetBlockType`'s overload resolution: given a call like + * `ADD(myInt, myInt)` with signature `(ANY_NUM, ANY_NUM) -> ANY_NUM`, each + * input is type-checked via `isOfType("INT", "ANY_NUM")`. + * + * Note on WSTRING: Python's hierarchy comments-out `("WSTRING", "ANY_STRING")` + * with a TODO. We preserve that — WSTRING returns false for any IsOfType + * lookup against ancestors except itself. + */ + +/** + * Parent pointer table. `null` parent means root (`"ANY"`). + * Insertion order matches `plcopen/definitions.py:TypeHierarchy_list`. + */ +export const TypeHierarchy: Readonly> = { + ANY: null, + ANY_DERIVED: 'ANY', + ANY_ELEMENTARY: 'ANY', + ANY_MAGNITUDE: 'ANY_ELEMENTARY', + ANY_BIT: 'ANY_ELEMENTARY', + ANY_NBIT: 'ANY_BIT', + ANY_STRING: 'ANY_ELEMENTARY', + ANY_DATE: 'ANY_ELEMENTARY', + ANY_NUM: 'ANY_MAGNITUDE', + ANY_REAL: 'ANY_NUM', + ANY_INT: 'ANY_NUM', + ANY_SINT: 'ANY_INT', + ANY_UINT: 'ANY_INT', + BOOL: 'ANY_BIT', + SINT: 'ANY_SINT', + INT: 'ANY_SINT', + DINT: 'ANY_SINT', + LINT: 'ANY_SINT', + USINT: 'ANY_UINT', + UINT: 'ANY_UINT', + UDINT: 'ANY_UINT', + ULINT: 'ANY_UINT', + REAL: 'ANY_REAL', + LREAL: 'ANY_REAL', + TIME: 'ANY_MAGNITUDE', + DATE: 'ANY_DATE', + TOD: 'ANY_DATE', + DT: 'ANY_DATE', + STRING: 'ANY_STRING', + BYTE: 'ANY_NBIT', + WORD: 'ANY_NBIT', + DWORD: 'ANY_NBIT', + LWORD: 'ANY_NBIT', + // WSTRING intentionally absent — matches Python's `# TODO` comment. +} + +/** + * Returns `true` iff `type` is `reference` or any of its subtypes (walking + * the parent chain). `null` `reference` returns `true` ("matches anything"), + * mirroring Python's `if reference is None: return True`. + * + * Unknown types (not in the hierarchy) return `false` against any + * reference except themselves. Same behavior as Python where the unknown + * type raises `KeyError` on the hierarchy lookup — we soften to `false` so + * derived user types (e.g. `Irrigation_State`) don't crash overload checks. + */ +export function isOfType(type: string, reference: string | null): boolean { + if (reference === null) return true + if (type === reference) return true + const parent = TypeHierarchy[type] + if (parent === undefined) return false // user-defined type, not in hierarchy + if (parent === null) return false // reached ANY without matching + return isOfType(parent, reference) +} + +/** + * Concrete (non-ANY-prefixed) types that are subtypes of `metaType`. + * Used by `get_standard_funtions` to expand polymorphic CSV signatures into + * concrete overload entries (e.g. `(ANY_NUM, ANY_NUM)` becomes one entry per + * `INT`, `SINT`, `DINT`, …). Catalog already pre-expanded; this stays in TS + * for completeness and future use. + */ +export function getSubTypes(metaType: string): string[] { + const out: string[] = [] + for (const typename of Object.keys(TypeHierarchy)) { + if (typename.startsWith('ANY')) continue + if (isOfType(typename, metaType)) out.push(typename) + } + return out +} diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/type-text.ts b/src/backend/shared/transpilers/st-transpiler/helpers/type-text.ts new file mode 100644 index 000000000..caa3862b0 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/type-text.ts @@ -0,0 +1,17 @@ +/** + * PLCOpen varlist localName → IEC 61131-3 declaration keyword. + * Identical to the python oracle's `varTypeNames` constant + * (`PLCGenerator.py:38`). Used by the POU wrap to choose the right + * `VAR_*` keyword per variable class. + */ + +export const varTypeNames: Readonly> = { + localVars: 'VAR', + tempVars: 'VAR_TEMP', + inputVars: 'VAR_INPUT', + outputVars: 'VAR_OUTPUT', + inOutVars: 'VAR_IN_OUT', + externalVars: 'VAR_EXTERNAL', + globalVars: 'VAR_GLOBAL', + accessVars: 'VAR_ACCESS', +} diff --git a/src/backend/shared/transpilers/st-transpiler/index.ts b/src/backend/shared/transpilers/st-transpiler/index.ts new file mode 100644 index 000000000..489d493a5 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/index.ts @@ -0,0 +1,118 @@ +/** + * Project IR → Structured Text transpiler. + * + * Dispatch: + * - Data types, configuration, textual POUs (ST / IL / Python / C++) + * emit directly from the IR via `./emit/*`. + * - Graphical POUs (LD / FBD) emit via the React Flow walker + * (`./walker/`), wrapped by `./emit/pou-graphical.ts`. SFC is + * currently unsupported — `from-schema.ts` throws at projection + * time. + * + * 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 './emit/configuration' +import { generateDataTypes } from './emit/data-types' +import { generateGraphicalPou } from './emit/pou-graphical' +import { generateTextualPou } from './emit/pou-textual' +import { buildPouEmissionOrder } from './pou-emission-order' +import type { TranspileProject } from './types' + +export { fromSchemaShape, type SchemaProjectData } from './from-schema' +export type { ConfigurationExtraVariablesProvider, CtnGlobalEntry, CtnGlobalVarTuple } from './helpers/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[] = [] + + // TYPE … END_TYPE — emit IR-native. + for (const [text] of generateDataTypes(project)) { + pieces.push(text) + } + + // Per-POU emission. + // + // 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 + } + if (pou.body.language === 'ld' || pou.body.language === 'fbd') { + const chunks = generateGraphicalPou(pou, project) + pieces.push(chunks.map((c) => c[0]).join('')) + pouNames.push(pou.name) + continue + } + errors.push(`POU "${pou.name}" (${pou.body.language} body): language not supported`) + } 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 } +} diff --git a/src/backend/shared/transpilers/st-transpiler/pou-emission-order.ts b/src/backend/shared/transpilers/st-transpiler/pou-emission-order.ts new file mode 100644 index 000000000..21b5ff73c --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/pou-emission-order.ts @@ -0,0 +1,127 @@ +/** + * 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 — graphical bodies iterate React Flow nodes; + // textual bodies regex-match POU names in the source text. + if (pou.body.language === 'ld') { + for (const rung of pou.body.value.rungs) { + for (const node of rung.nodes) { + if (node.type !== 'block') continue + const typeName = readBlockTypeName(node.data) + if (typeName && typeName !== pou.name && pouNames.has(typeName)) deps.add(typeName) + } + } + } else if (pou.body.language === 'fbd') { + for (const node of pou.body.value.rung.nodes) { + if (node.type !== 'block') continue + const typeName = readBlockTypeName(node.data) + if (typeName && typeName !== pou.name && pouNames.has(typeName)) deps.add(typeName) + } + } 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, '\\$&') +} + +/** Read the block's referenced POU/function type name from the React + * Flow `data` payload. Editor stores this at `data.variant.name`. */ +function readBlockTypeName(data: Record): string | null { + const variant = data['variant'] + if (!isRecord(variant)) return null + const name = variant['name'] + return typeof name === 'string' ? name : null +} + +function isRecord(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v) +} + +/* 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/st-transpiler/types.ts b/src/backend/shared/transpilers/st-transpiler/types.ts new file mode 100644 index 000000000..02d50c2d3 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/types.ts @@ -0,0 +1,163 @@ +/** + * Minimal IR consumed by the JSON-fed transpiler. + * + * Decoupled from both `middleware/shared/ports/types.ts` (port shape, + * what the renderer store holds) and `backend/shared/types/PLC/open-plc.ts` + * (schema shape, what `project.json` persists). Callers project their + * own shape into `TranspileProject` via the adapter helpers + * (`from-schema.ts` here for the editor's IPC payload; openplc-web + * ships a `transpile-from-port.ts` under middleware for its port-shape + * renderer payload). + * + * Carries ONLY the fields the transpiler actually reads — no + * `servers`, no `remoteDevices`, no `libraries`, no `debugVariables`. + * That makes the surface small, the projections cheap, and the + * transpiler stable when the renderer eventually migrates between + * shapes (only the adapters change; this IR doesn't). + */ + +/* ─────────────────────────── project ────────────────────────────────────── */ + +export interface TranspileProject { + dataTypes: TranspileDataType[] + pous: TranspilePou[] + configuration: { + tasks: TranspileTask[] + instances: TranspileInstance[] + globalVariables: TranspileVariable[] + } +} + +/* ─────────────────────────── pou ────────────────────────────────────────── */ + +export type TranspilePouKind = 'program' | 'function' | 'function-block' +export type TranspileBodyLanguage = 'st' | 'il' | 'ld' | 'fbd' | 'sfc' | 'python' | 'cpp' + +export interface TranspilePou { + name: string + pouType: TranspilePouKind + documentation?: string + /** Empty for POUs with no parameters / locals. */ + interface: TranspilePouInterface + body: TranspileBody +} + +export interface TranspilePouInterface { + /** Only set on `function` POUs. */ + returnType?: string + variables: TranspileVariable[] +} + +/** + * Body payload — discriminated by `language`: + * + * - Textual ('st' | 'il' | 'python' | 'cpp') carry `value: string` + * (raw source) — the transpiler emits the IEC text directly. + * + * - Graphical ('ld' | 'fbd') carry the raw React Flow body + * (`{ rungs: [...] }` for LD, `{ rung: {...} }` for FBD) that the + * editor stores on disk under `pous/s/.{ld,fbd}`. + * The walker in `./walker/` consumes this shape directly — no + * PLCOpen XML intermediate. + * + * The plain-object shape is structured-clonable so the IR rides + * the worker boundary verbatim. + */ +export type TranspileBody = + | { + language: 'st' | 'il' | 'python' | 'cpp' + value: string + } + | { + language: 'ld' + value: import('./walker/types').RFBody + } + | { + language: 'fbd' + value: import('./walker/fbd').RFFbdBody + } + +/* ──────────────────────────── variable ──────────────────────────────────── */ + +export type TranspileVariableClass = 'input' | 'output' | 'inOut' | 'external' | 'local' | 'temp' + +export interface TranspileVariable { + name: string + type: TranspileVariableType + class?: TranspileVariableClass + /** IEC located address (`%QX0.0`, `%IW3`, …). */ + location?: string + /** Raw initial-value text — caller-supplied, no quote-wrapping. */ + initialValue?: string + documentation?: string +} + +/* ──────────────────────────── variable type ─────────────────────────────── */ + +/** + * - `base-type` — elementary IEC type (`BOOL`, `INT`, `STRING`, …) + * - `array` — `ARRAY [a..b, …] OF T` + * - `derived` / + * `user-data-type` — referenced data-type name (struct/enum/subrange) + */ +export type TranspileVariableType = + | { definition: 'base-type'; value: string } + | { definition: 'derived' | 'user-data-type'; value: string } + | { + definition: 'array' + data: { + dimensions: { dimension: string }[] + baseType: string | { value: string } + } + } + +/* ──────────────────────────── data type ─────────────────────────────────── */ + +export type TranspileDataType = + | { + name: string + derivation: 'array' + dimensions: { dimension: string }[] + baseType: string | { value: string } + initialValue?: string + } + | { + name: string + derivation: 'enumerated' + values: { description: string }[] + initialValue?: string + } + | { + name: string + derivation: 'structure' + variable: TranspileVariable[] + initialValue?: string + } + | { + name: string + derivation: 'directly-derived' + baseType: string + initialValue?: string + } + +/* ──────────────────────────── configuration ─────────────────────────────── */ + +export interface TranspileTask { + name: string + priority: number + /** Triggering mode: 'Cyclic' uses `interval`; 'Interrupt' uses `single`. */ + triggering: 'Cyclic' | 'Interrupt' + interval?: string + /** Source signal expression for non-Cyclic tasks (mapped to SINGLE/MULTI). */ + single?: string +} + +export interface TranspileInstance { + /** Instance name (`instance0`). */ + name: string + /** POU type the instance references (`main`). */ + program: string + /** Task this instance binds to. Empty / unset → no task assignment + * (instance lives directly under the resource). */ + task?: string +} diff --git a/src/backend/shared/transpilers/st-transpiler/walker/README.md b/src/backend/shared/transpilers/st-transpiler/walker/README.md new file mode 100644 index 000000000..18dba4d1d --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/walker/README.md @@ -0,0 +1,44 @@ +# walker + +React Flow → Structured Text walker. Consumes the **React Flow** +body shape (the exact format `openplc-editor` / `openplc-web` store +in `pous/s/.{ld,fbd}` on disk) and emits the body bytes +that go between `END_VAR` and `END_PROGRAM` — byte-identical to the +python oracle (`xml2st.py`). + +The orchestrator at `../index.ts` calls these entry points via +`../emit/pou-graphical.ts`; the wrap there composes the POU header, +VAR sections, the body bytes returned here, and the closing +`END_PROGRAM` / `END_FUNCTION` / `END_FUNCTION_BLOCK`. + +## Files + +- `ld.ts` — `emitLdBody(body: RFBody): EmitResult`. Handles both + LD and FBD bodies (FBD is a strict subset of LD's vocabulary). +- `fbd.ts` — thin adapter that wraps `{ rung }` into the LD + `{ rungs: [rung] }` shape and delegates to `emitLdBody`. +- `narrow.ts` — type-safe accessors for the loosely-typed + `RFNode.data: Record` payloads. Each `as*Data` + helper returns `null` when the payload doesn't match the expected + shape, letting the walker decide whether to warn or skip. +- `types.ts` — minimal React Flow types (`RFBody`, `RFRung`, + `RFNode`, `RFEdge`). Loose enough that the schema boundary in + `../from-schema.ts` can project the editor's Zod-inferred shapes + without typecasts. + +## Where the algebra lives + +The walker's emission steps (contact/coil dispatch, block-call +emission, parallel-branch factoring) build up `PathNode` trees, then +hand them to `../core/path-tree.ts` for normalisation and chunk +serialisation. Contact / coil modifiers (negated, set, reset, edge +triggers) flow through `../core/modifiers.ts:extractModifier`. + +## Source of truth + +`xml2st`'s python `PLCGenerator.py` is the canonical reference for +every emission rule. Any divergence between this walker's output +and the oracle is a walker bug, never an oracle bug — see the +fixture corpus under `xml2st/fixtures/` and the test harness under +`xml2st/shared-backend/transpilers/generate-st-from-react-flow/tests/` +for the validation loop. diff --git a/src/backend/shared/transpilers/st-transpiler/walker/fbd.ts b/src/backend/shared/transpilers/st-transpiler/walker/fbd.ts new file mode 100644 index 000000000..82c9b481b --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/walker/fbd.ts @@ -0,0 +1,29 @@ +/** + * React Flow FBD body → Structured Text. + * + * FBD is a strict subset of LD's node vocabulary (no contacts, no + * coils, no power rails, no parallel-branch markers) plus connector + * / continuation pairs. Both subsets feed the same execution-order + * driven sink iteration and the same block-call / variable emission + * code paths, so the FBD entry point is a thin adapter that wraps + * the body's single `rung` into the `RFBody.rungs[]` shape the LD + * walker consumes. + * + * FBD-specific behaviour (ENO gating on outVariable assignments, + * connector caching, continuation lookup, executionOrderId-bucketed + * sink sort) all live in the LD walker — they're no-ops for LD + * bodies that don't exercise them. + */ +import { emitLdBody, type EmitResult } from './ld' +import type { RFRung } from './types' + +export type { EmitResult } from './ld' + +/** FBD body shape — a single `rung` container of nodes + edges. */ +export interface RFFbdBody { + rung: RFRung +} + +export function emitFbdBody(body: RFFbdBody): EmitResult { + return emitLdBody({ rungs: [body.rung] }) +} diff --git a/src/backend/shared/transpilers/st-transpiler/walker/ld.ts b/src/backend/shared/transpilers/st-transpiler/walker/ld.ts new file mode 100644 index 000000000..fe2c3a6b4 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/walker/ld.ts @@ -0,0 +1,813 @@ +/** + * React Flow LD body → Structured Text. + * + * Walks `RFBody.rungs[*].nodes/edges` directly — no PLCOpen + * intermediate. Output must match the python oracle + * (`xml2st.py --keep-structs --no-complex-parser`) byte-for-byte; + * `tests/per_case.test.ts` is the per-case validation loop, and + * `tests/golden_react_flow.test.ts` covers the larger harvested + * corpus once it lands. + * + * Reuses the legacy LD walker's algorithmic primitives + * (`path_tree`, `modifiers`) for boolean factorization and modifier + * emission — those modules are PLCOpen-independent and don't pull + * the `LdBody` IR in. Only the topology traversal is rewritten to + * read React Flow's `nodes`/`edges` directly. + */ + +import { extractModifier } from '../core/modifiers' +import { + computePaths, + factorizePaths, + leafNode, + type Location, + type PathNode, + type ProgramChunk, + TRUE_NODE, +} from '../core/path-tree' +import { + asBlockData, + asCoilData, + asConnectorData, + asContactData, + asContinuationData, + asParallelData, + asPowerRailData, + asVariableData, + asVariableExpressionData, + type BlockData, + coilModifierFromVariant, + contactModifierFromVariant, + type VariableData, +} from './narrow' +import type { RFBody, RFEdge, RFNode, RFRung } from './types' + +/** + * A variable the walker synthesises during emission that the caller + * must declare in the POU's trailing local `VAR` section. Two + * flavours flow through this shape: + * - Trigger instances (`R_TRIG1`, `F_TRIG1`, …) — `type` is + * already the resolved `'R_TRIG'`/`'F_TRIG'` name. `origin*` + * fields are absent. + * - Function-call output temps (`_TMP__`) + * — `type` is `'BOOL'` for `ENO`, otherwise the literal string + * `'ANY'`. When `'ANY'`, the caller resolves it against the + * standard block catalog or the project's POU table using + * `originBlockTypeName` + `originFormalParameter`. + */ +export interface SyntheticVar { + name: string + type: string + originBlockTypeName?: string + originFormalParameter?: string +} + +export interface EmitResult { + bodySt: string + syntheticVars: SyntheticVar[] + warnings: string[] +} + +interface WalkerState { + tagName: string + byId: Map + /** Edges keyed by their target node id. */ + incoming: Map + /** Outgoing edges keyed by source node id — used to detect + * standalone blocks (no consumer of any output). */ + outgoing: Map + program: ProgramChunk[] + currentIndent: string + declaredVars: Set + triggerVars: { name: string; type: 'R_TRIG' | 'F_TRIG' }[] + /** `_TMP__` temps synthesised by + * function-call emission. `originBlockTypeName` + + * `originFormalParameter` let the caller resolve `'ANY'` types + * against the standard block catalog or a project POU's declared + * `returnType` after the walk completes. */ + functionTempVars: { + name: string + type: string + originBlockTypeName: string + originFormalParameter: string + }[] + emittedBlocks: Set + /** Connector expressions cached by name, consumed by continuation + * visits later in the same rung (FBD-only). */ + connectorExprs: Map + /** Cumulative Y-offset per node id — mirrors how `ladder-xml.ts` + * globalises rung positions when it serialises React Flow into + * PLCOpen XML (each rung's `reactFlowViewport[1]` height adds to + * the running offset). Used to position-sort sinks across rungs + * in the same coordinate space the python oracle sees. */ + yOffset: Map + warnings: string[] +} + +function rungHeight(rung: RFRung): number { + const vp = rung.reactFlowViewport + if (Array.isArray(vp) && typeof vp[1] === 'number' && Number.isFinite(vp[1])) { + return vp[1] + } + return 0 +} + +/** + * Location-tuple identity for a React Flow node. Prefers the editor- + * assigned `data.numericId` (an integer, matching PLCOpen `@localId`) + * over the React Flow UUID — `factorizePaths` sorts paths by the + * stable `pythonReprNode` key, so the location bytes embedded in chunk + * reprs decide tie-break order between sibling AND-paths. The python + * oracle sees `int` localIds; using the same integers here keeps our + * sort byte-identical with it. Falls back to `node.id` when a fixture + * lacks `numericId` (per-case fixtures that don't need the integer + * round-trip). + */ +function locId(node: RFNode): number | string { + const raw = node.data['numericId'] + if (typeof raw === 'string') { + const parsed = Number.parseInt(raw, 10) + if (Number.isFinite(parsed) && String(parsed) === raw) return parsed + } + if (typeof raw === 'number' && Number.isInteger(raw)) return raw + return node.id +} + +/** + * True for any node that holds a variable reference — covers both + * vocabularies the editor produces: + * - 'variable' with `data.variant` of 'input'/'output'/'inout' (LD + * bodies + the older FBD shape used by per-case fixtures) + * - 'input-variable' / 'output-variable' / 'inout-variable' (modern + * FBD bodies as harvested from real projects) + */ +function isVariableNode(node: RFNode): boolean { + return ( + node.type === 'variable' || + node.type === 'input-variable' || + node.type === 'output-variable' || + node.type === 'inout-variable' + ) +} + +/* ─────────────────────────── public entry ───────────────────────────────── */ + +export function emitLdBody(body: RFBody): EmitResult { + // POU name doesn't influence body emission today (it's only the + // first element of the `Location` tuples used for source-map back- + // references). Hard-code a sentinel; the orchestrator can pass a + // real name when it wires this walker in. + const tagName = 'P::main' + const state: WalkerState = { + tagName, + byId: new Map(), + incoming: new Map(), + outgoing: new Map(), + program: [], + currentIndent: ' ', + declaredVars: new Set(), + triggerVars: [], + functionTempVars: [], + emittedBlocks: new Set(), + connectorExprs: new Map(), + yOffset: new Map(), + warnings: [], + } + + // Index every node + edge from every rung up front. Sinks are then + // collected and bucketed GLOBALLY (across all rungs) — the python + // oracle iterates a flat instance list, so per-rung bucketing would + // misorder cases where an ordered block (executionOrderId > 0) lives + // in a later rung but must emit before unordered work earlier in the + // body. Each rung's local Y coords are shifted by the cumulative + // viewport heights of earlier rungs (matches `ladder-xml.ts`:612). + let cumulativeYOffset = 0 + for (const rung of body.rungs) { + for (const node of rung.nodes) { + if (state.byId.has(node.id)) { + state.warnings.push(`duplicate node id "${node.id}" across rungs`) + } + state.byId.set(node.id, node) + state.incoming.set(node.id, []) + state.outgoing.set(node.id, []) + state.yOffset.set(node.id, cumulativeYOffset) + } + for (const edge of rung.edges) { + const targetBucket = state.incoming.get(edge.target) + if (targetBucket !== undefined) targetBucket.push(edge) + const sourceBucket = state.outgoing.get(edge.source) + if (sourceBucket !== undefined) sourceBucket.push(edge) + } + cumulativeYOffset += rungHeight(rung) + } + + // Collect sinks across the body. A sink is any instance that + // appears in the body iteration sweep (mirrors PLCGenerator.py's + // generate-instances loop): + // - coils, outVariables, inOutVariables — write targets + // - blocks (idempotent via `emittedBlocks`; ensures cascaded + // blocks get their call site regardless of consumer chain) + // - connectors — cache their upstream expression for later + // continuation visits. + // + // Empty-name variables are dropped to match `ladder-xml.ts`:602, which + // filters them out of the PLCOpen serialization the oracle consumes. + // Those nodes represent dangling block outputs the user never wired + // to a real variable. + const sinks: RFNode[] = [] + for (const rung of body.rungs) { + for (const node of rung.nodes) { + if (node.type === 'coil') { + if (asCoilData(node.data)?.variable) sinks.push(node) + } else if (isVariableNode(node)) { + const data = asVariableData(node.data) + if (data === null || data.variable === '') continue + if (data.variant === 'output' || data.variant === 'inout') sinks.push(node) + } else if (node.type === 'block') { + sinks.push(node) + } else if (node.type === 'connector') { + sinks.push(node) + } + } + } + + // Bucket by executionOrderId: explicit > 0 emits first (sorted + // ascending), zero/unset falls back to position sort (Y first + // 10-unit tol, then X). Mirrors `body_emit.ts` in the legacy + // walker. Walks launched from an ordered sink propagate + // `order=true` so upstream blocks don't emit eagerly — they emit + // at their own iteration step instead. + const ordered: RFNode[] = [] + const others: RFNode[] = [] + for (const node of sinks) { + if (nodeExecutionOrder(node) > 0) ordered.push(node) + else others.push(node) + } + ordered.sort((a, b) => nodeExecutionOrder(a) - nodeExecutionOrder(b)) + others.sort((a, b) => compareNodePosition(state, a, b)) + + for (const sink of ordered) emitSink(state, sink) + for (const sink of others) emitSink(state, sink) + + const bodySt = '\n' + state.program.map((c) => c[0]).join('') + const syntheticVars: SyntheticVar[] = [ + ...state.triggerVars.map((t) => ({ name: t.name, type: t.type })), + ...state.functionTempVars, + ] + return { bodySt, syntheticVars, warnings: state.warnings } +} + +function nodeExecutionOrder(node: RFNode): number { + if (node.type === 'coil') return asCoilData(node.data)?.executionOrder ?? 0 + if (node.type === 'block') return asBlockData(node.data)?.executionOrder ?? 0 + if (isVariableNode(node)) return asVariableData(node.data)?.executionOrder ?? 0 + return 0 +} + +function compareNodePosition(state: WalkerState, a: RFNode, b: RFNode): number { + const aOff = state.yOffset.get(a.id) ?? 0 + const bOff = state.yOffset.get(b.id) ?? 0 + const ax = Math.trunc(a.position.x) + const ay = Math.trunc(a.position.y + aOff) + const bx = Math.trunc(b.position.x) + const by = Math.trunc(b.position.y + bOff) + if (Math.abs(ay - by) >= 10) return ay - by + return ax - bx +} + +function emitSink(state: WalkerState, node: RFNode): void { + // Top-level sink walks ALWAYS use order=false — the sink's own + // executionOrderId doesn't propagate into its upstream walks + // (mirrors body_emit.emitCoil / emitOutVariable in the legacy + // walker). Order suppression is purely a block→upstream-block + // concern, applied inside `buildInputArgs` based on the emitting + // block's eoid. + if (node.type === 'coil') return emitCoilNode(state, node) + if (isVariableNode(node)) { + const data = asVariableData(node.data) + if (data?.variant === 'output') return emitOutVariableNode(state, node, data) + if (data?.variant === 'inout') return emitInOutVariableNode(state, node, data) + return + } + if (node.type === 'block') return emitStandaloneBlock(state, node) + if (node.type === 'connector') return emitConnectorNode(state, node) +} + +/* ─────────────────────────── coil emission ──────────────────────────────── */ + +function emitCoilNode(state: WalkerState, node: RFNode): void { + const data = asCoilData(node.data) + if (data === null) { + state.warnings.push(`coil node "${node.id}" has unrecognised data shape`) + return + } + + const paths = pathsFromIncoming(state, node.id, /*order=*/ false) + if (paths.length === 0) { + state.warnings.push(`Coil "${data.variable}" must be connected.`) + return + } + const expr = pathsToChunks(paths) + const coilInfo: Location = [state.tagName, 'coil', locId(node)] + const modifier = coilModifierFromVariant(data.variant) + const modified = extractModifier(state, modifier, expr, coilInfo) + + state.program.push([state.currentIndent, []]) + state.program.push([data.variable, [...coilInfo, 'reference']]) + state.program.push([' := ', []]) + for (const chunk of modified) state.program.push(chunk) + state.program.push([';\n', []]) +} + +/* ─────────────────────────── outVariable emission ───────────────────────── */ + +function emitOutVariableNode(state: WalkerState, node: RFNode, data: VariableData): void { + const paths = pathsFromIncoming(state, node.id, /*order=*/ false) + if (paths.length === 0) { + state.warnings.push(`outVariable "${data.variable}" must be connected.`) + return + } + const expr = pathsToChunks(paths) + const info: Location = [state.tagName, 'io_variable', locId(node), 'expression'] + + // ENO gating — when the single upstream connection points at a + // block with `EN` wired, wrap the assignment in + // `IF THEN ... END_IF;`. Mirrors PLCGenerator.py:1243 + // (`GetUsedEno`). + const enoVar = getUsedEnoForNode(state, node.id) + 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([data.variable, info]) + state.program.push([' := ', []]) + for (const chunk of expr) state.program.push(chunk) + state.program.push([';\n', []]) + + if (enoVar !== null) { + state.currentIndent = state.currentIndent.slice(0, -2) + state.program.push([`${state.currentIndent}END_IF;\n`, []]) + } +} + +/** + * If the only incoming edge to `nodeId` originates from a block that + * has its `EN` input wired, return the ST reference of the matching + * `ENO` output — `.ENO` for FB instances, + * `_TMP__ENO` for functions. Otherwise null. + */ +function getUsedEnoForNode(state: WalkerState, nodeId: string): string | null { + const edges = state.incoming.get(nodeId) ?? [] + if (edges.length !== 1) return null + const upstream = state.byId.get(edges[0].source) + if (upstream === undefined || upstream.type !== 'block') return null + const data = asBlockData(upstream.data) + if (data === null) return null + // Block has EN wired iff some incoming edge targets the EN handle. + const blockIncoming = state.incoming.get(upstream.id) ?? [] + const enWired = blockIncoming.some((e) => e.targetHandle === 'EN') + if (!enWired) return null + if (data.blockKind === 'function-block-instance') { + return `${data.instanceName}.ENO` + } + return `_TMP_${data.typeName}${data.numericId}_ENO` +} + +/* ─────────────────────────── inOutVariable emission ────────────────────── */ + +function emitInOutVariableNode(state: WalkerState, node: RFNode, data: VariableData): void { + const paths = pathsFromIncoming(state, node.id, /*order=*/ false) + if (paths.length === 0) { + state.warnings.push(`inOutVariable "${data.variable}" must be connected.`) + return + } + const expr = pathsToChunks(paths) + const info: Location = [state.tagName, 'io_variable', locId(node), 'expression'] + state.program.push([state.currentIndent, []]) + state.program.push([data.variable, info]) + state.program.push([' := ', []]) + for (const chunk of expr) state.program.push(chunk) + state.program.push([';\n', []]) +} + +/** + * Connector emission: walk the connector's single incoming edge, + * cache the resulting expression chunks under the connector's name + * for later continuation lookups. No bytes emitted into + * `state.program` — the continuation is what surfaces the cached + * expression at its consumer's call site. + */ +function emitConnectorNode(state: WalkerState, node: RFNode): void { + const data = asConnectorData(node.data) + if (data === null) { + state.warnings.push(`connector node "${node.id}" has unrecognised data shape`) + return + } + if (state.connectorExprs.has(data.name)) return + const paths = pathsFromIncoming(state, node.id, /*order=*/ false) + if (paths.length === 0) return + state.connectorExprs.set(data.name, pathsToChunks(paths)) +} + +/* ─────────────────────────── standalone block ───────────────────────────── */ + +function emitStandaloneBlock(state: WalkerState, node: RFNode): void { + const data = asBlockData(node.data) + if (data === null) { + state.warnings.push(`block node "${node.id}" has unrecognised data shape`) + return + } + if (data.blockKind === 'function-block-instance') { + emitFunctionBlockCall(state, node, data) + } else { + emitFunctionCall(state, node, data) + } +} + +/* ─────────────────────────── upstream walk ──────────────────────────────── */ + +function pathsFromIncoming(state: WalkerState, nodeId: string, order: boolean): PathNode[] { + const edges = state.incoming.get(nodeId) ?? [] + const out: PathNode[] = [] + for (const edge of edges) { + const upstream = state.byId.get(edge.source) + if (upstream === undefined) continue + const node = visitUpstream(state, upstream, edge, order) + if (node !== undefined) out.push(node) + } + return out +} + +function visitUpstream(state: WalkerState, node: RFNode, edge: RFEdge, order: boolean): PathNode | undefined { + switch (node.type) { + case 'powerRail': { + const data = asPowerRailData(node.data) + // A left-rail upstream contributes "always-on" energization. + // A right-rail upstream is never reached during a back-walk + // (rails sink wires; they don't source them). + if (data?.variant === 'left') return TRUE_NODE + return undefined + } + case 'contact': + return visitContact(state, node, order) + case 'parallel': + return visitParallel(state, node, order) + case 'variable': + case 'input-variable': + case 'output-variable': + case 'inout-variable': + return visitVariable(state, node) + case 'block': + return visitBlockOutput(state, node, edge, order) + case 'coil': + // Coil-as-passthrough: editor topologies sometimes route a wire + // past a coil to feed a downstream node. Mirror legacy walker + // behaviour: emit nothing for the coil, just propagate the + // upstream signal. + return visitCoilPassthrough(state, node, order) + case 'continuation': + return visitContinuation(state, node) + case 'connector': + // Connectors are sinks, never sources — skip when encountered + // as an upstream during a back-walk. + return undefined + default: + state.warnings.push(`unknown upstream node type "${node.type}"`) + return undefined + } +} + +function visitContact(state: WalkerState, node: RFNode, order: boolean): PathNode { + const data = asContactData(node.data) + if (data === null) { + state.warnings.push(`contact node "${node.id}" has unrecognised data shape`) + return TRUE_NODE + } + const contactInfo: Location = [state.tagName, 'contact', locId(node)] + const variableChunks: ProgramChunk[] = [[data.variable, [...contactInfo, 'reference']]] + const modifier = contactModifierFromVariant(data.variant) + const variableLeaf = leafNode(extractModifier(state, modifier, variableChunks, contactInfo)) + + const upstream = pathsFromIncoming(state, node.id, order) + if (upstream.length === 0) { + state.warnings.push(`Contact "${data.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] } +} + +function visitCoilPassthrough(state: WalkerState, node: RFNode, order: boolean): PathNode | undefined { + const upstream = pathsFromIncoming(state, node.id, 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 } +} + +/** + * Parallel-marker nodes (open/close) are zero-cost pass-throughs in + * the path-tree algebra. Their job is purely topological: they let + * React Flow express wire merges that the editor renders as parallel + * rails. + * + * - `close`: collect every incoming branch, factor common terms, + * return as a single `or` (or unwrapped child if only one). + * - `open`: collapse to its single upstream — the open marker + * itself doesn't contribute a term. + */ +function visitParallel(state: WalkerState, node: RFNode, order: boolean): PathNode | undefined { + const data = asParallelData(node.data) + if (data === null) { + state.warnings.push(`parallel node "${node.id}" has unrecognised data shape`) + return undefined + } + const paths = pathsFromIncoming(state, node.id, order) + if (paths.length === 0) return undefined + // Flatten nested OR children — when the editor chains close markers + // to render a 3+ way merge (each close takes 2 inputs), the naive + // recursion produces `((A OR B) OR C)`. The python oracle has no + // such intermediates and emits a flat `A OR B OR C`; the + // factorize-then-stable-sort below relies on the OR being flat. + const flat: PathNode[] = [] + for (const p of paths) { + if (p.kind === 'or') flat.push(...p.children) + else flat.push(p) + } + if (data.side === 'open') { + // Open markers should have exactly one upstream wire; if multiple + // arrive, fall back to OR-ing them so we surface the topology + // weirdness rather than silently dropping branches. + if (flat.length === 1) return flat[0] + const factored = factorizePaths(flat) + if (factored.length === 1) return factored[0] + return { kind: 'or', children: factored } + } + // close + if (flat.length === 1) return flat[0] + const factored = factorizePaths(flat) + if (factored.length === 1) return factored[0] + return { kind: 'or', children: factored } +} + +/** + * Continuation visit: surface the connector expression cached + * earlier under the same `name`. If the matching connector hasn't + * been visited yet (iteration order quirk), resolve it eagerly by + * walking the connector's incoming edge now. + */ +function visitContinuation(state: WalkerState, node: RFNode): PathNode | undefined { + const data = asContinuationData(node.data) + if (data === null) { + state.warnings.push(`continuation node "${node.id}" has unrecognised data shape`) + return undefined + } + const cached = state.connectorExprs.get(data.name) + if (cached !== undefined) return leafNode([...cached]) + const connector = findConnectorByName(state, data.name) + if (connector !== undefined) { + emitConnectorNode(state, connector) + const resolved = state.connectorExprs.get(data.name) + if (resolved !== undefined) return leafNode([...resolved]) + } + state.warnings.push(`continuation "${data.name}" has no matching connector`) + return undefined +} + +function findConnectorByName(state: WalkerState, name: string): RFNode | undefined { + for (const node of state.byId.values()) { + if (node.type !== 'connector') continue + if (asConnectorData(node.data)?.name === name) return node + } + return undefined +} + +/** + * A `variable` node — both inVariable (variant='input') and inOut- + * Variable when read as an upstream — contributes its `expression` + * (or fallback variable name) as a single leaf. + */ +function visitVariable(state: WalkerState, node: RFNode): PathNode | undefined { + // Editor stores `data.variable.name` as the expression text; when + // the body comes from a non-editor source (or older versions), a + // dedicated `data.expression` field may be present instead. + const expr = asVariableExpressionData(node.data)?.expression ?? asVariableData(node.data)?.variable + if (expr === undefined || expr === '') return undefined + return leafNode([[expr, [state.tagName, 'io_variable', locId(node), 'expression']]]) +} + +/* ─────────────────────────── block emission ─────────────────────────────── */ + +/** + * One block-output read. Emits the block-call statement on first + * visit (idempotent via `emittedBlocks`) and returns a leaf naming + * the requested output — `.` for FB instances or + * `_TMP__` for functions. + */ +function visitBlockOutput(state: WalkerState, node: RFNode, edge: RFEdge, order: boolean): PathNode | undefined { + const data = asBlockData(node.data) + if (data === null) { + state.warnings.push(`block node "${node.id}" has unrecognised data shape`) + return undefined + } + const requestedOutput = + typeof edge.sourceHandle === 'string' && edge.sourceHandle.length > 0 + ? edge.sourceHandle + : (data.outputs[0] ?? 'OUT') + + // `order=true` suppresses eager emission: the upstream block emits + // at its own iteration step instead. Only emit now when walking + // from an unordered sink (or the block hasn't been reached yet + // and the consumer will need its call site). + if (data.blockKind === 'function-block-instance') { + if (!order) emitFunctionBlockCall(state, node, data) + const out = requestedOutput + return leafNode([[`${data.instanceName}.${out}`, [state.tagName, 'block', locId(node), 'output', out]]]) + } + // function path + if (!order) emitFunctionCall(state, node, data) + const out = requestedOutput + const tempName = `_TMP_${data.typeName}${data.numericId}_${out}` + return leafNode([[tempName, [state.tagName, 'block', locId(node), 'output', out]]]) +} + +function emitFunctionBlockCall(state: WalkerState, node: RFNode, data: BlockData): void { + if (state.emittedBlocks.has(node.id)) return + state.emittedBlocks.add(node.id) + + // Block-input walks inherit the block's own ordering — when the + // block carries an explicit executionOrderId, upstream blocks + // emit at their own steps; otherwise they emit eagerly. + const recurseOrdered = data.executionOrder > 0 + const info: Location = [state.tagName, 'block', locId(node)] + const parts = buildInputArgs(state, node, data, /*useNamedArgs=*/ true, recurseOrdered) + + state.program.push([state.currentIndent, []]) + state.program.push([data.instanceName, [...info, 'instance']]) + state.program.push(['(', []]) + for (let i = 0; i < parts.length; i++) { + if (i > 0) state.program.push([', ', []]) + for (const chunk of parts[i]) state.program.push(chunk) + } + state.program.push([');\n', []]) +} + +function emitFunctionCall(state: WalkerState, node: RFNode, data: BlockData): void { + if (state.emittedBlocks.has(node.id)) return + state.emittedBlocks.add(node.id) + + const info: Location = [state.tagName, 'block', locId(node)] + const wiredInputs = data.inputs.filter((name) => firstIncomingForHandle(state, node.id, name) !== undefined) + const allInputConnected = wiredInputs.length === data.inputs.length + const useNamedArgs = data.outputs.length > 1 || !allInputConnected + + const recurseOrdered = data.executionOrder > 0 + const parts = buildInputArgs(state, node, data, useNamedArgs, recurseOrdered) + + // Pick the primary output — function emits `_TMP__ + // := (...)` on the LHS; secondary outputs (e.g. `ENO`) tail + // as `param => tempName` extras. + let primaryName: string | null = null + let primaryFormal = '' + for (let i = 0; i < data.outputs.length; i++) { + const out = data.outputs[i] + const tempName = `_TMP_${data.typeName}${data.numericId}_${out}` + const tempType = out === 'ENO' ? 'BOOL' : 'ANY' + state.functionTempVars.push({ + name: tempName, + type: tempType, + originBlockTypeName: data.typeName, + originFormalParameter: out, + }) + const isPrimary = data.outputs.length === 1 || out === '' || out === 'OUT' + if (isPrimary && primaryName === null) { + primaryName = tempName + primaryFormal = out + } else { + parts.push([ + [out, [...info, 'output', i]], + [` => ${tempName}`, []], + ]) + } + } + if (primaryName === null) return + + state.program.push([state.currentIndent, []]) + state.program.push([primaryName, [...info, 'output', 0]]) + state.program.push([' := ', []]) + state.program.push([data.typeName, [...info, 'type']]) + state.program.push(['(', []]) + for (let i = 0; i < parts.length; i++) { + if (i > 0) state.program.push([', ', []]) + for (const chunk of parts[i]) state.program.push(chunk) + } + state.program.push([');\n', []]) + void primaryFormal +} + +function buildInputArgs( + state: WalkerState, + node: RFNode, + data: BlockData, + useNamedArgs: boolean, + order: boolean, +): ProgramChunk[][] { + const parts: ProgramChunk[][] = [] + for (const inputName of data.inputs) { + if (data.extensible) { + // Extensible inputs: the editor's XML serializer (`fbd-xml.ts`:67) + // emits one `` per edge that + // targets this handle. The python oracle then puts the + // serialized list through a dict keyed on the formal parameter + // (`PLCGenerator.py`:1498) — duplicates overwrite, so all + // occurrences of the same name resolve to the LAST connected + // edge's upstream. Mimic that here: emit one arg per edge, all + // using the shared "last edge" upstream's expression. + const edges = edgesForBlockInput(state, node.id, inputName) + if (edges.length === 0) continue + const lastEdge = edges[edges.length - 1] + const lastUpstream = state.byId.get(lastEdge.source) + if (lastUpstream === undefined) continue + const sharedPath = visitUpstream(state, lastUpstream, lastEdge, order) + if (sharedPath === undefined) continue + const sharedExpr = pathsToChunks([sharedPath]) + for (let i = 0; i < edges.length; i++) { + if (useNamedArgs) { + const chunk: ProgramChunk[] = [[`${inputName} := `, []]] + for (const c of sharedExpr) chunk.push(c) + parts.push(chunk) + } else { + parts.push([...sharedExpr]) + } + } + continue + } + const upstreamPaths = pathsForBlockInput(state, node.id, inputName, order) + if (upstreamPaths.length === 0) continue + const inputExpr = pathsToChunks(upstreamPaths) + if (useNamedArgs) { + const chunk: ProgramChunk[] = [[`${inputName} := `, []]] + for (const c of inputExpr) chunk.push(c) + parts.push(chunk) + } else { + parts.push([...inputExpr]) + } + } + return parts +} + +function edgesForBlockInput(state: WalkerState, blockId: string, inputName: string): RFEdge[] { + const incoming = state.incoming.get(blockId) ?? [] + return incoming.filter((e) => e.targetHandle === inputName) +} + +function pathsForBlockInput(state: WalkerState, blockId: string, inputName: string, order: boolean): PathNode[] { + const incoming = state.incoming.get(blockId) ?? [] + const out: PathNode[] = [] + for (const edge of incoming) { + if (edge.targetHandle !== inputName) continue + const upstream = state.byId.get(edge.source) + if (upstream === undefined) continue + const node = visitUpstream(state, upstream, edge, order) + if (node !== undefined) out.push(node) + } + return out +} + +function firstIncomingForHandle(state: WalkerState, blockId: string, handle: string): RFEdge | undefined { + const incoming = state.incoming.get(blockId) ?? [] + for (const edge of incoming) { + if (edge.targetHandle === handle) return edge + } + return undefined +} + +/* ─────────────────────────── 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/st-transpiler/walker/narrow.ts b/src/backend/shared/transpilers/st-transpiler/walker/narrow.ts new file mode 100644 index 000000000..127740cc7 --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/walker/narrow.ts @@ -0,0 +1,254 @@ +/** + * Narrowing helpers — read the loosely-typed `data: Record` + * payload on `RFNode` into a structured shape the walker can consume + * without `as` casts. + * + * Each helper returns `null` when the payload doesn't match the + * expected shape so the walker can decide whether to warn or skip. + * Forward-compatible: extra fields are ignored, missing optional + * fields default to safe values. + */ + +import type { CoilModifier, ContactModifier } from '../core/modifier-types' + +export type ContactVariant = 'default' | 'negated' | 'risingEdge' | 'fallingEdge' +export type CoilVariant = ContactVariant | 'set' | 'reset' +export type PowerRailSide = 'left' | 'right' +export type VariableVariant = 'input' | 'output' | 'inout' +export type BlockKind = 'function' | 'function-block' | 'function-block-instance' + +export interface ContactData { + variant: ContactVariant + variable: string +} + +export interface CoilData { + variant: CoilVariant + variable: string + executionOrder: number +} + +export interface PowerRailData { + variant: PowerRailSide +} + +export interface VariableData { + variant: VariableVariant + variable: string + executionOrder: number + block?: { id: string; handleId: string } +} + +export interface ConnectorData { + name: string +} + +export interface ContinuationData { + name: string +} + +export interface BlockData { + typeName: string + blockKind: BlockKind + instanceName: string + /** Editor-assigned numeric localId — string of digits. Feeds the + * `_TMP__` temp-var naming and the + * PLCOpen `@localId` round-trip. */ + numericId: string + executionOrder: number + executionControl: boolean + /** Extensible-input flag — when true, multiple edges may target a + * single declared input handle; the editor's XML serializer emits + * one `` per edge (`fbd-xml.ts:67`), and the python + * oracle then collapses them into "for each occurrence of the + * formal parameter in the variable list, use the LAST connected + * variable's expression" (`PLCGenerator.py:1498`). */ + extensible: boolean + /** Declared input formal-parameter names, in declaration order. */ + inputs: string[] + /** Declared output formal-parameter names, in declaration order. */ + outputs: string[] +} + +export interface VariableExpression { + expression: string +} + +export interface ParallelData { + side: 'open' | 'close' +} + +function isObject(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v) +} + +function asString(v: unknown): string | null { + return typeof v === 'string' ? v : null +} + +function asNumber(v: unknown): number | null { + return typeof v === 'number' && Number.isFinite(v) ? v : null +} + +function asBoolean(v: unknown): boolean { + return v === true +} + +function readVariableName(v: unknown): string { + if (!isObject(v)) return '' + const name = v['name'] + return typeof name === 'string' ? name : '' +} + +export function asContactData(data: Record): ContactData | null { + const variant = data['variant'] + if (variant !== 'default' && variant !== 'negated' && variant !== 'risingEdge' && variant !== 'fallingEdge') + return null + return { variant, variable: readVariableName(data['variable']) } +} + +export function asCoilData(data: Record): CoilData | null { + const variant = data['variant'] + if ( + variant !== 'default' && + variant !== 'negated' && + variant !== 'risingEdge' && + variant !== 'fallingEdge' && + variant !== 'set' && + variant !== 'reset' + ) + return null + const eo = asNumber(data['executionOrder']) + return { + variant, + variable: readVariableName(data['variable']), + executionOrder: eo ?? 0, + } +} + +export function asPowerRailData(data: Record): PowerRailData | null { + const variant = data['variant'] + if (variant !== 'left' && variant !== 'right') return null + return { variant } +} + +export function asVariableData(data: Record): VariableData | null { + // Two variant shapes coexist in real-world fixtures: + // - LD per-case + serialized LD bodies: 'input' / 'output' / 'inout' + // - Modern FBD bodies: 'input-variable' / 'output-variable' / 'inout-variable' + // Both serialize to the same PLCOpen XML element; map both to the + // short form so downstream code only sees one vocabulary. + const rawVariant = data['variant'] + let variant: VariableVariant + if (rawVariant === 'input' || rawVariant === 'input-variable') variant = 'input' + else if (rawVariant === 'output' || rawVariant === 'output-variable') variant = 'output' + else if (rawVariant === 'inout' || rawVariant === 'inout-variable') variant = 'inout' + else return null + const out: VariableData = { + variant, + variable: readVariableName(data['variable']), + executionOrder: asNumber(data['executionOrder']) ?? 0, + } + const block = data['block'] + if (isObject(block)) { + const id = asString(block['id']) + const handleId = asString(block['handleId']) + if (id !== null && handleId !== null) out.block = { id, handleId } + } + return out +} + +export function asConnectorData(data: Record): ConnectorData | null { + const name = asString(data['name']) + if (name === null) return null + return { name } +} + +export function asContinuationData(data: Record): ContinuationData | null { + const name = asString(data['name']) + if (name === null) return null + return { name } +} + +export function asParallelData(data: Record): ParallelData | null { + const t = data['type'] + if (t !== 'open' && t !== 'close') return null + return { side: t } +} + +export function asBlockData(data: Record): BlockData | null { + const variant = data['variant'] + if (!isObject(variant)) return null + const typeName = asString(variant['name']) + if (typeName === null) return null + const rawKind = variant['type'] + const blockKind: BlockKind = + rawKind === 'function-block' || rawKind === 'function-block-instance' ? 'function-block-instance' : 'function' + const inputs: string[] = [] + const outputs: string[] = [] + const variables = variant['variables'] + if (Array.isArray(variables)) { + for (const v of variables) { + if (!isObject(v)) continue + const name = asString(v['name']) + if (name === null) continue + const cls = v['class'] + if (cls === 'input') inputs.push(name) + else if (cls === 'output') outputs.push(name) + // `inOut` (also written `'inout'` in some shapes) is a single + // formal parameter that the call site binds as an input *and* + // the caller can read post-call as an output. For call-site + // emission both the python oracle and the editor's XML + // serializer treat it as an input slot (the binding appears in + // the argument list as ` := `), so we list it + // under `inputs` in source order — VAR_IN_OUT typically appears + // before VAR_INPUT in the declaration, which is the order the + // call site emits. + else if (cls === 'inOut' || cls === 'inout') inputs.push(name) + } + } + const rawNumericId = data['numericId'] + const numericId = + typeof rawNumericId === 'string' + ? rawNumericId + : typeof rawNumericId === 'number' && Number.isFinite(rawNumericId) + ? String(rawNumericId) + : '' + return { + typeName, + blockKind, + instanceName: readVariableName(data['variable']), + numericId, + executionOrder: asNumber(data['executionOrder']) ?? 0, + executionControl: asBoolean(data['executionControl']), + extensible: asBoolean(variant['extensible']), + inputs, + outputs, + } +} + +/** inVariable / outVariable / inOutVariable carry an `expression` + * string (variable name OR literal OR free-form expression). */ +export function asVariableExpressionData(data: Record): VariableExpression | null { + const expression = asString(data['expression']) + if (expression === null) return null + return { expression } +} + +export function contactModifierFromVariant(variant: ContactVariant): ContactModifier { + const mod: ContactModifier = {} + if (variant === 'negated') mod.negated = true + else if (variant === 'risingEdge') mod.edge = 'rising' + else if (variant === 'fallingEdge') mod.edge = 'falling' + return mod +} + +export function coilModifierFromVariant(variant: CoilVariant): CoilModifier { + const mod: CoilModifier = {} + if (variant === 'negated') mod.negated = true + else if (variant === 'risingEdge') mod.edge = 'rising' + else if (variant === 'fallingEdge') mod.edge = 'falling' + else if (variant === 'set') mod.storage = 'set' + else if (variant === 'reset') mod.storage = 'reset' + return mod +} diff --git a/src/backend/shared/transpilers/st-transpiler/walker/types.ts b/src/backend/shared/transpilers/st-transpiler/walker/types.ts new file mode 100644 index 000000000..a2182413f --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/walker/types.ts @@ -0,0 +1,51 @@ +/** + * Minimal types describing the React Flow LD body shape — exactly + * what the editor stores in `pous/s/.ld` after the + * declaration / variables header. + * + * Kept narrower than the renderer's `RungLadderState` (the editor + * type carries layout-only fields like `defaultBounds`, + * `reactFlowViewport`, `selectedNodes`). Only the walker-relevant + * fields are typed here; the rest passes through as `unknown` so + * harvested fixtures from different editor versions still load. + */ + +export interface RFBody { + rungs: RFRung[] +} + +export interface RFRung { + /** Layout-only; the walker doesn't read it. Optional so the FBD + * schema shape (`{ comment, nodes, edges }`, no `id`) flows in + * without a typecast at the projection boundary. */ + id?: string + comment?: string + nodes: RFNode[] + edges: RFEdge[] + /** Layout-only field. Stored as `[width, height]`; the walker reads + * the height to globalise per-rung Y coordinates the same way the + * editor's `ladder-xml.ts` does when it serialises to PLCOpen. */ + reactFlowViewport?: unknown +} + +export interface RFNode { + id: string + /** Kept as `string` (not narrowed to a literal union) so the schema + * boundary in `from-schema.ts` can pass the editor's `node.type: + * string` straight through without a typecast. Known values the + * walker dispatches on: `'powerRail' | 'contact' | 'coil' | 'block' + * | 'variable' | 'input-variable' | 'output-variable' | + * 'inout-variable' | 'parallel' | 'connector' | 'continuation'`. + * Anything else is treated as a no-op sink. */ + type: string + position: { x: number; y: number } + data: Record +} + +export interface RFEdge { + id: string + source: string + target: string + sourceHandle?: string | null + targetHandle?: string | null +} diff --git a/src/middleware/shared/ports/compiler-platform-port.ts b/src/middleware/shared/ports/compiler-platform-port.ts index 05f2aa579..cb032097c 100644 --- a/src/middleware/shared/ports/compiler-platform-port.ts +++ b/src/middleware/shared/ports/compiler-platform-port.ts @@ -35,7 +35,7 @@ * server-side, but the pipeline never knows. */ -import type { StructuredCompileError } from './types' +import type { PLCProjectData, StructuredCompileError } from './types' /** * Canonical progress callback the pipeline passes to every port @@ -99,24 +99,30 @@ export type PlatformDeviceContext = * `/generate-st` endpoint, logging a warning for anything it * doesn't recognise (defence in depth — service has its own * allowlist too). */ -export interface TranspileXmlToStArgs { - xml: string - /** Extra CLI tokens to append after `--generate-st ` in the - * xml2st invocation. For strucpp targets the shared pipeline - * sets `['--keep-structs']`; future flags get added here at the - * single call site in `runCompilePipeline`. */ - xml2stArgs: readonly string[] +/** + * Input to the in-process JSON → ST transpiler. Each adapter + * projects this into the transpiler's `TranspileProject` IR with + * its own helper — editor: `fromSchemaShape` (IPC schema-shape); + * web: `fromPortShape` after a port→schema conversion at the + * adapter boundary. Replaces the legacy XML-fed surface that + * routed through a bundled `xml2st` binary; transpilation now + * runs in-process against the JSON IR. + * + * The declared type is port-shape because that's the renderer + * store's shape; pipeline callers that hold schema-shape data + * cast through `never` at the call site (see `pipeline.ts` Step 1 + * and `library-build-orchestrator.ts` Stage 2). */ +export interface TranspileToStArgs { + projectData: PLCProjectData } -export interface TranspileXmlToStResult { +export interface TranspileToStResult { ok: boolean - /** ST source emitted by xml2st when the transpile succeeded. - * Empty / undefined on failure. */ + /** ST source emitted by the JSON transpiler when transpilation + * succeeded. Empty / undefined on failure. */ programSt?: string - /** Structured diagnostics emitted by xml2st. Web maps the - * server's `output_stderr` parser output here; editor parses - * the binary's stderr stream. Carried over to the pipeline's - * caller via the `errors[]` return on `CompileResult`. */ + /** Structured diagnostics from the transpiler. Forwarded to the + * pipeline caller via `errors[]` on `CompileResult`. */ errors?: StructuredCompileError[] /** Same shape as `errors[]` but for non-fatal warnings. */ warnings?: StructuredCompileError[] @@ -304,8 +310,8 @@ export interface CompilerPlatformPort { * hash-impl dependency. */ computeMd5(input: string): Promise - /** Step 3 of the editor pipeline. Transpile IEC XML to ST. */ - transpileXmlToSt(args: TranspileXmlToStArgs, log: PlatformLog): Promise + /** Step 3 of the editor pipeline. Transpile project IR to ST. */ + transpileToSt(args: TranspileToStArgs, log: PlatformLog): Promise /** Step 5 of the editor pipeline. Arduino-CLI core install. * Web returns `{ ok: true }` immediately. */ diff --git a/src/middleware/shared/ports/library-build-port.ts b/src/middleware/shared/ports/library-build-port.ts index 7a9b06bfc..3134a05e5 100644 --- a/src/middleware/shared/ports/library-build-port.ts +++ b/src/middleware/shared/ports/library-build-port.ts @@ -28,7 +28,7 @@ * the exact pattern. */ -import type { TranspileXmlToStArgs, TranspileXmlToStResult } from './compiler-platform-port' +import type { TranspileToStArgs, TranspileToStResult } from './compiler-platform-port' /** * Outcome of an attempted verification compile against the OpenPLC @@ -99,16 +99,16 @@ export interface LibraryBuildPort { computeMd5(input: string): Promise /** - * Transpile IEC 61131-3 XML to ST. Editor spawns the bundled - * `xml2st` binary; web posts to its compile-service. Both honor - * the same `xml2stArgs` and surface the same diagnostic shape. - * The `log` callback is the orchestrator's emit channel — every - * line xml2st produces flows through here. + * Transpile the project IR directly to ST via the in-process + * JSON transpiler (`st-transpiler`). Both editor and + * web adapters project their port-shape input into the + * transpiler's minimal `TranspileProject` IR. The `log` + * callback is the orchestrator's emit channel. */ - transpileXmlToSt( - args: TranspileXmlToStArgs, + transpileToSt( + args: TranspileToStArgs, log: (message: string, level: 'info' | 'warning' | 'error') => void, - ): Promise + ): Promise // ------------------------------------------------------------------------- // Generic file IO over the project tree