Посібник по промисловим мережам
Даний розділ описує бібліотеку 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 nodes7
Bad
, і зрештою з’єднання буде автоматично відновлено.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
та рівнем критичності debugLevel
isoConnectionState=0
) і якщо немає ніяких запитів запускається таймер на 3.5 с після чого запускається функція resetNow
isoConnectionState = 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 |
setTranslationCB
writePacketArray - масив пакетів на запис
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
, наприклад conn
initiateConnection
з передачею параметрів підключення та функцією зворотного виклику наприклад connected
var 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 :
isoclient
isoclient = net.connect(cParam);
isoConnectionState = 2
- TCP підключено, чекаємо підключення ISOconnBuf = self.connectReq.slice();
- де connectReq
- масив з байтів запиту на підключенняconnBuf
localTSAP
, remoteTSAP
write
сокету isoclient
isoConnectionState = 3
- ISO-ON-TCP connected, Wait for PDU responsenegotiatePDU
(буфер запиту переговорів по S7)isoclient.write
data=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
тоді повернути null
itemList
для кожного з яких вилкикати processS7Packet(data, itemList[itemCount], dataPointer, connectionID), який:
qualityBuffer
та valid
byteBuffer
та кількість отриманих байт в byteLength
byteLength
з урахуванням вирівнювання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.