/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2018-12-19 15:26:04 * @modify date 2018-12-19 15:26:04 * @desc [Tool to convert the the provided Inputs to a config file] */ import { writeFileSync } from "fs"; import { capitalize } from "lodash"; const readXlsxFile = require("read-excel-file/node"); const xl = require("excel4node"); export const DEFAULT_PLC_MODULE_CONFIG_FILE = "./config/plc-module-configuration.json"; export const DEFAULT_PORT_CONFIG_FILE = "./modules/mod-Generic-PLC-Interface/helpers/SPS-Belegung.txt"; export const DEFAULT_PLC_CONFIG_FILE = "./config/plc-configuration.json"; export async function loadExcelFile( _pathToExcelFile = "", _configFile = "", _pathToVarFile = "" ) { 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")); if (!_pathToExcelFile) { _pathToExcelFile = ( await inquirer.prompt([ { type: "path", name: "excelSheet", excludePath: (nodePath) => nodePath.startsWith(".git") || nodePath.startsWith("node_modules") || nodePath.startsWith("dist"), // excludePath :: (String) -> Bool // excludePath to exclude some paths from the file-system scan itemType: "file", // itemType :: 'any' | 'directory' | 'file' // specify the type of nodes to display // default value: 'any' // example: itemType: 'file' - hides directories from the item list rootPath: "./", // rootPath :: String // Root search directory message: "Select an excel sheet which shoud be used:", default: "modules\\ZISS-Generic-PLC-Interface\\helpers\\sim-file.xlsx", suggestOnly: true, // suggestOnly :: Bool // Restrict prompt answer to available choices or use them as suggestions depthLimit: 0 // depthLimit :: integer >= 0 // Limit the depth of sub-folders to scan // Defaults to infinite depth if undefined } ]) ).excelSheet; } if (!_configFile) { _configFile = ( await inquirer.prompt([ { type: "input", name: "configFile", message: "Enter the Filename, where the File 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)"; } } ]) ).configFile; } if (!_pathToVarFile) { _pathToVarFile = ( await inquirer.prompt([ { type: "input", name: "output", message: "Enter Variable Table", default: "./config/vars.xlsx", validate: function (value: string) { if (value.endsWith(".xlsx")) { return true; } return "Please add the Enter a Valid Filename (including .xlsx)"; } } ]) ).output; } const schema = { "Signal Name": { prop: "name", type: String, required: true }, Memory: { prop: "memory", type: String }, Type: { prop: "type", type: String }, "Robot Signal Name": { prop: "robotSignal", type: String }, Address: { prop: "byteAddress", type: String }, "IEC Format": { prop: "address", type: String } }; const dataSheet = await readXlsxFile(_pathToExcelFile, { schema }); const inputs = {}; const outputs = {}; let maxByteNumber = -Infinity; // https://www.spshaus.ch/files/inc/Downloads/Lernumgebung/Downloads/Allgemein/TIA_Portal_Uebersicht_Datentypen.pdf const originalDataTypes = [ { prefix: "", validTypes: ["BOOL"], size: 1 }, { prefix: "B", validTypes: ["BYTE", "CHAR", "SINT", "USINT", "WCHAR"], size: 1 }, { prefix: "W", validTypes: ["INT", "UINT", "DATE"], size: 2 }, { prefix: "D", validTypes: ["REAL", "UINT", , "UDINT", "TIME", "TOD"], size: 4 } ]; const getPrefix = (type: string) => { let ret = ""; /** Iterate over the Valid Types */ for (const element of originalDataTypes) { if (element.validTypes.includes(type.toUpperCase())) { return element.prefix; } } return ret; }; const getLastAddress = (type: string, address: string) => { let high = parseFloat(address.split(".")[0]); /** Iterate over the Valid Types */ for (const element of originalDataTypes) { if (element.validTypes.includes(type.toUpperCase())) { return element.size + high; } } return high + 1; }; dataSheet.rows.map((element) => { // A Row is defined like // { // name: 'Tragwerk_Steel_structure_Position', // memory: false, // type: 'REAL', // byteAddress: 'No Address', // address: 'I' // }, if ( element.byteAddress != "No Address" && element.address.toLowerCase().startsWith("i") ) { // Input inputs[element.name] = { start: element.byteAddress, type: element.type, pathToData: "SIMULATION.term01.inputs." + element.name, plcVarName: element.name }; } else if ( element.byteAddress != "No Address" && element.address.toLowerCase().startsWith("q") ) { // Output outputs[element.name] = { start: element.byteAddress, type: element.type, pathToData: "SIMULATION.term01.outputs." + element.name, plcVarName: element.name }; } if (maxByteNumber < getLastAddress(element.type, element.byteAddress)) { maxByteNumber = getLastAddress(element.type, element.byteAddress); } }); const _plcConfig = { plcs: { SIMULATION: { name: "SIMULATION", modules: { inputs: { term01: { start: 0, len: maxByteNumber, pathToData: "SIMULATION.modules.term01.rawData.input", db: "input", elements: inputs } }, outputs: { term01: { start: 0, len: maxByteNumber, pathToData: "SIMULATION.modules.term01.rawData.output", db: "output", elements: outputs } }, pathToSubscribe: "SIMULATION.modules.rawData.output", pathToPublish: "SIMULATION.modules.rawData.input" }, connection: { mqtt: { hostname: "localhost", port: 1883 }, plc: { ip: "192.168.2.2", rack: 0, slot: 0 } } } }, load: "SIMULATION" }; /** Write the new Configuration */ writeFileSync(_configFile, JSON.stringify(_plcConfig, undefined, 4)); /** Create a new Excel file * */ // Create a new instance of a Workbook class var wb = new xl.Workbook(); // Add Worksheets to the workbook var ws = wb.addWorksheet("PLC Tags"); const rows = [ "Name", "Path", "Data Type", "Logical Address", "Comment", "Hmi Visible", "Hmi Accessible", "Hmi Writeable", "Typeobject ID", "Version ID" ]; for (const [index, tag] of rows.entries()) { /** Define the header */ ws.cell(1, index + 1).string(tag); } let rowNumber = 2; for (const content of dataSheet.rows) { if (content.byteAddress != "No Address") { ws.cell(rowNumber, 1).string(content.name); ws.cell(rowNumber, 2).string("Standard-Variablentabelle"); ws.cell(rowNumber, 3).string(capitalize(content.type)); // Determine the Prefix const inOutPrefix = content.address.toLowerCase().startsWith("i") || content.address.toLowerCase().startsWith("e") ? "I" : "Q"; ws.cell(rowNumber, 4).string( "%" + inOutPrefix + getPrefix(content.type) + content.byteAddress ); ws.cell(rowNumber, 6).string("True"); ws.cell(rowNumber, 7).string("True"); ws.cell(rowNumber, 8).string("True"); rowNumber += 1; } } wb.write(_pathToVarFile); } if (require.main == module) { let _pathToExcelFile = "", _configFile = "", _pathToVarFile = ""; /** Extract the Process pathes. */ process.argv.forEach((value, idx) => { switch (idx) { case 2: _pathToExcelFile = value; break; case 3: _configFile = value; break; case 4: _pathToVarFile = value; break; } }); loadExcelFile(_pathToExcelFile, _configFile, _pathToVarFile); }