
Один из способов подружить телефон и модуль ESP8266 через интернет с помощью сервера на nodejs.
Готовое устройство в этой статье.
Иногда забываю включить камеру наблюдения уходя из дома. Решил сделать управление питанием через WiFi c помощью модуля ESP-01.
Но вот вопрос, как получить доступ к модулю WiFi через интернет не имея статик IP у модуля или же модуль за NAT.
Будучи любителем node.js я решил сделать прослойку и поместить ее на одном из моих VPS серверов. Подойдет любой хостинг с поддержкой node.js или самая дешевая vps (у меня за 100 руб. в месяц).
Я покажу простой пример с express и socket.io для web странички. В данном примере я покажу как получить температуру и с датчика DHT-11 и переключать какие то данные.
Можно пойти несколькими путями, например node.js будет просто proxy сервером между соединением по web-сокетам от браузера и tcp-сокетом от eps8266.
Я выбрал другой путь, где сервер будет выполнять часть работы, по согласованию протоколов.
Протокол общения ESP8266 и Node.js
При чтении данных с сокета нет гарантии что будет получено за один раз столько же данных сколько было отправлено. Для определения что нужно сделать определимся в каком формате будем слать данные. Нам нужно определить когда мы получим все данных нашей команды, а так же когда закончатся данные одной команды и начнутся другой.
Протокол общения может быть различным. Я обычно делаю бинарный протокол для минимизации трафика. Первый байт это число, которое говорит сколько данных в пакете. Дальше идут данные. Получив 1 байт я знаю сколько байт будет в полном пакете и жду его складывая в буфер.
Но благодаря тому, что библиотека под Arduino написана больше для передачи текста, я решил остановится на текстовом формате данных.
Пакеты разбиты переводом строки, при чем библиотека ESP8266 шлет \r\n для завершения строки, а сервером я шлю только \n.
Пакет может состоять из частей, раз дело имеем с web я остановился на амперсанде (&) в качестве разделителя.
Ключ - значение я делю двоеточием.
Пакет для запроса статуса будет такой "status\n"
ответ: "status&l:0&t:25.10&h:56.50"
Пакет включения/выключения нагрузки будет: 'load&on' и 'load&off'
Создание проекта Node.js для соединения ESP8266 и Web-сокетов
Создаем каталог, например esp
mkdir esp
cd esp
инициализируем npm, данные можете заполнить а можете оставить все поля пустыми
npm init
Устанавливаем необходимые библиотеки
npm install socket.io --save
npm install express --save
создаем файл index.js или какой вы указали при настройки проекта после команды init.
touch index.js
Вставьте в него следующий код:
let net = require('net'); let app = require('express')(); let http = require('http').createServer(app); let io = require('socket.io')(http); // фейковое хранилище всех подключенных ESP // в реальной жизни вам понадобится различать их // вы можете при подключении передавать информацию, например уникальный номер let esps = []; // буфер входящих данных let packet = ''; // создаем TCP сервер для приема соединений от наших ESP var server = net.createServer(function(sock) { console.log('ESP connected'); // кладем в наше хранилище новый коннект esps.push(sock); // подписываем на событие прихода данных по сокету sock.on('data', (data) => { // складываем в буфер // в реальной жизни нужно учитывать если платок больше 1ой // на каждую нужен отдельный буфер packet += data; // ищем символы переноса строки const idx = packet.indexOf('\r\n'); // если найдены то мы получили полный пакет if (idx != -1) { // берем из буфера часть, которая содержит 1 целый пакет // и вызываем обработчик для этого пакета handler(packet.substr(0, idx)); // удаляем обработанный пакет из буфера packet = packet.substr(idx + 2); } }) // в реальной жизни, в случае ошибки на сокете, // обрабатываем ее, например отключаем сокет и удаляем из хранилища sock.on('error', (err) => { console.log(err); }); // если сокет отключился удаляем его из нашего хранилища sock.on('end', () => { console.log('ESP disconnected'); const pos = esps.indexOf(sock); if (pos != -1) { esps.splice(pos, 1); } }); }); // указываем какой порт и ip адрес нужно начать слушать нашему TCP серверу server.listen(3030, '192.168.0.102'); // вспомогательная функция для отправки пакета function send_packet(data) { // если есть подключенные esp if (esps.length) { //перебираем их и шлем по очереди наш пакет for (const esp of esps) { try { esp.write(data + '\n'); } catch (err) { console.log(err); } } } else { console.log('Error: ESP not found'); } } // вспомогательная функция для обработки пакетов function handler(packet) { console.log('Packet:', packet); // формат пакета описан в статье // разбиваем на части const parts = packet.split('&'); // смотрим что за пакет switch (parts[0]) { case 'status': // удалим часть которая нам больше не нужна parts.splice(0, 1); // вспомогательный объект для отправки в web сокет const res = {}; // перебираем все части и складываем в один объект // мы могли формировать сразу в esp8266 готовый json // или могли бы парсить данные на клиенте for (const el of parts) { // разбиваем на ключ - значение const tmp = el.split(':'); // пишем в объект res[tmp[0]] = tmp[1]; } // и отсылаем в web сокет io.emit('status', res); break; default: console.log('Error: unknown handler') } } // эту часть я поручаю nginx // но для краткости и удобства для статьи я решил обойтись средствами node.js app.get('/', (req, res) => { // отдаем статик файл в браузер res.sendFile(__dirname + '/public/index.html'); }); // событие при подключении нового web socket io.on('connection', function(sock) { // если пришла команда status sock.on('status', () => { // просто отправляем ее на наш wifi модуль send_packet('status'); }).on('toggle', (data) => { // для команды toggle меняем название команды // и добавляем параметр в том формате в котором его ожидает наш esp-01 модуль send_packet('load&' + (data ? 'on' : 'off')); }); }); // запускаем наш web сервер http.listen(3000, () => { console.log('listening on *:3000'); });
Создадим каталог public и в нем файл index.html с кодом
<div> <div>Температура: <span id="field-t">---</span></div> <div>Влажность: <span id="field-h">---</span></div> <div>Нагрузка: <span id="field-l">---</span></div> <div> <button id="btn-toggle" disabled="disabled">Нагрузка...</button> </div> </div> <script src="/socket.io/socket.io.js"></script> <script> var sock = io(); var btnToggle = document.getElementById('btn-toggle'); var fieldT = document.getElementById('field-t'); var fieldH = document.getElementById('field-h'); var fieldL = document.getElementById('field-l'); var loadEnabled = 0; // обработчик события status sock.on('status', function (data) { if (data) { // если пришли данные температуры if (data.hasOwnProperty('t')) { // выводим ее fieldT.innerHTML = data.t; } // влажность if (data.hasOwnProperty('h')) { fieldH.innerHTML = data.h; } // статус нашего переключателя if (data.hasOwnProperty('l')) { loadEnabled = parseInt(data.l) fieldL.innerHTML = loadEnabled ? 'ВКЛ.' : 'ВЫКЛ.'; // кнопка для изменения состояния btnToggle.innerHTML = loadEnabled ? 'ВЫКЛ.' : 'ВКЛ.'; btnToggle.removeAttribute('disabled'); } } }) // при клике на кнопку btnToggle.addEventListener('click', function () { // отсылаем новое состояние sock.emit('toggle', !loadEnabled); }); // обновляем статус оп таймеру 1 раз в секунду setTimeout(function () { sock.emit('status'); }, 1000); </script>
Прошивка модуля ESP-01 для связи с Node.js по сокету.
Для опроса датчика температуры и влажности DHT-11 я выбрал библиотеку DHT-sensor-library.
Скачиваем, и распаковываем ее, переименовываем в DHT и кладем в папку скетчей libraries.
Для ее работы необходимо установить Adafruit Unified Sensor Library через менеджер библиотек IDE Arduino.
Полный код прошивки wifi модуля на базе ESP8266:
#include#include "DHT.h" // тип датчика #define DHTTYPE DHT11 const char* ssid = "Имя WiFi сети"; const char* password = "пароль wifi сети"; // вспомогательная переменная для вывода debug информации в консоль int wifiConnecting = false; // позиция следующег бита в буфере для записи при получении из сокета byte pos = 0; // сам буфер char packet[255]; // переменная для эмуляции переключения управления нагрузкой bool loadEnabled = false; // объявлеем клиент для подключения к node.js WiFiClient client; // указываем IP нашего nodejs сервера IPAddress server(192, 168, 0, 102); // номер ножки на которой висит наш датчик DHT11 const int DHTPin = 2; DHT dht(DHTPin, DHTTYPE); // переменные для хранения температуры и влажности float h = 0, t = 0; void setup() { // настройки порта для вывода отладочной информации Serial.begin(115200); delay(10); // инициализация dht11 dht.begin(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); // подключение по wifi для выхода в интернет WiFi.begin(ssid, password); } // функция для чтения данных с датчика DHT-11 void DHTRead() { h = dht.readHumidity(); t = dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println("Failed to read from DHT sensor!"); } else { float hic = dht.computeHeatIndex(t, h, false); Serial.print("Humidity: "); Serial.println(h); Serial.print(" %\t Temperature: "); Serial.print(t); Serial.println(" *C "); } } // функция обработчик пакетов void handler() { // получаем первую часть пакета char* part = strtok(packet, "&"); Serial.print("Readed: "); Serial.println(part); // если это пакета статуса if (strcmp(part, "status") == 0) { Serial.println("Command read"); читаем данные с датчика DHTRead(); // шлет информацию в сокет // l - состояние переключателя client.print("status&l:"); client.print(loadEnabled); // темература client.print("&t:"); client.print(t); // и влажность client.print("&h:"); client.println(h); } else if (strcmp(part, "load") == 0) { // если управление нашим перключателем Serial.println("Command load"); // получаем вторую часть пакета part = strtok(0, "&"); // если пришло on - значит включен, иначе выключен loadEnabled = strcmp(part, "on") == 0; // обновляем наш статус выключателя на клиенте client.print("status&l:"); client.println(loadEnabled); } else { Serial.println("Invalid command"); } } // чтение данных из сокета void ClientRead() { // есть доступные данные? if (client.available()) { Serial.print("Read: "); // читаем байт byte c = client.read(); Serial.println(c); // если это перевод строки if (c == '\n') { // закроем наш пакет символом нуля // иначе могут мешать данные от прошлого пакета packet[pos++] = 0; // обработаем пакет handler(); // сбросим счетчик pos = 0; } else { // если это не конец строки // поместим символ в буфер и сдвинем позицию на 1 packet[pos++] = c; } } } // основной цикл void loop() { // если вайфай не подключен if (WiFi.status() != WL_CONNECTED) { if (!wifiConnecting) { wifiConnecting = true; Serial.print("WiFi connecting"); } Serial.print("."); // ждем 1 сек и перепроверяем статус delay(1000); } else { // вайфай подключен if (wifiConnecting) { wifiConnecting = false; Serial.println(); Serial.println('WiFi connected'); } // если нет подключения к nidejs серверу if (!client.connected()) { Serial.println("Connecting to server"); client.stop(); // переподключаемся client.connect(server, 3030); } else { // читаем данные из сокета ClientRead(); } delay(1); } }
Получился вот такой веб интерфейс:
Это демо код, в реальной жизни возможно вам понадобится добавить авторизацию как клиента так и модулей при подключении к серверу.
Для клиентской части я буду использовать angular.js или react. Можно так сделать мобильное приложение, например на react native, ionic или flutter.
Комментарии к статье: Связь с ESP8266 через Node.js
а вы не написали что должно лежать в этом файле ?
код не работает подскажите плиз какие порты надо юзать 3000 или 3030 ? а так же ругается на /socket.io/socket.io
Порт 3000 указан для web сервера. Это связь web странички и nodejs сервера. А порт 3030 это просто TCP сокет, по нему подключается. ESP к серверу.
Оба порта используются.
В каком файле? В /socket.io/socket.io.js?
Это файл из библиотеки socket.io. Его сервер автоматически отдаст из модулей.