Глава 3. Практическая часть
Подключаемые устройства можно разделить на следующие категории:
-
электронные модули ввода данных, производства ООО «Прикладная робототехника ПРО» (кнопки, потенциометры, концевой выключатель)
-
электронные сенсорные модули, производства ООО «Прикладная робототехника ПРО» (датчики линии, инерциальный датчик и магнетометр, датчик шума, датчик атмосферного давления, датчик освещенности, датчик температуры и влажности)
-
электронные индикационные модули, производства ООО «Прикладная робототехника ПРО» (светодиоды, трехцветные светодиоды, звуковой излучатель)
-
электронные управляющие модули, производства ООО «Прикладная робототехника ПРО» (силовой ключ)
Подробное описание данных модулей приведено в соответствующем учебном пособии «Периферийные функциональные модули».
Помимо вышеперечисленных модулей, можно использовать Arduino
-совместимые комплектующие:
-
двигатели постоянного тока;
-
сервоприводы;
-
набор светодиодов;
-
набор резисторов;
-
LCD-дисплей;
-
УЗ-дальномер;
-
эластичная клавиатура.
Подробнее про данные работу с данными компонентами можно ознакомиться в учебном пособии «Программирование моделей инженерных систем».
Работа с модулями ввода данных
Рассмотрим процесс опроса модулей тактовой кнопки, потенциометра и концевого выключателя одновременно. Поскольку каждый тип модуля имеет свой индивидуальный идентификационный номер, то можно без боязни включать в цепь устройств модули различного типа. Например, как в данном случае – в одной цепи находятся модуль "Тактовая кнопка" (ID 3), модуль "Потенциометр" ( ID 19) и модуль "Концевой выключатель" (ID 23) (Рисунок 3.1). Для того чтобы подключить в одну цепь несколько одинаковых модулей (например, несколько кнопок) необходимо выполнить изменение их ID таким образом, чтобы у всех модулей, включенных в цепь, были разные ID. Такой пример будет рассмотрен далее.

Рисунок 3.1. Последовательное подключение модулей ввода данных к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Код для опроса модулей будет выглядеть следующим образом:
#include "DxlMaster.h"
#include "JsAr.h"
// указываем ID модулей в цепи
const uint8_t id_but = 3;
const uint8_t id_pot = 19;
const uint8_t id_usw = 23;
// указываем скорость обмена данными
const long unsigned int baudrate = 57600;
// создаем экземпляры класса для каждого модуля
DynamixelDevice device_but(id_but);
DynamixelDevice device_pot(id_pot);
DynamixelDevice device_usw(id_usw);
// объявляем переменные для работы
uint8_t data_but = 0;
uint8_t data_pot_L = 0;
uint8_t data_pot_H = 0;
uint8_t data_usw = 0;
void setup()
{
JsAr.begin();
DxlMaster.begin(baudrate);
delay(100);
// инициализируем модули
device_but.init();
delay(50);
device_pot.init();
delay(50);
device_usw.init();
// открываем соединение с ПК
// для отображения показаний
Serial.begin(115200);
}
void loop() {
// опрашиваем необходимые регистры модулей device_but.read(27, data_but);
device_pot.read(26, data_pot_L); // малый регистр от 0 до 255
device_pot.read(27, data_pot_H); // большой регистр от 1*256 до 3*256
device_usw.read(27, data_usw);
// выводим показания в терминал
if(data_but)
Serial.println("But is pressed");
Serial.print("Pot position: ");
Serial.print((data_pot_L + 256*data_pot_H)/10.23);
Serial.print("%\n");
if(data_usw) Serial.println("USW is pressed");
delay(100);
}
В результате, в окне терминала можно будет увидеть подобную картину (Рисунок 3.2):

Рисунок 3.2. Результат опроса подключенных модулей ввода данных в среде Arduino IDE
Таким образом, можно использовать модули для ввода данных и для реализации каких-либо событий по триггеру.
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load('api_config.js'); // Загрузка конфигурации платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймером
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с Dynamixel
print('Start js app');
// ID устройств Dynamixel
let id_but = 3; // Кнопка
let id_pot = 19; // Потенциометр
let id_usw = 23; // Концевой выключатель
// Скорость передачи для шины Dynamixel
let baudrate = 57600;
// Переменные для хранения данных с устройств
let data_but = 0;
let data_pot = 0;
let data_usw = 0;
// Создание объектов устройств
let but = DynamixelDevice.create(id_but);
let pot = DynamixelDevice.create(id_pot);
let usw = DynamixelDevice.create(id_usw);
// Инициализация шины и устройств
DxlMaster.begin(baudrate);
but.init();
pot.init();
usw.init();
// Таймер для считывания данных каждые 0.5 секунды
Timer.set(500, Timer.REPEAT, function() {
data_but = but.read(27); // Чтение состояния кнопки
data_pot = pot.read16(26); // Чтение значения потенциометра (16- бит)
data_usw = usw.read(27); // Чтение состояния концевого выключателя
// Вывод значений в консоль
print('But:', data_but);
print('POT:', data_pot);
print('USW:', data_usw);
},null);

Рисунок 3.3. Результат опроса подключенных модулей ввода данных в среде Mongoose OS
Работа с сенсорными модулями
Рассмотрим аналогичную предыдущей конфигурацию модулей – в одну цепь подключим модуль "Датчик атмосферного давления" (ID 20), модуль "Датчик температуры и влажности воздуха" (ID 22) и модуль "Датчик освещённости" (ID 25) (Рисунок 3.4):

Рисунок 3.4. Последовательное подключение сенсорных модулей к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Программная реализация получения данных с модуля будет аналогична опросу модулей ввода данных – так же читаем необходимые регистры и выводим результат в терминал:
#include "DxlMaster.h"
#include "JsAr.h"
// указываем ID модулей в цепи
const uint8_t id_bmp = 20;
const uint8_t id_temp = 22;
const uint8_t id_light = 25;
// указываем скорость обмена данными
const long unsigned int baudrate = 57600;
// создаем экземпляры класса для каждого модуля
DynamixelDevice device_bmp(id_bmp);
DynamixelDevice device_temp(id_temp);
DynamixelDevice device_light(id_light);
// объявляем переменные для работы
uint8_t data_light_L = 0;
uint8_t data_light_H = 0;
uint32_t data_bmp_press_32 = 0;
uint16_t data_bmp_press_16 = 0;
uint32_t data_bmp_alt_32 = 0;
uint16_t data_bmp_alt_16 = 0;
uint16_t data_temp_RH_int = 0;
uint8_t data_temp_RH_dec = 0;
uint16_t data_temp_temp_int = 0;
uint8_t data_temp_temp_dec = 0;
void setup()
{
JsAr.begin();
DxlMaster.begin(baudrate);
delay(100);
// инициализируем модули
device_bmp.init();
delay(50);
device_temp.init();
delay(50);
device_light.init();
// открываем соединение с ПК
// для отображения показаний
Serial.begin(115200);
}
void loop() {
// опрашиваем необходимые регистры модулей
device_light.read(26, data_light_L); // малый регистр от 0 до 255
device_light.read(27, data_light_H); // большой регистр от 1*256 до 3*256
// считываем значение давления
device_bmp.read(24, data_bmp_press_32);
device_bmp.read(32, data_bmp_press_16);
// считываем значение альтитуды
device_bmp.read(28, data_bmp_alt_32);
device_bmp.read(34, data_bmp_alt_16);
// считываем значение влажности
device_temp.read(24, data_temp_RH_int);
device_temp.read(26, data_temp_RH_dec);
// считываем значение температуры
device_temp.read(28, data_temp_temp_int);
device_temp.read(30, data_temp_temp_dec);
// выводим показания в терминал
Serial.print("Light: ");
Serial.print((data_light_L + 256*data_light_H)/10.23);
Serial.print("%\n");
Serial.print("Pres_32: ");
Serial.print(data_bmp_press_32);
Serial.print("\n");
Serial.print("Pres_16: ");
Serial.print(data_bmp_press_16);
Serial.print("\n");
Serial.print("Alt_32: ");
Serial.print(data_bmp_alt_32);
Serial.print("\n");
Serial.print("Alt_16: ");
Serial.print(data_bmp_alt_16);
Serial.print("\n");
Serial.print("RH_int: ");
Serial.print(data_temp_RH_int);
Serial.print("\n");
Serial.print("RH_dec: ");
Serial.print(data_temp_RH_dec);
Serial.print("\n");
Serial.print("Temp_int: ");
Serial.print(data_temp_temp_int);
Serial.print("\n");
Serial.print("Temp_dec: ");
Serial.print(data_temp_temp_dec);
Serial.print("\n");
delay(500);
}
В результате, вывод в терминал выглядит следующим образом (Рисунок 3.5):

Рисунок 3.5. Результат опроса подключенных сенсорных модулей в среде Arduino IDE
Таким образом, можно получать данные с сенсорных модулей. В рассматриваемом случае получаемые данные следующие:
data_bmp_press_32 – давление в Па. Т.е. 1000 = 1 кПа.
data_bmp_press_16 - давление в даПа (в декапаскалях), т.е. 100 = 1 кПа.
data_bmp_alt_32 и data_bmp_alt_16 - высота в дм. Например, 567 = 56.7 м над уровнем моря. В data_bmp_alt_16 не помещается большая высота – предел - 6553.5 м.
data_temp_RH_int - целая часть влажности (относительная) в %, при этом data_temp_RH_dec – десятичная. Аналогично и для температуры: data_temp_temp_int - целая часть, т.е. 25.5С => 25, а data_temp_temp_ dec - десятичная часть, т.е. 25.5С => 5.
Такая организация вывода данных может сильно упростить работу с модулями, выдавая только ту информацию, которая требуется пользователю, без лишнего мусора.
Важно! В случае отсутствия получаемых данных после загрузки управляющей программы, иногда требуется нажать на кнопку RST контроллера ESP-JS-AR для реинициализации подключенных модулей.
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load('api_config.js'); // Загрузка конфигурации платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймером
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с Dynamixel
print('Start js app');
// ID устройств Dynamixel
let id_bmp = 20; // Барометр BMP
let id_temp = 22; // Датчик температуры и влажности
let id_light = 25; // Датчик освещенности
// Скорость передачи для шины Dynamixel
let baudrate = 57600;
// Переменные для хранения данных с датчиков
let data_light = 0;
let data_bmp_press_16 = 0;
let data_bmp_alt_16 = 0;
let data_temp_RH_int = 0;
let data_temp_RH_dec = 0;
let data_temp_temp_int = 0;
let data_temp_temp_dec = 0;
// Создание объектов устройств
let bmp = DynamixelDevice.create(id_bmp);
let temp = DynamixelDevice.create(id_temp);
let light = DynamixelDevice.create(id_light);
// Инициализация шины и устройств
DxlMaster.begin(baudrate);
bmp.init();
temp.init();
light.init();
// Таймер для чтения данных каждые 1.5 секунды
Timer.set(1500, Timer.REPEAT, function() {
data_light = light.read16(26); // Считываем значение освещенности
data_bmp_press_16 = bmp.read16(32); // Считываем давление (целое)
data_bmp_alt_16 = bmp.read16(34); // Считываем высоту (целое)
data_temp_RH_int = temp.read16(24); // Считываем целую часть влажности
data_temp_RH_dec = temp.read(26); // Считываем дробную часть влажности туры
data_temp_temp_int = temp.read16(28); // Считываем целую часть темпераратуры
data_temp_temp_dec = temp.read(30); // Считываем дробную часть температуры
// Вывод данных в консоль
print('Light: ', data_light);
print('Pres16: ', data_bmp_press_16);
print('ALT16: ', data_bmp_alt_16);
print('RH: ', data_temp_RH_int, ',', data_temp_RH_dec);
print('Temp: ', data_temp_temp_int, ',', data_temp_temp_dec);
}, null);
По сравнению с примером для Arduino IDE, в данном примере отсутствует вывод 32-х битных значений параметров. Результатом работы кода станет следующий вывод в окно терминала (Рисунок 3.6):

Рисунок 3.6. Результат опроса подключенных сенсорных модулей в среде Mongoose OS
Работа с индикационными модулями
Единственной отличительной особенностью работы с индикационными модулями, производства ООО "Прикладная робототехника ПРО", является то, что в процессе работы происходит не чтение регистров, а изменение их состояния. Рассмотрим этот механизм на примере цепи, в которую включены модуль "Светодиод" (ID 9), модуль "Трехцветный светодиод" (ID 21) и модуль "Звуковой пьезоизлучатель 4 кГц" (ID 24) (Рисунок 3.7):
Рисунок 3.7. Последовательное подключение индикационных модулей к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Код для управления данными модулями будет выглядеть следующим образом:
#include "DxlMaster.h"
#include "JsAr.h"
// указываем ID модулей в цепи
const uint8_t id_rgb = 21;
const uint8_t id_led = 9;
const uint8_t id_zum = 24;
// указываем скорость обмена данными
const long unsigned int baudrate = 57600;
// создаем экземпляры класса для каждого модуля
DynamixelDevice device_rgb(id_rgb);
DynamixelDevice device_led(id_led);
DynamixelDevice device_zum(id_zum);
// объявляем переменные для работы
uint8_t data_but = 0;
uint8_t data_pot_L = 0;
uint8_t data_pot_H = 0;
uint8_t data_usw = 0;
void setup()
{
JsAr.begin();
DxlMaster.begin(baudrate);
delay(100);
// инициализируем модули
device_rgb.init();
delay(50);
device_led.init();
delay(50);
device_zum.init();
}
void loop() {
// записываем значения в требуемые регистры
// задаем скважность сигнала
device_zum.write(28, 127);
// задаем частоту сигнала
device_zum.write(26, (uint16_t) 512);
// включаем светодиод
device_led.write(26, 1); delay(1000);
// выключаем светодиод
device_led.write(26, 0);
// далее включаем каждую составляющую
// на RGB светодиоде через 1 секунду
device_rgb.write(26, 1);
delay(1000);
device_rgb.write(26, 0);
device_rgb.write(27, 1);
delay(1000);
device_rgb.write(27, 0);
device_rgb.write(28, 1);
delay(1000);
device_rgb.write(28, 0);
delay(1000);
}
Таким образом, после загрузки данного кода в контроллер пьезоизлучатель будет издавать звук, светодиод включаться и выключаться, а трехцветный светодиод по очереди включать RGB-составляющие.
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript , код, реализующий аналогичный пример, будет выглядеть следующим образом:
load('api_config.js'); // Загрузка конфигурации платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймером
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с Dynamixel
print('Start js app');
// ID устройств Dynamixel
let id_rgb = 21; // RGB светодиод
let id_led = 9; // Индикаторный светодиод
let id_zum = 24; // Зуммер
// Скорость передачи для шины Dynamixel
let baudrate = 57600;
// Создание объектов устройств
let rgb = DynamixelDevice.create(id_rgb);
let led = DynamixelDevice.create(id_led);
let zum = DynamixelDevice.create(id_zum);
// Инициализация шины Dynamixel
DxlMaster.begin(baudrate);
// Инициализация устройств
rgb.init();
led.init();
zum.init();
// Таймер, который каждые 1.5 секунды управляет устройствами
Timer.set(1500, Timer.REPEAT, function() {
zum.write(28, 127); // Управление зуммером (мощность/скорость)
zum.write(26, 512); // Дополнительная настройка зуммера
led.write(26, 1); // Включение светодиода
Sys.usleep(2000000); // Задержка 2 секунды
led.write(26, 0); // Выключение светодиода
rgb.write(26, 1); // Включение RGB (канал 1)
Sys.usleep(2000000);
rgb.write(26, 0); // Выключение RGB (канал 1)
Sys.usleep(2000000);
rgb.write(27, 1); // Включение RGB (канал 2)
Sys.usleep(2000000);
rgb.write(27, 0); // Выключение RGB (канал 2)
Sys.usleep(2000000);
rgb.write(28, 1); // Включение RGB (канал 3)
Sys.usleep(2000000);
rgb.write(28, 0); // Выключение RGB (канал 3)
Sys.usleep(2000000);
}, null);
Работа с управляющими модулями
В качестве управляющего модуля в набор входит модуль "Силовой ключ" (ID 17), с помощью которого можно коммутировать силовую нагрузку – например, управлять вентилятором, насосом, электромагнитным ключом и т.п. В качестве демонстрации рассмотрим пример управления двигателем постоянного тока (Рисунок 3.8):

Рисунок 3.8. Подключение управляющего модуля к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Код управления током, приходящим на нагрузку (управление осуществляется по принципу ШИМ), выглядит следующим образом:
#include "DxlMaster.h"
#include "JsAr.h"
// указываем ID модулей в цепи
const uint8_t id_mos = 17;
// указываем скорость обмена данными
const long unsigned int baudrate = 57600;
// создаем экземпляры класса для каждого модуля
DynamixelDevice device_mos(id_mos);
void setup()
{
JsAr.begin();
DxlMaster.begin(baudrate);
delay(100);
// инициализируем модули
device_mos.init();
delay(50);
}
void loop() {
// записываем значения в требуемые регистры
// регулируем скважность сигнала
device_mos.write(28, 0);
delay(2000);
device_mos.write(28, 127);
delay(2000);
device_mos.write(28, 255);
delay(2000);
}
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load('api_config.js'); // Загрузка конфигурации платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймером
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с Dynamixel
print('Start js app');
// ID устройства MOSFET (Dynamixel)
let id_mos = 17;
// Скорость передачи для шины Dynamixel
let baudrate = 57600;
// Создаём объект устройства
let mos = DynamixelDevice.create(id_mos);
// Инициализация шины Dynamixel
DxlMaster.begin(baudrate);
// Инициализация устройства
mos.init();
// Таймер, который каждые 1.5 секунды переключает MOSFET
Timer.set(1500, Timer.REPEAT, function() {
mos.write(28, 0); // Устанавливаем минимальное значение
Sys.usleep(2000000); // Пауза 2 секунды
mos.write(28, 127); // Устанавливаем среднее значение
Sys.usleep(2000000); // Пауза 2 секунды
mos.write(28, 255); // Устанавливаем максимальное значение
Sys.usleep(2000000); // Пауза 2 секунды
}, null);
Работа с одинаковыми модулями
Рассмотрим ситуацию, когда в цепь модулей необходимо включить одинаковые модули. В качестве примера, пусть это будут два модуля "Тактовая кнопка" и два модуля "Светодиод". По умолчанию, у всех тактовых кнопок ID=3, а у всех светодиодов ID=9. Таким образом, необходимо у одного модуля "Тактовая кнопка" и у одного модуля "Светодиод" изменить их ID на другие. Это можно сделать следующим образом:
Подключаем к программируемому контроллеру модуль, у которого необходимо изменить ID. Внимание! Модуль должен быть подключен один.
Из примеров библиотеки "JsAr" – "Dynamixel" необходимо загрузить в контроллер пример "DxlConsole", при этом, не забыть изменить скорость обмена данными между модулем и контроллером. По умолчанию, это значение равно 57600, а в скетче скорость по умолчанию указана 1000000. В результате, необходимо внести изменения в значение параметра "dynamixel_baudrate":
const unsigned long dynamixel_baudrate = 57600;
После загрузки скетча с измененным значением скорости в контроллер, требуется открыть монитор порта. Далее рассмотрим на примере модуля "Светодиод":
отправляем команду:
ping 9
Получаем статус "ОК" – это значит, что модуль "Светодиод" корректно идентифицировался.
затем отправляем команду на смену ID. Для примера изменим ID модуля с 9 на 50:
write 9 3 50
Получаем сообщение об ошибке – это нормально. Перезагружаем модуль и контроллер по питанию. После перезагрузки можно убедиться в изменении ID модуля, путем отправки команды
ping 50.
Процесс смены ID показан на рисунке 3.9:

Рисунок 3.9. Процесс смены ID периферийного модуля
Аналогичным образом изменим ID модуля "Тактовая кнопка" c 3 на 60 (Рисунок 3.10):

Рисунок 3.10. Процесс смены ID тактовой кнопки
Соберем цепь из четырех модулей и подключим ее к программируемому контроллеру ESP-JS-AR. Примерный внешний вид собранной цепи представлен на следующем рисунке (Рисунок 3.11):

Рисунок 3.11. Последовательного подключение нескольких одинаковых модулей к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Реализуем управляющую программу, в которой при нажатии на модуль "Тактовая кнопка" с ID 60 будет включаться светодиод с ID 50. И аналогично, при нажатии на модуль "Тактовая кнопка" с ID 3 будет включаться светодиод с ID 9:
#include "DxlMaster.h"
#include "JsAr.h"
// указываем ID модулей в цепи
const uint8_t id_but_1 = 3;
const uint8_t id_but_2 = 60;
const uint8_t id_led_1 = 9;
const uint8_t id_led_2 = 50;
// указываем скорость обмена данными
const long unsigned int baudrate = 57600;
// создаем экземпляры класса для каждого модуля
DynamixelDevice device_but_1(id_but_1);
DynamixelDevice device_but_2(id_but_2);
DynamixelDevice device_led_1(id_led_1);
DynamixelDevice device_led_2(id_led_2);
// объявляем переменные для работы
uint8_t data_but_1 = 0;
uint8_t data_but_2 = 0;
void setup()
{
JsAr.begin();
DxlMaster.begin(baudrate);
delay(100);
// инициализируем модули
device_but_1.init();
delay(50);
device_but_2.init();
delay(50);
device_led_1.init();
delay(50);
device_led_2.init();
}
void loop() {
// опрашиваем необходимые регистры модулей
device_but_1.read(27, data_but_1);
device_but_2.read(27, data_but_2);
// управляем светодиодами
if(data_but_1)
device_led_1.write(26, 1);
else
device_led_1.write(26, 0);
if(data_but_2)
device_led_2.write(26, 1);
else
device_led_2.write(26, 0);
delay(10);
}
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load("api_config.js");
load('api_config.js'); // Загрузка конфигурации платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймером
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с Dynamixel
print('Start js app');
// ID устройств Dynamixel
let id_led_1 = 9;
let id_led_2 = 50;
let id_but_1 = 3;
let id_but_2 = 60;
// Переменные для хранения состояний кнопок
let data_but_1 = 0;
let data_but_2 = 0;
// Скорость передачи для шины Dynamixel
let baudrate = 57600;
// Создаём объекты устройств
let led_1 = DynamixelDevice.create(id_led_1);
let led_2 = DynamixelDevice.create(id_led_2);
let but_1 = DynamixelDevice.create(id_but_1);
let but_2 = DynamixelDevice.create(id_but_2);
// Инициализация шины Dynamixel
DxlMaster.begin(baudrate);
// Инициализация устройств
led_1.init();
led_2.init();
but_1.init();
but_2.init();
// Таймер, который каждые 250 мс проверяет кнопки и включает/выключает светодиоды
Timer.set(250, Timer.REPEAT,
function() {
data_but_1 = but_1.read(27); // Чтение состояния кнопки 1
data_but_2 = but_2.read(27); // Чтение состояния кнопки 2
if(data_but_1)
led_1.write(26, 1); // Включаем светодиод 1
else led_1.write(26, 0); // Выключаем светодиод 1
if(data_but_2)
led_2.write(26, 1); // Включаем светодиод 2
else led_2.write(26, 0); // Выключаем светодиод 2
},
null);
Таким образом, после загрузки данного скетча в контроллер, при нажатии на кнопку будет включаться соответствующий ей светодиод. Аналогичным образом, становится возможным взаимодействовать и с другими модулями, которых в цепи находится несколько штук.
Управление двигателями постоянного тока
В контроллере ESP-JS-AR имеются встроенные драйвера моторов, с помощью которых можно управлять двигателями постоянного тока. Контроллер поддерживает одновременное подключение до двух двигателей постоянного тока в двустороннем режиме управления (Рисунок 3.12), либо до четырех двигателей в одностороннем управлении.

Рисунок 3.12. Подключение двигателей постоянного тока к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Рассмотрим простейший пример:
#include <JsAr.h>
#include <JsArMotors.h>
void setup()
{
JsAr.begin();
JsArMotors.begin();
JsArMotors.powerWrite(1, 255);
delay(5000);
JsArMotors.powerWrite(1, 0);
JsArMotors.powerWrite(1, -255);
delay(5000);
}
void loop()
{
}
Здесь показано управление двигателем постоянного тока по мощности. Аргументами к функции powerWrite() являются номер двигателя постоянного тока ("1" - если подключение выполнено к выводам H1 и H2, и "2" - если подключение выполнено к выводам H3 иH4), а также значение ШИМ в диапазоне от 0 до 1023. Реверс задается указанием знака " - " перед значением ШИМ.
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
// Подключаем необходимые библиотеки API
load('api_config.js'); // Настройки конфигурации
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймерами
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_dxl.js'); // Работа с моторами Dynamixel
print('Start js app'); // Сообщение в консоль о старте программы
JsArMotors.begin(); // Инициализация моторов JsAr
// Включаем мотор 1 на максимальную мощность вперёд
JsArMotors.powerWrite(1, 255);
Sys.usleep(2000000); // Ждём 2 секунды
// Останавливаем мотор 1
JsArMotors.powerWrite(1, 0);
Sys.usleep(2000000); // Ждём 2 секунды
// Включаем мотор 1 на максимальную мощность назад
JsArMotors.powerWrite(1, -255);
Sys.usleep(2000000); // Ждём 2 секунды
// Останавливаем мотор 1
JsArMotors.powerWrite(1, 0);
Таким образом, становится возможно управлять двигателями постоянного тока напрямую с контроллера ESP-JS-AR. В случае необходимости подключения сразу четырех двигателей или двигателей с энкодерами, рекомендуется рассмотреть примеры, содержащиеся в соответствующей вкладке.
Управление сервоприводами
Помимо двигателей постоянного тока, в качестве исполнительных механизмов так же широко используются сервоприводы, управлять положением фланца которых можно с помощью ШИМ сигнала. В качестве примера рассмотрим управление двумя простейшими сервоприводами, подключенными к контроллеру ESP-JS-AR через плату расширения для подключения периферийных модулей. Такая схема подключения выбрана, во-первых, для удобства подключения, а во-вторых, на плате расширения находится свой стабилизатор питания, который будет отвечать за питание сервоприводов стабилизированными 5В (Рисунок 3.13):

Рисунок 3.13. Подключение сервоприводов к программируемому контроллеру IoT ESP-JS-AR с помощью платы расширения
В приведенной выше схеме подключение выполнено к колодкам, содержащим сигнальные линии под номером 12 и 13. Однако, надо понимать, что соответствующие им линии управления на контроллере ESP-JS-AR будут 12и 14, соответственно.
Пример для Arduino IDE
Для удобства управления сервоприводами рекомендуется установить библиотеку "ESP32Servo" (Рисунок 3.14):
Рисунок 3.14. Установка библиотеки для управления сервоприводами ESP32Servo
Используя установленную библиотеку, код для управления положением фланца сервоприводов выглядит следующим образом:
// Подключаем необходимые библиотеки
#include <ESP32Servo.h> // Библиотека для управления сервоприводами на ESP32
#include "JsAr.h" // Библиотека для работы с платой ESP-JS-AR
// Объявляем объекты сервоприводов
Servo myservo1;
Servo myservo2;
int pos = 0; // Переменная для задания положения сервоприводов
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
// Инициализация сервоприводов на конкретных пинах
myservo1.attach(12);
myservo2.attach(14);
}
void loop() {
// Плавное движение сервоприводов от 0 до 180 градусов
for (pos = 0; pos <= 180; pos += 1) {
myservo1.write(pos);
myservo2.write(pos);
delay(30); // Задержка для плавного движения
}
// Плавное движение сервоприводов от 180 до 0 градусов
for (pos = 180; pos >= 0; pos -= 1) {
myservo1.write(pos);
myservo2.write(pos);
delay(30); // Задержка для плавного движения
}
}
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load('api_config.js'); // Конфигурация платы
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймерами
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Работа с платой ESP-JS-AR
load('api_jsar_servo.js'); // Работа с сервоприводами через JsAr
let led_state = false; // Состояние светодиода
let servo1 = Servo.create(); // Создаем объект сервопривода 1
let servo2 = Servo.create(); // Создаем объект сервопривода 2
servo1.attach(12); // Подключаем servo1 к пину 12
servo2.attach_full(19, 1000, 2000); // Подключаем servo2 к пину 19 с рабочим диапазоном 1000-2000 мкс
Timer.set(2000, Timer.REPEAT, function() {
// Каждые 2 секунды переключаем состояние светодиода и сервоприводов
if (led_state === false) {
led_state = true;
JsAr.expanderWriteLed(1); // Включаем светодиод на плате
servo1.write(45); // Устанавливаем угол сервопривода 1
servo2.write(45); // Устанавливаем угол сервопривода 2
} else {
led_state = false;
JsAr.expanderWriteLed(0); // Выключаем светодиод
servo1.write(135); // Устанавливаем другой угол сервопривода 1
servo2.write(135); // Устанавливаем другой угол сервопривода 2
}
print(servo1.read()); // Выводим угол servo1 в консоль
print(servo2.read_us()); // Выводим значение в микросекундах servo2
}, null);
Подключение LCD экрана
В состав набора входит LCD дисплей со встроенным I2C драйвером. Сам дисплей представляет собой классический символьный экран в 2 строки по 16 символов каждый и может быть использован как инструмент для вывода текстовой информации. Подключается данный экран по шине I2C, сигнальные линии которой, по умолчанию, находятся на следующих выводах: SCL – 22, SDA – 21 (но при необходимости это можно изменить). Так для работы дисплея требуется подать на него питание в 5В, и с помощью потенциометра, расположенного на драйвере экрана, установить подходящую контрастность символов. Таким образом, схема подключения будет выглядеть примерно следующим образом (Рисунок 3.15):

Рисунок 3.15. Подключение LCD экрана к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Для более удобного управления экраном рекомендуется установить библиотеку "LiquidCrystal I2C" (Рисунок 3.16):

Рисунок 3.16. Установка библиотеки для управления LCD дисплеем LiquidCrystal I2C
После установки библиотеки код для вывода данных на экран выглядит следующим образом:
#include <Wire.h> // Библиотека для работы с I2C
#include <LiquidCrystal_I2C.h> // Библиотека для работы с LCD по I2C
#include "JsAr.h" // Библиотека для платы ESP-JS-AR
LiquidCrystal_I2C lcd(0x27, 16, 2); // Адрес LCD 0x27, 16 символов на 2 строки
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
lcd.init(); // Инициализация LCD
lcd.backlight(); // Включение подсветки дисплея
}
void loop() {
// Первая строка и первая надпись
lcd.setCursor(0, 0);
lcd.print("Applied Robotics");
lcd.setCursor(0, 1);
lcd.print("KPMIS IoT");
delay(1000); // Задержка 1 секунда
lcd.clear(); // Очистка экрана
// Вторая надпись
lcd.setCursor(0, 0);
lcd.print("TEXT TEXT");
delay(1000);
lcd.setCursor(0, 1);
lcd.print("I2C Display");
delay(1000);
}
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, будет выглядеть следующим образом:
load("api_config.js");
load("api_sys.js");
load("api_gpio.js");
load("api_timer.js");
load("api_lcd_i2c.js");
load("api_jsar.js");
// I2C: SDA - 32, SDC - 33
print("Start user js app at", Sys.uptime());
let lcd = LCD_I2C.create();
lcd.begin(2, 10);
lcd.home();
lcd.print("Hello World!");
lcd.setCursor(0, 1);
lcd.print("Uptime: ");
Timer.set(1000, Timer.REPEAT, function() { lcd.setCursor(8, 1); lcd.print(Sys.uptime() |0)
}, null);
Подключение эластичной клавиатуры
В состав набора входит эластичная клавиатура на 16 (4х4) или на 12 (3х4) кнопок. Данная клавиатура может быть подключена к контроллеру и использована для ввода данных. Несмотря на отсутствие полноценного алфавита, данная клавиатура может быть использована для моделирования ввода простейших команд, либо же неких символьных последовательностей, которые могут быть использованы в качестве кодов доступа. Для более удобной работы с клавиатурой рекомендуется скачать и установить соответствующую библиотеку, например, "iarduino_KB". Подключение клавиатуры к контроллеру выполняется перемычками, которые можно подключать к любым цифровым линиям (однако, надо помнить, что в системе некоторые линии могут быть задействованы другим оборудованием). В результате, подключение клавиатуры к контроллеру может выглядеть следующим образом (Рисунок 3.17):

Рисунок 3.17. Подключение эластичной клавиатуры к программируемому контроллеру IoT ESP-JS-AR
Благодаря установленной библиотеке, код работы с клавиатурой будет выглядеть следующим образом:
#include "JsAr.h" // Библиотека для работы с платой ESP-JS-AR
#include <iarduino_KB.h> // Библиотека для работы с эластичной клавиатурой
// Инициализация клавиатуры с указанием выводов (пины для рядов и колонок)
iarduino_KB KB(14, 12, 13, 15, 4, 2, 22, 21);
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
KB.begin(KB1); // Инициализация клавиатуры (режим KB1)
Serial.begin(115200); // Инициализация Serial для вывода данных
}
void loop() {
// Проверка нажатия клавиши
if (KB.check(KEY_DOWN)) {
// Вывод номера нажатой клавиши и символа в
Serial Serial.print(KB.getNum());
Serial.print(" = \"");
Serial.print(KB.getChar());
Serial.println("\"");
}
}
В данном примере, после инициализации всех необходимых библиотек, происходит инициализация подключенной клавиатуры, путем указания линий, к которым подключен шлейф. Указание линий производится, начиная с левого вывода шлейфа. Затем указывается тип клавиатуры: KB1 (16ти кнопочная) или KB3 (12ти кнопочная). После чего, в бесконечном цикле происходит опрос кнопок клавиатуры, и в случае их нажатия, вывод номера кнопки и его символа в терминал (Рисунок 3.18):

Рисунок 3.18. Результат опроса эластичной клавиатуры
Работа с Arduino-совместимыми компонентами
В качестве примера работы контроллера ESP-JS-AR с классическими Arduino-совместимыми комплектующими, такими как: светодиоды, кнопки, различные простейшие датчики, рассмотрим процесс взаимодействия с собранной на макетной плате системы из 3х светодиодов, кнопки и датчика расстояния (Рисунок 3.19):

Рисунок 3.19. Подключение Arduino-совместимых компонентов к программируемому контроллеру IoT ESP-JS-AR
Схема сборки данной системы изображена на рисунке 3.20. Здесь номера линий указы в нотации платы для подключения периферийных модулей. Т.е. при разработке кода номера линий, указанные на схеме и указываемые в коде, совпадать не будут (смотрите описание платы для подключения периферийных модулей).

Рисунок 3.20. Схема подключения Arduino-совместимых компонентов к программируемому контроллеру IoT ESP-JS-AR
Пример для Arduino IDE
Код управляющей программы выглядит следующим образом:
#include "JsAr.h"
// указываем пины светодиодов
int LedG = 2;
int LedY = 4;
int LedR = 15;
// указываем пин кнопки
int But = 22;
// указываем пины дальномера
int Trig = 21;
int Echo = 18;
void setup() {
JsAr.begin();
Serial.begin(115200);
pinMode(LedG, OUTPUT);
pinMode(LedY, OUTPUT);
pinMode(LedR, OUTPUT);
pinMode(But, INPUT);
pinMode(Trig, OUTPUT);
pinMode(Echo, INPUT);
// желтый светодиод всегда включен
digitalWrite(LedY, HIGH);
digitalWrite(LedR, LOW);
}
void loop() {
int duration, cm;
// если кнопка нажата, выводим сообщение
if(!digitalRead(But)){
Serial.println("But pressed");
// включаем зеленый светодиод
digitalWrite(LedG, HIGH);
delay(100);
}
else digitalWrite(LedG, LOW);
// формируем импульс для дальномера
digitalWrite(Trig, LOW);
delayMicroseconds(2);
digitalWrite(Trig, HIGH);
delayMicroseconds(10);
digitalWrite(Trig, LOW);
// вычисляем расстояние
duration = pulseIn(Echo, HIGH);
cm = duration / 58;
Serial.print(cm);
Serial.println(" cm");
// если расстояние меньше 10 см,
// то включается красный светодиод
if(cm < 10){ digitalWrite(LedR, HIGH);
}
else digitalWrite(LedR, LOW);
delay(100);
}
В данном примере происходит следующее. После инициализации всех необходимых библиотек, происходит включение желтого светодиода, который в примере включен постоянно. Затем, в непрерывном цикле происходит опрос кнопки и ультразвукового датчика расстояния. Если кнопка нажата, то включается зеленый светодиод. А если УЗ датчик расстояния фиксирует расстояние до объекта, менее 10 см, то включается красный светодиод. Таким образом, совместно с контроллером ESP-JS-AR могут быть использованы стандартные Arduino-совместимые комплектующие.
Пример для Mongoose OS
В случае использования среды разработки Mongoose OS при использовании JavaScript, код, реализующий аналогичный пример, но без функционала дальномера будет выглядеть следующим образом:
// Подключаем необходимые модули Mongoose OS
load('api_config.js'); // Работа с конфигурацией
load('api_gpio.js'); // Работа с GPIO
load('api_timer.js'); // Работа с таймерами
load('api_sys.js'); // Системные функции
load('api_jsar.js'); // Специфичные функции для платы ESP-JS-AR
// Определение пинов
let Gpin = 2; // Зелёный светодиод
let Ypin = 4; // Жёлтый светодиод
let Rpin = 15; // Красный светодиод
let Bpin = 22; // Кнопка/датчик
let Trig = 21; // Триггер ультразвукового датчика
let Echo = 18; // Эхо ультразвукового датчика
// Переменные для работы с ультразвуковым датчиком
let Time_start = 0;
let Time_stop = 0;
let duration = 0;
// Настройка пинов
GPIO.setup_input(Echo, GPIO.PULL_NONE); // Эхо как вход без подтяжки
GPIO.setup_output(Gpin, 0); // Зелёный светодиод как выход, начальное состояние LOW
GPIO.setup_output(Ypin, 0); // Жёлтый светодиод как выход, начальное состояние LOW
GPIO.setup_output(Rpin, 0); // Красный светодиод как выход, начальное состояние LOW
GPIO.setup_input(Bpin, GPIO.PULL_NONE); // Кнопка как вход без подтяжки
// Таймер с периодом 500 мс
Timer.set(500, Timer.REPEAT, function() {
GPIO.write(Gpin, 1); // Включаем зелёный светодиод
if (GPIO.read(Bpin)) { // Если кнопка нажата
GPIO.write(Ypin, 0); // Выключаем жёлтый светодиод
} else {
GPIO.write(Ypin, 1); // Иначе включаем жёлтый светодиод
}
}, null);
Отправка данных с платы в веб-интерфейс
Рассмотрим способ отправки данных с микроконтроллера в веб-интерфейс. Для работы нам понадобится 2 файла – основной файл .ino, который содержит логику устройства (платы), и файл .h с разметкой HTML для создания интерфейса веб-страницы.
Сторона веб-интерфейса
Создаем файл html_base.h и открываем его. В нем создаем переменную, которая будет содержать в себе всю разметку:
char html[] PROGMEM = R"rawliteral()rawliteral";
Весь дальнейший код пишется в скобках. Объявляем тип документа как HTML, указываем русский язык:
<!DOCTYPE html>
<html lang="ru">
Тег <head> содержит метаинформацию о документе, то есть данные для браузера. Это техническая информация о веб-странице, которая не отображается напрямую пользователю, но необходима браузерам, поисковым системам и другим сервисам для правильной обработки документа:
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Example</title>
В теге <style> находится секция CSS. Это язык стилей, который определяет внешний вид HTML-документа. После этого тег <head> закрывается:
<style>
…
</style>
</head>
Тег<body> – это тело документа. Здесь находится видимое содержимое страницы. Интересует объект <p>, в котором будут выводится данные. Зададим ему id – text:
<p id="text" style="margin: 0 0 0 10px;">text</p>
Необходимо написать скрипт, который будет принимать значения и выводить их в нужный объект. Создаем функцию getText. Внутри создаем новый объект xhr – новый запрос на плату, который будет экземпляром XMLHttpRequest:
var xhr = new XMLHttpRequest;
Теперь откроем запрос с методом “GET” по адресу "/getData", третий элемент отвечает за асинхронную работу – пишем true:
xhr.open("GET", "/getData", true);
Далее пишем обработчик на изменение состояния запроса:
xhr.onreadystatechange = function() {}
Внутри проверяем состояние. Интересует только успешный запрос:
if (xhr.readyState === 4) {
Добавляем внутрь изменение объекта <p>. Запишем в него значение, которое пришло:
document.getElementById("text").innerHTML = xhr.responseText;
В конце необходимо отправить запрос:
xhr.send();
Далее необходимо прописать функцию, которая будет вызывать чтение с платы. Для этого создаем константу и присваиваем ей функцию setInterval, где впишем функцию и время в миллисекундах, через которое она будет срабатывать:
const intervalID = setInterval(getText,1000);
Полный код файла html_base.h:
char html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Example</title>
<style>
:root {
--bg: #638be9; /* фон страницы */
--card: #3c75cf; /* фон карточек */
--text: #ffffff; /* цвет текста */
--accent: #3b82f6; /* акцентный цвет */
}
* { box-sizing: border-box; } html, body { height: 100%; } body {
margin: 0;
font-family: system-ui, -apple-system, Micra, Roboto, Inter, Tahoma, sans-serif; background: var(--bg);
color: var(--text);
}
.grid {
display: grid;
min-height: 80svh; /* минимальная высота */
place-content: center; /* центрирование содержимого */ gap: 16px; /* расстояние между элементами */
grid-template-columns: repeat(1, 320px); /* 1 колонка по 320px */ grid-auto-rows: 280px; /* высота строк */
padding: 16px;
}
.card {
display: grid;
place-items: center; background: var(--card); border: 1px solid #334155; border-radius: 16px; padding: 16px;
box-shadow: 0 6px 20px rgb(0 0 0 / .2);
user-select: none; /* запрет выделения текста */
}
.card span {
font-weight: 600; letter-spacing: 0.3px; text-align: center; display: flex;
}
.innerDivFlex{
# 73
**Программируемый контроллер IoT ESP-JS-AR. **Учебное пособие
display: flex;
flex-direction: column; /* элементы по колонке */
}
.onlyFlex{ display: flex;
align-items: center;
justify-content: center; /* выравнивание по центру */ margin-bottom: 10px;
}
</style>
</head>
<body>
<main class="grid">
<div class="card">
<span>Случайное значение</span>
<span>
<div class="innerDivFlex">
<div class="onlyFlex">
<p id="text" style="margin: 0 0 0 10px;">text</p> <!-- сюда будет выводиться значение с ESP -->
</div>
</div>
</span>
</div>
</main>
<script>
// Функция для получения данных с ESP function getText(){
var xhr = new XMLHttpRequest;
xhr.open("GET", "/getData", true); // GET-запрос на ESP xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // когда получен ответ document.getElementById("text").innerHTML = xhr.responseText; // выво-
дим данные
}
}
xhr.send(); // отправляем запрос
}
// Интервал вызова функции каждую секунду const intervalID = setInterval(getText, 1000);
</script>
</body>
</html>
)rawliteral";
Сторона платы
Для работы помимо уже известной нам "JsAr" необходимо использовать библиотеки "WiFi" для управления WiFi модулем платы и "ESPAsyncWebServer", позволяющей создавать веб-сервер и обрабатывать запросы без блокировки основного кода. Эти библиотеки можно найти в репозиториях Arduino IDE. Также необходимо подключить HTML верстку:
#include <WiFi.h> // Модуль для Wi-Fi
#include "ESPAsyncWebServer.h" // Библиотека для создания Web-server"а #include "html_base.h" // html
#include <JsAr.h> // Подключение библиотеки для работы с платой ESP.
Пропишем логин и пароль которые присвоим Wi-Fi:
const char *ssid = "testCase"; // ssid Wi-Fi
const char *password = "12345678"; // Пароль к Wi-Fi
Далее напишем ip, маску и шлюз веб-сервера. Также необходимо присвоить порт веб-серверу. Пропишем стандартный порт, который умеет обрабатывать http запросы – 80. И создадим переменную, в которую будем генерировать случайное число:
IPAddress local_ip(192, 168, 0, 5); // IР на Web страничку
IPAddress gateway(192, 168, 0, 5);
IPAddress subnet(255, 255, 255, 0); // Маска подсети
AsyncWebServer server(80); // Стандартный порт для обработки HTTP запросов uint8_t randNumber = 0; // Число для вывода на веб-сервер
Переходим в функцию setup, где инициализируем Wi-Fi модуль с нашими данными:
WiFi.softAP(ssid, password); WiFi.softAPConfig(local_ip, gateway, subnet);
Теперь пропишем стандартный обработчик для начальной странички. Внутри него пропишем ответ с выводом html:
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", html); // Обязательный ответ на запрос
});
Добавим ещё один обработчик, который будет отправлять данные с платы на веб-сервер. Уже создан запрос по адресу "/getData". Будем отправлять в ответ на него данные. Внутри генерируем случайное число,
используя встроенную функцию random, в свойствах которой прописываем минимум и максимум, между которыми будет генерироваться число. Далее отправляем ответ с числом, преобразованным в строку, и выводим в Serial для проверки значений с веб-cервером:
#include <WiFi.h> // Модуль для работы с Wi-Fi
#include "ESPAsyncWebServer.h" // Библиотека для создания асинхронного веб-сервера
#include "html_base.h" // HTML-шаблон страницы #include <JsAr.h> // Библиотека для работы с платой ESP
// Логин и пароль для Wi-Fi
const char *ssid = "testCase"; // SSID сети
const char *password = "12345678"; // Пароль сети
// Настройки локального IP
IPAddress local_ip(192, 168, 0, 5); // IP-адрес ESP
IPAddress gateway(192, 168, 0, 5); // Шлюз
IPAddress subnet(255, 255, 255, 0); // Маска подсети
AsyncWebServer server(80); // Порт веб-сервера
uint8_t randNumber = 0; // Переменная для случайного числа
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
Serial.begin(115200); // Запуск Serial для вывода отладочной информации
// Инициализация Wi-Fi в режиме точки доступа
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
// Обработчик главной страницы
server.on("/", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/html", html); // Отправляем HTML-страницу
});
// Обработчик запроса для получения случайного числа
server.on("/getData", HTTP_GET,
[](AsyncWebServerRequest *request) {
randNumber = random(10, 100); // Генерируем случайное число от 10 до 99
request->send(200, "text/plain", (String)randNumber); // Отправляем число клиенту
Serial.println(randNumber); // Выводим число в Serial для отладки
});
server.begin(); // Запуск веб-сервера
}
void loop() {
// Пустой loop, так как сервер работает асинхронно
}

Рисунок 3.21. Генерация случайного числа в веб-интерфейсе
Отправка данных с веб-интерфейса на плату
Теперь рассмотрим способ отправки данных с веб-интерфейса на микроконтроллер. Для работы нам также понадобится два файла – основной файл .ino и файл .h для создания интерфейса веб-страницы.
Сторона веб-интерфейса
Создаем файл html_base.h, открываем его. Так же, как в предыдущей работе, создаем переменную, которая будет содержать в себе всю разметку. Объявляем тип документа, указываем русский язык. Прописываем метаданные в теге <head>.
В теге <body> интересует <input>, и кнопка, которая будет отправлять значение. У <input> пропишем id – inputValue, а у <button> – кнопки отправки данных – пропишем ивент onclick и присвоим функцию sendData(), которая будет вызываться при нажатии на кнопку:
<div class="onlyFlex">
<input id="inputValue" type="text" class="input-text" placeholder="" />
</div>
<div class="onlyFlex">
<button class="btn" onclick="sendData()">Отправить</button>
</div>
Теперь напишем скрипт, который будет отправлять значения на плату. Создаем функцию sendData, которую использовали раньше. Внутри создаем новый объект xhr – новый запрос на плату, который будет экземпляром XMLHttpRequest:
var xhr = new XMLHttpRequest;
Получим значение с input"а:
var text = document.getElementById("inputValue").value;
Теперь откроем запрос с методом “GET” по адресу /NewData, а также добавим параметр для отправки data, используя символ "?": ""NewData?data=" + text"; третий элемент отвечает за асинхронную работу – пишем true:
xhr.open("GET", "NewData?data=" + text, true);
В конце необходимо отправить запрос: xhr.send();
Полный код файла html_base.h:
char html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Example 2</title>
<style>
:root {
--bg: #638be9; /* фон */
--card: #3c75cf; /* карточки */
--text: #ffffff; /* текст */
--accent: #3b82f6; /* акцентный цвет */
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; } body {
font-family: system-ui, -apple-system, Micra, Roboto, Inter, Tahoma, sans-serif; background: var(--bg);
color: var(--text);
}
.grid {
display: grid;
min-height: 80svh; place-content: center; gap: 16px;
grid-template-columns: repeat(1, 320px); grid-auto-rows: 280px;
padding: 16px;
}
.card {
display: grid;
place-items: center; background: var(--card); border: 1px solid #334155; border-radius: 16px; padding: 16px;
box-shadow: 0 6px 20px rgb(0 0 0 / .2);
user-select: none; /* запрет выделения текста */
}
.card span {
font-weight: 600; letter-spacing: 0.3px; text-align: center; display: flex;
}
.innerDivFlex{ display: flex;
flex-direction: column;
}
.onlyFlex{ display: flex;
align-items: center; justify-content: center; margin-bottom: 10px;
}
.input-text {
margin-left: 15px; width: 100%; padding: 10px 14px;
background: var(--card); border: 1px solid #334155; border-radius: 12px;
color: var(--text); font-size: 14px; outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.input-text::placeholder{ color: var(--text);
}
.input-text:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.3);
}
.btn {
display: inline-block; padding: 10px 14px; background: var(--card); border: 1px solid #334155; border-radius: 12px;
color: var(--text); font-size: 14px; font-weight: 500; cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.btn:hover {
background: #273449;
}
.btn:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.3);
}
</style>
</head>
<body>
<main class="grid">
<div class="card">
<span>Введите текст</span>
<span>
<div class="innerDivFlex">
<div class="onlyFlex">
<input id="inputValue" type="text" class="input-text" placeholder=""/>
</div>
<div class="onlyFlex">
<button class="btn" onclick="sendData()">Отправить</button>
</div>
</div>
</span>
</div>
</main>
<script>
// Функция отправки данных на ESP function sendData(){
var xhr = new XMLHttpRequest;
var text = document.getElementById("inputValue").value; // получаем значение из поля
xhr.open("GET", "NewData?data=" + text, true); // формируем GET-запрос xhr.send(); // отправляем запрос
}
</script>
</body>
</html>
)rawliteral";
Сторона платы
Для работы используем библиотеки "JsAr", "WiFi" для управления WiFi модулем платы и "ESPAsyncWebServer", позволяющую создавать веб-сервер и обрабатывать запросы без блокировки основного кода. Также подключаем HTML верстку:
#include <WiFi.h> //Модуль для Wi-Fi
#include "ESPAsyncWebServer.h" //Библиотека для создания Web-server"а #include "html_base.h" //html
#include <JsAr.h> // Подключение библиотеки для работы с платой ESP.
Пропишем логин и пароль для Wi-Fi. Далее напишем ip, маску и шлюз веб-сервера. Прописываем порт для обработки http запросов и создаем переменную для записи приходящего значения:
const char *ssid = "testCase2"; //ssid Wi-Fi
const char *password = "12345678"; //Пароль к Wi-Fi
//Выставляем настройки Wi-Fi
IPAddress local_ip(192, 168, 0, 5); //Ip на Web страничку
IPAddress gateway(192, 168, 0, 5);
IPAddress subnet(255, 255, 255, 0); //Маска подсети
AsyncWebServer server(80); //Стандартный порт для обработки HTTP запросов
String text = ""; //Переменная для записи приходящего значения В функции setup инициализируем Wi-Fi модуль с данными и прописываем обработчик для начальной страницы:
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
server.on("/", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/html", html); //Обязательный ответ на запрос
});
Добавим ещё один обработчик, который будет принимать значения с веб-сервера. Уже создан запрос по адресу "/NewData". Теперь будем принимать значения с него. Внутри записываем в переменную text параметр, который прописали в запросе. Далее выводим его в Serial для проверки и отправляем ответ об успешном запросе:
server.on("/NewData", HTTP_GET, [](AsyncWebServerRequest *request) {
text = request->getParam("data")->value(); //Получаем информация с запроса
Serial.println(text); //Выводим для проверки
request->send(200, "text/html", html); //Отправляем 200, об успешном получении
});
В конце функции setup запускаем сервер командой begin: server.begin(); //Инициализация и запуск Web-сервера
Полный код выглядит следующим образом:
#include <WiFi.h> // Модуль для работы с Wi-Fi
#include "ESPAsyncWebServer.h" // Библиотека для асинхронного веб-сервера
#include "html_base.h" // HTML страница
#include <JsAr.h> // Библиотека для работы с платой ESP
// Логин и пароль для Wi-Fi точки доступа
const char *ssid = "testCase2"; // Имя сети Wi-Fi
const char *password = "12345678"; // Пароль
// Настройки сети
IPAddress local_ip(192, 168, 0, 5); // Статический IP для веб-страницы
IPAddress gateway(192, 168, 0, 5); // Шлюз
IPAddress subnet(255, 255, 255, 0); // Маска подсети
AsyncWebServer server(80); // Создание сервера на стандартном
HTTP-порту String text = ""; // Переменная для хранения данных, полученных с веб-сервера
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
Serial.begin(115200); // Инициализация Serial для отладки
// Настройка Wi-Fi в режиме точки доступа
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
// Обработчик для начальной страницы
server.on("/", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/html", html); // Отправляем HTML страницу
});
// Обработчик для получения данных с веб-страницы
server.on("/NewData", HTTP_GET,
[](AsyncWebServerRequest *request) {
// Считываем значение параметра "data" из запроса
if (request->hasParam("data")) {
text = request->getParam("data")->value();
Serial.println(text); // Выводим значение для проверки
} else {
Serial.println("Параметр 'data' отсутствует в запросе");
});
}
// Отправляем HTML страницу в ответ
request->send(200, "text/html", html);
server.begin(); // Запуск веб-сервера
}
void loop() {
// Основной цикл пустой, обработка запросов асинхронная
}
В результате при работе кода можно увидеть, как появилась сеть testСase. Подключимся к ней. После успешного подключения перейдем по адресу, который прописали в коде, чтобы посмотреть сайт. Напишем любую строку ввода и нажмем на кнопку для отправки. Проверим, что строка дошла до платы. При открытии монитора порта, отобразилась переменная.

Рисунок 3.22. Получение данных с веб-интерфейса на плату, с отображением результата на мониторе порта
Работа с модулями с использованием веб-интерфейса
Аналогично предыдущим работам, воспользуемся способом получения и управления данными через веб-интерфейс. Для этого примера подключаем несколько модулей: модуль "Датчик температуры и влажности воздуха" (ID 22), модуль "Трёхцветный светодиод" (ID 21), модуль "Концевой выключатель" (ID 23) и LCD-экран.
Рассмотрим обработчики на стороне платы:
При открытии веб-страницы происходит запрос на путь "/" и веб-сервер в ответ на этот запрос отправляет html разметку.
Для пути "/getData" вызывается метод "setJson", который в свою очередь считывает данные с датчиков, таких как температура, влажность и состояние концевого выключателя, формирует их в формат JSON и отправляет на веб-страницу. При отправке запроса на путь "/RGB" обработчик принимает параметры значений красного, зелёного и синего цветов и использует их для управления светодиодом. Путь "/lcdPrint" позволяет управлять дисплеем с помощью полученных параметров: текст, строка и позиция. Путь "/lcdClear" очищает дисплей.
Полный код выглядит следующим образом:
#include <LiquidCrystal_I2C.h> // Библиотека для работы с LCD дисплеем
#include <WiFi.h> // Модуль Wi-Fi
#include "ESPAsyncWebServer.h" // Библиотека для асинхронного Web-сервера
#include "html_base.h" // HTML страница
#include <JsAr.h> // Библиотека для работы с платой ESP
#include "DxlMaster2.h" // Библиотека для управления Dynamixel устройствами
// Логин и пароль Wi-Fi
const char *ssid = "ESP_JS_AR";
const char *password = "12345678";
// Настройки сети
IPAddress local_ip(192, 168, 0, 5);
IPAddress gateway(192, 168, 0, 5);
IPAddress subnet(255, 255, 255, 0);
AsyncWebServer server(80); // Порт для HTTP сервера
// Объявляем LCD-дисплей (адрес, количество столбцов, количество строк)
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Dynamixel устройства
DynamixelDevice deviceRGB(21); // Трёхцветный светодиод DynamixelDevice deviceTemp(22); // Датчик температуры и влажности
DynamixelDevice deviceUsw(23); // Концевой выключатель
// Дополнительные переменные
String json = ""; // Для формирования JSON объекта
uint8_t uswData = 0; // Данные концевого выключателя
uint8_t HumInt = 0,
HumDot = 0; // Целая и дробная части влажности
uint8_t TempInt = 0,
TempDot = 0; // Целая и дробная части температуры
void setup() {
JsAr.begin(); // Инициализация платы ESP-JS-AR
Serial.begin(115200); // Инициализация Serial для отладки
DxlMaster.begin(57600); // Инициализация шины Dynamixel
// Настройка Wi-Fi в режиме точки доступа
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
// Инициализация LCD дисплея
lcd.init();
lcd.backlight(); // Включаем подсветку
lcd.clear(); // Очищаем дисплей
// Обработчик начальной страницы
server.on("/", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/html", html); // Отправляем HTML страницу
});
// Обработчик для получения данных с датчиков
server.on("/getData", HTTP_GET,
[](AsyncWebServerRequest *request){
setJson(); // Формируем JSON объект
request->send(200, "text/plain", json);
json = ""; // Очищаем для следующей отправки
});
// Обработчик для управления RGB светодиодом
server.on("/RGB", HTTP_GET,
[](AsyncWebServerRequest *request){
String red = request->getParam("red")->value();
String green = request->getParam("green")->value();
String blue = request->getParam("blue")->value();
writeRGB(red.toInt(), green.toInt(), blue.toInt());
request->send(200, "text/html", html);
});
// Обработчик очистки LCD дисплея
server.on("/lcdClear", HTTP_GET,
[](AsyncWebServerRequest *request){
lcd.clear();
request->send(200, "text/html", html);
});
// Обработчик для вывода текста на LCD
server.on("/lcdPrint", HTTP_GET, [](AsyncWebServerRequest *request){
String row = request->getParam("row")->value();
String pos = request->getParam("pos")->value();
String text = request->getParam("text")->value();
writeLCD(row.toInt(), pos.toInt(), text);
request->send(200, "text/html", html);
});
server.begin(); // Запуск сервера
}
void loop() {
delay(20); // Минимальная задержка
}
// Управление RGB светодиодом
void writeRGB(int red, int green, int blue){
deviceRGB.write(26, (uint8_t)green);
deviceRGB.write(27, (uint8_t)red);
deviceRGB.write(28, (uint8_t)blue);
}
// Управление LCD дисплеем
void writeLCD(int row, int pos, String text){
lcd.setCursor(pos, row);
lcd.print(text);
}
// Формирование JSON объекта с данными датчиков
void setJson(){
readData(); // Чтение данных с датчиков json = "[{";
json += "\"temp\":" + getTempValue() + ","; json += "\"humi\":" + getHumidityValue() + ","; json += "\"usw\":" + String(uswData);
json += "}]";
}
// Получение значения температуры в формате строки String getTempValue(){
return String(TempInt) + "." + String(TempDot);
}
// Получение значения влажности в формате строки String getHumidityValue(){
return String(HumInt) + "." + String(HumDot);
}
// Чтение данных с датчиков void readData(){
deviceTemp.read(24, HumInt); // Целая часть влажности
deviceTemp.read(26, HumDot); // Дробная часть влажности
deviceTemp.read(28, TempInt); // Целая часть температуры
deviceTemp.read(30, TempDot); // Дробная часть температуры
deviceUsw.read(27, uswData); // Концевой выключатель
}
Рассмотрим работу на стороне веб-страницы:
Обмен данными между страницей и платой происходит асинхронно, чтобы интерфейс оставался отзывчивым. Для получения данных страница каждые 200 мс отправляет GET-запрос с помощью метода setInterval на "/getData" с использованием XMLHttpRequest. Страница разбирает полученный JSON и обновляет значения в карточках интерфейса, обеспечивая отображение в реальном времени. Для отправки данных пользователь вводит значения в поля ввода (например, цвета для RGB или текст для LCD) и нажимает кнопку. JavaScript формирует GET-запрос с параметрами в URL, например, "/RGBred=255&green=0&blue=0" или "/lcdPrint?r ow=0&pos=0&text=Hello", и отправляет его.
Полный код файла html_base.h:
Julia, [11.09.2025 13:38]
char html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ESP-JS-AR</title>
<style>
:root {
--bg: #638be9; /* фон страницы */
--card: #3c75cf; /* фон карточек */
--text: #ffffff; /* цвет текста */
--accent: #3b82f6; /* цвет акцента */
}
* { box-sizing: border-box; } html, body { height: 100%; } body {
margin: 0;
font-family: system-ui, -apple-system, Micra, Roboto, Inter, Tahoma, sans-serif; background: var(--bg);
color: var(--text);
}
h1 {
text-align: center; margin-top: 20px; font-size: 28px; font-weight: bold;
}
/* Сетка для карточек */
.grid {
display: grid;
min-height: 80svh; place-content: center; gap: 16px;
grid-template-columns: repeat(2, 360px); grid-auto-rows: 280px;
padding: 16px;
}
/* Стили карточек */
.card {
display: grid;
place-items: center; background: var(--card); border: 1px solid #334155; border-radius: 16px; padding: 16px;
box-shadow: 0 6px 20px rgb(0 0 0 / .2);
user-select: none; /* запрет выделения текста */
}
.card span {
font-weight: 600; letter-spacing: .3px; text-align: center; display: flex;
}
.innerDivFlex{ display: flex;
flex-direction: column;
}
.onlyFlex{ display: flex;
align-items: center; justify-content: center; margin-bottom: 10px;
}
.valueOffset{ margin-left: 15px;
}
.input-text {
margin-left: 15px; width: 100%; padding: 10px 14px;
background: var(--card); border: 1px solid #334155; border-radius: 12px;
color: var(--text); font-size: 14px; outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.input-text::placeholder{ color: var(--text);
}
.input-text:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.3);
}
.btn {
display: inline-block; padding: 10px 14px; background: var(--card); border: 1px solid #334155; border-radius: 12px;
color: var(--text); font-size: 14px;
font-weight: 500; cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.btn:hover {
background: #273449;
}
.btn:focus { outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.3);
}
/* Адаптив для мобильных устройств */ @media (max-width: 480px) {
.grid {
grid-template-columns: repeat(1, 300px); grid-auto-rows: 280px;
gap: 12px;
}
}
</style>
</head>
<body>
<h1>ESP JS AR</h1>
<main class="grid">
<!-- Карточка концевого выключателя -->
<div class="card">
<span>Концевой выключатель</span>
<span>
<p>Значение: </p>
<p id="uswData" class="valueOffset">0</p>
</span>
</div>
<!-- Карточка RGB светодиода -->
<div class="card">
<span>Трехцветный светодиод</span>
<div class="innerDivFlex">
<div class="onlyFlex">
<div>Зеленый</div>
<input id="rgb_G" type="text" class="input-text" placeholder="0" />
</div>
<div class="onlyFlex">
<div>Красный</div>
<input id="rgb_R" type="text" class="input-text" placeholder="0" />
</div>
<div class="onlyFlex">
<div>Синий</div>
<input id="rgb_B" type="text" class="input-text" placeholder="0" />
</div>
<div class="onlyFlex">
<button class="btn" onclick="sendRGB()">Отправить</button>
</div>
</div>
</div>
<!-- Карточка датчика температуры и влажности -->
<div class="card">
<span>Датчик температуры и влажности</span>
<span>
<div class="innerDivFlex">
<div class="onlyFlex">
<p>Температура: </p>
<p id="temp" class="valueOffset">0</p>
</div>
<div class="onlyFlex">
<p>Влажность: </p>
<p id="humidity" class="valueOffset">0</p>
</div>
</div>
</span>
</div>
Julia, [11.09.2025 13:38]
<!-- Карточка LCD дисплея -->
<div class="card">
<span>LCD экран</span>
<div class="innerDivFlex">
<div class="onlyFlex">
<div>Строка</div>
<input id="LCD_row" type="text" class="input-text" placeholder="0" />
</div>
<div class="onlyFlex">
<div>Позиция</div>
<input id="LCD_point" type="text" class="input-text" placeholder="0" />
</div>
<div class="onlyFlex">
<div>Текст</div>
<input id="LCD_text" type="text" class="input-text" placeholder="Пример"
/>
</div>
<div class="onlyFlex">
<button class="btn" onclick="sendLCD()">Отправить</button>
<button class="btn" onclick="clearLCD()" style="margin-left: 15px;">Очи-
стить</button>
</div>
</div>
</div>
</main>
<script>
// Получаем элементы для обновления данных с ESP
let tempVal = document.getElementById("temp");
let humiVal = document.getElementById("humidity");
let uswVal = document.getElementById("uswData");
// Функция отправки значений RGB на плату function
sendRGB(){
let green = document.getElementById("rgb_G").value;
let red = document.getElementById("rgb_R").value;
let blue = document.getElementById("rgb_B").value;
let uri = RGB?red=${red}&green=${green}&blue=${blue};
var xhr = new XMLHttpRequest();
xhr.open("GET", uri, true);
xhr.send();
}
// Функция очистки LCD дисплея
function clearLCD(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "lcdClear", true);
xhr.send();
}
// Функция отправки текста на LCD дисплей
function sendLCD(){
let row = document.getElementById("LCD_row").value;
let pos = document.getElementById("LCD_point").value;
let text = document.getElementById("LCD_text").value;
let uri = lcdPrint?row=${row}&pos=${pos}&text=${text};
var xhr = new XMLHttpRequest();
xhr.open("GET", uri, true);
xhr.send();
}
// Функция периодического получения данных с платы function getData(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "/getData", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
let jsonObject = JSON.parse(xhr.responseText);
tempVal.innerHTML = jsonObject[0].temp; // Обновляем температуру
humiVal.innerHTML = jsonObject[0].humi; // Обновляем влажность
uswVal.innerHTML = jsonObject[0].usw; // Обновляем значение концевого выключателя
}
};
xhr.send();
}
// Запуск периодического опроса каждые 200 мс
const intervalID = setInterval(getData, 200);
</script>
</body>
</html>
)rawliteral";

Рисунок 3.23. Внешний вид веб-страницы