571 lines
17 KiB
TypeScript
571 lines
17 KiB
TypeScript
|
/**
|
||
|
* @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<any>)[0];
|
||
|
} else if (/(\d+)/.test(addr)) {
|
||
|
/** Extract the Offset using a Regular expression */
|
||
|
offset = (/(\d+)/.exec(addr) as Array<any>)[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);
|
||
|
}
|