atpv

Навчальні матеріали з автоматизації технологічних процесів та виробництв, розроблені спільнотою

<- До підрозділу

Практичне заняття. Програмування IoT шлюзів з використанням Node.js та Modbus TCP/IP

Увага! Усі наведені в лабораторній роботі приклади передбачають, що здобувач буде розбиратися з їх функціонуванням.

Частина 1. Підготовка IoT Gateway

1. Створення віртуальної машини

Увага, передбачається що в мережі є DHCP-сервер, який автоматично видає IP адреси!

image-20231229134659698

hostname -I
sudo apt install ufw

2. Встановлення необхідних утиліт для роботи з віддаленою машиною

image-20231229153853736

image-20231229153353676

image-20231229154416141

Надалі усі дії з віртуальною машиною можна робити як через вікно машини так і через термінал Putty. Термінал працює з використанням захищеного протоколу ssh. При підключенні до реального пристрою з Linux, Putty дає можливість працювати в командному режимі.

image-20231229165738585

image-20231229165954453

3. Встановлення Node.js на віртуальну машину

sudo apt update

повторно введіть пароль адмінітсратора, для продовження операції. Ця дія приведе до оновлення інформації з репозиторіїв пакетів.

sudo apt install nodejs

натисніть y на пропозицію встановлення

node -v
sudo apt install npm
npm -v

Частина 2. Створення проекту Node.js

1. Створення папки проекту та ініціалізація

mkdir nodejsprj
cd nodejsprj
npm init -y

Після цього з’явиться повідомлення в яке виводиться зміст файлу package.json з налаштуванням проекту.

image-20231230130216669

2. Створення та перевірка простої програми

image-20231230141530121

image-20231230141152262

Відкриється редактор Notepad++ .

console.log ('Hello world!')

3. Встановлення пакунку nodemon

Пакунок nodemon дає можливість змінювати програму в середовищі виконання без зупинки та повторного запуску. Цей пакунок відноситься до середовищ розроблення.

Команда install встановлює потрібний пакунок, а опція -D вказує на те, що цей пакунок потрібен тільки в середовищі розроблення для налагодження застосунку.

У результаті виконання команди менеджер npm завантажує необхідний пакунок з репозиторію, розпаковує його у вашу папку і встановлює залежності в:

4. Використання nodemon

Для внесення змінних у виконавчу програму приходиться її зупиняти і запускати заново. Пакунок nodemon , який був встановлений дає можливість вносити зміни без перезапуску. Цей пакунок запускає застосунок та слідкує за зміною JS файлів. Як тільки файл змінюється - nodemon автоматично перезапускає проект.

Для можливості запуску в режимі налагодження (з nodemon) та виконання, можна використати функціональність скриптів пакунку npm. Для цього у файлі package.json можна записати скрипти, які будуть запускатися при виклику команди npm run <назва скрипта>. Також скрипти використовуються при розгортанні застосунку на кінцевому місці розташування. Так, після розгортання застосунку може використовуватися скрипт start, а при запуску на комп’ютері розробника - dev.

  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  }

Перший скрипт викликає звичайну команду запуску файлу js середовищем node. Другий скрипт запускає модуль nodemon, який у свою чергу запускає index.js в особливому режимі. Тепер для запуску необхідного скрипта треба викликати команду npm run <назва скрипта>.

npm run dev

Частина 3. Взаємодія через COM-порт

1. Встановлення com0com

image-20231227141224609

image-20231227145728438

2. Встановлення та налаштування утиліти роботи з COM-портом та TCP/UDP

image-20231227142458190

image-20231227151224019

3. Налаштування віртуальної машини на роботу з послідовним портом

image-20231227195419816

Для Debian 1-й COM-порт має назву пристрою /dev/ttyS0.

sudo chmod o+rw /dev/ttyS0
ls -l /dev/ttyS0

Якщо права надалися, Ви отримаєте відповідь -crw-rw-rw-

image-20231227201108097

echo '123' > /dev/ttyS0

image-20231227201917994

cat -v < /dev/ttyS0
456$0d$0a

Останні символи відправляються в 16-ковому форматі для сигналізування завершення передачі.

image-20231227204716603

Зміни прав доступу (chmod) не зберігаються після перезавантаження системи. Щоб зробити ці зміни на постійній основі, можна використовувати udev - правила для присвоєння дозволів пристрою (ttyS0 у нашому випадку) при завантаженні системи. Для збереження змін налаштувань доступу до порту, необхідно зробити наступні команди.

sudo nano /etc/udev/rules.d/ttyS0.rules
KERNEL=="ttyS0", MODE="0666"

Це правило надасть повний доступ (rw-rw-rw- або 0666) до пристрою ttyS0 всім користувачам при кожному завантаженні системи.

sudo reboot
ls -l /dev/ttyS0

4. Створення проекту node.js для прослуховування COM-порта

npm install serialport@10.5.0
const {SerialPort, ReadlineParser } = require('serialport');
const port = new SerialPort({
	path: '/dev/ttyS0',
    baudRate: 9600
});
// створення парсера, що орієнтується на розмежувач \n (0A) 
const parser = new ReadlineParser();
// парсер ставить в поток після отримання даних портом
port.pipe(parser);
// коли отримали рядок запускається функція зворотного виклику
parser.on('data', (data)=> {
	console.log(data);
	port.write ('I recive ' + data + ' ');
});
// відправка даних на порт напочатку
port.write(' Hello! \n');
Hello gateway $0A

Ви повинні отримати в консолі Debian відповідний текст, натомість в Hercules має прийти відповідь.

Частина 4. Робота з Modbus RTU та TCP/IP

1. Встановлення та налаштування Modrsism2

image-20240121120847517

image-20240121121944744

image-20240121122123745

image-20240121122340170

image-20240121122702922

2. Перевірка роботи імітаційної установки

У цій лабораторній роботі у якості імітаційної установки використовується той самий об’єкт що і попередінй лабораторній роботі. Передбачається, що з установкою зв’язаний пристрій вводу/виводу, що має інтерфейси Modbus RTU та Modbus TCP/IP. Нижче наведений перелік об’єктів Modbus для даної установки

Позначення параметру Діапазон зміни Вхід/вихід Modbus Область, зміщення Примітка
Кнопка SB1 “ПУСК” вкл/відкл 100001 di, 0 вмикається людиною, без фіксації
Кнопка SB2 “СТОП” вкл/відкл 100002 di, 1 вмикається людиною, без фіксації
Сигналізатор LS1 вкл/відкл 100003 di, 2 “ВКЛ” коли присутня рідина на даному рівні, коли ємність порожня буде в стані “ВІДКЛ”
Сигналізатор LS2 вкл/відкл 100004 di, 3 “ВКЛ” коли присутня рідина на даному рівні, буде “ВКЛ” коли в ємності рідини більше за 50%
Сигналізатор LS3 вкл/відкл 100005 di, 4 “ВКЛ” коли присутня рідина на даному рівні, буде “ВКЛ” коли в ємності рідини більше за 95%
Температура TE1 0 - 10000 300001 ai, 0 лінійна шкала в 0.01 °С
Клапан LVS1 Відкритий/закритий 000001 coils, 0  
Клапан LVS2 Відкритий/закритий 000002 coils, 1  
Клапан LVS3 Відкритий/закритий 000003 coils, 2  
Клапан TV1 0 - 10000 400001 a0, 0 лінійна шкала в 0.01 %, 0 - повністю закритий, лінійна шкала

image-20240122082535847

3. Перевірка коду для зчитування регістрів по Modbus RTU

https://www.npmjs.com/package/modbus-serial/v/7.8.1

image-20240122210741114

image-20240122210900059

image-20240122211109973

npm i modbus-serial@7.8.1
// створення modbus client
let ModbusRTU = require("modbus-serial");
var client = new ModbusRTU();

// відкриття підключення до serial port, 
// після підключення запустити task
client.connectRTUBuffered("/dev/ttyS0", { baudRate: 9600 }, task);
function task() {
    //slave 1
	client.setID(1);
	// інтервал 1000 мс
	setInterval(function() {
		// читати Holding Registers з 11 1 шт
		client.readHoldingRegisters(11, 1, 
			function(err, data) {
				console.log(data.data);
			});
	}, 1000)
}

image-20240122212409273

4. Перевірка коду для зчитування регістрів по Modbus TCP/IP

ping 192.168.2.103

Політики брандмауерів хостової ОС можуть блокувати icmp ехо-запити, тому якщо ping не проходить, це не значить що з’єднання немає.

// створення modbus client
let ModbusRTU = require("modbus-serial");
var client = new ModbusRTU();

// відкриття підключення до tcp, 
// після підключення запустити task, вкажіть нижче адресу хостової машини
client.connectTCP("192.168.2.103", { port: 502 }, task);

function task() {
    //slave 1
	client.setID(1);
	// інтервал 1000 мс
	setInterval(function() {
		// читати Holding Registers з 11 1 шт
		client.readHoldingRegisters(11, 1, 
			function(err, data) {
				console.log(data.data);
			});
	}, 1000)
}

image-20240219134654890

5. Розробка програми користувача

let states = {init:0, idle:1, load1:2, load2:3, hea1:4, hea2:5, dwnld:6};
let state = states.idle;
let io = {msg: '', tstep: 0,
	sb1Strt:false, sb2Stop:false, ls1Lo:false, ls2Mdl:false, ls3Hi:false,
	lvs1:false, lvs2:false, lvs3:false, 
	te1:0.0, tv1:0.0}
let ModbusRTU = require("modbus-serial");
let client = new ModbusRTU();
client.connectTCP("192.168.2.103", { port: 502 }, task);
function task() {
	client.setID(1);
	setInterval(function() {
		client.readInputRegisters(0, 1, 
			function(err, data) {
				io.te1 = data.data[0] * 0.01;
				logic ();
			});
	}, 500);
	setInterval(function() {
		client.readDiscreteInputs(0, 5, 
			function(err, data) {
				let bools = data.data
				io.sb1Strt = bools[0]; io.sb2Stop = bools[1]; 
				io.ls1Lo = bools[2]; io.ls2Mdl = bools[3]; io.ls3Hi = bools[4]; 
			});
	}, 500);
	setInterval(function() {io.tstep ++; console.log(io)}, 1000)
}

function logic () {
    switch (state) {
        case states.idle:
            io.lvs1 = false; io.lvs2 = false; io.lvs3 = false;
            io.tv1 = 0.0; io.msg = 'idle';            
            if (io.sb1Strt === true) {
                if (io.ls1Lo === true) {
                    state = states.dwnld;
                    io.msg = 'Downloading';io.tstep = 0;
                } else {
                    state = states.load1;
                    io.msg = 'Loading 1';io.tstep = 0;
                }
            }
            break
        case states.load1:
            io.lvs3 = false; io.lvs1 = true;
            if (io.ls2Mdl === true) {
                state = states.load2;
                io.msg = 'Loading 2';io.tstep = 0;
            }
            break
        case states.load2:
            io.lvs1 = false; io.lvs2 = true;
            if (io.ls3Hi === true) {
                state = states.hea1;
                io.msg = 'Heating1';io.tstep = 0;
            }
            break
        case states.hea1:
            io.lvs2 = false; io.tv1 = 100.0;
            if (io.te1>50.0) {
                state = states.hea2;
                io.msg = 'Heating2';io.tstep = 0;
            }
            break
        case states.hea2:
            io.tv1 = 50.0;
            if (io.te1 > 55.0) {
                state = states.dwnld;
                io.msg = 'Downloading';io.tstep = 0;
            }
            break
        case states.dwnld:
            io.tv1 = 0.0; io.lvs3 = true;
            if (io.ls1Lo === false) {
                if (io.sb2Stop) {
                    state = states.idle;
                    io.msg = 'Program stopped';io.tstep = 0;
                } else {
                    state = states.load1;
                    io.msg = 'Next cycle: Loading1';io.tstep = 0;
                }
            }
            break
        default :
            state = states.idle;
    }
    client.writeCoils(0, [io.lvs1,io.lvs2,io.lvs3]);
    client.writeRegister(0, Math.round(io.tv1*100.0)) 	
}	

Цей код реалізує логіку керування установкою через Modbus TCP/IP.

Частина 5. Індивідуальне завдання

Лабораторне заняття розробив Олександр Пупена.