/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2018-12-19 15:26:04 * @modify date 2020-03-12 21:40:52 * @desc [Tool to convert the the provided Inputs to a config file] */ import { existsSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; import { SPLITCHAR } from "../../../lib/helpers/objectMethods"; import { createCollectionObject } from "../../mod-Arango-Connector/src/creating"; /** Imports for File Handeling */ import { getAddress, getBitNumber, getType } from "../src/logic-functions"; import { IPLCsConfig, IPLCsModuleConfig, validTypes } from "../type/interfaces"; /** * Function to Convert the Belegungs-Txt to a JSON Object * @param _pathToFile The Path to the File. */ export function convertPortConfig(_pathToFile = "SPS-Belegung.txt") { if (existsSync(join(process.cwd(), _pathToFile))) { const data = readFileSync(join(process.cwd(), _pathToFile)).toString(); /** Split the Config based on '\n' */ const lines = data.split("\n"); const ports: { inputs: { [index: string]: { start: string; type: validTypes; db: "input" | "output" | number; }; }; outputs: { [index: string]: { start: string; type: validTypes; db: "input" | "output" | number; }; }; } = { inputs: {}, outputs: {} }; /** Iterate over the Lines */ for (const line of lines) { /** The Values are splitted by a tab-stop */ const values = line.split("\t"); /** Extract the Values, only if the Line is Valid */ if (values.length >= 4) { /** Based on the Location extract the Name, Type and Address */ const name = values[0]; let db: "input" | "output" | number = 0; if (values[1].replace(/[0-9]/g, "").toUpperCase() === "DB") { db = parseInt(values[1].toUpperCase().split("DB")[1]); } else { db = values[3].toUpperCase().startsWith("%I") ? "input" : "output"; } const type = getType(values[2].toUpperCase()); const addr = values[3].toUpperCase(); const isInput = addr.startsWith("%I"); let offset: string = ""; /** Test if the offset could be extracted */ if (/(\d+)\.(\d+)/.test(addr)) { /** Extract the Offset using a Regular expression */ offset = (/(\d+)\.(\d+)/.exec(addr) as Array)[0]; } else if (/(\d+)/.test(addr)) { /** Extract the Offset using a Regular expression */ offset = (/(\d+)/.exec(addr) as Array)[0] + ".0"; } /** Check if all Data are */ if (name && type && addr && offset) { /** Store the Value in the Config */ ports[isInput ? "inputs" : "outputs"][name] = { start: offset, type, db }; } else { console.log("Can't read line: \"" + line + '"'); console.log(name, type, addr, offset); } } } return ports; } return null; } export const DEFAULT_PLC_MODULE_CONFIG_FILE = "./config/plc-module-configuration.json"; export const DEFAULT_PORT_CONFIG_FILE = "./modules/ZISS-Generic-PLC-Interface/helpers/SPS-Belegung.txt"; export const DEFAULT_PLC_CONFIG_FILE = "./config/plc-configuration.json"; export async function adaptConfig( _moduleConfig = "", _portConfig = "", _plcConfig = "" ) { process.stdout.write("\x1B[2J\x1B[0f"); // Import Inquirer and the Fuzzy-Path Module const inquirer = require("inquirer"); inquirer.registerPrompt("path", require("inquirer-fuzzy-path")); let result: any = {}; /** Create the File Name */ const _dirName = join(process.cwd(), "config"); if (!_moduleConfig) { _moduleConfig = ( await inquirer.prompt([ { type: "input", name: "moduleConfig", message: "Enter the Configuration File of the Utilized PLC-Modules", default: DEFAULT_PLC_MODULE_CONFIG_FILE, validate: function (value: string) { if (value.endsWith(".json")) { return true; } return "Please add the Enter a Valid Filename (including .json)"; } } ]) ).moduleConfig; } if (!_portConfig) { _portConfig = ( await inquirer.prompt([ { type: "input", name: "portConfig", message: "Enter the Configuration File of the Ports, provided by a Siemens PLC", default: DEFAULT_PORT_CONFIG_FILE, validate: function (value: string) { if (value.endsWith(".txt")) { return true; } return "Please add the Enter a Valid Filename (including .txt)"; } } ]) ).portConfig; } if (!_plcConfig) { _plcConfig = ( await inquirer.prompt([ { type: "input", name: "plcConfig", message: "Enter the Configuration File, in whicht the configuration should be stored", default: DEFAULT_PLC_CONFIG_FILE, validate: function (value: string) { if (value.endsWith(".json")) { return true; } return "Please add the Enter a Valid Filename (including .json)"; } } ]) ).plcConfig; } let _config: IPLCsModuleConfig = { plcs: {} }; let _plcConfigObject: { plcs: IPLCsConfig } = { plcs: {} }; const _ports = convertPortConfig(_portConfig); console.log(_portConfig); if (_ports) { /** Check if a Config exists => If So Extend the given config */ while (!existsSync(_moduleConfig)) { _config = { plcs: { S7MRK: { name: "S7MRK", options: { connection: { mqtt: { hostname: "127.0.0.1", port: 1883 }, plc: { ip: "192.168.2.2", rack: 0, slot: 0 } } }, type: "raw", modules: { inputs: { "Digital-Term-01": { start: 0, len: 4, pathToData: "S7MRK.modules.Digital-Term-01.rawData.input.value", db: "input" }, "Analog-Term-01": { start: 4, len: 16, pathToData: "S7MRK.modules.Analog-Term-01.rawData.input.value", db: "input" }, "Nachladestation.CPX": { start: 64, len: 2, pathToData: "S7MRK.Nachladestation.CPX.rawData.input.value", db: "input" }, "Nachladestation.Motor": { start: 32, len: 30, pathToData: "S7MRK.Nachladestation.Motor.rawData.input.value", db: 19 }, "Schrauber.Motor": { start: 32, len: 30, pathToData: "S7MRK.Schrauber.Motor.rawData.input.value", db: 20 }, "ET200.Digital": { start: 20, len: 4, pathToData: "S7MRK.modules.ET200.Digital.rawData.input.value", db: "input" }, "ET200.Analog": { start: 24, len: 8, pathToData: "S7MRK.modules.ET200.Analog.rawData.input.value", db: "input" }, C30SXMv3: { start: 256, len: 29, pathToData: "S7MRK.modules.C30SXMv3.rawData.input.value", db: "input" }, Kamera: { start: 32, len: 30, pathToData: "S7MRK.modules.Kamera.rawData.input.value", db: 100 } }, outputs: { "Digital-Term-01": { start: 0, len: 4, pathToData: "S7MRK.modules.Digital-Term-01.rawData.output.value", db: "output" }, "Analog-Term-01": { start: 4, len: 8, pathToData: "S7MRK.modules.Analog-Term-01.rawData.output.value", db: "output" }, "Nachladestation.CPX": { start: 64, len: 4, pathToData: "S7MRK.Nachladestation.CPX.rawData.output.value", db: "output" }, "Nachladestation.Motor": { start: 0, len: 32, pathToData: "S7MRK.Nachladestation.Motor.rawData.output.value", db: 19 }, "Schrauber.Motor": { start: 0, len: 32, pathToData: "S7MRK.Schrauber.Motor.rawData.output.value", db: 20 }, "ET200.Digital": { start: 12, len: 4, pathToData: "S7MRK.modules.ET200.Digital.rawData.output.value", db: "output" }, C30SXMv3: { start: 256, len: 2, pathToData: "S7MRK.modules.C30SXMv3.rawData.output.value", db: "output" }, Kamera: { start: 0, len: 32, pathToData: "S7MRK.modules.Kamera.rawData.output.value", db: 100 } }, pathToSubscribe: "S7MRK.modules.rawData.output.value", pathToPublish: "S7MRK.modules.rawData.input.value" } } }, // load: "S7MRK" }; /** Write the new Configuration */ writeFileSync(_moduleConfig, JSON.stringify(_config, undefined, 4)); console.log( new Date().toISOString(), "Generated sample Config File. Will use this one." ); } _config = JSON.parse(readFileSync(_moduleConfig, { encoding: "utf-8" })); result = await inquirer.prompt([ { type: "list", name: "selectedPLC", message: "What size do you need?", choices: Object.getOwnPropertyNames(_config.plcs) }, { type: "checkbox", name: "inputs", message: "Select/Deselect the available Inputs", choices: Object.getOwnPropertyNames(_ports.inputs).map((name) => { return { name, checked: true }; }) }, { type: "checkbox", name: "outputs", message: "Select/Deselect the available Outputs", choices: Object.getOwnPropertyNames(_ports.outputs).map((name) => { return { name, checked: true }; }) } ]); const unfound = []; const plcConfig = _config.plcs[result.selectedPLC]; // Update the Configuration Object. _plcConfigObject.plcs[result.selectedPLC] = { modules: { inputs: {}, outputs: {}, pathToPublish: plcConfig.modules.pathToPublish, pathToSubscribe: plcConfig.modules.pathToSubscribe }, name: result.selectedPLC, options: plcConfig.options, type: plcConfig.type }; for (const _type of ["inputs", "outputs"]) { const type: "inputs" | "outputs" = _type as "inputs" | "outputs"; /** Overwrite old Config */ for (const name in _config.plcs[result.selectedPLC].modules[type]) { // Define initial Config Entry. _plcConfigObject.plcs[result.selectedPLC].modules[type][name] = { // In the Beginning No Element is Provided elements: {}, // Use the Length of the Module len: _config.plcs[result.selectedPLC].modules[type][name].len, // Module Name name, // Path for the Buffer pathToPublishBufferContent: _config.plcs[result.selectedPLC].modules[type][name].pathToData, // Path for the Buffer pathToSubscribeBufferContent: _config.plcs[result.selectedPLC].modules[type][name].pathToData, // PLC Settings of the Module plc: { // PLC Name name: result.selectedPLC, // Path for Publishing updated Buffers pathToPublishModules: _config.plcs[result.selectedPLC].modules.pathToPublish, // Path for Subscribing updated Buffers pathToSubscribeModules: _config.plcs[result.selectedPLC].modules.pathToSubscribe }, // Start Address. start: _config.plcs[result.selectedPLC].modules[type][name].start, // Type of the Module (Input / Output) type }; } for (const portName of result[type]) { /** Get the Object */ const port = _ports[type as "inputs" | "outputs"][portName]; let found = false; if (typeof port.db === "string") { /** Iterate over the Modules to finde the corresponding Definition. */ for (const modName in _config.plcs[result.selectedPLC].modules[ type ]) { const mod = _config.plcs[result.selectedPLC].modules[type][modName]; const min = mod.start * 8; const max = (mod.start + mod.len) * 8; if ( typeof mod.db === "string" && getBitNumber(port.start) >= min && getBitNumber(port.start) < max ) { console.log("[x]", portName, "->", modName); /** Adapt the Bit-Address to Match the Modules Buffer */ port.start = getAddress(getBitNumber(port.start) - min); /** Extract the Module Name */ _plcConfigObject.plcs[result.selectedPLC].modules[type][ modName ].elements[portName] = Object.assign( { pathToData: result.selectedPLC + SPLITCHAR + modName + SPLITCHAR + type + SPLITCHAR + portName + SPLITCHAR + "value" }, port ); found = true; break; } } } else { /** Element is mapped to a DB */ for (const modName in _config.plcs[result.selectedPLC].modules[ type ]) { const mod = _config.plcs[result.selectedPLC].modules[type][modName]; const min = mod.start * 8; const max = (mod.start + mod.len) * 8; if ( typeof mod.db === "number" && mod.db == port.db && getBitNumber(port.start) >= min && getBitNumber(port.start) < max ) { console.log("[x]", portName, "->", modName); /** Adapt the Bit-Address to Match the Modules Buffer */ port.start = getAddress(getBitNumber(port.start) - min); /** Extract the Module Name */ _plcConfigObject.plcs[result.selectedPLC].modules[type][ modName ].elements[portName] = Object.assign( { pathToData: result.selectedPLC + SPLITCHAR + modName + SPLITCHAR + type + SPLITCHAR + portName + SPLITCHAR + "value" }, port ); found = true; break; } } } if (!found) { console.log("[ ]", portName); unfound.push(portName); } } } console.log("\n\n\n"); process.stdout.write("\x1B[2J\x1B[0f"); writeFileSync(_plcConfig, JSON.stringify(_plcConfigObject, undefined, 4)); console.log("\n\n\n\n" + new Date().toISOString(), "Generated Config File"); console.log( "\n\nUnfound Elements:\n", JSON.stringify(unfound, undefined, 4) ); await createCollectionObject("config", "plc", _plcConfigObject); } } if (require.main == module) { let moduleConfig = ""; let portConfig = ""; let plcConfig = ""; /** Extract the Process pathes. */ process.argv.forEach((value, idx) => { switch (idx) { case 2: moduleConfig = value; break; case 3: portConfig = value; break; case 4: plcConfig = value; break; } }); adaptConfig(moduleConfig, portConfig, plcConfig); }