fieldbusbook

Посібник по промисловим мережам

S7 TCP/IP

Бібліотека NodeS7

Даний розділ описує бібліотеку 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:

Використовуючи yarn:

Оптимізація

Підтримка PLC

Підтримка перетворювачів частоти

Credit to the S7 Wireshark dissector plugin for help understanding why things were not working. (http://sourceforge.net/projects/s7commwireshark/)

Заслуга плагіна 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(); }
}

API

nodes7.initiateConnection(options, callback)

Підключається до 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)

nodes7.dropConnection(callback)

Відключається від ПЛК. Це просто розриває з’єднання TCP.

Arguments

callback()

Зворотний виклик викликається після завершення закриття.

nodes7.setTranslationCB(translator)

Встановлює зворотний виклик для перекладу імені та адреси.

Це необов’язково – ви можете використовувати 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, який посилається на ім’я замість абсолютної адреси.

nodes7.addItems(items)

Додає items до внутрішнього списку опитувань читання.

Аргументи

items може бути string або масивом string . Якщо items містить значення _COMMERR, воно поверне поточний статус зв’язку.

nodes7.removeItems(items)

Видаляє items з внутрішнього списку опитувань читання.

Аргументи

items може бути string або масивом string . Якщо items не визначено, усі елементи буде видалено.

nodes7.writeItems(items, values, callback)

Записує items до ПЛК, використовуючи відповідні values, і після завершення викликає callback. Ви повинні стежити за поверненим значенням - якщо воно відмінне від нуля, запис не буде оброблено, оскільки він вже триває, і зворотний виклик не буде викликано.

Аргументи

items може бути string або масивом string. Якщо items є одним string , values має бути одним елементом. Якщо items є масивом string , values також має бути масивом значень.

callback(err)

nodes7.readAllItems(callback)

Читає внутрішній список опитувань і після завершення викликає callback.

Аргументи

callback(err, values)

Методи

Даний розділ та інші файли папки репозиторію розібрані та прокоментовані вже мною.

Властивості

Класи

S7Item

Властивості:

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

byteLength - загальний обєм в байтах

byteLengthWithFill - вирівняний обєм в байтах

readTransportCode -

writeTransportCode - повторює readTransportCode, з винятком:

byteBuffer - сирі прочитані дані

writeBuffer

qualityBuffer - буфер з заповненими значеннями якосіт для даних:

writeQualityBuffer

value - значення

writeValue

valid - прийшли валідні дані

errCode - код помилки в текстовому вигляді

part

maxPart

isOptimized

resultReference

itemReference

Методи:

clone - клонування об’єкту

badValue - повертає значення по дефолту в залежності від типу обєкту datatype

Принципи роботи

Ініціювання підключення

З точки зору користувача:

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 :

Читання

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, яка обробляє отримані вхідні дані:

Загальний код

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.