From 758c790e783d08401115d604f8742d4a7f018088 Mon Sep 17 00:00:00 2001 From: VPavlusha Date: Tue, 11 Apr 2023 18:29:50 +0300 Subject: [PATCH] First commit --- .github/workflows/cpp-linter.yml | 26 ++ .gitignore | 8 + CMakeLists.txt | 4 + README.md | 75 +++- components/onewire_bus/CMakeLists.txt | 3 + components/onewire_bus/README.md | 90 +++++ components/onewire_bus/onewire_bus.c | 158 ++++++++ components/onewire_bus/onewire_bus.h | 77 ++++ components/onewire_bus/onewire_bus_rmt.c | 438 +++++++++++++++++++++++ components/onewire_bus/onewire_bus_rmt.h | 106 ++++++ doc/img/iot_onoff.png | Bin 0 -> 26216 bytes main/CMakeLists.txt | 17 + main/Kconfig.projbuild | 94 +++++ main/app_main.c | 22 ++ main/ds18b20.c | 106 ++++++ main/include/ds18b20.h | 78 ++++ main/include/led.h | 20 ++ main/include/mqtt.h | 8 + main/include/non_volatile_storage.h | 8 + main/include/task_monitor.h | 26 ++ main/include/temperature.h | 18 + main/include/types.h | 13 + main/include/wifi.h | 8 + main/led.c | 113 ++++++ main/mqtt.c | 194 ++++++++++ main/non_volatile_storage.c | 16 + main/task_monitor.c | 180 ++++++++++ main/temperature.c | 183 ++++++++++ main/wifi.c | 178 +++++++++ 29 files changed, 2266 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cpp-linter.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 components/onewire_bus/CMakeLists.txt create mode 100644 components/onewire_bus/README.md create mode 100644 components/onewire_bus/onewire_bus.c create mode 100644 components/onewire_bus/onewire_bus.h create mode 100644 components/onewire_bus/onewire_bus_rmt.c create mode 100644 components/onewire_bus/onewire_bus_rmt.h create mode 100644 doc/img/iot_onoff.png create mode 100644 main/CMakeLists.txt create mode 100644 main/Kconfig.projbuild create mode 100644 main/app_main.c create mode 100644 main/ds18b20.c create mode 100644 main/include/ds18b20.h create mode 100644 main/include/led.h create mode 100644 main/include/mqtt.h create mode 100644 main/include/non_volatile_storage.h create mode 100644 main/include/task_monitor.h create mode 100644 main/include/temperature.h create mode 100644 main/include/types.h create mode 100644 main/include/wifi.h create mode 100644 main/led.c create mode 100644 main/mqtt.c create mode 100644 main/non_volatile_storage.c create mode 100644 main/task_monitor.c create mode 100644 main/temperature.c create mode 100644 main/wifi.c diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml new file mode 100644 index 0000000..6fca937 --- /dev/null +++ b/.github/workflows/cpp-linter.yml @@ -0,0 +1,26 @@ +name: cpp-linter + +on: + push: + paths-ignore: "doc/**" + pull_request: + paths-ignore: "doc/**" + +jobs: + cpp-linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: cpp-linter/cpp-linter-action@master + id: linter + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + style: file + + - name: Fail fast?! + if: steps.linter.outputs.checks-failed > 0 + run: | + echo "Some files failed the linting checks!" + # for actual deployment + # run: exit 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7a8068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Build results +build/ + +# VSCode +.vscode/ + +# Project +sdkconfig diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2418b0b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp32_wifi_onewire_mqtt) diff --git a/README.md b/README.md index 0e952d2..ee500ed 100644 --- a/README.md +++ b/README.md @@ -1 +1,74 @@ -# ESP32_WiFi_OneWire_MQTT \ No newline at end of file +[![GitHub](https://img.shields.io/github/license/VPavlusha/ESP32_WiFi_OneWire_MQTT?color=blue&label=License&logo=github)](LICENSE) +[![cpp-linter](https://github.com/VPavlusha/ESP32_WiFi_OneWire_MQTT/actions/workflows/cpp-linter.yml/badge.svg)](https://github.com/VPavlusha/ESP32_WiFi_OneWire_MQTT/actions/workflows/cpp-linter.yml) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/VPavlusha/ESP32_WiFi_OneWire_MQTT?label=Release&logo=github)](https://github.com/VPavlusha/ESP32_WiFi_OneWire_MQTT/releases) +[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) +[![Made in Ukraine](https://img.shields.io/badge/Made_in-Ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua) +--- + +# ESP32 Wi-Fi OneWire MQTT +The ESP32 WiFi OneWire MQTT project is a simple way to read the data from DS18B20 temperature sensors connected to an ESP32 microcontroller, and send the data to an MQTT broker over Wi-Fi.
+Based on: https://github.com/espressif/esp-idf + +#### Table of Contents + [1. Features](#1-features) + [2. Monitor And Control Your Project](#2-monitor-and-control-your-project) + [3. Getting Started](#3-getting-started) + [4. Contributing](#4-contributing) + [5. License](#5-license) + +## 1. Features + - ESP-IDF v5.0.2 + - Reading temperature data from a DS18B20 sensor using the OneWire protocol. + - Sending temperature data to an MQTT broker over Wi-Fi. + - Easy-to-use API for customizing the firmware to meet your specific needs. + - Used Wi-Fi, OneWire, DS18B20, MQTT technology. + - Written in C language. + - MIT License. + +## 2. Monitor And Control Your Project +iot_onoff.png + +You have the ability to monitor and control your Internet of Things (IoT) projects with some app like [IoT OnOff](https://www.iot-onoff.com/). + +## 3. Getting Started +To get started with the ESP32 WiFi OneWire MQTT project, you'll need an ESP32 microcontroller, a DS18B20 temperature sensor, and access to an MQTT broker. You'll also need to install the ESP-IDF development framework. + +### 3.1 Clone the project repository: +```C + git clone https://github.com/VPavlusha/ESP32_WiFi_OneWire_MQTT.git +``` +### 3.2 Customize the firmware to match your Wi-Fi, DS18B20 and MQTT settings: + - Edit the **main/Kconfig.projbuild** file with your Wi-Fi, DS18B20 and MQTT settings. + +The ESP32 WiFi OneWire MQTT firmware is designed to be easily customizable. You can use the provided API to change the MQTT topic, adjust the temperature DS18B20 port connections, Wi-Fi settings and more. + +### 3.3 Build the project: +```C + cd ESP32_WiFi_OneWire_MQTT + idf.py build +``` +### 3.4 Flash onto your ESP32 microcontroller: +```C + idf.py -p PORT [-b BAUD] flash +``` +Replace PORT with your ESP32 board’s serial port name. +You can also change the flasher baud rate by replacing BAUD with the baud rate you need. The default baud rate is 460800.
+### 3.5 Monitor the output: +```C + idf.py -p monitor +``` +Do not forget to replace PORT with your serial port name. + +### 3.6 Check Wi-Fi network and MQTT broker: + - Check the output on the serial monitor to verify that the ESP32 is connecting to your Wi-Fi network and MQTT broker. + - Check your MQTT broker to verify that temperature data is being sent. + +More information how to build project: [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/v5.0.2/esp32/get-started/start-project.html). + +## 4. Contributing +Contributions to the ESP32 WiFi OneWire MQTT project are welcome. If you find a bug or have a feature request, please submit an issue on the project's GitHub page. If you'd like to contribute code, please submit a pull request. + +## 5. License +The ESP32_WiFi_OneWire_MQTT project is licensed under the MIT License. See the [MIT license] file for more information. + + [MIT license]: http://www.opensource.org/licenses/mit-license.html diff --git a/components/onewire_bus/CMakeLists.txt b/components/onewire_bus/CMakeLists.txt new file mode 100644 index 0000000..05df568 --- /dev/null +++ b/components/onewire_bus/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "onewire_bus_rmt.c" "onewire_bus.c" + INCLUDE_DIRS "." + PRIV_REQUIRES driver) diff --git a/components/onewire_bus/README.md b/components/onewire_bus/README.md new file mode 100644 index 0000000..cb9eb18 --- /dev/null +++ b/components/onewire_bus/README.md @@ -0,0 +1,90 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# RMT Transmit & Receive Example -- 1-Wire bus + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +RMT peripheral has both transmit and receive channels. Connecting one transmit channel and one receive channel to the same GPIO and put the GPIO in open-drain mode can simulate bi-directional single wire protocols, such as [1-Wire protocol](https://www.maximintegrated.com/en/design/technical-documents/tutorials/1/1796.html). + +This example demonstrates how to use RMT to simulate 1-Wire bus and read temperatrue from [DS18B20](https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf). + +## How to Use Example + +### Hardware Required + +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) +* Several DS18B20 temperature sensors and a 4.7kohm pullup resistor + +Connection : + +``` +┌──────────────────────────┐ +│ 3.3V├───────┬─────────────┬──────────────────────┐ +│ │ ┌┴┐ │VDD │VDD +│ ESP32 Board │ 4.7k│ │ ┌──────┴──────┐ ┌──────┴──────┐ +│ │ └┬┘ DQ│ │ DQ│ │ +│ ONEWIRE_GPIO_PIN├───────┴──┬───┤ DS18B20 │ ┌───┤ DS18B20 │ ...... +│ │ └───│-------------│────┴───│-------------│── +│ │ └──────┬──────┘ └──────┬──────┘ +│ │ │GND │GND +│ GND├─────────────────────┴──────────────────────┘ +└──────────────────────────┘ +``` + +The GPIO number used in this example can be changed according to your board, by the macro `EXAMPLE_ONEWIRE_GPIO_PIN` defined in [onewire_ds18b20_example_main.c](main/onewire_ds18b20_example_main.c). + +*Note*: Parasite power mode is not supported currently by this example, you have to connect VDD pin to make DS18B20 functional. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Console Output + +If there are some DS18B20s on the bus: + +``` +I (327) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (338) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (348) onewire_rmt: RMT Tx channel created for 1-wire bus +I (358) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 +I (358) onewire_rmt: RMT Rx channel created for 1-wire bus +I (368) example: 1-wire bus installed +I (418) example: found device with rom id 28FF30BE21170317 +I (458) example: found device with rom id 28FF297E211703A1 +I (498) example: found device with rom id 28FF6F7921170352 +I (508) example: 3 devices found on 1-wire bus +I (2518) example: temperature of device 28FF30BE21170317: 27.00C +I (2528) example: temperature of device 28FF297E211703A1: 26.81C +I (2538) example: temperature of device 28FF6F7921170352: 26.50C +I (3548) example: temperature of device 28FF30BE21170317: 26.94C +I (3558) example: temperature of device 28FF297E211703A1: 26.75C +I (3568) example: temperature of device 28FF6F7921170352: 26.44C +``` + +If there is no DS18B20 on the bus: + +``` +I (327) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (337) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (347) onewire_rmt: RMT Tx channel created for 1-wire bus +I (357) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 +I (357) onewire_rmt: RMT Rx channel created for 1-wire bus +I (367) example: 1-wire bus installed +E (377) onewire_rmt: no device present on 1-wire bus +I (377) example: 0 device found on 1-wire bus +I (387) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (397) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (397) example: 1-wire bus deleted +``` + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/components/onewire_bus/onewire_bus.c b/components/onewire_bus/onewire_bus.c new file mode 100644 index 0000000..8bfa126 --- /dev/null +++ b/components/onewire_bus/onewire_bus.c @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_check.h" +#include "esp_log.h" +#include "onewire_bus.h" + +static const char *TAG = "onewire"; + +struct onewire_rom_search_context_t { + onewire_bus_handle_t bus_handle; + + uint8_t last_device_flag; + uint16_t last_discrepancy; + uint8_t rom_number[8]; +}; + +// Algorithm inspired by https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/187.html + +static const uint8_t dscrc_table[] = { + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53 +}; + +uint8_t onewire_check_crc8(uint8_t *input, size_t input_size) +{ + uint8_t crc8 = 0; + + for (size_t i = 0; i < input_size; i ++) { + crc8 = dscrc_table[crc8 ^ input[i]]; + } + + return crc8; +} + +esp_err_t onewire_rom_search_context_create(onewire_bus_handle_t handle, onewire_rom_search_context_handler_t *context_out) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(context_out, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + struct onewire_rom_search_context_t *context = calloc(1, sizeof(struct onewire_rom_search_context_t)); + if (!context) { + return ESP_ERR_NO_MEM; + } + + context->bus_handle = handle; + *context_out = context; + + return ESP_OK; +} + +esp_err_t onewire_rom_search_context_delete(onewire_rom_search_context_handler_t context) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + free(context); + + return ESP_OK; +} + +esp_err_t onewire_rom_search(onewire_rom_search_context_handler_t context) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + uint8_t last_zero = 0; + + if (!context->last_device_flag) { + if (onewire_bus_reset(context->bus_handle) != ESP_OK) { // no device present + return ESP_ERR_NOT_FOUND; + } + + // send rom search command and start search algorithm + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(context->bus_handle, (uint8_t[]){ONEWIRE_CMD_SEARCH_ROM}, 1), + TAG, "error while sending search rom command"); + + for (uint16_t rom_bit_index = 0; rom_bit_index < 64; rom_bit_index ++) { + uint8_t rom_byte_index = rom_bit_index / 8; + uint8_t rom_bit_mask = 1 << (rom_bit_index % 8); // calculate byte index and bit mask in advance for convenience + + uint8_t rom_bit, rom_bit_complement; + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(context->bus_handle, &rom_bit), TAG, "error while reading rom bit"); // write 1 bit to read from the bus + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(context->bus_handle, &rom_bit_complement), + TAG, "error while reading rom bit"); // read a bit and its complement + + uint8_t search_direction; + if (rom_bit && rom_bit_complement) { // No devices participating in search. + ESP_LOGE(TAG, "no devices participating in search"); + return ESP_ERR_NOT_FOUND; + } else { + if (rom_bit != rom_bit_complement) { // There are only 0s or 1s in the bit of the participating ROM numbers. + search_direction = rom_bit; // just go ahead + } else { // There are both 0s and 1s in the current bit position of the participating ROM numbers. This is a discrepancy. + if (rom_bit_index < context->last_discrepancy) { // current id bit is before the last discrepancy bit + search_direction = (context->rom_number[rom_byte_index] & rom_bit_mask) ? 0x01 : 0x00; // follow previous way + } else { + search_direction = (rom_bit_index == context->last_discrepancy) ? 0x01 : 0x00; // search for 0 bit first + } + + if (search_direction == 0) { // record zero's position in last zero + last_zero = rom_bit_index; + } + } + + if (search_direction == 1) { // set corrsponding rom bit by serach direction + context->rom_number[rom_byte_index] |= rom_bit_mask; + } else { + context->rom_number[rom_byte_index] &= ~rom_bit_mask; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bit(context->bus_handle, search_direction), + TAG, "error while writing direction bit"); // set search direction + } + } + } else { + ESP_LOGD(TAG, "1-wire rom search finished"); + return ESP_FAIL; + } + + // if the search was successful + context->last_discrepancy = last_zero; + if (context->last_discrepancy == 0) { // last zero loops back to the first bit + context->last_device_flag = true; + } + + if (onewire_check_crc8(context->rom_number, 7) != context->rom_number[7]) { // check crc + ESP_LOGE(TAG, "bad crc checksum of device with id " ONEWIRE_ROM_ID_STR, ONEWIRE_ROM_ID(context->rom_number)); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +esp_err_t onewire_rom_get_number(onewire_rom_search_context_handler_t context, uint8_t *rom_number_out) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context pointer"); + ESP_RETURN_ON_FALSE(rom_number_out, ESP_ERR_INVALID_ARG, TAG, "invalid rom_number pointer"); + + memcpy(rom_number_out, context->rom_number, sizeof(context->rom_number)); + + return ESP_OK; +} diff --git a/components/onewire_bus/onewire_bus.h b/components/onewire_bus/onewire_bus.h new file mode 100644 index 0000000..3cb0ea5 --- /dev/null +++ b/components/onewire_bus/onewire_bus.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "onewire_bus_rmt.h" + +#define ONEWIRE_CMD_SEARCH_ROM 0xF0 +#define ONEWIRE_CMD_READ_ROM 0x33 +#define ONEWIRE_CMD_MATCH_ROM 0x55 +#define ONEWIRE_CMD_SKIP_ROM 0xCC +#define ONEWIRE_CMD_ALARM_SEARCH_ROM 0xEC + +#define ONEWIRE_ROM_ID(id) (id)[0], (id)[1], (id)[2], (id)[3], (id)[4], (id)[5], (id)[6], (id)[7] +#define ONEWIRE_ROM_ID_STR "%02X%02X%02X%02X%02X%02X%02X%02X" + +/** + * @brief Type of 1-wire ROM search algorithm context + * + */ +typedef struct onewire_rom_search_context_t *onewire_rom_search_context_handler_t; + +/** + * @brief Calculate Dallas CRC value of given buffer + * + * @param[in] input Input buffer to calculate CRC value + * @param[in] input_size Size of input buffer + * @return CRC result of input buffer + */ +uint8_t onewire_check_crc8(uint8_t *input, size_t input_size); + +/** + * @brief Create context for 1-wire ROM search algorithm + * + * @param[in] handle 1-wire handle used for ROM search + * @param[out] context_out Created context for ROM search algorithm + * @return + * - ESP_OK 1-wire ROM search context is created successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_search_context_create(onewire_bus_handle_t handle, onewire_rom_search_context_handler_t *context_out); + +/** + * @brief Delete context for 1-wire ROM search algorithm + * + * @param[in] context Context for ROM search algorithm + * @return + * - ESP_OK 1-wire ROM search context is deleted successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_search_context_delete(onewire_rom_search_context_handler_t context); + +/** + * @brief Search next device on 1-wire bus + * + * @param[in] context Context for ROM search algorithm + * @return + * - ESP_OK Successfully found a device + * - ESP_ERR_NOT_FOUND There are no device on the bus + * - ESP_ERR_INVALID_CRC Bad CRC value of found device + * - ESP_FAIL Reached last device on the bus, search algorighm finishes + */ +esp_err_t onewire_rom_search(onewire_rom_search_context_handler_t context); + +/** + * @brief Get device ROM number from ROM search context + * + * @param[in] context Context for ROM search algorithm + * @param[out] rom_number_out Device ROM number + * @return + * - ESP_OK Get ROM numbuer from 1-wire ROM search context success. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_get_number(onewire_rom_search_context_handler_t context, uint8_t *rom_number_out); diff --git a/components/onewire_bus/onewire_bus_rmt.c b/components/onewire_bus/onewire_bus_rmt.c new file mode 100644 index 0000000..af0e6a2 --- /dev/null +++ b/components/onewire_bus/onewire_bus_rmt.c @@ -0,0 +1,438 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "driver/rmt_types.h" +#include "driver/rmt_encoder.h" +#include "onewire_bus_rmt.h" + +static const char *TAG = "onewire_rmt"; + +/** + * @brief RMT resolution for 1-wire bus, in Hz + * + */ +#define ONEWIRE_RMT_RESOLUTION_HZ 1000000 + +/** + * @brief 1-wire bus timing parameters, in us (1/ONEWIRE_RMT_RESOLUTION_HZ) + * + */ +#define ONEWIRE_RESET_PULSE_DURATION 500 // duration of reset bit +#define ONEWIRE_RESET_WAIT_DURATION 200 // how long should master wait for device to show its presence +#define ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN 15 // minimum duration for master to wait device to show its presence +#define ONEWIRE_RESET_PRESENSE_DURATION_MIN 60 // minimum duration for master to recognize device as present + +#define ONEWIRE_SLOT_START_DURATION 2 // bit start pulse duration +#define ONEWIRE_SLOT_BIT_DURATION 60 // duration for each bit to transmit +// refer to https://www.maximintegrated.com/en/design/technical-documents/app-notes/3/3829.html for more information +#define ONEWIRE_SLOT_RECOVERY_DURATION 2 // recovery time between each bit, should be longer in parasite power mode +#define ONEWIRE_SLOT_BIT_SAMPLE_TIME 15 // how long after bit start pulse should the master sample from the bus + +/* +Reset Pulse: + + | RESET_PULSE | RESET_WAIT_DURATION | + | _DURATION | | + | | | | RESET | | + | | * | | _PRESENSE | | + | | | | _DURATION | | +------────┐ ┌─────┐ ┌───────------- + │ │ │ │ + │ │ │ │ + │ │ │ │ + └─────────────┘ └───────────┘ +*: RESET_PRESENSE_WAIT_DURATION + +Write 1 bit: + + | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT + | _DURATION | _DURATION | _DURATION | SLOT + | | | | +------────┐ ┌───────────────────────────────------ + │ │ + │ │ + │ │ + └────────────┘ + +Write 0 bit: + + | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT + | _DURATION | _DURATION | _DURATION | SLOT + | | | | +------────┐ ┌───────────────────------ + │ │ + │ │ + │ │ + └────────────────────────┘ + +Read 1 bit: + + + | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT + | _DURATION | | _DURATION | SLOT + | | SLOT_BIT_ | | | + | | SAMPLE_TIME | | | +------────┐ ┌────────────────────────────────────────------ + │ │ + │ │ + │ │ + └────────────┘ + +Read 0 bit: + + | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT + | _DURATION | | _DURATION | SLOT + | | SLOT_BIT_ | | | + | | SAMPLE_TIME | | | +------────┐ | | ┌───────────────────────------ + │ | │ + │ | PULLED DOWN │ + │ | BY DEVICE │ + └─────────────────────────────┘ +*/ + +struct onewire_bus_t { + rmt_channel_handle_t tx_channel; /*!< rmt tx channel handler */ + rmt_encoder_handle_t tx_bytes_encoder; /*!< used to encode commands and data */ + rmt_encoder_handle_t tx_copy_encoder; /*!< used to encode reset pulse and bits */ + + rmt_channel_handle_t rx_channel; /*!< rmt rx channel handler */ + rmt_symbol_word_t *rx_symbols; /*!< hold rmt raw symbols */ + + size_t max_rx_bytes; /*!< buffer size in byte for single receive transaction */ + + QueueHandle_t receive_queue; +}; + +const static rmt_symbol_word_t onewire_bit0_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_SLOT_START_DURATION + ONEWIRE_SLOT_BIT_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_SLOT_RECOVERY_DURATION +}; + +const static rmt_symbol_word_t onewire_bit1_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_SLOT_START_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_SLOT_BIT_DURATION + ONEWIRE_SLOT_RECOVERY_DURATION +}; + +const static rmt_symbol_word_t onewire_reset_pulse_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_RESET_PULSE_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_RESET_WAIT_DURATION +}; + +const static rmt_transmit_config_t onewire_rmt_tx_config = { + .loop_count = 0, // no transfer loop + .flags.eot_level = 1 // onewire bus should be released in IDLE +}; + +const static rmt_receive_config_t onewire_rmt_rx_config = { + .signal_range_min_ns = 1000000000 / ONEWIRE_RMT_RESOLUTION_HZ, + .signal_range_max_ns = (ONEWIRE_RESET_PULSE_DURATION + ONEWIRE_RESET_WAIT_DURATION) * 1000 +}; + +static bool onewire_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t task_woken = pdFALSE; + struct onewire_bus_t *handle = (struct onewire_bus_t *)user_data; + + xQueueSendFromISR(handle->receive_queue, edata, &task_woken); + + return task_woken; +} + +/* +[0].0 means symbol[0].duration0 + +First reset pulse after rmt channel init: + +Bus is low | Reset | Wait | Device | Bus Idle +after init | Pulse | | Presense | + ┌──────┐ ┌─────------ + │ │ │ + │ │ │ + │ │ │ +------─────────────┘ └──────────┘ + 1 2 3 + + [0].1 [0].0 [1].1 [1].0 + + +Following reset pulses: + +Bus is high | Reset | Wait | Device | Bus Idle +after init | Pulse | | Presense | +------──────┐ ┌──────┐ ┌─────------ + │ │ │ │ + │ │ │ │ + │ │ │ │ + └───────┘ └──────────┘ + 1 2 3 4 + + [0].0 [0].1 [1].0 [1].1 +*/ + +static bool onewire_rmt_check_presence_pulse(rmt_symbol_word_t *rmt_symbols, size_t symbol_num) +{ + if (symbol_num >= 2) { // there should be at lease 2 symbols(3 or 4 edges) + if (rmt_symbols[0].level1 == 1) { // bus is high before reset pulse + if (rmt_symbols[0].duration1 > ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN && + rmt_symbols[1].duration0 > ONEWIRE_RESET_PRESENSE_DURATION_MIN) { + return true; + } + } else { // bus is low before reset pulse(first pulse after rmt channel init) + if (rmt_symbols[0].duration0 > ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN && + rmt_symbols[1].duration1 > ONEWIRE_RESET_PRESENSE_DURATION_MIN) { + return true; + } + } + } + + ESP_LOGE(TAG, "no device present on 1-wire bus"); + return false; +} + +static void onewire_rmt_decode_data(rmt_symbol_word_t *rmt_symbols, size_t symbol_num, uint8_t *decoded_bytes) +{ + size_t byte_pos = 0, bit_pos = 0; + for (size_t i = 0; i < symbol_num; i ++) { + if (rmt_symbols[i].duration0 > ONEWIRE_SLOT_BIT_SAMPLE_TIME) { // 0 bit + decoded_bytes[byte_pos] &= ~(1 << bit_pos); // LSB first + } else { // 1 bit + decoded_bytes[byte_pos] |= 1 << bit_pos; + } + + bit_pos ++; + if (bit_pos >= 8) { + bit_pos = 0; + byte_pos ++; + } + } +} + +esp_err_t onewire_new_bus_rmt(onewire_rmt_config_t *config, onewire_bus_handle_t *handle_out) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "invalid config pointer"); + ESP_RETURN_ON_FALSE(handle_out, ESP_ERR_INVALID_ARG, TAG, "invalid handle pointer"); + + esp_err_t ret = ESP_OK; + + struct onewire_bus_t *handle = calloc(1, sizeof(struct onewire_bus_t)); + ESP_GOTO_ON_FALSE(handle, ESP_ERR_NO_MEM, err, TAG, "memory allocation for 1-wire bus handler failed"); + + // create rmt bytes encoder to transmit 1-wire commands and data + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = onewire_bit0_symbol, + .bit1 = onewire_bit1_symbol, + .flags.msb_first = 0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &handle->tx_bytes_encoder), + err, TAG, "create data tx encoder failed"); + + // create rmt copy encoder to transmit 1-wire reset pulse or bits + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &handle->tx_copy_encoder), + err, TAG, "create reset pulse tx encoder failed"); + + // create rmt rx channel + rmt_rx_channel_config_t onewire_rx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = config->gpio_pin, +#if SOC_RMT_SUPPORT_RX_PINGPONG + .mem_block_symbols = 64, // when the chip is ping-pong capable, we can use less rx memory blocks +#else + .mem_block_symbols = config->max_rx_bytes * 8, +#endif + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, // in us + }; + ESP_GOTO_ON_ERROR(rmt_new_rx_channel(&onewire_rx_channel_cfg, &handle->rx_channel), + err, TAG, "create rmt rx channel failed"); + ESP_LOGI(TAG, "RMT Tx channel created for 1-wire bus"); + + // create rmt tx channel after rx channel + rmt_tx_channel_config_t onewire_tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = config->gpio_pin, + .mem_block_symbols = 64, // ping-pong is always avaliable on tx channel, save hardware memory blocks + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, // in us + .trans_queue_depth = 4, + .flags.io_loop_back = true, // make tx channel coexist with rx channel on the same gpio pin + .flags.io_od_mode = true // enable open-drain mode for 1-wire bus + }; + ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&onewire_tx_channel_cfg, &handle->tx_channel), + err, TAG, "create rmt tx channel failed"); + ESP_LOGI(TAG, "RMT Rx channel created for 1-wire bus"); + + // allocate rmt rx symbol buffer + handle->rx_symbols = malloc(config->max_rx_bytes * sizeof(rmt_symbol_word_t) * 8); + ESP_GOTO_ON_FALSE(handle->rx_symbols, ESP_ERR_NO_MEM, err, TAG, "memory allocation for rx symbol buffer failed"); + handle->max_rx_bytes = config->max_rx_bytes; + + handle->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); + ESP_GOTO_ON_FALSE(handle->receive_queue, ESP_ERR_NO_MEM, err, TAG, "receive queue creation failed"); + + // register rmt rx done callback + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = onewire_rmt_rx_done_callback + }; + ESP_GOTO_ON_ERROR(rmt_rx_register_event_callbacks(handle->rx_channel, &cbs, handle), + err, TAG, "enable rmt rx channel failed"); + + // enable rmt channels + ESP_GOTO_ON_ERROR(rmt_enable(handle->rx_channel), err, TAG, "enable rmt rx channel failed"); + ESP_GOTO_ON_ERROR(rmt_enable(handle->tx_channel), err, TAG, "enable rmt tx channel failed"); + + *handle_out = handle; + return ESP_OK; + +err: + if (handle) { + onewire_del_bus(handle); + } + + return ret; +} + +esp_err_t onewire_del_bus(onewire_bus_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + if (handle->tx_bytes_encoder) { + rmt_del_encoder(handle->tx_bytes_encoder); + } + if (handle->tx_copy_encoder) { + rmt_del_encoder(handle->tx_copy_encoder); + } + if (handle->rx_channel) { + rmt_disable(handle->rx_channel); + rmt_del_channel(handle->rx_channel); + } + if (handle->tx_channel) { + rmt_disable(handle->tx_channel); + rmt_del_channel(handle->tx_channel); + } + if (handle->receive_queue) { + vQueueDelete(handle->receive_queue); + } + if (handle->rx_symbols) { + free(handle->rx_symbols); + } + free(handle); + + return ESP_OK; +} + +esp_err_t onewire_bus_reset(onewire_bus_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + // send reset pulse while receive presence pulse + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, sizeof(rmt_symbol_word_t) * 2, &onewire_rmt_rx_config), + TAG, "1-wire reset pulse receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, &onewire_reset_pulse_symbol, sizeof(onewire_reset_pulse_symbol), &onewire_rmt_tx_config), + TAG, "1-wire reset pulse transmit failed"); + + // wait and check presence pulse + bool is_present = false; + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + is_present = onewire_rmt_check_presence_pulse(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols); + } + + return is_present ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t handle, const uint8_t *tx_data, uint8_t tx_data_size) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(tx_data && tx_data_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid tx buffer or buffer size"); + + // transmit data + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_bytes_encoder, tx_data, tx_data_size, &onewire_rmt_tx_config), + TAG, "1-wire data transmit failed"); + + // wait the transmission to complete + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(handle->tx_channel, 50), TAG, "wait for 1-wire data transmit failed"); + + return ESP_OK; +} + +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t handle, uint8_t *rx_data, size_t rx_data_size) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(rx_data && rx_data_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid rx buffer or buffer size"); + ESP_RETURN_ON_FALSE(!(rx_data_size > handle->max_rx_bytes), ESP_ERR_INVALID_ARG, + TAG, "rx_data_size too large for buffer to hold"); + + uint8_t tx_buffer[rx_data_size]; + memset(tx_buffer, 0xFF, rx_data_size); // transmit one bits to generate read clock + + // transmit 1 bits while receiving + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, rx_data_size * 8 * sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + TAG, "1-wire data receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_bytes_encoder, tx_buffer, sizeof(tx_buffer), &onewire_rmt_tx_config), + TAG, "1-wire data transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_data); + } else { + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} + +esp_err_t onewire_bus_write_bit(onewire_bus_handle_t handle, uint8_t tx_bit) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + const rmt_symbol_word_t *symbol_to_transmit = tx_bit ? &onewire_bit1_symbol : &onewire_bit0_symbol; + + // transmit bit + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, symbol_to_transmit, sizeof(onewire_bit1_symbol), &onewire_rmt_tx_config), + TAG, "1-wire bit transmit failed"); + + // wait the transmission to complete + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(handle->tx_channel, 50), TAG, "wait for 1-wire bit transmit failed"); + + return ESP_OK; +} + +esp_err_t onewire_bus_read_bit(onewire_bus_handle_t handle, uint8_t *rx_bit) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid rx_bit pointer"); + + // transmit 1 bit while receiving + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + TAG, "1-wire bit receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, &onewire_bit1_symbol, sizeof(onewire_bit1_symbol), &onewire_rmt_tx_config), + TAG, "1-wire bit transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + uint8_t rx_buffer[1]; + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_buffer); + *rx_bit = rx_buffer[0] & 0x01; + } else { + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} diff --git a/components/onewire_bus/onewire_bus_rmt.h b/components/onewire_bus/onewire_bus_rmt.h new file mode 100644 index 0000000..5661364 --- /dev/null +++ b/components/onewire_bus/onewire_bus_rmt.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "driver/gpio.h" + +/** + * @brief 1-wire bus config + * + */ +typedef struct { + gpio_num_t gpio_pin; /*!< gpio used for 1-wire bus */ + uint8_t max_rx_bytes; /*!< should be larger than the largest possible single receive size */ +} onewire_rmt_config_t; + +/** + * @brief Type of 1-wire bus handle + * + */ +typedef struct onewire_bus_t *onewire_bus_handle_t; + +/** + * @brief Install new 1-wire bus + * + * @param[in] config 1-wire bus configurations + * @param[out] handle_out Installed new 1-wire bus' handle + * @return + * - ESP_OK 1-wire bus is installed successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NO_MEM Memory allocation failed. + */ +esp_err_t onewire_new_bus_rmt(onewire_rmt_config_t *config, onewire_bus_handle_t *handle_out); + +/** + * @brief Delete existing 1-wire bus + * + * @param[in] handle 1-wire bus handle to be deleted + * @return + * - ESP_OK 1-wire bus is deleted successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_del_bus(onewire_bus_handle_t handle); + +/** + * @brief Send reset pulse on 1-wire bus, and detect if there are devices on the bus + * + * @param[in] handle 1-wire bus handle + * @return + * - ESP_OK There are devices present on 1-wire bus. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t onewire_bus_reset(onewire_bus_handle_t handle); + +/** + * @brief Write bytes to 1-wire bus, this is a blocking function + * + * @param[in] handle 1-wire bus handle + * @param[in] tx_data pointer to data to be sent + * @param[in] tx_data_count number of data to be sent + * @return + * - ESP_OK Write bytes to 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t handle, const uint8_t *tx_data, uint8_t tx_data_size); + +/** + * @brief Read bytes from 1-wire bus + * + * @note While receiving data, we use rmt transmit channel to send 0xFF to generate read pulse, + * at the same time, receive channel is used to record weather the bus is pulled down by device. + * + * @param[in] handle 1-wire bus handle + * @param[out] rx_data pointer to received data + * @param[in] rx_data_count number of received data + * @return + * - ESP_OK Read bytes from 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t handle, uint8_t *rx_data, size_t rx_data_size); + +/** + * @brief Write a bit to 1-wire bus, this is a blocking function + * + * @param[in] handle 1-wire bus handle + * @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit + * @return + * - ESP_OK Write bit to 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_write_bit(onewire_bus_handle_t handle, uint8_t tx_bit); + +/** + * @brief Read a bit from 1-wire bus + * + * @param[in] handle 1-wire bus handle + * @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit + * @return + * - ESP_OK Read bit from 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_read_bit(onewire_bus_handle_t handle, uint8_t *rx_bit); diff --git a/doc/img/iot_onoff.png b/doc/img/iot_onoff.png new file mode 100644 index 0000000000000000000000000000000000000000..e6012bca1458b30c234bae963f12310b6275c662 GIT binary patch literal 26216 zcmaI7Ra_iT&^EfbYhZDLySpY3+}%9{3A(`IlEs|>2@*6c?iwKI;ud5fxGnAuK?3CC z|GwYFIp^ZsO!rhh^K^Ajbxn8mZ(_AImGH1Bu>k-8o{F-(E&zap0023(JX~B{+&tX;0{l+^02mAo z2?-Gr5>ixDG&3^`3kwqz6qJ#b5evET_xBGB43vH&?d;-gXlN)eFQ1l{rlh1aG&D3Y zFyQ9yW@%+2f1tOXgfGM@KedEYp8!CeWj+R zYDrI9R8(YSY^1EB97M$V(dna|y?tF>-CLQrot>Q@K75c=HaE90*V58#X=(BB^w2lZ ze{cJKVPT=KuP-GfrKhLo=g*(kHr8cjWnv8U+RO}1O-&6A4Z3=|;-2u5k`f3hY5w>8 zBo^B0>gwrfcwAgucXxMYX6D4iM08AaF)Ix|{+EQr1Y%-HMMcE}8%0D!#OKdRn3$Nk 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 j2)8ABg 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 OC~8XDiq-Pg5&sL#OK_6_ literal 0 HcmV?d00001 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..a86a892 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,17 @@ +set(SOURCES + "app_main.c" + "non_volatile_storage.c" + "led.c" + "wifi.c" + "ds18b20.c" + "temperature.c" + "mqtt.c" + "task_monitor.c" +) + +set(INCLUDES "." "include") + +idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES}) + +set_source_files_properties(${SOURCES} PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Werror") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..7e2ee0e --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,94 @@ +menu "ESP32_WIFI_ONEWIRE_MQTT Project Configuration" + + config ESP_WIFI_SSID + string "WiFi SSID" + default "Wi-Fi_name" + help + SSID (network name) for the example to connect to. + + config ESP_WIFI_PASSWORD + string "WiFi Password" + default "Wi-Fi_password" + help + WiFi password (WPA or WPA2) for the example to use. + + choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD + prompt "WiFi Scan auth mode threshold" + default ESP_WIFI_AUTH_WPA2_PSK + help + The weakest authmode to accept in the scan mode. + This value defaults to ESP_WIFI_AUTH_WPA2_PSK incase password is present and ESP_WIFI_AUTH_OPEN is used. + Please select ESP_WIFI_AUTH_WEP/ESP_WIFI_AUTH_WPA_PSK incase AP is operating in WEP/WPA mode. + + config ESP_WIFI_AUTH_OPEN + bool "OPEN" + config ESP_WIFI_AUTH_WEP + bool "WEP" + config ESP_WIFI_AUTH_WPA_PSK + bool "WPA PSK" + config ESP_WIFI_AUTH_WPA2_PSK + bool "WPA2 PSK" + config ESP_WIFI_AUTH_WPA_WPA2_PSK + bool "WPA/WPA2 PSK" + config ESP_WIFI_AUTH_WPA3_PSK + bool "WPA3 PSK" + config ESP_WIFI_AUTH_WPA2_WPA3_PSK + bool "WPA2/WPA3 PSK" + config ESP_WIFI_AUTH_WAPI_PSK + bool "WAPI PSK" + endchoice + + config BROKER_URL + string "Broker URL" + default "mqtt://mqtt.eclipseprojects.io" + help + URL of the broker to connect to. + + config BROKER_PORT + int "Broker Server Port" + default 1883 + help + Server Port of the broker to connect to. + + config BROKER_TOPIC_PREFIX + string "Broker Topic Prefix" + default "ESP32_WIFI_ONEWIRE_MQTT" + help + The MQTT topic name starting with prefix. + + config ONEWIRE_DATA_GPIO_PIN + int "GPIO pin for DS18B20 device DATA bus" + range 0 39 + default 5 + help + Select the GPIO pin that is connected to the DS18B20 device DATA bus. + This pin is used for data communication between the device and the microcontroller. + + config ONEWIRE_NUMBER_OF_DEVICES + int "Number of DS18B20 devices" + range 1 128 + default 1 + help + Specify the number of DS18B20 temperature devices that are connected to the DATA bus. + + config ONEWIRE_TEMPERATURE_UPDATE_TIME + int "Update time for DS18B20 devices in seconds" + default 2 + help + Specify the update time for DS18B20 temperature devices in seconds. + + config FREERTOS_USE_TRACE_FACILITY + bool "Enable trace facility" + default y + help + Enables additional structure members and functions to assist with execution visualization and tracing + (see configUSE_TRACE_FACILITY documentation for more details). + + config FREERTOS_GENERATE_RUN_TIME_STATS + bool "Enable trace facility" + default y + help + Enables collection of run time statistics for each task + (see configGENERATE_RUN_TIME_STATS documentation for more details). + +endmenu diff --git a/main/app_main.c b/main/app_main.c new file mode 100644 index 0000000..15f22e4 --- /dev/null +++ b/main/app_main.c @@ -0,0 +1,22 @@ +#include "esp_check.h" + +#include "led.h" +#include "mqtt.h" +#include "non_volatile_storage.h" +#include "task_monitor.h" +#include "temperature.h" +#include "wifi.h" + +void app_main(void) +{ + ESP_ERROR_CHECK(nvs_init()); + + ESP_ERROR_CHECK(led_init()); + ESP_ERROR_CHECK(wifi_init()); + ESP_ERROR_CHECK(ds18b20_init()); + ESP_ERROR_CHECK(mqtt_init()); + + // NOTE: USE_TRACE_FACILITY and GENERATE_RUN_TIME_STATS must be defined as 1 + // in FreeRTOSConfig.h for this API function task_monitor() to be available. + ESP_ERROR_CHECK(task_monitor()); +} diff --git a/main/ds18b20.c b/main/ds18b20.c new file mode 100644 index 0000000..6801c4b --- /dev/null +++ b/main/ds18b20.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "ds18b20.h" +#include "esp_check.h" + +static const char *TAG = "ds18b20"; + +esp_err_t ds18b20_trigger_temperature_conversion(onewire_bus_handle_t handle, const uint8_t *rom_number) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_CONVERT_TEMP; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { // skip rom id + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_CONVERT_TEMP; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while triggering temperature convert"); + + return ESP_OK; +} + +esp_err_t ds18b20_get_temperature(onewire_bus_handle_t handle, const uint8_t *rom_number, float *temperature) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(temperature, ESP_ERR_INVALID_ARG, TAG, "invalid temperature pointer"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + ds18b20_scratchpad_t scratchpad; + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_READ_SCRATCHPAD; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_READ_SCRATCHPAD; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while sending read scratchpad command"); + ESP_RETURN_ON_ERROR(onewire_bus_read_bytes(handle, (uint8_t *)&scratchpad, sizeof(scratchpad)), + TAG, "error while reading scratchpad command"); + + ESP_RETURN_ON_FALSE(onewire_check_crc8((uint8_t *)&scratchpad, 8) == scratchpad.crc_value, ESP_ERR_INVALID_CRC, + TAG, "crc error"); + + static const uint8_t lsb_mask[4] = { 0x07, 0x03, 0x01, 0x00 }; + uint8_t lsb_masked = scratchpad.temp_lsb & (~lsb_mask[scratchpad.configuration >> 5]); // mask bits not used in low resolution + *temperature = (((int16_t)scratchpad.temp_msb << 8) | lsb_masked) / 16.0f; + + return ESP_OK; +} + +esp_err_t ds18b20_set_resolution(onewire_bus_handle_t handle, const uint8_t *rom_number, ds18b20_resolution_t resolution) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_WRITE_SCRATCHPAD; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_WRITE_SCRATCHPAD; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while sending read scratchpad command"); + + tx_buffer[0] = 0; + tx_buffer[1] = 0; + tx_buffer[2] = resolution; + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, 3), + TAG, "error while sending write scratchpad command"); + + return ESP_OK; +} diff --git a/main/include/ds18b20.h b/main/include/ds18b20.h new file mode 100644 index 0000000..7c10646 --- /dev/null +++ b/main/include/ds18b20.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#include "onewire_bus.h" + +#define DS18B20_CMD_CONVERT_TEMP 0x44 +#define DS18B20_CMD_WRITE_SCRATCHPAD 0x4E +#define DS18B20_CMD_READ_SCRATCHPAD 0xBE + +/** + * @brief Structure of DS18B20's scratchpad + * + */ +typedef struct { + uint8_t temp_lsb; /*!< lsb of temperature */ + uint8_t temp_msb; /*!< msb of temperature */ + uint8_t th_user1; /*!< th register or user byte 1 */ + uint8_t tl_user2; /*!< tl register or user byte 2 */ + uint8_t configuration; /*!< configuration register */ + uint8_t _reserved1; + uint8_t _reserved2; + uint8_t _reserved3; + uint8_t crc_value; /*!< crc value of scratchpad data */ +} ds18b20_scratchpad_t; + +/** + * @brief Enumeration of DS18B20's resolution config + * + */ +typedef enum { + DS18B20_RESOLUTION_12B = 0x7F, /*!< 750ms convert time */ + DS18B20_RESOLUTION_11B = 0x5F, /*!< 375ms convert time */ + DS18B20_RESOLUTION_10B = 0x3F, /*!< 187.5ms convert time */ + DS18B20_RESOLUTION_9B = 0x1F, /*!< 93.75ms convert time */ +} ds18b20_resolution_t; + +/** + * @brief Trigger temperature conversion of DS18B20 + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to send command, NULL to skip ROM + * @return + * - ESP_OK Trigger tempreture convertsion success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t ds18b20_trigger_temperature_conversion(onewire_bus_handle_t handle, const uint8_t *rom_number); + +/** + * @brief Get temperature from DS18B20 + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to read from, NULL to skip ROM + * @param[out] temperature result from DS18B20 + * @return + * - ESP_OK Get tempreture from DS18B20 success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + * - ESP_ERR_INVALID_CRC CRC check failed. + */ +esp_err_t ds18b20_get_temperature(onewire_bus_handle_t handle, const uint8_t *rom_number, float *temperature); + +/** + * @brief Set DS18B20's temperation conversion resolution + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to read from, NULL to skip ROM + * @param[in] resolution resolution of DS18B20's temperation conversion + * @return + * - ESP_OK Set DS18B20 resolution success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t ds18b20_set_resolution(onewire_bus_handle_t handle, const uint8_t *rom_number, ds18b20_resolution_t resolution); diff --git a/main/include/led.h b/main/include/led.h new file mode 100644 index 0000000..eb021b1 --- /dev/null +++ b/main/include/led.h @@ -0,0 +1,20 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_LED_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_LED_H_ + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" // NOTE: #include "FreeRTOS.h" must appear before #include "event_groups.h" + +#include "esp_err.h" +#include "esp_bit_defs.h" + +esp_err_t led_init(void); + +extern EventGroupHandle_t led_event_group; + +typedef enum { + LED_EVENT_ON = BIT0, + LED_EVENT_OFF = BIT1, + LED_EVENT_BLINK = BIT2, +} led_event_t; + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_LED_H_ diff --git a/main/include/mqtt.h b/main/include/mqtt.h new file mode 100644 index 0000000..c64d667 --- /dev/null +++ b/main/include/mqtt.h @@ -0,0 +1,8 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_MQTT_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_MQTT_H_ + +#include "esp_err.h" + +esp_err_t mqtt_init(void); + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_MQTT_H_ diff --git a/main/include/non_volatile_storage.h b/main/include/non_volatile_storage.h new file mode 100644 index 0000000..932b41d --- /dev/null +++ b/main/include/non_volatile_storage.h @@ -0,0 +1,8 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_NON_VOLATILE_STORAGE_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_NON_VOLATILE_STORAGE_H_ + +#include "esp_err.h" + +esp_err_t nvs_init(void); + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_NON_VOLATILE_STORAGE_H_ diff --git a/main/include/task_monitor.h b/main/include/task_monitor.h new file mode 100644 index 0000000..6b89e2e --- /dev/null +++ b/main/include/task_monitor.h @@ -0,0 +1,26 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TASK_MONITOR_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TASK_MONITOR_H_ + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// NOTE: USE_TRACE_FACILITY and GENERATE_RUN_TIME_STATS must be defined as 1 +// in FreeRTOSConfig.h for this API function task_monitor() to be available. + +/** + * @brief Create a new task to monitor the status and activity of other FreeRTOS tasks in the system + * + * @return + * - ESP_OK Success. + * - ESP_ERR_NO_MEM Out of memory. + */ +esp_err_t task_monitor(void); + +#ifdef __cplusplus +} +#endif + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TASK_MONITOR_H_ diff --git a/main/include/temperature.h b/main/include/temperature.h new file mode 100644 index 0000000..53d5285 --- /dev/null +++ b/main/include/temperature.h @@ -0,0 +1,18 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TEMPERATURE_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TEMPERATURE_H_ + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" // NOTE: #include "FreeRTOS.h" must appear in source files before #include "queue.h" + +#include "esp_err.h" + +typedef struct { + uint8_t device; + float temperature; +} temperature_device_t; + +esp_err_t ds18b20_init(void); + +extern QueueHandle_t temperature_queue; + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TEMPERATURE_H_ diff --git a/main/include/types.h b/main/include/types.h new file mode 100644 index 0000000..3b8fe5b --- /dev/null +++ b/main/include/types.h @@ -0,0 +1,13 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TYPES_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TYPES_H_ + +typedef enum { + PRIORITY_IDLE = 0, + PRIORITY_LOWEST, + PRIORITY_LOW, + PRIORITY_MIDDLE, + PRIORITY_HIGH, + PRIORITY_MAX, +} task_priority_t; + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_TYPES_H_ diff --git a/main/include/wifi.h b/main/include/wifi.h new file mode 100644 index 0000000..059df02 --- /dev/null +++ b/main/include/wifi.h @@ -0,0 +1,8 @@ +#ifndef ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_WIFI_H_ +#define ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_WIFI_H_ + +#include "esp_err.h" + +esp_err_t wifi_init(void); + +#endif // ESP32_WIFI_ONEWIRE_MQTT_MAIN_INCLUDE_WIFI_H_ diff --git a/main/led.c b/main/led.c new file mode 100644 index 0000000..328c924 --- /dev/null +++ b/main/led.c @@ -0,0 +1,113 @@ +#include "led.h" + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_event.h" +#include "freertos/event_groups.h" + +#include "driver/gpio.h" +#include "esp_err.h" +#include "esp_timer.h" +#include "esp_log.h" + +#include "types.h" + +static const char *TAG = "led"; + +#define LED_1 GPIO_NUM_2 + +typedef enum { + LED_OFF = 0, + LED_ON = 1, +} led_state_t; + +EventGroupHandle_t led_event_group = NULL; +static esp_timer_handle_t led_blink_timer = NULL; + +static void led_blink_timer_callback(void *arg); + +static void led_blink_timer_start(uint64_t timeout_us) +{ + if (led_blink_timer == NULL) { + const esp_timer_create_args_t timer_args = { + .callback = led_blink_timer_callback, + .arg = NULL, + .name = "Led timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &led_blink_timer)); + } + ESP_ERROR_CHECK(esp_timer_start_once(led_blink_timer, timeout_us)); +} + +static void led_blink_timer_stop(void) +{ + if (led_blink_timer != NULL) { + if (esp_timer_is_active(led_blink_timer)) { + ESP_ERROR_CHECK(esp_timer_stop(led_blink_timer)); + } + ESP_ERROR_CHECK(esp_timer_delete(led_blink_timer)); + led_blink_timer = NULL; + } +} + +static void led_blink_timer_callback(void *arg) +{ + static bool is_led_on = true; + is_led_on = !is_led_on; + + if (is_led_on) { + gpio_set_level(LED_1, LED_OFF); + led_blink_timer_start(9 * 100000); // 900 ms + } else { + gpio_set_level(LED_1, LED_ON); + led_blink_timer_start(1 * 100000); // 100 ms + } +} + +static void led_task(void *params) +{ + while (true) { + EventBits_t event_bits = xEventGroupWaitBits(led_event_group, LED_EVENT_ON | LED_EVENT_OFF | LED_EVENT_BLINK, + pdTRUE, pdFALSE, portMAX_DELAY); + + if (event_bits & LED_EVENT_ON) { + led_blink_timer_stop(); + gpio_set_level(LED_1, LED_ON); + } else if (event_bits & LED_EVENT_OFF) { + led_blink_timer_stop(); + gpio_set_level(LED_1, LED_OFF); + } else if (event_bits & LED_EVENT_BLINK) { + led_blink_timer_start(0); + } + } +} + +esp_err_t led_init(void) +{ + const gpio_config_t io_config = { + .pin_bit_mask = (1ULL << LED_1), // ((1ULL << LED_1) | (1ULL << LED_2) ...) + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&io_config); + gpio_set_level(LED_1, LED_OFF); + + led_event_group = xEventGroupCreate(); + if (led_event_group == NULL) { + ESP_LOGE(TAG, "led_event_group: Event Group was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + + BaseType_t status = xTaskCreate(led_task, "led_task", configMINIMAL_STACK_SIZE * 4, NULL, PRIORITY_MIDDLE, NULL); + if (status != pdPASS) { + ESP_LOGE(TAG, "led_task(): Task was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "led_init() finished successfully"); + return ESP_OK; +} diff --git a/main/mqtt.c b/main/mqtt.c new file mode 100644 index 0000000..a51b2c1 --- /dev/null +++ b/main/mqtt.c @@ -0,0 +1,194 @@ +#include "mqtt.h" + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" + +#include "esp_err.h" +#include "esp_log.h" + +#include "mqtt_client.h" + +#include "led.h" +#include "temperature.h" +#include "types.h" + +static const char *TAG = "mqtt"; + +typedef enum { + MQTT_RETAIN_TRUE = true, + MQTT_RETAIN_FALSE = false, +} mqtt_retain_t; + +static const char TOPIC_LED_SWITCH[] = CONFIG_BROKER_TOPIC_PREFIX "/led_switch"; +static const char TOPIC_LED_STATUS[] = CONFIG_BROKER_TOPIC_PREFIX "/led_status"; +static const char TOPIC_TEMPERATURE[] = CONFIG_BROKER_TOPIC_PREFIX "/temperature/device_"; + +static TaskHandle_t mqtt_task_handle = NULL; + +static void log_error_if_nonzero(const char *message, int error_code) +{ + if (error_code != 0) { + ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); + } +} + +static inline bool is_event_group_created(EventGroupHandle_t event) +{ + return event != NULL ? true : false; +} + +static void handle_data(void *event_data) +{ + esp_mqtt_event_handle_t event = event_data; + esp_mqtt_client_handle_t client = event->client; + + if (*(event->topic) == *TOPIC_LED_SWITCH) { + //xEventGroupSetBits(led_event_group, LED_EVENT_BLINK); + if (*(event->data) == '1') { + if (is_event_group_created(led_event_group)) { + xEventGroupSetBits(led_event_group, LED_EVENT_ON); + esp_mqtt_client_publish(client, TOPIC_LED_STATUS, "1", 0, 1, MQTT_RETAIN_TRUE); + ESP_LOGI(TAG, "led_event_group - LED_EVENT_ON"); + } + } else if (*(event->data) == '0') { + if (is_event_group_created(led_event_group)) { + xEventGroupSetBits(led_event_group, LED_EVENT_OFF); + esp_mqtt_client_publish(client, TOPIC_LED_STATUS, "0", 0, 1, MQTT_RETAIN_TRUE); + ESP_LOGI(TAG, "led_event_group - LED_EVENT_OFF"); + } + } + } +} + +/* + * @brief Event handler registered to receive MQTT events + * + * This function is called by the MQTT client event loop. + * + * @param handler_args user data registered to the event. + * @param base Event base for the handler(always MQTT Base in this example). + * @param event_id The id for the received event. + * @param event_data The data for the event, esp_mqtt_event_handle_t. + */ +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%lu", base, event_id); + esp_mqtt_event_handle_t event = event_data; + esp_mqtt_client_handle_t client = event->client; + int msg_id; + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + vTaskResume(mqtt_task_handle); + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + + msg_id = esp_mqtt_client_subscribe(client, TOPIC_LED_SWITCH, 0); + ESP_LOGI(TAG, "Sent subscribe successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_DISCONNECTED: + vTaskSuspend(mqtt_task_handle); + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + ESP_LOGI(TAG, "TOPIC=%.*s\r\n", event->topic_len, event->topic); + ESP_LOGI(TAG, "DATA=%.*s\r\n", event->data_len, event->data); + + handle_data(event_data); + break; + case MQTT_EVENT_ERROR: + ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + log_error_if_nonzero("Reported from esp-tls", event->error_handle->esp_tls_last_esp_err); + log_error_if_nonzero("Reported from tls stack", event->error_handle->esp_tls_stack_err); + log_error_if_nonzero("Captured as transport's socket errno", event->error_handle->esp_transport_sock_errno); + ESP_LOGE(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); + } + break; + default: + ESP_LOGW(TAG, "Other event id:%d", event->event_id); + break; + } +} + +static const char* float_to_string(float number, char *string) +{ + sprintf(string, "%.1f", number); // Convert float to string with one decimal place + return string; +} + +static const char* get_topic(char *topic, int device) +{ + sprintf(topic, "%s%d", TOPIC_TEMPERATURE, device); + return topic; +} + +static inline bool is_queue_created(QueueHandle_t queue) +{ + return queue != NULL ? true : false; +} + +static void mqtt_task(void *params) +{ + while (true) { + if (is_queue_created(temperature_queue)) { + temperature_device_t received_value; + BaseType_t status = xQueueReceive(temperature_queue, &received_value, portMAX_DELAY); + + if (status == pdPASS) { + char topic[sizeof(TOPIC_TEMPERATURE) + 3 * sizeof(char)]; // 3 chars for number 128 (max devices) + char string[20]; // 20 - maximum number of characters for a float: -[sign][d].[d...]e[sign]d + const esp_mqtt_client_handle_t client = *(esp_mqtt_client_handle_t*)params; + + esp_mqtt_client_publish(client, get_topic(topic, received_value.device), + float_to_string(received_value.temperature, string), 0, 1, MQTT_RETAIN_TRUE); + } else { + ESP_LOGE(TAG, "mqtt_task(): Failed to receive the message from the temperature_queue"); + } + } else { + ESP_LOGE(TAG, "The temperature_queue has not been created yet"); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + } +} + +esp_err_t mqtt_init(void) +{ + const esp_mqtt_client_config_t mqtt_cfg = { + .broker.address.uri = CONFIG_BROKER_URL, + .broker.address.port = CONFIG_BROKER_PORT, + }; + + // NOTE: The parameter "client" must still exist when the created task executes. It must be static. + static esp_mqtt_client_handle_t client; + client = esp_mqtt_client_init(&mqtt_cfg); + + ESP_ERROR_CHECK(esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL)); + ESP_ERROR_CHECK(esp_mqtt_client_start(client)); + + BaseType_t status = xTaskCreate(mqtt_task, "mqtt_task", configMINIMAL_STACK_SIZE * 4, &client, PRIORITY_MIDDLE, + &mqtt_task_handle); + if (status != pdPASS) { + ESP_LOGE(TAG, "mqtt_task(): Task was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + vTaskSuspend(mqtt_task_handle); + + ESP_LOGI(TAG, "mqtt_init() finished successfully"); + + return ESP_OK; +} diff --git a/main/non_volatile_storage.c b/main/non_volatile_storage.c new file mode 100644 index 0000000..82e4a54 --- /dev/null +++ b/main/non_volatile_storage.c @@ -0,0 +1,16 @@ +#include "non_volatile_storage.h" + +#include "nvs_flash.h" + +#include "esp_check.h" +#include "esp_err.h" + +esp_err_t nvs_init(void) +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + return err; +} diff --git a/main/task_monitor.c b/main/task_monitor.c new file mode 100644 index 0000000..ab4affb --- /dev/null +++ b/main/task_monitor.c @@ -0,0 +1,180 @@ +#include "task_monitor.h" + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "sdkconfig.h" + +#if !defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) || !defined(CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS) + #error "USE_TRACE_FACILITY and GENERATE_RUN_TIME_STATS must be defined!" +#endif + +#define COLOR_BLACK "30" +#define COLOR_RED "31" +#define COLOR_GREEN "32" +#define COLOR_YELLOW "33" +#define COLOR_BLUE "34" +#define COLOR_PURPLE "35" +#define COLOR_CYAN "36" +#define COLOR_WHITE "37" + +#define COLOR(COLOR) "\033[0;" COLOR "m" +#define RESET_COLOR "\033[0m" +#define UNDERLINE "\033[4m" // TODO: FIX. Does not work! +#define BOLD "\033[1m" // TODO: FIX. Does not work! + +#define BLACK COLOR(COLOR_BLACK) +#define RED COLOR(COLOR_RED) +#define GREEN COLOR(COLOR_GREEN) +#define YELLOW COLOR(COLOR_YELLOW) +#define BLUE COLOR(COLOR_BLUE) +#define PURPLE COLOR(COLOR_PURPLE) +#define CYAN COLOR(COLOR_CYAN) +#define WHITE COLOR(COLOR_WHITE) + +static uint32_t get_current_time_ms(void) +{ + return xTaskGetTickCount() * 1000 / configTICK_RATE_HZ; +} + + +static float get_current_heap_free_percent(void) +{ + size_t current_size = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t total_size = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + return ((float) current_size / total_size) * 100.0; +} + +static float get_minimum_heap_free_percent(void) +{ + size_t minimum_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + size_t total_size = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + return ((float) minimum_size / total_size) * 100.0; +} + +static const char* int_to_string(int number, char *string) +{ + sprintf(string, "%d", number); + return string; +} + +static const char* task_state_to_string(eTaskState state) { + switch (state) { + case eRunning: + return "Running"; + case eReady: + return "Ready"; + case eBlocked: + return "Blocked"; + case eSuspended: + return "Suspended"; + case eDeleted: + return "Deleted"; + case eInvalid: + return "Invalid"; + default: + return "Unknown state"; + } +} + +static void sort_tasks_by_runtime(TaskStatus_t *tasks_status_array, size_t number_of_tasks) +{ + for (size_t i = 0; i < number_of_tasks - 1; ++i) { + for (size_t k = i + 1; k < number_of_tasks; ++k) { + if (tasks_status_array[k].ulRunTimeCounter > tasks_status_array[i].ulRunTimeCounter) { + TaskStatus_t temp = tasks_status_array[i]; + tasks_status_array[i] = tasks_status_array[k]; + tasks_status_array[k] = temp; + } + } + } +} + +static void task_status_monitor_task(void *params) +{ + while (true) { + UBaseType_t number_of_tasks = uxTaskGetNumberOfTasks(); + TaskStatus_t *p_tasks_status_array = pvPortMalloc(number_of_tasks * sizeof(TaskStatus_t)); + + if (p_tasks_status_array != NULL) { + uint32_t total_run_time; + number_of_tasks = uxTaskGetSystemState(p_tasks_status_array, number_of_tasks, &total_run_time); + + if (total_run_time > 0) { // Avoid divide by zero error + sort_tasks_by_runtime(p_tasks_status_array, number_of_tasks); + + printf("I (%lu) tm: " CYAN "%-18.16s %-11.10s %-7.6s %-8.7s %-11.10s %-12.10s %-15.15s %-s" + RESET_COLOR, + get_current_time_ms(), + "TASK NAME:", + "STATE:", + "CORE:", + "NUMBER:", + "PRIORITY:", + "STACK_MIN:", + "RUNTIME, µs:", + "RUNTIME, %:\n" + ); + + for (size_t i = 0; i < number_of_tasks; ++i) { + char string[10]; // 10 - maximum number of characters for int + printf("I (%lu) tm: " YELLOW "%-18.16s %-11.10s %-7.6s %-8d %-11d %-12lu %-14lu %-10.3f\n" + RESET_COLOR, + get_current_time_ms(), + p_tasks_status_array[i].pcTaskName, + task_state_to_string(p_tasks_status_array[i].eCurrentState), + + xTaskGetAffinity(p_tasks_status_array[i].xHandle) == tskNO_AFFINITY ? + "Any" : int_to_string((int)xTaskGetAffinity(p_tasks_status_array[i].xHandle), string), + + p_tasks_status_array[i].xTaskNumber, + p_tasks_status_array[i].uxCurrentPriority, + p_tasks_status_array[i].usStackHighWaterMark, + p_tasks_status_array[i].ulRunTimeCounter, + (p_tasks_status_array[i].ulRunTimeCounter * 100.0) / total_run_time); + } + + printf("I (%lu) tm: " YELLOW "Total heap free size: " GREEN "%d" YELLOW " bytes\n" RESET_COLOR, + get_current_time_ms(), heap_caps_get_total_size(MALLOC_CAP_DEFAULT)); + + printf("I (%lu) tm: " YELLOW "Current heap free size: " GREEN "%d" YELLOW " bytes (" GREEN "%.2f" + YELLOW " %%)\n" RESET_COLOR, get_current_time_ms(), heap_caps_get_free_size(MALLOC_CAP_DEFAULT), + get_current_heap_free_percent()); + + printf("I (%lu) tm: " YELLOW "Minimum heap free size: " GREEN "%d" YELLOW " bytes (" GREEN "%.2f" + YELLOW " %%)\n" RESET_COLOR, get_current_time_ms(), + heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT), get_minimum_heap_free_percent()); + + printf("I (%lu) tm: " YELLOW "Total RunTime: " GREEN "%lu" YELLOW " µs (" GREEN "%lu" YELLOW + " seconds)\n" RESET_COLOR, get_current_time_ms(), total_run_time, total_run_time / 1000000); + + uint64_t current_time = esp_timer_get_time(); + printf("I (%lu) tm: " YELLOW "System UpTime: " GREEN "%llu" YELLOW " µs (" GREEN "%llu" YELLOW + " seconds)\n\n" RESET_COLOR, get_current_time_ms(), current_time, current_time / 1000000); + } + vPortFree(p_tasks_status_array); + } else { + printf("I (%lu) tm: " RED "Could not allocate required memory\n" RESET_COLOR, get_current_time_ms()); + } + vTaskDelay(pdMS_TO_TICKS(30 * 1000)); + } +} + +esp_err_t task_monitor(void) +{ + BaseType_t status = xTaskCreatePinnedToCore(task_status_monitor_task, "monitor_task", configMINIMAL_STACK_SIZE * 4, + NULL, tskIDLE_PRIORITY + 1, NULL, tskNO_AFFINITY); + if (status != pdPASS) { + printf("I (%lu) tm: task_status_monitor_task(): Task was not created. Could not allocate required memory\n", + get_current_time_ms()); + return ESP_ERR_NO_MEM; + } + printf("I (%lu) tm: task_status_monitor_task() started successfully\n", get_current_time_ms()); + return ESP_OK; +} diff --git a/main/temperature.c b/main/temperature.c new file mode 100644 index 0000000..5d3d90e --- /dev/null +++ b/main/temperature.c @@ -0,0 +1,183 @@ +#include "temperature.h" + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "sdkconfig.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "onewire_bus.h" +#include "ds18b20.h" + +#include "types.h" + +static const char *TAG = "temperature"; + +typedef struct { + uint8_t device_num; + onewire_bus_handle_t handle; + uint8_t device_rom_id[CONFIG_ONEWIRE_NUMBER_OF_DEVICES][8]; +} ds18b20_task_params_t; + +QueueHandle_t temperature_queue = NULL; + +#define AVERAGE_ARRAY_SIZE 3 +static float get_average_temperature(size_t device, float temperature) +{ + static float average_array[CONFIG_ONEWIRE_NUMBER_OF_DEVICES][AVERAGE_ARRAY_SIZE]; + static bool is_average_array_init_with_nan = false; + + if (!is_average_array_init_with_nan) { + for (size_t i = 0; i < CONFIG_ONEWIRE_NUMBER_OF_DEVICES; ++i) { + for (size_t k = 0; k < AVERAGE_ARRAY_SIZE; ++k) { + average_array[i][k] = NAN; + } + } + is_average_array_init_with_nan = true; + } + + static size_t index[CONFIG_ONEWIRE_NUMBER_OF_DEVICES] = {0}; + average_array[device][index[device]] = temperature; + index[device] = (index[device] + 1) % AVERAGE_ARRAY_SIZE; + + size_t count = 0; + float average = 0.0; + for (size_t i = 0; i < AVERAGE_ARRAY_SIZE; ++i) { + if (!isnan(average_array[device][i])) { + average += average_array[device][i]; + count++; + } + } + return count > 0 ? average / (float)count : NAN; +} + +#define CHANGE_THRESHOLD 0.09 // °C +static bool is_temperature_changed(size_t device, float current_temperature) +{ + static float previous_temperature[CONFIG_ONEWIRE_NUMBER_OF_DEVICES] = {0.0}; + + bool is_changed = fabs(current_temperature - previous_temperature[device]) > (float)CHANGE_THRESHOLD; + if (is_changed) { + previous_temperature[device] = current_temperature; + } + return is_changed; +} + +static void ds18b20_task(void *params) +{ + // convert and read temperature + while (true) { + esp_err_t err; + ds18b20_task_params_t task_params = *(ds18b20_task_params_t*)params; + + vTaskDelay(pdMS_TO_TICKS(200)); + + // set all sensors' temperature conversion resolution + err = ds18b20_set_resolution(task_params.handle, NULL, DS18B20_RESOLUTION_12B); + if (err != ESP_OK) { + continue; + } + + // trigger all sensors to start temperature conversion + err = ds18b20_trigger_temperature_conversion(task_params.handle, NULL); // skip rom to send command to all devices on the bus + if (err != ESP_OK) { + continue; + } + + vTaskDelay(pdMS_TO_TICKS(800)); // 12-bit resolution needs 750ms to convert + + // get temperature from sensors + for (size_t device = 0; device < task_params.device_num; ++device) { + float temperature; + err = ds18b20_get_temperature(task_params.handle, task_params.device_rom_id[device], &temperature); // read scratchpad and get temperature + if (err != ESP_OK) { + continue; + } + ESP_LOGI(TAG, "Temperature of device " ONEWIRE_ROM_ID_STR ": %.2f°C", + ONEWIRE_ROM_ID(task_params.device_rom_id[device]), temperature); + + temperature = get_average_temperature(device, temperature); + + if (is_temperature_changed(device, temperature)) { + temperature_device_t temperature_device_to_send = { + .device = device, + .temperature = temperature, + }; + + BaseType_t status = xQueueSend(temperature_queue, &temperature_device_to_send, 0); + if (status != pdPASS) { + ESP_LOGW(TAG, "ds18b20_task(): Failed to send the message"); + } + } + } + vTaskDelay(pdMS_TO_TICKS(CONFIG_ONEWIRE_TEMPERATURE_UPDATE_TIME * 1000)); + } +} + +esp_err_t ds18b20_init(void) +{ + onewire_rmt_config_t config = { + .gpio_pin = CONFIG_ONEWIRE_DATA_GPIO_PIN, + .max_rx_bytes = 10, // 10 tx bytes (1byte ROM command + 8byte ROM number + 1byte device command) + }; + + // install new 1-wire bus + + // NOTE: The parameter "task_params" must still exist when the created task executes. It must be static. + static ds18b20_task_params_t task_params = { + .device_num = 0, + }; + + ESP_ERROR_CHECK(onewire_new_bus_rmt(&config, &task_params.handle)); + ESP_LOGI(TAG, "1-wire bus installed"); + + // create 1-wire rom search context + onewire_rom_search_context_handler_t context_handler; + ESP_ERROR_CHECK(onewire_rom_search_context_create(task_params.handle, &context_handler)); + + // search for devices on the bus + do { + esp_err_t search_result = onewire_rom_search(context_handler); + + if (search_result == ESP_ERR_INVALID_CRC) { + continue; // continue on crc error + } else if (search_result == ESP_FAIL || search_result == ESP_ERR_NOT_FOUND) { + break; // break on finish or no device + } + + ESP_ERROR_CHECK(onewire_rom_get_number(context_handler, task_params.device_rom_id[task_params.device_num])); + ESP_LOGI(TAG, "found device with rom id " ONEWIRE_ROM_ID_STR, + ONEWIRE_ROM_ID(task_params.device_rom_id[task_params.device_num])); + task_params.device_num++; + } while (task_params.device_num < CONFIG_ONEWIRE_NUMBER_OF_DEVICES); + + // delete 1-wire rom search context + ESP_ERROR_CHECK(onewire_rom_search_context_delete(context_handler)); + ESP_LOGI(TAG, "%d device%s found on 1-wire bus", task_params.device_num, task_params.device_num > 1 ? "s" : ""); + + if (task_params.device_num > 0) { + temperature_queue = xQueueCreate(task_params.device_num, sizeof(temperature_device_t)); + if (temperature_queue == NULL) { + ESP_LOGE(TAG, "temperature_queue: Queue was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + + BaseType_t status = xTaskCreate(ds18b20_task, "ds18b20_task", configMINIMAL_STACK_SIZE * 4, &task_params, + PRIORITY_HIGH, NULL); + if (status != pdPASS) { + ESP_LOGE(TAG, "ds18b20_task(): Task was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + } else { + ESP_ERROR_CHECK(onewire_del_bus(task_params.handle)); + ESP_LOGI(TAG, "1-wire bus deleted"); + } + + ESP_LOGI(TAG, "ds18b20_init() finished"); + return ESP_OK; +} diff --git a/main/wifi.c b/main/wifi.c new file mode 100644 index 0000000..67d73e5 --- /dev/null +++ b/main/wifi.c @@ -0,0 +1,178 @@ +#include "wifi.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_timer.h" +#include +#include "esp_event.h" +#include "esp_check.h" +#include "esp_log.h" + +static const char *TAG = "wifi"; + +#if CONFIG_ESP_WIFI_AUTH_OPEN +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN +#elif CONFIG_ESP_WIFI_AUTH_WEP +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP +#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK +#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK +#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK +#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK +#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK +#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK +#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK +#endif + +// FreeRTOS event group to signal when we are connected +static EventGroupHandle_t wifi_event_group = NULL; + +// The event group allows multiple bits for each event, but we only care about two events: +// - we are connected to the AP with an IP +// - we failed to connect after the maximum amount of retries +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 + +static esp_timer_handle_t wifi_reconnect_timer = NULL; + +static void wifi_reconnect_timer_callback(void* arg) +{ + esp_wifi_connect(); + ESP_LOGW(TAG, "Retry to connect to the Wi-Fi"); +} + +static void wifi_reconnect_timer_start(void) +{ + if (wifi_reconnect_timer == NULL) { + const esp_timer_create_args_t timer_args = { + .callback = wifi_reconnect_timer_callback, + .arg = NULL, + .name = "Wi-Fi reconnect timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &wifi_reconnect_timer)); + } + ESP_ERROR_CHECK(esp_timer_start_once(wifi_reconnect_timer, 5 * 1000000)); // 5 seconds +} + +static void wifi_reconnect_timer_stop(void) +{ + if (wifi_reconnect_timer != NULL) { + if (esp_timer_is_active(wifi_reconnect_timer)) { + ESP_ERROR_CHECK(esp_timer_stop(wifi_reconnect_timer)); + } + ESP_ERROR_CHECK(esp_timer_delete(wifi_reconnect_timer)); + wifi_reconnect_timer = NULL; + } +} + +static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + if (event_base == WIFI_EVENT) { + switch (event_id) { + case WIFI_EVENT_STA_START: + esp_wifi_connect(); + ESP_LOGI(TAG, "Trying to connect to the Wi-Fi"); + break; + case WIFI_EVENT_STA_DISCONNECTED: + if (wifi_event_group != NULL) { + xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT); + } + wifi_reconnect_timer_start(); + break; + default: + break; + } + } else if (event_base == IP_EVENT) { + switch (event_id) { + case IP_EVENT_STA_GOT_IP: + ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data; + ESP_LOGI(TAG, "Connected to the Wi-Fi. Got IP:" IPSTR, IP2STR(&event->ip_info.ip)); + if (wifi_event_group != NULL) { + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + } else { + wifi_reconnect_timer_stop(); + } + break; + default: + break; + } + } +} + +esp_err_t wifi_init(void) +{ + wifi_event_group = xEventGroupCreate(); + if (wifi_event_group == NULL) { + ESP_LOGE(TAG, "wifi_event_group: Event Group was not created. Could not allocate required memory"); + return ESP_ERR_NO_MEM; + } + + ESP_ERROR_CHECK(esp_netif_init()); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &event_handler, + NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &event_handler, + NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_ESP_WIFI_SSID, + .password = CONFIG_ESP_WIFI_PASSWORD, + // Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8). + // If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value + // to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to + // WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards. + .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD, + .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + // Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum + // number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above). + EventBits_t bits = xEventGroupWaitBits(wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + // xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event + // actually happened. + if (bits & WIFI_CONNECTED_BIT) { + //ESP_LOGI(TAG, "Connected to AP. SSID:%s, password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); + ESP_LOGI(TAG, "Connected to the Wi-Fi. SSID:%s, password:%s", wifi_config.sta.ssid, wifi_config.sta.password); + } else if (bits & WIFI_FAIL_BIT) { + //ESP_LOGE(TAG, "Failed to connect to AP. SSID:%s, password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); + ESP_LOGE(TAG, "Failed to connect to the Wi-Fi. SSID:%s, password:%s", wifi_config.sta.ssid, wifi_config.sta.password); + } else { + ESP_LOGE(TAG, "Unexpected event"); + } + vEventGroupDelete(wifi_event_group); + wifi_event_group = NULL; + + ESP_LOGI(TAG, "wifi_init() finished"); + return ESP_OK; +}