2020-08-30 07:45:44 +00:00
|
|
|
/**
|
|
|
|
* @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 =
|
2020-09-08 14:59:06 +00:00
|
|
|
"./modules/mod-Generic-PLC-Interface/helpers/SPS-Belegung.txt";
|
2020-08-30 07:45:44 +00:00
|
|
|
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);
|
|
|
|
}
|