Version 1.0.0

Initial version.
This commit is contained in:
Alexey Zholtikov 2023-11-25 12:17:39 +03:00
parent b2de9cc36b
commit 0d3ef44966
15 changed files with 483 additions and 2 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode
.DS_Store
build
sdkconfig
sdkconfig.old

3
CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(zh_espnow_open_sensor_esp32)

View File

@ -1,3 +1,34 @@
# zh_espnow_open_sensor
# ESP-NOW open/close sensor
ESP-NOW based open/close sensor for ESP32 ESP-IDF and ESP8266 RTOS SDK.
ESP-NOW based open/close sensor for ESP32 ESP-IDF. Alternate firmware for Tuya/SmartLife/eWeLink WiFi sensors.
There are two branches - for ESP8266 family and for ESP32 family. Please use the appropriate one.
## Features
1. Automatically adds sensor configuration to Home Assistan via MQTT discovery as a binary_sensor.
2. Update firmware from HTTPS server via ESP-NOW.
## Notes
1. For initial settings use "menuconfig -> ZH ESP-NOW Open Sensor Configuration". After first boot all settings will be stored in NVS memory for prevente change during OTA firmware update.
2. To update the sensor firmware, press the button for > 5 seconds and send the "update" command to the root topic of the sensor (example - "homeassistant/espnow_sensor/70-03-9F-44-BE-F7"). The update path should be like as "https://your_server/zh_espnow_open_sensor_esp32.bin". The time and success of the update depends on the load on WiFi channel 1. Average update time is less than one minute. The online status of the update is displayed in the root sensor topic.
## Build and flash
Run the following command to firmware build and flash module:
```text
cd your_projects_folder
bash <(curl -Ls http://git.zh.com.ru/alexey.zholtikov/zh_espnow_open_sensor/raw/branch/esp32/install.sh)
cd zh_espnow_open_sensor
idf.py menuconfig
idf.py all
idf.py -p (PORT) flash
```
## Attention
1. A gateway is required. For details see [zh_gateway](http://git.zh.com.ru/alexey.zholtikov/zh_gateway).
2. Highly recommended connect an external power supply during firmware update.
3. Because this sensor is battery operated, it has an additional controller (MCU) that controls the power of the WiFi module (Module) and transmits data to it for transmission to the network. The communication is done via UART at 9600 speed. Make sure that the protocol is correct before flashing. Details [here](https://github.com/aZholtikov/ESP-NOW-Window-Door-Sensor/tree/main/doc).

45
doc/README.md Normal file
View File

@ -0,0 +1,45 @@
# Communication protocol
The protocol can be read with 2 USB-TTL converters. RX of the first one is connected to the RX of the WiFi module to read the MCU data. Connect the RX of the second to the TX of the WiFi module to read the module's data. Don't forget to connect all GND (of both converters and module).
Communication protocol used in the firmware (only necessary "cuts" from the original protocol):
```text
1. Normal mode. Sensor triggering.
Module power is on
Module sends 55 AA 00 01 00 00 00 (Initial message)
MCU returns 55 AA 00 01 00 ............ (MCU system information)
Module sends 55 AA 00 02 00 01 04 06 (Network connection established)
MCU returns 55 AA 00 02 00 00 01 (MCU confirmation message)
MCU sends 55 AA 00 08 00 0C 00 01 01 01 01 01 01 03 04 00 01 02 23 (Battery status. 02 23 - high, 01 22 - medium, 00 21 - low)
Module returns 55 AA 00 08 00 01 00 08 (Confirmation message)
MCU sends 55 AA 00 08 00 0C 00 02 02 02 02 02 02 01 01 00 01 00 22 (Sensor position. 01 23 - open, 00 22 - closed)
Module returns 55 AA 00 08 00 01 00 08 (Confirmation message)
Module power off
2. Sending the battery status. Pressing the button.
Module power is on
Module sends 55 AA 00 01 00 00 00 (Initial message)
MCU returns 55 AA 00 01 00 00 ............ (MCU system information)
Module sends 55 AA 00 02 00 01 04 06 (Network connection established)
MCU returns 55 AA 00 02 00 00 01 (MCU confirmation message)
MCU sends 55 AA 00 08 00 0C 00 01 01 01 01 01 01 03 04 00 01 02 23 (Battery status. 02 23 - high, 01 22 - medium, 00 21 - low)
Module returns 55 AA 00 08 00 01 00 08 (Confirmation message)
Module power off
3. Update mode. Pressing the button for > 5 seconds - the LED light on.
Module power on
Module sends 55 AA 00 01 00 00 00 (Initial message)
MCU returns 55 AA 00 01 00 00 ............ (MCU system information)
Module sends 55 AA 00 02 00 01 04 06 (Network connection established)
MCU returns 55 AA 00 02 00 00 01 (MCU confirmation message)
MCU sends 55 AA 00 03 00 00 02 (Message for switching to setting mode)
Module returns 55 AA 00 03 00 00 02 (Confirmation message)
Update mode has started. Will be available during 120 seconds until the module is powered off.
After updating and rebooting the module will return to normal mode - the LED will go off.
Highly recommended connect an external power supply during update.
```

Binary file not shown.

Binary file not shown.

7
install.sh Normal file
View File

@ -0,0 +1,7 @@
git clone -b esp32 --recursive http://git.zh.com.ru/alexey.zholtikov/zh_espnow_open_sensor.git
cd zh_espnow_open_sensor
mkdir components
cd components
git clone http://git.zh.com.ru/alexey.zholtikov/zh_config.git
git clone -b esp32 --recursive http://git.zh.com.ru/alexey.zholtikov/zh_espnow.git
cd ..

3
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
idf_build_get_property(project_dir PROJECT_DIR)
idf_component_register(SRCS "zh_espnow_open_sensor.c"
INCLUDE_DIRS ".")

15
main/Kconfig.projbuild Normal file
View File

@ -0,0 +1,15 @@
menu "ZH ESP-NOW Open Sensor Configuration"
choice SENSOR_TYPE
prompt "Sensor type"
help
Sensor type.
default SENSOR_TYPE_WINDOW
config SENSOR_TYPE_WINDOW
bool "WINDOW"
config SENSOR_TYPE_DOOR
bool "DOOR"
endchoice
endmenu

View File

@ -0,0 +1,346 @@
#include "stdio.h"
#include "string.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "driver/uart.h"
#include "esp_timer.h"
#include "esp_ota_ops.h"
#include "zh_espnow.h"
#include "zh_config.h"
#define ZH_UART_TASK_PRIORITY 6
#define ZH_UART_STACK_SIZE 2048
#define ZH_UART_BUFF_SIZE 1024
#define ZH_UART_QUEUE_SIZE 10
#define ZH_UART_NUM 0
static uint8_t s_sensor_type = HAST_NONE;
static uint8_t s_open_status = HAONOFT_NONE;
static uint8_t s_battery_status = HAONOFT_NONE;
static uint8_t s_gateway_mac[6] = {0};
static bool s_gateway_is_available = false;
static const esp_partition_t *s_update_partition = NULL;
static esp_ota_handle_t s_update_handle = 0;
static uint16_t s_ota_message_part_number = 0;
static QueueHandle_t s_zh_uart_queue = {0};
static const char s_initial_message[] = {0x55, 0xAA, 0x00, 0x01, 0x00, 0x00, 0x00};
static const char s_connected_message[] = {0x55, 0xAA, 0x00, 0x02, 0x00, 0x01, 0x04, 0x06};
static const char s_setting_message[] = {0x55, 0xAA, 0x00, 0x03, 0x00, 0x00, 0x02};
static const char s_confirmation_message[] = {0x55, 0xAA, 0x00, 0x08, 0x00, 0x01, 0x00, 0x08};
static void s_zh_uart_processing_task(void *pvParameter);
static void s_zh_load_config(void);
static void s_zh_save_config(void);
static void s_zh_load_status(void);
static void s_zh_save_status(void);
static void s_zh_send_sensor_attributes_message(void);
static void s_zh_send_sensor_config_message(void);
static void s_zh_send_sensor_status_message(void);
static void s_zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
void app_main(void)
{
#if CONFIG_SENSOR_TYPE_WINDOW
s_sensor_type = HAST_WINDOW;
#elif CONFIG_SENSOR_TYPE_DOOR
s_sensor_type = HAST_DOOR;
#endif
const esp_partition_t *running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state = {0};
esp_ota_get_state_partition(running, &ota_state);
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
s_zh_load_config();
s_zh_load_status();
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&wifi_init_config);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B);
zh_espnow_init_config_t zh_espnow_init_config = ZH_ESPNOW_INIT_CONFIG_DEFAULT();
zh_espnow_init(&zh_espnow_init_config);
esp_event_handler_instance_register(ZH_ESPNOW, ESP_EVENT_ANY_ID, &s_zh_espnow_event_handler, NULL, NULL);
xTaskCreatePinnedToCore(&s_zh_uart_processing_task, "s_zh_uart_processing_tack", ZH_UART_STACK_SIZE, NULL, ZH_UART_TASK_PRIORITY, NULL, tskNO_AFFINITY);
s_zh_send_sensor_config_message();
s_zh_send_sensor_attributes_message();
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
esp_ota_mark_app_valid_cancel_rollback();
}
}
static void s_zh_uart_processing_task(void *pvParameter)
{
uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
uart_driver_install(ZH_UART_NUM, ZH_UART_BUFF_SIZE, ZH_UART_BUFF_SIZE, ZH_UART_QUEUE_SIZE, &s_zh_uart_queue, 0);
uart_param_config(ZH_UART_NUM, &uart_config);
uart_set_pin(ZH_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uint8_t *data = (uint8_t *)calloc(1, ZH_UART_BUFF_SIZE);
uart_event_t event = {0};
uart_write_bytes(ZH_UART_NUM, &s_initial_message, sizeof(s_initial_message));
for (;;)
{
if (xQueueReceive(s_zh_uart_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
switch (event.type)
{
case UART_DATA:
uart_read_bytes(ZH_UART_NUM, data, event.size, portMAX_DELAY);
if (data[0] == 0x55 && data[3] == 0x01) // MCU system information.
{
uart_write_bytes(ZH_UART_NUM, &s_connected_message, sizeof(s_connected_message));
}
if (data[0] == 0x55 && data[3] == 0x03) // Message for switching to setting mode.
{
uart_write_bytes(ZH_UART_NUM, &s_setting_message, sizeof(s_setting_message));
}
if (data[0] == 0x55 && data[3] == 0x08) // Sensor status message.
{
if (data[7] == 0x01) // Battery status.
{
if (data[17] == 0x02 || data[17] == 0x01)
{
s_battery_status = HAONOFT_HIGH;
}
else
{
s_battery_status = HAONOFT_LOW;
}
s_zh_save_status();
s_zh_send_sensor_status_message();
uart_write_bytes(ZH_UART_NUM, &s_confirmation_message, sizeof(s_confirmation_message));
}
if (data[7] == 0x02) // Sensor position.
{
if (data[17] == 0x01)
{
s_open_status = HAONOFT_OPEN;
}
else
{
s_open_status = HAONOFT_CLOSE;
}
s_zh_save_status();
s_zh_send_sensor_status_message();
uart_write_bytes(ZH_UART_NUM, &s_confirmation_message, sizeof(s_confirmation_message));
}
}
break;
default:
break;
}
}
}
free(data);
vTaskDelete(NULL);
}
static void s_zh_load_config(void)
{
nvs_handle_t nvs_handle = 0;
nvs_open("config", NVS_READWRITE, &nvs_handle);
uint8_t config_is_present = 0;
if (nvs_get_u8(nvs_handle, "present", &config_is_present) == ESP_ERR_NVS_NOT_FOUND)
{
nvs_set_u8(nvs_handle, "present", 0xFE);
nvs_close(nvs_handle);
s_zh_save_config();
return;
}
nvs_get_u8(nvs_handle, "sensor_type", &s_sensor_type);
nvs_close(nvs_handle);
}
static void s_zh_save_config(void)
{
nvs_handle_t nvs_handle = 0;
nvs_open("config", NVS_READWRITE, &nvs_handle);
nvs_set_u8(nvs_handle, "sensor_type", s_sensor_type);
nvs_close(nvs_handle);
}
static void s_zh_load_status(void)
{
nvs_handle_t nvs_handle = 0;
nvs_open("status", NVS_READWRITE, &nvs_handle);
uint8_t status_is_present = 0;
if (nvs_get_u8(nvs_handle, "present", &status_is_present) == ESP_ERR_NVS_NOT_FOUND)
{
nvs_set_u8(nvs_handle, "present", 0xFE);
nvs_close(nvs_handle);
s_zh_save_status();
return;
}
nvs_get_u8(nvs_handle, "open_state", &s_open_status);
nvs_get_u8(nvs_handle, "battery_state", &s_battery_status);
nvs_close(nvs_handle);
}
static void s_zh_save_status(void)
{
nvs_handle_t nvs_handle = 0;
nvs_open("status", NVS_READWRITE, &nvs_handle);
nvs_set_u8(nvs_handle, "open_state", s_open_status);
nvs_set_u8(nvs_handle, "battery_state", s_battery_status);
nvs_close(nvs_handle);
}
static void s_zh_send_sensor_attributes_message(void)
{
const esp_app_desc_t *app_info = esp_app_get_description();
zh_attributes_message_t attributes_message = {0};
attributes_message.chip_type = HACHT_ESP32;
strcpy(attributes_message.flash_size, CONFIG_ESPTOOLPY_FLASHSIZE);
attributes_message.cpu_frequency = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
attributes_message.reset_reason = (uint8_t)esp_reset_reason();
strcpy(attributes_message.app_name, app_info->project_name);
strcpy(attributes_message.app_version, app_info->version);
zh_espnow_data_t data = {0};
data.device_type = ZHDT_BINARY_SENSOR;
data.payload_type = ZHPT_ATTRIBUTES;
attributes_message.heap_size = esp_get_free_heap_size();
attributes_message.min_heap_size = esp_get_minimum_free_heap_size();
attributes_message.uptime = esp_timer_get_time() / 1000000;
data.payload_data = (zh_payload_data_t)attributes_message;
zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}
static void s_zh_send_sensor_config_message(void)
{
zh_binary_sensor_config_message_t binary_sensor_config_message = {0};
binary_sensor_config_message.unique_id = 1;
binary_sensor_config_message.binary_sensor_device_class = (s_sensor_type == HAST_WINDOW) ? HABSDC_WINDOW : HABSDC_DOOR;
binary_sensor_config_message.payload_on = HAONOFT_OPEN;
binary_sensor_config_message.payload_off = HAONOFT_CLOSE;
binary_sensor_config_message.enabled_by_default = true;
binary_sensor_config_message.force_update = true;
binary_sensor_config_message.qos = 2;
binary_sensor_config_message.retain = true;
zh_config_message_t config_message = {0};
config_message = (zh_config_message_t)binary_sensor_config_message;
zh_espnow_data_t data = {0};
data.device_type = ZHDT_BINARY_SENSOR;
data.payload_type = ZHPT_CONFIG;
data.payload_data = (zh_payload_data_t)config_message;
zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
binary_sensor_config_message.unique_id = 2;
binary_sensor_config_message.binary_sensor_device_class = HABSDC_BATTERY;
binary_sensor_config_message.payload_on = HAONOFT_LOW;
binary_sensor_config_message.payload_off = HAONOFT_HIGH;
config_message = (zh_config_message_t)binary_sensor_config_message;
data.payload_data = (zh_payload_data_t)config_message;
zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}
static void s_zh_send_sensor_status_message(void)
{
zh_binary_sensor_status_message_t binary_sensor_status_message = {0};
binary_sensor_status_message.sensor_type = s_sensor_type;
binary_sensor_status_message.open = s_open_status;
binary_sensor_status_message.battery = s_battery_status;
zh_status_message_t status_message = {0};
status_message = (zh_status_message_t)binary_sensor_status_message;
zh_espnow_data_t data = {0};
data.device_type = ZHDT_BINARY_SENSOR;
data.payload_type = ZHPT_STATE;
data.payload_data = (zh_payload_data_t)status_message;
zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}
static void s_zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
const esp_app_desc_t *app_info = esp_app_get_description();
zh_espnow_data_t data_in = {0};
zh_espnow_data_t data_out = {0};
zh_espnow_ota_message_t espnow_ota_message = {0};
data_out.device_type = ZHDT_BINARY_SENSOR;
espnow_ota_message.chip_type = HACHT_ESP32;
data_out.payload_data = (zh_payload_data_t)espnow_ota_message;
switch (event_id)
{
case ZH_ESPNOW_ON_RECV_EVENT:;
zh_espnow_event_on_recv_t *recv_data = event_data;
if (recv_data->data_len != sizeof(zh_espnow_data_t))
{
goto ZH_ESPNOW_EVENT_HANDLER_EXIT;
}
memcpy(&data_in, recv_data->data, recv_data->data_len);
switch (data_in.device_type)
{
case ZHDT_GATEWAY:
if (s_gateway_is_available == false)
{
s_gateway_is_available = true;
memcpy(s_gateway_mac, recv_data->mac_addr, 6);
}
switch (data_in.payload_type)
{
case ZHPT_UPDATE:
s_update_partition = esp_ota_get_next_update_partition(NULL);
strcpy(espnow_ota_message.app_name, app_info->project_name);
strcpy(espnow_ota_message.app_version, app_info->version);
data_out.payload_type = ZHPT_UPDATE;
data_out.payload_data = (zh_payload_data_t)espnow_ota_message;
zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
break;
case ZHPT_UPDATE_BEGIN:
esp_ota_begin(s_update_partition, OTA_SIZE_UNKNOWN, &s_update_handle);
s_ota_message_part_number = 1;
break;
case ZHPT_UPDATE_PROGRESS:
if (s_ota_message_part_number == data_in.payload_data.espnow_ota_message.part)
{
++s_ota_message_part_number;
esp_ota_write(s_update_handle, (const void *)data_in.payload_data.espnow_ota_message.data, data_in.payload_data.espnow_ota_message.data_len);
}
data_out.payload_type = ZHPT_UPDATE_PROGRESS;
zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
break;
case ZHPT_UPDATE_ERROR:
esp_ota_end(s_update_handle);
break;
case ZHPT_UPDATE_END:
if (esp_ota_end(s_update_handle) != ESP_OK)
{
data_out.payload_type = ZHPT_UPDATE_FAIL;
zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
break;
}
esp_ota_set_boot_partition(s_update_partition);
data_out.payload_type = ZHPT_UPDATE_SUCCESS;
zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
break;
default:
break;
}
break;
default:
break;
}
ZH_ESPNOW_EVENT_HANDLER_EXIT:
free(recv_data->data);
break;
case ZH_ESPNOW_ON_SEND_EVENT:
break;
default:
break;
}
}

7
partitions.csv Normal file
View File

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
ota_0, app, ota_0, , 1500K,
ota_1, app, ota_1, , 1500K,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, , 0x4000,
4 otadata, data, ota, , 0x2000,
5 phy_init, data, phy, , 0x1000,
6 ota_0, app, ota_0, , 1500K,
7 ota_1, app, ota_1, , 1500K,

18
sdkconfig.defaults Normal file
View File

@ -0,0 +1,18 @@
CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_LOG_LEVEL=0
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_LOG_DEFAULT_LEVEL_NONE=y
CONFIG_LOG_DEFAULT_LEVEL=0

1
version.txt Normal file
View File

@ -0,0 +1 @@
1.0.0