В прошлых статьях мы рассмотрели работу с SoC Espressif ESP8266 на примере работы с базовой AT-прошивкой, сборкой компилятора и написания собственной прошивки с реализацией TCP-сервера (клиента). В этой статье мы рассмотрим пример написания прошивки для работы с модулем передатчика nooLite MT1132 и попробуем управлять освещением в реальной квартире. Всем кому это интересно, прошу под хабракат.
Для написания прошивки мы будем использовать Eclipse Luna и мою сборку Unofficial Development Kit for Espressif ESP8266 (стоит отметить, что недавно я её обновил, теперь для её работы не требуется Python, обновлена libhal.a, а так же было добавлено много примеров: 1wire_ds18b20, dht22, blinky, blinky2, i2c_24xx16, i2c_htu21d и др.), о ней я рассказывал в прошлой статье.
Для работы нам потребуются следующие материалы и устройства:
1. Плата ESP-01 с чипом ESP8266.
2. Модуль MT1132, приобрести можно в этом интернет-магазине или этом.
3. Спецификация на чип ESP8266.
4. Описание SDK ESP8266.
5. Руководство по эксплуатации модуля nooLite MT1132.
Разработчики nooLite достаточно подробно описали модуль MT1132 в документации, а так же привели пример скретча для работы модуля с Arduino, поэтому сложностей с подключением модуля к плате ESP-01 не возникло.
В прошивке реализуем 2 режима работы ESP-01:
1. Режим конфигурации. Через Web-интерфейс в данном режиме можно будет установить параметры подключения ESP-01 к нашей AP, установить уникальный ID нашей платы. ID будет использоваться только для идентификации платы, если у нас в сети их будет несколько штук.
2. Основной режим. Через Web-интерфейс в данном режиме можно будет привязывать-отвязывать модуль MT1132 от силовых блоков, включать-выключать силовые блоки nooLite.
Краткий алгоритм работы прошивки такой:
1. После включения питания читаем из памяти настройки и конфигурацию wi-fi, если настроек нет или wi-fi в режиме STA, то переводим wi-fi в режим STA-AP, даём AP имя NOOLITE_%MACADDRESS%, пароль %MACADDRESS%, запускаем web-сервер в режим конфигурации.
2. Через простенький Web-интерфейс устанавливаем параметры подключения к AP, к этой AP плата ESP-01 будет подключаться в основном режиме. Устанавливаем ID платы. Сохраняем настройки во flash. Перезапускаем плату в основной режим.
3. В основном режиме запускается простенький Web-сервер, через который можно привязывать-отвязывать модуль MT1132 от силовых блоков, включать-выключать силовые блоки nooLite.
4. К GPIO0 подключаем обычную кнопку, замыкание кнопки на землю переводит ESP-01 назад в режим конфигурации.
5. К GPIO16 (на самом деле китайцы обманули и это не вывод GPIO, а вывод RESET) подключаем обычную кнопку, замыкание кнопки на землю ��роизводит аппаратный reset платы.
Теперь приступим к написанию кода (сразу скажу, что реализация web-сервера была позаимствована из проекта elektronika-ba):
Прошивка состоит из 6 основных файлов:
driver\uart.c — драйвер UART, в нем модифицирована процедура uart0_rx_intr_handler для приема и парсинга ответа от MT1132;
user\user_main.c — основной файл, но не главный;
user\flash_param.c — работа с flash-памятью;
user\noolite_platform.c — основных функций, вся логика работы по переключению между разными режимами;
user\noolite_config_server.c — web-сервер конфигурации;
user\noolite_control_server.c — web-сервер основного режима, отправка команд MT1132;
Разберем каждый файл по порядку, под спулером будет много коду с комментариями:
Описывать файл noolite_config_server.c я не вижу смысла, в нем идет запуск web-сервера в режиме конфигурации, по принципу работы он аналогичен работе в обычном режиме, который мы и рассмотрим ниже.
Реализация web-сервера очень простая и не претендует на гибкость и функциональность, и более того, реализовавать web-сервер на ESP8266 не совсем правильно, более правильный подход — это организация на ESP8266 TCP-клиента, который будет держать соединение с сервером управления, а на сервере управления уже организовать полноценный интерфейс взаимодействия (RESTfull или JSON) с внешней средой, под внешней средой я подразумеваею Web-интерфейс, разного рода мобильные и стационарные клиенты (Windows, Android, iOS и т.п.)
Внешний вид страниц web-интерфейса получился такой, интерфейс очень простой:
При нажатии на Return to normal mode, происходит возврат платы к нормальному режиму.
Страница deviceID предназначена для получения номера нашего устройства для каких-то вспомогательных задач, она просто выдает номер без какого-либо оформления
Реализация всего этого в коде:
И в заключении небольшое видео с демонстрацией работы платы:
В целом, мы в очередной раз видим, что возможности платы на чипе ESP8266 достаточно велики.
В заключении я бы хотел поблагодарить компанию Ноотехника, а так же администрацию сайта Thinking-Home.RU за предоставленное для тестирования оборудование nooLite.
Репозитарий проекта на Github
P.S. Если у кого-то будут пожелания относительно следующей статьи про ESP8266, то я готов их выслушать. Можно написать про работу с различными сенсорами: DS18B20, DHT22, SHT21, BMP180, ИК-приемник/передатчик, внешние мс памяти, например серия Microchip 24xx16 и т.д.
Для написания прошивки мы будем использовать Eclipse Luna и мою сборку Unofficial Development Kit for Espressif ESP8266 (стоит отметить, что недавно я её обновил, теперь для её работы не требуется Python, обновлена libhal.a, а так же было добавлено много примеров: 1wire_ds18b20, dht22, blinky, blinky2, i2c_24xx16, i2c_htu21d и др.), о ней я рассказывал в прошлой статье.
Для работы нам потребуются следующие материалы и устройства:
1. Плата ESP-01 с чипом ESP8266.
2. Модуль MT1132, приобрести можно в этом интернет-магазине или этом.
3. Спецификация на чип ESP8266.
4. Описание SDK ESP8266.
5. Руководство по эксплуатации модуля nooLite MT1132.
Разработчики nooLite достаточно подробно описали модуль MT1132 в документации, а так же привели пример скретча для работы модуля с Arduino, поэтому сложностей с подключением модуля к плате ESP-01 не возникло.
Итоговая схема подключения:

В прошивке реализуем 2 режима работы ESP-01:
1. Режим конфигурации. Через Web-интерфейс в данном режиме можно будет установить параметры подключения ESP-01 к нашей AP, установить уникальный ID нашей платы. ID будет использоваться только для идентификации платы, если у нас в сети их будет несколько штук.
2. Основной режим. Через Web-интерфейс в данном режиме можно будет привязывать-отвязывать модуль MT1132 от силовых блоков, включать-выключать силовые блоки nooLite.
Краткий алгоритм работы прошивки такой:
1. После включения питания читаем из памяти настройки и конфигурацию wi-fi, если настроек нет или wi-fi в режиме STA, то переводим wi-fi в режим STA-AP, даём AP имя NOOLITE_%MACADDRESS%, пароль %MACADDRESS%, запускаем web-сервер в режим конфигурации.
2. Через простенький Web-интерфейс устанавливаем параметры подключения к AP, к этой AP плата ESP-01 будет подключаться в основном режиме. Устанавливаем ID платы. Сохраняем настройки во flash. Перезапускаем плату в основной режим.
3. В основном режиме запускается простенький Web-сервер, через который можно привязывать-отвязывать модуль MT1132 от силовых блоков, включать-выключать силовые блоки nooLite.
4. К GPIO0 подключаем обычную кнопку, замыкание кнопки на землю переводит ESP-01 назад в режим конфигурации.
5. К GPIO16 (на самом деле китайцы обманули и это не вывод GPIO, а вывод RESET) подключаем обычную кнопку, замыкание кнопки на землю ��роизводит аппаратный reset платы.
Теперь приступим к написанию кода (сразу скажу, что реализация web-сервера была позаимствована из проекта elektronika-ba):
Прошивка состоит из 6 основных файлов:
driver\uart.c — драйвер UART, в нем модифицирована процедура uart0_rx_intr_handler для приема и парсинга ответа от MT1132;
user\user_main.c — основной файл, но не главный;
user\flash_param.c — работа с flash-памятью;
user\noolite_platform.c — основных функций, вся логика работы по переключению между разными режимами;
user\noolite_config_server.c — web-сервер конфигурации;
user\noolite_control_server.c — web-сервер основного режима, отправка команд MT1132;
Разберем каждый файл по порядку, под спулером будет много коду с комментариями:
Файл user_main.c
#include "ets_sys.h" #include "osapi.h" #include "noolite_platform.h" #include "driver/uart.h" extern int ets_uart_printf(const char *fmt, ...); void user_init(void) { // Инициализация UART на скорости 9600, т.к. модуль MT1132 работает только с такой скоростью uart_init(BIT_RATE_9600, BIT_RATE_9600); #ifdef NOOLITE_LOGGING ets_uart_printf("nooLite platform starting...\r\n"); #endif // Вызов основной процедуры запуска из noolite_platform.c noolite_platform_init(); #ifdef NOOLITE_LOGGING ets_uart_printf("nooLite platform started!\r\n"); #endif }
Файл noolite_platform.c
#include "ets_sys.h" #include "osapi.h" #include "user_interface.h" #include "mem.h" #include "espconn.h" #include "os_type.h" #include "driver/uart.h" #include "gpio.h" #include "flash_param.h" #include "noolite_platform.h" #include "noolite_config_server.h" #include "noolite_control_server.h" // Таймер для проверка состояния wi-fi соединения с AP os_timer_t WiFiLinker; // Таймер для устранения дребезга контактов os_timer_t DebounceTimer; static tConnState connState = WIFI_CONNECTING; uint16_t wifiErrorConnect = 0; uint16_t controlServerStatus = 0; LOCAL void input_intr_handler(void *arg); void ICACHE_FLASH_ATTR debounce_timer_cb(void *arg); // Процедура проверки состояния wi-fi соединения с AP static void ICACHE_FLASH_ATTR noolite_platform_check_ip(void *arg) { // Структура с инф. о конфигурации ip struct ip_info ipconfig; // Откл. таймер проверки os_timer_disarm(&WiFiLinker); // Получаем настройки IP wifi_get_ip_info(STATION_IF, &ipconfig); // Проверяем файт подключения к AP и назначения IP адреса // если соединение установлено, то запускаем web-сервер в основном режиме if (wifi_station_get_connect_status() == STATION_GOT_IP && ipconfig.ip.addr != 0) { connState = WIFI_CONNECTED; #ifdef NOOLITE_LOGGING char temp[80]; ets_uart_printf("WiFi connected\r\n"); os_sprintf(temp, "Client IP address: " IPSTR "\r\n", IP2STR(&ipconfig.ip)); ets_uart_printf(temp); os_sprintf(temp, "Client IP netmask: " IPSTR "\r\n", IP2STR(&ipconfig.netmask)); ets_uart_printf(temp); os_sprintf(temp, "Client IP gateway: " IPSTR "\r\n", IP2STR(&ipconfig.gw)); ets_uart_printf(temp); #endif wifiErrorConnect = 0; if(controlServerStatus == 0) { controlServerStatus++; #ifdef NOOLITE_LOGGING ets_uart_printf("Init noolite control server...\r\n"); #endif // Запуск web-сервера в основном режиме noolite_control_server_init(); } // Если нужно периодически проверять wi-fi соединение, то раскомментируйте строки ниже //os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); //os_timer_arm(&WiFiLinker, 10000, 0); } else { // Wi-Fi соединение не установлено, неправильный пароль, запускаем проверку подключения с интервалом 1,5 сек. if(wifi_station_get_connect_status() == STATION_WRONG_PASSWORD) { connState = WIFI_CONNECTING_ERROR; #ifdef PLATFORM_DEBUG ets_uart_printf("WiFi connecting error, wrong password\r\n"); #endif wifiErrorConnect++; os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); os_timer_arm(&WiFiLinker, 1500, 0); } // Wi-Fi соединение не установлено, не найдена AP, запускаем проверку подключения с интервалом 1 сек. else if(wifi_station_get_connect_status() == STATION_NO_AP_FOUND) { connState = WIFI_CONNECTING_ERROR; #ifdef PLATFORM_DEBUG ets_uart_printf("WiFi connecting error, ap not found\r\n"); #endif wifiErrorConnect++; os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); os_timer_arm(&WiFiLinker, 1000, 0); } // Wi-Fi соединение не установлено, ошибка, запускаем проверку подключения с интервалом 1 сек. else if(wifi_station_get_connect_status() == STATION_CONNECT_FAIL) { connState = WIFI_CONNECTING_ERROR; #ifdef PLATFORM_DEBUG ets_uart_printf("WiFi connecting fail\r\n"); #endif wifiErrorConnect++; os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); os_timer_arm(&WiFiLinker, 1000, 0); } // Установка Wi-Fi соединения, ждите... else { os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); os_timer_arm(&WiFiLinker, 1000, 0); connState = WIFI_CONNECTING; wifiErrorConnect++; #ifdef PLATFORM_DEBUG ets_uart_printf("WiFi connecting...\r\n"); #endif } controlServerStatus = 0; } } // Вход в режим конфигурации, осужествляется путем перевода ESP-01 в режим STATIONAP и стиранием конфигурации static void ICACHE_FLASH_ATTR noolite_platform_enter_configuration_mode(void) { wipe_flash_param(ESP_PARAM_SAVE_1); wifi_set_opmode(STATIONAP_MODE); #ifdef NOOLITE_LOGGING ets_uart_printf("Restarting in STATIONAP mode...\r\n"); #endif system_restart(); } // Инициализация кнопки CONFMODE (GPIO0) void BtnInit() { // Уст. GPIO 0 на ввод-вывод PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO0); // Вкл. подтяг. резистор PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDI_U); // Откл. глоб.прерываний ETS_GPIO_INTR_DISABLE(); // Подкл. процедуры обраб. прерываний ETS_GPIO_INTR_ATTACH(input_intr_handler, NULL); GPIO_DIS_OUTPUT(BTN_CONFIG_GPIO); // Очистка статуса GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(0)); // Вкл.прерываний gpio_pin_intr_state_set(GPIO_ID_PIN(BTN_CONFIG_GPIO), GPIO_PIN_INTR_NEGEDGE); // Вкл.глоб.прерываний ETS_GPIO_INTR_ENABLE(); // Таймер os_timer_disarm(&DebounceTimer); os_timer_setfn(&DebounceTimer, &debounce_timer_cb, 0); } // Процедура обраб. прерываний LOCAL void input_intr_handler(void *arg) { // Состояние GPIO uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); // Очистка GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); // Откл.прерываний ETS_GPIO_INTR_DISABLE(); gpio_pin_intr_state_set(GPIO_ID_PIN(BTN_CONFIG_GPIO), GPIO_PIN_INTR_DISABLE); // Вкл.прерываний ETS_GPIO_INTR_ENABLE(); // Установка таймера os_timer_arm(&DebounceTimer, 200, FALSE); } // Выполняется в случае нажатия кнопки CONFMODE void ICACHE_FLASH_ATTR debounce_timer_cb(void *arg) { ETS_GPIO_INTR_DISABLE(); gpio_pin_intr_state_set(GPIO_ID_PIN(BTN_CONFIG_GPIO), GPIO_PIN_INTR_NEGEDGE); ETS_GPIO_INTR_ENABLE(); #ifdef NOOLITE_LOGGING ets_uart_printf("Button CONFMODE pressed, wiping configuration and restart in configuration mode...\r\n"); #endif noolite_platform_enter_configuration_mode(); } // Инициализация void ICACHE_FLASH_ATTR noolite_platform_init(void) { // Структура для хранения конфигурации, размер структуры должен быть выровнен до 4 tSetup nooLiteSetup; // Загружаем настройки из flash load_flash_param(ESP_PARAM_SAVE_1, (uint32 *)&nooLiteSetup, sizeof(tSetup)); // Инициализация кнопки CONFMODE BtnInit(); // Если во flash не было сохранено настроек или плата не в режиме STA, то запускаем web-сервер конфигурации if(nooLiteSetup.SetupOk != SETUP_OK_KEY || wifi_get_opmode() != STATION_MODE) { // Чистим память wipe_flash_param(ESP_PARAM_SAVE_1); // Если плата не в режиме STA-AP, то уст. это режим if(wifi_get_opmode() != STATIONAP_MODE) { wifi_set_opmode(STATIONAP_MODE); #ifdef NOOLITE_LOGGING ets_uart_printf("Restarting in STATIONAP mode...\r\n"); #endif system_restart(); } char macaddr[6]; // Читаем MAC адрес интерфейса AP wifi_get_macaddr(SOFTAP_IF, macaddr); // Устанавливаем для AP платы настройки IP struct ip_info ipinfo; IP4_ADDR(&ipinfo.ip, 10, 10, 10, 1); IP4_ADDR(&ipinfo.gw, 10, 10, 10, 1); IP4_ADDR(&ipinfo.netmask, 255, 255, 255, 0); wifi_set_ip_info(SOFTAP_IF, &ipinfo); // Устанавливаем настройки AP платы struct softap_config apConfig; os_memset(apConfig.ssid, 0, sizeof(apConfig.ssid)); os_sprintf(apConfig.ssid, "NOOLITE_%02x%02x%02x%02x%02x%02x", MAC2STR(macaddr)); os_memset(apConfig.password, 0, sizeof(apConfig.password)); os_sprintf(apConfig.password, "%02x%02x%02x%02x%02x%02x", MAC2STR(macaddr)); apConfig.authmode = AUTH_WPA_WPA2_PSK; apConfig.channel = 7; apConfig.max_connection = 255; apConfig.ssid_hidden = 0; wifi_softap_set_config(&apConfig); // Вывод настроек, если включен режим отладки, см.noolite_platform.h #ifdef NOOLITE_LOGGING char temp[80]; os_sprintf(temp, "OPMODE: %u\r\n", wifi_get_opmode()); ets_uart_printf(temp); os_sprintf(temp, "SSID: %s\r\n", apConfig.ssid); ets_uart_printf(temp); os_sprintf(temp, "PASSWORD: %s\r\n", apConfig.password); ets_uart_printf(temp); os_sprintf(temp, "CHANNEL: %u\r\n", apConfig.channel); ets_uart_printf(temp); os_sprintf(temp, "CONFIGURATION WEB SERVER IP: " IPSTR "\r\n", IP2STR(&ipinfo.ip)); ets_uart_printf(temp); ets_uart_printf("Starting nooLite configuration web server...\r\n"); #endif // Запуск сервера конфигурации noolite_config_server_init(); } else // Обычный режим работы платы { #ifdef NOOLITE_LOGGING ets_uart_printf("Starting in normal mode...\r\n"); #endif // Переводим платы в режим STA if(wifi_get_opmode() != STATION_MODE) { #ifdef NOOLITE_LOGGING ets_uart_printf("Restarting in STATION mode...\r\n"); #endif wifi_set_opmode(STATION_MODE); system_restart(); } // Вывод отлад. инф о режиме STA, в том числе клиентского MAC адреса #ifdef NOOLITE_LOGGING char temp[80]; uint8 bssid[6]; struct station_config stationConf; wifi_station_get_config(&stationConf); os_sprintf(temp, "OPMODE: %u\r\n", wifi_get_opmode()); ets_uart_printf(temp); os_sprintf(temp, "AP SSID: %s\r\n", stationConf.ssid); ets_uart_printf(temp); os_sprintf(temp, "AP PASSWORD: %s\r\n", stationConf.password); ets_uart_printf(temp); wifi_get_macaddr(STATION_IF, bssid); os_sprintf(temp, "STA MACADDR: " MACSTR "\r\n", MAC2STR(bssid)); ets_uart_printf(temp); #endif #ifdef NOOLITE_LOGGING ets_uart_printf("System initialization done!\r\n"); #endif // Запуск таймера проверки состояния wi-fi соединения с AP os_timer_disarm(&WiFiLinker); os_timer_setfn(&WiFiLinker, (os_timer_func_t *)noolite_platform_check_ip, NULL); os_timer_arm(&WiFiLinker, 100, 0); } }
Описывать файл noolite_config_server.c я не вижу смысла, в нем идет запуск web-сервера в режиме конфигурации, по принципу работы он аналогичен работе в обычном режиме, который мы и рассмотрим ниже.
Реализация web-сервера очень простая и не претендует на гибкость и функциональность, и более того, реализовавать web-сервер на ESP8266 не совсем правильно, более правильный подход — это организация на ESP8266 TCP-клиента, который будет держать соединение с сервером управления, а на сервере управления уже организовать полноценный интерфейс взаимодействия (RESTfull или JSON) с внешней средой, под внешней средой я подразумеваею Web-интерфейс, разного рода мобильные и стационарные клиенты (Windows, Android, iOS и т.п.)
Внешний вид страниц web-интерфейса получился такой, интерфейс очень простой:
Режим конфигурации, основная страница

При нажатии на Return to normal mode, происходит возврат платы к нормальному режиму.
Режим конфигурации, страница настройки WiFi соединения

Режим конфигурации, страница настройки deviveID

Основной режим, главная страница

Страница deviceID предназначена для получения номера нашего устройства для каких-то вспомогательных задач, она просто выдает номер без какого-либо оформления
Основной режим, страница deviceID

Основной режим, страница привязки-отвязки силовых блоков

Основной режим, страница управления силовыми блоками

Реализация всего этого в коде:
Файл noolite_control_server.c
#include "ets_sys.h" #include "osapi.h" #include "user_interface.h" #include "mem.h" #include "espconn.h" #include "driver/uart.h" #include "noolite_control_server.h" #include "flash_param.h" #include "noolite_platform.h" static struct espconn esp_conn; static esp_tcp esptcp; static unsigned char killConn; // http заголовок 404 static const char *http404Header = "HTTP/1.0 404 Not Found\r\nServer: nooLite-Control-Server\r\nContent-Type: text/plain\r\n\r\nNot Found (or method not implemented).\r\n"; // http заголовок 200 static const char *http200Header = "HTTP/1.0 200 OK\r\nServer: nooLite-Control-Server/0.1\r\nContent-Type: text/html\r\n"; // html страница, верхняя часть static const char *pageStart = "<html><head><title>nooLite Control Panel</title><style>body{font-family: Arial}</style></head><body><form method=\"get\" action=\"/\"><input type=\"hidden\" name=\"set\" value=\"1\">\r\n"; // html страница, нижняя часть static const char *pageEnd = "</form><hr>(c) 2014 by <a href=\"mailto:sleuthhound@gmail.com\" target=\"_blank\">Mikhail Grigorev</a>, <a href=\"http://programs74.ru\" target=\"_blank\">programs74.ru</a>\r\n</body></html>\r\n"; // основная html страница static const char *pageIndex = "<h2>Welcome to nooLite Control Panel</h2>nooLite Control deviceID: {deviceid}<ul><li><a href=\"?page=devid\">Get deviceID</a></li><li><a href=\"?page=bind\">nooLite bind-unbind channel</a></li><li><a href=\"?page=control\">nooLite control</a></li></ul>\r\n"; // html страница привязки каналов к силовым блокам static const char *pageSetBindChannel = "<h2><a href=\"/\">Home</a> / nooLite bind-unbind channel</h2><input type=\"hidden\" name=\"page\" value=\"bind\"><table border=\"0\"><tr><td><b>Channel #:</b></td><td><input type=\"text\" name=\"channel\" value=\"{channel}\" size=\"5\"> 0 .. 32</td></tr></tr><tr><td><b>Status:</b></td><td>{status}</td></tr><tr><td></td><td><input type=\"submit\" name = \"action\" value=\"Bind\"><input type=\"submit\" name = \"action\" value=\"Unbind\"></td></tr></table>\r\n"; // html страница управления силовыми блоками static const char *pageSetNooliteControl = "<h2><a href=\"/\">Home</a> / nooLite control</h2><input type=\"hidden\" name=\"page\" value=\"control\"><table border=\"0\"><tr><td><b>Channel #:</b></td><td><input type=\"text\" name=\"channel\" value=\"{channel}\" size=\"5\"> 0 .. 32</td></tr><tr><td><b>Status:</b></td><td>{status}</td></tr><tr><td></td><td><input type=\"submit\" name = \"action\" value=\"On\"><input type=\"submit\" name = \"action\" value=\"Off\"></td></tr></table>\r\n"; static const char *pageCommandSent = "<br><b style=\"color: green\">Command sent!</b>\r\n"; // html страница ID устройства, простая как угол дома static const char *pageDevID = "{deviceid}"; int recvOK = 0; static void recvTask(os_event_t *events); // Функция отправки данных по UART модулю nooLite MT1132 // Описание протокола обмена можно найти здесь http://www.noo.com.by/assets/files/PDF/MT1132.pdf static int ICACHE_FLASH_ATTR noolite_sendCommand(unsigned char channel, unsigned char command, unsigned char data, unsigned char format) { unsigned char buf[12]; unsigned int i; int checkSum = 0; recvOK = 0; os_memset(buf, 0, 12); buf[0] = 85; buf[1] = 80; buf[2] = command; buf[3] = format; buf[5] = channel; buf[6] = data; for(i = 0;i<10;i++){ checkSum+= buf[i]; } buf[10] = lowByte(checkSum); buf[11] = 170; uart0_tx_buffer(&buf, 12); sleepms(300); return recvOK; } #ifdef NOOLITE_LOGGING static void ICACHE_FLASH_ATTR noolite_control_server_recon(void *arg, sint8 err) { ets_uart_printf("noolite_control_server_recon\r\n"); } #endif #ifdef NOOLITE_LOGGING static void ICACHE_FLASH_ATTR noolite_control_server_discon(void *arg) { ets_uart_printf("noolite_control_server_discon\r\n"); } #endif // Получение данных static void ICACHE_FLASH_ATTR noolite_control_server_recv(void *arg, char *data, unsigned short len) { struct espconn *ptrespconn = (struct espconn *)arg; // Принимаем только GET запросы if(os_strncmp(data, "GET ", 4) == 0) { char page[16]; os_memset(page, 0, sizeof(page)); // Парсим GET запрос в поисках параметра page noolite_control_server_get_key_val("page", sizeof(page), data, page); // Если page=devid, то отдаем страницу с device id if(os_strncmp(page, "devid", 5) == 0 && strlen(page) == 5) { noolite_control_server_deviceid_page(ptrespconn, page, data); } else { // Иначе другие страницы noolite_control_server_process_page(ptrespconn, page, data); } return; } else // Если пришел другой тип запроса, выдаем страницу http404Header { espconn_sent(ptrespconn, (uint8 *)http404Header, os_strlen(http404Header)); killConn = 1; return; } } // Процедура отдачи страницы с device id static void ICACHE_FLASH_ATTR noolite_control_server_deviceid_page(struct espconn *ptrespconn, char *page, char *request) { // Читаем параметры из flash tSetup nooliteSetup; load_flash_param(ESP_PARAM_SAVE_1, (uint32 *)&nooliteSetup, sizeof(tSetup)); // Парсинг pageDevID в поисках конструкции вида {переменная}, эта конструкция заменяется на нужное значение и отсылается клиенту char *stream = (char *)pageDevID; char templateKey[16]; os_memset(templateKey, 0, sizeof(templateKey)); unsigned char templateKeyIdx; while(*stream) { if(*stream == '{') { templateKeyIdx = 0; stream++; while(*stream != '}') { templateKey[templateKeyIdx++] = *stream; stream++; } // подмена ключа {deviceid} на значение device id и отправка его клиенту if(os_strncmp(templateKey, "deviceid", 6) == 0) { char deviceid[3]; char i; for(i=0; i<16; i++) { os_sprintf(deviceid, "%02x", nooliteSetup.deviceId[i]); espconn_sent(ptrespconn, (uint8 *)deviceid, os_strlen(deviceid)); } } } else { espconn_sent(ptrespconn, (uint8 *)stream, 1); } stream++; } killConn = 1; } // Процедура отдачи других страниц static void ICACHE_FLASH_ATTR noolite_control_server_process_page(struct espconn *ptrespconn, char *page, char *request) { // Отправляем http200Header espconn_sent(ptrespconn, (uint8 *)http200Header, os_strlen(http200Header)); espconn_sent(ptrespconn, (uint8 *)"\r\n", 2); // Отправляем pageStart espconn_sent(ptrespconn, (uint8 *)pageStart, os_strlen(pageStart)); // подготовка данных char set[2] = {'0', '\0'}; noolite_control_server_get_key_val("set", sizeof(set), request, set); char channel_num[2] = "0"; char status[32] = "[status]"; char action[10] = "unbind"; os_memset(channel_num, 0, sizeof(channel_num)); os_memset(status, 0, sizeof(status)); os_memset(action, 0, sizeof(action)); // Отдача страницы pageSetBindChannel if(os_strncmp(page, "bind", 4) == 0 && strlen(page) == 4) { // Проверка значения переменной set в форме на странице pageSetBindChannel // Если значение 1, то подразумевается, что форма с данными о канале и операции привязки или отвязки отправлена, нужно прочитать данные и распарсить if(set[0] == '1') { // Парсим данные формы, переменные канала и действия (привязка или отвязка) noolite_control_server_get_key_val("channel", sizeof(channel_num), request, channel_num); noolite_control_server_get_key_val("action", sizeof(action), request, action); // Отправка команды модуля MT1132 // noolite_sendCommand(channel, command, data, format) // channel = 0 .. 32 // command = 15 - bind // command = 9 - unbind // command = 2 - on // command = 0 - off // После успешной отправки данных силовым блокам, модуль MT1132 должен дать ответ OK по UART // Прием и парсинг данных от модуля идет в uart.c, в основной программе мы запускаем системную задачу (см. в конце вызов system_os_task), которой из uart.c отправляется сигнал 0 // Проверяем номер канала, правильный от 0 до 32 if(atoi(channel_num) >= 0 && atoi(channel_num) <= 32) { // Привязка if(os_strncmp(action, "Bind", 4) == 0 && strlen(action) == 4) { if(noolite_sendCommand(atoi(channel_num), 15, 0, 0)) { os_sprintf(status, "Bind OK"); } else { os_sprintf(status, "Bind ERR"); } // Отвязка } else if(os_strncmp(action, "Unbind", 6) == 0 && strlen(action) == 6) { if(noolite_sendCommand(atoi(channel_num), 9, 0, 0)) { os_sprintf(status, "Unbind OK"); } else { os_sprintf(status, "Unbind ERR"); } } else { os_sprintf(status, "Err"); } } else { os_sprintf(status, "Err"); } } // Подготовка к отправке страницы char *stream = (char *)pageSetBindChannel; char templateKey[16]; os_memset(templateKey, 0, sizeof(templateKey)); unsigned char templateKeyIdx; while(*stream) { if(*stream == '{') { templateKeyIdx = 0; stream++; while(*stream != '}') { templateKey[templateKeyIdx++] = *stream; stream++; } // Замена конструкйи вида {переменная} на нужные значения if(os_strncmp(templateKey, "channel", 7) == 0) { if(strlen(channel_num) == 0) os_sprintf(channel_num, "0"); espconn_sent(ptrespconn, (uint8 *)channel_num, os_strlen(channel_num)); } else if(os_strncmp(templateKey, "status", 6) == 0) { if(strlen(status) == 0) { os_sprintf(status, "[status]"); } espconn_sent(ptrespconn, (uint8 *)status, os_strlen(status)); } } else { espconn_sent(ptrespconn, (uint8 *)stream, 1); } stream++; } // Отправка pageCommandSent if(set[0] == '1') { espconn_sent(ptrespconn, (uint8 *)pageCommandSent, os_strlen(pageCommandSent)); } } // Отдача страницы pageSetNooliteControl, логмка аналогична как и выше else if(os_strncmp(page, "control", 7) == 0 && strlen(page) == 7) { if(set[0] == '1') { noolite_control_server_get_key_val("channel", sizeof(channel_num), request, channel_num); noolite_control_server_get_key_val("action", sizeof(action), request, action); // noolite_sendCommand(channel, command, data, format) // channel = 0 .. 32 // command = 15 - bind // command = 9 - unbind // command = 2 - on // command = 0 - off if(atoi(channel_num) >= 0 && atoi(channel_num) <= 32) { if(os_strncmp(action, "On", 2) == 0 && strlen(action) == 2) { if(noolite_sendCommand(atoi(channel_num), 2, 0, 0)) { os_sprintf(status, "On"); } else { os_sprintf(status, "On ERR"); } } else if(os_strncmp(action, "Off", 3) == 0 && strlen(action) == 3) { if (noolite_sendCommand(atoi(channel_num), 0, 0, 0)) { os_sprintf(status, "Off"); } else { os_sprintf(status, "Off ERR"); } } else { os_sprintf(status, "Err"); } } else { os_sprintf(status, "Err"); } } char *stream = (char *)pageSetNooliteControl; char templateKey[16]; os_memset(templateKey, 0, sizeof(templateKey)); unsigned char templateKeyIdx; while(*stream) { if(*stream == '{') { templateKeyIdx = 0; stream++; while(*stream != '}') { templateKey[templateKeyIdx++] = *stream; stream++; } // send the replacing value now if(os_strncmp(templateKey, "channel", 7) == 0) { if(strlen(channel_num) == 0 ) os_sprintf(channel_num, "0"); espconn_sent(ptrespconn, (uint8 *)channel_num, os_strlen(channel_num)); } else if(os_strncmp(templateKey, "status", 6) == 0) { if(strlen(status) == 0) { os_sprintf(status, "[status]"); } espconn_sent(ptrespconn, (uint8 *)status, os_strlen(status)); } } else { espconn_sent(ptrespconn, (uint8 *)stream, 1); } stream++; } if(set[0] == '1') { espconn_sent(ptrespconn, (uint8 *)pageCommandSent, os_strlen(pageCommandSent)); } } else // отдача главной страницы pageIndex с device id { tSetup nooliteSetup; load_flash_param(ESP_PARAM_SAVE_1, (uint32 *)&nooliteSetup, sizeof(tSetup)); char *stream = (char *)pageIndex; char templateKey[16]; os_memset(templateKey, 0, sizeof(templateKey)); unsigned char templateKeyIdx; while(*stream) { if(*stream == '{') { templateKeyIdx = 0; stream++; while(*stream != '}') { templateKey[templateKeyIdx++] = *stream; stream++; } // замена {deviceid} if(os_strncmp(templateKey, "deviceid", 6) == 0) { char deviceid[3]; char i; for(i=0; i<16; i++) { os_sprintf(deviceid, "%02x", nooliteSetup.deviceId[i]); espconn_sent(ptrespconn, (uint8 *)deviceid, os_strlen(deviceid)); } } } else { espconn_sent(ptrespconn, (uint8 *)stream, 1); } stream++; } } // отдача pageEnd espconn_sent(ptrespconn, (uint8 *)pageEnd, os_strlen(pageEnd)); killConn = 1; } // Процедура поиска и извлечения из запроса конструкций вида key=value static unsigned char ICACHE_FLASH_ATTR noolite_control_server_get_key_val(char *key, unsigned char maxlen, char *str, char *retval) { unsigned char found = 0; char *keyptr = key; char prev_char = '\0'; *retval = '\0'; while( *str && *str!='\r' && *str!='\n' && !found ) { if(*str == *keyptr) { if(keyptr == key && !( prev_char == '?' || prev_char == '&' ) ) { str++; continue; } keyptr++; if (*keyptr == '\0') { str++; keyptr = key; if (*str == '=') { found = 1; } } } else { keyptr = key; } prev_char = *str; str++; } if(found == 1) { found = 0; while( *str && *str!='\r' && *str!='\n' && *str!=' ' && *str!='&' && maxlen>0 ) { *retval = *str; maxlen--; str++; retval++; found++; } *retval = '\0'; } return found; } // callback процедура, выполняется после отправки данных static void ICACHE_FLASH_ATTR noolite_control_server_sent(void *arg) { struct espconn *pesp_conn = (struct espconn *)arg; if (pesp_conn == NULL) { return; } if(killConn) { espconn_disconnect(pesp_conn); } } // callback процедура, выполняется после установки соединения static void ICACHE_FLASH_ATTR noolite_control_server_connect(void *arg) { struct espconn *pesp_conn = (struct espconn *)arg; #ifdef NOOLITE_LOGGING ets_uart_printf("noolite_control_server_connect\r\n"); #endif // регистрируем различные callback процедуры espconn_regist_recvcb(pesp_conn, noolite_control_server_recv); espconn_regist_sentcb(pesp_conn, noolite_control_server_sent); #ifdef NOOLITE_LOGGING espconn_regist_reconcb(pesp_conn, noolite_control_server_recon); espconn_regist_disconcb(pesp_conn, noolite_control_server_discon); #endif } // Инициализация и запуск we-сервера void ICACHE_FLASH_ATTR noolite_control_server_init() { #ifdef NOOLITE_LOGGING ets_uart_printf("noolite_control_server_init()\r\n"); #endif esptcp.local_port = 80; esp_conn.type = ESPCONN_TCP; esp_conn.state = ESPCONN_NONE; esp_conn.proto.tcp = &esptcp; // регистрируем callback процедуру espconn_regist_connectcb(&esp_conn, noolite_control_server_connect); espconn_accept(&esp_conn); // Запуск системной задачи для обработки приема данных по UART // В uart.c при приеме данных осуществляется парсинг и поиск наличия ответа от платы MT1132 (см. uart0_rx_intr_handler) // Если ответ OK, то отправляется сигнал 0 с помощью функции system_os_post(recvTaskPrio, 0, atHead); os_event_t *recvTaskQueue = (os_event_t *)os_malloc(sizeof(os_event_t) * recvTaskQueueLen); system_os_task(recvTask, recvTaskPrio, recvTaskQueue, recvTaskQueueLen); } // Процедура приема сигнала static void ICACHE_FLASH_ATTR recvTask(os_event_t *events) { switch (events->sig) { case 0: // Если принят сигнал 0, то устанавливаем флаг приема ответа от платы recvOK = 1 #ifdef NOOLITE_LOGGING ets_uart_printf("nooLite MT1132 sent OK\r\n"); #endif recvOK = 1; break; default: break; } }
И в заключении небольшое видео с демонстрацией работы платы:
Демонстрация работы Espressif ESP8266 и модуля nooLite MT1132
В целом, мы в очередной раз видим, что возможности платы на чипе ESP8266 достаточно велики.
В заключении я бы хотел поблагодарить компанию Ноотехника, а так же администрацию сайта Thinking-Home.RU за предоставленное для тестирования оборудование nooLite.
Репозитарий проекта на Github
P.S. Если у кого-то будут пожелания относительно следующей статьи про ESP8266, то я готов их выслушать. Можно написать про работу с различными сенсорами: DS18B20, DHT22, SHT21, BMP180, ИК-приемник/передатчик, внешние мс памяти, например серия Microchip 24xx16 и т.д.
