Как стать автором
Обновить

Работа с ESP8266: Пишем прошивку для управления системой nooLite

Время на прочтение20 мин
Количество просмотров60K
В прошлых статьях мы рассмотрели работу с 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;

Разберем каждый файл по порядку, под спулером будет много коду с комментариями:

Файл 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 и т.д.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 12: ↑12 и ↓0+12
Комментарии12

Публикации

Истории

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань