Посібник по промисловим мережам
Даний розділ описує бібліотеку NodeS7, оригінальний репозиторій знаходиться за наступним посиланням: https://github.com/plcpeople/nodeS7
NodeS7 — це бібліотека для Node.js, яка дозволяє зв’язуватися з ПЛК S7-300/400/1200/1500 за допомогою протоколу Siemens S7 Ethernet RFC1006. Це програмне забезпечення жодним чином не пов’язане з Siemens, як і автор. S7-300, S7-400, S7-1200 і S7-1500 є товарними знаками Siemens AG.
Даний підрозділ перекладено з оригінального репозиторію.
Використовуючи npm:
npm install nodes7Використовуючи yarn:
yarn add nodes7Bad, і зрештою з’єднання буде автоматично відновлено.Logo 0BA8 , хоча вам слід налаштувати локальний і віддалений TSAP відповідно до вашого проекту, а ваші адреси потрібно вказати по-різному. DB1,INT0 має отримати VM0. DB1,INT1118 має отримати AM1.Credit to the S7 Wireshark dissector plugin for help understanding why things were not working. (http://sourceforge.net/projects/s7commwireshark/)
DB24,REAL0 повертатиме вихідну частоту. Якби цей параметр був масивом, DB24,REAL1 повернув би наступний у послідовності, навіть якщо програміст Siemens мав би спокусу використати REAL4, що в даному випадку є неправильним. З цієї причини звичайну оптимізацію S7 потрібно вимкнути. Після оголошення conn = new nodes7; (або подібного) додайте conn.doNotOptimize = true;, щоб переконатися, що цього не зроблено, і не намагайтеся запитувати ці елементи за допомогою нотації масиву, оскільки це передбачає оптимізацію, запит REAL0, потім REAL1 тощо. Тепер doNotOptimize також підтримується як параметр підключення.Заслуга плагіна S7 Wireshark dissector, який допоміг зрозуміти, чому щось не працює. (http://sourceforge.net/projects/s7commwireshark/)
var nodes7 = require('nodes7'); // Це назва пакета, якщо репозиторій клоновано, вам може знадобитися вимагати «nodeS7» із S у верхньому регістрі
var conn = new nodes7;
var doneReading = false;
var doneWriting = false;
var variables = {
TEST1: 'MR4', // Memory real at MD4
TEST2: 'M32.2', // Bit at M32.2
TEST3: 'M20.0', // Bit at M20.0
TEST4: 'DB1,REAL0.20', // Array of 20 values in DB1
TEST5: 'DB1,REAL4', // Single real value
TEST6: 'DB1,REAL8', // Another single real value
TEST7: 'DB1,INT12.2', // Two integer value array
TEST8: 'DB1,LREAL4', // Single 8-byte real value
TEST9: 'DB1,X14.0', // Single bit in a data block
TEST10: 'DB1,X14.0.8' // Array of 8 bits in a data block
};
// slot 2 for 300/400, slot 1 for 1200/1500, change debug to true to get more info
conn.initiateConnection({ port: 102,
host: '192.168.56.102',
rack: 0, slot: 2,
debug: true }, connected);
// conn.initiateConnection({port: 102, host: '192.168.0.2', localTSAP: 0x0100, remoteTSAP: 0x0200, timeout: 8000, doNotOptimize: true}, connected);
// локальний і віддалений TSAP також можна вказати безпосередньо. Опція тайм-ауту вказує час очікування TCP.
function connected(err) {
if (typeof(err) !== "undefined") {
// У нас сталася помилка. Можливо, ПЛК недоступний.
console.log(err);
process.exit();
}
// Це встановлює "переклад", щоб ми могли працювати з назвами об’єктів
conn.setTranslationCB(function(tag) { return variables[tag]; });
conn.addItems(['TEST1', 'TEST4']);
conn.addItems('TEST6');
// conn.removeItems(['TEST2', 'TEST3']); // We could do this.
// conn.writeItems(['TEST5', 'TEST6'], [ 867.5309, 9 ], valuesWritten);
// You can write an array of items as well.
// conn.writeItems('TEST7', [666, 777], valuesWritten);
// You can write a single array item too.
// This writes a single boolean item (one bit) to true
conn.writeItems('TEST3', true, valuesWritten);
conn.readAllItems(valuesReady);
}
function valuesReady(anythingBad, values) {
if (anythingBad) { console.log("SOMETHING WENT WRONG READING VALUES!!!!"); }
console.log(values);
doneReading = true;
if (doneWriting) { process.exit(); }
}
function valuesWritten(anythingBad) {
if (anythingBad) { console.log("SOMETHING WENT WRONG WRITING VALUES!!!!"); }
console.log("Done writing.");
doneWriting = true;
if (doneReading) { process.exit(); }
}
Підключається до PLC.
Arguments
Options
| Property | type | default |
|---|---|---|
| rack | number | 0 |
| slot | number | 2 |
| port | number | 102 |
| host | string | 192.168.8.106 |
| timeout | number | 5000 |
| localTSAP | hex | undefined |
| remoteTSAP | hex | undefined |
callback(err)
err - є або об’єктом помилки, або undefined у разі успішного підключення.Відключається від ПЛК. Це просто розриває з’єднання TCP.
Arguments
callback()
Зворотний виклик викликається після завершення закриття.
Встановлює зворотний виклик для перекладу імені та адреси.
Це необов’язково – ви можете використовувати addItem тощо з абсолютними адресами.
Якщо ви його використовуєте, translator має бути функцією, яка приймає рядок як аргумент і повертає рядок у такому форматі: <data block number.><memory area><data type><byte offset><.array length>
Приклади:
Тип DT — це добре відомий тип DATE_AND_TIME ПЛК S7-300/400, поле шириною 8 байт із частинами, закодованими у BCD
Тип DTZ такий самий, як і DT, але він очікує, що мітка часу в ПЛК у форматі UTC (зазвичай це НЕ так)
Тип DTL – це той, який можна побачити на нових ПЛК S7-1200/1500, має довжину 12 байт і кодує мітку часу інакше, ніж старіший DATE_AND_TIME
Тип DTLZ також такий самий, як і DTL, але очікується позначка часу в UTC у ПЛК
У наведеному вище прикладі оголошено об’єкт, а translator посилається на цей об’єкт. Це може просто посилатися на файл або базу даних. У будь-якому випадку це дозволяє писати чистіший код Javascript, який посилається на ім’я замість абсолютної адреси.
Додає items до внутрішнього списку опитувань читання.
Аргументи
items може бути string або масивом string . Якщо items містить значення _COMMERR, воно поверне поточний статус зв’язку.
Видаляє items з внутрішнього списку опитувань читання.
Аргументи
items може бути string або масивом string . Якщо items не визначено, усі елементи буде видалено.
Записує items до ПЛК, використовуючи відповідні values, і після завершення викликає callback. Ви повинні стежити за поверненим значенням - якщо воно відмінне від нуля, запис не буде оброблено, оскільки він вже триває, і зворотний виклик не буде викликано.
Аргументи
items може бути string або масивом string. Якщо items є одним string , values має бути одним елементом. Якщо items є масивом string , values також має бути масивом значень.
callback(err)
Читає внутрішній список опитувань і після завершення викликає callback.
Аргументи
callback(err, values)
Даний розділ та інші файли папки репозиторію розібрані та прокоментовані вже мною.
txt з вказаним id та рівнем критичності debugLevelisoConnectionState=0) і якщо немає ніяких запитів запускається таймер на 3.5 с після чого запускається функція resetNowisoConnectionState = 0), та закриває зєднанняcb при перетворенні.instantWriteBlockList з елементів типу S7Item що записуються та їх значень плся чого викликає prepareWritePacket для … та sendWritePacket для.S7Item за текстовим представленнямaddRemoveArray - Масив дій з елементами
{arg: arg,
action: 'add' //дії add, remove,
}
connectCallback
connectCBIssued
connectionID
connectionParams
connectReq - буфер запиту на підключення по ISO/TCP
connectTimeout - таймер, який виконує функцію при таймауту пакету
doNotOptimize
dropConnectionCallback
dropConnectionTimer
effectiveDebugLevel = opts.debug ? 99 : 0
globalReadBlockList - глобальний список зчитуваних елементів що містять елементи S7Item
globalTimeout -
globalWriteBlockList - глобальний список записуваних елементів що містять елементи S7Item
instantWriteBlockList - Список S7Item для запису.
isoclient - клієнтський сокет
isoConnectionState -
0 = no connection
1 = trying to connect
2 = TCP connected, wait for ISO connection confirmation
3 = ISO-ON-TCP connected, Wait for PDU response
4 = Received PDU response, good to go
localTSAP
masterSequenceNumber
maxGap
maxParallel
maxPDU - параметри для переговорів при підключенні
negotiatePDU - буфер з даних запиту, що відправляються після відповіді-підтвердження на з’єднання, переговори по S7
opts - опції
parallelJobsNow
PDUTimeout -
polledReadBlockList - Масив елментів для читання
rack
readDoneCallback
readPacketArray - масив пакетів на читання
seqNum - номер пакету
sent - TRUE - пакет відправлено
rcvd - TRUE - відповідь на пакет отримано
itemList - масив S7Item
timeoutError| silentMode = opts.silent | false |
setTranslationCBwritePacketArray - масив пакетів на запис
seqNum - номер пакетуsent - TRUE - пакет відправлено
rcvd - TRUE - відповідь на пакет отримано
itemList - масив S7Item
timeoutErrorВластивості:
addr - адреса в форматі S7, наприклад DB1,X14.0
useraddr - користувацька навза елементу
addrtype - тип елементу S7:
datatype - тип даних S7:
dbNumber - номер DB, наприклад 1 (TEST10: 'DB1,X14.0' )
bitOffset - адреса біту, 14
offset - адреса байту, 14
arrayLength - довжина масиву
dtypelen - довина в байтах емелементу масиву
areaS7Code - область даних в S7
0x84 - DB, DI0x81 - I0x82 - Q0x83 - M0x80 - P0x1c - C0x1d - TbyteLength - загальний обєм в байтах
byteLengthWithFill - вирівняний обєм в байтах
readTransportCode -
0x04 - Deault0x09 - для areaS7Code:
writeTransportCode - повторює readTransportCode, з винятком:
0x03 - для datatype === ‘X’byteBuffer - сирі прочитані дані
writeBuffer
qualityBuffer - буфер з заповненими значеннями якосіт для даних:
0xFF - BAD0xC0 - GoodwriteQualityBuffer
value - значення
writeValue
valid - прийшли валідні дані
errCode - код помилки в текстовому вигляді
part
maxPart
isOptimized
resultReference
itemReference
Методи:
clone - клонування об’єкту
badValue - повертає значення по дефолту в залежності від типу обєкту datatype
З точки зору користувача:
nodes7, наприклад conninitiateConnection з передачею параметрів підключення та функцією зворотного виклику наприклад connectedvar nodes7 = require('nodes7'); // Це назва пакета, якщо репозиторій клоновано, вам може знадобитися вимагати «nodeS7» із S у верхньому регістрі
var conn = new nodes7;
// slot 2 for 300/400, slot 1 for 1200/1500, change debug to true to get more info
conn.initiateConnection({ port: 102,
host: '192.168.56.102',
rack: 0, slot: 2,
debug: true }, connected);
function connected(err) {
if (typeof(err) !== "undefined") {
// У нас сталася помилка. Можливо, ПЛК недоступний.
console.log(err);
process.exit();
}
// Далі використання conn для читання та запису
conn.setTranslationCB(function(tag) { return variables[tag]; });
conn.addItems(['TEST1', 'TEST4']);
conn.writeItems('TEST3', true, valuesWritten);
conn.readAllItems(valuesReady);
//...
}
Функція initiateConnection :
isoclientisoclient = net.connect(cParam);isoConnectionState = 2 - TCP підключено, чекаємо підключення ISOconnBuf = self.connectReq.slice(); - де connectReq - масив з байтів запиту на підключенняconnBuf localTSAP, remoteTSAPwrite сокету isoclientisoConnectionState = 3 - ISO-ON-TCP connected, Wait for PDU responsenegotiatePDU (буфер запиту переговорів по S7)isoclient.writedata=checkRFCData(theData);data==="fastACK" (старі ПЛК), повторно очікуємо прослушки, щоб доотримати порцію даних, що залишиласяmaxParallel та maxPDU, self.isoConnectionState = 4 - 4 = Received PDU response, good to go; включається прослушка на подію ‘data’: onResponse (див нижче опис)connected з передачею відповідної помилкиToDo
Запис відбувається викликом writeItems.
writeItems (arg, value, cb)
arg - назва змінної, або масив назв для запису
value - значення для запису, або масив значень для запису
cb - функція зворотного виклику для виклику після запису
ToDo
var nodes7 = require('nodes7');
var conn = new nodes7;
var doneWriting = false;
var variables = {
TEST3: 'M20.0', // Bit at M20.0
};
conn.initiateConnection({ port: 102,
host: '192.168.56.102',
rack: 0, slot: 2,
debug: true }, connected);
function connected(err) {
if (typeof(err) !== "undefined") {
// У нас сталася помилка. Можливо, ПЛК недоступний.
console.log(err);
process.exit();
}
// Це встановлює "переклад", щоб ми могли працювати з назвами об’єктів
conn.setTranslationCB(function(tag) { return variables[tag]; });
conn.writeItems('TEST3', true, valuesWritten);
}
function valuesWritten(anythingBad) {
if (anythingBad) { console.log("SOMETHING WENT WRONG WRITING VALUES!!!!"); }
console.log("Done writing.");
doneWriting = true;
if (doneReading) { process.exit(); }
}
Після підключення ставиться прослушка на подію ‘data’: onResponse, яка обробляє отримані вхідні дані:
data=checkRFCData(theData);, (старі ПЛК), повторно очікуємо прослушки, щоб доотримати порцію даних, що залишиласяisReadResponse = true;, виклик readResponse (data, foundSeqNum):
!sent або обробка вже відбуваласяrcvd==TRUE тоді повернути nullitemList для кожного з яких вилкикати processS7Packet(data, itemList[itemCount], dataPointer, connectionID), який:
qualityBuffer та validbyteBuffer та кількість отриманих байт в byteLengthbyteLength з урахуванням вирівнюванняreadPacketArray.every(doneSending)(doneSending) тоді
globalReadBlockList і для кожного елементу
sendReadPacket()isWriteResponse = true;виклик writeResponse (data, foundSeqNum), який:
var net = require("net");
var util = require("util");
var effectiveDebugLevel = 0; // intentionally global, shared between connections
var silentMode = false;
module.exports = NodeS7;
function NodeS7(opts) {
opts = opts || {};
silentMode = opts.silent || false;
effectiveDebugLevel = opts.debug ? 99 : 0
var self = this;
self.connectReq = Buffer.from([0x03, 0x00, 0x00, 0x16, 0x11, 0xe0, 0x00, 0x00, 0x00, 0x02, 0x00, 0xc0, 0x01, 0x0a, 0xc1, 0x02, 0x01, 0x00, 0xc2, 0x02, 0x01, 0x02]);
self.negotiatePDU = Buffer.from([0x03, 0x00, 0x00, 0x19, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x08, 0x00, 0x08, 0x03, 0xc0]);
self.readReqHeader = Buffer.from([0x03, 0x00, 0x00, 0x1f, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x04, 0x01]);
self.readReq = Buffer.alloc(1500);
self.writeReqHeader = Buffer.from([0x03, 0x00, 0x00, 0x1f, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x05, 0x01]);
self.writeReq = Buffer.alloc(1500);
self.resetPending = false;
self.resetTimeout = undefined;//Timed reset has happened (connectionReset)
self.isoclient = undefined; // клієнтський сокет
self.isoConnectionState = 0;
self.requestMaxPDU = 960;
self.maxPDU = 960;
self.requestMaxParallel = 8;
self.maxParallel = 8;
self.parallelJobsNow = 0;
self.maxGap = 5;
self.doNotOptimize = false;
self.connectCallback = undefined;
self.readDoneCallback = undefined;
self.writeDoneCallback = undefined;
self.connectTimeout = undefined;
//таймер, який виконує функцію при таймауту пакету
self.PDUTimeout = undefined;
self.globalTimeout = 1500; // In many use cases we will want to increase this
// In 0.3.17 this was made variable from cParam.timeout to ensure packets
// don't timeout at 1500ms if the user has specified a timeout externally.
self.rack = 0;
self.slot = 2;
self.localTSAP = null;
self.remoteTSAP = null;
self.readPacketArray = [];
self.writePacketArray = [];
self.polledReadBlockList = [];
self.instantWriteBlockList = [];
self.globalReadBlockList = [];
self.globalWriteBlockList = [];
self.masterSequenceNumber = 1;
self. = doNothing;
self.connectionParams = undefined;
self.connectionID = 'UNDEF';
self.addRemoveArray = [];
self.readPacketValid = false;
self.writeInQueue = false;
self.connectCBIssued = false;
self.dropConnectionCallback = null;
self.dropConnectionTimer = null;
self.reconnectTimer = undefined;
//таймер повторного підключення по ISO/TCP
self.rereadTimer = undefined;
}
function doNothing(arg) {
return arg;
}
// NodeS7 - A library for communication to Siemens PLCs from node.js.
// The MIT License (MIT)
// Copyright (c) 2013 Dana Moffit
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// EXTRA WARNING - This is BETA software and as such, be careful, especially when
// writing values to programmable controllers.
//
// Some actions or errors involving programmable controllers can cause injury or death,
// and YOU are indicating that you understand the risks, including the
// possibility that the wrong address will be overwritten with the wrong value,
// when using this library. Test thoroughly in a laboratory environment.