Compare commits

...

3 Commits

Author SHA1 Message Date
023644a887 new upgrade 2025-12-21 23:28:26 +00:00
82fa194bd8 v1.1 2025-12-10 12:59:55 +00:00
e6e2622a95 new module 2025-12-09 11:48:31 +00:00
169 changed files with 9978 additions and 12623 deletions

209
README.md
View File

@@ -1,85 +1,180 @@
![ESP32 EVSE](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/logo-full.svg) # ChargeFlow EVSE Firmware (ESP32, ESP-IDF 5.x)
J1772 EVSE firmware for ESP32 based devices. Firmware for an AC EVSE (EV charger) based on ESP32 and ESP-IDF 5.x, with:
![Build with ESP-IDF](https://github.com/dzurikmiroslav/esp32-evse/workflows/Build%20with%20ESP-IDF/badge.svg) - IEC-style EVSE state machine (Control Pilot A/B/C/D)
[![License](https://img.shields.io/github/license/dzurikmiroslav/esp32-evse.svg)](LICENSE.md) - Wi-Fi (STA + AP for local configuration)
- REST API served from SPIFFS
- Local authentication and OCPP integration
- Load balancing (master + slaves)
- Scheduler (time windows)
- Audible feedback (buzzer) and RGB LED status
- On-device ring-buffer logger
## Key features ---
- Hardware abstraction for device design
- Responsive web-interface
- OTA update
- Integrated energy meter
- Energy detection for relay control
- [REST](https://github.com/dzurikmiroslav/esp32-evse/wiki/Rest) API
- MQTT API
- [Modbus](https://github.com/dzurikmiroslav/esp32-evse/wiki/Modbus) (RS485, TCP)
- [Scripting](https://github.com/dzurikmiroslav/esp32-evse/wiki/Script)
- [Nextion HMI](https://github.com/dzurikmiroslav/esp32-evse/wiki/Nextion)
### Device definition method ## Features
_One firmware to rule them all._ Not really :-) one per device platform (ESP32, ESP32-S2...). ### Core EVSE
There is no need to compile the firmware for your EVSE design. - EVSE manager (`evse_manager`) coordinating:
Source code ist not hardcoded to GPIOs or other hardware design features. - Hardware layer (`evse_hardware`)
All code is written in ESP-IDF without additional mapping layer like Arduino. - State machine (`evse_state`)
- Error handling (`evse_error`)
- Energy metering (`evse_meter` / `meter_manager`)
- Session tracking (`evse_session`)
- Runs a periodic tick (`evse_manager_tick()`) in its own FreeRTOS task.
- Supports multiple auth modes (OPEN / RFID / OCPP), with scheduling and load-balancer aware logic.
All configuration is written outside firmware in configuration file named _board.cfg_ on dedicated partition. ### Networking & REST
For example, on following scheme is minimal EVSE circuit with ESP32 devkit.
![Minimal circuit](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/minimal-circuit.png) - Wi-Fi:
- Station mode for normal operation.
- Access point mode for local configuration, enabled by a physical button.
- REST server (`rest_main`) serving from `/data` SPIFFS mount:
- For configuration, status, logs, etc. (exact endpoints depend on your REST implementation).
For this circuit there is _board.cfg_, for more information's see [Wiki](https://github.com/dzurikmiroslav/esp32-evse/wiki/Board-config). ### Button & User Input
- One physical button (configured via `board_config`):
- **Short press** → Starts Wi-Fi AP mode for configuration.
- **Long press (~30s)** → Erases NVS and reboots (factory-like reset).
- Robust handling:
- ISR with software debounce and spinlock.
- Dedicated `user_input_task` that receives button press/release notifications via `xTaskNotify`.
```bash ### Storage
#Device name
DEVICE_NAME=ESP32 minimal EVSE
#Button
BUTTON_WIFI_GPIO=0
#Pilot
PILOT_PWM_GPIO=33
PILOT_ADC_CHANNEL=7
PILOT_DOWN_THRESHOLD_12=2410
PILOT_DOWN_THRESHOLD_9=2104
PILOT_DOWN_THRESHOLD_6=1797
PILOT_DOWN_THRESHOLD_3=1491
PILOT_DOWN_THRESHOLD_N12=265
#AC relay
AC_RELAY_GPIO=32
```
### Web interface - SPIFFS used for:
- `/cfg` partition: persistent configuration.
- `/data` partition: web assets, runtime data, logs, etc.
- Two separate mounts:
- `cfg_conf``/cfg` (label: `cfg`)
- `data_conf``/data` (label: `data`)
Fully responsive web interface is accessible local network IP address on port 80. ### LED Subsystem
Dashboard page - RGB LED driven by LEDC:
- `ledc_driver` abstracts LEDC timer + channels.
- `led` module maps EVSE state & sessions to colors/patterns.
- LED patterns per EVSE state:
- **IDLE** → Green solid.
- **WAITING** (vehicle plugged, not charging) → Blue slow blink.
- **CHARGING** → Blue “breathing” effect.
- **FAULT** → Red fast blink.
- Session effects:
- Distinct visual patterns when a session starts/finishes.
- Uses a one-shot timer and a dedicated effect state machine.
![Dashboard](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard.png) ### Buzzer
Settings page - Buzzer with multiple patterns (`buzzer` + `buzzer_events`):
- Plugged/unplugged, card read/denied, AP start, charging, fault, etc.
- Supported modes:
- Active buzzer (ON/OFF).
- Passive buzzer with LEDC PWM (frequency & duty configurable).
- Features:
- Central queue + dedicated `buzzer_task`.
- Quiet hours support (optionally suppress non-critical sounds at night).
- Anti-spam mechanism to avoid excessively frequent beeps.
- Integrated with:
- EVSE events (state changes & faults)
- Auth events (RFID card success/denied/added)
- Network events (AP/STA up)
![Settings](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-settings.png) ### Load Balancer
Mobile dashboard page - `loadbalancer` component:
- Monitors GRID meter and EVSE meter via `meter_events`.
- Supports one master + up to 255 slaves (connectors array).
- Fair distribution of current with:
- Headroom calculation based on grid limit and measured current.
- Min current guarantees (e.g. 6 A) using a “water-filling” algorithm.
- Session-age based priority (oldest sessions first).
- Per-connector hysteresis and LB suspension/resume flags.
- Publishes limits via `LOADBALANCER_EVENTS`:
- `LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT`
- `LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT`
- Fail-safe behavior:
- If GRID meter data times out, clamps connectors to minimum safe current instead of ramping up.
![Dashboard mobile](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard-mobile.png) ### Scheduler
## Hardware - Scheduler component (`scheduler`) emits `SCHED_EVENTS` with `allowed_now` flag:
- EVSE manager revokes authorization when the window closes.
- In OPEN mode, automatic re-authorization only happens when scheduler allows.
### ESP32DevkitC ### OCPP
Dev board with basic functionality, single phase energy meter, RS485. One side pcb, for DIY makers easy to make at home conditions ;-) - `ocpp` module integration:
- Listens to OCPP events (`OCPP_EVENTS`).
- Handles:
- RemoteStart/Stop
- Authorization results
- ChangeAvailability (operative/inoperative) → mapped into local `enabled` config.
- EVSE manager mediates OCPP decisions with scheduler + load balancer.
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32-devkit-evse) ### Logger
![ESP32DevkitC](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32devkitc.jpg) - `logger` + `output_buffer` components:
- Central log sink with ring buffer in RAM.
- Thread-safe via FreeRTOS mutex.
- Integrated with ESP log system via `esp_log_set_vprintf(logger_vprintf);`
- Optionally mirrors to UART (controlled via `CONFIG_ESP_CONSOLE_UART`).
- Simple reader API:
- Iterate entries using an index.
- Handy for exposing logs over REST/Web UI.
### ESP32-S2 DIY ALPHA ---
ESP32-S2 based EVSE with advanced functionality, three phase energy meter, RS485, UART, 1WIRE, RCM, socket lock. ## Project Structure (Relevant Parts)
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32s2-diy-evse) Approximate layout (names may vary slightly in your repo):
![ESP32-S2-DA](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32s2da.jpg) ```text
main/
main.c # System entrypoint, button setup, module init
components/
evse/
evse_manager.c/.h # High-level EVSE orchestration
evse_state.c/.h # State machine & events
evse_error.c/.h # Error handling
evse_hardware.c/.h # Hardware abstraction
evse_session.c/.h # Session metrics
loadbalancer/
src/
loadbalancer.c
loadbalancer_events.c
input_filter.c
include/
loadbalancer.h
loadbalancer_events.h
input_filter.h
buzzer/
src/
buzzer.c
buzzer_events.c
include/
buzzer.h
buzzer_events.h
led/
src/
led.c
ledc_driver.c
include/
led.h
ledc_driver.h
logger/
src/
logger.c
output_buffer.c
include/
logger.h
output_buffer.h
# ... other modules: auth, ocpp, scheduler, meter_manager, evse_link, etc.

View File

@@ -1,12 +1,12 @@
DEVICE_NAME=Custom EVSE DEVICE_NAME=ChargeFlow
#LEDs #LEDs
LED_CHARGING=n led_blue=n
LED_CHARGING_GPIO= led_blue_GPIO=
LED_ERROR=n led_red=n
LED_ERROR_GPIO= led_red_GPIO=
LED_STOP=n led_green=n
LED_STOP_GPIO= led_green_GPIO=
#Button #Button
BUTTON_WIFI_GPIO=32 BUTTON_WIFI_GPIO=32
@@ -35,32 +35,3 @@ SOCKET_LOCK=n
SOCKET_LOCK_A_GPIO= SOCKET_LOCK_A_GPIO=
SOCKET_LOCK_B_GPIO= SOCKET_LOCK_B_GPIO=
SOCKET_LOCK_DETECTION_GPIO= SOCKET_LOCK_DETECTION_GPIO=
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=none
ENERGY_METER_THREE_PHASES=n
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=
ENERGY_METER_L2_CUR_ADC_CHANNEL=
ENERGY_METER_L3_CUR_ADC_CHANNEL=
ENERGY_METER_CUR_SCALE=
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=
ENERGY_METER_L2_VLT_ADC_CHANNEL=
ENERGY_METER_L3_VLT_ADC_CHANNEL=
ENERGY_METER_VLT_SCALE=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART 1
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=UART 2
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=

View File

@@ -1,12 +1,12 @@
DEVICE_NAME=Plixin Evse DEVICE_NAME=ChargeFlow
#LEDs #LEDs
LED_CHARGING=y led_blue=y
LED_CHARGING_GPIO=14 led_blue_GPIO=14
LED_ERROR=y led_red=y
LED_ERROR_GPIO=26 led_red_GPIO=26
LED_STOP=y led_green=y
LED_STOP_GPIO=12 led_green_GPIO=12
#BUZZER #BUZZER
BUZZER=y BUZZER=y
@@ -42,84 +42,3 @@ SOCKET_LOCK=n
SOCKET_LOCK_A_GPIO= SOCKET_LOCK_A_GPIO=
SOCKET_LOCK_B_GPIO= SOCKET_LOCK_B_GPIO=
SOCKET_LOCK_DETECTION_GPIO= SOCKET_LOCK_DETECTION_GPIO=
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=none
ENERGY_METER_THREE_PHASES=n
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=7
ENERGY_METER_L2_CUR_ADC_CHANNEL=
ENERGY_METER_L3_CUR_ADC_CHANNEL=
ENERGY_METER_CUR_SCALE=0.090909091
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=
ENERGY_METER_L2_VLT_ADC_CHANNEL=
ENERGY_METER_L3_VLT_ADC_CHANNEL=
ENERGY_METER_VLT_SCALE=0.47
#AUX
AUX_IN_1=n
AUX_IN_1_NAME=
AUX_IN_1_GPIO=
AUX_IN_2=n
AUX_IN_2_NAME=
AUX_IN_2_GPIO=
AUX_IN_3=n
AUX_IN_3_NAME=
AUX_IN_3_GPIO=
AUX_IN_4=n
AUX_IN_4_NAME=
AUX_IN_4_GPIO=
AUX_OUT_1=n
AUX_OUT_1_NAME=
AUX_OUT_1_GPIO=
AUX_OUT_2=n
AUX_OUT_2_NAME=
AUX_OUT_2_GPIO=
AUX_OUT_3=n
AUX_OUT_3_NAME=
AUX_OUT_3_GPIO=
AUX_OUT_4=n
AUX_OUT_4_NAME=
AUX_OUT_4_GPIO=
AUX_AIN_1=n
AUX_AIN_1_NAME=
AUX_AIN_1_ADC_CHANNEL=
AUX_AIN_2=n
AUX_AIN_2_NAME=
AUX_AIN_2_ADC_CHANNEL=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART via USB
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=RS485
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=
SERIAL_3=none
SERIAL_3_NAME=UART
SERIAL_3_RXD_GPIO=
SERIAL_3_TXD_GPIO=
SERIAL_3_RTS_GPIO=
#Onewire devices
ONEWIRE=n
ONEWIRE_GPIO=
ONEWIRE_TEMP_SENSOR=n

View File

@@ -1,12 +1,12 @@
DEVICE_NAME=ESP32-S2-DA EVSE DEVICE_NAME=ChargeFlow
#LEDs #LEDs
LED_CHARGING=y led_blue=y
LED_CHARGING_GPIO=36 led_blue_GPIO=36
LED_ERROR=y led_red=y
LED_ERROR_GPIO=37 led_red_GPIO=37
LED_STOP=y led_green=y
LED_STOP_GPIO=35 led_green_GPIO=35
#Button #Button
BUTTON_WIFI_GPIO=32 BUTTON_WIFI_GPIO=32
@@ -42,78 +42,3 @@ SOCKET_LOCK_MIN_BREAK_TIME=1000
RCM=n RCM=n
RCM_GPIO=41 RCM_GPIO=41
RCM_TEST_GPIO=26 RCM_TEST_GPIO=26
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=cur_vlt
ENERGY_METER_THREE_PHASES=y
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=4
ENERGY_METER_L2_CUR_ADC_CHANNEL=5
ENERGY_METER_L3_CUR_ADC_CHANNEL=6
ENERGY_METER_CUR_SCALE=0.090909091
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=7
ENERGY_METER_L2_VLT_ADC_CHANNEL=8
ENERGY_METER_L3_VLT_ADC_CHANNEL=9
ENERGY_METER_VLT_SCALE=0.47
#AUX
AUX_IN_1=n
AUX_IN_1_NAME=
AUX_IN_1_GPIO=
AUX_IN_2=n
AUX_IN_2_NAME=
AUX_IN_2_GPIO=
AUX_IN_3=n
AUX_IN_3_NAME=
AUX_IN_3_GPIO=
AUX_IN_4=n
AUX_IN_4_NAME=
AUX_IN_4_GPIO=
AUX_OUT_1=n
AUX_OUT_1_NAME=
AUX_OUT_1_GPIO=
AUX_OUT_2=n
AUX_OUT_2_NAME=
AUX_OUT_2_GPIO=
AUX_OUT_3=n
AUX_OUT_3_NAME=
AUX_OUT_3_GPIO=
AUX_OUT_4=n
AUX_OUT_4_NAME=
AUX_OUT_4_GPIO=
AUX_AIN_1=n
AUX_AIN_1_NAME=
AUX_AIN_1_ADC_CHANNEL=
AUX_AIN_2=n
AUX_AIN_2_NAME=
AUX_AIN_2_ADC_CHANNEL=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=RS-485
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=
#Onewire devices
ONEWIRE=n
ONEWIRE_GPIO=
ONEWIRE_TEMP_SENSOR=n

View File

@@ -3,5 +3,5 @@ set(srcs "src/auth_types.c" "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c"
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src" PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash driver esp_timer PRIV_REQUIRES driver esp_timer
REQUIRES esp_event esp_idf_lib_helpers evse ocpp) REQUIRES esp_event evse ocpp evse_link storage_service)

View File

@@ -1,22 +1,25 @@
// components/auth/src/auth.c
#include "auth.h" #include "auth.h"
#include "auth_events.h" #include "auth_events.h"
#include "esp_event.h" #include "esp_event.h"
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/queue.h> #include <freertos/queue.h>
#include <freertos/task.h>
#include <esp_log.h> #include <esp_log.h>
#include <esp_err.h>
#include <string.h> #include <string.h>
#include <strings.h> // <-- necessário para strcasecmp #include <strings.h>
#include <stdio.h>
#include "wiegand_reader.h" #include "wiegand_reader.h"
#include "nvs_flash.h"
#include "nvs.h" #include "storage_service.h"
#include "esp_random.h"
#include "evse_link.h"
#include "evse_link_events.h"
#define MAX_TAGS 50 #define MAX_TAGS 50
static const char *TAG = "Auth"; static const char *TAG = "Auth";
/* ===== Estado ===== */ /* ===== Estado ===== */
@@ -25,229 +28,379 @@ static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN]; static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0; static int tag_count = 0;
static uint32_t s_next_req_id = 1; static uint32_t s_next_req_id = 1;
static bool s_wiegand_started = false;
/* ===== NVS keys ===== */ /* ===== Storage keys ===== */
#define NVS_NAMESPACE "auth" #define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_" #define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count" #define NVS_TAG_COUNT_KEY "count"
#define NVS_MODE_KEY "mode" // uint8_t #define NVS_MODE_KEY "mode" // uint8_t
/* ========================= // timeout para operações sync do storage
* NVS Persistence (tags) #define STORAGE_TO pdMS_TO_TICKS(2000)
* ========================= */
static void load_tags_from_nvs(void) {
nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) {
ESP_LOGW(TAG, "No stored tags in NVS");
return;
}
uint8_t count = 0;
if (nvs_get_u8(handle, NVS_TAG_COUNT_KEY, &count) != ESP_OK) {
nvs_close(handle);
return;
}
tag_count = 0;
for (int i = 0; i < count && i < MAX_TAGS; i++) {
char key[16];
char tag_buf[AUTH_TAG_MAX_LEN];
size_t len = sizeof(tag_buf);
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
if (nvs_get_str(handle, key, tag_buf, &len) == ESP_OK) {
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
}
}
nvs_close(handle);
ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count);
}
static void save_tags_to_nvs(void) {
nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open NVS to save tags");
return;
}
nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
for (int i = 0; i < tag_count; i++) {
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
nvs_set_str(handle, key, valid_tags[i]);
}
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count);
}
/* =========================
* NVS Persistence (mode)
* ========================= */
static void load_mode_from_nvs(void) {
nvs_handle_t h;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &h) == ESP_OK) {
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
if (nvs_get_u8(h, NVS_MODE_KEY, &u) == ESP_OK) {
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID) s_mode = (auth_mode_t)u;
}
nvs_close(h);
} else {
ESP_LOGW(TAG, "No stored auth mode in NVS; default OPEN");
}
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}
static void save_mode_to_nvs(auth_mode_t mode) {
nvs_handle_t h;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
} else {
ESP_LOGE(TAG, "Failed to save auth mode to NVS");
}
}
/* ========================= /* =========================
* Helpers * Helpers
* ========================= */ * ========================= */
static bool is_tag_valid(const char *tag) { static bool is_tag_valid(const char *tag)
for (int i = 0; i < tag_count; i++) { {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
return true; return true;
} }
}
return false; return false;
} }
/* =========================
* Storage Persistence (tags)
* ========================= */
static void load_tags_from_storage(void)
{
uint8_t count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
{
ESP_LOGD(TAG, "No stored tags (count not found)");
tag_count = 0;
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Failed to read tag count (%s)", esp_err_to_name(err));
tag_count = 0;
return;
}
tag_count = 0;
for (int i = 0; i < (int)count && i < MAX_TAGS; i++)
{
char key[16];
char tag_buf[AUTH_TAG_MAX_LEN] = {0};
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = storage_get_str_sync(NVS_NAMESPACE, key, tag_buf, sizeof(tag_buf), STORAGE_TO);
if (err == ESP_OK)
{
if (tag_buf[0] != '\0')
{
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
}
}
else if (err == ESP_ERR_NOT_FOUND)
{
// pode acontecer se count estiver desfasado; ignora
continue;
}
else
{
ESP_LOGW(TAG, "Failed to load tag %d (%s)", i, esp_err_to_name(err));
}
}
ESP_LOGI(TAG, "Loaded %d tags from storage", tag_count);
}
static void save_tags_to_storage(void)
{
// ler count antigo (para apagar keys antigas se removemos tags)
uint8_t old_count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &old_count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
old_count = 0;
// grava count + tags
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, (uint8_t)tag_count);
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_set_str_async(NVS_NAMESPACE, key, valid_tags[i]);
}
// se removemos tags: apagar chaves antigas
if (old_count > (uint8_t)tag_count)
{
for (int i = tag_count; i < (int)old_count && i < MAX_TAGS; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_erase_key_async(NVS_NAMESPACE, key);
}
}
// opcional: forçar commit “já”
(void)storage_flush_async();
ESP_LOGD(TAG, "Tags saved to storage (%d tags)", tag_count);
}
/* =========================
* Storage Persistence (mode)
* ========================= */
static void load_mode_from_storage(void)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_MODE_KEY, &u, STORAGE_TO);
if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
else
s_mode = AUTH_MODE_OPEN;
}
else if (err == ESP_ERR_NOT_FOUND)
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGD(TAG, "No stored mode -> default OPEN");
}
else
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGW(TAG, "Failed to read mode (%s) -> default OPEN", esp_err_to_name(err));
}
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}
static void save_mode_to_storage(auth_mode_t mode)
{
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_MODE_KEY, (uint8_t)mode);
(void)storage_flush_async(); // opcional: commit mais rápido
ESP_LOGD(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}
/* =========================
* Bridge: EVSE-Link -> AUTH (remote AUTH_GRANTED no slave)
* ========================= */
static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != EVSE_LINK_EVENTS || id != LINK_EVENT_REMOTE_AUTH_GRANTED || data == NULL)
return;
const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data;
if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE)
return;
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true;
ESP_LOGD(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
esp_err_t err = esp_event_post(
AUTH_EVENTS,
AUTH_EVENT_TAG_PROCESSED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to post AUTH_EVENT_TAG_PROCESSED (remote grant): %s",
esp_err_to_name(err));
}
}
/* ========================= /* =========================
* Public API * Public API
* ========================= */ * ========================= */
void auth_init(void) { void auth_init(void)
load_mode_from_nvs(); {
load_tags_from_nvs(); // garantir que o storage service está pronto
ESP_ERROR_CHECK(storage_service_init());
if (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID) { load_mode_from_storage();
load_tags_from_storage();
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand)
{
initWiegand(); initWiegand();
s_wiegand_started = true;
ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode)); ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
} else { }
else
{
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started"); ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
} }
// bridge EVSE-Link -> AUTH
{
esp_err_t err = esp_event_handler_register(
EVSE_LINK_EVENTS,
LINK_EVENT_REMOTE_AUTH_GRANTED,
on_remote_auth_grant,
NULL);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to register EVSE-Link auth grant handler: %s",
esp_err_to_name(err));
}
}
auth_mode_event_data_t evt = {.mode = s_mode}; auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode)); ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
} }
void auth_set_mode(auth_mode_t mode) { void auth_set_mode(auth_mode_t mode)
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID) { {
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID)
{
ESP_LOGW(TAG, "Invalid mode: %d", (int)mode); ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
return; return;
} }
if (mode == s_mode) {
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode)); if (mode == s_mode)
{
ESP_LOGD(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return; return;
} }
auth_mode_t old = s_mode;
s_mode = mode; s_mode = mode;
save_mode_to_nvs(mode); save_mode_to_storage(mode);
// Nota: se precisares, aqui podes parar/iniciar o Wiegand consoante o modo. bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (s_mode == AUTH_MODE_OPEN) {
ESP_LOGI(TAG, "Mode set to OPEN"); if (need_wiegand && !s_wiegand_started)
} else { {
ESP_LOGI(TAG, "Mode set to %s; ensure Wiegand reader is running", auth_mode_to_str(s_mode)); ESP_LOGD(TAG, "Mode changed %s -> %s, starting Wiegand",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
initWiegand();
s_wiegand_started = true;
}
else if (!need_wiegand && s_wiegand_started)
{
ESP_LOGD(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
else
{
ESP_LOGD(TAG, "Mode changed %s -> %s, no change in Wiegand state",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
} }
auth_mode_event_data_t evt = {.mode = s_mode}; auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY);
} }
auth_mode_t auth_get_mode(void) { auth_mode_t auth_get_mode(void)
{
return s_mode; return s_mode;
} }
bool auth_add_tag(const char *tag) { bool auth_add_tag(const char *tag)
if (tag_count >= MAX_TAGS) return false; {
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false; if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN)
if (is_tag_valid(tag)) return true; // já existe return false;
if (tag_count >= MAX_TAGS)
return false;
if (is_tag_valid(tag))
return true;
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1); strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++; tag_count++;
save_tags_to_nvs(); save_tags_to_storage();
ESP_LOGI(TAG, "Tag added: %s", tag); ESP_LOGD(TAG, "Tag added: %s", tag);
return true; return true;
} }
bool auth_remove_tag(const char *tag) { bool auth_remove_tag(const char *tag)
for (int i = 0; i < tag_count; i++) { {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { if (!tag)
for (int j = i; j < tag_count - 1; j++) { return false;
for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
{
for (int j = i; j < tag_count - 1; j++)
{
strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN); strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN);
valid_tags[j][AUTH_TAG_MAX_LEN - 1] = '\0';
} }
tag_count--; tag_count--;
save_tags_to_nvs(); save_tags_to_storage();
ESP_LOGI(TAG, "Tag removed: %s", tag); ESP_LOGD(TAG, "Tag removed: %s", tag);
return true; return true;
} }
} }
return false; return false;
} }
bool auth_tag_exists(const char *tag) { bool auth_tag_exists(const char *tag)
{
if (!tag)
return false;
return is_tag_valid(tag); return is_tag_valid(tag);
} }
void auth_list_tags(void) { void auth_list_tags(void)
ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); {
for (int i = 0; i < tag_count; i++) { ESP_LOGD(TAG, "Registered Tags (%d):", tag_count);
ESP_LOGI(TAG, "- %s", valid_tags[i]); for (int i = 0; i < tag_count; i++)
} ESP_LOGD(TAG, "- %s", valid_tags[i]);
} }
void auth_wait_for_tag_registration(void) { void auth_wait_for_tag_registration(void)
if (s_mode != AUTH_MODE_LOCAL_RFID) { {
if (s_mode != AUTH_MODE_LOCAL_RFID)
{
ESP_LOGW(TAG, "Registration is only available in LOCAL mode"); ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
return; return;
} }
waiting_for_registration = true; waiting_for_registration = true;
ESP_LOGI(TAG, "Tag registration mode enabled."); ESP_LOGD(TAG, "Tag registration mode enabled.");
} }
void auth_process_tag(const char *tag) { void auth_process_tag(const char *tag)
if (!tag || !*tag) { {
if (!tag || !*tag)
{
ESP_LOGW(TAG, "NULL/empty tag received"); ESP_LOGW(TAG, "NULL/empty tag received");
return; return;
} }
switch (s_mode) { switch (s_mode)
case AUTH_MODE_OPEN: { {
// Sem verificação; normalmente nem é necessário evento. case AUTH_MODE_OPEN:
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag); ESP_LOGD(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break; break;
}
case AUTH_MODE_LOCAL_RFID: { case AUTH_MODE_LOCAL_RFID:
if (waiting_for_registration) { {
if (waiting_for_registration)
{
waiting_for_registration = false; waiting_for_registration = false;
if (auth_add_tag(tag)) { if (auth_add_tag(tag))
{
auth_tag_event_data_t ev = {0}; auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1); strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true; ev.authorized = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &ev, sizeof(ev), portMAX_DELAY);
ESP_LOGI(TAG, "Tag registered: %s", tag); esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
} else { &ev, sizeof(ev), portMAX_DELAY);
ESP_LOGD(TAG, "Tag registered: %s", tag);
}
else
{
ESP_LOGW(TAG, "Failed to register tag: %s", tag); ESP_LOGW(TAG, "Failed to register tag: %s", tag);
} }
return; return;
@@ -255,30 +408,42 @@ void auth_process_tag(const char *tag) {
auth_tag_event_data_t ev = {0}; auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1); strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = is_tag_valid(tag); ev.authorized = is_tag_valid(tag);
ESP_LOGI(TAG, "LOCAL tag %s: %s", tag, ev.authorized ? "AUTHORIZED" : "DENIED"); ESP_LOGD(TAG, "LOCAL tag %s: %s", tag,
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, &ev, sizeof(ev), portMAX_DELAY); ev.authorized ? "AUTHORIZED" : "DENIED");
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
&ev, sizeof(ev), portMAX_DELAY);
break; break;
} }
case AUTH_MODE_OCPP_RFID: { case AUTH_MODE_OCPP_RFID:
// Não decide localmente. Pede validação ao OCPP. {
auth_tag_verify_event_t rq = {0}; auth_tag_verify_event_t rq = {0};
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1); strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
rq.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
rq.req_id = s_next_req_id++; rq.req_id = s_next_req_id++;
ESP_LOGI(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)", rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, &rq, sizeof(rq), portMAX_DELAY); ESP_LOGD(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
&rq, sizeof(rq), portMAX_DELAY);
break; break;
} }
} }
} }
int auth_get_tag_count(void) { int auth_get_tag_count(void)
{
return tag_count; return tag_count;
} }
const char *auth_get_tag_by_index(int index) { const char *auth_get_tag_by_index(int index)
if (index < 0 || index >= tag_count) return NULL; {
if (index < 0 || index >= tag_count)
return NULL;
return valid_tags[index]; return valid_tags[index];
} }

View File

@@ -1,3 +1,4 @@
// === Início de: components/auth/src/wiegand.c ===
/** /**
* @file wiegand.c * @file wiegand.c
* *
@@ -6,9 +7,13 @@
#include <esp_log.h> #include <esp_log.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <esp_idf_lib_helpers.h> #include <esp_attr.h> // <- para IRAM_ATTR
#include "wiegand.h" #include "wiegand.h"
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif
static const char *TAG = "wiegand"; static const char *TAG = "wiegand";
#define TIMER_INTERVAL_US 50000 // 50ms #define TIMER_INTERVAL_US 50000 // 50ms
@@ -39,11 +44,7 @@ static void isr_enable(wiegand_reader_t *reader)
gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE); gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE);
} }
#if HELPER_TARGET_IS_ESP32
static void IRAM_ATTR isr_handler(void *arg) static void IRAM_ATTR isr_handler(void *arg)
#else
static void isr_handler(void *arg)
#endif
{ {
wiegand_reader_t *reader = (wiegand_reader_t *)arg; wiegand_reader_t *reader = (wiegand_reader_t *)arg;
if (!reader->enabled) if (!reader->enabled)
@@ -181,3 +182,5 @@ esp_err_t wiegand_reader_done(wiegand_reader_t *reader)
return ESP_OK; return ESP_OK;
} }
// === Fim de: components/auth/src/wiegand.c ===

View File

@@ -1,23 +1,23 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <freertos/queue.h> #include <freertos/queue.h>
#include <esp_log.h> #include <esp_log.h>
#include <wiegand.h> #include <wiegand.h>
#include "auth.h" #include "auth.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#define CONFIG_EXAMPLE_BUF_SIZE 50 #define CONFIG_EXAMPLE_BUF_SIZE 50
#define IDTAG_MAX_LEN 20 #define IDTAG_MAX_LEN 20
static const char *TAG = "WiegandReader"; static const char *TAG = "WiegandReader";
// ---- Parâmetro global/locale para controlar a convenção de paridade ---- // ---- Parâmetro global/local para controlar a convenção de paridade ----
static const bool PARITY_INVERTED = false; // mude para true se o seu leitor vier "invertido" static const bool PARITY_INVERTED = false; // mude para true se o seu leitor vier "invertido"
static wiegand_reader_t reader; static wiegand_reader_t reader;
@@ -35,7 +35,7 @@ static inline uint8_t get_bit_msb_first(const uint8_t *buf, size_t bit_index)
return (buf[bit_index / 8] >> (7 - (bit_index % 8))) & 0x01; return (buf[bit_index / 8] >> (7 - (bit_index % 8))) & 0x01;
} }
// Versão parametrizável // Versão parametrizável de verificação de paridade
static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert) static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
{ {
if (bits != 26 && bits != 34) if (bits != 26 && bits != 34)
@@ -55,8 +55,8 @@ static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
if (get_bit_msb_first(buf, i)) if (get_bit_msb_first(buf, i))
right++; right++;
} }
else else // 34 bits
{ // 34 {
for (int i = 1; i <= 16; i++) for (int i = 1; i <= 16; i++)
if (get_bit_msb_first(buf, i)) if (get_bit_msb_first(buf, i))
left++; left++;
@@ -117,24 +117,28 @@ static bool wiegand_extract_fc_cn(const uint8_t *buf, size_t bits, uint32_t *fc,
{ {
if (bits != 26 && bits != 34) if (bits != 26 && bits != 34)
return false; return false;
uint32_t payload = 0; uint32_t payload = 0;
size_t payload_bits = bits - 2; size_t payload_bits = bits - 2;
for (size_t i = 0; i < payload_bits; i++) for (size_t i = 0; i < payload_bits; i++)
{ {
size_t bit = 1 + i; // ignora bit de paridade inicial size_t bit = 1 + i; // ignora bit de paridade inicial
uint8_t bitval = (buf[bit / 8] >> (7 - (bit % 8))) & 0x01; uint8_t bitval = (buf[bit / 8] >> (7 - (bit % 8))) & 0x01;
payload = (payload << 1) | bitval; payload = (payload << 1) | bitval;
} }
if (bits == 26) if (bits == 26)
{ {
*fc = (payload >> 16) & 0xFF; // 8b *fc = (payload >> 16) & 0xFF; // 8b
*cn = payload & 0xFFFF; // 16b *cn = payload & 0xFFFF; // 16b
} }
else else // 34 bits
{ {
*fc = (payload >> 16) & 0xFFFF; // 16b *fc = (payload >> 16) & 0xFFFF; // 16b
*cn = payload & 0xFFFF; // 16b *cn = payload & 0xFFFF; // 16b
} }
return true; return true;
} }
@@ -145,10 +149,14 @@ static bool build_idtag_w26_4B(uint32_t fc, uint32_t cn, char *out, size_t outle
(uint8_t)((cn >> 8) & 0xFF), (uint8_t)((cn >> 8) & 0xFF),
(uint8_t)(cn & 0xFF), (uint8_t)(cn & 0xFF),
0}; 0};
raw[3] = crc8_atm(raw, 3); raw[3] = crc8_atm(raw, 3);
if (outlen < 9) if (outlen < 9)
return false; return false;
int n = snprintf(out, outlen, "%02X%02X%02X%02X", raw[0], raw[1], raw[2], raw[3]);
int n = snprintf(out, outlen, "%02X%02X%02X%02X",
raw[0], raw[1], raw[2], raw[3]);
return (n > 0 && (size_t)n < outlen); return (n > 0 && (size_t)n < outlen);
} }
@@ -160,31 +168,42 @@ static bool build_idtag_w34_7B(uint32_t fc, uint32_t cn, char *out, size_t outle
raw[2] = (uint8_t)(fc & 0xFF); raw[2] = (uint8_t)(fc & 0xFF);
raw[3] = (uint8_t)((cn >> 8) & 0xFF); raw[3] = (uint8_t)((cn >> 8) & 0xFF);
raw[4] = (uint8_t)(cn & 0xFF); raw[4] = (uint8_t)(cn & 0xFF);
uint16_t crc = crc16_ibm(raw, 5); uint16_t crc = crc16_ibm(raw, 5);
raw[5] = (uint8_t)((crc >> 8) & 0xFF); raw[5] = (uint8_t)((crc >> 8) & 0xFF);
raw[6] = (uint8_t)(crc & 0xFF); raw[6] = (uint8_t)(crc & 0xFF);
if (outlen < 15) if (outlen < 15)
return false; return false;
int n = snprintf(out, outlen, "%02X%02X%02X%02X%02X%02X%02X", int n = snprintf(out, outlen, "%02X%02X%02X%02X%02X%02X%02X",
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]); raw[0], raw[1], raw[2], raw[3],
raw[4], raw[5], raw[6]);
return (n > 0 && (size_t)n < outlen); return (n > 0 && (size_t)n < outlen);
} }
// Se o callback for de ISR, troque para xQueueSendToBackFromISR. // Callback chamado pelo esp_timer_task (contexto de task, NÃO ISR)
static void reader_callback(wiegand_reader_t *r) static void reader_callback(wiegand_reader_t *r)
{ {
if (!queue)
{
ESP_LOGW(TAG, "Queue not ready, dropping packet");
return;
}
data_packet_t p = {0}; data_packet_t p = {0};
p.bits = r->bits; p.bits = r->bits;
p.bytes = (r->bits + 7) / 8; p.bytes = (r->bits + 7) / 8;
if (p.bytes > sizeof(p.data)) if (p.bytes > sizeof(p.data))
p.bytes = sizeof(p.data); p.bytes = sizeof(p.data);
memcpy(p.data, r->buf, p.bytes); memcpy(p.data, r->buf, p.bytes);
BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (xQueueSendToBack(queue, &p, 0) != pdPASS)
// Se NÃO for ISR, use xQueueSendToBack(queue, &p, 0); {
xQueueSendToBackFromISR(queue, &p, &xHigherPriorityTaskWoken); ESP_LOGW(TAG, "Queue full, dropping Wiegand packet");
if (xHigherPriorityTaskWoken) }
portYIELD_FROM_ISR();
} }
static void wiegand_task(void *arg) static void wiegand_task(void *arg)
@@ -197,13 +216,20 @@ static void wiegand_task(void *arg)
return; return;
} }
ESP_ERROR_CHECK(wiegand_reader_init(&reader, 21, 22, ESP_ERROR_CHECK(wiegand_reader_init(&reader,
true, CONFIG_EXAMPLE_BUF_SIZE, reader_callback, WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST)); 21, 22, // GPIO D0, D1
true, // internal pullups
CONFIG_EXAMPLE_BUF_SIZE,
reader_callback,
WIEGAND_MSB_FIRST,
WIEGAND_LSB_FIRST));
data_packet_t p; data_packet_t p;
for (;;) for (;;)
{ {
ESP_LOGI(TAG, "Waiting for Wiegand data..."); ESP_LOGI(TAG, "Waiting for Wiegand data...");
if (xQueueReceive(queue, &p, portMAX_DELAY) != pdPASS) if (xQueueReceive(queue, &p, portMAX_DELAY) != pdPASS)
continue; continue;
@@ -216,7 +242,7 @@ static void wiegand_task(void *arg)
continue; continue;
} }
char tag[21] = {0}; // OCPP 1.6: máx 20 chars (+NUL) char tag[IDTAG_MAX_LEN + 1] = {0}; // OCPP 1.6: máx 20 chars (+NUL)
uint32_t fc = 0, cn = 0; uint32_t fc = 0, cn = 0;
if (!wiegand_extract_fc_cn(p.data, p.bits, &fc, &cn)) if (!wiegand_extract_fc_cn(p.data, p.bits, &fc, &cn))
@@ -230,8 +256,8 @@ static void wiegand_task(void *arg)
{ {
ok = build_idtag_w26_4B(fc, cn, tag, sizeof(tag)); // 8 hex ok = build_idtag_w26_4B(fc, cn, tag, sizeof(tag)); // 8 hex
} }
else else // 34
{ // 34 {
ok = build_idtag_w34_7B(fc, cn, tag, sizeof(tag)); // 14 hex ok = build_idtag_w34_7B(fc, cn, tag, sizeof(tag)); // 14 hex
} }
@@ -250,9 +276,9 @@ static void wiegand_sim_task(void *arg)
{ {
// lista fixa de idTags simuladas // lista fixa de idTags simuladas
static const char *idtaglist[] = { static const char *idtaglist[] = {
"0000004134962107", "00000041349",
"W2602312345", "W2602312345",
"W34ABCDE12345", "W34ABCDE123",
}; };
const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]); const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]);
@@ -263,7 +289,7 @@ static void wiegand_sim_task(void *arg)
ESP_LOGI(TAG, "Simulação -> idTag: %s", idtaglist[i]); ESP_LOGI(TAG, "Simulação -> idTag: %s", idtaglist[i]);
auth_process_tag(idtaglist[i]); auth_process_tag(idtaglist[i]);
// espera 20 segundos entre cada tag // espera 30 segundos entre cada tag
vTaskDelay(pdMS_TO_TICKS(30000)); vTaskDelay(pdMS_TO_TICKS(30000));
} }
} }
@@ -274,6 +300,8 @@ void initWiegand(void)
ESP_LOGI(TAG, "Initializing Wiegand reader"); ESP_LOGI(TAG, "Initializing Wiegand reader");
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL); xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);
// Para testes, podes ativar o simulador:
// ESP_LOGI(TAG, "Inicializando Wiegand simulado"); // ESP_LOGI(TAG, "Inicializando Wiegand simulado");
// xTaskCreate(wiegand_sim_task, "WiegandSim", configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL); // xTaskCreate(wiegand_sim_task, "WiegandSim",
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
} }

View File

@@ -8,5 +8,5 @@ idf_component_register(
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src" PRIV_INCLUDE_DIRS "src"
REQUIRES esp_event REQUIRES esp_event
PRIV_REQUIRES driver nvs_flash esp_timer evse PRIV_REQUIRES driver esp_timer evse network
) )

View File

@@ -18,50 +18,32 @@
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
// ===================== Configuração padrão (pode migrar para Kconfig) ===================== // ===================== Configuração padrão =====================
#ifndef CONFIG_BUZZER_GPIO
#define CONFIG_BUZZER_GPIO GPIO_NUM_27 #define CONFIG_BUZZER_GPIO GPIO_NUM_27
#endif
#ifndef CONFIG_BUZZER_MODE_PASSIVE // 1 = PASSIVE (PWM), 0 = ACTIVE (on/off) // 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
#define CONFIG_BUZZER_MODE_PASSIVE 1 #define CONFIG_BUZZER_MODE_PASSIVE 0
#endif
#ifndef CONFIG_BUZZER_FREQ_HZ
#define CONFIG_BUZZER_FREQ_HZ 3500 #define CONFIG_BUZZER_FREQ_HZ 3500
#endif
#ifndef CONFIG_BUZZER_DUTY_PCT
#define CONFIG_BUZZER_DUTY_PCT 70 #define CONFIG_BUZZER_DUTY_PCT 70
#endif
#ifndef CONFIG_BUZZER_QUEUE_LEN
#define CONFIG_BUZZER_QUEUE_LEN 8 #define CONFIG_BUZZER_QUEUE_LEN 8
#endif
#ifndef CONFIG_BUZZER_TASK_STACK
#define CONFIG_BUZZER_TASK_STACK 2048 #define CONFIG_BUZZER_TASK_STACK 2048
#endif
#ifndef CONFIG_BUZZER_TASK_PRIO
#define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1) #define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1)
#endif
#ifndef CONFIG_BUZZER_MIN_GAP_MS // anti-spam (gap mínimo entre toques) // anti-spam (gap mínimo entre toques)
#define CONFIG_BUZZER_MIN_GAP_MS 70 #define CONFIG_BUZZER_MIN_GAP_MS 70
#endif
#ifndef CONFIG_BUZZER_ENABLE_DEFAULT
#define CONFIG_BUZZER_ENABLE_DEFAULT 1 #define CONFIG_BUZZER_ENABLE_DEFAULT 1
#endif
#ifndef CONFIG_BUZZER_QUIET_START_MIN // quiet hours start (minutos desde 00:00) // quiet hours start (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_START_MIN (22 * 60) #define CONFIG_BUZZER_QUIET_START_MIN (22 * 60)
#endif
#ifndef CONFIG_BUZZER_QUIET_END_MIN // quiet hours end (minutos desde 00:00) // quiet hours end (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_END_MIN (7 * 60) #define CONFIG_BUZZER_QUIET_END_MIN (7 * 60)
#endif
// ===================== Tipos e estado ===================== // ===================== Tipos e estado =====================
static const char *TAG = "Buzzer"; static const char *TAG = "Buzzer";
@@ -415,11 +397,25 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
{ {
if (base != NETWORK_EVENTS) if (base != NETWORK_EVENTS)
return; return;
if (id == NETWORK_EVENT_AP_STARTED)
ESP_LOGD(TAG, "Network event id=%d", (int)id);
buzzer_event_data_t evt = {0};
switch ((network_event_id_t)id)
{ {
buzzer_event_data_t evt = {.pattern = BUZZER_PATTERN_AP_START}; case NETWORK_EVENT_AP_STARTED:
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY); case NETWORK_EVENT_STA_CONNECTED:
// Usa padrão de AP_START para indicar rede disponível
evt.pattern = BUZZER_PATTERN_AP_START;
break;
default:
// Para já, ignorar outros eventos de rede
return;
} }
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY);
} }
static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data) static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
@@ -506,7 +502,7 @@ void buzzer_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, network_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler, NULL));
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d", ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d",
s_buzzer_cfg.gpio, s_buzzer_cfg.gpio,
@@ -522,7 +518,7 @@ void buzzer_deinit(void)
(void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler); (void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler); (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler); (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler);
(void)esp_event_handler_unregister(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, network_event_handler); (void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler);
if (s_buzzer_q) if (s_buzzer_q)
{ {

View File

@@ -2,5 +2,5 @@ set(srcs
"board_config.c") "board_config.c")
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES nvs_flash PRIV_REQUIRES
INCLUDE_DIRS "include") INCLUDE_DIRS "include")

View File

@@ -15,32 +15,6 @@ bool atob(const char *value)
return value[0] == 'y'; return value[0] == 'y';
} }
board_config_energy_meter_t atoem(const char *value)
{
if (!strcmp(value, "cur"))
{
return BOARD_CONFIG_ENERGY_METER_CUR;
}
if (!strcmp(value, "cur_vlt"))
{
return BOARD_CONFIG_ENERGY_METER_CUR_VLT;
}
return BOARD_CONFIG_ENERGY_METER_NONE;
}
board_config_serial_t atoser(const char *value)
{
if (!strcmp(value, "uart"))
{
return BOARD_CONFIG_SERIAL_UART;
}
if (!strcmp(value, "rs485"))
{
return BOARD_CONFIG_SERIAL_RS485;
}
return BOARD_CONFIG_SERIAL_NONE;
}
#define SET_CONFIG_VALUE(name, prop, convert_fn) \ #define SET_CONFIG_VALUE(name, prop, convert_fn) \
if (!strcmp(key, name)) \ if (!strcmp(key, name)) \
{ \ { \
@@ -94,16 +68,14 @@ void board_config_load()
if (value != NULL) if (value != NULL)
{ {
SET_CONFIG_VALUE_STR("DEVICE_NAME", device_name); SET_CONFIG_VALUE_STR("DEVICE_NAME", device_name);
SET_CONFIG_VALUE("LED_CHARGING", led_charging, atob); SET_CONFIG_VALUE("led_blue", led_blue, atob);
SET_CONFIG_VALUE("LED_CHARGING_GPIO", led_charging_gpio, atoi); SET_CONFIG_VALUE("led_blue_GPIO", led_blue_gpio, atoi);
SET_CONFIG_VALUE("LED_ERROR", led_error, atob); SET_CONFIG_VALUE("led_red", led_red, atob);
SET_CONFIG_VALUE("LED_ERROR_GPIO", led_error_gpio, atoi); SET_CONFIG_VALUE("led_red_GPIO", led_red_gpio, atoi);
SET_CONFIG_VALUE("LED_STOP", led_stop, atob); SET_CONFIG_VALUE("led_green", led_green, atob);
SET_CONFIG_VALUE("LED_STOP_GPIO", led_stop_gpio, atoi); SET_CONFIG_VALUE("led_green_GPIO", led_green_gpio, atoi);
SET_CONFIG_VALUE("BUZZER", buzzer, atob); SET_CONFIG_VALUE("BUZZER", buzzer, atob);
SET_CONFIG_VALUE("BUZZER_GPIO", buzzer_gpio, atoi); SET_CONFIG_VALUE("BUZZER_GPIO", buzzer_gpio, atoi);
SET_CONFIG_VALUE("BUTTON_WIFI_GPIO", button_wifi_gpio, atoi); SET_CONFIG_VALUE("BUTTON_WIFI_GPIO", button_wifi_gpio, atoi);
SET_CONFIG_VALUE("PILOT_PWM_GPIO", pilot_pwm_gpio, atoi); SET_CONFIG_VALUE("PILOT_PWM_GPIO", pilot_pwm_gpio, atoi);
SET_CONFIG_VALUE("PILOT_ADC_CHANNEL", pilot_adc_channel, atoi); SET_CONFIG_VALUE("PILOT_ADC_CHANNEL", pilot_adc_channel, atoi);
@@ -130,76 +102,6 @@ void board_config_load()
SET_CONFIG_VALUE("RCM", rcm, atob); SET_CONFIG_VALUE("RCM", rcm, atob);
SET_CONFIG_VALUE("RCM_GPIO", rcm_gpio, atoi); SET_CONFIG_VALUE("RCM_GPIO", rcm_gpio, atoi);
SET_CONFIG_VALUE("RCM_TEST_GPIO", rcm_test_gpio, atoi); SET_CONFIG_VALUE("RCM_TEST_GPIO", rcm_test_gpio, atoi);
SET_CONFIG_VALUE("ENERGY_METER", energy_meter, atoem);
SET_CONFIG_VALUE("ENERGY_METER_THREE_PHASES", energy_meter_three_phases, atob);
SET_CONFIG_VALUE("ENERGY_METER_L1_CUR_ADC_CHANNEL", energy_meter_l1_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L2_CUR_ADC_CHANNEL", energy_meter_l2_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L3_CUR_ADC_CHANNEL", energy_meter_l3_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_CUR_SCALE", energy_meter_cur_scale, atoff);
SET_CONFIG_VALUE("ENERGY_METER_L1_VLT_ADC_CHANNEL", energy_meter_l1_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L2_VLT_ADC_CHANNEL", energy_meter_l2_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L3_VLT_ADC_CHANNEL", energy_meter_l3_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_VLT_SCALE", energy_meter_vlt_scale, atoff);
SET_CONFIG_VALUE("AUX_IN_1", aux_in_1, atob);
SET_CONFIG_VALUE_STR("AUX_IN_1_NAME", aux_in_1_name);
SET_CONFIG_VALUE("AUX_IN_1_GPIO", aux_in_1_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_2", aux_in_2, atob);
SET_CONFIG_VALUE_STR("AUX_IN_2_NAME", aux_in_2_name);
SET_CONFIG_VALUE("AUX_IN_2_GPIO", aux_in_2_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_3", aux_in_3, atob);
SET_CONFIG_VALUE_STR("AUX_IN_3_NAME", aux_in_3_name);
SET_CONFIG_VALUE("AUX_IN_3_GPIO", aux_in_3_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_4", aux_in_4, atob);
SET_CONFIG_VALUE_STR("AUX_IN_4_NAME", aux_in_4_name);
SET_CONFIG_VALUE("AUX_IN_4_GPIO", aux_in_4_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_1", aux_out_1, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_1_NAME", aux_out_1_name);
SET_CONFIG_VALUE("AUX_OUT_1_GPIO", aux_out_1_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_2", aux_out_2, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_2_NAME", aux_out_2_name);
SET_CONFIG_VALUE("AUX_OUT_2_GPIO", aux_out_2_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_3", aux_out_3, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_3_NAME", aux_out_3_name);
SET_CONFIG_VALUE("AUX_OUT_3_GPIO", aux_out_3_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_4", aux_out_4, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_4_NAME", aux_out_4_name);
SET_CONFIG_VALUE("AUX_OUT_4_GPIO", aux_out_4_gpio, atoi);
SET_CONFIG_VALUE("AUX_AIN_1", aux_ain_1, atob);
SET_CONFIG_VALUE_STR("AUX_AIN_1_NAME", aux_ain_1_name);
SET_CONFIG_VALUE("AUX_AIN_1_ADC_CHANNEL", aux_ain_1_adc_channel, atoi);
SET_CONFIG_VALUE("AUX_AIN_2", aux_ain_2, atob);
SET_CONFIG_VALUE_STR("AUX_AIN_2_NAME", aux_ain_2_name);
SET_CONFIG_VALUE("AUX_AIN_2_ADC_CHANNEL", aux_ain_2_adc_channel, atoi);
/*
#if CONFIG_ESP_CONSOLE_UART_NUM != 0
SET_CONFIG_VALUE("SERIAL_1", serial_1, atoser);
SET_CONFIG_VALUE_STR("SERIAL_1_NAME", serial_1_name);
SET_CONFIG_VALUE("SERIAL_1_RXD_GPIO", serial_1_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_1_TXD_GPIO", serial_1_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_1_RTS_GPIO", serial_1_rts_gpio, atoi);
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 0
#if CONFIG_ESP_CONSOLE_UART_NUM != 1
SET_CONFIG_VALUE("SERIAL_2", serial_2, atoser);
SET_CONFIG_VALUE_STR("SERIAL_2_NAME", serial_2_name);
SET_CONFIG_VALUE("SERIAL_2_RXD_GPIO", serial_2_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_2_TXD_GPIO", serial_2_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_2_RTS_GPIO", serial_2_rts_gpio, atoi);
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 1
#if SOC_UART_NUM > 2
#if CONFIG_ESP_CONSOLE_UART_NUM != 2
SET_CONFIG_VALUE("SERIAL_3", serial_3, atoser);
SET_CONFIG_VALUE_STR("SERIAL_3_NAME", serial_3_name);
SET_CONFIG_VALUE("SERIAL_3_RXD_GPIO", serial_3_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_3_TXD_GPIO", serial_3_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_3_RTS_GPIO", serial_3_rts_gpio, atoi);
#endif / CONFIG_ESP_CONSOLE_UART_NUM != 2
#endif // SOC_UART_NUM > 2
SET_CONFIG_VALUE("ONEWIRE", onewire, atob);
SET_CONFIG_VALUE("ONEWIRE_GPIO", onewire_gpio, atoi);
SET_CONFIG_VALUE("ONEWIRE_TEMP_SENSOR", onewire_temp_sensor, atob);
ESP_LOGE(TAG, "Unknown config value %s=%s", key, value);
*/
} }
} }
} }

View File

@@ -5,13 +5,15 @@
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "soc/soc_caps.h" #include "soc/soc_caps.h"
typedef enum { typedef enum
{
BOARD_CONFIG_ENERGY_METER_NONE, BOARD_CONFIG_ENERGY_METER_NONE,
BOARD_CONFIG_ENERGY_METER_CUR, BOARD_CONFIG_ENERGY_METER_CUR,
BOARD_CONFIG_ENERGY_METER_CUR_VLT BOARD_CONFIG_ENERGY_METER_CUR_VLT
} board_config_energy_meter_t; } board_config_energy_meter_t;
typedef enum { typedef enum
{
BOARD_CONFIG_SERIAL_NONE, BOARD_CONFIG_SERIAL_NONE,
BOARD_CONFIG_SERIAL_UART, BOARD_CONFIG_SERIAL_UART,
BOARD_CONFIG_SERIAL_RS485 BOARD_CONFIG_SERIAL_RS485
@@ -21,12 +23,12 @@ typedef struct
{ {
char device_name[32]; char device_name[32];
bool led_charging : 1; bool led_blue : 1;
gpio_num_t led_charging_gpio; gpio_num_t led_blue_gpio;
bool led_error : 1; bool led_red : 1;
gpio_num_t led_error_gpio; gpio_num_t led_red_gpio;
bool led_stop : 1; bool led_green : 1;
gpio_num_t led_stop_gpio; gpio_num_t led_green_gpio;
bool buzzer : 1; bool buzzer : 1;
gpio_num_t buzzer_gpio; gpio_num_t buzzer_gpio;
@@ -63,80 +65,6 @@ typedef struct
gpio_num_t rcm_gpio; gpio_num_t rcm_gpio;
gpio_num_t rcm_test_gpio; gpio_num_t rcm_test_gpio;
board_config_energy_meter_t energy_meter;
bool energy_meter_three_phases : 1;
adc_channel_t energy_meter_l1_cur_adc_channel;
adc_channel_t energy_meter_l2_cur_adc_channel;
adc_channel_t energy_meter_l3_cur_adc_channel;
float energy_meter_cur_scale;
adc_channel_t energy_meter_l1_vlt_adc_channel;
adc_channel_t energy_meter_l2_vlt_adc_channel;
adc_channel_t energy_meter_l3_vlt_adc_channel;
float energy_meter_vlt_scale;
bool aux_in_1 : 1;
char aux_in_1_name[8];
gpio_num_t aux_in_1_gpio;
bool aux_in_2 : 1;
char aux_in_2_name[8];
gpio_num_t aux_in_2_gpio;
bool aux_in_3 : 1;
char aux_in_3_name[8];
gpio_num_t aux_in_3_gpio;
bool aux_in_4 : 1;
char aux_in_4_name[8];
gpio_num_t aux_in_4_gpio;
bool aux_out_1 : 1;
char aux_out_1_name[8];
gpio_num_t aux_out_1_gpio;
bool aux_out_2 : 1;
char aux_out_2_name[8];
gpio_num_t aux_out_2_gpio;
bool aux_out_3 : 1;
char aux_out_3_name[8];
gpio_num_t aux_out_3_gpio;
bool aux_out_4 : 1;
char aux_out_4_name[8];
gpio_num_t aux_out_4_gpio;
bool aux_ain_1 : 1;
char aux_ain_1_name[8];
adc_channel_t aux_ain_1_adc_channel;
bool aux_ain_2 : 1;
char aux_ain_2_name[8];
adc_channel_t aux_ain_2_adc_channel;
board_config_serial_t serial_1;
char serial_1_name[16];
gpio_num_t serial_1_rxd_gpio;
gpio_num_t serial_1_txd_gpio;
gpio_num_t serial_1_rts_gpio;
board_config_serial_t serial_2;
char serial_2_name[16];
gpio_num_t serial_2_rxd_gpio;
gpio_num_t serial_2_txd_gpio;
gpio_num_t serial_2_rts_gpio;
#if SOC_UART_NUM > 2
board_config_serial_t serial_3;
char serial_3_name[16];
gpio_num_t serial_3_rxd_gpio;
gpio_num_t serial_3_txd_gpio;
gpio_num_t serial_3_rts_gpio;
#endif /* SOC_UART_NUM > 2 */
bool onewire : 1;
gpio_num_t onewire_gpio;
bool onewire_temp_sensor : 1;
} board_config_t; } board_config_t;
extern board_config_t board_config; extern board_config_t board_config;

View File

@@ -1,20 +0,0 @@
name: esp_idf_lib_helpers
description: Common support library for esp-idf-lib
version: 1.2.0
groups:
- common
code_owners:
- trombik
- UncleRus
depends:
- freertos
thread_safe: n/a
targets:
- esp32
- esp8266
- esp32s2
- esp32c3
license: ISC
copyrights:
- name: trombik
year: 2019

View File

@@ -1,4 +0,0 @@
idf_component_register(
INCLUDE_DIRS .
REQUIRES freertos
)

View File

@@ -1,13 +0,0 @@
Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,8 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS = .
ifdef CONFIG_IDF_TARGET_ESP8266
COMPONENT_DEPENDS = esp8266 freertos
else
COMPONENT_DEPENDS = freertos
endif

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if !defined(__ESP_IDF_LIB_HELPERS__H__)
#define __ESP_IDF_LIB_HELPERS__H__
/* XXX this header file does not need to include freertos/FreeRTOS.h.
* but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct
* order. as this header depends on sdkconfig.h, sdkconfig.h must be included
* first. however, the SDK includes this header first, then includes
* `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an
* evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed
* build.
*/
#include <freertos/FreeRTOS.h>
#include <esp_idf_version.h>
#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL)
#error Unknown ESP-IDF/ESP8266 RTOS SDK version
#endif
/* Minimal supported version for ESP32, ESP32S2 */
#define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5)
/* Minimal supported version for ESP8266 */
#define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0)
/* HELPER_TARGET_IS_ESP32
* 1 when the target is esp32
*/
#if defined(CONFIG_IDF_TARGET_ESP32) \
|| defined(CONFIG_IDF_TARGET_ESP32S2) \
|| defined(CONFIG_IDF_TARGET_ESP32S3) \
|| defined(CONFIG_IDF_TARGET_ESP32C2) \
|| defined(CONFIG_IDF_TARGET_ESP32C3) \
|| defined(CONFIG_IDF_TARGET_ESP32C6) \
|| defined(CONFIG_IDF_TARGET_ESP32H2)
#define HELPER_TARGET_IS_ESP32 (1)
#define HELPER_TARGET_IS_ESP8266 (0)
/* HELPER_TARGET_IS_ESP8266
* 1 when the target is esp8266
*/
#elif defined(CONFIG_IDF_TARGET_ESP8266)
#define HELPER_TARGET_IS_ESP32 (0)
#define HELPER_TARGET_IS_ESP8266 (1)
#else
#error BUG: cannot determine the target
#endif
#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER
#error Unsupported ESP-IDF version. Please update!
#endif
#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER
#error Unsupported ESP8266 RTOS SDK version. Please update!
#endif
/* show the actual values for debugging */
#if DEBUG
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32H2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266))
#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR))
#endif
#endif

View File

@@ -1,21 +0,0 @@
#if CONFIG_IDF_TARGET_ESP32
#include <esp32/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C2
#include <esp32c2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C3
#include <esp32c3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C6
#include <esp32c6/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H2
#include <esp32h2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H4
#include <esp32h4/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S2
#include <esp32s2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S3
#include <esp32s3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP8266
#include <rom/ets_sys.h>
#else
#error "ets_sys: Unknown target"
#endif

View File

@@ -17,6 +17,6 @@ set(srcs
idf_component_register( idf_component_register(
SRCS ${srcs} SRCS ${srcs}
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver PRIV_REQUIRES driver
REQUIRES peripherals auth loadbalancer REQUIRES peripherals auth loadbalancer scheduler storage_service
) )

View File

@@ -1,216 +1,289 @@
#include <inttypes.h> // For PRI macros #include <inttypes.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "evse_config.h" #include "evse_config.h"
#include "board_config.h" #include "board_config.h"
#include "evse_limits.h" #include "evse_limits.h"
#include "evse_api.h" // <— para evse_get_state / evse_state_is_charging #include "evse_api.h"
#include "evse_state.h"
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "esp_err.h"
#include "esp_timer.h" #include "esp_check.h"
#include "storage_service.h"
static const char *TAG = "evse_config"; static const char *TAG = "evse_config";
#define NVS_NAMESPACE "evse_config"
static nvs_handle_t nvs;
// ======================== // ========================
// Configurable parameters // 3 variáveis (semântica simples)
// ======================== // ========================
static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
static uint16_t charging_current; // Persisted (NVS) // 1) Hardware (FIXO)
static uint16_t charging_current_runtime = 0; // Runtime only static const uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
static bool socket_outlet;
static bool rcm; // 2) Configurável (persistido)
static uint16_t charging_current = MAX_CHARGING_CURRENT_LIMIT;
// 3) Runtime (RAM)
static uint16_t charging_current_runtime = 0;
// Outros parâmetros (persistidos)
static bool socket_outlet = false;
static bool rcm = false;
static uint8_t temp_threshold = 60; static uint8_t temp_threshold = 60;
static bool require_auth;
// Availability / Enable flags // Availability / Enable flags (persistidos)
static bool is_available = true; static bool is_available = true;
static bool is_enabled = true; static bool is_enabled = true;
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
// Ajusta conforme o teu boot:
// 1000ms pode ser curto com Wi-Fi/FS/tasks; 2000ms é mais robusto em produto.
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(2000); }
// ======================== // ========================
// Initialization // Initialization
// ======================== // ========================
esp_err_t evse_config_init(void) esp_err_t evse_config_init(void)
{ {
ESP_LOGD(TAG, "Initializing NVS configuration..."); // garante storage iniciado
return nvs_open("evse", NVS_READWRITE, &nvs); ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
ESP_LOGI(TAG, "EVSE config init OK (storage-backed)");
return ESP_OK;
} }
void evse_check_defaults(void) void evse_check_defaults(void)
{ {
esp_err_t err; esp_err_t err;
uint8_t u8; uint8_t u8 = 0;
uint16_t u16; uint16_t u16 = 0;
uint32_t u32;
bool needs_commit = false;
uint8_t u8_bool;
ESP_LOGD(TAG, "Checking default parameters..."); // Timeouts: leitura e escrita no boot
const TickType_t rd_to = BOOT_TO();
const TickType_t wr_to = TO_TICKS_MS(2000);
// Max charging current ESP_LOGD(TAG, "Checking default parameters (sync persistence)...");
err = nvs_get_u8(nvs, "max_chrg_curr", &u8);
if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT)
{
max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
nvs_set_u8(nvs, "max_chrg_curr", max_charging_current);
needs_commit = true;
ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current);
}
else
{
max_charging_current = u8;
}
// -----------------------------------------
// Charging current (default, persisted) // Charging current (default, persisted)
err = nvs_get_u16(nvs, "def_chrg_curr", &u16); // -----------------------------------------
if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT) || u16 > (max_charging_current)) err = storage_get_u16_sync(NVS_NAMESPACE, "def_chrg_curr", &u16, rd_to);
if (err != ESP_OK || u16 < MIN_CHARGING_CURRENT_LIMIT || u16 > max_charging_current)
{ {
charging_current = max_charging_current; charging_current = max_charging_current;
nvs_set_u16(nvs, "def_chrg_curr", charging_current);
needs_commit = true; esp_err_t se = storage_set_u16_sync(NVS_NAMESPACE, "def_chrg_curr", charging_current, wr_to);
ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current); if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist def_chrg_curr=%u: %s",
(unsigned)charging_current, esp_err_to_name(se));
// seguimos com RAM correta; persist pode falhar por flash/partição
}
ESP_LOGW(TAG, "Invalid/missing def_chrg_curr (%s) -> reset to %u (sync persisted)",
esp_err_to_name(err), (unsigned)charging_current);
} }
else else
{ {
charging_current = u16; charging_current = u16;
} }
// Runtime charging current initialized from persisted default // runtime inicializa a partir do default
charging_current_runtime = max_charging_current; charging_current_runtime = charging_current;
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime); ESP_LOGD(TAG, "Runtime charging current initialized from default: %u",
(unsigned)charging_current_runtime);
// Auth required // -----------------------------------------
err = nvs_get_u8(nvs, "require_auth", &u8); // Socket outlet (persisted) + capability gate
require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false; // -----------------------------------------
if (err != ESP_OK) err = storage_get_u8_sync(NVS_NAMESPACE, "socket_outlet", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{ {
nvs_set_u8(nvs, "require_auth", require_auth); bool wanted = (u8 != 0);
needs_commit = true;
if (wanted && !board_config.proximity)
{
// NVS dizia 1, mas HW não suporta -> runtime false e persistir 0
socket_outlet = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet=0 (capability mismatch): %s",
esp_err_to_name(se));
} }
// Socket outlet ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
err = nvs_get_u8(nvs, "socket_outlet", &u8);
socket_outlet = (err == ESP_OK && u8) && board_config.proximity;
if (err != ESP_OK)
{
nvs_set_u8(nvs, "socket_outlet", socket_outlet);
needs_commit = true;
}
// RCM
err = nvs_get_u8(nvs, "rcm", &u8);
rcm = (err == ESP_OK && u8) && board_config.rcm;
if (err != ESP_OK)
{
nvs_set_u8(nvs, "rcm", rcm);
needs_commit = true;
}
// Temp threshold
err = nvs_get_u8(nvs, "temp_threshold", &u8);
temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60;
if (err != ESP_OK)
{
nvs_set_u8(nvs, "temp_threshold", temp_threshold);
needs_commit = true;
}
// Optional limits
if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK)
evse_set_consumption_limit(u32);
if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK)
evse_set_charging_time_limit(u32);
if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK)
evse_set_under_power_limit(u16);
// Availability (persist)
if (nvs_get_u8(nvs, "available", &u8_bool) == ESP_OK && u8_bool <= 1)
{
is_available = (u8_bool != 0);
} }
else else
{ {
is_available = true; // default socket_outlet = wanted;
nvs_set_u8(nvs, "available", (uint8_t)is_available);
needs_commit = true;
ESP_LOGW(TAG, "Missing 'available' -> default=true (persisted).");
} }
// Enabled (persist)
if (nvs_get_u8(nvs, "enabled", &u8_bool) == ESP_OK && u8_bool <= 1)
{
is_enabled = (u8_bool != 0);
} }
else else
{ {
is_enabled = true; // default socket_outlet = false;
nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
needs_commit = true; esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted)."); if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet default=0: %s", esp_err_to_name(se));
} }
// Save to NVS if needed ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
if (needs_commit) esp_err_to_name(err));
}
// -----------------------------------------
// RCM (persisted) + capability gate
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "rcm", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{ {
err = nvs_commit(nvs); bool wanted = (u8 != 0);
if (err == ESP_OK)
if (wanted && !board_config.rcm)
{ {
ESP_LOGD(TAG, "Configuration committed to NVS."); rcm = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm=0 (capability mismatch): %s",
esp_err_to_name(se));
}
ESP_LOGW(TAG, "rcm requested but HW has no RCM -> forcing false (sync persisted)");
} }
else else
{ {
ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); rcm = wanted;
} }
} }
else
{
rcm = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm default=0: %s", esp_err_to_name(se));
}
ESP_LOGW(TAG, "Missing/invalid rcm (%s) -> default=false (sync persisted).",
esp_err_to_name(err));
}
// -----------------------------------------
// Temp threshold (persisted)
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "temp_threshold", &u8, rd_to);
if (err == ESP_OK && u8 >= 40 && u8 <= 80)
{
temp_threshold = u8;
}
else
{
temp_threshold = 60;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "temp_threshold", temp_threshold, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist temp_threshold=%u: %s",
(unsigned)temp_threshold, esp_err_to_name(se));
}
ESP_LOGW(TAG, "Invalid/missing temp_threshold (%s) -> default=60 (sync persisted).",
esp_err_to_name(err));
}
// -----------------------------------------
// Availability (persisted) [0/1]
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "available", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
is_available = (u8 != 0);
}
else
{
is_available = true;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "available", 1, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist available=1: %s", esp_err_to_name(se));
}
ESP_LOGW(TAG, "Missing/invalid 'available' (%s) -> default=true (sync persisted).",
esp_err_to_name(err));
}
// -----------------------------------------
// Enabled (persisted) [0/1]
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "enabled", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
is_enabled = (u8 != 0);
}
else
{
is_enabled = true;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "enabled", 1, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist enabled=1: %s", esp_err_to_name(se));
}
ESP_LOGW(TAG, "Missing/invalid 'enabled' (%s) -> default=true (sync persisted).",
esp_err_to_name(err));
}
// Flush explícito no boot:
// - ajuda a garantir commit determinístico antes do resto do sistema avançar
// - mantém-se útil mesmo com setters sync se o teu storage ainda estiver com debounce interno
esp_err_t fe = storage_flush_sync(wr_to);
if (fe != ESP_OK)
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
} }
// ======================== // ========================
// Charging current getters/setters // Charging current getters/setters
// ======================== // ========================
uint8_t evse_get_max_charging_current(void) uint8_t evse_get_max_charging_current(void) { return max_charging_current; }
{
return max_charging_current;
}
esp_err_t evse_set_max_charging_current(uint8_t value) uint16_t evse_get_charging_current(void) { return charging_current; }
{
if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT)
return ESP_ERR_INVALID_ARG;
max_charging_current = value;
evse_set_runtime_charging_current(value);
nvs_set_u8(nvs, "max_chrg_curr", value);
return nvs_commit(nvs);
}
uint16_t evse_get_charging_current(void)
{
return charging_current;
}
esp_err_t evse_set_charging_current(uint16_t value) esp_err_t evse_set_charging_current(uint16_t value)
{ {
if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) if (value < MIN_CHARGING_CURRENT_LIMIT || value > max_charging_current)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
if (value == charging_current)
{
evse_set_runtime_charging_current(value);
return ESP_OK;
}
charging_current = value; charging_current = value;
nvs_set_u16(nvs, "def_chrg_curr", value);
return nvs_commit(nvs); esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_chrg_curr", value);
if (err != ESP_OK)
{
// Em runtime, isto pode falhar por fila cheia. RAM fica correta; persistência é best-effort.
ESP_LOGE(TAG, "Failed to persist def_chrg_curr async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
} }
uint16_t evse_get_default_charging_current(void) evse_set_runtime_charging_current(value);
{ return ESP_OK;
uint16_t value;
if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK)
return value;
return charging_current;
}
esp_err_t evse_set_default_charging_current(uint16_t value)
{
if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current))
return ESP_ERR_INVALID_ARG;
nvs_set_u16(nvs, "def_chrg_curr", value);
return nvs_commit(nvs);
} }
// ======================== // ========================
@@ -218,142 +291,120 @@ esp_err_t evse_set_default_charging_current(uint16_t value)
// ======================== // ========================
void evse_set_runtime_charging_current(uint16_t value) void evse_set_runtime_charging_current(uint16_t value)
{ {
if (value > max_charging_current) if (value > max_charging_current)
{
value = max_charging_current; value = max_charging_current;
}
else if (value < MIN_CHARGING_CURRENT_LIMIT) else if (value < MIN_CHARGING_CURRENT_LIMIT)
{
value = MIN_CHARGING_CURRENT_LIMIT; value = MIN_CHARGING_CURRENT_LIMIT;
}
charging_current_runtime = value; charging_current_runtime = value;
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
evse_config_event_data_t evt = {
.charging = evse_state_is_charging(evse_get_state()),
.hw_max_current = (float)evse_get_max_charging_current(),
.runtime_current = (float)evse_get_runtime_charging_current(),
.timestamp_us = esp_timer_get_time()};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_CONFIG_UPDATED,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
uint16_t evse_get_runtime_charging_current(void) uint16_t evse_get_runtime_charging_current(void) { return charging_current_runtime; }
{
return charging_current_runtime;
}
// ======================== // ========================
// Socket outlet // Socket outlet
// ======================== // ========================
bool evse_get_socket_outlet(void) bool evse_get_socket_outlet(void) { return socket_outlet; }
{
return socket_outlet;
}
esp_err_t evse_set_socket_outlet(bool value) esp_err_t evse_set_socket_outlet(bool value)
{ {
if (value && !board_config.proximity) if (value && !board_config.proximity)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
if (value == socket_outlet)
return ESP_OK;
socket_outlet = value; socket_outlet = value;
nvs_set_u8(nvs, "socket_outlet", value);
return nvs_commit(nvs); esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "socket_outlet", (uint8_t)value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
return ESP_OK;
} }
// ======================== // ========================
// RCM // RCM
// ======================== // ========================
bool evse_is_rcm(void) bool evse_is_rcm(void) { return rcm; }
{
return rcm;
}
esp_err_t evse_set_rcm(bool value) esp_err_t evse_set_rcm(bool value)
{ {
if (value && !board_config.rcm) if (value && !board_config.rcm)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
if (value == rcm)
return ESP_OK;
rcm = value; rcm = value;
nvs_set_u8(nvs, "rcm", value);
return nvs_commit(nvs); esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "rcm", (uint8_t)value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
return ESP_OK;
} }
// ======================== // ========================
// Temperature // Temperature
// ======================== // ========================
uint8_t evse_get_temp_threshold(void) uint8_t evse_get_temp_threshold(void) { return temp_threshold; }
{
return temp_threshold;
}
esp_err_t evse_set_temp_threshold(uint8_t value) esp_err_t evse_set_temp_threshold(uint8_t value)
{ {
if (value < 40 || value > 80) if (value < 40 || value > 80)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
if (value == temp_threshold)
return ESP_OK;
temp_threshold = value; temp_threshold = value;
nvs_set_u8(nvs, "temp_threshold", value);
return nvs_commit(nvs); esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "temp_threshold", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist temp_threshold async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
return ESP_OK;
} }
// ======================== // ========================
// Availability // Availability
// ======================== // ========================
bool evse_config_is_available(void) bool evse_config_is_available(void) { return is_available; }
{
return is_available;
}
void evse_config_set_available(bool available) void evse_config_set_available(bool available)
{ {
is_available = available ? true : false; bool newv = available;
if (newv == is_available)
return;
// Persist is_available = newv;
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
if (err == ESP_OK) esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "available", (uint8_t)is_available);
err = nvs_commit(nvs);
if (err != ESP_OK) if (err != ESP_OK)
{ ESP_LOGE(TAG, "Failed to persist 'available' async=%u: %s", (unsigned)is_available, esp_err_to_name(err));
ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err));
}
// AVAILABLE_UPDATED
evse_available_event_data_t e = {
.available = is_available,
.timestamp_us = esp_timer_get_time()};
esp_event_post(EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
} }
// ======================== // ========================
// Enable/Disable // Enable/Disable
// ======================== // ========================
bool evse_config_is_enabled(void) bool evse_config_is_enabled(void) { return is_enabled; }
{
return is_enabled;
}
void evse_config_set_enabled(bool enabled) void evse_config_set_enabled(bool enabled)
{ {
is_enabled = enabled ? true : false; bool newv = enabled;
if (newv == is_enabled)
return;
// Persist is_enabled = newv;
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
if (err == ESP_OK) esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "enabled", (uint8_t)is_enabled);
err = nvs_commit(nvs);
if (err != ESP_OK) if (err != ESP_OK)
{ ESP_LOGE(TAG, "Failed to persist 'enabled' async=%u: %s", (unsigned)is_enabled, esp_err_to_name(err));
ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err));
}
// ENABLE_UPDATED
evse_enable_event_data_t e = {
.enabled = is_enabled,
.timestamp_us = esp_timer_get_time()};
esp_event_post(EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
} }

View File

@@ -1,5 +1,4 @@
// evse_core.c - Main EVSE control logic // components/evse/evse_core.c
#include "evse_fsm.h" #include "evse_fsm.h"
#include "evse_error.h" #include "evse_error.h"
#include "evse_limits.h" #include "evse_limits.h"
@@ -16,67 +15,115 @@ static const char *TAG = "evse_core";
static SemaphoreHandle_t mutex; static SemaphoreHandle_t mutex;
static evse_state_t last_state = EVSE_STATE_A; static evse_state_t last_state = EVSE_STATE_A;
// Filtro simples de histerese no pilot
#define PILOT_STABLE_SAMPLES 2
static pilot_voltage_t s_last_raw = PILOT_VOLTAGE_12;
static pilot_voltage_t s_filtered = PILOT_VOLTAGE_12;
static int s_stable_count = 0;
static pilot_voltage_t filter_pilot_voltage(pilot_voltage_t raw)
{
if (raw == s_last_raw)
{
if (s_stable_count < PILOT_STABLE_SAMPLES)
{
s_stable_count++;
}
}
else
{
s_last_raw = raw;
s_stable_count = 1;
}
if (s_stable_count >= PILOT_STABLE_SAMPLES && raw != s_filtered)
{
s_filtered = raw;
}
return s_filtered;
}
static void evse_process(void);
static void evse_core_task(void *arg); static void evse_core_task(void *arg);
// ================================ void evse_init(void)
// Initialization {
// ================================
void evse_init(void) {
ESP_LOGI(TAG, "EVSE Init"); ESP_LOGI(TAG, "EVSE Init");
mutex = xSemaphoreCreateMutex(); // Optional: use static version for deterministic memory mutex = xSemaphoreCreateMutex();
if (!mutex)
{
ESP_LOGE(TAG, "Failed to create EVSE core mutex");
return;
}
evse_check_defaults(); evse_check_defaults();
evse_fsm_reset(); evse_fsm_reset();
pilot_set_level(true); // Enable pilot output pilot_set_level(true);
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); BaseType_t rc = xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 6, NULL);
configASSERT(rc == pdPASS);
} }
// ================================ static void evse_process(void)
// Main Processing Logic {
// ================================ if (!mutex)
{
return;
}
void evse_process(void) {
xSemaphoreTake(mutex, portMAX_DELAY); xSemaphoreTake(mutex, portMAX_DELAY);
pilot_voltage_t pilot_voltage; pilot_voltage_t pilot_raw;
bool is_n12v = false; bool is_n12v = false;
pilot_measure(&pilot_voltage, &is_n12v); pilot_measure(&pilot_raw, &is_n12v);
ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no"); pilot_voltage_t pilot_voltage = filter_pilot_voltage(pilot_raw);
ESP_LOGD(TAG, "Pilot(raw=%d, filt=%d), -12V: %s",
pilot_raw, pilot_voltage, is_n12v ? "yes" : "no");
// raw set/clear; erro visível mantém holdoff interno (60s após sumir)
evse_error_check(pilot_voltage, is_n12v); evse_error_check(pilot_voltage, is_n12v);
// Só chama FSM, que decide tudo // ✅ Sem cooldown externo: disponibilidade depende só do erro "visível"
bool available = evse_config_is_available() && (evse_get_error() == 0);
bool enabled = evse_config_is_enabled();
evse_fsm_process( evse_fsm_process(
pilot_voltage, pilot_voltage,
evse_state_get_authorized(), evse_state_get_authorized(),
evse_config_is_available(), available,
evse_config_is_enabled() enabled);
);
evse_limits_check(); evse_limits_check();
evse_state_t current = evse_get_state(); if (evse_is_limit_reached())
if (current != last_state) { {
//ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current)); if (evse_state_get_authorized())
last_state = current; {
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
evse_state_set_authorized(false);
}
} }
evse_mark_error_cleared(); evse_state_t current = evse_get_state();
if (current != last_state)
{
last_state = current;
}
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
} }
// ================================ static void evse_core_task(void *arg)
// Background Task {
// ================================ (void)arg;
while (true)
static void evse_core_task(void *arg) { {
while (true) {
evse_process(); evse_process();
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz cycle vTaskDelay(pdMS_TO_TICKS(100));
} }
} }

View File

@@ -1,120 +1,310 @@
#include "evse_error.h" #include "evse_error.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h" #include "esp_log.h"
#include "ntc_sensor.h" #include "ntc_sensor.h"
#include "esp_event.h"
#include "esp_timer.h"
#include "evse_events.h"
#include "evse_config.h"
static const char *TAG = "evse_error"; static const char *TAG = "evse_error";
static uint32_t error_bits = 0; // ----------------------------------------------------
static TickType_t auto_clear_timeout = 0; // Estado interno
// ----------------------------------------------------
// raw_bits = erros “instantâneos” conforme checks (set/clear)
// visible_bits = erros expostos ao resto do sistema (com holdoff)
// clear_deadline = quando pode finalmente limpar visible_bits para 0
static uint32_t raw_bits = 0;
static uint32_t visible_bits = 0;
static TickType_t clear_deadline = 0;
// Sticky flag: "todos erros visíveis foram limpos"
static bool error_cleared = false; static bool error_cleared = false;
void evse_error_init(void) { // Proteção contra concorrência
// Inicialização do sistema de erros static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
}
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) { // ----------------------------------------------------
ESP_LOGD(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", // Helper: publicar evento de alteração de erro (visible_bits)
pilot_voltage, is_n12v ? "true" : "false"); // ----------------------------------------------------
static void evse_error_post_event(uint32_t new_bits, uint32_t changed_mask)
{
evse_error_event_data_t ev = {
.error_bits = new_bits,
.changed_mask = changed_mask,
.timestamp_us = esp_timer_get_time(),
};
// Falha elétrica geral no pilot esp_err_t err = esp_event_post(
if (pilot_voltage == PILOT_VOLTAGE_1) { EVSE_EVENTS,
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT)) { // Verifica se o erro já foi registrado EVSE_EVENT_ERROR_CHANGED,
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT); &ev,
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
esp_err_to_name(err));
} }
} }
// Falta de -12V durante PWM (C ou D) // ----------------------------------------------------
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) { // Helpers internos
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT)) { // Verifica se o erro já foi registrado // ----------------------------------------------------
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT); static bool raw_has_bit(uint32_t bit)
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)"); {
ESP_LOGW(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", pilot_voltage, is_n12v ? "true" : "false"); bool v;
} portENTER_CRITICAL(&error_mux);
} v = ((raw_bits & bit) != 0);
portEXIT_CRITICAL(&error_mux);
return v;
} }
void evse_temperature_check(void) { static void reconcile_visible_locked(TickType_t now)
float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida) {
uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável // Se existem erros reais, o visível segue imediatamente
if (raw_bits != 0)
// Log informativo com os valores {
ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold); visible_bits = raw_bits;
clear_deadline = 0;
// Se a temperatura parecer inválida, aplica erro de sensor error_cleared = false;
if (temp_c < -40.0f || temp_c > 150.0f) {
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
return; return;
} }
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida // raw_bits == 0
if (visible_bits == 0)
if (temp_c >= threshold) { {
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado clear_deadline = 0;
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); return;
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold);
}
} else {
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
} }
uint32_t evse_get_error(void) { // Ainda há erro visível (holdoff). Arma deadline 1x.
return error_bits; if (clear_deadline == 0)
{
clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
return;
} }
bool evse_is_error_cleared(void) { // Expirou -> limpar finalmente
return error_cleared; if ((int32_t)(now - clear_deadline) >= 0)
} {
visible_bits = 0;
void evse_mark_error_cleared(void) { clear_deadline = 0;
error_cleared = false;
}
// Já existentes
void evse_error_set(uint32_t bitmask) {
error_bits |= bitmask;
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) {
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
}
}
void evse_error_clear(uint32_t bitmask) {
bool had_error = error_bits != 0;
error_bits &= ~bitmask;
if (had_error && error_bits == 0) {
error_cleared = true; error_cleared = true;
} }
} }
void evse_error_tick(void) { // ----------------------------------------------------
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) { // API pública
evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS); // ----------------------------------------------------
auto_clear_timeout = 0; void evse_error_init(void)
} {
} uint32_t old_vis, new_vis, changed;
bool post = false;
bool evse_error_is_active(void) { portENTER_CRITICAL(&error_mux);
return error_bits != 0;
}
uint32_t evse_error_get_bits(void) { old_vis = visible_bits;
return error_bits;
}
void evse_error_reset_flag(void) { raw_bits = 0;
visible_bits = 0;
clear_deadline = 0;
error_cleared = false; error_cleared = false;
new_vis = visible_bits;
changed = old_vis ^ new_vis;
post = (changed != 0);
portEXIT_CRITICAL(&error_mux);
if (post)
{
evse_error_post_event(new_vis, changed);
}
} }
bool evse_error_cleared_flag(void) { uint32_t evse_get_error(void)
return error_cleared; {
portENTER_CRITICAL(&error_mux);
uint32_t val = visible_bits;
portEXIT_CRITICAL(&error_mux);
return val;
}
bool evse_error_is_active(void)
{
return evse_get_error() != 0;
}
uint32_t evse_error_get_bits(void)
{
return evse_get_error();
}
bool evse_error_cleared_flag(void)
{
portENTER_CRITICAL(&error_mux);
bool v = error_cleared;
portEXIT_CRITICAL(&error_mux);
return v;
}
void evse_error_reset_flag(void)
{
portENTER_CRITICAL(&error_mux);
error_cleared = false;
portEXIT_CRITICAL(&error_mux);
}
void evse_error_set(uint32_t bitmask)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
raw_bits |= bitmask;
// se aparece qualquer erro, o "cleared" deixa de ser verdade
error_cleared = false;
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
void evse_error_clear(uint32_t bitmask)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
raw_bits &= ~bitmask;
// ✅ Aqui é onde o “60s depois do erro desaparecer” é armado:
// quando raw_bits chega a 0, reconcile arma clear_deadline (uma vez)
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
void evse_error_tick(void)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
// ----------------------------------------------------
// Checks (raw -> set/clear)
// ----------------------------------------------------
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
{
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
pilot_voltage, is_n12v ? "true" : "false");
// 1) Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1)
{
if (!raw_has_bit(EVSE_ERR_PILOT_FAULT_BIT))
{
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
}
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
}
else
{
evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
}
// 2) Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
{
if (!raw_has_bit(EVSE_ERR_DIODE_SHORT_BIT))
{
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
}
else
{
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
}
}
void evse_temperature_check(void)
{
float temp_c = ntc_temp_sensor();
uint8_t threshold = evse_get_temp_threshold();
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
temp_c, threshold);
// Temperatura inválida -> erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f)
{
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_FAULT_BIT))
{
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
return;
}
// Leitura válida -> limpa erro de sensor
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
// Temperatura máxima
if (temp_c >= threshold)
{
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_HIGH_BIT))
{
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
temp_c, threshold);
}
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
else
{
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
} }

View File

@@ -1,3 +1,4 @@
// components/evse/evse_fsm.c
#include "evse_fsm.h" #include "evse_fsm.h"
#include "evse_api.h" #include "evse_api.h"
#include "evse_pilot.h" #include "evse_pilot.h"
@@ -17,48 +18,60 @@ static const char *TAG = "evse_fsm";
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif #endif
static bool c1_d1_waiting = false; void evse_fsm_reset(void)
static TickType_t c1_d1_relay_to = 0; {
void evse_fsm_reset(void) {
evse_set_state(EVSE_STATE_A); evse_set_state(EVSE_STATE_A);
c1_d1_waiting = false;
c1_d1_relay_to = 0;
} }
// ... includes e defines como já estão /**
* @brief Atualiza saídas de hardware (pilot, relé, trava) em função do estado lógico.
static void update_outputs(evse_state_t state) { */
static void update_outputs(evse_state_t state)
{
const uint16_t current = evse_get_runtime_charging_current(); const uint16_t current = evse_get_runtime_charging_current();
uint8_t cable_max_current = evse_get_max_charging_current(); uint8_t cable_max_current = evse_get_max_charging_current();
const bool socket_outlet = evse_get_socket_outlet(); const bool socket_outlet = evse_get_socket_outlet();
if (socket_outlet) { if (socket_outlet)
{
cable_max_current = proximity_get_max_current(); cable_max_current = proximity_get_max_current();
} }
// Segurança: relé sempre off e outputs seguros em caso de erro // Segurança total: qualquer erro ativo força saída segura
if (evse_get_error() != 0) { if (evse_get_error() != 0)
if (ac_relay_get_state()) { {
if (ac_relay_get_state())
{
ac_relay_set_state(false); ac_relay_set_state(false);
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!"); ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
} }
ac_relay_set_state(false); // redundância tolerável else
pilot_set_level(true); // sinal pilot sempre 12V (A) {
if (board_config.socket_lock && socket_outlet) { ac_relay_set_state(false);
}
// Em erro, garantir pilot OFF (não PWM / não +12V)
pilot_set_level(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false); socket_lock_set_locked(false);
} }
return; return;
} }
// Fluxo normal switch (state)
switch (state) { {
case EVSE_STATE_A: case EVSE_STATE_A:
case EVSE_STATE_E: case EVSE_STATE_E:
case EVSE_STATE_F: case EVSE_STATE_F:
ac_relay_set_state(false); ac_relay_set_state(false);
// A → pilot alto (+12V), E/F → pilot OFF
pilot_set_level(state == EVSE_STATE_A); pilot_set_level(state == EVSE_STATE_A);
if (board_config.socket_lock && socket_outlet) {
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false); socket_lock_set_locked(false);
} }
break; break;
@@ -66,84 +79,117 @@ static void update_outputs(evse_state_t state) {
case EVSE_STATE_B1: case EVSE_STATE_B1:
pilot_set_level(true); pilot_set_level(true);
ac_relay_set_state(false); ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet) {
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true); socket_lock_set_locked(true);
} }
if (rcm_test()) { (void)rcm_test();
//ESP_LOGI(TAG, "RCM self test passed");
} else {
//ESP_LOGW(TAG, "RCM self test failed");
}
break; break;
case EVSE_STATE_B2: case EVSE_STATE_B2:
pilot_set_amps(MIN(current, cable_max_current)); pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false); ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break; break;
case EVSE_STATE_C1: case EVSE_STATE_C1:
case EVSE_STATE_D1: { case EVSE_STATE_D1:
pilot_set_amps(MIN(current, cable_max_current)); // mantém PWM pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false); // relé ainda desligado ac_relay_set_state(false);
c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000); if (board_config.socket_lock && socket_outlet)
break; {
socket_lock_set_locked(true);
} }
break;
case EVSE_STATE_C2: case EVSE_STATE_C2:
case EVSE_STATE_D2: case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current)); pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true); // Só chega aqui se não há erro! ac_relay_set_state(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break; break;
} }
} }
// FSM principal - centraliza a lógica de erro e de todos os estados /**
* @brief Máquina de estados principal do EVSE (IEC 61851).
*/
void evse_fsm_process( void evse_fsm_process(
pilot_voltage_t pilot_voltage, pilot_voltage_t pilot_voltage,
bool authorized, bool authorized,
bool available, bool available,
bool enabled bool enabled)
) { {
// Proteção total: erro força F sempre! // 1) Erros globais: dominam qualquer outra lógica
if (evse_get_error() != 0) { uint32_t err_bits = evse_get_error();
if (evse_get_state() != EVSE_STATE_F) { if (err_bits != 0)
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)"); {
evse_set_state(EVSE_STATE_F); evse_state_t forced_state =
(err_bits & EVSE_ERR_PILOT_FAULT_BIT) ? EVSE_STATE_E : EVSE_STATE_F;
if (evse_get_state() != forced_state)
{
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado %s",
evse_state_to_str(forced_state));
evse_set_state(forced_state);
} }
update_outputs(EVSE_STATE_F);
update_outputs(forced_state);
return; return;
} }
TickType_t now = xTaskGetTickCount(); evse_state_t curr = evse_get_state();
evse_state_t prev = evse_get_state();
evse_state_t curr = prev;
switch (curr) { switch (curr)
{
case EVSE_STATE_A: case EVSE_STATE_A:
if (!available) { if (!available)
{
evse_set_state(EVSE_STATE_F); evse_set_state(EVSE_STATE_F);
} else if (pilot_voltage == PILOT_VOLTAGE_9) { }
else if (pilot_voltage == PILOT_VOLTAGE_9)
{
evse_set_state(EVSE_STATE_B1); evse_set_state(EVSE_STATE_B1);
} }
break; break;
case EVSE_STATE_B1: case EVSE_STATE_B1:
case EVSE_STATE_B2: case EVSE_STATE_B2:
if (!available) { if (!available)
{
evse_set_state(EVSE_STATE_F); evse_set_state(EVSE_STATE_F);
break; break;
} }
switch (pilot_voltage) {
switch (pilot_voltage)
{
case PILOT_VOLTAGE_12: case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A); evse_set_state(EVSE_STATE_A);
break; break;
case PILOT_VOLTAGE_9: case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break; break;
case PILOT_VOLTAGE_6: case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break; break;
case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break;
default: default:
break; break;
} }
@@ -151,48 +197,61 @@ void evse_fsm_process(
case EVSE_STATE_C1: case EVSE_STATE_C1:
case EVSE_STATE_D1: case EVSE_STATE_D1:
if (c1_d1_waiting && now >= c1_d1_relay_to) { case EVSE_STATE_C2:
ac_relay_set_state(false); case EVSE_STATE_D2:
c1_d1_waiting = false; if (!available)
if (!available) { {
evse_set_state(EVSE_STATE_F); evse_set_state(EVSE_STATE_F);
break; break;
} }
}
__attribute__((fallthrough));
case EVSE_STATE_C2: if (!enabled)
case EVSE_STATE_D2: {
if (!enabled || !available) { if (curr == EVSE_STATE_C2)
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1) {
? EVSE_STATE_D1 : EVSE_STATE_C1); evse_set_state(EVSE_STATE_C1);
}
else if (curr == EVSE_STATE_D2)
{
evse_set_state(EVSE_STATE_D1);
}
break; break;
} }
switch (pilot_voltage) {
switch (pilot_voltage)
{
case PILOT_VOLTAGE_6: case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break; break;
case PILOT_VOLTAGE_3: case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1); evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break; break;
case PILOT_VOLTAGE_9: case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break; break;
case PILOT_VOLTAGE_12: case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A); evse_set_state(EVSE_STATE_A);
break; break;
default: default:
break; break;
} }
break; break;
case EVSE_STATE_E: case EVSE_STATE_E:
// Estado elétrico grave: só reset manual // ✅ Agora recupera como F: se disponível e sem erro -> volta a A
if (available && evse_get_error() == 0)
{
evse_set_state(EVSE_STATE_A);
}
break; break;
case EVSE_STATE_F: case EVSE_STATE_F:
// Fault: só sai se disponível e sem erro if (available && evse_get_error() == 0)
if (available && evse_get_error() == 0) { {
evse_set_state(EVSE_STATE_A); evse_set_state(EVSE_STATE_A);
} }
break; break;
@@ -200,10 +259,4 @@ void evse_fsm_process(
evse_state_t next = evse_get_state(); evse_state_t next = evse_get_state();
update_outputs(next); update_outputs(next);
if (next != prev) {
ESP_LOGI(TAG, "State changed: %s -> %s",
evse_state_to_str(prev),
evse_state_to_str(next));
}
} }

View File

@@ -1,49 +1,125 @@
#include <inttypes.h> // for PRIu32 #include <inttypes.h>
#include <stdbool.h>
#include "evse_state.h" #include "evse_state.h"
#include "evse_api.h" #include "evse_api.h"
#include "evse_limits.h" #include "evse_limits.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "evse_session.h" #include "evse_session.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_err.h"
#include "esp_check.h"
#include "storage_service.h"
// ======================== #define NVS_NAMESPACE "evse_limits"
// External state references static const char *TAG = "evse_limits";
// ========================
//extern evse_state_t current_state; // Current EVSE FSM state
//extern TickType_t session_start_tick; // Timestamp of charging session start
// ========================
// Concurrency protection
// ========================
static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
// ========================
// Runtime state (volatile)
// ========================
static bool limit_reached = false; static bool limit_reached = false;
static uint32_t consumption_limit = 0; // Energy limit in Wh static uint32_t consumption_limit = 0; // Wh
static uint32_t charging_time_limit = 0; // Time limit in seconds static uint32_t charging_time_limit = 0; // seconds
static uint16_t under_power_limit = 0; // Minimum acceptable power in W static uint16_t under_power_limit = 0; // W
// ======================== static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
// Default (persistent) limits static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); }
// ========================
static uint32_t default_consumption_limit = 0; // ---------------------------------
static uint32_t default_charging_time_limit = 0; // Init + defaults
static uint16_t default_under_power_limit = 0; // ---------------------------------
esp_err_t evse_limits_init(void)
{
ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
ESP_LOGI(TAG, "EVSE limits init OK (storage-backed)");
return ESP_OK;
}
// ======================== void evse_limits_check_defaults(void)
// Limit status flag {
// ======================== esp_err_t err;
bool needs_flush = false;
bool evse_get_limit_reached(void) { uint32_t u32 = 0;
uint16_t u16 = 0;
ESP_LOGD(TAG, "Checking default limits...");
// Consumption limit (Wh) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
// Charging time limit (s) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
// Under-power limit (W) default = 0 (disabled)
err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = u16;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
if (needs_flush)
{
esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
if (fe != ESP_OK)
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
else
ESP_LOGD(TAG, "Defaults committed (flush).");
}
}
// ---------------------------------
// Limit reached flag
// ---------------------------------
bool evse_get_limit_reached(void)
{
bool val; bool val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = limit_reached; val = limit_reached;
@@ -51,17 +127,23 @@ bool evse_get_limit_reached(void) {
return val; return val;
} }
void evse_set_limit_reached(bool v) { void evse_set_limit_reached(bool v)
{
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
limit_reached = v; limit_reached = v;
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
} }
// ======================== bool evse_is_limit_reached(void)
// Runtime limit accessors {
// ======================== return evse_get_limit_reached();
}
uint32_t evse_get_consumption_limit(void) { // ---------------------------------
// Consumption limit
// ---------------------------------
uint32_t evse_get_consumption_limit(void)
{
uint32_t val; uint32_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = consumption_limit; val = consumption_limit;
@@ -69,13 +151,35 @@ uint32_t evse_get_consumption_limit(void) {
return val; return val;
} }
void evse_set_consumption_limit(uint32_t value) { void evse_set_consumption_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
if (consumption_limit != value)
{
consumption_limit = value; consumption_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
value, esp_err_to_name(err));
}
} }
uint32_t evse_get_charging_time_limit(void) { // ---------------------------------
// Charging time limit
// ---------------------------------
uint32_t evse_get_charging_time_limit(void)
{
uint32_t val; uint32_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = charging_time_limit; val = charging_time_limit;
@@ -83,13 +187,35 @@ uint32_t evse_get_charging_time_limit(void) {
return val; return val;
} }
void evse_set_charging_time_limit(uint32_t value) { void evse_set_charging_time_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
if (charging_time_limit != value)
{
charging_time_limit = value; charging_time_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist charging time limit (%" PRIu32 " s): %s",
value, esp_err_to_name(err));
}
} }
uint16_t evse_get_under_power_limit(void) { // ---------------------------------
// Under-power limit
// ---------------------------------
uint16_t evse_get_under_power_limit(void)
{
uint16_t val; uint16_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = under_power_limit; val = under_power_limit;
@@ -97,92 +223,79 @@ uint16_t evse_get_under_power_limit(void) {
return val; return val;
} }
void evse_set_under_power_limit(uint16_t value) { void evse_set_under_power_limit(uint16_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
if (under_power_limit != value)
{
under_power_limit = value; under_power_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
}
// ======================== if (!changed)
// Default (persistent) limit accessors
// These values can be stored/restored via NVS
// ========================
uint32_t evse_get_default_consumption_limit(void) {
return default_consumption_limit;
}
void evse_set_default_consumption_limit(uint32_t value) {
default_consumption_limit = value;
}
uint32_t evse_get_default_charging_time_limit(void) {
return default_charging_time_limit;
}
void evse_set_default_charging_time_limit(uint32_t value) {
default_charging_time_limit = value;
}
uint16_t evse_get_default_under_power_limit(void) {
return default_under_power_limit;
}
void evse_set_default_under_power_limit(uint16_t value) {
default_under_power_limit = value;
}
bool evse_is_limit_reached(void) {
return evse_get_limit_reached();
}
// ========================
// Limit checking logic
// This function must be called periodically while charging.
// It will flag the session as "limit reached" when thresholds are violated.
// ========================
void evse_limits_check(void) {
// Only check during an active charging session
if (!evse_state_is_charging(evse_get_state())) {
return; return;
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist under-power limit (%" PRIu32 " W): %s",
(uint32_t)value, esp_err_to_name(err));
} }
}
// ---------------------------------
// Runtime check
// ---------------------------------
void evse_limits_check(void)
{
// Só faz sentido quando há energia ativa (C2/D2)
if (!evse_state_is_charging(evse_get_state()))
return;
evse_session_t sess; evse_session_t sess;
// Retrieve accumulated data for the current session if (!evse_session_get(&sess) || !sess.is_current)
if (!evse_session_get(&sess) || !sess.is_current) {
// If there's no active session, abort
return; return;
}
uint32_t cons_lim;
uint32_t time_lim;
uint16_t unp_lim;
portENTER_CRITICAL(&evse_mux);
cons_lim = consumption_limit;
time_lim = charging_time_limit;
unp_lim = under_power_limit;
portEXIT_CRITICAL(&evse_mux);
bool reached = false; bool reached = false;
// 1) Energy consumption limit (Wh) if (cons_lim > 0 && sess.energy_wh >= cons_lim)
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) { {
ESP_LOGW("EVSE_LIMITS", ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
"Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh", sess.energy_wh, cons_lim);
sess.energy_wh, consumption_limit);
reached = true; reached = true;
} }
// 2) Charging time limit (seconds) if (time_lim > 0 && sess.duration_s >= time_lim)
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) { {
ESP_LOGW("EVSE_LIMITS", ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
"Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s", sess.duration_s, time_lim);
sess.duration_s, charging_time_limit);
reached = true; reached = true;
} }
// 3) Under-power limit (instantaneous power) int32_t p = evse_meter_get_instant_power();
uint32_t inst_power = evse_meter_get_instant_power(); uint32_t inst_power = (p > 0) ? (uint32_t)p : 0;
if (under_power_limit > 0 && inst_power < under_power_limit) {
ESP_LOGW("EVSE_LIMITS", if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", {
(uint32_t)inst_power, ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
(uint32_t)under_power_limit); inst_power, (uint32_t)unp_lim);
reached = true; reached = true;
} }
if (reached) { if (reached)
evse_set_limit_reached(true); evse_set_limit_reached(true);
} }
}

View File

@@ -1,4 +1,3 @@
// === Início de: components/evse/evse_manager.c ===
#include "evse_manager.h" #include "evse_manager.h"
#include "evse_state.h" #include "evse_state.h"
#include "evse_error.h" #include "evse_error.h"
@@ -7,69 +6,124 @@
#include "evse_api.h" #include "evse_api.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "evse_session.h" #include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_event.h"
#include "esp_err.h"
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
#include "auth_events.h" #include "auth_events.h"
#include "loadbalancer_events.h" #include "loadbalancer_events.h"
#include "ocpp_events.h" #include "ocpp_events.h"
#include "esp_event.h" #include "scheduler_events.h"
static const char *TAG = "EVSE_Manager"; static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex; static SemaphoreHandle_t evse_mutex;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false; static bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer // Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false; static bool lb_paused = false;
static bool lb_prev_authorized = false; static bool lb_prev_authorized = false;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo // Estado de janela do scheduler
static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
// ================= Helpers internos ================= #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
static void lb_clear_pause_state(void) static void lb_clear_pause_state(void)
{ {
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = false; lb_paused = false;
lb_prev_authorized = false; lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
bool evse_sched_is_allowed(void)
{
bool v;
portENTER_CRITICAL(&s_sched_mux);
v = s_sched_allowed;
portEXIT_CRITICAL(&s_sched_mux);
return v;
} }
static void evse_manager_handle_auth_on_tick(void) static void evse_manager_handle_auth_on_tick(void)
{ {
if (auth_enabled) bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
bool local_auth_enabled;
bool local_lb_paused;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
local_lb_paused = lb_paused;
portEXIT_CRITICAL(&s_mgr_mux);
if (local_auth_enabled)
{ {
// Se o carro foi desconectado, revoga autorização
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{ {
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_state_set_authorized(false); evse_state_set_authorized(false);
// Desconexão física invalida qualquer pausa pendente do LB
lb_clear_pause_state(); lb_clear_pause_state();
} }
if (has_error && evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
err_bits);
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
} }
else else
{ {
// Se autenticação está desativada, garante autorização sempre ativa bool limit_hit = evse_is_limit_reached();
if (!evse_state_get_authorized()) bool can_operate = evse_config_is_available() && evse_config_is_enabled();
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
(int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
evse_state_set_authorized(false);
}
if (!local_lb_paused && sched_allowed && can_operate &&
!has_error && !limit_hit &&
!evse_state_get_authorized())
{ {
evse_state_set_authorized(true); evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization."); ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
// Em modo OPEN, pausa do LB não é tão relevante, mas limpamos mesmo assim
lb_clear_pause_state(); lb_clear_pause_state();
} }
} }
} }
// ===== Task de ciclo principal =====
static void evse_manager_task(void *arg) static void evse_manager_task(void *arg)
{ {
(void)arg;
while (true) while (true)
{ {
evse_manager_tick(); evse_manager_tick();
@@ -79,10 +133,8 @@ static void evse_manager_task(void *arg)
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data) static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
if (base != AUTH_EVENTS || !data) (void)arg;
return; if (base != AUTH_EVENTS || !data) return;
auth_mode_t g_mode = AUTH_MODE_OPEN;
switch (id) switch (id)
{ {
@@ -91,8 +143,6 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data; const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED"); ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
evse_state_set_authorized(evt->authorized); evse_state_set_authorized(evt->authorized);
// Qualquer alteração explícita de auth invalida pausa do LB
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
} }
@@ -101,122 +151,110 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
case AUTH_EVENT_INIT: case AUTH_EVENT_INIT:
{ {
const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data; const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data;
g_mode = evt->mode; ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(g_mode));
if (g_mode == AUTH_MODE_OPEN)
{
evse_state_set_authorized(true);
auth_enabled = false;
}
else
{
evse_state_set_authorized(false);
auth_enabled = true;
}
// Modo mudou -> qualquer pausa antiga deixa de fazer sentido portENTER_CRITICAL(&s_mgr_mux);
auth_enabled = (evt->mode != AUTH_MODE_OPEN);
portEXIT_CRITICAL(&s_mgr_mux);
evse_state_set_authorized(false);
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
} }
} }
} }
// ===== Tratador de eventos de loadbalancer =====
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base, static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) int32_t event_id, void *event_data)
{ {
(void)handler_arg;
(void)event_base;
if (!event_data) return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED) if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{ {
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data; const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)", ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED", evt->enabled ? "ENABLED" : "DISABLED",
(long long)evt->timestamp_us); (long long)evt->timestamp_us);
// Ações adicionais podem ser adicionadas aqui conforme necessário
} }
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT) else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{ {
const loadbalancer_master_limit_event_t *evt = const loadbalancer_master_limit_event_t *evt =
(const loadbalancer_master_limit_event_t *)event_data; (const loadbalancer_master_limit_event_t *)event_data;
ESP_LOGI(TAG, ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
"Novo limite de corrente (master): %u A (ts: %lld)",
evt->max_current, (long long)evt->timestamp_us); evt->max_current, (long long)evt->timestamp_us);
if (evt->max_current == 0) if (evt->max_current == 0)
{ {
// Suspensão por LB (não interessa se é OPEN ou RFID/OCPP) bool prev_auth = evse_state_get_authorized();
lb_paused = true;
lb_prev_authorized = evse_state_get_authorized();
if (lb_prev_authorized) portENTER_CRITICAL(&s_mgr_mux);
lb_paused = true;
lb_prev_authorized = prev_auth;
portEXIT_CRITICAL(&s_mgr_mux);
if (prev_auth)
{ {
ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)"); ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)");
evse_state_set_authorized(false); evse_state_set_authorized(false);
} }
else
{
ESP_LOGD(TAG, "[LB] limit=0A → já não estava autorizado");
}
} }
else else
{ {
// Ajusta corrente em runtime
evse_set_runtime_charging_current(evt->max_current); evse_set_runtime_charging_current(evt->max_current);
if (lb_paused) bool was_paused;
{ bool prev_auth;
lb_paused = false;
// Só retomamos se EVSE estiver operacional portENTER_CRITICAL(&s_mgr_mux);
was_paused = lb_paused;
prev_auth = lb_prev_authorized;
portEXIT_CRITICAL(&s_mgr_mux);
if (was_paused)
{
bool can_resume = bool can_resume =
(evse_get_error() == 0) && (evse_get_error() == 0) &&
evse_config_is_available() && evse_config_is_available() &&
evse_config_is_enabled(); evse_config_is_enabled() &&
evse_sched_is_allowed() &&
!evse_is_limit_reached();
if (!can_resume) if (!can_resume)
{ {
ESP_LOGW(TAG, ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado)", "[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
evt->max_current); evt->max_current);
lb_clear_pause_state(); lb_clear_pause_state();
return; return;
} }
if (!auth_enabled) bool local_auth_enabled;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
lb_paused = false; // já vai tentar retomar
portEXIT_CRITICAL(&s_mgr_mux);
if (!local_auth_enabled)
{ {
// Modo OPEN: retoma sempre ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
ESP_LOGI(TAG,
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
evt->max_current);
evse_state_set_authorized(true); evse_state_set_authorized(true);
} }
else else
{ {
// RFID/OCPP: só retoma se havia autorização antes da pausa if (prev_auth)
if (lb_prev_authorized)
{ {
ESP_LOGI(TAG, ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
"[LB] limit=%uA → RFID/OCPP, retomando autorização anterior (auto-resume)",
evt->max_current);
evse_state_set_authorized(true); evse_state_set_authorized(true);
} }
else
{
ESP_LOGI(TAG,
"[LB] limit=%uA → RFID/OCPP, sem autorização prévia, mantendo estado atual",
evt->max_current);
}
} }
// Limpa estado prévio (não reaplicar em pausas futuras) portENTER_CRITICAL(&s_mgr_mux);
lb_prev_authorized = false; lb_prev_authorized = false;
} portEXIT_CRITICAL(&s_mgr_mux);
else
{
// Caso normal: apenas ajuste de corrente, sem mexer em auth
ESP_LOGD(TAG,
"[LB] limit=%uA → ajustando corrente runtime (sem mudança de autorização)",
evt->max_current);
} }
} }
} }
@@ -224,8 +262,8 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data) static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
if (base != OCPP_EVENTS) (void)arg;
return; if (base != OCPP_EVENTS) return;
switch (id) switch (id)
{ {
@@ -236,13 +274,10 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
break; break;
case OCPP_EVENT_AUTH_REJECTED: case OCPP_EVENT_AUTH_REJECTED:
ESP_LOGW(TAG, "[OCPP] Authorization rejected");
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
case OCPP_EVENT_AUTH_TIMEOUT: case OCPP_EVENT_AUTH_TIMEOUT:
ESP_LOGW(TAG, "[OCPP] Authorization timeout"); case OCPP_EVENT_REMOTE_STOP:
case OCPP_EVENT_STOP_TX:
ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
evse_state_set_authorized(false); evse_state_set_authorized(false);
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
@@ -253,25 +288,11 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
case OCPP_EVENT_REMOTE_STOP:
ESP_LOGI(TAG, "[OCPP] RemoteStop");
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
case OCPP_EVENT_START_TX: case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx"); ESP_LOGI(TAG, "[OCPP] StartTx");
// StartTx em si não precisa mexer em auth, mas limpamos estado de pausa por segurança
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
case OCPP_EVENT_STOP_TX:
ESP_LOGI(TAG, "[OCPP] StopTx");
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
// ChangeAvailability remoto (operative/inoperative)
case OCPP_EVENT_OPERATIVE_UPDATED: case OCPP_EVENT_OPERATIVE_UPDATED:
{ {
if (!data) if (!data)
@@ -283,10 +304,7 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld", ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
(int)ev->operative, (long long)ev->timestamp_us); (int)ev->operative, (long long)ev->timestamp_us);
// Mapear operative → enabled local (persiste e emite EVSE_EVENT_ENABLE_UPDATED)
evse_config_set_enabled(ev->operative); evse_config_set_enabled(ev->operative);
// Opcional: poderias também limpar a pausa aqui, dependendo da política
// lb_clear_pause_state();
break; break;
} }
@@ -296,12 +314,37 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
} }
} }
// ===== Inicialização ===== static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
const sched_event_state_t *ev = (const sched_event_state_t *)data;
portENTER_CRITICAL(&s_sched_mux);
s_sched_allowed = ev->allowed_now;
portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
}
void evse_manager_init(void) void evse_manager_init(void)
{ {
evse_mutex = xSemaphoreCreateMutex(); evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
esp_err_t err = evse_config_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
}
evse_config_init();
evse_error_init(); evse_error_init();
evse_hardware_init(); evse_hardware_init();
evse_state_init(); evse_state_init();
@@ -311,12 +354,14 @@ void evse_manager_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
ESP_LOGI(TAG, "EVSE Manager inicializado."); ESP_LOGI(TAG, "EVSE Manager inicializado.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
configASSERT(rc == pdPASS);
} }
// ===== Main Tick =====
void evse_manager_tick(void) void evse_manager_tick(void)
{ {
xSemaphoreTake(evse_mutex, portMAX_DELAY); xSemaphoreTake(evse_mutex, portMAX_DELAY);
@@ -331,4 +376,3 @@ void evse_manager_tick(void)
xSemaphoreGive(evse_mutex); xSemaphoreGive(evse_mutex);
} }
// === Fim de: components/evse/evse_manager.c ===

View File

@@ -48,7 +48,7 @@ void evse_meter_on_meter_event(void *arg, void *event_data)
meter_data.energy_wh = (uint32_t)(evt->total_energy); meter_data.energy_wh = (uint32_t)(evt->total_energy);
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
ESP_LOGI(TAG, ESP_LOGD(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, " "Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, " "voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, " "current[A]={%.2f,%.2f,%.2f}, "

View File

@@ -1,8 +1,7 @@
// components/evse/evse_pilot.c
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h>
#include <string.h>
#include "driver/ledc.h" #include "driver/ledc.h"
#include "esp_err.h" #include "esp_err.h"
@@ -19,37 +18,63 @@
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT #define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
#define PILOT_PWM_MAX_DUTY 1023 #define PILOT_PWM_MAX_DUTY 1023
// --- Configuração de amostragem do Pilot ---
#define NUM_PILOT_SAMPLES 100 #define NUM_PILOT_SAMPLES 100
#define MAX_SAMPLE_ATTEMPTS 1000 #define MAX_SAMPLE_ATTEMPTS 1000
#define PILOT_SAMPLE_DELAY_US 10
// Percentagem para descartar extremos superior/inferior (ruído)
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior #define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
// ADC121S021 setup // ADC referência
#define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware! #define ADC121_VREF_MV 3300
#define ADC121_MAX 4095 // 12 bits #define ADC121_MAX 4095
static const char *TAG = "evse_pilot"; static const char *TAG = "evse_pilot";
// Memoização de estado para evitar comandos/logs desnecessários typedef enum {
static int last_pilot_level = -1; PILOT_MODE_DC_HIGH = 0, // +12V (nível alto)
PILOT_MODE_DC_LOW, // nível baixo / pilot desligado (dependente do hardware)
PILOT_MODE_PWM // PWM ativo
} pilot_mode_t;
static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
static uint32_t last_pwm_duty = 0; static uint32_t last_pwm_duty = 0;
// Função para converter leitura bruta do ADC para mV // ---------------------
static int adc_raw_to_mv(uint16_t raw) { // Helpers internos
return (raw * ADC121_VREF_MV) / ADC121_MAX; // ---------------------
static int adc_raw_to_mv(uint16_t raw)
{
return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
} }
static int compare_uint16(const void *a, const void *b)
{
uint16_t va = *(const uint16_t *)a;
uint16_t vb = *(const uint16_t *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
// ---------------------
// Inicialização PWM + ADC
// ---------------------
void pilot_init(void) void pilot_init(void)
{ {
// PWM (LEDC) configuração // Configura timer do PWM do Pilot (1 kHz)
ledc_timer_config_t ledc_timer = { ledc_timer_config_t ledc_timer = {
.speed_mode = PILOT_PWM_SPEED_MODE, .speed_mode = PILOT_PWM_SPEED_MODE,
.timer_num = PILOT_PWM_TIMER, .timer_num = PILOT_PWM_TIMER,
.duty_resolution = PILOT_PWM_DUTY_RES, .duty_resolution = PILOT_PWM_DUTY_RES,
.freq_hz = 1000, .freq_hz = 1000, // 1 kHz (IEC 61851)
.clk_cfg = LEDC_AUTO_CLK .clk_cfg = LEDC_AUTO_CLK
}; };
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// Canal do PWM no pino configurado em board_config
ledc_channel_config_t ledc_channel = { ledc_channel_config_t ledc_channel = {
.speed_mode = PILOT_PWM_SPEED_MODE, .speed_mode = PILOT_PWM_SPEED_MODE,
.channel = PILOT_PWM_CHANNEL, .channel = PILOT_PWM_CHANNEL,
@@ -60,129 +85,159 @@ void pilot_init(void)
.hpoint = 0 .hpoint = 0
}; };
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
//ESP_ERROR_CHECK(ledc_fade_func_install(0));
// Inicializa ADC121S021 externo // Garante que começa parado e em idle baixo (pilot off)
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
s_mode = PILOT_MODE_DC_LOW;
last_pwm_duty = 0;
// Inicializa driver do ADC121S021
adc121s021_dma_init(); adc121s021_dma_init();
} }
void pilot_set_level(bool level) // ---------------------
// Controlo do modo do Pilot
// ---------------------
void pilot_set_level(bool high)
{ {
if (last_pilot_level == level) return; // só muda se necessário pilot_mode_t target = high ? PILOT_MODE_DC_HIGH : PILOT_MODE_DC_LOW;
last_pilot_level = level;
ESP_LOGI(TAG, "Set level %d", level); // Se já estiver no modo DC desejado e sem PWM ativo, ignora
ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); if (s_mode == target && last_pwm_duty == 0) {
last_pwm_duty = 0; // PWM parado return;
}
ESP_LOGI(TAG, "Pilot set DC level: %s", high ? "HIGH(+12V)" : "LOW/OFF");
// Para PWM e fixa o nível idle do GPIO
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, high ? 1 : 0));
s_mode = target;
last_pwm_duty = 0;
} }
void pilot_set_amps(uint16_t amps) void pilot_set_amps(uint16_t amps)
{ {
if (amps < 6 || amps > 80) { if (amps < 6 || amps > 80)
{
ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 680 A)", amps); ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 680 A)", amps);
return; return;
} }
uint32_t duty_percent; uint32_t duty_percent;
if (amps <= 51)
if (amps <= 51) { {
duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6 duty_percent = (amps * 10) / 6;
} else { }
duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64 else
{
duty_percent = (amps * 10) / 25 + 64;
} }
if (duty_percent > 100) duty_percent = 100; if (duty_percent > 100) duty_percent = 100;
uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100; uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;
if (last_pilot_level == 0 && last_pwm_duty == duty) return; // Se já estiver em PWM com o mesmo duty, ignora
last_pilot_level = 0; if (s_mode == PILOT_MODE_PWM && last_pwm_duty == duty) {
return;
}
s_mode = PILOT_MODE_PWM;
last_pwm_duty = duty; last_pwm_duty = duty;
ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)", ESP_LOGI(TAG, "Pilot set PWM: %d A → %d/%d (≈ %d%% duty)",
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent); amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty); ESP_ERROR_CHECK(ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty));
ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); ESP_ERROR_CHECK(ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL));
} }
bool pilot_get_state(void) { bool pilot_get_state(void)
// true se estamos em 12V fixo; false se PWM ou -12V {
// Se quiser diferenciar PWM, guarde um flag quando chamar set_amps. // "Alto" significa DC +12V (estado A). PWM não conta como DC high.
return (last_pilot_level == 1) && (last_pwm_duty == 0); return (s_mode == PILOT_MODE_DC_HIGH);
}
static int compare_int(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
static int select_low_median_qsort(int *src, int n, int percent) {
int k = (n * percent) / 100;
if (k == 0) k = 1;
int *copy = alloca(n * sizeof(int));
memcpy(copy, src, n * sizeof(int));
qsort(copy, n, sizeof(int), compare_int);
return copy[k / 2];
}
static int select_high_median_qsort(int *src, int n, int percent) {
int k = (n * percent) / 100;
if (k == 0) k = 1;
int *copy = alloca(n * sizeof(int));
memcpy(copy, src, n * sizeof(int));
qsort(copy, n, sizeof(int), compare_int);
return copy[n - k + (k / 2)];
} }
// ---------------------
// Medição do sinal de Pilot (PWM 1 kHz J1772)
// ---------------------
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
{ {
ESP_LOGD(TAG, "pilot_measure"); ESP_LOGD(TAG, "pilot_measure");
int samples[NUM_PILOT_SAMPLES]; uint16_t samples[NUM_PILOT_SAMPLES];
int collected = 0, attempts = 0; int collected = 0;
uint16_t adc_sample = 0; int attempts = 0;
// Lê samples usando ADC121S021 externo while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { {
adc_sample = 0; uint16_t adc_sample;
if (adc121s021_dma_get_sample(&adc_sample)) {
if (adc121s021_dma_get_sample(&adc_sample))
{
samples[collected++] = adc_sample; samples[collected++] = adc_sample;
esp_rom_delay_us(10); esp_rom_delay_us(PILOT_SAMPLE_DELAY_US);
} else { }
else
{
esp_rom_delay_us(100); esp_rom_delay_us(100);
attempts++; attempts++;
} }
} }
if (collected < NUM_PILOT_SAMPLES) { if (collected < NUM_PILOT_SAMPLES)
{
ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES); ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES);
*up_voltage = PILOT_VOLTAGE_1; *up_voltage = PILOT_VOLTAGE_1;
*down_voltage_n12 = false; *down_voltage_n12 = false;
return; return;
} }
int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); // Ordena as amostras para eliminar extremos (ruído/espúrios)
int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); qsort(samples, collected, sizeof(uint16_t), compare_uint16);
int k = (collected * PILOT_EXTREME_PERCENT) / 100;
if (k < 2) k = 2; // garante margem mínima
// descarta k/2 em cada lado (aprox. 10% total, mantendo simetria)
int low_index = k / 2;
int high_index = collected - 1 - (k / 2);
if (low_index < 0) low_index = 0;
if (high_index >= collected) high_index = collected - 1;
if (high_index <= low_index) high_index = low_index;
uint16_t low_raw = samples[low_index];
uint16_t high_raw = samples[high_index];
int high_mv = adc_raw_to_mv(high_raw); int high_mv = adc_raw_to_mv(high_raw);
int low_mv = adc_raw_to_mv(low_raw); int low_mv = adc_raw_to_mv(low_raw);
// Aplica thresholds definidos em board_config (em mV) // Determina o nível positivo (+12, +9, +6, +3 ou <3 V)
if (high_mv >= board_config.pilot_down_threshold_12) if (high_mv >= board_config.pilot_down_threshold_12)
{
*up_voltage = PILOT_VOLTAGE_12; *up_voltage = PILOT_VOLTAGE_12;
}
else if (high_mv >= board_config.pilot_down_threshold_9) else if (high_mv >= board_config.pilot_down_threshold_9)
{
*up_voltage = PILOT_VOLTAGE_9; *up_voltage = PILOT_VOLTAGE_9;
}
else if (high_mv >= board_config.pilot_down_threshold_6) else if (high_mv >= board_config.pilot_down_threshold_6)
{
*up_voltage = PILOT_VOLTAGE_6; *up_voltage = PILOT_VOLTAGE_6;
}
else if (high_mv >= board_config.pilot_down_threshold_3) else if (high_mv >= board_config.pilot_down_threshold_3)
{
*up_voltage = PILOT_VOLTAGE_3; *up_voltage = PILOT_VOLTAGE_3;
}
else else
{
*up_voltage = PILOT_VOLTAGE_1; *up_voltage = PILOT_VOLTAGE_1;
}
// Verifica se o nível negativo atinge -12 V (diodo presente, C/D válidos)
*down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12); *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12);
ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12); ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d (high=%d mV, low=%d mV)",
*up_voltage, *down_voltage_n12, high_mv, low_mv);
} }

View File

@@ -1,73 +1,210 @@
/* #include <inttypes.h>
* evse_session.c
* Implementation of evse_session module using instantaneous power accumulation
*/
#include <inttypes.h> // for PRIu32
#include "evse_session.h" #include "evse_session.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_log.h" #include "esp_log.h"
#include "evse_events.h"
#include "esp_event.h"
#include "evse_limits.h"
#include "esp_timer.h"
#define EVSE_EVENT_POST_TIMEOUT_MS 50
static const char *TAG = "evse_session"; static const char *TAG = "evse_session";
// Internal state
static TickType_t session_start_tick = 0; static TickType_t session_start_tick = 0;
static uint32_t watt_seconds = 0; // accumulator of W·s
// Tempo real (microsegundos)
static int64_t session_start_us = 0;
static int64_t last_tick_us = 0;
// Energia integrada com tempo real: soma de (W * us)
static uint64_t watt_microseconds = 0;
static evse_session_t last_session; static evse_session_t last_session;
static bool last_session_valid = false; static bool last_session_valid = false;
static uint32_t session_counter = 0;
void evse_session_init(void) { static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
static void post_session_event(const evse_session_event_data_t *evt)
{
esp_err_t err = esp_event_post(
EVSE_EVENTS,
EVSE_EVENT_SESSION,
evt,
sizeof(*evt),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_event_post(EVSE_EVENT_SESSION) failed: %s", esp_err_to_name(err));
}
}
void evse_session_init(void)
{
portENTER_CRITICAL(&session_mux);
session_start_tick = 0; session_start_tick = 0;
watt_seconds = 0; session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
last_session_valid = false; last_session_valid = false;
session_counter = 0;
portEXIT_CRITICAL(&session_mux);
} }
void evse_session_start(void) { void evse_session_start(void)
session_start_tick = xTaskGetTickCount(); {
watt_seconds = 0; TickType_t tick = xTaskGetTickCount();
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick); int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
session_start_tick = tick;
session_start_us = now_us;
last_tick_us = now_us;
watt_microseconds = 0;
session_counter++;
uint32_t id = session_counter;
portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64,
id, (unsigned)tick, now_us);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED,
.session_id = id,
.duration_s = 0,
.energy_wh = 0,
.avg_power_w = 0,
.is_current = true,
};
post_session_event(&evt);
} }
void evse_session_end(void) { void evse_session_end(void)
if (session_start_tick == 0) { {
TickType_t start_tick;
int64_t start_us;
uint64_t w_us;
uint32_t id;
int64_t end_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
if (session_start_tick == 0)
{
portEXIT_CRITICAL(&session_mux);
ESP_LOGW(TAG, "evse_session_end called without active session"); ESP_LOGW(TAG, "evse_session_end called without active session");
return; return;
} }
TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ;
uint32_t energy_wh = watt_seconds / 3600U;
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0;
last_session.start_tick = session_start_tick; start_tick = session_start_tick;
start_us = session_start_us;
w_us = watt_microseconds;
id = session_counter;
session_start_tick = 0;
session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
portEXIT_CRITICAL(&session_mux);
uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
portENTER_CRITICAL(&session_mux);
last_session.start_tick = start_tick;
last_session.duration_s = duration_s; last_session.duration_s = duration_s;
last_session.energy_wh = energy_wh; last_session.energy_wh = energy_wh;
last_session.avg_power_w = avg_power; last_session.avg_power_w = avg_power;
last_session.is_current = false; last_session.is_current = false;
last_session_valid = true; last_session_valid = true;
portEXIT_CRITICAL(&session_mux);
session_start_tick = 0; ESP_LOGI(TAG,
ESP_LOGI(TAG, "Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg_power=%" PRIu32 " W", "Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
(uint32_t)duration_s, (uint32_t)energy_wh, (uint32_t)avg_power); " Wh, avg_power=%" PRIu32 " W",
id, duration_s, energy_wh, avg_power);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_FINISHED,
.session_id = id,
.duration_s = duration_s,
.energy_wh = energy_wh,
.avg_power_w = avg_power,
.is_current = false,
};
post_session_event(&evt);
} }
void evse_session_tick(void) { void evse_session_tick(void)
if (session_start_tick == 0) return; {
// Should be called every second (or known interval) // Potência instantânea pode ser negativa (ruído/overflow de sensor) -> clamp
uint32_t power_w = evse_meter_get_instant_power(); int p = evse_meter_get_instant_power();
watt_seconds += power_w; uint32_t power_w = (p > 0) ? (uint32_t)p : 0;
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
if (session_start_tick != 0)
{
if (last_tick_us == 0)
{
last_tick_us = now_us;
}
int64_t dt_us = now_us - last_tick_us;
if (dt_us > 0)
{
// Energia incremental: W * us (64-bit)
watt_microseconds += ((uint64_t)power_w * (uint64_t)dt_us);
last_tick_us = now_us;
}
else
{
// relógio não devia andar para trás; ignora
last_tick_us = now_us;
}
}
portEXIT_CRITICAL(&session_mux);
} }
bool evse_session_get(evse_session_t *out) { bool evse_session_get(evse_session_t *out)
if (out == NULL) return false; {
if (out == NULL)
return false;
if (session_start_tick != 0) { TickType_t start_tick;
TickType_t now = xTaskGetTickCount(); int64_t start_us;
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ; uint64_t w_us;
uint32_t energy_wh = watt_seconds / 3600U; bool has_current;
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0; evse_session_t last_copy;
bool last_valid;
out->start_tick = session_start_tick; int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
start_tick = session_start_tick;
start_us = session_start_us;
w_us = watt_microseconds;
has_current = (session_start_tick != 0);
last_copy = last_session;
last_valid = last_session_valid;
portEXIT_CRITICAL(&session_mux);
if (has_current)
{
uint32_t duration_s = (now_us > start_us) ? (uint32_t)((now_us - start_us) / 1000000LL) : 0;
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
out->start_tick = start_tick;
out->duration_s = duration_s; out->duration_s = duration_s;
out->energy_wh = energy_wh; out->energy_wh = energy_wh;
out->avg_power_w = avg_power; out->avg_power_w = avg_power;
@@ -75,8 +212,9 @@ bool evse_session_get(evse_session_t *out) {
return true; return true;
} }
if (last_session_valid) { if (last_valid)
*out = last_session; {
*out = last_copy;
return true; return true;
} }

View File

@@ -5,150 +5,203 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_event.h"
// ========================= #define EVSE_EVENT_POST_TIMEOUT_MS 50
// Internal State Variables
// =========================
static evse_state_t current_state = EVSE_STATE_A; static evse_state_t current_state = EVSE_STATE_A;
static bool is_authorized = false; static bool is_authorized = false;
static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED;
static const char *TAG = "evse_state"; static const char *TAG = "evse_state";
// ========================= static evse_state_event_t map_state_to_event(evse_state_t s)
// Internal Mapping {
// ========================= switch (s)
{
case EVSE_STATE_A:
return EVSE_STATE_EVENT_IDLE;
static evse_state_event_t map_state_to_event(evse_state_t s) {
switch (s) {
case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE;
case EVSE_STATE_B1: case EVSE_STATE_B1:
case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING; case EVSE_STATE_B2:
return EVSE_STATE_EVENT_WAITING;
case EVSE_STATE_C1: case EVSE_STATE_C1:
case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING; case EVSE_STATE_C2:
case EVSE_STATE_D1:
case EVSE_STATE_D2:
return EVSE_STATE_EVENT_CHARGING;
case EVSE_STATE_E: case EVSE_STATE_E:
case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT; case EVSE_STATE_F:
default: return EVSE_STATE_EVENT_IDLE; return EVSE_STATE_EVENT_FAULT;
default:
return EVSE_STATE_EVENT_IDLE;
} }
} }
// ========================= static void post_evse_event(evse_event_id_t id, const void *data, size_t len)
// Public API {
// ========================= esp_err_t err = esp_event_post(
EVSE_EVENTS,
id,
data,
len,
portMAX_DELAY);
void evse_set_state(evse_state_t new_state) { if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_event_post(id=%d) failed: %s", (int)id, esp_err_to_name(err));
}
}
bool evse_state_is_charging(evse_state_t state)
{
// “charging” == energia efetiva (relé ON)
return (state == EVSE_STATE_C2 || state == EVSE_STATE_D2);
}
bool evse_state_is_power_flowing(evse_state_t state)
{
return evse_state_is_charging(state);
}
bool evse_state_is_requesting(evse_state_t state)
{
// EV pediu carga mas o relé ainda está OFF
return (state == EVSE_STATE_C1 || state == EVSE_STATE_D1);
}
bool evse_state_is_plugged(evse_state_t state)
{
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
}
bool evse_state_is_session(evse_state_t state)
{
// Sessão lógica: “autorizado/pronto” ou “a pedir/a fornecer energia”
return (state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2);
}
void evse_set_state(evse_state_t new_state)
{
bool changed = false; bool changed = false;
evse_state_t prev_state; evse_state_t prev_state;
bool start_session = false; bool start_session = false;
bool end_session = false; bool end_session = false;
// 1) Detecta transição de estado dentro da região crítica
portENTER_CRITICAL(&state_mux); portENTER_CRITICAL(&state_mux);
prev_state = current_state; prev_state = current_state;
if (new_state != current_state) {
// se entrou em charging pela primeira vez if (new_state != current_state)
if (evse_state_is_charging(new_state) && !evse_state_is_charging(prev_state)) { {
// Sessão começa quando entra em energia (relé ON)
if (evse_state_is_power_flowing(new_state) && !evse_state_is_power_flowing(prev_state))
{
start_session = true; start_session = true;
} }
// se saiu de charging para qualquer outro // Sessão termina quando sai de energia
else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) { else if (!evse_state_is_power_flowing(new_state) && evse_state_is_power_flowing(prev_state))
{
end_session = true; end_session = true;
} }
current_state = new_state; current_state = new_state;
changed = true; changed = true;
} }
portEXIT_CRITICAL(&state_mux); portEXIT_CRITICAL(&state_mux);
// 2) Executa start/end de sessão FORA da região crítica, evitando logs/alloc dentro dela // Fora da região crítica
if (start_session) { if (start_session)
{
evse_session_start(); evse_session_start();
} }
if (end_session) { if (end_session)
{
evse_session_end(); evse_session_end();
} }
// 3) Se mudou o estado, faz log e dispara evento if (changed)
if (changed) { {
const char *prev_str = evse_state_to_str(prev_state); ESP_LOGI(TAG, "State changed: %s → %s",
const char *curr_str = evse_state_to_str(new_state); evse_state_to_str(prev_state),
ESP_LOGI(TAG, "State changed: %s → %s", prev_str, curr_str); evse_state_to_str(new_state));
evse_state_event_data_t evt = { evse_state_event_data_t evt = {
.state = map_state_to_event(new_state) .state = map_state_to_event(new_state)};
}; post_evse_event(EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt));
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
} }
evse_state_t evse_get_state(void)
{
evse_state_t evse_get_state(void) {
portENTER_CRITICAL(&state_mux); portENTER_CRITICAL(&state_mux);
evse_state_t s = current_state; evse_state_t s = current_state;
portEXIT_CRITICAL(&state_mux); portEXIT_CRITICAL(&state_mux);
return s; return s;
} }
const char* evse_state_to_str(evse_state_t state) { const char *evse_state_to_str(evse_state_t state)
switch (state) { {
case EVSE_STATE_A: return "A - EV Not Connected (12V)"; switch (state)
case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)"; {
case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)"; case EVSE_STATE_A:
case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)"; return "A - EV Not Connected (12V)";
case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)"; case EVSE_STATE_B1:
case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)"; return "B1 - EV Connected (9V, Not Authorized)";
case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)"; case EVSE_STATE_B2:
case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)"; return "B2 - EV Connected (9V, Authorized and Ready)";
case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal"; case EVSE_STATE_C1:
default: return "Unknown State"; return "C1 - Charging Requested (6V, Relay Off)";
case EVSE_STATE_C2:
return "C2 - Charging Active (6V, Relay On)";
case EVSE_STATE_D1:
return "D1 - Ventilation Required (3V, Relay Off)";
case EVSE_STATE_D2:
return "D2 - Ventilation Active (3V, Relay On)";
case EVSE_STATE_E:
return "E - Error: Control Pilot Shorted to Ground (0V)";
case EVSE_STATE_F:
return "F - Fault: EVSE Unavailable or No Pilot Signal";
default:
return "Unknown State";
} }
} }
void evse_state_init(void) { void evse_state_init(void)
{
portENTER_CRITICAL(&state_mux); portENTER_CRITICAL(&state_mux);
current_state = EVSE_STATE_A; current_state = EVSE_STATE_A;
is_authorized = true; is_authorized = false;
portEXIT_CRITICAL(&state_mux); portEXIT_CRITICAL(&state_mux);
ESP_LOGI("EVSE_STATE", "Initialized in state: %s", evse_state_to_str(current_state)); ESP_LOGI(TAG, "Initialized in state: %s", evse_state_to_str(current_state));
evse_state_event_data_t evt = { evse_state_event_data_t evt = {
.state = map_state_to_event(current_state) .state = map_state_to_event(current_state)};
}; post_evse_event(EVSE_EVENT_INIT, &evt, sizeof(evt));
esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
} }
void evse_state_tick(void) { void evse_state_tick(void)
// Placeholder for future state logic {
// placeholder
} }
bool evse_state_is_charging(evse_state_t state) { void evse_state_set_authorized(bool authorized)
return state == EVSE_STATE_C1 || state == EVSE_STATE_C2; {
}
bool evse_state_is_plugged(evse_state_t state) {
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
}
bool evse_state_is_session(evse_state_t state) {
return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2;
}
void evse_state_set_authorized(bool authorized) {
portENTER_CRITICAL(&state_mux); portENTER_CRITICAL(&state_mux);
is_authorized = authorized; is_authorized = authorized;
portEXIT_CRITICAL(&state_mux); portEXIT_CRITICAL(&state_mux);
} }
bool evse_state_get_authorized(void) { bool evse_state_get_authorized(void)
{
portENTER_CRITICAL(&state_mux); portENTER_CRITICAL(&state_mux);
bool result = is_authorized; bool result = is_authorized;
portEXIT_CRITICAL(&state_mux); portEXIT_CRITICAL(&state_mux);

View File

@@ -71,6 +71,24 @@ void evse_state_set_authorized(bool authorized);
*/ */
bool evse_state_get_authorized(void); bool evse_state_get_authorized(void);
// ===============================
// Configuration / Availability
// ===============================
/**
* @brief Enable or disable the EVSE (software flag, persisted in NVS).
*/
void evse_set_enabled(bool value);
/**
* @brief Returns true if the EVSE is currently available for use.
*/
bool evse_is_available(void);
/**
* @brief Set EVSE availability flag (may be persisted in NVS).
*/
void evse_set_available(bool value);
// =============================== // ===============================
// Limit Status // Limit Status

View File

@@ -15,11 +15,9 @@ extern "C" {
// Limites Globais (Defines) // Limites Globais (Defines)
// ======================== // ========================
// Corrente máxima de carregamento (configurável pelo usuário)
#define MIN_CHARGING_CURRENT_LIMIT 6 // A #define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A #define MAX_CHARGING_CURRENT_LIMIT 32 // A
// Corrente via cabo (proximity) — se configurável
#define MIN_CABLE_CURRENT_LIMIT 6 // A #define MIN_CABLE_CURRENT_LIMIT 6 // A
#define MAX_CABLE_CURRENT_LIMIT 63 // A #define MAX_CABLE_CURRENT_LIMIT 63 // A
@@ -31,23 +29,20 @@ extern "C" {
esp_err_t evse_config_init(void); esp_err_t evse_config_init(void);
void evse_check_defaults(void); void evse_check_defaults(void);
// Corrente de carregamento // Corrente máxima de hardware (fixa)
uint8_t evse_get_max_charging_current(void); uint8_t evse_get_max_charging_current(void);
esp_err_t evse_set_max_charging_current(uint8_t value);
// Corrente configurável (persistida) <= max hardware
uint16_t evse_get_charging_current(void); uint16_t evse_get_charging_current(void);
esp_err_t evse_set_charging_current(uint16_t value); esp_err_t evse_set_charging_current(uint16_t value);
uint16_t evse_get_default_charging_current(void); // Corrente runtime (RAM) <= max hardware (load balancer pode alterar)
esp_err_t evse_set_default_charging_current(uint16_t value);
// Configuração de socket outlet
bool evse_get_socket_outlet(void);
esp_err_t evse_set_socket_outlet(bool socket_outlet);
void evse_set_runtime_charging_current(uint16_t value); void evse_set_runtime_charging_current(uint16_t value);
uint16_t evse_get_runtime_charging_current(void); uint16_t evse_get_runtime_charging_current(void);
// Socket outlet
bool evse_get_socket_outlet(void);
esp_err_t evse_set_socket_outlet(bool socket_outlet);
// RCM // RCM
bool evse_is_rcm(void); bool evse_is_rcm(void);

View File

@@ -5,11 +5,13 @@
#include <stdbool.h> #include <stdbool.h>
#include "evse_pilot.h" #include "evse_pilot.h"
// ----------------------------------------------------
#define EVSE_ERR_AUTO_CLEAR_BITS ( \ // Holdoff interno pós-erro (sem expor "cooldown" ao resto)
EVSE_ERR_DIODE_SHORT_BIT | \ // ----------------------------------------------------
EVSE_ERR_TEMPERATURE_HIGH_BIT | \ // Após TODOS os erros reais desaparecerem (raw_bits == 0),
EVSE_ERR_RCM_TRIGGERED_BIT ) // o módulo mantém o erro "visível" durante este tempo.
// Durante este período, evse_get_error() continua != 0.
#define EVSE_ERROR_COOLDOWN_MS 60000
// Error bits // Error bits
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0) #define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
@@ -26,20 +28,22 @@ void evse_error_init(void);
// Verificações e monitoramento // Verificações e monitoramento
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v); void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
void evse_temperature_check(void); void evse_temperature_check(void);
void evse_error_tick(void); void evse_error_tick(void);
// Leitura e controle de erros // Leitura e controle de erros (estado "visível" com holdoff)
uint32_t evse_get_error(void); uint32_t evse_get_error(void);
bool evse_is_error_cleared(void);
void evse_mark_error_cleared(void);
void evse_error_set(uint32_t bitmask); void evse_error_set(uint32_t bitmask);
void evse_error_clear(uint32_t bitmask); void evse_error_clear(uint32_t bitmask);
bool evse_error_is_active(void); bool evse_error_is_active(void);
uint32_t evse_error_get_bits(void); uint32_t evse_error_get_bits(void);
void evse_error_reset_flag(void);
// ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos"
// (fica true quando o erro visível chega a 0; pode ser útil para UI/logs)
// ----------------------------------------------------
bool evse_error_cleared_flag(void); bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H #endif // EVSE_ERROR_H

View File

@@ -2,6 +2,9 @@
#define EVSE_EVENTS_H #define EVSE_EVENTS_H
#pragma once #pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_event.h" #include "esp_event.h"
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
@@ -12,8 +15,13 @@ typedef enum {
EVSE_EVENT_CONFIG_UPDATED, EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED, EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED, EVSE_EVENT_AVAILABLE_UPDATED,
EVSE_EVENT_SESSION,
EVSE_EVENT_ERROR_CHANGED,
} evse_event_id_t; } evse_event_id_t;
// -----------------
// Eventos de STATE
// -----------------
typedef enum { typedef enum {
EVSE_STATE_EVENT_IDLE, EVSE_STATE_EVENT_IDLE,
EVSE_STATE_EVENT_WAITING, EVSE_STATE_EVENT_WAITING,
@@ -25,22 +33,52 @@ typedef struct {
evse_state_event_t state; evse_state_event_t state;
} evse_state_event_data_t; } evse_state_event_data_t;
// -----------------
// Eventos de SESSÃO
// -----------------
typedef enum {
EVSE_SESSION_EVENT_STARTED = 0,
EVSE_SESSION_EVENT_FINISHED,
} evse_session_event_type_t;
typedef struct { typedef struct {
bool charging; // Estado de carregamento evse_session_event_type_t type; ///< STARTED / FINISHED
float hw_max_current; // Corrente máxima suportada pelo hardware
float runtime_current; // Corrente de carregamento em uso uint32_t session_id;
int64_t timestamp_us; // Momento da atualização uint32_t duration_s;
uint32_t energy_wh;
uint32_t avg_power_w;
bool is_current;
} evse_session_event_data_t;
// -----------------
// Eventos de CONFIG
// -----------------
typedef struct {
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp_us;
} evse_config_event_data_t; } evse_config_event_data_t;
// Eventos simples e específicos
typedef struct { typedef struct {
bool enabled; // novo estado de enabled bool enabled;
int64_t timestamp_us; // epoch micros int64_t timestamp_us;
} evse_enable_event_data_t; } evse_enable_event_data_t;
typedef struct { typedef struct {
bool available; // novo estado de available bool available;
int64_t timestamp_us; // epoch micros int64_t timestamp_us;
} evse_available_event_data_t; } evse_available_event_data_t;
// -----------------
// Eventos de ERRO
// -----------------
typedef struct {
uint32_t error_bits; ///< estado atual (todos os bits de erro)
uint32_t changed_mask; ///< bits que mudaram nesta notificação
int64_t timestamp_us; ///< esp_timer_get_time()
} evse_error_event_data_t;
#endif // EVSE_EVENTS_H #endif // EVSE_EVENTS_H

View File

@@ -1,3 +1,4 @@
// === Início de: components/evse/include/evse_limits.h ===
#ifndef EVSE_LIMITS_H #ifndef EVSE_LIMITS_H
#define EVSE_LIMITS_H #define EVSE_LIMITS_H
@@ -15,7 +16,6 @@ extern "C" {
/** /**
* @brief Sets the internal 'limit reached' flag. * @brief Sets the internal 'limit reached' flag.
* Called internally when a limit condition is triggered.
*/ */
void evse_set_limit_reached(bool value); void evse_set_limit_reached(bool value);
@@ -24,6 +24,11 @@ void evse_set_limit_reached(bool value);
*/ */
bool evse_get_limit_reached(void); bool evse_get_limit_reached(void);
/**
* @brief Convenience alias for evse_get_limit_reached().
*/
bool evse_is_limit_reached(void);
/** /**
* @brief Checks if any session limit has been exceeded (energy, time or power). * @brief Checks if any session limit has been exceeded (energy, time or power).
* Should be called periodically during charging. * Should be called periodically during charging.
@@ -48,30 +53,13 @@ void evse_set_charging_time_limit(uint32_t value);
/** /**
* @brief Get/set minimum acceptable power level (in Watts). * @brief Get/set minimum acceptable power level (in Watts).
* If the power remains below this for a long time, the session may be interrupted.
*/ */
uint16_t evse_get_under_power_limit(void); uint16_t evse_get_under_power_limit(void);
void evse_set_under_power_limit(uint16_t value); void evse_set_under_power_limit(uint16_t value);
// ============================
// Default (Persistent) Limits
// ============================
/**
* @brief Default values used after system boot or reset.
* These can be restored from NVS or fallback values.
*/
uint32_t evse_get_default_consumption_limit(void);
void evse_set_default_consumption_limit(uint32_t value);
uint32_t evse_get_default_charging_time_limit(void);
void evse_set_default_charging_time_limit(uint32_t value);
uint16_t evse_get_default_under_power_limit(void);
void evse_set_default_under_power_limit(uint16_t value);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // EVSE_LIMITS_H #endif // EVSE_LIMITS_H
// === Fim de: components/evse/include/evse_limits.h ===

View File

@@ -2,64 +2,42 @@
#define PILOT_H_ #define PILOT_H_
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C"
{
#endif #endif
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
/**
* @brief Níveis categóricos de tensão no sinal CP (Control Pilot)
*/
typedef enum typedef enum
{ {
PILOT_VOLTAGE_12, ///< Estado A: +12V PILOT_VOLTAGE_12,
PILOT_VOLTAGE_9, ///< Estado B: +9V PILOT_VOLTAGE_9,
PILOT_VOLTAGE_6, ///< Estado C: +6V PILOT_VOLTAGE_6,
PILOT_VOLTAGE_3, ///< Estado D: +3V PILOT_VOLTAGE_3,
PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V PILOT_VOLTAGE_1
} pilot_voltage_t; } pilot_voltage_t;
/**
* @brief Inicializa o driver do sinal Pilot
*/
void pilot_init(void); void pilot_init(void);
/** /**
* @brief Define o nível do Pilot: +12V ou -12V * @brief Define o pilot em modo DC.
* *
* @param level true = +12V, false = -12V * @param high true = nível alto (+12V)
* false = nível baixo (-12V)
*/ */
void pilot_set_level(bool level); void pilot_set_level(bool high);
/**
* @brief Ativa o PWM do Pilot com corrente limitada
*
* @param amps Corrente em ampères (ex: 16 = 16A)
*/
void pilot_set_amps(uint16_t amps); void pilot_set_amps(uint16_t amps);
/**
* @brief Mede o nível de tensão do Pilot e detecta -12V
*
* @param up_voltage Valor categórico da tensão positiva
* @param down_voltage_n12 true se o nível negativo atingir -12V
*/
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12); void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
/**
* @brief Retorna o estado lógico atual do Pilot (nível alto = +12V)
*
* @return true se nível atual for +12V, false se for -12V
*/
bool pilot_get_state(void); bool pilot_get_state(void);
/** typedef struct
* @brief Cache interno opcional dos níveis de tensão reais do Pilot {
*/ uint16_t high_mv;
typedef struct { uint16_t low_mv;
uint16_t high_mv; ///< Pico positivo medido (mV)
uint16_t low_mv; ///< Pico negativo medido (mV)
} pilot_voltage_cache_t; } pilot_voltage_cache_t;
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -1,9 +1,3 @@
/*
* evse_session.h
* Module to track and retrieve charging session data (current or last completed),
* accumulating energy via periodic tick of instantaneous power.
*/
#ifndef EVSE_SESSION_H #ifndef EVSE_SESSION_H
#define EVSE_SESSION_H #define EVSE_SESSION_H
@@ -15,39 +9,23 @@
* @brief Charging session statistics * @brief Charging session statistics
*/ */
typedef struct { typedef struct {
TickType_t start_tick; ///< tick when session began TickType_t start_tick; ///< tick when session began (debug/trace)
uint32_t duration_s; ///< total duration in seconds uint32_t duration_s; ///< total duration in seconds (tempo real)
uint32_t energy_wh; ///< total energy consumed in Wh uint32_t energy_wh; ///< total energy consumed in Wh (tempo real)
uint32_t avg_power_w; ///< average power in W uint32_t avg_power_w; ///< average power in W
bool is_current; ///< true if session still in progress bool is_current; ///< true if session still in progress
} evse_session_t; } evse_session_t;
/**
* @brief Initialize the session module
*/
void evse_session_init(void); void evse_session_init(void);
/**
* @brief Mark the beginning of a charging session
*/
void evse_session_start(void); void evse_session_start(void);
/**
* @brief Mark the end of the charging session and store it as "last session"
*/
void evse_session_end(void); void evse_session_end(void);
/** /**
* @brief Periodic tick: must be called (e.g., each 1s) to accumulate energy from instant power * @brief Periodic tick: called (e.g., each 1s) to accumulate energy from instant power.
* Implementação usa esp_timer (não assume 1s exato).
*/ */
void evse_session_tick(void); void evse_session_tick(void);
/**
* @brief Retrieve statistics of either the current ongoing session (if any) or
* the last completed session.
* @param out pointer to evse_session_t to be filled
* @return true if there is a current or last session available, false otherwise
*/
bool evse_session_get(evse_session_t *out); bool evse_session_get(evse_session_t *out);
#endif // EVSE_SESSION_H #endif // EVSE_SESSION_H

View File

@@ -6,14 +6,12 @@
#include "evse_events.h" #include "evse_events.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C"
{
#endif #endif
// ============================ typedef enum
// EVSE Pilot Signal States {
// ============================
typedef enum {
EVSE_STATE_A, // EV Not Connected (12V) EVSE_STATE_A, // EV Not Connected (12V)
EVSE_STATE_B1, // EV Connected (9V, Not Authorized) EVSE_STATE_B1, // EV Connected (9V, Not Authorized)
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready) EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
@@ -25,70 +23,50 @@ typedef enum {
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
} evse_state_t; } evse_state_t;
// ============================
// Initialization // Initialization
// ============================
/**
* @brief Initializes the EVSE state machine and default state.
*/
void evse_state_init(void); void evse_state_init(void);
/**
* @brief Periodic tick for state handling (optional hook).
*/
void evse_state_tick(void); void evse_state_tick(void);
// ============================
// State Access & Control // State Access & Control
// ============================
/**
* @brief Returns the current EVSE state.
*/
evse_state_t evse_get_state(void); evse_state_t evse_get_state(void);
/**
* @brief Sets the current EVSE state and emits a change event if needed.
*/
void evse_set_state(evse_state_t state); void evse_set_state(evse_state_t state);
/**
* @brief Converts the state enum into a human-readable string.
*/
const char *evse_state_to_str(evse_state_t state); const char *evse_state_to_str(evse_state_t state);
// ============================ // ---------------------------
// State Evaluation Helpers // State Evaluation Helpers
// ============================ // ---------------------------
/** /**
* @brief True if EV is in an active session (B2, C1, C2). * @brief True se existe uma sessão "lógica" ativa (carro ligado e autorizado/pronto ou a carregar).
* Inclui B2, C1/C2, D1/D2.
*/ */
bool evse_state_is_session(evse_state_t state); bool evse_state_is_session(evse_state_t state);
/** /**
* @brief True if EV is actively charging (C1, C2). * @brief True se o EVSE está a fornecer energia (relé ON).
* Estados com energia: C2 e D2.
*
* Nota: isto substitui a antiga interpretação “C1/C2”.
*/ */
bool evse_state_is_charging(evse_state_t state); bool evse_state_is_charging(evse_state_t state);
/** /**
* @brief True if EV is physically plugged in (B1 and beyond). * @brief True se o EV pediu carga mas o relé ainda está OFF (C1/D1).
*/
bool evse_state_is_requesting(evse_state_t state);
/**
* @brief True se há fluxo de energia (alias explícito para charging).
*/
bool evse_state_is_power_flowing(evse_state_t state);
/**
* @brief True se o EV está fisicamente ligado (B1 e além).
*/ */
bool evse_state_is_plugged(evse_state_t state); bool evse_state_is_plugged(evse_state_t state);
// ============================
// Authorization Control // Authorization Control
// ============================
/**
* @brief Sets whether the EV is authorized to charge.
*/
void evse_state_set_authorized(bool authorized); void evse_state_set_authorized(bool authorized);
/**
* @brief Gets whether the EV is currently authorized.
*/
bool evse_state_get_authorized(void); bool evse_state_get_authorized(void);
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -9,8 +9,8 @@ set(srcs
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES driver esp_timer nvs_flash PRIV_REQUIRES driver esp_timer
REQUIRES config evse loadbalancer) REQUIRES config evse loadbalancer storage_service)

View File

@@ -1,16 +1,31 @@
// === Início de: components/evse_link/include/evse_link_events.h ===
#ifndef EVSE_LINK_EVENTS_H_ #ifndef EVSE_LINK_EVENTS_H_
#define EVSE_LINK_EVENTS_H_ #define EVSE_LINK_EVENTS_H_
#include "esp_event.h" #include "esp_event.h"
// Base de eventos do EVSE-Link
ESP_EVENT_DECLARE_BASE(EVSE_LINK_EVENTS); ESP_EVENT_DECLARE_BASE(EVSE_LINK_EVENTS);
// Tamanho máximo de tag propagada via EVSE-Link (inclui NUL)
#define EVSE_LINK_TAG_MAX_LEN 32
// IDs de eventos EVSE-Link
typedef enum { typedef enum {
LINK_EVENT_FRAME_RECEIVED, // qualquer frame válido LINK_EVENT_FRAME_RECEIVED, // qualquer frame válido
LINK_EVENT_SLAVE_ONLINE, // heartbeat recebido primeira vez LINK_EVENT_SLAVE_ONLINE, // heartbeat recebido primeira vez
LINK_EVENT_SLAVE_OFFLINE, // sem heartbeat no timeout LINK_EVENT_SLAVE_OFFLINE, // sem heartbeat no timeout
LINK_EVENT_MASTER_POLL_SENT, // opcional: poll enviado pelo master LINK_EVENT_MASTER_POLL_SENT, // opcional: poll enviado pelo master
LINK_EVENT_CURRENT_LIMIT_APPLIED, LINK_EVENT_CURRENT_LIMIT_APPLIED,
LINK_EVENT_SLAVE_CONFIG_UPDATED // <- NOVO evento LINK_EVENT_SLAVE_CONFIG_UPDATED, // config atualizada pelo master
LINK_EVENT_REMOTE_AUTH_GRANTED // autorização remota (master -> slave)
} evse_link_event_t; } evse_link_event_t;
// Payload para LINK_EVENT_REMOTE_AUTH_GRANTED
typedef struct {
char tag[EVSE_LINK_TAG_MAX_LEN]; // idTag enviada pelo master
} evse_link_auth_grant_event_t;
#endif // EVSE_LINK_EVENTS_H_ #endif // EVSE_LINK_EVENTS_H_
// === Fim de: components/evse_link/include/evse_link_events.h ===

View File

@@ -5,14 +5,20 @@
#include <stdbool.h> #include <stdbool.h>
#include "driver/uart.h" #include "driver/uart.h"
// UART configuration // UART instance and configuration
#define UART_PORT UART_NUM_2 #define UART_PORT UART_NUM_2 // Usa a UART2
#define UART_BAUDRATE 115200 #define UART_BAUDRATE 115200
#define UART_RX_BUF_SIZE 256 #define UART_RX_BUF_SIZE 256
// GPIO pin assignments for UART // GPIO pin assignments for UART (ajuste conforme o hardware)
#define TX_PIN 21 // GPIO21 -> RX on other board #define UART_TXD 17 // TX -> DI do MAX3485
#define RX_PIN 22 // GPIO22 -> TX on other board #define UART_RXD 16 // RX -> RO do MAX3485
#define UART_RTS 2 // RTS -> DE+RE do MAX3485
// Conveniência: nomes usados no .c
#define TX_PIN UART_TXD
#define RX_PIN UART_RXD
#define RTS_PIN UART_RTS
// Frame delimiters // Frame delimiters
#define MAGIC_START 0x7E #define MAGIC_START 0x7E

View File

@@ -2,19 +2,23 @@
#include "evse_link.h" #include "evse_link.h"
#include "evse_link_framing.h" #include "evse_link_framing.h"
#include "driver/uart.h" #include "driver/uart.h"
#include "nvs.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h"
#include <stdbool.h>
#include <stdint.h>
#include "storage_service.h"
static const char *TAG = "evse_link"; static const char *TAG = "evse_link";
// NVS keys // Storage keys
#define _NVS_NAMESPACE "evse_link" #define _NVS_NAMESPACE "evse_link"
#define _NVS_MODE_KEY "mode" #define _KEY_MODE "mode"
#define _NVS_ID_KEY "self_id" #define _KEY_SELF_ID "self_id"
#define _NVS_ENABLED_KEY "enabled" #define _KEY_ENABLED "enabled"
// UART parameters // UART parameters
#define UART_PORT UART_NUM_2 #define UART_PORT UART_NUM_2
@@ -37,10 +41,8 @@ static void framing_rx_cb(uint8_t src, uint8_t dest,
{ {
ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len); ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len);
if (_rx_cb) if (_rx_cb)
{
_rx_cb(src, dest, payload, len); _rx_cb(src, dest, payload, len);
} }
}
// Register protocol-level Rx callback // Register protocol-level Rx callback
void evse_link_register_rx_cb(evse_link_rx_cb_t cb) void evse_link_register_rx_cb(evse_link_rx_cb_t cb)
@@ -48,99 +50,140 @@ void evse_link_register_rx_cb(evse_link_rx_cb_t cb)
_rx_cb = cb; _rx_cb = cb;
} }
// Load config from NVS // Load config from storage_service (NVS-backed)
enum
{
EV_OK = ESP_OK
};
static void load_link_config(void) static void load_link_config(void)
{ {
nvs_handle_t handle; uint8_t u8 = 0;
if (nvs_open(_NVS_NAMESPACE, NVS_READONLY, &handle) != EV_OK)
{
ESP_LOGW(TAG, "NVS open failed, using defaults");
return;
}
uint8_t mode, id, en;
if (nvs_get_u8(handle, _NVS_MODE_KEY, &mode) == EV_OK &&
(mode == EVSE_LINK_MODE_MASTER || mode == EVSE_LINK_MODE_SLAVE))
{
_mode = (evse_link_mode_t)mode;
}
if (nvs_get_u8(handle, _NVS_ID_KEY, &id) == EV_OK)
{
_self_id = id;
}
if (nvs_get_u8(handle, _NVS_ENABLED_KEY, &en) == EV_OK)
{
_enabled = (en != 0);
}
nvs_close(handle);
}
// Save config to NVS // mode
static void save_link_config(void) esp_err_t err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_MODE, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK && (u8 == (uint8_t)EVSE_LINK_MODE_MASTER || u8 == (uint8_t)EVSE_LINK_MODE_SLAVE))
{ {
nvs_handle_t handle; _mode = (evse_link_mode_t)u8;
if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK)
{
nvs_set_u8(handle, _NVS_MODE_KEY, (uint8_t)_mode);
nvs_set_u8(handle, _NVS_ID_KEY, _self_id);
nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0);
nvs_commit(handle);
nvs_close(handle);
} }
else else
{ {
ESP_LOGE(TAG, "Failed to save NVS"); // default + persist
_mode = EVSE_LINK_MODE_MASTER;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_MODE, (uint8_t)_mode);
ESP_LOGW(TAG, "Missing/invalid mode (%s) -> default MASTER (persisted async)",
esp_err_to_name(err));
} }
// self_id
err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_SELF_ID, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK)
{
_self_id = u8;
}
else
{
_self_id = 0x01;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_SELF_ID, _self_id);
ESP_LOGW(TAG, "Missing self_id (%s) -> default 0x%02X (persisted async)",
esp_err_to_name(err), _self_id);
}
// enabled
err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_ENABLED, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK && u8 <= 1)
{
_enabled = (u8 != 0);
}
else
{
_enabled = false;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_ENABLED, 0);
ESP_LOGW(TAG, "Missing/invalid enabled (%s) -> default false (persisted async)",
esp_err_to_name(err));
}
}
// Save config to storage_service (debounced)
static void save_link_config(void)
{
// Debounced writes: não bloqueia e reduz desgaste
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_MODE, (uint8_t)_mode);
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_SELF_ID, _self_id);
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_ENABLED, _enabled ? 1 : 0);
// opcional: se quiseres persistência imediata em configurações “críticas”
// (void)storage_flush_async();
} }
// Getters/setters // Getters/setters
void evse_link_set_mode(evse_link_mode_t m) void evse_link_set_mode(evse_link_mode_t m)
{ {
if (m != EVSE_LINK_MODE_MASTER && m != EVSE_LINK_MODE_SLAVE)
{
ESP_LOGW(TAG, "Invalid link mode: %d", (int)m);
return;
}
if (_mode == m)
return;
_mode = m; _mode = m;
save_link_config(); save_link_config();
} }
evse_link_mode_t evse_link_get_mode(void) { return _mode; } evse_link_mode_t evse_link_get_mode(void) { return _mode; }
void evse_link_set_self_id(uint8_t id) void evse_link_set_self_id(uint8_t id)
{ {
if (_self_id == id)
return;
_self_id = id; _self_id = id;
save_link_config(); save_link_config();
} }
uint8_t evse_link_get_self_id(void) { return _self_id; } uint8_t evse_link_get_self_id(void) { return _self_id; }
void evse_link_set_enabled(bool en) void evse_link_set_enabled(bool en)
{ {
if (_enabled == en)
return;
_enabled = en; _enabled = en;
save_link_config(); save_link_config();
} }
bool evse_link_is_enabled(void) { return _enabled; } bool evse_link_is_enabled(void) { return _enabled; }
// RX task: reads bytes from UART and feeds framing // RX task: reads bytes from UART and feeds framing
static void evse_link_rx_task(void *arg) static void evse_link_rx_task(void *arg)
{ {
(void)arg;
uint8_t buf[UART_RX_BUF_SIZE]; uint8_t buf[UART_RX_BUF_SIZE];
while (true) while (true)
{ {
int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000)); int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000));
if (len > 0) if (len > 0)
{ {
for (int i = 0; i < len; ++i) for (int i = 0; i < len; ++i)
{
evse_link_recv_byte(buf[i]); evse_link_recv_byte(buf[i]);
} }
} }
} }
}
// Initialize EVSE-Link component // Initialize EVSE-Link component
void evse_link_init(void) void evse_link_init(void)
{
// garante storage disponível
esp_err_t se = storage_service_init();
if (se != ESP_OK)
{
ESP_LOGE(TAG, "storage_service_init failed: %s (using defaults in RAM)", esp_err_to_name(se));
// segue com defaults em RAM
}
else
{ {
load_link_config(); load_link_config();
}
ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d", ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d",
_mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S', _mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S',
_self_id, _enabled); _self_id, _enabled);
if (!_enabled) if (!_enabled)
return; return;
@@ -149,24 +192,21 @@ void evse_link_init(void)
evse_link_framing_register_cb(framing_rx_cb); evse_link_framing_register_cb(framing_rx_cb);
// 2) start RX task // 2) start RX task
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL); xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 3, NULL);
// 3) delegate to master or slave // 3) delegate to master or slave
if (_mode == EVSE_LINK_MODE_MASTER) if (_mode == EVSE_LINK_MODE_MASTER)
{
evse_link_master_init(); evse_link_master_init();
}
else else
{
evse_link_slave_init(); evse_link_slave_init();
} }
}
// Send a frame (delegates to framing module) // Send a frame (delegates to framing module)
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len) bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len)
{ {
if (!evse_link_is_enabled()) if (!evse_link_is_enabled())
return false; return false;
uint8_t src = evse_link_get_self_id(); uint8_t src = evse_link_get_self_id();
return evse_link_framing_send(dest, src, payload, len); return evse_link_framing_send(dest, src, payload, len);
} }

View File

@@ -1,5 +1,3 @@
// components/evse_link_framing/src/evse_link_framing.c
#include "evse_link_framing.h" #include "evse_link_framing.h"
#include "driver/uart.h" #include "driver/uart.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
@@ -8,75 +6,124 @@
static const char *TAG = "evse_framing"; static const char *TAG = "evse_framing";
static SemaphoreHandle_t tx_mutex; static SemaphoreHandle_t tx_mutex = NULL;
static uint8_t seq = 0; static uint8_t seq = 0;
static evse_link_frame_cb_t rx_cb = NULL; static evse_link_frame_cb_t rx_cb = NULL;
// CRC-8 (polynomial 0x07) // CRC-8 (polynomial 0x07)
static uint8_t crc8(const uint8_t *data, uint8_t len) { static uint8_t crc8(const uint8_t *data, uint8_t len)
{
uint8_t crc = 0; uint8_t crc = 0;
for (uint8_t i = 0; i < len; ++i) { for (uint8_t i = 0; i < len; ++i) {
crc ^= data[i]; crc ^= data[i];
for (uint8_t b = 0; b < 8; ++b) { for (uint8_t b = 0; b < 8; ++b) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1); if (crc & 0x80) {
crc = (uint8_t)((crc << 1) ^ 0x07);
} else {
crc <<= 1;
}
} }
} }
return crc; return crc;
} }
void evse_link_framing_init(void) { void evse_link_framing_init(void)
// Create mutex for TX {
// Mutex para proteger TX (framings de várias tasks)
tx_mutex = xSemaphoreCreateMutex(); tx_mutex = xSemaphoreCreateMutex();
// Install UART driver
uart_driver_install(UART_PORT, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0); // Instala driver UART
uart_param_config(UART_PORT, &(uart_config_t){ uart_driver_install(UART_PORT,
UART_RX_BUF_SIZE * 2, // RX buffer
0, // TX buffer (0 = usa buffer interno)
0,
NULL,
0);
uart_config_t cfg = {
.baud_rate = UART_BAUDRATE, .baud_rate = UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS, .data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE, .parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1, .stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
}); .source_clk = UART_SCLK_DEFAULT,
uart_set_pin(UART_PORT, TX_PIN, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); };
uart_param_config(UART_PORT, &cfg);
// Define pinos: TX, RX e RTS (RTS ligado a DE+RE do transceiver RS485)
uart_set_pin(UART_PORT,
TX_PIN, // MB_UART_TXD (ex: GPIO17)
RX_PIN, // MB_UART_RXD (ex: GPIO16)
RTS_PIN, // MB_UART_RTS (ex: GPIO2, DE+RE)
UART_PIN_NO_CHANGE);
// Modo RS485 half-duplex: driver controla RTS/DE/RE automaticamente
uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
ESP_LOGI(TAG, "Framing init: UART%d TX=%d RX=%d RTS(DE/RE)=%d baud=%d",
UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE);
} }
bool evse_link_framing_send(uint8_t dest, uint8_t src, bool evse_link_framing_send(uint8_t dest, uint8_t src,
const uint8_t *payload, uint8_t len) { const uint8_t *payload, uint8_t len)
if (len > EVSE_LINK_MAX_PAYLOAD) return false; {
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) return false; if (len > EVSE_LINK_MAX_PAYLOAD) {
ESP_LOGW(TAG, "Payload too large: %u (max=%u)",
len, EVSE_LINK_MAX_PAYLOAD);
return false;
}
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGW(TAG, "Failed to take TX mutex");
return false;
}
// Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END // Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END
uint8_t frame[EVSE_LINK_MAX_PAYLOAD + 7]; uint8_t frame[EVSE_LINK_MAX_PAYLOAD + 7];
int idx = 0; int idx = 0;
frame[idx++] = MAGIC_START; frame[idx++] = MAGIC_START;
frame[idx++] = dest; frame[idx++] = dest;
frame[idx++] = src; frame[idx++] = src;
frame[idx++] = len + 1; // +1 for SEQ frame[idx++] = (uint8_t)(len + 1); // LEN = SEQ + payload
frame[idx++] = seq; frame[idx++] = seq;
if (len > 0 && payload != NULL) {
memcpy(&frame[idx], payload, len); memcpy(&frame[idx], payload, len);
idx += len; idx += len;
}
// CRC covers DEST + SRC + LEN + SEQ + PAYLOAD // CRC cobre: DEST + SRC + LEN + SEQ + PAYLOAD
uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD]; uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
memcpy(crc_input, &frame[1], 3 + 1 + len); memcpy(crc_input, &frame[1], 3 + 1 + len);
frame[idx++] = crc8(crc_input, 3 + 1 + len); uint8_t crc = crc8(crc_input, (uint8_t)(3 + 1 + len));
frame[idx++] = crc;
frame[idx++] = MAGIC_END; frame[idx++] = MAGIC_END;
uart_write_bytes(UART_PORT, (const char *)frame, idx); // Envia frame completo
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(10)); int written = uart_write_bytes(UART_PORT, (const char *)frame, idx);
if (written != idx) {
ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx);
}
// Aguarda TX terminar (o driver controla DE/RE via RTS)
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(20));
xSemaphoreGive(tx_mutex); xSemaphoreGive(tx_mutex);
ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u", ESP_LOGI(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
dest, src, len, seq); dest, src, len, seq);
seq++; // increment sequence after sending seq++; // incrementa sequência após envio
return true; return true;
} }
void evse_link_framing_recv_byte(uint8_t b) { void evse_link_framing_recv_byte(uint8_t b)
// State machine for frame parsing {
// Máquina de estados para parsing do frame
static enum { static enum {
ST_WAIT_START, ST_WAIT_START = 0,
ST_WAIT_DEST, ST_WAIT_DEST,
ST_WAIT_SRC, ST_WAIT_SRC,
ST_WAIT_LEN, ST_WAIT_LEN,
@@ -88,7 +135,7 @@ void evse_link_framing_recv_byte(uint8_t b) {
static uint8_t rx_dest; static uint8_t rx_dest;
static uint8_t rx_src; static uint8_t rx_src;
static uint8_t rx_len; static uint8_t rx_len; // inclui SEQ + payload
static uint8_t rx_seq; static uint8_t rx_seq;
static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD]; static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD];
static uint8_t rx_pos; static uint8_t rx_pos;
@@ -112,19 +159,25 @@ void evse_link_framing_recv_byte(uint8_t b) {
break; break;
case ST_WAIT_LEN: case ST_WAIT_LEN:
rx_len = b; // includes SEQ + payload rx_len = b; // LEN = SEQ + payload
rx_pos = 0; rx_pos = 0;
rx_state = ST_WAIT_SEQ; rx_state = ST_WAIT_SEQ;
break; break;
case ST_WAIT_SEQ: case ST_WAIT_SEQ:
rx_seq = b; rx_seq = b;
rx_state = (rx_len > 1) ? ST_READING : ST_WAIT_CRC; if (rx_len > 1) {
rx_state = ST_READING;
} else {
rx_state = ST_WAIT_CRC;
}
break; break;
case ST_READING: case ST_READING:
if (rx_pos < EVSE_LINK_MAX_PAYLOAD) {
rx_buf[rx_pos++] = b; rx_buf[rx_pos++] = b;
if (rx_pos >= (rx_len - 1)) { // all payload bytes read }
if (rx_pos >= (uint8_t)(rx_len - 1)) { // payload completo
rx_state = ST_WAIT_CRC; rx_state = ST_WAIT_CRC;
} }
break; break;
@@ -136,33 +189,43 @@ void evse_link_framing_recv_byte(uint8_t b) {
case ST_WAIT_END: case ST_WAIT_END:
if (b == MAGIC_END) { if (b == MAGIC_END) {
// Build data for CRC calculation // Monta buffer para verificar CRC:
// DEST + SRC + LEN + SEQ + PAYLOAD
uint8_t temp[3 + 1 + EVSE_LINK_MAX_PAYLOAD]; uint8_t temp[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
int temp_len = 0; int temp_len = 0;
temp[temp_len++] = rx_dest; temp[temp_len++] = rx_dest;
temp[temp_len++] = rx_src; temp[temp_len++] = rx_src;
temp[temp_len++] = rx_len; temp[temp_len++] = rx_len;
temp[temp_len++] = rx_seq; temp[temp_len++] = rx_seq;
if (rx_len > 1) {
memcpy(&temp[temp_len], rx_buf, rx_len - 1); memcpy(&temp[temp_len], rx_buf, rx_len - 1);
temp_len += rx_len - 1; temp_len += rx_len - 1;
}
uint8_t expected = crc8(temp, temp_len); uint8_t expected = crc8(temp, (uint8_t)temp_len);
if (expected == rx_crc) { if (expected == rx_crc) {
uint8_t payload_len = (uint8_t)(rx_len - 1); // exclui SEQ
if (rx_cb) { if (rx_cb) {
rx_cb(rx_src, rx_dest, rx_buf, rx_len - 1); rx_cb(rx_src, rx_dest, rx_buf, payload_len);
} }
ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u", ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u",
rx_src, rx_dest, rx_len - 1, rx_seq); rx_src, rx_dest, payload_len, rx_seq);
} else { } else {
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X", ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
expected, rx_crc); expected, rx_crc);
} }
} }
// Em qualquer caso, volta a esperar novo frame
rx_state = ST_WAIT_START;
break;
default:
rx_state = ST_WAIT_START; rx_state = ST_WAIT_START;
break; break;
} }
} }
void evse_link_framing_register_cb(evse_link_frame_cb_t cb) { void evse_link_framing_register_cb(evse_link_frame_cb_t cb)
{
rx_cb = cb; rx_cb = cb;
} }

View File

@@ -1,6 +1,5 @@
// === components/evse_link/src/evse_link_master.c ===
#include "evse_link.h" #include "evse_link.h"
#include "evse_link_events.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/timers.h" #include "freertos/timers.h"
@@ -8,8 +7,10 @@
#include "esp_event.h" #include "esp_event.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h>
#include "loadbalancer_events.h" #include "loadbalancer_events.h"
#include "auth_events.h"
static const char *TAG = "evse_link_master"; static const char *TAG = "evse_link_master";
@@ -19,6 +20,7 @@ static const char *TAG = "evse_link_master";
#define CMD_HEARTBEAT_ACK 0x09 #define CMD_HEARTBEAT_ACK 0x09
#define CMD_CONFIG_BROADCAST 0x03 #define CMD_CONFIG_BROADCAST 0x03
#define CMD_SET_CURRENT 0x08 #define CMD_SET_CURRENT 0x08
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização a slave
// payload lengths (exclui byte de opcode) // payload lengths (exclui byte de opcode)
#define LEN_POLL_REQ 1 // [ CMD_POLL ] #define LEN_POLL_REQ 1 // [ CMD_POLL ]
@@ -29,7 +31,8 @@ static const char *TAG = "evse_link_master";
#define LEN_HEARTBEAT_ACK 1 #define LEN_HEARTBEAT_ACK 1
// polling / heartbeat timers interval // polling / heartbeat timers interval
typedef struct { typedef struct
{
TimerHandle_t timer; TimerHandle_t timer;
TickType_t interval; TickType_t interval;
} timer_def_t; } timer_def_t;
@@ -37,8 +40,10 @@ static timer_def_t poll_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(300
static timer_def_t hb_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)}; static timer_def_t hb_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
// --- Send new limit to slave --- // --- Send new limit to slave ---
static void on_new_limit(void* arg, esp_event_base_t base, int32_t id, void* data) { static void on_new_limit(void *arg, esp_event_base_t base, int32_t id, void *data)
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT) return; {
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT)
return;
const loadbalancer_slave_limit_event_t *evt = data; const loadbalancer_slave_limit_event_t *evt = data;
uint8_t slave_id = evt->slave_id; uint8_t slave_id = evt->slave_id;
uint16_t max_current = evt->max_current; uint16_t max_current = evt->max_current;
@@ -46,32 +51,75 @@ static void on_new_limit(void* arg, esp_event_base_t base, int32_t id, void* dat
uint8_t buf[LEN_SET_CURRENT] = { uint8_t buf[LEN_SET_CURRENT] = {
CMD_SET_CURRENT, CMD_SET_CURRENT,
(uint8_t)(max_current & 0xFF), (uint8_t)(max_current & 0xFF),
(uint8_t)(max_current >> 8) (uint8_t)(max_current >> 8)};
};
evse_link_send(slave_id, buf, sizeof(buf)); evse_link_send(slave_id, buf, sizeof(buf));
ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, max_current); ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, max_current);
} }
// --- Bridge AUTH -> EVSE-Link: enviar AUTH_GRANTED para slaves ---
static void on_auth_result(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != AUTH_EVENTS || id != AUTH_EVENT_TAG_PROCESSED || data == NULL) {
return;
}
const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data;
if (!ev->authorized) {
ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag);
return;
}
// Construir payload: [ CMD_AUTH_GRANTED, tag..., '\0' ]
uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN];
buf[0] = CMD_AUTH_GRANTED;
// Copiar tag e garantir NUL
strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1);
((char *)&buf[1])[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
uint8_t payload_len = 1 + (uint8_t)(strlen((char *)&buf[1]) + 1); // opcode + tag + '\0'
// Neste exemplo: broadcast para todos os slaves (0xFF)
uint8_t dest = 0xFF;
if (!evse_link_send(dest, buf, payload_len)) {
ESP_LOGW(TAG, "Failed to send CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
dest, (char *)&buf[1]);
} else {
ESP_LOGI(TAG, "Sent CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
dest, (char *)&buf[1]);
}
}
// --- Polling broadcast callback --- // --- Polling broadcast callback ---
static void poll_timer_cb(TimerHandle_t xTimer) { static void poll_timer_cb(TimerHandle_t xTimer)
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");; {
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");
;
// Optionally post event LINK_EVENT_MASTER_POLL_SENT // Optionally post event LINK_EVENT_MASTER_POLL_SENT
} }
// --- Heartbeat timeout callback --- // --- Heartbeat timeout callback ---
static void hb_timer_cb(TimerHandle_t xTimer) { static void hb_timer_cb(TimerHandle_t xTimer)
{
ESP_LOGW(TAG, "Heartbeat timeout: possible slave offline"); ESP_LOGW(TAG, "Heartbeat timeout: possible slave offline");
// post event LINK_EVENT_SLAVE_OFFLINE ??? // post event LINK_EVENT_SLAVE_OFFLINE ???
} }
static void on_frame_master(uint8_t src, uint8_t dest, static void on_frame_master(uint8_t src, uint8_t dest,
const uint8_t *payload, uint8_t len) { const uint8_t *payload, uint8_t len)
if (len < 1) return; {
if (len < 1)
return;
uint8_t cmd = payload[0]; uint8_t cmd = payload[0];
switch (cmd) { switch (cmd)
case CMD_HEARTBEAT: { {
if (len != 6) { // CMD + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi case CMD_HEARTBEAT:
{
if (len != LEN_HEARTBEAT)
{ // CMD + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi
ESP_LOGW(TAG, "HEARTBEAT len invalid from 0x%02X: %u bytes", src, len); ESP_LOGW(TAG, "HEARTBEAT len invalid from 0x%02X: %u bytes", src, len);
return; return;
} }
@@ -87,8 +135,7 @@ static void on_frame_master(uint8_t src, uint8_t dest,
.charging = charging, .charging = charging,
.hw_max_current = (float)hw_max, .hw_max_current = (float)hw_max,
.runtime_current = (float)runtime, // corrente real medida no slave .runtime_current = (float)runtime, // corrente real medida no slave
.timestamp_us = esp_timer_get_time() .timestamp_us = esp_timer_get_time()};
};
esp_event_post(LOADBALANCER_EVENTS, esp_event_post(LOADBALANCER_EVENTS,
LOADBALANCER_EVENT_SLAVE_STATUS, LOADBALANCER_EVENT_SLAVE_STATUS,
@@ -115,10 +162,11 @@ static void on_frame_master(uint8_t src, uint8_t dest,
} }
} }
// --- Master initialization --- // --- Master initialization ---
void evse_link_master_init(void) { void evse_link_master_init(void)
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled()) { {
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled())
{
return; return;
} }
ESP_LOGI(TAG, "Initializing MASTER (ID=0x%02X)", evse_link_get_self_id()); ESP_LOGI(TAG, "Initializing MASTER (ID=0x%02X)", evse_link_get_self_id());
@@ -132,9 +180,15 @@ void evse_link_master_init(void) {
LOADBALANCER_EVENTS, LOADBALANCER_EVENTS,
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
on_new_limit, on_new_limit,
NULL NULL));
)
); // escutar resultado do AUTH para propagar autorização aos slaves
ESP_ERROR_CHECK(
esp_event_handler_register(
AUTH_EVENTS,
AUTH_EVENT_TAG_PROCESSED,
on_auth_result,
NULL));
// create and start poll timer // create and start poll timer
poll_timer.timer = xTimerCreate("poll_tmr", poll_timer.timer = xTimerCreate("poll_tmr",
@@ -150,3 +204,4 @@ void evse_link_master_init(void) {
hb_timer_cb); hb_timer_cb);
xTimerStart(hb_timer.timer, 0); xTimerStart(hb_timer.timer, 0);
} }

View File

@@ -23,6 +23,7 @@ static const char *TAG = "evse_link_slave";
#define CMD_CONFIG_BROADCAST 0x03 #define CMD_CONFIG_BROADCAST 0x03
#define CMD_SET_CURRENT 0x08 #define CMD_SET_CURRENT 0x08
#define CMD_HEARTBEAT_ACK 0x09 #define CMD_HEARTBEAT_ACK 0x09
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização
// payload lengths (exclui seq byte) // payload lengths (exclui seq byte)
#define LEN_POLL_REQ 1 // [ CMD_POLL ] #define LEN_POLL_REQ 1 // [ CMD_POLL ]
@@ -82,6 +83,11 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
break; break;
case CMD_SET_CURRENT: { case CMD_SET_CURRENT: {
if (len < LEN_SET_CURRENT) {
ESP_LOGW(TAG, "SET_CURRENT from 0x%02X with invalid length %u", src, len);
break;
}
uint16_t amps = payload[1] | (payload[2] << 8); uint16_t amps = payload[1] | (payload[2] << 8);
evse_set_runtime_charging_current(amps); evse_set_runtime_charging_current(amps);
ESP_LOGI(TAG, "Applied runtime limit: %uA from master 0x%02X", amps, src); ESP_LOGI(TAG, "Applied runtime limit: %uA from master 0x%02X", amps, src);
@@ -103,6 +109,33 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
} }
break; break;
case CMD_AUTH_GRANTED: {
if (len < 2) {
ESP_LOGW(TAG, "CMD_AUTH_GRANTED from 0x%02X with invalid length %u", src, len);
break;
}
const char *tag = (const char *)&payload[1];
evse_link_auth_grant_event_t ev = {0};
strncpy(ev.tag, tag, EVSE_LINK_TAG_MAX_LEN - 1);
ev.tag[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
ESP_LOGI(TAG, "Received CMD_AUTH_GRANTED from master 0x%02X, tag='%s'", src, ev.tag);
esp_err_t err = esp_event_post(
EVSE_LINK_EVENTS,
LINK_EVENT_REMOTE_AUTH_GRANTED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to post LINK_EVENT_REMOTE_AUTH_GRANTED: %s", esp_err_to_name(err));
}
break;
}
default: default:
ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src); ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src);
} }
@@ -162,3 +195,5 @@ void evse_link_slave_init(void) {
) )
); );
} }
// === Fim de: components/evse_link/src/evse_link_slave.c ===

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "button/button.c" "button/button_obj.cpp"
INCLUDE_DIRS "button/include"
REQUIRES "driver")

View File

@@ -1,6 +0,0 @@
menu "GPIO Button"
config IO_GLITCH_FILTER_TIME_MS
int "IO glitch filter timer ms (10~100)"
range 10 100
default 50
endmenu

View File

@@ -1,353 +0,0 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/timers.h>
#include <esp_log.h>
#include <driver/gpio.h>
#include <iot_button.h>
#define IOT_CHECK(tag, a, ret) if(!(a)) { \
ESP_LOGE(tag,"%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \
return (ret); \
}
#define ERR_ASSERT(tag, param) IOT_CHECK(tag, (param) == ESP_OK, ESP_FAIL)
#define POINT_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) != NULL, (ret))
typedef enum {
BUTTON_STATE_IDLE = 0,
BUTTON_STATE_PUSH,
BUTTON_STATE_PRESSED,
} button_status_t;
typedef struct button_dev button_dev_t;
typedef struct btn_cb button_cb_t;
struct btn_cb{
TickType_t interval;
button_cb cb;
void* arg;
uint8_t on_press;
TimerHandle_t tmr;
button_dev_t *pbtn;
button_cb_t *next_cb;
};
struct button_dev{
uint8_t io_num;
uint8_t active_level;
uint32_t serial_thres_sec;
uint8_t taskq_on;
QueueHandle_t taskq;
QueueHandle_t argq;
button_status_t state;
button_cb_t tap_short_cb;
button_cb_t tap_psh_cb;
button_cb_t tap_rls_cb;
button_cb_t press_serial_cb;
button_cb_t* cb_head;
};
#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_IO_GLITCH_FILTER_TIME_MS
static const char* TAG = "button";
static void button_press_cb(TimerHandle_t tmr)
{
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
button_dev_t* btn = btn_cb->pbtn;
// low, then restart
if (btn->active_level == gpio_get_level(btn->io_num)) {
btn->state = BUTTON_STATE_PRESSED;
if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && !btn_cb->on_press) {
void *tmp = btn_cb->cb;
xQueueOverwrite(btn->taskq, &tmp);
xQueueOverwrite(btn->argq, &btn_cb->arg);
} else if (btn_cb->cb) {
btn_cb->cb(btn_cb->arg);
}
}
}
static void button_tap_psh_cb(TimerHandle_t tmr)
{
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
button_dev_t* btn = btn_cb->pbtn;
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
int lv = gpio_get_level(btn->io_num);
if (btn->active_level == lv) {
// True implies key is pressed
btn->state = BUTTON_STATE_PUSH;
if (btn->press_serial_cb.tmr) {
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
}
if (btn->tap_psh_cb.cb) {
btn->tap_psh_cb.cb(btn->tap_psh_cb.arg);
}
} else {
// 50ms, check if this is a real key up
if (btn->tap_rls_cb.tmr) {
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
xTimerReset(btn->tap_rls_cb.tmr, portMAX_DELAY);
}
}
}
static void button_tap_rls_cb(TimerHandle_t tmr)
{
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
button_dev_t* btn = btn_cb->pbtn;
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
if (btn->active_level == gpio_get_level(btn->io_num)) {
} else {
// high, then key is up
button_cb_t *pcb = btn->cb_head;
while (pcb != NULL) {
if (pcb->tmr != NULL) {
xTimerStop(pcb->tmr, portMAX_DELAY);
}
pcb = pcb->next_cb;
}
if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && uxQueueMessagesWaiting(btn->taskq) != 0 && btn->state != BUTTON_STATE_IDLE) {
void (*task)(void*);
void *arg;
xQueueReceive(btn->taskq, &task, 0);
xQueueReceive(btn->argq, &arg, 0);
task(arg);
}
if (btn->press_serial_cb.tmr && btn->press_serial_cb.tmr != NULL) {
xTimerStop(btn->press_serial_cb.tmr, portMAX_DELAY);
}
if (btn->tap_short_cb.cb && btn->state == BUTTON_STATE_PUSH) {
btn->tap_short_cb.cb(btn->tap_short_cb.arg);
}
if(btn->tap_rls_cb.cb && btn->state != BUTTON_STATE_IDLE) {
btn->tap_rls_cb.cb(btn->tap_rls_cb.arg);
}
btn->state = BUTTON_STATE_IDLE;
}
}
static void button_press_serial_cb(TimerHandle_t tmr)
{
button_dev_t* btn = (button_dev_t*) pvTimerGetTimerID(tmr);
if (btn->press_serial_cb.cb) {
btn->press_serial_cb.cb(btn->press_serial_cb.arg);
}
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->press_serial_cb.interval, portMAX_DELAY);
xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
}
static void button_gpio_isr_handler(void* arg)
{
button_dev_t* btn = (button_dev_t*) arg;
portBASE_TYPE HPTaskAwoken = pdFALSE;
int level = gpio_get_level(btn->io_num);
if (level == btn->active_level) {
if (btn->tap_psh_cb.tmr) {
xTimerStopFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
xTimerResetFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
}
button_cb_t *pcb = btn->cb_head;
while (pcb != NULL) {
if (pcb->tmr != NULL) {
xTimerStopFromISR(pcb->tmr, &HPTaskAwoken);
xTimerResetFromISR(pcb->tmr, &HPTaskAwoken);
}
pcb = pcb->next_cb;
}
} else {
// 50ms, check if this is a real key up
if (btn->tap_rls_cb.tmr) {
xTimerStopFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
xTimerResetFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
}
}
if(HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static void button_free_tmr(TimerHandle_t* tmr)
{
if (tmr && *tmr) {
xTimerStop(*tmr, portMAX_DELAY);
xTimerDelete(*tmr, portMAX_DELAY);
*tmr = NULL;
}
}
esp_err_t iot_button_delete(button_handle_t btn_handle)
{
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
button_dev_t* btn = (button_dev_t*) btn_handle;
gpio_set_intr_type(btn->io_num, GPIO_INTR_DISABLE);
gpio_isr_handler_remove(btn->io_num);
button_free_tmr(&btn->tap_rls_cb.tmr);
button_free_tmr(&btn->tap_psh_cb.tmr);
button_free_tmr(&btn->tap_short_cb.tmr);
button_free_tmr(&btn->press_serial_cb.tmr);
button_cb_t *pcb = btn->cb_head;
while (pcb != NULL) {
button_cb_t *cb_next = pcb->next_cb;
button_free_tmr(&pcb->tmr);
free(pcb);
pcb = cb_next;
}
free(btn);
return ESP_OK;
}
button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level)
{
IOT_CHECK(TAG, gpio_num < GPIO_NUM_MAX, NULL);
button_dev_t* btn = (button_dev_t*) calloc(1, sizeof(button_dev_t));
POINT_ASSERT(TAG, btn, NULL);
btn->active_level = active_level;
btn->io_num = gpio_num;
btn->state = BUTTON_STATE_IDLE;
btn->taskq_on = 0;
btn->taskq = xQueueCreate(1, sizeof(void*));
btn->argq = xQueueCreate(1, sizeof(void *));
btn->tap_rls_cb.arg = NULL;
btn->tap_rls_cb.cb = NULL;
btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
btn->tap_rls_cb.pbtn = btn;
btn->tap_rls_cb.tmr = xTimerCreate("btn_rls_tmr", btn->tap_rls_cb.interval, pdFALSE,
&btn->tap_rls_cb, button_tap_rls_cb);
btn->tap_psh_cb.arg = NULL;
btn->tap_psh_cb.cb = NULL;
btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
btn->tap_psh_cb.pbtn = btn;
btn->tap_psh_cb.tmr = xTimerCreate("btn_psh_tmr", btn->tap_psh_cb.interval, pdFALSE,
&btn->tap_psh_cb, button_tap_psh_cb);
gpio_install_isr_service(0);
gpio_config_t gpio_conf;
gpio_conf.intr_type = GPIO_INTR_ANYEDGE;
gpio_conf.mode = GPIO_MODE_INPUT;
gpio_conf.pin_bit_mask = (uint64_t)1 << gpio_num;
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_conf);
gpio_isr_handler_add(gpio_num, button_gpio_isr_handler, btn);
return (button_handle_t) btn;
}
esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type)
{
button_dev_t* btn = (button_dev_t*) btn_handle;
button_cb_t* btn_cb = NULL;
if (type == BUTTON_CB_PUSH) {
btn_cb = &btn->tap_psh_cb;
} else if (type == BUTTON_CB_RELEASE) {
btn_cb = &btn->tap_rls_cb;
} else if (type == BUTTON_CB_TAP) {
btn_cb = &btn->tap_short_cb;
} else if (type == BUTTON_CB_SERIAL) {
btn_cb = &btn->press_serial_cb;
}
btn_cb->cb = NULL;
btn_cb->arg = NULL;
btn_cb->pbtn = btn;
button_free_tmr(&btn_cb->tmr);
return ESP_OK;
}
esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg)
{
button_dev_t* btn = (button_dev_t*) btn_handle;
btn->serial_thres_sec = start_after_sec;
if (btn->press_serial_cb.tmr == NULL) {
btn->press_serial_cb.tmr = xTimerCreate("btn_serial_tmr", btn->serial_thres_sec*1000 / portTICK_PERIOD_MS,
pdFALSE, btn, button_press_serial_cb);
}
btn->press_serial_cb.arg = arg;
btn->press_serial_cb.cb = cb;
btn->press_serial_cb.interval = interval_tick;
btn->press_serial_cb.pbtn = btn;
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
return ESP_OK;
}
esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg)
{
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
button_dev_t* btn = (button_dev_t*) btn_handle;
if (type == BUTTON_CB_PUSH) {
btn->tap_psh_cb.arg = arg;
btn->tap_psh_cb.cb = cb;
btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
btn->tap_psh_cb.pbtn = btn;
xTimerChangePeriod(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
} else if (type == BUTTON_CB_RELEASE) {
btn->tap_rls_cb.arg = arg;
btn->tap_rls_cb.cb = cb;
btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
btn->tap_rls_cb.pbtn = btn;
xTimerChangePeriod(btn->tap_rls_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
} else if (type == BUTTON_CB_TAP) {
btn->tap_short_cb.arg = arg;
btn->tap_short_cb.cb = cb;
btn->tap_short_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
btn->tap_short_cb.pbtn = btn;
} else if (type == BUTTON_CB_SERIAL) {
iot_button_set_serial_cb(btn_handle, 1, 1000 / portTICK_PERIOD_MS, cb, arg);
}
return ESP_OK;
}
esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
{
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
button_dev_t* btn = (button_dev_t*) btn_handle;
button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
POINT_ASSERT(TAG, cb_new, ESP_FAIL);
cb_new->on_press = 1;
cb_new->arg = arg;
cb_new->cb = cb;
cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
cb_new->pbtn = btn;
cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
cb_new->next_cb = btn->cb_head;
btn->cb_head = cb_new;
return ESP_OK;
}
esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
{
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
button_dev_t* btn = (button_dev_t*) btn_handle;
button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
POINT_ASSERT(TAG, cb_new, ESP_FAIL);
btn->taskq_on = 1;
cb_new->arg = arg;
cb_new->cb = cb;
cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
cb_new->pbtn = btn;
cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
cb_new->next_cb = btn->cb_head;
btn->cb_head = cb_new;
return ESP_OK;
}

View File

@@ -1,54 +0,0 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <iot_button.h>
CButton::CButton(gpio_num_t gpio_num, button_active_t active_level)
{
m_btn_handle = iot_button_create(gpio_num, active_level);
}
CButton::~CButton()
{
iot_button_delete(m_btn_handle);
m_btn_handle = NULL;
}
esp_err_t CButton::set_evt_cb(button_cb_type_t type, button_cb cb, void* arg)
{
return iot_button_set_evt_cb(m_btn_handle, type, cb, arg);
}
esp_err_t CButton::set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec)
{
return iot_button_set_serial_cb(m_btn_handle, start_after_sec, interval_tick, cb, arg);
}
esp_err_t CButton::add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg)
{
return iot_button_add_on_press_cb(m_btn_handle, press_sec, cb, arg);
}
esp_err_t CButton::add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg)
{
return iot_button_add_on_release_cb(m_btn_handle, press_sec, cb, arg);
}
esp_err_t CButton::rm_cb(button_cb_type_t type)
{
return iot_button_rm_cb(m_btn_handle, type);
}

View File

@@ -1,272 +0,0 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_H_
#define _IOT_BUTTON_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
typedef void (* button_cb)(void*);
typedef void* button_handle_t;
typedef enum {
BUTTON_ACTIVE_HIGH = 1, /*!<button active level: high level*/
BUTTON_ACTIVE_LOW = 0, /*!<button active level: low level*/
} button_active_t;
typedef enum {
BUTTON_CB_PUSH = 0, /*!<button push callback event */
BUTTON_CB_RELEASE, /*!<button release callback event */
BUTTON_CB_TAP, /*!<button quick tap callback event(will not trigger if there already is a "PRESS" event) */
BUTTON_CB_SERIAL, /*!<button serial trigger callback event */
} button_cb_type_t;
/**
* @brief Init button functions
*
* @param gpio_num GPIO index of the pin that the button uses
* @param active_level button hardware active level.
* For "BUTTON_ACTIVE_LOW" it means when the button pressed, the GPIO will read low level.
*
* @return A button_handle_t handle to the created button object, or NULL in case of error.
*/
button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level);
/**
* @brief Register a callback function for a serial trigger event.
*
* @param btn_handle handle of the button object
* @param start_after_sec define the time after which to start serial trigger action
* @param interval_tick serial trigger interval
* @param cb callback function for "TAP" action.
* @param arg Parameter for callback function
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg);
/**
* @brief Register a callback function for a button_cb_type_t action.
*
* @param btn_handle handle of the button object
* @param type callback function type
* @param cb callback function for "TAP" action.
* @param arg Parameter for callback function
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg);
/**
* @brief Callbacks invoked as timer events occur while button is pressed.
* Example: If a button is configured for 2 sec, 5 sec and 7 sec callbacks and if the button is pressed for 6 sec then 2 sec callback would be invoked at 2 sec event and 5 sec callback would be invoked at 5 sec event
*
* @param btn_handle handle of the button object
* @param press_sec the callback function would be called if you press the button for a specified period of time
* @param cb callback function for "PRESS and HOLD" action.
* @param arg Parameter for callback function
*
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg);
/**
* @brief Single callback invoked according to the latest timer event on button release.
* Example: If a button is configured for 2 sec, 5 sec and 7 sec callbacks and if the button is released at 6 sec then only 5 sec callback would be invoked
*
* @param btn_handle handle of the button object
* @param press_sec the callback function would be called if you press the button for a specified period of time
* @param cb callback function for "PRESS and RELEASE" action.
* @param arg Parameter for callback function
*
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg);
/**
* @brief Delete button object and free memory
* @param btn_handle handle of the button object
*
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t iot_button_delete(button_handle_t btn_handle);
/**
* @brief Remove callback
*
* @param btn_handle The handle of the button object
* @param type callback function event type
*
* @return
* - ESP_OK Success
*/
esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
/**
* class of button
* simple usage:
* CButton* btn = new CButton(BUTTON_IO_NUM, BUTTON_ACTIVE_LEVEL, BUTTON_SERIAL_TRIGGER, 3);
* btn->add_cb(BUTTON_CB_PUSH, button_tap_cb, (void*) push, 50 / portTICK_PERIOD_MS);
* btn->add_custom_cb(5, button_press_5s_cb, NULL);
* ......
* delete btn;
*/
class CButton
{
private:
button_handle_t m_btn_handle;
/**
* prevent copy constructing
*/
CButton(const CButton&);
CButton& operator = (const CButton&);
public:
/**
* @brief constructor of CButton
*
* @param gpio_num GPIO index of the pin that the button uses
* @param active_level button hardware active level.
* For "BUTTON_ACTIVE_LOW" it means when the button pressed, the GPIO will read low level.
*/
CButton(gpio_num_t gpio_num, button_active_t active_level = BUTTON_ACTIVE_LOW);
~CButton();
/**
* @brief Register a callback function for a button_cb_type_t action.
*
* @param type callback function type
* @param cb callback function for "TAP" action.
* @param arg Parameter for callback function
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t set_evt_cb(button_cb_type_t type, button_cb cb, void* arg);
/**
* @brief Register a callback function for a serial trigger event.
*
* @param btn_handle handle of the button object
* @param start_after_sec define the time after which to start serial trigger action
* @param interval_tick serial trigger interval
* @param cb callback function for "TAP" action.
* @param arg Parameter for callback function
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec);
/**
* @brief Callbacks invoked as timer events occur while button is pressed
*
* @param press_sec the callback function would be called if you press the button for a specified period of time
* @param cb callback function for "PRESS and HOLD" action.
* @param arg Parameter for callback function
*
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg);
/**
* @brief Single callback invoked according to the latest timer event on button release.
*
* @param press_sec the callback function would be called if you press the button for a specified period of time
* @param cb callback function for "PRESS and RELEASE" action.
* @param arg Parameter for callback function
*
* @note
* Button callback functions execute in the context of the timer service task.
* It is therefore essential that button callback functions never attempt to block.
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
* or specify a non zero block time when accessing a queue or a semaphore.
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
*/
esp_err_t add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg);
/**
* @brief Remove callback
*
* @param type callback function event type
*
* @return
* - ESP_OK Success
*/
esp_err_t rm_cb(button_cb_type_t type);
};
#endif
#endif

View File

@@ -1,2 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS := ./button/include
COMPONENT_SRCDIRS := ./button

6
components/led/CMakeLists.txt Executable file
View File

@@ -0,0 +1,6 @@
set(srcs "src/led.c" "src/ledc_driver.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver esp_timer
REQUIRES config evse)

78
components/led/include/led.h Executable file
View File

@@ -0,0 +1,78 @@
#ifndef LED_H_
#define LED_H_
#include <stdint.h>
#include <stdbool.h>
/**
* @brief Identificadores dos LEDs disponíveis no hardware (por cor)
*/
typedef enum
{
LED_ID_GREEN = 0,
LED_ID_BLUE,
LED_ID_RED,
LED_ID_MAX
} led_id_t;
/**
* @brief Padrões de comportamento possíveis para os LEDs.
*
* Estes padrões são usados tanto pela lógica interna (estado EVSE, efeitos
* de sessão, etc.) como por código externo que queira forçar um determinado
* comportamento num LED.
*
* Nota:
* - LED_PATTERN_BREATHING é implementado por uma task interna (HSV).
* - LED_PATTERN_CHARGING_EFFECT é legado/experimental e pode não ser usado
* diretamente na implementação atual.
*/
typedef enum
{
LED_PATTERN_OFF, ///< LED sempre desligado
LED_PATTERN_ON, ///< LED sempre ligado
LED_PATTERN_BLINK, ///< Pisca com ciclo padrão (500ms on / 500ms off)
LED_PATTERN_BLINK_FAST, ///< Pisca rápido (250ms on / 250ms off)
LED_PATTERN_BLINK_SLOW, ///< Pisca lento (500ms on / 1500ms off)
LED_PATTERN_CHARGING_EFFECT, ///< Efeito de carregamento (2s on / 1s off) - legado/opcional
LED_PATTERN_BREATHING ///< Efeito "breathing" (brilho suave via HSV)
} led_pattern_t;
/**
* @brief Inicializa o subsistema de LEDs com base na configuração da placa.
*
* - Configura o driver LEDC com os GPIOs definidos em board_config.
* - Cria a task de efeitos (para padrões como BREATHING).
* - Regista handlers dos eventos EVSE (estado e sessão) para aplicar
* padrões automáticos em função do estado do carregador.
*
* Deve ser chamada uma única vez na inicialização do sistema.
*/
void led_init(void);
/**
* @brief Define diretamente o tempo ligado/desligado de um LED.
*
* Esta função permite criar padrões personalizados à parte da tabela
* led_pattern_t. Normalmente, código de alto nível deve preferir
* led_apply_pattern(), deixando a lógica de tempos a cargo do módulo.
*
* @param led_id Identificador do LED (ver led_id_t)
* @param ontime Tempo ligado em milissegundos (0 = sempre off)
* @param offtime Tempo desligado em milissegundos (0 = sempre on)
*/
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime);
/**
* @brief Aplica um padrão de piscar/efeito definido ao LED.
*
* Esta é a API recomendada para controlar LEDs externamente. A implementação
* interna do módulo também a usa para reagir a eventos EVSE (estado de
* carregamento, início/fim de sessão, etc.).
*
* @param led_id Identificador do LED (ver led_id_t)
* @param pattern Padrão desejado (ver led_pattern_t)
*/
void led_apply_pattern(led_id_t led_id, led_pattern_t pattern);
#endif /* LED_H_ */

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "driver/gpio.h"
/**
* @brief Inicializa o controlador LEDC para os 3 LEDs (R,G,B)
*
* @param gpio_red GPIO ligado ao LED vermelho (via ULN2003)
* @param gpio_green GPIO ligado ao LED verde (via ULN2003)
* @param gpio_blue GPIO ligado ao LED azul (via ULN2003)
*
* @return ESP_OK em sucesso, erro caso contrário
*/
esp_err_t ledc_init(gpio_num_t gpio_red,
gpio_num_t gpio_green,
gpio_num_t gpio_blue);
/**
* @brief Define a intensidade RGB (0255 por cor)
*
* @param red Intensidade do vermelho (0255)
* @param green Intensidade do verde (0255)
* @param blue Intensidade do azul (0255)
*
* @return ESP_OK em sucesso, erro caso contrário
*/
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Desliga todos os LEDs (R,G,B)
*/
esp_err_t ledc_clear(void);

598
components/led/src/led.c Executable file
View File

@@ -0,0 +1,598 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_event.h"
#include <inttypes.h> // para PRIu32
#include "led.h"
#include "board_config.h"
#include "evse_events.h"
#include "evse_state.h"
#include "ledc_driver.h"
#define BLOCK_TIME portMAX_DELAY
static const char *TAG = "led";
typedef struct
{
bool present; ///< LED existe nesta placa
bool on : 1; ///< estado lógico atual (ligado/desligado)
uint16_t ontime; ///< ms ligado
uint16_t offtime; ///< ms desligado
TimerHandle_t timer; ///< timer de piscar (para padrões que usam on/off)
led_pattern_t pattern; ///< padrão atual
uint8_t blink_count; ///< reservado para padrões mais complexos
} led_t;
static led_t leds[LED_ID_MAX] = {0};
// ----------------------------
// Tabela de padrões (tempo on/off)
// ----------------------------
typedef struct
{
uint16_t on_ms;
uint16_t off_ms;
} led_timing_t;
// índice = led_pattern_t
static const led_timing_t led_pattern_table[] = {
[LED_PATTERN_OFF] = {0, 0},
[LED_PATTERN_ON] = {1, 0}, // 1ms só para cair na lógica "sempre ligado"
[LED_PATTERN_BLINK] = {500, 500},
[LED_PATTERN_BLINK_FAST] = {250, 250},
[LED_PATTERN_BLINK_SLOW] = {500, 1500},
[LED_PATTERN_CHARGING_EFFECT] = {2000, 1000},
// Para BREATHING, o temporizador FreeRTOS NÃO é usado; tratamos via task de efeitos.
// Mesmo assim, usamos (1,0) para marcar LED como "on" logicamente.
[LED_PATTERN_BREATHING] = {1, 0},
};
#define LED_PATTERN_COUNT (sizeof(led_pattern_table) / sizeof(led_pattern_table[0]))
// ----------------------------
// Estado base + efeitos de sessão
// ----------------------------
typedef enum
{
SESSION_EFFECT_NONE = 0,
SESSION_EFFECT_START,
SESSION_EFFECT_FINISH,
} led_session_effect_type_t;
static evse_state_event_t current_state_mode = EVSE_STATE_EVENT_IDLE;
static bool session_effect_active = false;
static TimerHandle_t session_effect_timer = NULL;
static led_session_effect_type_t session_effect_type = SESSION_EFFECT_NONE;
static uint8_t session_effect_phase = 0;
// Forwards
static void led_update_rgb_from_state(void);
static void led_effect_task(void *arg);
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void session_effect_timer_cb(TimerHandle_t xTimer);
static void led_apply_state_mode(evse_state_event_t state);
// ----------------------------
// Timer de piscar (para padrões on/off)
// ----------------------------
static void led_timer_callback(TimerHandle_t xTimer)
{
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
led->on = !led->on;
uint32_t next_time = led->on ? led->ontime : led->offtime;
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
// Atualiza hardware (via LEDC).
led_update_rgb_from_state();
}
// ----------------------------
// Atualiza hardware a partir do estado lógico dos LEDs
// (para padrões "normais": ON/OFF/BLINK/...)
// ----------------------------
static void led_update_rgb_from_state(void)
{
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
if (LED_ID_RED < LED_ID_MAX && leds[LED_ID_RED].present && leds[LED_ID_RED].on)
red = 255;
if (LED_ID_GREEN < LED_ID_MAX && leds[LED_ID_GREEN].present && leds[LED_ID_GREEN].on)
green = 255;
if (LED_ID_BLUE < LED_ID_MAX && leds[LED_ID_BLUE].present && leds[LED_ID_BLUE].on)
blue = 255;
ledc_set_rgb(red, green, blue);
}
// ----------------------------
// Task de efeitos (BREATHING)
// ----------------------------
static void led_effect_task(void *arg)
{
// v = "intensidade" lógica 0100
uint32_t v = 0;
bool up = true;
const uint32_t v_min = 0;
const uint32_t v_max = 100;
const uint32_t step = 3;
const TickType_t delay_breathe = pdMS_TO_TICKS(50); // velocidade da respiração
const TickType_t delay_idle = pdMS_TO_TICKS(1000); // quando não há LED em BREATHING
for (;;)
{
// Verifica se algum LED está em BREATHING
bool has_breath = false;
led_id_t breath_id = LED_ID_MAX;
for (int i = 0; i < LED_ID_MAX; ++i)
{
if (leds[i].present && leds[i].pattern == LED_PATTERN_BREATHING)
{
has_breath = true;
breath_id = (led_id_t)i;
break;
}
}
if (has_breath)
{
// Usa o valor atual de v para calcular o brilho (0255)
uint32_t brightness = (v * 255U) / 100U;
uint32_t r = 0, g = 0, b = 0;
switch (breath_id)
{
case LED_ID_RED:
r = brightness;
break;
case LED_ID_GREEN:
g = brightness;
break;
case LED_ID_BLUE:
b = brightness;
break;
default:
// fallback: usa azul se algo estranho acontecer
b = brightness;
break;
}
// Aplica só um canal, sem misturar cores
ledc_set_rgb(r, g, b);
// Se estiver completamente apagado (v == 0),
// queremos que fique 500 ms off antes de voltar a subir
TickType_t delay = (v == v_min) ? pdMS_TO_TICKS(500) : delay_breathe;
// Atualiza v para a próxima iteração (triângulo 0→100→0)
if (up)
{
if (v + step >= v_max)
{
v = v_max;
up = false;
}
else
{
v += step;
}
}
else
{
if (v <= v_min + step)
{
v = v_min;
up = true;
}
else
{
v -= step;
}
}
vTaskDelay(delay);
}
else
{
// Ninguém em BREATHING: deixa padrões normais controlarem o LED
led_update_rgb_from_state();
vTaskDelay(delay_idle);
// Opcional: quando sair de BREATHING e voltar mais tarde,
// começa de novo a partir de apagado
v = v_min;
up = true;
}
}
}
// ----------------------------
// Aplica padrão em função do estado EVSE (base)
// GARANTINDO apenas 1 LED por estado
// ----------------------------
static void led_apply_state_mode(evse_state_event_t state)
{
// Desliga padrões anteriores para todos os canais
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
switch (state)
{
case EVSE_STATE_EVENT_IDLE:
// IDLE → verde fixo (claro e visível)
led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON);
break;
case EVSE_STATE_EVENT_WAITING:
// WAITING → azul a piscar lento
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_SLOW);
break;
case EVSE_STATE_EVENT_CHARGING:
// CHARGING → azul "breathing"
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BREATHING);
break;
case EVSE_STATE_EVENT_FAULT:
// FAULT → vermelho a piscar rápido
led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST);
break;
default:
break;
}
led_update_rgb_from_state();
}
// ----------------------------
// Timer callback do efeito de sessão
// Efeitos usam sempre UM LED forte
// ----------------------------
static void session_effect_timer_cb(TimerHandle_t xTimer)
{
switch (session_effect_type)
{
case SESSION_EFFECT_START:
session_effect_phase++;
switch (session_effect_phase)
{
case 1:
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
led_update_rgb_from_state();
// Mantém piscar rápido mais um bocado
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(5000),
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case 2:
default:
// Fim do efeito de START → volta ao estado base (tipicamente CHARGING)
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
break;
case SESSION_EFFECT_FINISH:
session_effect_phase++;
switch (session_effect_phase)
{
case 1:
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
led_update_rgb_from_state();
// Mantém piscar rápido mais tempo (destaque de fim de sessão)
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(5000),
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case 2:
default:
// Fim do efeito de FINISH → volta ao estado base (IDLE/WAITING)
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
break;
case SESSION_EFFECT_NONE:
default:
session_effect_active = false;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
}
// ----------------------------
// Event Handler: EVSE State
// ----------------------------
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
{
return;
}
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
ESP_LOGD(TAG, "EVSE State Changed: state=%d", evt->state);
// Atualiza o estado base
current_state_mode = evt->state;
// Se estiver a decorrer um efeito de sessão, não mexe agora nos LEDs.
if (session_effect_active)
{
return;
}
led_apply_state_mode(current_state_mode);
}
// ----------------------------
// Event Handler: EVSE Session
// (efeitos de início/fim de sessão, 1 LED de cada vez)
// ----------------------------
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != EVSE_EVENTS || id != EVSE_EVENT_SESSION || data == NULL)
{
return;
}
const evse_session_event_data_t *evt =
(const evse_session_event_data_t *)data;
ESP_LOGD(TAG,
"EVSE Session Event: type=%d, id=%" PRIu32
", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
(int)evt->type,
evt->session_id,
evt->duration_s,
evt->energy_wh,
evt->avg_power_w,
evt->is_current);
// Marca que um efeito de sessão está ativo
session_effect_active = true;
session_effect_phase = 0;
if (session_effect_timer)
{
xTimerStop(session_effect_timer, BLOCK_TIME);
}
// Apaga tudo antes de iniciar o efeito
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
switch (evt->type)
{
case EVSE_SESSION_EVENT_STARTED:
// Efeito de início:
// Fase 0: azul sólido curto
session_effect_type = SESSION_EFFECT_START;
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
led_update_rgb_from_state();
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(300), // 0.3 s flash
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case EVSE_SESSION_EVENT_FINISHED:
// Efeito de fim:
// Fase 0: azul sólido curto
session_effect_type = SESSION_EFFECT_FINISH;
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
led_update_rgb_from_state();
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(300), // 0.3 s flash
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
default:
// Se for tipo desconhecido, desiste do efeito e volta ao estado base
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
}
// ----------------------------
// Inicialização
// ----------------------------
void led_init(void)
{
// Marca quais LEDs existem de acordo com o board_config
leds[LED_ID_GREEN].present = board_config.led_green;
leds[LED_ID_BLUE].present = board_config.led_blue;
leds[LED_ID_RED].present = board_config.led_red;
// Inicializa LEDC com os GPIOs definidos na board
esp_err_t err = ledc_init(board_config.led_red_gpio,
board_config.led_green_gpio,
board_config.led_blue_gpio);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init LEDC: %s", esp_err_to_name(err));
}
// Regista handler de evento EVSE - STATE
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
evse_led_event_handler,
NULL));
// Regista handler de evento EVSE - SESSION
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS,
EVSE_EVENT_SESSION,
evse_session_led_event_handler,
NULL));
// Cria task de efeitos (breathing)
xTaskCreate(led_effect_task, "led_effect_task", 2048, NULL, 1, NULL);
// Cria timer one-shot para efeitos de sessão
session_effect_timer = xTimerCreate(
"session_eff",
pdMS_TO_TICKS(1000), // valor default; ajustado com xTimerChangePeriod
pdFALSE, // one-shot
NULL,
session_effect_timer_cb);
ESP_LOGI(TAG, "LED system initialized");
// Estado inicial: IDLE
evse_state_event_data_t evt = {
.state = EVSE_STATE_EVENT_IDLE};
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
}
// ----------------------------
// API Pública
// ----------------------------
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
{
if (led_id >= LED_ID_MAX)
{
return;
}
led_t *led = &leds[led_id];
if (!led->present)
{
return;
}
if (led->ontime == ontime && led->offtime == offtime)
{
return;
}
if (led->timer)
{
xTimerStop(led->timer, BLOCK_TIME);
}
led->ontime = ontime;
led->offtime = offtime;
if (ontime == 0)
{
// sempre desligado
led->on = false;
}
else if (offtime == 0)
{
// sempre ligado
led->on = true;
}
else
{
// pisca
led->on = true;
if (!led->timer)
{
// nome só para debug; opcional
led->timer = xTimerCreate("led_timer",
pdMS_TO_TICKS(ontime),
pdFALSE,
(void *)led,
led_timer_callback);
}
if (led->timer)
{
xTimerStart(led->timer, BLOCK_TIME);
}
}
// Atualiza hardware (para estados sem BREATHING)
led_update_rgb_from_state();
}
void led_apply_pattern(led_id_t id, led_pattern_t pattern)
{
if (id >= LED_ID_MAX)
{
return;
}
led_t *led = &leds[id];
if (!led->present)
{
return;
}
if ((unsigned)pattern >= LED_PATTERN_COUNT)
{
ESP_LOGW(TAG, "Invalid LED pattern %d", pattern);
return;
}
if (led->pattern == pattern)
{
return;
}
if (led->timer)
{
xTimerStop(led->timer, BLOCK_TIME);
}
led->pattern = pattern;
led->blink_count = 0;
const led_timing_t *cfg = &led_pattern_table[pattern];
led_set_state(id, cfg->on_ms, cfg->off_ms);
// led_set_state já chama led_update_rgb_from_state()
}

129
components/led/src/ledc_driver.c Executable file
View File

@@ -0,0 +1,129 @@
/*
* LEDC driver para 3 LEDs (R,G,B) controlados via ULN2003
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "ledc_driver.h"
// Pode ser futuramente ligado a uma opção de Kconfig.
// Para o teu hardware (comum a 12 V via ULN2003), visto do ESP é ativo-alto.
#define IS_ACTIVE_HIGH 1
#define LEDC_LS_TIMER LEDC_TIMER_2
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
// Canais usados: 2, 3, 4
#define LEDC_CH_RED LEDC_CHANNEL_2
#define LEDC_CH_GREEN LEDC_CHANNEL_3
#define LEDC_CH_BLUE LEDC_CHANNEL_4
#define LEDC_NUM_CHANNELS (3)
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
#define LEDC_DUTY_MAX (8192 - 1)
#define LEDC_FREQUENCY (5000) // 5 kHz
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
{
.channel = LEDC_CH_RED,
.duty = 0,
.gpio_num = -1, // preenchido em runtime
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
{
.channel = LEDC_CH_GREEN,
.duty = 0,
.gpio_num = -1,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
{
.channel = LEDC_CH_BLUE,
.duty = 0,
.gpio_num = -1,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
};
esp_err_t ledc_init(gpio_num_t gpio_red,
gpio_num_t gpio_green,
gpio_num_t gpio_blue)
{
// Configuração do timer
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY,
.speed_mode = LEDC_LS_MODE,
.timer_num = LEDC_LS_TIMER,
.clk_cfg = LEDC_AUTO_CLK,
};
esp_err_t err = ledc_timer_config(&ledc_timer);
if (err != ESP_OK)
{
return err;
}
// Atribuir GPIOs aos canais
ledc_channel[0].gpio_num = gpio_red;
ledc_channel[1].gpio_num = gpio_green;
ledc_channel[2].gpio_num = gpio_blue;
// Configurar canais
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++)
{
err = ledc_channel_config(&ledc_channel[ch]);
if (err != ESP_OK)
{
return err;
}
}
return ESP_OK;
}
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
{
if (red > 255)
red = 255;
if (green > 255)
green = 255;
if (blue > 255)
blue = 255;
red = red * LEDC_DUTY_MAX / 255;
green = green * LEDC_DUTY_MAX / 255;
blue = blue * LEDC_DUTY_MAX / 255;
if (!IS_ACTIVE_HIGH)
{
red = LEDC_DUTY_MAX - red;
green = LEDC_DUTY_MAX - green;
blue = LEDC_DUTY_MAX - blue;
}
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_RED, red);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_RED);
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_GREEN, green);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_GREEN);
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_BLUE, blue);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_BLUE);
return ESP_OK;
}
esp_err_t ledc_clear(void)
{
return ledc_set_rgb(0, 0, 0);
}

View File

@@ -1,5 +0,0 @@
set(srcs "ledc_driver.c")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES "driver")

View File

@@ -1,2 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_SRCDIRS := .

View File

@@ -1,179 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/ledc.h>
#include <esp_err.h>
#include "ledc_driver.h"
/**
* @brief LEDC driver: Basic LEDC driver
*/
#define IS_ACTIVE_HIGH 0
#define LEDC_LS_TIMER LEDC_TIMER_0
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
#define LEDC_LS_CH0_GPIO (0)
#define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0
#define LEDC_LS_CH1_GPIO (1)
#define LEDC_LS_CH1_CHANNEL LEDC_CHANNEL_1
#define LEDC_LS_CH2_GPIO (8)
#define LEDC_LS_CH2_CHANNEL LEDC_CHANNEL_2
#define LEDC_NUM_CHANNELS (3)
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Set duty resolution to 13 bits
#define LEDC_DUTY_MAX (8192 - 1) // (2 ** 13) - 1
#define LEDC_FREQUENCY (5000) // Frequency in Hertz. Set frequency at 5 kHz
/*
* Prepare individual configuration
* for each channel of LED Controller
* by selecting:
* - controller's channel number
* - output duty cycle, set initially to 0
* - GPIO number where LED is connected to
* - speed mode, either high or low
* - timer servicing selected channel
* Note: if different channels use one timer,
* then frequency and bit_num of these channels
* will be the same
*/
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
{
.channel = LEDC_LS_CH0_CHANNEL,
.duty = 0,
.gpio_num = LEDC_LS_CH0_GPIO,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
{
.channel = LEDC_LS_CH1_CHANNEL,
.duty = 0,
.gpio_num = LEDC_LS_CH1_GPIO,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER, },
{
.channel = LEDC_LS_CH2_CHANNEL,
.duty = 0,
.gpio_num = LEDC_LS_CH2_GPIO,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
};
esp_err_t ledc_init(void)
{
/*
* Prepare and set configuration of timers
* that will be used by LED Controller
*/
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_DUTY_RES, // resolution of PWM duty
.freq_hz = LEDC_FREQUENCY, // frequency of PWM signal
.speed_mode = LEDC_LS_MODE, // timer mode
.timer_num = LEDC_LS_TIMER, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};
// Set configuration of timer0 for high speed channels
ledc_timer_config(&ledc_timer);
// Set LED Controller with previously prepared configuration
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++) {
ledc_channel_config(&ledc_channel[ch]);
}
return ESP_OK;
}
static void ledc_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
{
h %= 360; // h -> [0,360]
uint32_t rgb_max = v * 2.55f;
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
uint32_t i = h / 60;
uint32_t diff = h % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
*r = rgb_max;
*g = rgb_min + rgb_adj;
*b = rgb_min;
break;
case 1:
*r = rgb_max - rgb_adj;
*g = rgb_max;
*b = rgb_min;
break;
case 2:
*r = rgb_min;
*g = rgb_max;
*b = rgb_min + rgb_adj;
break;
case 3:
*r = rgb_min;
*g = rgb_max - rgb_adj;
*b = rgb_max;
break;
case 4:
*r = rgb_min + rgb_adj;
*g = rgb_min;
*b = rgb_max;
break;
default:
*r = rgb_max;
*g = rgb_min;
*b = rgb_max - rgb_adj;
break;
}
}
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
{
red = red * LEDC_DUTY_MAX / 255;
green = green * LEDC_DUTY_MAX / 255;
blue = blue * LEDC_DUTY_MAX / 255;
if (!IS_ACTIVE_HIGH) {
red = LEDC_DUTY_MAX - red;
green = LEDC_DUTY_MAX - green;
blue = LEDC_DUTY_MAX - blue;
}
ledc_set_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel, red);
ledc_update_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel);
ledc_set_duty(ledc_channel[1].speed_mode, ledc_channel[1].channel, green);
ledc_update_duty(ledc_channel[1].speed_mode, ledc_channel[1].channel);
ledc_set_duty(ledc_channel[2].speed_mode, ledc_channel[2].channel, blue);
ledc_update_duty(ledc_channel[2].speed_mode, ledc_channel[2].channel);
return ESP_OK;
}
esp_err_t ledc_set_hsv(uint32_t hue, uint32_t saturation, uint32_t value)
{
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
ledc_hsv2rgb(hue, saturation, value, &red, &green, &blue);
return ledc_set_rgb(red, green, blue);
}
esp_err_t ledc_clear()
{
return ledc_set_rgb(0, 0, 0);
return ESP_OK;
}

View File

@@ -1,49 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
/**
* @brief Initialize the LEDC RGB LED
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t ledc_init(void);
/**
*
* @brief Set RGB value for the LED
*
* @param[in] red Intensity of Red color (0-100)
* @param[in] green Intensity of Green color (0-100)
* @param[in] blue Intensity of Green color (0-100)
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set HSV value for the LED
*
* @param[in] hue Value of hue in arc degrees (0-360)
* @param[in] saturation Saturation in percentage (0-100)
* @param[in] value Value (also called Intensity) in percentage (0-100)
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t ledc_set_hsv(uint32_t hue, uint32_t saturation, uint32_t value);
/**
* @brief Clear (turn off) the LED
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t ledc_clear();

View File

@@ -4,5 +4,4 @@ set(srcs
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash
REQUIRES esp_event esp_timer meter_manager evse) REQUIRES esp_event esp_timer meter_manager evse)

View File

@@ -1,17 +1,26 @@
// components/loadbalancer/include/loadbalancer_events.h
#pragma once #pragma once
#include "esp_event.h" #include "esp_event.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "esp_timer.h" #include "esp_timer.h"
#ifdef __cplusplus
extern "C" {
#endif
ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS); ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
typedef enum { typedef enum {
LOADBALANCER_EVENT_INIT, LOADBALANCER_EVENT_INIT = 0,
LOADBALANCER_EVENT_STATE_CHANGED, LOADBALANCER_EVENT_STATE_CHANGED,
LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT, LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT,
// IMPORTANT: eventos separados e payloads diferentes
LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
LOADBALANCER_EVENT_SLAVE_STATUS LOADBALANCER_EVENT_SLAVE_STATUS
} loadbalancer_event_id_t; } loadbalancer_event_id_t;
@@ -20,18 +29,18 @@ typedef struct {
int64_t timestamp_us; int64_t timestamp_us;
} loadbalancer_state_event_t; } loadbalancer_state_event_t;
// (opcional)
typedef struct { typedef struct {
float limit; float limit;
int64_t timestamp_us; int64_t timestamp_us;
} loadbalancer_global_limit_event_t; } loadbalancer_global_limit_event_t;
// MASTER: NÃO tem slave_id
typedef struct { typedef struct {
uint8_t slave_id;
uint16_t max_current; uint16_t max_current;
int64_t timestamp_us; int64_t timestamp_us;
} loadbalancer_master_limit_event_t; } loadbalancer_master_limit_event_t;
// SLAVE: tem slave_id
typedef struct { typedef struct {
uint8_t slave_id; uint8_t slave_id;
uint16_t max_current; uint16_t max_current;
@@ -39,9 +48,13 @@ typedef struct {
} loadbalancer_slave_limit_event_t; } loadbalancer_slave_limit_event_t;
typedef struct { typedef struct {
uint8_t slave_id; // ID do slave que reportou uint8_t slave_id;
bool charging; // Status de carregamento bool charging;
float hw_max_current; // Limite máximo de corrente do hardware informado float hw_max_current;
float runtime_current; // Corrente atual de carregamento (A) float runtime_current;
int64_t timestamp_us; // Momento em que o status foi coletado int64_t timestamp_us;
} loadbalancer_slave_status_event_t; } loadbalancer_slave_status_event_t;
#ifdef __cplusplus
}
#endif

View File

@@ -1,18 +1,25 @@
// components/loadbalancer/src/loadbalancer.c
#include "loadbalancer.h" #include "loadbalancer.h"
#include "loadbalancer_events.h" #include "loadbalancer_events.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "input_filter.h" #include "input_filter.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.h>
#include "meter_events.h" #include "meter_events.h"
#include "evse_events.h" #include "evse_events.h"
#include "math.h"
#include <inttypes.h> // Necessário para PRIu64 #include "storage_service.h"
#include <string.h>
#include <math.h>
#include <inttypes.h>
#ifndef MIN #ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -20,23 +27,18 @@
static const char *TAG = "loadbalancer"; static const char *TAG = "loadbalancer";
// Limites configuráveis #define MIN_CHARGING_CURRENT_LIMIT 6
#define MIN_CHARGING_CURRENT_LIMIT 6 // A #define MAX_CHARGING_CURRENT_LIMIT 32
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6 // A #define MIN_GRID_CURRENT_LIMIT 6
#define MAX_GRID_CURRENT_LIMIT 100 // A #define MAX_GRID_CURRENT_LIMIT 100
// Pequena tolerância para considerar "sem margem"
#define AVAILABLE_EPS 1.0f #define AVAILABLE_EPS 1.0f
#define LB_SUSPEND_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT - 1.0f)
#define LB_RESUME_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT + 1.0f)
// Histerese de suspensão / retoma (em torno dos 6A) #define GRID_METER_TIMEOUT_US (120LL * 1000000LL)
#define LB_SUSPEND_THRESHOLD 5.0f // abaixo disto -> suspende
#define LB_RESUME_THRESHOLD 7.0f // acima disto -> pode retomar
// Timeout para perda de medição de GRID (fail-safe)
#define GRID_METER_TIMEOUT_US (10LL * 1000000LL) // 30 segundos
// Parâmetros
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
static bool loadbalancer_enabled = false; static bool loadbalancer_enabled = false;
@@ -45,56 +47,66 @@ static float evse_current = 0.0f;
static input_filter_t grid_filter; static input_filter_t grid_filter;
static input_filter_t evse_filter; static input_filter_t evse_filter;
static int64_t last_grid_timestamp_us = 0; // última atualização de medição GRID static int64_t last_grid_timestamp_us = 0;
#define MAX_SLAVES 255 #define MAX_SLAVES 255
#define CONNECTOR_COUNT (MAX_SLAVES + 1) #define CONNECTOR_COUNT (MAX_SLAVES + 1)
// Proteção simples de concorrência
static SemaphoreHandle_t lb_mutex = NULL; static SemaphoreHandle_t lb_mutex = NULL;
// Estrutura unificada para master e slaves
typedef struct typedef struct
{ {
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave uint8_t id; // para slaves: 0..254 ; para master não usado externamente
bool is_master; bool is_master;
bool charging; bool charging;
float hw_max_current; float hw_max_current;
float runtime_current; float runtime_current;
int64_t timestamp; // microssegundos (última métrica EVSE/slave) int64_t timestamp;
bool online; bool online;
float assigned; // limite calculado pelo LB float assigned;
int64_t started_us; // início da sessão de carregamento (para prioridade) int64_t started_us;
uint16_t last_limit; // último max_current enviado uint16_t last_limit;
bool suspended_by_lb; // flag de suspensão por LB (para histerese) bool suspended_by_lb;
} evse_connector_t; } evse_connector_t;
static evse_connector_t connectors[CONNECTOR_COUNT]; static evse_connector_t connectors[CONNECTOR_COUNT];
const int64_t METRICS_TIMEOUT_US = 60 * 1000000; // 60 segundos const int64_t METRICS_TIMEOUT_US = 60LL * 1000000LL;
// Storage namespace/keys (mantém os mesmos nomes para compatibilidade)
#define LB_NS "loadbalancing"
#define LB_KEY_MAX_GRID "max_grid_curr" // u8
#define LB_KEY_ENABLED "enabled" // u8 (0/1)
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static void input_filter_reset(input_filter_t *filter)
{
if (!filter)
return;
filter->value = 0.0f;
filter->initialized = 0;
}
// Helper: inicializa array de conectores
static void init_connectors(void) static void init_connectors(void)
{ {
// master em índice 0
connectors[0] = (evse_connector_t){ connectors[0] = (evse_connector_t){
.id = 0xFF, .id = 0,
.is_master = true, .is_master = true,
.charging = false, .charging = false,
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT, .hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
.runtime_current = 0, .runtime_current = 0,
.timestamp = 0, .timestamp = 0,
.online = false, .online = true,
.assigned = 0.0f, .assigned = 0.0f,
.started_us = 0, .started_us = 0,
.last_limit = 0, .last_limit = 0,
.suspended_by_lb = false}; .suspended_by_lb = false};
// slaves em 1..CONNECTOR_COUNT-1
for (int i = 1; i < CONNECTOR_COUNT; i++) for (int i = 1; i < CONNECTOR_COUNT; i++)
{ {
connectors[i] = (evse_connector_t){ connectors[i] = (evse_connector_t){
.id = (uint8_t)(i - 1), .id = (uint8_t)(i - 1), // slaves 0..254
.is_master = false, .is_master = false,
.charging = false, .charging = false,
.hw_max_current = 0.0f, .hw_max_current = 0.0f,
@@ -108,26 +120,23 @@ static void init_connectors(void)
} }
} }
// --- Helpers ---
static void input_filter_reset(input_filter_t *filter)
{
filter->value = 0.0f;
}
// Callback de status de slave
static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data) static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)handler_arg;
if (base != LOADBALANCER_EVENTS || id != LOADBALANCER_EVENT_SLAVE_STATUS || !data)
return;
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data; const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
if (status->slave_id >= MAX_SLAVES) if (status->slave_id >= MAX_SLAVES)
{ {
ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id); ESP_LOGW(TAG, "Invalid slave_id %u", (unsigned)status->slave_id);
return; return;
} }
int idx = status->slave_id + 1; // slaves começam no índice 1 int idx = (int)status->slave_id + 1;
bool was_charging; bool was_charging = false;
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -140,44 +149,40 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
connectors[idx].timestamp = esp_timer_get_time(); connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true; connectors[idx].online = true;
// Se começou agora a carregar, marca início da sessão
if (status->charging && !was_charging) if (status->charging && !was_charging)
{ {
connectors[idx].started_us = connectors[idx].timestamp; connectors[idx].started_us = connectors[idx].timestamp;
connectors[idx].suspended_by_lb = false; // reset connectors[idx].suspended_by_lb = false;
} }
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA", "Slave %u status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
status->slave_id, status->charging, (unsigned)status->slave_id, status->charging,
status->hw_max_current, status->runtime_current); status->hw_max_current, status->runtime_current);
} }
static void on_evse_config_event(void *handler_arg, static void on_evse_config_event(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
esp_event_base_t base,
int32_t id,
void *event_data)
{ {
const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data; (void)handler_arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_CONFIG_UPDATED || !event_data)
return;
int idx = 0; // MASTER INDICE 0 const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data;
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
connectors[idx].charging = evt->charging; connectors[0].charging = evt->charging;
connectors[idx].hw_max_current = evt->hw_max_current; connectors[0].hw_max_current = evt->hw_max_current;
connectors[idx].runtime_current = evt->runtime_current; connectors[0].runtime_current = evt->runtime_current;
connectors[idx].timestamp = esp_timer_get_time(); connectors[0].timestamp = esp_timer_get_time();
connectors[idx].online = true; connectors[0].online = true;
if (!evt->charging) if (!evt->charging)
{ connectors[0].suspended_by_lb = false;
connectors[idx].suspended_by_lb = false;
}
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
@@ -186,30 +191,25 @@ static void on_evse_config_event(void *handler_arg,
evt->charging, evt->hw_max_current, evt->runtime_current); evt->charging, evt->hw_max_current, evt->runtime_current);
} }
// --- Handlers de eventos externos ---
static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data) static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
{ {
if (id != METER_EVENT_DATA_READY || event_data == NULL) (void)handler_arg;
(void)base;
if (id != METER_EVENT_DATA_READY || !event_data)
return; return;
const meter_event_data_t *evt = (const meter_event_data_t *)event_data; const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
float max_irms = evt->irms[0]; float max_irms = evt->irms[0];
for (int i = 1; i < 3; ++i) for (int i = 1; i < 3; ++i)
{
if (evt->irms[i] > max_irms) if (evt->irms[i] > max_irms)
{
max_irms = evt->irms[i]; max_irms = evt->irms[i];
}
}
float max_vrms = evt->vrms[0]; float max_vrms = evt->vrms[0];
for (int i = 1; i < 3; ++i) for (int i = 1; i < 3; ++i)
{
if (evt->vrms[i] > max_vrms) if (evt->vrms[i] > max_vrms)
{
max_vrms = evt->vrms[i]; max_vrms = evt->vrms[i];
}
}
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -218,28 +218,28 @@ static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t
{ {
grid_current = input_filter_update(&grid_filter, max_irms); grid_current = input_filter_update(&grid_filter, max_irms);
last_grid_timestamp_us = esp_timer_get_time(); last_grid_timestamp_us = esp_timer_get_time();
ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current); ESP_LOGD(TAG, "GRID IRMS (filtered): %.2f A VRMS: %.2f V", grid_current, max_vrms);
ESP_LOGI(TAG, "GRID VRMS: %.2f V", max_vrms);
} }
else if (evt->source && strcmp(evt->source, "EVSE") == 0) else if (evt->source && strcmp(evt->source, "EVSE") == 0)
{ {
evse_current = input_filter_update(&evse_filter, max_irms); evse_current = input_filter_update(&evse_filter, max_irms);
ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current); ESP_LOGD(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
} }
else else
{ {
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source); ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source ? evt->source : "(null)");
} }
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
} }
static void loadbalancer_evse_event_handler(void *handler_arg, static void loadbalancer_evse_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
esp_event_base_t base,
int32_t id,
void *event_data)
{ {
(void)handler_arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || !event_data)
return;
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data; const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data;
if (lb_mutex) if (lb_mutex)
@@ -249,17 +249,13 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
{ {
case EVSE_STATE_EVENT_IDLE: case EVSE_STATE_EVENT_IDLE:
case EVSE_STATE_EVENT_WAITING: case EVSE_STATE_EVENT_WAITING:
ESP_LOGI(TAG, "Local EVSE is %s - vehicle %sconnected / not charging",
evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING",
evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : "");
connectors[0].charging = false; connectors[0].charging = false;
connectors[0].online = true; // master está sempre online connectors[0].online = true;
connectors[0].suspended_by_lb = false; connectors[0].suspended_by_lb = false;
break; break;
case EVSE_STATE_EVENT_CHARGING: case EVSE_STATE_EVENT_CHARGING:
{ {
ESP_LOGI(TAG, "Local EVSE is CHARGING - resetting filters");
grid_current = 0.0f; grid_current = 0.0f;
evse_current = 0.0f; evse_current = 0.0f;
input_filter_reset(&grid_filter); input_filter_reset(&grid_filter);
@@ -280,14 +276,12 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
} }
case EVSE_STATE_EVENT_FAULT: case EVSE_STATE_EVENT_FAULT:
ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily");
connectors[0].charging = false; connectors[0].charging = false;
connectors[0].online = true; // EVSE está online mas com falha connectors[0].online = true;
connectors[0].suspended_by_lb = false; connectors[0].suspended_by_lb = false;
break; break;
default: default:
ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state);
break; break;
} }
@@ -295,56 +289,84 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
} }
// --- Config persistência --- // --------- Config load/save via storage_service ---------
static esp_err_t loadbalancer_load_config()
static esp_err_t loadbalancer_load_config(void)
{ {
nvs_handle_t handle; // garante storage iniciado
esp_err_t err = nvs_open("loadbalancing", NVS_READWRITE, &handle); esp_err_t se = storage_service_init();
if (err != ESP_OK) if (se != ESP_OK)
return err; return se;
bool needs_commit = false; esp_err_t err;
uint8_t temp_u8; bool needs_flush = false;
err = nvs_get_u8(handle, "max_grid_curr", &temp_u8); uint8_t temp_u8 = 0;
// max_grid_curr
err = storage_get_u8_sync(LB_NS, LB_KEY_MAX_GRID, &temp_u8, TO_TICKS_MS(800));
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT) if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
{
max_grid_current = temp_u8; max_grid_current = temp_u8;
}
else else
{ {
max_grid_current = MAX_GRID_CURRENT_LIMIT; max_grid_current = MAX_GRID_CURRENT_LIMIT;
nvs_set_u8(handle, "max_grid_curr", max_grid_current); (void)storage_set_u8_async(LB_NS, LB_KEY_MAX_GRID, max_grid_current);
needs_commit = true; needs_flush = true;
ESP_LOGW(TAG, "Invalid/missing max_grid_curr (%s) -> default=%u (persisted)",
esp_err_to_name(err), (unsigned)max_grid_current);
} }
err = nvs_get_u8(handle, "enabled", &temp_u8); // enabled
err = storage_get_u8_sync(LB_NS, LB_KEY_ENABLED, &temp_u8, TO_TICKS_MS(800));
if (err == ESP_OK && temp_u8 <= 1) if (err == ESP_OK && temp_u8 <= 1)
{
loadbalancer_enabled = (temp_u8 != 0); loadbalancer_enabled = (temp_u8 != 0);
}
else else
{ {
loadbalancer_enabled = false; loadbalancer_enabled = false;
nvs_set_u8(handle, "enabled", 0); (void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, 0);
needs_commit = true; needs_flush = true;
ESP_LOGW(TAG, "Invalid/missing enabled (%s) -> default=false (persisted)",
esp_err_to_name(err));
}
if (needs_flush)
{
// determinístico no boot
(void)storage_flush_sync(TO_TICKS_MS(2000));
} }
if (needs_commit)
nvs_commit(handle);
nvs_close(handle);
return ESP_OK; return ESP_OK;
} }
// --- API --- static void persist_lb_enabled(bool enabled)
{
(void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, enabled ? 1 : 0);
// debounced por defeito
}
static void persist_max_grid_current(uint8_t value)
{
(void)storage_set_u8_async(LB_NS, LB_KEY_MAX_GRID, value);
// debounced por defeito
}
void loadbalancer_set_enabled(bool enabled) void loadbalancer_set_enabled(bool enabled)
{ {
nvs_handle_t handle; // Só persiste/propaga se mudou
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) if (enabled == loadbalancer_enabled)
{ {
nvs_set_u8(handle, "enabled", enabled ? 1 : 0); return;
nvs_commit(handle);
nvs_close(handle);
} }
loadbalancer_enabled = enabled; loadbalancer_enabled = enabled;
persist_lb_enabled(enabled);
loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()}; loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED, (void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
&evt, sizeof(evt), portMAX_DELAY); &evt, sizeof(evt), portMAX_DELAY);
} }
@@ -352,29 +374,25 @@ esp_err_t load_balancing_set_max_grid_current(uint8_t value)
{ {
if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT) if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
nvs_handle_t handle;
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) != ESP_OK) // Só persiste se mudou
return ESP_FAIL; if (value == max_grid_current)
nvs_set_u8(handle, "max_grid_curr", value); {
nvs_commit(handle);
nvs_close(handle);
max_grid_current = value;
return ESP_OK; return ESP_OK;
} }
uint8_t load_balancing_get_max_grid_current(void) max_grid_current = value;
{ persist_max_grid_current(value);
return max_grid_current; return ESP_OK;
} }
bool loadbalancer_is_enabled(void) uint8_t load_balancing_get_max_grid_current(void) { return max_grid_current; }
{ bool loadbalancer_is_enabled(void) { return loadbalancer_enabled; }
return loadbalancer_enabled;
}
// --- Task principal ---
void loadbalancer_task(void *param) void loadbalancer_task(void *param)
{ {
(void)param;
while (true) while (true)
{ {
if (!loadbalancer_is_enabled()) if (!loadbalancer_is_enabled())
@@ -387,110 +405,66 @@ void loadbalancer_task(void *param)
int active_cnt = 0; int active_cnt = 0;
int64_t now = esp_timer_get_time(); int64_t now = esp_timer_get_time();
// --- Atualiza estado online e conta ativos ---
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int i = 0; i < CONNECTOR_COUNT; i++) for (int i = 0; i < CONNECTOR_COUNT; i++)
{ {
// --- Master nunca pode ficar offline ---
if (connectors[i].is_master) if (connectors[i].is_master)
{ {
connectors[i].online = true; connectors[i].online = true;
ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)",
i, connectors[i].charging, connectors[i].hw_max_current);
if (connectors[i].charging) if (connectors[i].charging)
{
idxs[active_cnt++] = i; idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
continue; continue;
} }
// --- Ignora conectores já marcados como offline ---
if (!connectors[i].online) if (!connectors[i].online)
{
continue; continue;
}
// --- Timeout de heartbeat para escravos ---
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US) if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US)
{ {
connectors[i].online = false; connectors[i].online = false;
ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)",
i, connectors[i].charging, (long long)(now - connectors[i].timestamp));
continue; continue;
} }
ESP_LOGI(TAG, "Connector[%d] ONLINE (charging=%d, hw_max_current=%.1f, timestamp_diff=%lld us)",
i, connectors[i].charging, connectors[i].hw_max_current,
(long long)(now - connectors[i].timestamp));
if (connectors[i].charging) if (connectors[i].charging)
{
idxs[active_cnt++] = i; idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
} }
// snapshot de grid_current e last_grid_timestamp sob mutex
float grid_snapshot = grid_current; float grid_snapshot = grid_current;
int64_t last_grid_ts_snapshot = last_grid_timestamp_us; int64_t last_grid_ts_snapshot = last_grid_timestamp_us;
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
ESP_LOGI(TAG, "Active connectors: %d", active_cnt);
if (active_cnt == 0) if (active_cnt == 0)
{ {
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
continue; continue;
} }
// --- Verifica timeout de medição de GRID (fail-safe) ---
bool meter_timeout = (last_grid_ts_snapshot == 0 || bool meter_timeout = (last_grid_ts_snapshot == 0 ||
(now - last_grid_ts_snapshot) > GRID_METER_TIMEOUT_US); (now - last_grid_ts_snapshot) > GRID_METER_TIMEOUT_US);
if (meter_timeout) if (meter_timeout)
{ {
ESP_LOGW(TAG,
"GRID meter timeout (last update=%lld us ago). Applying fail-safe limits (<=%dA).",
(long long)(now - last_grid_ts_snapshot), MIN_CHARGING_CURRENT_LIMIT);
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
// Fail-safe: limitar cada EV ao mínimo permitido (6A) ou menos, nunca aumentar
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ {
int i = idxs[k]; int i = idxs[k];
float cur = connectors[i].runtime_current; float cur = connectors[i].runtime_current;
if (cur > MIN_CHARGING_CURRENT_LIMIT) connectors[i].assigned = (cur > MIN_CHARGING_CURRENT_LIMIT) ? (float)MIN_CHARGING_CURRENT_LIMIT : cur;
connectors[i].assigned = (float)MIN_CHARGING_CURRENT_LIMIT;
else
connectors[i].assigned = cur;
} }
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
goto publish_limits;
} }
// --- Calcula corrente disponível (headroom global) ---
float available = (float)max_grid_current - grid_snapshot; float available = (float)max_grid_current - grid_snapshot;
ESP_LOGI(TAG, "LB raw headroom: max_grid=%uA, grid_current=%.1fA, available=%.2fA",
max_grid_current, grid_snapshot, available);
// ==========================
// ZONA A: overload significativo -> reduzir correntes (throttling)
// ==========================
if (available < -AVAILABLE_EPS) if (available < -AVAILABLE_EPS)
{ {
ESP_LOGW(TAG, "Overload: grid=%.1fA, max=%.1fA (available=%.2fA) -> throttling",
grid_snapshot, (float)max_grid_current, available);
float factor = ((float)max_grid_current) / grid_snapshot; float factor = ((float)max_grid_current) / grid_snapshot;
if (factor < 0.0f) if (factor < 0.0f)
factor = 0.0f; factor = 0.0f;
@@ -499,89 +473,53 @@ void loadbalancer_task(void *param)
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ {
int i = idxs[k]; int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current * factor; connectors[i].assigned = connectors[i].runtime_current * factor;
ESP_LOGI(TAG,
"Connector[%d] overload throttling: runtime=%.1fA -> assigned=%.1fA",
i, connectors[i].runtime_current, connectors[i].assigned);
} }
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
} }
// ==========================
// ZONA B: sem margem prática -> manter correntes atuais como limites
// ==========================
else if (fabsf(available) <= AVAILABLE_EPS) else if (fabsf(available) <= AVAILABLE_EPS)
{ {
ESP_LOGI(TAG,
"No effective headroom: grid=%.1fA, max=%.1fA (available=%.2fA). Keeping current as limit.",
grid_snapshot, (float)max_grid_current, available);
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ {
int i = idxs[k]; int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current; connectors[i].assigned = connectors[i].runtime_current;
ESP_LOGI(TAG,
"Connector[%d] keep runtime as limit: assigned=%.1fA",
i, connectors[i].assigned);
} }
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
} }
// ========================== else
// ZONA C: há margem positiva -> garantir mínimo e depois water-filling SOBRE assigned
// ==========================
else // available > AVAILABLE_EPS
{ {
if (available > max_grid_current) if (available > max_grid_current)
{
available = (float)max_grid_current; available = (float)max_grid_current;
}
ESP_LOGI(TAG, "LB Calc (zone C): available=%.1fA, active_connectors=%d",
available, active_cnt);
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
// 1) Ordenar conectores ativos por started_us (mais antigos primeiro) // ordenar por started_us
for (int a = 0; a < active_cnt - 1; a++) for (int a = 0; a < active_cnt - 1; a++)
{
for (int b = 0; b < active_cnt - 1 - a; b++) for (int b = 0; b < active_cnt - 1 - a; b++)
{ if (connectors[idxs[b]].started_us > connectors[idxs[b + 1]].started_us)
int i1 = idxs[b];
int i2 = idxs[b + 1];
if (connectors[i1].started_us > connectors[i2].started_us)
{ {
int tmp = idxs[b]; int tmp = idxs[b];
idxs[b] = idxs[b + 1]; idxs[b] = idxs[b + 1];
idxs[b + 1] = tmp; idxs[b + 1] = tmp;
} }
}
}
// Inicialmente: assigned = runtime_current
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ connectors[idxs[k]].assigned = connectors[idxs[k]].runtime_current;
int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current;
}
float remaining = available; // margem extra total float remaining = available;
// 2) FASE 1: tentar garantir pelo menos 6A (ou hw_max, se menor) aos mais antigos // fase 1 min 6A
for (int k = 0; k < active_cnt && remaining > 0.0f; k++) for (int k = 0; k < active_cnt && remaining > 0.0f; k++)
{ {
int i = idxs[k]; int i = idxs[k];
float current = connectors[i].runtime_current; float current = connectors[i].runtime_current;
float hw_max = connectors[i].hw_max_current; float hw_max = connectors[i].hw_max_current;
@@ -590,10 +528,7 @@ void loadbalancer_task(void *param)
target_min = hw_max; target_min = hw_max;
if (current >= target_min) if (current >= target_min)
{
connectors[i].assigned = current;
continue; continue;
}
float delta = target_min - current; float delta = target_min - current;
if (delta <= remaining) if (delta <= remaining)
@@ -601,37 +536,25 @@ void loadbalancer_task(void *param)
connectors[i].assigned = current + delta; connectors[i].assigned = current + delta;
remaining -= delta; remaining -= delta;
} }
else
{
connectors[i].assigned = current;
}
} }
// 3) FASE 2: "last in, first cut" -> cortar quem ficou abaixo do mínimo começando pelos mais recentes // fase 2 suspender recentes abaixo min
for (int k = active_cnt - 1; k >= 0; k--) for (int k = active_cnt - 1; k >= 0; k--)
{ {
int i = idxs[k]; int i = idxs[k];
if (connectors[i].assigned >= MIN_CHARGING_CURRENT_LIMIT) if (connectors[i].assigned >= MIN_CHARGING_CURRENT_LIMIT)
{
continue; continue;
}
ESP_LOGI(TAG, "Connector[%d] below min after phase1 (assigned=%.1fA) -> suspending (0A)",
i, connectors[i].assigned);
connectors[i].assigned = 0.0f; connectors[i].assigned = 0.0f;
connectors[i].suspended_by_lb = true; connectors[i].suspended_by_lb = true;
} }
// 4) FASE 3: se ainda sobrar margem, distribuir extra por cima dos que ficaram ON (assigned > 0) // fase 3 distribuir extra
if (remaining > AVAILABLE_EPS) if (remaining > AVAILABLE_EPS)
{ {
int on_cnt = 0; int on_cnt = 0;
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ if (connectors[idxs[k]].assigned > 0.0f)
int i = idxs[k];
if (connectors[i].assigned > 0.0f)
on_cnt++; on_cnt++;
}
float extra_remaining = remaining; float extra_remaining = remaining;
int extra_cnt = on_cnt; int extra_cnt = on_cnt;
@@ -639,10 +562,8 @@ void loadbalancer_task(void *param)
for (int k = 0; k < active_cnt; k++) for (int k = 0; k < active_cnt; k++)
{ {
int i = idxs[k]; int i = idxs[k];
if (connectors[i].assigned <= 0.0f) if (connectors[i].assigned <= 0.0f)
continue; continue;
if (extra_cnt <= 0 || extra_remaining <= 0.0f) if (extra_cnt <= 0 || extra_remaining <= 0.0f)
break; break;
@@ -666,7 +587,6 @@ void loadbalancer_task(void *param)
for (int m = k; m < active_cnt; m++) for (int m = k; m < active_cnt; m++)
{ {
int j = idxs[m]; int j = idxs[m];
if (connectors[j].assigned <= 0.0f) if (connectors[j].assigned <= 0.0f)
continue; continue;
@@ -686,7 +606,7 @@ void loadbalancer_task(void *param)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
} }
// --- Publicação de limites / suspensão com histerese --- publish_limits:
if (lb_mutex) if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY); xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -696,10 +616,8 @@ void loadbalancer_task(void *param)
float assigned = connectors[i].assigned; float assigned = connectors[i].assigned;
float effective = assigned; float effective = assigned;
// Histerese de suspensão / retoma
if (connectors[i].suspended_by_lb) if (connectors[i].suspended_by_lb)
{ {
// Está suspenso: só retoma se limite calculado for >= limiar de retoma
if (assigned >= LB_RESUME_THRESHOLD) if (assigned >= LB_RESUME_THRESHOLD)
{ {
effective = assigned; effective = assigned;
@@ -712,7 +630,6 @@ void loadbalancer_task(void *param)
} }
else else
{ {
// Ainda não está suspenso: só suspende se ficar claramente abaixo do limiar
if (assigned > 0.0f && assigned < LB_SUSPEND_THRESHOLD) if (assigned > 0.0f && assigned < LB_SUSPEND_THRESHOLD)
{ {
effective = 0.0f; effective = 0.0f;
@@ -720,48 +637,40 @@ void loadbalancer_task(void *param)
} }
} }
uint16_t max_cur; uint16_t max_cur = (effective <= 0.0f) ? 0 : (uint16_t)MIN(effective, (float)MAX_CHARGING_CURRENT_LIMIT);
if (effective <= 0.0f)
{
max_cur = 0;
}
else
{
max_cur = (uint16_t)MIN(effective, (float)MAX_CHARGING_CURRENT_LIMIT);
}
// Evita flapping de comandos: só envia se o limite mudou
if (connectors[i].last_limit == max_cur) if (connectors[i].last_limit == max_cur)
{ continue;
continue; // sem alteração
}
connectors[i].last_limit = max_cur; connectors[i].last_limit = max_cur;
// sair do mutex durante o post
if (lb_mutex) if (lb_mutex)
xSemaphoreGive(lb_mutex); xSemaphoreGive(lb_mutex);
if (connectors[i].is_master) if (connectors[i].is_master)
{ {
loadbalancer_master_limit_event_t master_evt = { loadbalancer_master_limit_event_t m = {
.slave_id = connectors[i].id,
.max_current = max_cur, .max_current = max_cur,
.timestamp_us = now}; .timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, (void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
&master_evt, sizeof(master_evt), portMAX_DELAY); &m, sizeof(m), portMAX_DELAY);
ESP_LOGI(TAG, "Master limit changed -> %.1f A (assigned=%.2f A)",
(float)max_cur, assigned);
} }
else else
{ {
loadbalancer_slave_limit_event_t slave_evt = { uint8_t sid = connectors[i].id;
.slave_id = connectors[i].id, if (sid < MAX_SLAVES)
{
loadbalancer_slave_limit_event_t s = {
.slave_id = sid,
.max_current = max_cur, .max_current = max_cur,
.timestamp_us = now}; .timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, (void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
&slave_evt, sizeof(slave_evt), portMAX_DELAY); &s, sizeof(s), portMAX_DELAY);
ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (assigned=%.2f A)", }
connectors[i].id, (float)max_cur, assigned); else
{
ESP_LOGW(TAG, "Skipping publish: invalid slave id=%u", (unsigned)sid);
}
} }
if (lb_mutex) if (lb_mutex)
@@ -775,36 +684,31 @@ void loadbalancer_task(void *param)
} }
} }
// --- Init ---
void loadbalancer_init(void) void loadbalancer_init(void)
{ {
if (loadbalancer_load_config() != ESP_OK) if (loadbalancer_load_config() != ESP_OK)
ESP_LOGW(TAG, "Failed to load/init config. Using defaults."); ESP_LOGW(TAG, "Failed to load/init config. Using defaults.");
lb_mutex = xSemaphoreCreateMutex(); lb_mutex = xSemaphoreCreateMutex();
if (lb_mutex == NULL) if (!lb_mutex)
{
ESP_LOGE(TAG, "Failed to create loadbalancer mutex"); ESP_LOGE(TAG, "Failed to create loadbalancer mutex");
}
init_connectors(); init_connectors();
input_filter_init(&grid_filter, 0.3f); input_filter_init(&grid_filter, 0.3f);
input_filter_init(&evse_filter, 0.3f); input_filter_init(&evse_filter, 0.3f);
if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS) if (xTaskCreate(loadbalancer_task, "loadbalancer", 8192, NULL, 4, NULL) != pdPASS)
ESP_LOGE(TAG, "Failed to create loadbalancer task"); ESP_LOGE(TAG, "Failed to create loadbalancer task");
loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()}; loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); (void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY,
&loadbalancer_meter_event_handler, NULL)); &loadbalancer_meter_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
&loadbalancer_evse_event_handler, NULL)); &loadbalancer_evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED, ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED,
&on_evse_config_event, NULL)); &on_evse_config_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_STATUS, ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_STATUS,
&on_slave_status, NULL)); &on_slave_status, NULL));
} }

View File

@@ -3,56 +3,45 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h" #include "freertos/event_groups.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define LOGGER_SERIAL_BIT BIT0 #define LOGGER_SERIAL_BIT BIT0
/**
* @brief Logger event group LOGGER_SERIAL_BIT
*
*/
extern EventGroupHandle_t logger_event_group; extern EventGroupHandle_t logger_event_group;
/**
* @brief Initialize logger
*
*/
void logger_init(void); void logger_init(void);
/**
* @brief Print
*
* @param str
*/
void logger_print(const char *str); void logger_print(const char *str);
/** int logger_vprintf(const char *fmt, va_list args);
* @brief Print va
*
* @param str
* @param l
* @return int
*/
int logger_vprintf(const char* str, va_list l);
/**
* @brief Get entries count
*
* @return uint16_t
*/
uint16_t logger_count(void); uint16_t logger_count(void);
// opcional: quantas mensagens foram dropadas por contenção de mutex
uint32_t logger_dropped_count(void);
/** /**
* @brief Read line from index, set index for reading next entry * ⚠️ API antiga (não recomendada): devolve ponteiro interno.
* * Pode ficar inválido se houver novas escritas/rotação.
* @param index
* @param str
* @param v
* @return true When has next entry
* @return false When no entry left
*/ */
bool logger_read(uint16_t *index, char **str, uint16_t *len); bool logger_read(uint16_t *index, char **str, uint16_t *len);
/**
* ✅ API recomendada: copia a entrada para buffer do caller (safe).
* out é sempre terminado com '\0' (se out_sz > 0).
*/
bool logger_read_copy(uint16_t *index, char *out, uint16_t out_sz, uint16_t *out_len);
#ifdef __cplusplus
}
#endif
#endif /* LOGGER_H_ */ #endif /* LOGGER_H_ */

View File

@@ -4,7 +4,13 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
typedef struct { #ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
uint16_t size; uint16_t size;
uint16_t count; uint16_t count;
uint8_t *data; uint8_t *data;
@@ -21,4 +27,8 @@ void output_buffer_append_str(output_buffer_t* buffer, const char* str);
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len); bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len);
#ifdef __cplusplus
}
#endif
#endif /* OUTPUT_BUFFER_H_ */ #endif /* OUTPUT_BUFFER_H_ */

View File

@@ -1,66 +1,155 @@
#include <stdio.h> #include <stdio.h>
#include <memory.h> #include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/param.h> #include <sys/param.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "logger.h" #include "logger.h"
#include "output_buffer.h" #include "output_buffer.h"
#define LOG_BUFFER_SIZE 6096 //4096 #define LOG_BUFFER_SIZE 6096 // tamanho total do buffer circular
#define MAX_LOG_SIZE 512 #define MAX_LOG_SIZE 256 // ✅ reduzir stack/CPU; era 512
static SemaphoreHandle_t mutex;
static SemaphoreHandle_t mutex = NULL;
static output_buffer_t *buffer = NULL; static output_buffer_t *buffer = NULL;
EventGroupHandle_t logger_event_group = NULL; EventGroupHandle_t logger_event_group = NULL;
// opcional: contador de mensagens dropadas quando mutex está ocupado
static volatile uint32_t s_dropped = 0;
void logger_init(void) void logger_init(void)
{ {
// Permitir múltiplas chamadas seguras
if (mutex != NULL)
{
return;
}
mutex = xSemaphoreCreateMutex(); mutex = xSemaphoreCreateMutex();
configASSERT(mutex != NULL);
logger_event_group = xEventGroupCreate(); logger_event_group = xEventGroupCreate();
configASSERT(logger_event_group != NULL);
buffer = output_buffer_create(LOG_BUFFER_SIZE); buffer = output_buffer_create(LOG_BUFFER_SIZE);
configASSERT(buffer != NULL);
} }
uint16_t logger_count(void) uint16_t logger_count(void)
{ {
return buffer->count; if (!mutex || !buffer)
{
return 0;
}
// ✅ não bloquear para sempre (mas aqui pode bloquear sem stress)
xSemaphoreTake(mutex, portMAX_DELAY);
uint16_t c = buffer->count;
xSemaphoreGive(mutex);
return c;
}
uint32_t logger_dropped_count(void)
{
return s_dropped;
} }
void logger_print(const char *str) void logger_print(const char *str)
{ {
xSemaphoreTake(mutex, portMAX_DELAY); if (!str || !mutex || !buffer)
{
return;
}
output_buffer_append_str(buffer, str); // Limitar comprimento para evitar entradas enormes
xEventGroupSetBits(logger_event_group, 0xFF); size_t len = strlen(str);
if (len > (MAX_LOG_SIZE - 1))
{
len = MAX_LOG_SIZE - 1;
}
// ✅ NÃO bloquear aqui: se o mutex estiver ocupado, dropa
if (xSemaphoreTake(mutex, 0) != pdTRUE)
{
s_dropped++;
return;
}
output_buffer_append_buf(buffer, str, (uint16_t)len);
xEventGroupSetBits(logger_event_group, LOGGER_SERIAL_BIT);
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
} }
int logger_vprintf(const char* str, va_list l) int logger_vprintf(const char *fmt, va_list args)
{ {
char log_buf[MAX_LOG_SIZE];
#ifdef CONFIG_ESP_CONSOLE_UART #ifdef CONFIG_ESP_CONSOLE_UART
vprintf(str, l); // Duplicar va_list para ecoar na UART sem consumir o original
va_list args_copy;
va_copy(args_copy, args);
vprintf(fmt, args_copy);
va_end(args_copy);
#endif #endif
xSemaphoreTake(mutex, portMAX_DELAY); // Se ainda não inicializado, apenas formatar para devolver comprimento
if (!mutex || !buffer)
{
int len = vsnprintf(log_buf, MAX_LOG_SIZE, fmt, args);
if (len < 0)
{
len = 0;
}
else if (len >= MAX_LOG_SIZE)
{
len = MAX_LOG_SIZE - 1;
}
return len;
}
static char log[MAX_LOG_SIZE]; int len = vsnprintf(log_buf, MAX_LOG_SIZE, fmt, args);
int len = vsnprintf(log, MAX_LOG_SIZE, str, l); if (len < 0)
{
len = 0;
}
else if (len >= MAX_LOG_SIZE)
{
len = MAX_LOG_SIZE - 1;
}
output_buffer_append_buf(buffer, log, len); // ✅ NÃO bloquear o sistema (sys_evt/httpd/wifi/etc) por causa de log
xEventGroupSetBits(logger_event_group, 0xFF); if (xSemaphoreTake(mutex, 0) != pdTRUE)
{
s_dropped++;
return len;
}
output_buffer_append_buf(buffer, log_buf, (uint16_t)len);
xEventGroupSetBits(logger_event_group, LOGGER_SERIAL_BIT);
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
return len; return len;
} }
/**
* ⚠️ API antiga: devolve ponteiro interno do buffer.
* Só é segura se o caller COPIAR imediatamente e garantir que não há novas escritas.
* Recomendo usar logger_read_copy().
*/
bool logger_read(uint16_t *index, char **str, uint16_t *len) bool logger_read(uint16_t *index, char **str, uint16_t *len)
{ {
if (!mutex || !buffer || !index || !str || !len)
{
return false;
}
xSemaphoreTake(mutex, portMAX_DELAY); xSemaphoreTake(mutex, portMAX_DELAY);
bool has_next = output_buffer_read(buffer, index, str, len); bool has_next = output_buffer_read(buffer, index, str, len);
@@ -69,3 +158,35 @@ bool logger_read(uint16_t* index, char** str, uint16_t* len)
return has_next; return has_next;
} }
// ✅ API segura: copia a linha para buffer do caller (evita ponteiro ficar inválido após rotação)
bool logger_read_copy(uint16_t *index, char *out, uint16_t out_sz, uint16_t *out_len)
{
if (!mutex || !buffer || !index || !out || out_sz == 0)
{
return false;
}
xSemaphoreTake(mutex, portMAX_DELAY);
char *ptr = NULL;
uint16_t len = 0;
bool ok = output_buffer_read(buffer, index, &ptr, &len);
if (!ok)
{
xSemaphoreGive(mutex);
return false;
}
uint16_t n = (len < (out_sz - 1)) ? len : (out_sz - 1);
memcpy(out, ptr, n);
out[n] = '\0';
if (out_len)
{
*out_len = n;
}
xSemaphoreGive(mutex);
return true;
}

View File

@@ -1,49 +1,125 @@
#include <memory.h> #include <stdlib.h>
#include <string.h>
#include "output_buffer.h" #include "output_buffer.h"
output_buffer_t *output_buffer_create(uint16_t size) output_buffer_t *output_buffer_create(uint16_t size)
{ {
if (size == 0)
{
return NULL;
}
output_buffer_t *buffer = (output_buffer_t *)malloc(sizeof(output_buffer_t)); output_buffer_t *buffer = (output_buffer_t *)malloc(sizeof(output_buffer_t));
if (!buffer)
{
return NULL;
}
buffer->data = (uint8_t *)malloc((size_t)size);
if (!buffer->data)
{
free(buffer);
return NULL;
}
buffer->size = size; buffer->size = size;
buffer->count = 0; buffer->count = 0;
buffer->data = (uint8_t*)malloc(sizeof(uint8_t) * size);
buffer->append = buffer->data; buffer->append = buffer->data;
return buffer; return buffer;
} }
void output_buffer_delete(output_buffer_t *buffer) void output_buffer_delete(output_buffer_t *buffer)
{
if (!buffer)
{
return;
}
if (buffer->data)
{ {
free((void *)buffer->data); free((void *)buffer->data);
buffer->data = NULL;
}
free((void *)buffer); free((void *)buffer);
} }
void output_buffer_append_buf(output_buffer_t *buffer, const char *str, uint16_t len) void output_buffer_append_buf(output_buffer_t *buffer, const char *str, uint16_t len)
{ {
if (((buffer->append - buffer->data) + sizeof(uint16_t) + len) >= buffer->size) { if (!buffer || !buffer->data || !str || len == 0)
//rotate buffer {
return;
}
// Garantir que nunca escrevemos entradas absurdamente grandes
if (len > buffer->size / 2)
{
// Tamanho de entrada demasiado grande para a lógica de rotação;
// corta-a para caber de forma segura.
len = buffer->size / 2;
}
size_t used = (size_t)(buffer->append - buffer->data);
if (used > buffer->size)
{
// Estado incoerente: reset defensivo
buffer->append = buffer->data;
buffer->count = 0;
used = 0;
}
// Se não couber mais esta entrada, rodar o buffer
if (used + sizeof(uint16_t) + len > buffer->size)
{
uint8_t *pos = buffer->data; uint8_t *pos = buffer->data;
uint16_t rotate_count = 0; uint16_t rotate_count = 0;
while ((pos - buffer->data) < buffer->size / 2) { uint8_t *end = buffer->data + buffer->size;
//seek first half
// Avança entradas até aproximadamente metade do buffer
while ((pos + sizeof(uint16_t)) < end &&
(size_t)(pos - buffer->data) < buffer->size / 2)
{
uint16_t entry_len; uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t)); memcpy(&entry_len, pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
// Sanitizar entry_len para evitar corrupções
if (entry_len == 0 || entry_len > buffer->size)
{
// Corrompido → reset defensivo
pos = buffer->data;
rotate_count = 0;
buffer->count = 0;
break;
}
if (pos + sizeof(uint16_t) + entry_len > end)
{
// Entrada incompleta na cauda → para por aqui
break;
}
pos += sizeof(uint16_t) + entry_len;
rotate_count++; rotate_count++;
} }
memmove((void*)buffer->data, (void*)pos, buffer->size - (pos - buffer->data)); // Compacta o que sobrou para o início
buffer->count -= rotate_count; size_t remaining = (size_t)(end - pos);
buffer->append -= (pos - buffer->data); memmove(buffer->data, pos, remaining);
buffer->count = (buffer->count >= rotate_count) ? (buffer->count - rotate_count) : 0;
buffer->append = buffer->data + remaining;
used = (size_t)(buffer->append - buffer->data);
} }
memcpy((void*)buffer->append, (void*)&len, sizeof(uint16_t)); // Escreve [len][dados]
memcpy(buffer->append, &len, sizeof(uint16_t));
buffer->append += sizeof(uint16_t); buffer->append += sizeof(uint16_t);
memcpy((void*)buffer->append, (void*)str, len); memcpy(buffer->append, str, len);
buffer->append += len; buffer->append += len;
buffer->count++; buffer->count++;
@@ -51,36 +127,91 @@ void output_buffer_append_buf(output_buffer_t* buffer, const char* str, uint16_t
void output_buffer_append_str(output_buffer_t *buffer, const char *str) void output_buffer_append_str(output_buffer_t *buffer, const char *str)
{ {
output_buffer_append_buf(buffer, str, strlen(str)); if (!buffer || !str)
{
return;
}
size_t len = strlen(str);
if (len == 0)
{
return;
}
// A API pública em logger.c já limita o tamanho, mas aqui fazemos
// um clamp defensivo para o caso de uso direto.
if (len > UINT16_MAX)
{
len = UINT16_MAX;
}
output_buffer_append_buf(buffer, str, (uint16_t)len);
} }
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len) bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len)
{ {
if (*index > buffer->count) { if (!buffer || !buffer->data || !index || !str || !len)
{
return false;
}
if (*index > buffer->count)
{
*index = buffer->count; *index = buffer->count;
} }
bool has_next = false; if (*index >= buffer->count)
{
return false;
}
if (*index < buffer->count) {
uint8_t *pos = buffer->data; uint8_t *pos = buffer->data;
uint8_t *end = buffer->data + buffer->size;
uint16_t current = 0; uint16_t current = 0;
while (current != *index) { // Avança até à entrada [*index]
while (current < *index)
{
if (pos + sizeof(uint16_t) > end)
{
// Dados corrompidos ou índice fora → fail-safe
return false;
}
uint16_t entry_len; uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t)); memcpy(&entry_len, pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
if (entry_len == 0 || entry_len > buffer->size)
{
// Corrompido → aborta
return false;
}
if (pos + sizeof(uint16_t) + entry_len > end)
{
return false;
}
pos += sizeof(uint16_t) + entry_len;
current++; current++;
} }
memcpy((void*)len, (void*)pos, sizeof(uint16_t)); // Agora pos aponta para o len da entrada desejada
pos += sizeof(uint16_t); if (pos + sizeof(uint16_t) > end)
*str = (char*)pos; {
return false;
}
memcpy(len, pos, sizeof(uint16_t));
pos += sizeof(uint16_t);
if (pos + *len > end)
{
return false;
}
*str = (char *)pos;
(*index)++; (*index)++;
has_next = true; return true;
}
return has_next;
} }

View File

@@ -1,29 +1,30 @@
# List the source files to be compiled # components/meter_manager/CMakeLists.txt
set(srcs set(srcs
"driver/meter_ade7758/meter_ade7758.c" driver/meter_ade7758/meter_ade7758.c
"driver/meter_ade7758/ade7758.c" driver/meter_ade7758/ade7758.c
"driver/meter_orno/meter_orno513.c" driver/meter_orno/meter_orno513.c
"driver/meter_orno/meter_orno526.c" driver/meter_orno/meter_orno526.c
"driver/meter_orno/meter_orno516.c" driver/meter_orno/meter_orno516.c
"driver/meter_orno/meter_dts6619.c" driver/meter_orno/meter_dts6619.c
"driver/meter_orno/meter_dds661.c" driver/meter_orno/meter_dds661.c
"driver/meter_orno/meter_ea777.c" driver/meter_orno/meter_ea777.c
"driver/meter_orno/modbus_params.c" driver/meter_orno/modbus_params.c
"driver/meter_zigbee/meter_zigbee.c" driver/meter_zigbee/meter_zigbee.c
"src/meter_manager.c" src/meter_manager.c
"src/meter_events.c" src/meter_events.c
) )
# List the include directories
set(includes set(includes
"include" include
"driver/meter_ade7758" driver/meter_ade7758
"driver/meter_orno" driver/meter_orno
"driver/meter_zigbee" driver/meter_zigbee
) )
# Register the component with the ESP-IDF build system idf_component_register(
idf_component_register(SRCS "${srcs}" SRCS ${srcs}
INCLUDE_DIRS "${includes}" INCLUDE_DIRS ${includes}
PRIV_REQUIRES nvs_flash REQUIRES esp_event
REQUIRES esp_event esp-modbus spi_bus_manager network) PRIV_REQUIRES esp-modbus spi_bus_manager storage_service network
)

View File

@@ -53,7 +53,7 @@ static void meter_ade7758_post_event(const meter_ade7758_internal_data_t *data)
memcpy(evt.irms, data->irms, sizeof(evt.irms)); memcpy(evt.irms, data->irms, sizeof(evt.irms));
memcpy(evt.watt, data->watt, sizeof(evt.watt)); memcpy(evt.watt, data->watt, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10)); esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
} }

View File

@@ -197,7 +197,7 @@ static void serial_mdb_task(void *param)
memcpy(evt.irms, current, sizeof(evt.irms)); memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt)); memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10)); esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL); vTaskDelay(UPDATE_INTERVAL);
} }
} }

View File

@@ -138,7 +138,7 @@ static void meter_dts6619_post_event(float *voltage, float *current, int *power_
memcpy(evt.watt, power_w, sizeof(evt.watt)); memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10)); &evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -1,35 +1,32 @@
#ifndef METER_EA777_H_ #ifndef METER_DTS6619_H_
#define METER_EA777_H_ #define METER_DTS6619_H_
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "esp_err.h" #include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/** /**
* @brief Inicializa o driver do medidor EA777 (UART RS485, Modbus, registradores). * @brief Inicializa o driver do medidor dts6619 (SPI, mutex, registradores).
* *
* @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro. * @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro.
*/ */
esp_err_t meter_ea777_init(void); esp_err_t meter_dts6619_init(void);
/** /**
* @brief Inicia a tarefa de leitura de dados do medidor EA777. * @brief Inicia a tarefa de leitura de dados do medidor DTS6619.
* *
* @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro. * @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro.
*/ */
esp_err_t meter_ea777_start(void); esp_err_t meter_dts6619_start(void);
/** /**
* @brief Para a tarefa de leitura e limpa os dados internos do medidor EA777. * @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS6619.
*/ */
void meter_ea777_stop(void); void meter_dts6619_stop(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif /* METER_EA777_H_ */ #endif /* METER_DTS6619_H_ */

View File

@@ -7,6 +7,7 @@
#include "driver/uart.h" #include "driver/uart.h"
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include "meter_ea777.h"
#define TAG "serial_mdb_ea777" #define TAG "serial_mdb_ea777"
@@ -148,7 +149,7 @@ static void meter_ea777_post_event(float *voltage, float *current, int *power_w,
memcpy(evt.watt, power_w, sizeof(evt.watt)); memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10)); &evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -1,32 +1,35 @@
#ifndef METER_DTS6619_H_ #ifndef METER_EA777_H_
#define METER_DTS6619_H_ #define METER_EA777_H_
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "esp_err.h" #include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/** /**
* @brief Inicializa o driver do medidor dts6619 (SPI, mutex, registradores). * @brief Inicializa o driver do medidor EA777 (UART RS485, Modbus, registradores).
* *
* @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro. * @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro.
*/ */
esp_err_t meter_dts6619_init(void); esp_err_t meter_ea777_init(void);
/** /**
* @brief Inicia a tarefa de leitura de dados do medidor DTS6619. * @brief Inicia a tarefa de leitura de dados do medidor EA777.
* *
* @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro. * @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro.
*/ */
esp_err_t meter_dts6619_start(void); esp_err_t meter_ea777_start(void);
/** /**
* @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS6619. * @brief Para a tarefa de leitura e limpa os dados internos do medidor EA777.
*/ */
void meter_dts6619_stop(void); void meter_ea777_stop(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif /* METER_DTS6619_H_ */ #endif /* METER_EA777_H_ */

View File

@@ -8,11 +8,12 @@
#define TAG "serial_mdb_orno513" #define TAG "serial_mdb_orno513"
#define MB_PORT_NUM 2 #define MB_PORT_NUM 2
#define MB_DEV_SPEED 9600 #define MB_DEV_SPEED 9600
#define MB_UART_TXD 17 #define MB_UART_TXD 17
#define MB_UART_RXD 16 #define MB_UART_RXD 16
#define MB_UART_RTS 5 #define MB_UART_RTS 2
#define UPDATE_INTERVAL (3000 / portTICK_PERIOD_MS) #define UPDATE_INTERVAL (3000 / portTICK_PERIOD_MS)
#define POLL_INTERVAL (100 / portTICK_PERIOD_MS) #define POLL_INTERVAL (100 / portTICK_PERIOD_MS)
@@ -129,7 +130,7 @@ static void serial_mdb_task(void *param) {
memcpy(evt.irms, current, sizeof(evt.irms)); memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt)); memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10)); esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL); vTaskDelay(UPDATE_INTERVAL);

View File

@@ -91,7 +91,7 @@ static void meter_orno516_post_event(float *voltage, float *current, int *power)
memcpy(evt.watt, power, sizeof(evt.watt)); memcpy(evt.watt, power, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10)); &evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -207,7 +207,7 @@ static void serial_mdb_task(void *param)
memcpy(evt.irms, current, sizeof(evt.irms)); memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt)); memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10)); esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL); vTaskDelay(UPDATE_INTERVAL);
} }
} }

View File

@@ -12,7 +12,7 @@
#define TAG "meter_zigbee" #define TAG "meter_zigbee"
// UART config // UART config
#define UART_PORT UART_NUM_1 #define UART_PORT UART_NUM_2
#define TXD_PIN GPIO_NUM_17 #define TXD_PIN GPIO_NUM_17
#define RXD_PIN GPIO_NUM_16 #define RXD_PIN GPIO_NUM_16
#define UART_BUF_SIZE 128 #define UART_BUF_SIZE 128
@@ -85,7 +85,7 @@ static void meter_zigbee_post_event(void) {
METER_EVENT_DATA_READY, METER_EVENT_DATA_READY,
&evt, &evt,
sizeof(evt), sizeof(evt),
pdMS_TO_TICKS(10)); portMAX_DELAY);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
@@ -94,8 +94,8 @@ static void meter_zigbee_post_event(void) {
static void handle_zigbee_frame(const uint8_t *buf, size_t len) { static void handle_zigbee_frame(const uint8_t *buf, size_t len) {
ESP_LOGI(TAG, "Received UART frame (%d bytes):", len); ESP_LOGD(TAG, "Received UART frame (%d bytes):", len);
ESP_LOG_BUFFER_HEX(TAG, buf, len); //ESP_LOG_BUFFER_HEX(TAG, buf, len);
if (len < RX_FRAME_SIZE) { if (len < RX_FRAME_SIZE) {
ESP_LOGW(TAG, "Invalid frame: too short (len = %d)", len); ESP_LOGW(TAG, "Invalid frame: too short (len = %d)", len);
@@ -118,7 +118,7 @@ static void handle_zigbee_frame(const uint8_t *buf, size_t len) {
float current = current_raw / 1000.0f; float current = current_raw / 1000.0f;
float power = power_raw; float power = power_raw;
ESP_LOGI(TAG, "Parsed Attr 0x%04X: V=%.1fV I=%.2fA P=%.1fW", attr, volt, current, power); ESP_LOGD(TAG, "Parsed Attr 0x%04X: V=%.1fV I=%.2fA P=%.1fW", attr, volt, current, power);
if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) { if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
switch (attr) { switch (attr) {
@@ -224,13 +224,8 @@ esp_err_t meter_zigbee_start(void) {
void meter_zigbee_stop(void) { void meter_zigbee_stop(void) {
//send_stop_command();
//vTaskDelay(pdMS_TO_TICKS(100)); // Aguarda o outro lado processar
if (meter_zigbee_task) { if (meter_zigbee_task) {
vTaskDelete(meter_zigbee_task); vTaskDelete(meter_zigbee_task);
meter_zigbee_task = NULL; meter_zigbee_task = NULL;

View File

@@ -3,6 +3,7 @@
#include "esp_event.h" #include "esp_event.h"
#include "meter_manager.h" // Para meter_type_t #include "meter_manager.h" // Para meter_type_t
#include <stdint.h> // Para int64_t
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -16,7 +17,8 @@ typedef enum {
METER_EVENT_DATA_READY = 0, METER_EVENT_DATA_READY = 0,
METER_EVENT_ERROR, METER_EVENT_ERROR,
METER_EVENT_STARTED, METER_EVENT_STARTED,
METER_EVENT_STOPPED METER_EVENT_STOPPED,
METER_EVENT_CONFIG_UPDATED // Novo: configuração (grid/evse) atualizada
} meter_event_id_t; } meter_event_id_t;
// Estrutura de dados enviados com METER_EVENT_DATA_READY // Estrutura de dados enviados com METER_EVENT_DATA_READY
@@ -30,6 +32,12 @@ typedef struct {
float total_energy; // Energia acumulada (kWh) float total_energy; // Energia acumulada (kWh)
} meter_event_data_t; } meter_event_data_t;
// Estrutura de dados enviados com METER_EVENT_CONFIG_UPDATED
typedef struct {
meter_type_t grid_type; // Tipo de contador configurado para o GRID
meter_type_t evse_type; // Tipo de contador configurado para a EVSE
int64_t timestamp_us; // Momento da atualização (esp_timer_get_time)
} meter_config_event_t;
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -10,10 +10,15 @@
#include "meter_zigbee.h" #include "meter_zigbee.h"
#include "meter_ea777.h" #include "meter_ea777.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.h> #include <string.h>
#include "network_events.h" #include "network_events.h"
#include "meter_events.h"
#include "esp_event.h"
#include "esp_timer.h"
// NEW:
#include "storage_service.h"
static const char *TAG = "meter_manager"; static const char *TAG = "meter_manager";
@@ -21,179 +26,200 @@ static const char *TAG = "meter_manager";
static meter_type_t meter_evse_type = METER_TYPE_NONE; static meter_type_t meter_evse_type = METER_TYPE_NONE;
static meter_type_t meter_grid_type = METER_TYPE_NONE; static meter_type_t meter_grid_type = METER_TYPE_NONE;
#define NVS_NAMESPACE "meterconfig" #define STORE_NAMESPACE "meterconfig"
#define NVS_EVSE_MODEL "evse_model" #define STORE_EVSE_MODEL "evse_model"
#define NVS_GRID_MODEL "grid_model" #define STORE_GRID_MODEL "grid_model"
// timeouts storage
#define STORAGE_TO pdMS_TO_TICKS(800)
// ------------------------------------------------------------------
// Helpers storage (robustos a queue cheia)
// ------------------------------------------------------------------
static esp_err_t storage_try_flush(void)
{
return storage_flush_sync(pdMS_TO_TICKS(2000));
}
static esp_err_t storage_try_set_u8(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t err = storage_set_u8_async(ns, key, v);
if (err == ESP_OK)
return ESP_OK;
if (err == ESP_ERR_TIMEOUT)
{
(void)storage_try_flush();
continue;
}
return err;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t storage_get_u8_default(const char *ns, const char *key, uint8_t *out, uint8_t def)
{
if (!out)
return ESP_ERR_INVALID_ARG;
uint8_t v = def;
esp_err_t err = storage_get_u8_sync(ns, key, &v, STORAGE_TO);
if (err == ESP_OK)
{
*out = v;
return ESP_OK;
}
// se não existir / erro, devolve default
*out = def;
return err;
}
// ------------------------------------------------------------------
/*
static void meter_manager_network_event_handler(void *arg, esp_event_base_t base, int32_t event_id, void *data) static void meter_manager_network_event_handler(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{ {
(void)arg;
(void)data;
if (base != NETWORK_EVENTS) if (base != NETWORK_EVENTS)
return; return;
switch (event_id) switch (event_id)
{ {
case NETWORK_EVENT_AP_STARTED: case NETWORK_EVENT_AP_STARTED:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid"); ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid");
// meter_manager_grid_stop();
break; break;
case NETWORK_EVENT_AP_STOP: case NETWORK_EVENT_AP_STOP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid"); ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid");
// meter_manager_grid_start();
break; break;
case NETWORK_EVENT_STA_GOT_IP: case NETWORK_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP"); ESP_LOGD(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP");
// opcional: reiniciar ou logar
break; break;
default: default:
break; break;
} }
} }
*/
// Função unificada para ler ou inicializar um modelo de medidor // Função unificada para ler ou inicializar um modelo de medidor (via storage_service)
static esp_err_t load_or_init_meter_model(const char *key, meter_type_t *type) static esp_err_t load_or_init_meter_model(const char *key, meter_type_t *type)
{ {
nvs_handle_t handle; if (!type)
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); return ESP_ERR_INVALID_ARG;
if (err != ESP_OK)
{ // tenta ler
ESP_LOGE(TAG, "Failed to open NVS handle for %s: %s", key, esp_err_to_name(err)); uint8_t value = 0xFF;
return err; esp_err_t err = storage_get_u8_sync(STORE_NAMESPACE, key, &value, STORAGE_TO);
}
uint8_t value = 0;
err = nvs_get_u8(handle, key, &value);
if (err == ESP_OK && value < 255) if (err == ESP_OK && value < 255)
{ {
*type = (meter_type_t)value; *type = (meter_type_t)value;
ESP_LOGI(TAG, "Loaded meter type %d from NVS key '%s'", value, key); ESP_LOGD(TAG, "Loaded meter type %u from storage key '%s/%s'", (unsigned)value, STORE_NAMESPACE, key);
}
else
{
*type = METER_TYPE_NONE;
nvs_set_u8(handle, key, *type);
nvs_commit(handle);
ESP_LOGW(TAG, "Invalid or missing key '%s', setting default (NONE)", key);
}
nvs_close(handle);
return ESP_OK; return ESP_OK;
} }
static esp_err_t write_meter_model_to_nvs(const char *key, meter_type_t meter_type) // se não existir / inválido -> default NONE e grava
*type = METER_TYPE_NONE;
esp_err_t w = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)(*type));
if (w != ESP_OK)
ESP_LOGE(TAG, "Failed to init key '%s/%s' to NONE: %s", STORE_NAMESPACE, key, esp_err_to_name(w));
(void)storage_try_flush();
ESP_LOGW(TAG, "Invalid/missing key '%s/%s' (read=%s), setting default (NONE)",
STORE_NAMESPACE, key, esp_err_to_name(err));
return ESP_OK; // seguimos com default
}
static esp_err_t write_meter_model_to_storage(const char *key, meter_type_t meter_type)
{ {
nvs_handle_t handle; esp_err_t err = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)meter_type);
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGE(TAG, "Failed to open NVS handle for writing"); ESP_LOGE(TAG, "Failed to write meter type to storage key '%s/%s': %s",
STORE_NAMESPACE, key, esp_err_to_name(err));
return err; return err;
} }
err = nvs_set_u8(handle, key, (uint8_t)meter_type); (void)storage_try_flush();
if (err == ESP_OK) ESP_LOGD(TAG, "Saved meter type %d to storage key '%s/%s'", (int)meter_type, STORE_NAMESPACE, key);
{ return ESP_OK;
err = nvs_commit(handle);
ESP_LOGI(TAG, "Saved meter type %d to NVS key '%s'", meter_type, key);
}
else
{
ESP_LOGE(TAG, "Failed to write meter type to NVS key '%s'", key);
}
nvs_close(handle);
return err;
} }
/** /**
* @brief Initializes the meter manager system. * @brief Inicializa o sistema de meter manager.
*
* This function initializes both the EVSE and GRID meters,
* and registers the event handler to listen for NETWORK_EVENTS
* (e.g., AP started/stopped, STA got IP).
*
* @return esp_err_t ESP_OK on success, or an error code.
*/ */
esp_err_t meter_manager_init(void) esp_err_t meter_manager_init(void)
{ {
esp_err_t err; // garantir storage pronto
esp_err_t s = storage_service_init();
if (s != ESP_OK)
ESP_LOGE(TAG, "storage_service_init failed: %s", esp_err_to_name(s));
// Initialize EVSE meter (habilite quando quiser) esp_err_t err;
// err = meter_manager_evse_init();
// if (err != ESP_OK) return err;
// Initialize GRID meter // Initialize GRID meter
err = meter_manager_grid_init(); err = meter_manager_grid_init();
if (err != ESP_OK) if (err != ESP_OK)
return err; return err;
// Register handler for custom network events // Regista handler para eventos de rede
ESP_LOGI(TAG, "Registering network event handler"); /*
return esp_event_handler_register(NETWORK_EVENTS, ESP_LOGD(TAG, "Registering network event handler");
err = esp_event_handler_register(
NETWORK_EVENTS,
ESP_EVENT_ANY_ID, ESP_EVENT_ANY_ID,
meter_manager_network_event_handler, meter_manager_network_event_handler,
NULL); NULL);
*/
if (err != ESP_OK)
return err;
// Emite um evento inicial de configuração
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_event_post(METER_EVENT,
METER_EVENT_CONFIG_UPDATED,
&ev,
sizeof(ev),
0);
return ESP_OK;
} }
/**
* @brief Starts all configured meters (EVSE and GRID).
*
* This function starts the EVSE and GRID meters based on their configured types.
* It does not register event handlers — that is handled by `meter_manager_init()`.
*
* @return esp_err_t ESP_OK on success, or an error code from one of the start calls.
*/
esp_err_t meter_manager_start(void) esp_err_t meter_manager_start(void)
{ {
esp_err_t err;
// Start EVSE meter (habilite quando quiser)
// err = meter_manager_evse_start();
// if (err != ESP_OK) return err;
// Start GRID meter // Start GRID meter
err = meter_manager_grid_start(); return meter_manager_grid_start();
if (err != ESP_OK)
return err;
return ESP_OK;
} }
/**
* @brief Stops all meters and unregisters event handlers.
*
* This function gracefully stops the EVSE and GRID meters
* and unregisters the previously registered network event handler.
*
* @return esp_err_t ESP_OK on success, or an error code.
*/
esp_err_t meter_manager_stop(void) esp_err_t meter_manager_stop(void)
{ {
esp_err_t err;
// Stop EVSE meter
// err = meter_manager_evse_stop();
// if (err != ESP_OK) return err;
// Stop GRID meter // Stop GRID meter
err = meter_manager_grid_stop(); return meter_manager_grid_stop();
if (err != ESP_OK)
return err;
return ESP_OK;
} }
// ---------- EVSE ---------- // ---------- EVSE ----------
esp_err_t meter_manager_evse_init() esp_err_t meter_manager_evse_init()
{ {
esp_err_t err = load_or_init_meter_model(NVS_EVSE_MODEL, &meter_evse_type); esp_err_t err = load_or_init_meter_model(STORE_EVSE_MODEL, &meter_evse_type);
if (err != ESP_OK) if (err != ESP_OK)
return err; return err;
ESP_LOGI(TAG, "Initializing EVSE meter of type %s", meter_type_to_str(meter_evse_type)); ESP_LOGD(TAG, "Initializing EVSE meter of type %s", meter_type_to_str(meter_evse_type));
switch (meter_evse_type) switch (meter_evse_type)
{ {
@@ -292,13 +318,13 @@ esp_err_t meter_manager_evse_stop(void)
esp_err_t meter_manager_grid_init() esp_err_t meter_manager_grid_init()
{ {
esp_err_t err = load_or_init_meter_model(NVS_GRID_MODEL, &meter_grid_type); esp_err_t err = load_or_init_meter_model(STORE_GRID_MODEL, &meter_grid_type);
if (err != ESP_OK) if (err != ESP_OK)
return err; return err;
ESP_LOGI(TAG, "Initializing GRID meter of type %s", meter_type_to_str(meter_grid_type)); ESP_LOGD(TAG, "Initializing GRID meter of type %s", meter_type_to_str(meter_grid_type));
switch (meter_grid_type) // corrigido: ORNO-513 -> driver orno513 switch (meter_grid_type)
{ {
case METER_TYPE_NONE: case METER_TYPE_NONE:
return ESP_OK; return ESP_OK;
@@ -334,7 +360,7 @@ esp_err_t meter_manager_grid_start()
case METER_TYPE_ADE7758: case METER_TYPE_ADE7758:
return meter_ade7758_start(); return meter_ade7758_start();
case METER_TYPE_ORNO513: case METER_TYPE_ORNO513:
return meter_orno513_start(); // corrigido return meter_orno513_start();
case METER_TYPE_ORNO516: case METER_TYPE_ORNO516:
return meter_orno516_start(); return meter_orno516_start();
case METER_TYPE_ORNO526: case METER_TYPE_ORNO526:
@@ -364,7 +390,7 @@ esp_err_t meter_manager_grid_stop(void)
meter_ade7758_stop(); meter_ade7758_stop();
break; break;
case METER_TYPE_ORNO513: case METER_TYPE_ORNO513:
meter_orno513_stop(); // corrigido meter_orno513_stop();
break; break;
case METER_TYPE_ORNO516: case METER_TYPE_ORNO516:
meter_orno516_stop(); meter_orno516_stop();
@@ -391,18 +417,64 @@ esp_err_t meter_manager_grid_stop(void)
return ESP_OK; return ESP_OK;
} }
// ---------- Utilidades ---------- // ---------- Utilidades / setters com evento ----------
esp_err_t meter_manager_evse_set_model(meter_type_t meter_type) esp_err_t meter_manager_evse_set_model(meter_type_t meter_type)
{ {
esp_err_t err;
err = meter_manager_evse_stop();
if (err != ESP_OK)
ESP_LOGW(TAG, "Failed to stop EVSE meter before changing model: %s", esp_err_to_name(err));
meter_evse_type = meter_type; meter_evse_type = meter_type;
return write_meter_model_to_nvs(NVS_EVSE_MODEL, meter_evse_type);
err = write_meter_model_to_storage(STORE_EVSE_MODEL, meter_evse_type);
if (err != ESP_OK)
return err;
err = meter_manager_evse_init();
if (err == ESP_OK)
err = meter_manager_evse_start();
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_err_t evt_err = esp_event_post(METER_EVENT, METER_EVENT_CONFIG_UPDATED, &ev, sizeof(ev), portMAX_DELAY);
if (evt_err != ESP_OK)
ESP_LOGW(TAG, "Failed to post METER_EVENT_CONFIG_UPDATED (EVSE): %s", esp_err_to_name(evt_err));
return err;
} }
esp_err_t meter_manager_grid_set_model(meter_type_t meter_type) esp_err_t meter_manager_grid_set_model(meter_type_t meter_type)
{ {
esp_err_t err;
err = meter_manager_grid_stop();
if (err != ESP_OK)
ESP_LOGW(TAG, "Failed to stop GRID meter before changing model: %s", esp_err_to_name(err));
meter_grid_type = meter_type; meter_grid_type = meter_type;
return write_meter_model_to_nvs(NVS_GRID_MODEL, meter_grid_type);
err = write_meter_model_to_storage(STORE_GRID_MODEL, meter_grid_type);
if (err != ESP_OK)
return err;
err = meter_manager_grid_init();
if (err == ESP_OK)
err = meter_manager_grid_start();
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_err_t evt_err = esp_event_post(METER_EVENT, METER_EVENT_CONFIG_UPDATED, &ev, sizeof(ev), portMAX_DELAY);
if (evt_err != ESP_OK)
ESP_LOGW(TAG, "Failed to post METER_EVENT_CONFIG_UPDATED (GRID): %s", esp_err_to_name(evt_err));
return err;
} }
bool meter_manager_evse_is_enabled(void) bool meter_manager_evse_is_enabled(void)
@@ -410,15 +482,8 @@ bool meter_manager_evse_is_enabled(void)
return meter_manager_evse_get_model() != METER_TYPE_NONE; return meter_manager_evse_get_model() != METER_TYPE_NONE;
} }
meter_type_t meter_manager_evse_get_model(void) meter_type_t meter_manager_evse_get_model(void) { return meter_evse_type; }
{ meter_type_t meter_manager_grid_get_model(void) { return meter_grid_type; }
return meter_evse_type;
}
meter_type_t meter_manager_grid_get_model(void)
{
return meter_grid_type;
}
const char *meter_type_to_str(meter_type_t type) const char *meter_type_to_str(meter_type_t type)
{ {
@@ -453,6 +518,7 @@ meter_type_t string_to_meter_type(const char *str)
{ {
if (!str) if (!str)
return METER_TYPE_NONE; return METER_TYPE_NONE;
if (strcmp(str, "IC ADE") == 0) if (strcmp(str, "IC ADE") == 0)
return METER_TYPE_ADE7758; return METER_TYPE_ADE7758;
if (strcmp(str, "ORNO-513") == 0) if (strcmp(str, "ORNO-513") == 0)
@@ -471,5 +537,6 @@ meter_type_t string_to_meter_type(const char *str)
return METER_TYPE_TRIF_ZIGBEE; return METER_TYPE_TRIF_ZIGBEE;
if (strcmp(str, "EA-777") == 0) if (strcmp(str, "EA-777") == 0)
return METER_TYPE_EA777; return METER_TYPE_EA777;
return METER_TYPE_NONE; return METER_TYPE_NONE;
} }

View File

@@ -5,4 +5,4 @@ set(srcs
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash esp_netif esp_wifi mdns esp_event) PRIV_REQUIRES nvs_flash esp_netif esp_wifi storage_service mdns esp_event)

View File

@@ -18,7 +18,7 @@
typedef struct typedef struct
{ {
char ssid[32]; char ssid[33];
int rssi; int rssi;
bool auth; bool auth;
} wifi_scan_ap_t; } wifi_scan_ap_t;

View File

@@ -11,22 +11,24 @@
#include "freertos/event_groups.h" #include "freertos/event_groups.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "esp_mac.h" #include "esp_mac.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "mdns.h" #include "mdns.h"
#include "network_events.h" #include "network_events.h"
#include "network.h" #include "network.h"
// NEW:
#include "storage_service.h"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Config // Config
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC) #define AP_SSID "plx-%02x%02x%02x"
#define MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC) #define MDNS_SSID "plx%02x"
#define NVS_NAMESPACE "wifi" #define NVS_NAMESPACE "wifi"
#define NVS_ENABLED "enabled" #define NVS_ENABLED "enabled"
@@ -35,60 +37,106 @@
// Comprimentos com terminador // Comprimentos com terminador
#define SSID_MAX_LEN 32 #define SSID_MAX_LEN 32
#define PASS_MAX_LEN 64 // 63 chars + '\0' #define PASS_MAX_LEN 64
#define SSID_BUF_SZ (SSID_MAX_LEN + 1) // 33 #define SSID_BUF_SZ (SSID_MAX_LEN + 1)
#define PASS_BUF_SZ (PASS_MAX_LEN + 1) // 65 #define PASS_BUF_SZ (PASS_MAX_LEN + 1)
static const char *TAG = "wifi"; static const char *TAG = "wifi";
// Storage timeouts
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Estado global // Estado global
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static nvs_handle_t nvs;
static esp_netif_t *sta_netif; static esp_netif_t *sta_netif;
static esp_netif_t *ap_netif; static esp_netif_t *ap_netif;
EventGroupHandle_t wifi_event_group; EventGroupHandle_t wifi_event_group;
// Backoff simples para reconexão STA (agora sem delay no event handler) // Backoff simples para reconexão STA
static int s_retry_count = 0; static int s_retry_count = 0;
static const int s_retry_max = 7; static const int s_retry_max = 7;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Helpers // Helpers storage (robustos)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Lê string do NVS com segurança (trunca se necessário) static esp_err_t store_flush_best_effort(void)
static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, size_t out_sz) {
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_str_async(ns, key, s);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// Lê string de forma segura (lê para buffer grande e depois trunca para out)
// Nota: isto ajuda se houver lixo antigo no NVS com strings maiores do que o esperado.
static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz)
{ {
if (!out || out_sz == 0) if (!out || out_sz == 0)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
out[0] = '\0'; out[0] = '\0';
size_t need = 0; // buffer grande (sem heap): usa o máximo definido no storage_service
esp_err_t err = nvs_get_str(h, key, NULL, &need); char tmp[STORAGE_MAX_VALUE_BYTES + 1];
if (err == ESP_ERR_NVS_NOT_FOUND) memset(tmp, 0, sizeof(tmp));
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
{
out[0] = '\0';
return ESP_OK; return ESP_OK;
if (err != ESP_OK)
return err;
if (need == 0)
return ESP_OK; // vazio
if (need > out_sz)
{
// Truncar de forma segura
char *tmp = (char *)malloc(need);
if (!tmp)
return ESP_ERR_NO_MEM;
err = nvs_get_str(h, key, tmp, &need);
if (err == ESP_OK)
{
snprintf(out, out_sz, "%s", tmp);
} }
free(tmp); if (e != ESP_OK)
return err; {
out[0] = '\0';
return e;
} }
return nvs_get_str(h, key, out, &need);
size_t n = strnlen(tmp, out_sz - 1); // no máximo out_sz-1
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -96,6 +144,8 @@ static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, si
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{ {
(void)arg;
if (event_base == WIFI_EVENT) if (event_base == WIFI_EVENT)
{ {
switch (event_id) switch (event_id)
@@ -125,6 +175,16 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
esp_wifi_connect(); esp_wifi_connect();
break; break;
} }
case WIFI_EVENT_STA_CONNECTED:
{
ESP_LOGI(TAG, "STA associated (L2 connected)");
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_CONNECTED,
NULL,
0,
portMAX_DELAY);
break;
}
case WIFI_EVENT_STA_DISCONNECTED: case WIFI_EVENT_STA_DISCONNECTED:
{ {
xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
@@ -133,7 +193,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data; wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1); ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1);
// NÃO bloquear o event loop com vTaskDelay esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_DISCONNECTED,
ev,
ev ? sizeof(*ev) : 0,
portMAX_DELAY);
if (s_retry_count < s_retry_max) if (s_retry_count < s_retry_max)
{ {
esp_err_t err = esp_wifi_connect(); esp_err_t err = esp_wifi_connect();
@@ -144,7 +209,7 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
} }
else else
{ {
ESP_LOGW(TAG, "esp_wifi_connect failed (%d)", err); ESP_LOGW(TAG, "esp_wifi_connect failed (%s)", esp_err_to_name(err));
} }
} }
else else
@@ -166,14 +231,21 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0; s_retry_count = 0;
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_GOT_IP,
&event->ip_info,
sizeof(event->ip_info),
portMAX_DELAY);
} }
else if (event_id == IP_EVENT_GOT_IP6) else if (event_id == IP_EVENT_STA_LOST_IP)
{ {
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; ESP_LOGW(TAG, "STA lost IP");
ESP_LOGI(TAG, "WiFi STA got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip)); esp_event_post(NETWORK_EVENTS,
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); NETWORK_EVENT_STA_LOST_IP,
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); NULL,
s_retry_count = 0; 0,
portMAX_DELAY);
} }
} }
} }
@@ -192,28 +264,21 @@ static void sta_set_config(void)
wifi_config.sta.pmf_cfg.capable = true; wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false; wifi_config.sta.pmf_cfg.required = false;
// buffers "seguros" vindos da NVS (estes sim têm terminador) char ssid_buf[SSID_BUF_SZ] = {0};
char ssid_buf[SSID_BUF_SZ] = {0}; // 33 (32 + '\0') char pass_buf[PASS_BUF_SZ] = {0};
char pass_buf[PASS_BUF_SZ] = {0}; // 65 (64 + '\0')
wifi_get_ssid(ssid_buf); wifi_get_ssid(ssid_buf);
wifi_get_password(pass_buf); wifi_get_password(pass_buf);
// Copiar **sem** terminador para os campos do esp_wifi (32/64 bytes) size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN);
// SSID: max 32
size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN); // até 32
memcpy(wifi_config.sta.ssid, ssid_buf, ssid_len); memcpy(wifi_config.sta.ssid, ssid_buf, ssid_len);
// Password WPA/WPA2: 8..63 chars (campo tem 64 bytes) size_t pass_len = strnlen(pass_buf, 63);
// (se usares rede aberta, pass_len pode ser 0)
size_t pass_len = strnlen(pass_buf, 63); // até 63
memcpy(wifi_config.sta.password, pass_buf, pass_len); memcpy(wifi_config.sta.password, pass_buf, pass_len);
if (pass_len <= 63) if (pass_len <= 63)
{
wifi_config.sta.password[pass_len] = '\0'; wifi_config.sta.password[pass_len] = '\0';
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // v5.x ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
} }
static void ap_set_config(void) static void ap_set_config(void)
@@ -222,14 +287,13 @@ static void ap_set_config(void)
wifi_config_t wifi_ap_config = {0}; wifi_config_t wifi_ap_config = {0};
wifi_ap_config.ap.max_connection = 1; wifi_ap_config.ap.max_connection = 1;
wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN; // para portal cativo, por exemplo wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN;
uint8_t mac[6]; uint8_t mac[6];
esp_wifi_get_mac(WIFI_IF_AP, mac); esp_wifi_get_mac(WIFI_IF_AP, mac);
snprintf((char *)wifi_ap_config.ap.ssid, sizeof(wifi_ap_config.ap.ssid), snprintf((char *)wifi_ap_config.ap.ssid, sizeof(wifi_ap_config.ap.ssid),
AP_SSID, mac[3], mac[4], mac[5]); // "plx-XXXXXX" AP_SSID, mac[3], mac[4], mac[5]);
// Só AP (não mexer na config STA aqui para não a limpar)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
} }
@@ -248,7 +312,7 @@ static void sta_try_start(void)
esp_err_t e = esp_wifi_start(); esp_err_t e = esp_wifi_start();
if (e != ESP_OK && e != ESP_ERR_WIFI_CONN) if (e != ESP_OK && e != ESP_ERR_WIFI_CONN)
{ {
ESP_LOGW(TAG, "esp_wifi_start returned %d", e); ESP_LOGW(TAG, "esp_wifi_start returned %s", esp_err_to_name(e));
} }
xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT);
} }
@@ -261,8 +325,10 @@ void wifi_ini(void)
{ {
ESP_LOGI(TAG, "Wifi init"); ESP_LOGI(TAG, "Wifi init");
// Abre NVS (assume que nvs_flash_init() já foi chamado no boot geral) // garante storage pronto (não assume NVS handle aberto aqui)
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); esp_err_t se = storage_service_init();
if (se != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(se));
wifi_event_group = xEventGroupCreate(); wifi_event_group = xEventGroupCreate();
@@ -275,14 +341,11 @@ void wifi_ini(void)
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// mDNS: usa dois bytes do MAC para identificar
uint8_t mac[6]; uint8_t mac[6];
esp_wifi_get_mac(WIFI_IF_STA, mac); esp_wifi_get_mac(WIFI_IF_STA, mac);
char chargeid[16]; char chargeid[16];
// ex.: "plx00"
snprintf(chargeid, sizeof(chargeid), MDNS_SSID, 0); snprintf(chargeid, sizeof(chargeid), MDNS_SSID, 0);
// Hostname das interfaces alinhado com o mDNS
ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, chargeid)); ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, chargeid));
ESP_ERROR_CHECK(esp_netif_set_hostname(ap_netif, chargeid)); ESP_ERROR_CHECK(esp_netif_set_hostname(ap_netif, chargeid));
@@ -300,10 +363,9 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
{ {
ESP_LOGI(TAG, "wifi_set_config(enabled=%d)", enabled); ESP_LOGI(TAG, "wifi_set_config(enabled=%d)", enabled);
// Validação (quando habilitar STA) // Validação
if (enabled) if (enabled)
{ {
// SSID 1..32
if (ssid && (strlen(ssid) == 0 || strlen(ssid) > SSID_MAX_LEN)) if (ssid && (strlen(ssid) == 0 || strlen(ssid) > SSID_MAX_LEN))
{ {
ESP_LOGE(TAG, "SSID out of range"); ESP_LOGE(TAG, "SSID out of range");
@@ -311,15 +373,15 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
} }
if (!ssid) if (!ssid)
{ {
size_t len = 0; char cur_ssid[SSID_BUF_SZ] = {0};
esp_err_t e = nvs_get_str(nvs, NVS_SSID, NULL, &len); wifi_get_ssid(cur_ssid);
if (e != ESP_OK || len <= 1) if (strlen(cur_ssid) == 0)
{ {
ESP_LOGE(TAG, "Required SSID"); ESP_LOGE(TAG, "Required SSID");
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
} }
// Password: 8..63 (se não for vazia). Aceita "" para rede open (caso uses).
if (password) if (password)
{ {
size_t lp = strlen(password); size_t lp = strlen(password);
@@ -331,13 +393,27 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
} }
} }
// Persiste no NVS // Persiste via storage_service
ESP_ERROR_CHECK(nvs_set_u8(nvs, NVS_ENABLED, enabled)); esp_err_t err = store_set_u8_best_effort(NVS_NAMESPACE, NVS_ENABLED, enabled ? 1 : 0);
if (err != ESP_OK)
return err;
if (ssid) if (ssid)
ESP_ERROR_CHECK(nvs_set_str(nvs, NVS_SSID, ssid)); {
err = store_set_str_best_effort(NVS_NAMESPACE, NVS_SSID, ssid);
if (err != ESP_OK)
return err;
}
if (password) if (password)
ESP_ERROR_CHECK(nvs_set_str(nvs, NVS_PASSWORD, password)); {
ESP_ERROR_CHECK(nvs_commit(nvs)); err = store_set_str_best_effort(NVS_NAMESPACE, NVS_PASSWORD, password);
if (err != ESP_OK)
return err;
}
// Força persistência (para sobreviver a reboot imediato)
(void)store_flush_best_effort();
// Reinicia modo // Reinicia modo
ESP_LOGI(TAG, "Stopping AP/STA"); ESP_LOGI(TAG, "Stopping AP/STA");
@@ -345,7 +421,7 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
esp_err_t e = esp_wifi_stop(); esp_err_t e = esp_wifi_stop();
if (e != ESP_OK && e != ESP_ERR_WIFI_NOT_INIT && e != ESP_ERR_WIFI_STOP_STATE) if (e != ESP_OK && e != ESP_ERR_WIFI_NOT_INIT && e != ESP_ERR_WIFI_STOP_STATE)
{ {
ESP_LOGW(TAG, "esp_wifi_stop returned %d", e); ESP_LOGW(TAG, "esp_wifi_stop returned %s", esp_err_to_name(e));
} }
sta_try_start(); sta_try_start();
@@ -367,21 +443,21 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
esp_err_t err = esp_wifi_scan_start(NULL, true); esp_err_t err = esp_wifi_scan_start(NULL, true);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "esp_wifi_scan_start failed (%d)", err); ESP_LOGW(TAG, "esp_wifi_scan_start failed (%s)", esp_err_to_name(err));
return 0; return 0;
} }
err = esp_wifi_scan_get_ap_records(&number, ap_info); err = esp_wifi_scan_get_ap_records(&number, ap_info);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed (%d)", err); ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed (%s)", esp_err_to_name(err));
return 0; return 0;
} }
err = esp_wifi_scan_get_ap_num(&ap_count); err = esp_wifi_scan_get_ap_num(&ap_count);
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_num failed (%d)", err); ESP_LOGW(TAG, "esp_wifi_scan_get_ap_num failed (%s)", esp_err_to_name(err));
return 0; return 0;
} }
@@ -389,8 +465,7 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++) for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++)
{ {
// garante que scan_aps[i].ssid tenha pelo menos 33 bytes snprintf(scan_aps[i].ssid, sizeof(scan_aps[i].ssid), "%s", (const char *)ap_info[i].ssid);
snprintf(scan_aps[i].ssid, SSID_BUF_SZ, "%s", (const char *)ap_info[i].ssid);
scan_aps[i].rssi = ap_info[i].rssi; scan_aps[i].rssi = ap_info[i].rssi;
scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN; scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN;
} }
@@ -401,29 +476,29 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
bool wifi_get_enabled(void) bool wifi_get_enabled(void)
{ {
uint8_t value = 0; uint8_t value = 0;
esp_err_t e = nvs_get_u8(nvs, NVS_ENABLED, &value); esp_err_t e = storage_get_u8_sync(NVS_NAMESPACE, NVS_ENABLED, &value, STORE_TO);
if (e == ESP_ERR_NVS_NOT_FOUND) if (e == ESP_ERR_NOT_FOUND)
return false; return false;
if (e != ESP_OK) if (e != ESP_OK)
{ {
ESP_LOGW(TAG, "nvs_get_u8(NVS_ENABLED) failed (%d), assuming disabled", e); ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed (%s), assuming disabled", esp_err_to_name(e));
return false; return false;
} }
return value; return (value != 0);
} }
void wifi_get_ssid(char *value) void wifi_get_ssid(char *value)
{ {
if (!value) if (!value)
return; return;
nvs_get_str_safe(nvs, NVS_SSID, value, SSID_BUF_SZ); // 33 (void)store_get_str_safe(NVS_NAMESPACE, NVS_SSID, value, SSID_BUF_SZ);
} }
void wifi_get_password(char *value) void wifi_get_password(char *value)
{ {
if (!value) if (!value)
return; return;
nvs_get_str_safe(nvs, NVS_PASSWORD, value, PASS_BUF_SZ); // 65 (void)store_get_str_safe(NVS_NAMESPACE, NVS_PASSWORD, value, PASS_BUF_SZ);
} }
void wifi_ap_start(void) void wifi_ap_start(void)
@@ -439,7 +514,7 @@ void wifi_ap_start(void)
esp_err_t e = esp_wifi_start(); esp_err_t e = esp_wifi_start();
if (e != ESP_OK && e != ESP_ERR_WIFI_CONN) if (e != ESP_OK && e != ESP_ERR_WIFI_CONN)
{ {
ESP_LOGW(TAG, "esp_wifi_start (AP) returned %d", e); ESP_LOGW(TAG, "esp_wifi_start (AP) returned %s", esp_err_to_name(e));
} }
xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT);

View File

@@ -5,5 +5,4 @@ set(srcs
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash REQUIRES esp_event storage_service config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose)
REQUIRES esp_event config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose)

View File

@@ -1,32 +1,37 @@
// components/ocpp/src/ocpp.c // components/ocpp/src/ocpp.c
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
#include <math.h>
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_timer.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ocpp.h" #include "ocpp.h"
#include "ocpp_events.h" #include "ocpp_events.h"
#include "esp_wifi.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "evse_error.h" #include "evse_error.h"
#include "auth_events.h" #include "auth_events.h"
#include "evse_events.h" #include "evse_events.h"
#include "evse_state.h" #include "evse_state.h"
#include "meter_events.h" #include "meter_events.h"
#include "esp_timer.h"
#include <math.h>
/* MicroOcpp includes */ /* MicroOcpp includes */
#include <mongoose.h> #include <mongoose.h>
#include <MicroOcpp_c.h> // C-facade of MicroOcpp #include <MicroOcpp_c.h> // C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> // WebSocket integration for ESP-IDF #include <MicroOcppMongooseClient_c.h> // WebSocket integration for ESP-IDF
// NEW storage layer
#include "storage_service.h"
#define NVS_NAMESPACE "ocpp" #define NVS_NAMESPACE "ocpp"
#define NVS_OCPP_ENABLED "enabled" #define NVS_OCPP_ENABLED "enabled"
#define NVS_OCPP_SERVER "ocpp_server" #define NVS_OCPP_SERVER "ocpp_server"
@@ -59,19 +64,18 @@ static esp_event_handler_instance_t s_evse_available_inst = NULL;
// --- cache de medições vindas de METER_EVENT_DATA_READY --- // --- cache de medições vindas de METER_EVENT_DATA_READY ---
typedef struct typedef struct
{ {
// dados por fase
float vrms[3]; float vrms[3];
float irms[3]; float irms[3];
int32_t watt[3]; // ativo por fase (W) int32_t watt[3]; // ativo por fase (W)
float frequency; float frequency;
float power_factor; float power_factor;
// acumulados
float total_energy_Wh; float total_energy_Wh;
// derivados práticos
int32_t sum_watt; // soma das 3 fases int32_t sum_watt;
float avg_voltage; // média das 3 fases float avg_voltage;
float sum_current; // soma das 3 fases float sum_current;
// flag de validade
bool have_data; bool have_data;
} ocpp_meter_cache_t; } ocpp_meter_cache_t;
@@ -82,18 +86,101 @@ static esp_event_handler_instance_t s_meter_inst = NULL;
// valor de oferta (A por conector) // valor de oferta (A por conector)
static float s_current_offered_A = 16.0f; static float s_current_offered_A = 16.0f;
// novo input apropriado
static float getCurrentOffered(void) static float getCurrentOffered(void)
{ {
return s_current_offered_A; return s_current_offered_A;
} }
/* ========================= // -----------------------------------------------------------------------------
* Task / Main Loop // Storage helpers (robustos)
* ========================= * // -----------------------------------------------------------------------------
*/ #define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
static void storage_init_best_effort(void)
{
esp_err_t e = storage_service_init();
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e));
}
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_str_async(ns, key, s ? s : "");
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(portMAX_DELAY);
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// Lê string de forma segura (buffer grande -> truncagem segura para out)
static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz)
{
if (!out || out_sz == 0)
return ESP_ERR_INVALID_ARG;
out[0] = '\0';
char tmp[STORAGE_MAX_VALUE_BYTES + 1];
memset(tmp, 0, sizeof(tmp));
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
return ESP_OK;
if (e != ESP_OK)
return e;
size_t n = strnlen(tmp, out_sz - 1);
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
}
// -----------------------------------------------------------------------------
// Task / Main Loop
// -----------------------------------------------------------------------------
static void ocpp_task_func(void *param) static void ocpp_task_func(void *param)
{ {
(void)param;
while (true) while (true)
{ {
if (enabled) if (enabled)
@@ -106,7 +193,6 @@ static void ocpp_task_func(void *param)
{ {
s_evse_enabled = operative; s_evse_enabled = operative;
// >>> enviar OCPP_EVENT (remoto → local)
ocpp_operative_event_t ev = { ocpp_operative_event_t ev = {
.operative = operative, .operative = operative,
.timestamp_us = esp_timer_get_time()}; .timestamp_us = esp_timer_get_time()};
@@ -115,8 +201,7 @@ static void ocpp_task_func(void *param)
&ev, sizeof(ev), &ev, sizeof(ev),
portMAX_DELAY); portMAX_DELAY);
ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative);
(int)operative);
} }
} }
else else
@@ -126,33 +211,21 @@ static void ocpp_task_func(void *param)
} }
} }
/* ========================= // -----------------------------------------------------------------------------
* NVS GETs // Storage GETs
* ========================= */ // -----------------------------------------------------------------------------
bool ocpp_get_enabled(void) bool ocpp_get_enabled(void)
{ {
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: disabled
return false;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return false;
}
uint8_t value = 0; uint8_t value = 0;
err = nvs_get_u8(h, NVS_OCPP_ENABLED, &value); esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO);
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND) if (err == ESP_ERR_NOT_FOUND)
return false; // default return false;
if (err != ESP_OK) if (err != ESP_OK)
{ {
ESP_LOGW(TAG, "nvs_get_u8(enabled) failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed: %s", esp_err_to_name(err));
return false; return false;
} }
return value != 0; return value != 0;
@@ -164,31 +237,12 @@ void ocpp_get_server(char *value /* out, size>=64 */)
return; return;
value[0] = '\0'; value[0] = '\0';
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: ""
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return;
}
size_t len = 64; esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64);
err = nvs_get_str(h, NVS_OCPP_SERVER, value, &len); if (e != ESP_OK)
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{ {
value[0] = '\0'; ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e));
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(server) failed: %s", esp_err_to_name(err));
value[0] = '\0'; value[0] = '\0';
} }
} }
@@ -199,92 +253,81 @@ void ocpp_get_charge_id(char *value /* out, size>=64 */)
return; return;
value[0] = '\0'; value[0] = '\0';
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: ""
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return;
}
size_t len = 64; esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64);
err = nvs_get_str(h, NVS_OCPP_CHARGE_ID, value, &len); if (e != ESP_OK)
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{ {
value[0] = '\0'; ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e));
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(charge_id) failed: %s", esp_err_to_name(err));
value[0] = '\0'; value[0] = '\0';
} }
} }
/* ========================= // -----------------------------------------------------------------------------
* NVS SETs // Storage SETs
* ========================= */ // -----------------------------------------------------------------------------
// --- SETTERS: RW (cria o namespace na 1ª escrita), commit e fecha ---
void ocpp_set_enabled(bool value) void ocpp_set_enabled(bool value)
{ {
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK) ESP_LOGI(TAG, "set enabled %d", value);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0);
if (e != ESP_OK)
{ {
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); ESP_LOGE(TAG, "store_set_u8_best_effort(enabled) failed: %s", esp_err_to_name(e));
return; return;
} }
ESP_LOGI(TAG, "set enabled %d", value);
ESP_ERROR_CHECK(nvs_set_u8(h, NVS_OCPP_ENABLED, value ? 1 : 0)); (void)store_flush_best_effort();
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
enabled = value; enabled = value;
} }
void ocpp_set_server(char *value) void ocpp_set_server(char *value)
{ {
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK) ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : "");
if (e != ESP_OK)
{ {
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); ESP_LOGE(TAG, "store_set_str_best_effort(server) failed: %s", esp_err_to_name(e));
return; return;
} }
ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_SERVER, value ? value : "")); (void)store_flush_best_effort();
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
} }
void ocpp_set_charge_id(char *value) void ocpp_set_charge_id(char *value)
{ {
nvs_handle_t h; storage_init_best_effort();
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK) ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : "");
if (e != ESP_OK)
{ {
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); ESP_LOGE(TAG, "store_set_str_best_effort(charge_id) failed: %s", esp_err_to_name(e));
return; return;
} }
ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_CHARGE_ID, value ? value : "")); (void)store_flush_best_effort();
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
} }
// -----------------------------------------------------------------------------
// Event handlers (AUTH / EVSE / METER)
// -----------------------------------------------------------------------------
static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data) static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{ {
(void)arg;
(void)base;
(void)id;
const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data; const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data;
if (!rq) if (!rq)
return; return;
// Sanitizar/copiar a idTag
char idtag[AUTH_TAG_MAX_LEN]; char idtag[AUTH_TAG_MAX_LEN];
if (rq->tag[0] == '\0') if (rq->tag[0] == '\0')
{ {
@@ -299,16 +342,13 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo
ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id); ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id);
// Se não está pronto, apenas regista e sai (podes adaptar conforme política)
if (!enabled || g_ocpp_conn == NULL) if (!enabled || g_ocpp_conn == NULL)
{ {
ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) ignoring verify", enabled, (void *)g_ocpp_conn); ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) ignoring verify",
enabled, (void *)g_ocpp_conn);
return; return;
} }
// Regra pedida:
// - se já existe transação/charge em andamento -> terminar
// - senão -> iniciar com a IDTAG recebida
if (ocpp_isTransactionActive()) if (ocpp_isTransactionActive())
{ {
ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag); ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag);
@@ -323,6 +363,7 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo
static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
return; return;
@@ -343,12 +384,11 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
case EVSE_STATE_EVENT_CHARGING: case EVSE_STATE_EVENT_CHARGING:
s_ev_plugged = true; s_ev_plugged = true;
s_ev_ready = true; // EV está a pedir/receber energia s_ev_ready = true;
break; break;
case EVSE_STATE_EVENT_FAULT: case EVSE_STATE_EVENT_FAULT:
default: default:
// em falha, considera não pronto (mantém plugged se quiseres)
s_ev_ready = false; s_ev_ready = false;
break; break;
} }
@@ -356,6 +396,7 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data) static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)arg;
if (base != EVSE_EVENTS || data == NULL) if (base != EVSE_EVENTS || data == NULL)
return; return;
@@ -363,7 +404,8 @@ static void evse_enable_available_handler(void *arg, esp_event_base_t base, int3
{ {
const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data; const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data;
s_evse_enabled = e->enabled; s_evse_enabled = e->enabled;
ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)", (int)e->enabled, (long long)e->timestamp_us); ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)",
(int)e->enabled, (long long)e->timestamp_us);
return; return;
} }
@@ -371,23 +413,24 @@ static void evse_enable_available_handler(void *arg, esp_event_base_t base, int3
{ {
const evse_available_event_data_t *e = (const evse_available_event_data_t *)data; const evse_available_event_data_t *e = (const evse_available_event_data_t *)data;
s_evse_available = e->available; s_evse_available = e->available;
ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)", (int)e->available, (long long)e->timestamp_us); ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)",
(int)e->available, (long long)e->timestamp_us);
return; return;
} }
} }
static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data) static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)arg;
if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data) if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data)
return; return;
const meter_event_data_t *evt = (const meter_event_data_t *)data; const meter_event_data_t *evt = (const meter_event_data_t *)data;
// Só queremos o medidor do EVSE (não o GRID)
if (!evt->source || strcmp(evt->source, "EVSE") != 0) if (!evt->source || strcmp(evt->source, "EVSE") != 0)
return; return;
// Derivados simples
int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2]; int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2];
float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f; float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f;
float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2]; float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2];
@@ -408,22 +451,21 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d
portEXIT_CRITICAL(&s_meter_mux); portEXIT_CRITICAL(&s_meter_mux);
} }
/* ========================= // -----------------------------------------------------------------------------
* MicroOCPP Inputs/CBs // MicroOCPP Inputs/CBs
* ========================= */ // -----------------------------------------------------------------------------
bool setConnectorPluggedInput(void) bool setConnectorPluggedInput(void)
{ {
return s_ev_plugged; // EV fisicamente ligado return s_ev_plugged;
} }
bool setEvReadyInput(void) bool setEvReadyInput(void)
{ {
return s_ev_ready; // EV pede / pronto a carregar return s_ev_ready;
} }
bool setEvseReadyInput(void) bool setEvseReadyInput(void)
{ {
// EVSE autorizado / operacional
return s_evse_enabled && s_evse_available; return s_evse_enabled && s_evse_available;
} }
@@ -439,13 +481,10 @@ float setPowerMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux); portEXIT_CRITICAL(&s_meter_mux);
if (!have) if (!have)
{
ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)"); ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)");
}
else else
{
ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w); ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w);
}
return (float)w; return (float)w;
} }
@@ -461,14 +500,11 @@ float setEnergyMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux); portEXIT_CRITICAL(&s_meter_mux);
if (!have) if (!have)
{
ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)"); ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)");
}
else else
{
ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh); ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh);
}
return wh; // agora devolve Wh return wh;
} }
int setEnergyInput(void) int setEnergyInput(void)
@@ -491,13 +527,10 @@ float setCurrentInput(void)
portEXIT_CRITICAL(&s_meter_mux); portEXIT_CRITICAL(&s_meter_mux);
if (!have) if (!have)
{
ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)"); ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)");
}
else else
{
ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a); ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a);
}
return a; return a;
} }
@@ -513,19 +546,16 @@ float setVoltageInput(void)
portEXIT_CRITICAL(&s_meter_mux); portEXIT_CRITICAL(&s_meter_mux);
if (!have) if (!have)
{
ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)"); ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)");
}
else else
{
ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v); ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v);
}
return v; return v;
} }
float setPowerInput(void) float setPowerInput(void)
{ {
float w = setPowerMeterInput(); // alias float w = setPowerMeterInput();
ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w); ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w);
return w; return w;
} }
@@ -598,111 +628,63 @@ bool setOnResetNotify(bool value)
void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification) void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification)
{ {
(void)transaction;
ESP_LOGI(TAG, "TxNotification: %d", txNotification); ESP_LOGI(TAG, "TxNotification: %d", txNotification);
switch (txNotification) switch (txNotification)
{ {
case Authorized: case Authorized:
ESP_LOGI(TAG, "Authorized"); ESP_LOGI(TAG, "Authorized");
// TODO: send event ocpp Authorized
// evse_authorize();
// Opcional: enviar idTag no payload (se tiveres como obter do transaction)
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY);
break; break;
case AuthorizationRejected: case AuthorizationRejected:
ESP_LOGI(TAG, "AuthorizationRejected"); ESP_LOGI(TAG, "AuthorizationRejected");
// TODO: send event ocpp AuthorizationRejected
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY);
break; break;
case AuthorizationTimeout: case AuthorizationTimeout:
ESP_LOGI(TAG, "AuthorizationTimeout"); ESP_LOGI(TAG, "AuthorizationTimeout");
// TODO: send event ocpp AuthorizationTimeout
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY);
break; break;
case ReservationConflict: case ReservationConflict:
ESP_LOGI(TAG, "ReservationConflict"); ESP_LOGI(TAG, "ReservationConflict");
// TODO: send event ocpp ReservationConflict
// (Se quiseres, cria um ID específico no enum e publica aqui)
break; break;
case ConnectionTimeout: case ConnectionTimeout:
ESP_LOGI(TAG, "ConnectionTimeout"); ESP_LOGI(TAG, "ConnectionTimeout");
// TODO: send event ocpp ConnectionTimeout
// (Se quiseres, cria um ID específico no enum e publica aqui)
break; break;
case DeAuthorized: case DeAuthorized:
ESP_LOGI(TAG, "DeAuthorized"); ESP_LOGI(TAG, "DeAuthorized");
// TODO: send event ocpp DeAuthorized
// TODO: adapt to the new interface
// evse_set_authorized(false);
// evse_set_limit_reached(2);
// Poderias mapear para AUTH_REJECTED ou STOP_TX por política da aplicação:
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY);
break; break;
case RemoteStart: case RemoteStart:
ESP_LOGI(TAG, "RemoteStart"); ESP_LOGI(TAG, "RemoteStart");
// TODO: send event ocpp RemoteStart
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY);
break; break;
case RemoteStop: case RemoteStop:
ESP_LOGI(TAG, "RemoteStop"); ESP_LOGI(TAG, "RemoteStop");
// TODO: send event ocpp RemoteStop
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY);
break; break;
case StartTx: case StartTx:
ESP_LOGI(TAG, "StartTx"); ESP_LOGI(TAG, "StartTx");
// TODO: send event ocpp StartTx
// ocpp_tx_event_t tx = { .tx_id = ocpp_tx_get_id(transaction) };
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, &tx, sizeof(tx), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY);
break; break;
case StopTx: case StopTx:
ESP_LOGI(TAG, "StopTx"); ESP_LOGI(TAG, "StopTx");
// TODO: send event ocpp StopTx
// TODO: adapt to the new interface
// evse_set_authorized(false);
// evse_set_limit_reached(2);
// ocpp_reason_event_t rs = {0};
// strlcpy(rs.reason, "Local", sizeof(rs.reason));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, &rs, sizeof(rs), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY);
break; break;
} }
} }
// Estado de conexão simples do OCPP
bool ocpp_is_connected(void) bool ocpp_is_connected(void)
{ {
// Se quiser algo mais preciso (WS aberto), substitui por uma chamada da MicroOcpp,
// mas como fallback isto já evita o undefined symbol.
return g_ocpp_conn != NULL; return g_ocpp_conn != NULL;
} }
@@ -728,12 +710,12 @@ const char *addErrorCodeInput(void)
else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT) else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
ptr = "OtherError"; ptr = "OtherError";
return ptr; // NULL => sem erro return ptr;
} }
/* ========================= // -----------------------------------------------------------------------------
* Start / Stop OCPP // Start / Stop OCPP
* ========================= */ // -----------------------------------------------------------------------------
void ocpp_start(void) void ocpp_start(void)
{ {
ESP_LOGI(TAG, "Starting OCPP"); ESP_LOGI(TAG, "Starting OCPP");
@@ -744,6 +726,8 @@ void ocpp_start(void)
return; return;
} }
storage_init_best_effort();
enabled = ocpp_get_enabled(); enabled = ocpp_get_enabled();
if (!enabled) if (!enabled)
{ {
@@ -767,15 +751,14 @@ void ocpp_start(void)
return; return;
} }
/* Inicializar Mongoose + MicroOcpp */
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_log_set(MG_LL_ERROR); mg_log_set(MG_LL_ERROR);
struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true}; struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true};
g_ocpp_conn = ocpp_makeConnection(&mgr, g_ocpp_conn = ocpp_makeConnection(&mgr,
serverstr, /* ex: ws://host:port/OCPP16/... */ serverstr,
charge_id, /* ChargeBoxId / identity */ charge_id,
"", "",
"", "",
fsopt); fsopt);
@@ -788,19 +771,16 @@ void ocpp_start(void)
ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false); ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false);
/* Inputs/outputs e callbacks */
ocpp_setEvReadyInput(&setEvReadyInput); ocpp_setEvReadyInput(&setEvReadyInput);
ocpp_setEvseReadyInput(&setEvseReadyInput); ocpp_setEvseReadyInput(&setEvseReadyInput);
ocpp_setConnectorPluggedInput(&setConnectorPluggedInput); ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);
ocpp_setOnResetExecute(&OnResetExecute); ocpp_setOnResetExecute(&OnResetExecute);
ocpp_setTxNotificationOutput(&notificationOutput); ocpp_setTxNotificationOutput(&notificationOutput);
// ocpp_setStartTxReadyInput(&setStartTxReadyInput);
ocpp_setStopTxReadyInput(&setStopTxReadyInput); ocpp_setStopTxReadyInput(&setStopTxReadyInput);
ocpp_setOnResetNotify(&setOnResetNotify); ocpp_setOnResetNotify(&setOnResetNotify);
ocpp_setEnergyMeterInput(&setEnergyInput); // inteiro Wh ocpp_setEnergyMeterInput(&setEnergyInput);
/* Metering */
ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL); ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL); ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL); ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL);
@@ -810,8 +790,7 @@ void ocpp_start(void)
ocpp_addErrorCodeInput(&addErrorCodeInput); ocpp_addErrorCodeInput(&addErrorCodeInput);
/* Task */ xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task);
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 5, &ocpp_task);
if (!s_auth_verify_inst) if (!s_auth_verify_inst)
{ {
@@ -821,7 +800,6 @@ void ocpp_start(void)
ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener"); ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener");
} }
// ouvir mudanças de estado do EVSE
if (!s_evse_state_inst) if (!s_evse_state_inst)
{ {
ESP_ERROR_CHECK(esp_event_handler_instance_register( ESP_ERROR_CHECK(esp_event_handler_instance_register(
@@ -829,7 +807,6 @@ void ocpp_start(void)
&evse_event_handler, NULL, &s_evse_state_inst)); &evse_event_handler, NULL, &s_evse_state_inst));
} }
// ouvir mudanças de ENABLE / AVAILABLE do EVSE (Local → OCPP)
if (!s_evse_enable_inst) if (!s_evse_enable_inst)
{ {
ESP_ERROR_CHECK(esp_event_handler_instance_register( ESP_ERROR_CHECK(esp_event_handler_instance_register(

View File

@@ -2,19 +2,15 @@ set(srcs
"src/adc.c" "src/adc.c"
"src/adc121s021_dma.c" "src/adc121s021_dma.c"
"src/peripherals.c" "src/peripherals.c"
"src/led.c"
"src/proximity.c" "src/proximity.c"
"src/ac_relay.c" "src/ac_relay.c"
"src/socket_lock.c" "src/socket_lock.c"
"src/rcm.c" "src/rcm.c"
"src/aux_io.c"
"src/onewire.c"
"src/ds18x20.c"
"src/temp_sensor.c" "src/temp_sensor.c"
"src/ntc_sensor.c" "src/ntc_sensor.c"
) )
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver esp_adc esp_timer PRIV_REQUIRES driver esp_adc esp_timer
REQUIRES config evse ntc_driver spi_bus_manager) REQUIRES config evse ntc_driver spi_bus_manager storage_service)

View File

@@ -1,12 +1,35 @@
// components/peripherals/include/adc121s021_dma.h
#ifndef ADC_DMA_H_ #ifndef ADC_DMA_H_
#define ADC_DMA_H_ #define ADC_DMA_H_
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa o ADC121S021 no barramento SPI partilhado.
*
* - Garante que o spi_bus_manager foi inicializado.
* - Regista o dispositivo ADC no bus.
*/
void adc121s021_dma_init(void); void adc121s021_dma_init(void);
/**
* @brief Lê uma única amostra (12 bits) do ADC121S021.
*
* Esta função faz uma transação SPI bloqueante (polling), suficientemente
* rápida para uso em burst (100 amostras em ~23 ms).
*
* @param[out] sample Ponteiro onde será escrito o valor lido (0..4095).
* @return true em caso de sucesso, false se ocorrer erro.
*/
bool adc121s021_dma_get_sample(uint16_t *sample); bool adc121s021_dma_get_sample(uint16_t *sample);
#ifdef __cplusplus
}
#endif
#endif /* ADC_DMA_h_ */ #endif /* ADC_DMA_H_ */

View File

@@ -1,39 +0,0 @@
#ifndef AUX_IO_H_
#define AUX_IO_H_
#include "esp_err.h"
/**
* @brief Initialize aux
*
*/
void aux_init(void);
/**
* @brief Read digital input
*
* @param name
* @param value
* @return esp_err_t
*/
esp_err_t aux_read(const char *name, bool *value);
/**
* @brief Write digial output
*
* @param name
* @param value
* @return esp_err_t
*/
esp_err_t aux_write(const char *name, bool value);
/**
* @brief Read analog input
*
* @param name
* @param value
* @return esp_err_t
*/
esp_err_t aux_analog_read(const char *name, int *value);
#endif /* AUX_IO_H_ */

View File

@@ -1,53 +0,0 @@
#ifndef LED_H_
#define LED_H_
#include <stdint.h>
#include <stdbool.h>
/**
* @brief Identificadores dos LEDs disponíveis no hardware
*/
typedef enum {
LED_ID_STOP,
LED_ID_CHARGING,
LED_ID_ERROR,
LED_ID_MAX
} led_id_t;
/**
* @brief Padrões de comportamento possíveis para os LEDs
*/
typedef enum {
LED_PATTERN_OFF, ///< LED sempre desligado
LED_PATTERN_ON, ///< LED sempre ligado
LED_PATTERN_BLINK, ///< Pisca com ciclo padrão (500ms on / 500ms off)
LED_PATTERN_BLINK_FAST, ///< Pisca rápido (200ms / 200ms)
LED_PATTERN_BLINK_SLOW, ///< Pisca lento (300ms / 1700ms)
LED_PATTERN_CHARGING_EFFECT ///< Efeito visual para carregamento (2s on / 1s off)
} led_pattern_t;
/**
* @brief Inicializa os LEDs com base na configuração da placa
* Deve ser chamada uma única vez na inicialização do sistema.
*/
void led_init(void);
/**
* @brief Define diretamente o tempo ligado/desligado de um LED.
* Pode ser usado para padrões personalizados.
*
* @param led_id Identificador do LED (ver enum led_id_t)
* @param ontime Tempo ligado em milissegundos
* @param offtime Tempo desligado em milissegundos
*/
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime);
/**
* @brief Aplica um dos padrões de piscar definidos ao LED
*
* @param led_id Identificador do LED (ver enum led_id_t)
* @param pattern Padrão desejado (ver enum led_pattern_t)
*/
void led_apply_pattern(led_id_t led_id, led_pattern_t pattern);
#endif /* LED_H_ */

View File

@@ -1,28 +1,41 @@
// components/peripherals/src/adc121s021_dma.c
#include "driver/spi_master.h" #include "driver/spi_master.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "adc121s021_dma.h" #include "adc121s021_dma.h"
#include "spi_bus_manager.h" #include "spi_bus_manager.h"
#define TAG "adc_dma" #define TAG "adc_dma"
// Pino de chip-select do ADC121S021 (ajusta se necessário)
#define PIN_NUM_CS 5 #define PIN_NUM_CS 5
#define SAMPLE_SIZE_BYTES 2
// ADC é 12-bit, mas transferimos 16 bits via SPI
#define ADC_BITS 12 #define ADC_BITS 12
#define SPI_CLOCK_HZ (6 * 1000 * 1000) // 6 MHz
// Clock SPI: 1 MHz → ~16 µs de transferência por amostra.
// Com um pequeno delay entre leituras, 100 amostras ficam em ~23 ms,
// o que é perfeito para analisar um PWM de 1 kHz a cada 100 ms.
#define SPI_CLOCK_HZ (1 * 1000 * 1000)
static spi_device_handle_t adc_spi = NULL; static spi_device_handle_t adc_spi = NULL;
void adc121s021_dma_init(void) void adc121s021_dma_init(void)
{ {
if (adc_spi) { if (adc_spi)
{
ESP_LOGW(TAG, "ADC121S021 já foi inicializado."); ESP_LOGW(TAG, "ADC121S021 já foi inicializado.");
return; return;
} }
if (!spi_bus_manager_is_initialized()) { // Garante que o SPI bus partilhado está configurado
if (!spi_bus_manager_is_initialized())
{
ESP_LOGI(TAG, "SPI bus não inicializado. Inicializando..."); ESP_LOGI(TAG, "SPI bus não inicializado. Inicializando...");
esp_err_t err = spi_bus_manager_init(); // 🔧 CORRIGIDO: sem argumentos esp_err_t err = spi_bus_manager_init();
if (err != ESP_OK) { if (err != ESP_OK)
{
ESP_LOGE(TAG, "Falha ao inicializar o SPI bus: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Falha ao inicializar o SPI bus: %s", esp_err_to_name(err));
return; return;
} }
@@ -32,44 +45,55 @@ void adc121s021_dma_init(void)
.clock_speed_hz = SPI_CLOCK_HZ, .clock_speed_hz = SPI_CLOCK_HZ,
.mode = 0, .mode = 0,
.spics_io_num = PIN_NUM_CS, .spics_io_num = PIN_NUM_CS,
.queue_size = 2, .queue_size = 2, // suficiente para uso em burst
.flags = SPI_DEVICE_NO_DUMMY, .flags = SPI_DEVICE_NO_DUMMY,
.pre_cb = NULL, .pre_cb = NULL,
.post_cb = NULL, .post_cb = NULL,
}; };
esp_err_t err = spi_bus_add_device(spi_bus_manager_get_host(), &devcfg, &adc_spi); esp_err_t err = spi_bus_add_device(spi_bus_manager_get_host(), &devcfg, &adc_spi);
if (err != ESP_OK) { if (err != ESP_OK)
{
ESP_LOGE(TAG, "Falha ao registrar ADC121S021 no SPI: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Falha ao registrar ADC121S021 no SPI: %s", esp_err_to_name(err));
adc_spi = NULL;
return; return;
} }
ESP_LOGI(TAG, "ADC121S021 registrado no SPI com sucesso."); ESP_LOGI(TAG, "ADC121S021 registrado no SPI (CS=%d, fSPI=%d Hz).",
PIN_NUM_CS, SPI_CLOCK_HZ);
} }
bool adc121s021_dma_get_sample(uint16_t *sample) bool adc121s021_dma_get_sample(uint16_t *sample)
{ {
if (!adc_spi) { if (!sample)
ESP_LOGE(TAG, "ADC SPI não inicializado!"); {
return false; return false;
} }
uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy if (!adc_spi)
{
ESP_LOGE(TAG, "ADC SPI não inicializado! Chama adc121s021_dma_init() primeiro.");
return false;
}
uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy (ADC só precisa de clock)
uint8_t rx_buffer[2] = {0}; uint8_t rx_buffer[2] = {0};
spi_transaction_t t = { spi_transaction_t t = {
.length = 16, .length = 16, // 16 bits
.tx_buffer = tx_buffer, .tx_buffer = tx_buffer,
.rx_buffer = rx_buffer, .rx_buffer = rx_buffer,
.flags = 0 .flags = 0};
};
esp_err_t err = spi_device_transmit(adc_spi, &t); // Polling transmit → menor overhead que fila + espera.
if (err != ESP_OK) { esp_err_t err = spi_device_polling_transmit(adc_spi, &t);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Erro na transmissão SPI: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Erro na transmissão SPI: %s", esp_err_to_name(err));
return false; return false;
} }
*sample = ((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF; // ADC121S021 devolve os 12 bits mais significativos em 16 bits.
*sample = (uint16_t)(((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF);
return true; return true;
} }

View File

@@ -1,174 +0,0 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "aux_io.h"
#include "board_config.h"
#include "adc.h"
#define MAX_AUX_IN 4
#define MAX_AUX_OUT 4
#define MAX_AUX_AIN 4
//static const char* TAG = "aux";
static int aux_in_count = 0;
static int aux_out_count = 0;
static int aux_ain_count = 0;
static struct aux_gpio_s
{
gpio_num_t gpio;
const char* name;
} aux_in[MAX_AUX_IN], aux_out[MAX_AUX_OUT];
static struct aux_adc_s
{
adc_channel_t adc;
const char* name;
} aux_ain[MAX_AUX_AIN];
void aux_init(void)
{
// IN
gpio_config_t io_conf = {
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLDOWN_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
.pin_bit_mask = 0
};
if (board_config.aux_in_1) {
aux_in[aux_in_count].gpio = board_config.aux_in_1_gpio;
aux_in[aux_in_count].name = board_config.aux_in_1_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_1_gpio);
aux_in_count++;
}
if (board_config.aux_in_2) {
aux_in[aux_in_count].gpio = board_config.aux_in_2_gpio;
aux_in[aux_in_count].name = board_config.aux_in_2_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_2_gpio);
aux_in_count++;
}
if (board_config.aux_in_3) {
aux_in[aux_in_count].gpio = board_config.aux_in_3_gpio;
aux_in[aux_in_count].name = board_config.aux_in_3_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_3_gpio);
aux_in_count++;
}
if (board_config.aux_in_4) {
aux_in[aux_in_count].gpio = board_config.aux_in_4_gpio;
aux_in[aux_in_count].name = board_config.aux_in_4_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_4_gpio);
aux_in_count++;
}
if (io_conf.pin_bit_mask > 0) {
ESP_ERROR_CHECK(gpio_config(&io_conf));
}
// OUT
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = 0;
if (board_config.aux_out_1) {
aux_out[aux_out_count].gpio = board_config.aux_out_1_gpio;
aux_out[aux_out_count].name = board_config.aux_out_1_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_1_gpio);
aux_out_count++;
}
if (board_config.aux_out_2) {
aux_out[aux_out_count].gpio = board_config.aux_out_2_gpio;
aux_out[aux_out_count].name = board_config.aux_out_2_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_2_gpio);
aux_out_count++;
}
if (board_config.aux_out_3) {
aux_out[aux_out_count].gpio = board_config.aux_out_3_gpio;
aux_out[aux_out_count].name = board_config.aux_out_3_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_3_gpio);
aux_out_count++;
}
if (board_config.aux_out_4) {
aux_out[aux_out_count].gpio = board_config.aux_out_4_gpio;
aux_out[aux_out_count].name = board_config.aux_out_4_name;
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_4_gpio);
aux_out_count++;
}
if (io_conf.pin_bit_mask > 0) {
ESP_ERROR_CHECK(gpio_config(&io_conf));
}
// AIN
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_12
};
if (board_config.aux_ain_1) {
aux_ain[aux_ain_count].adc = board_config.aux_ain_1_adc_channel;
aux_ain[aux_ain_count].name = board_config.aux_out_1_name;
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.aux_ain_1_adc_channel, &config));
aux_ain_count++;
}
if (board_config.aux_ain_2) {
aux_ain[aux_ain_count].adc = board_config.aux_ain_2_adc_channel;
aux_ain[aux_ain_count].name = board_config.aux_out_2_name;
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.aux_ain_2_adc_channel, &config));
aux_ain_count++;
}
}
esp_err_t aux_read(const char* name, bool* value)
{
for (int i = 0; i < aux_in_count; i++) {
if (strcmp(aux_in[i].name, name) == 0) {
*value = gpio_get_level(aux_in[i].gpio) == 1;
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t aux_write(const char* name, bool value)
{
for (int i = 0; i < aux_out_count; i++) {
if (strcmp(aux_out[i].name, name) == 0) {
return gpio_set_level(aux_out[i].gpio, value);
}
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t aux_analog_read(const char* name, int* value)
{
for (int i = 0; i < aux_ain_count; i++) {
if (strcmp(aux_ain[i].name, name) == 0) {
int raw = 0;
esp_err_t ret = adc_oneshot_read(adc_handle, aux_ain[i].adc, &raw);
if (ret == ESP_OK) {
return adc_cali_raw_to_voltage(adc_cali_handle, raw, value);
} else {
return ret;
}
}
}
return ESP_ERR_NOT_FOUND;
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
* Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
* Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <math.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "ds18x20.h"
#define ds18x20_WRITE_SCRATCHPAD 0x4E
#define ds18x20_READ_SCRATCHPAD 0xBE
#define ds18x20_COPY_SCRATCHPAD 0x48
#define ds18x20_READ_EEPROM 0xB8
#define ds18x20_READ_PWRSUPPLY 0xB4
#define ds18x20_SEARCHROM 0xF0
#define ds18x20_SKIP_ROM 0xCC
#define ds18x20_READROM 0x33
#define ds18x20_MATCHROM 0x55
#define ds18x20_ALARMSEARCH 0xEC
#define ds18x20_CONVERT_T 0x44
#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
static const char* TAG = "ds18x20";
esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait)
{
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
portENTER_CRITICAL(&mux);
onewire_write(pin, ds18x20_CONVERT_T);
// For parasitic devices, power must be applied within 10us after issuing
// the convert command.
onewire_power(pin);
portEXIT_CRITICAL(&mux);
if (wait){
vTaskDelay(pdMS_TO_TICKS(750));
onewire_depower(pin);
}
return ESP_OK;
}
esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer)
{
CHECK_ARG(buffer);
uint8_t crc;
uint8_t expected_crc;
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
onewire_write(pin, ds18x20_READ_SCRATCHPAD);
for (int i = 0; i < 8; i++)
buffer[i] = onewire_read(pin);
crc = onewire_read(pin);
expected_crc = onewire_crc8(buffer, 8);
if (crc != expected_crc)
{
ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1],
buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc);
return ESP_ERR_INVALID_CRC;
}
return ESP_OK;
}
esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer)
{
CHECK_ARG(buffer);
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
onewire_write(pin, ds18x20_WRITE_SCRATCHPAD);
for (int i = 0; i < 3; i++)
onewire_write(pin, buffer[i]);
return ESP_OK;
}
esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr)
{
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
portENTER_CRITICAL(&mux);
onewire_write(pin, ds18x20_COPY_SCRATCHPAD);
// For parasitic devices, power must be applied within 10us after issuing
// the convert command.
onewire_power(pin);
portEXIT_CRITICAL(&mux);
// And then it needs to keep that power up for 10ms.
vTaskDelay(pdMS_TO_TICKS(10));
onewire_depower(pin);
return ESP_OK;
}
esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
uint8_t scratchpad[8];
int16_t temp;
CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
temp = scratchpad[1] << 8 | scratchpad[0];
*temperature = ((int16_t)temp * 625.0) / 100;
return ESP_OK;
}
esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
uint8_t scratchpad[8];
int16_t temp;
CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
temp = scratchpad[1] << 8 | scratchpad[0];
temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4;
*temperature = (temp * 625) / 100 - 25;
return ESP_OK;
}
esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
if ((uint8_t)addr == DS18B20_FAMILY_ID) {
return ds18b20_read_temperature(pin, addr, temperature);
} else {
return ds18s20_read_temperature(pin, addr, temperature);
}
}
esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18b20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18s20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18x20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list)
{
CHECK_ARG(result_list && addr_count);
CHECK(ds18x20_measure(pin, DS18X20_ANY, true));
return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list);
}
esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, size_t* found)
{
CHECK_ARG(addr_list && addr_count);
onewire_search_t search;
onewire_addr_t addr;
*found = 0;
onewire_search_start(&search);
while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE)
{
uint8_t family_id = (uint8_t)addr;
if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID)
{
if (*found < addr_count)
addr_list[*found] = addr;
*found += 1;
}
}
return ESP_OK;
}
esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list)
{
CHECK_ARG(result_list);
esp_err_t res = ESP_OK;
for (size_t i = 0; i < addr_count; i++)
{
esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]);
if (tmp != ESP_OK)
res = tmp;
}
return res;
}

View File

@@ -1,254 +0,0 @@
/*
* Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
* Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
* Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _DS18X20_H
#define _DS18X20_H
#include <esp_err.h>
#include "onewire.h"
typedef onewire_addr_t ds18x20_addr_t;
/** An address value which can be used to indicate "any device on the bus" */
#define DS18X20_ANY ONEWIRE_NONE
/** Family ID (lower address byte) of DS18B20 sensors */
#define DS18B20_FAMILY_ID 0x28
/** Family ID (lower address byte) of DS18S20 sensors */
#define DS18S20_FAMILY_ID 0x10
/**
* @brief Find the addresses of all ds18x20 devices on the bus.
*
* Scans the bus for all devices and places their addresses in the supplied
* array. If there are more than `addr_count` devices on the bus, only the
* first `addr_count` are recorded.
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A pointer to an array of ::ds18x20_addr_t values.
* This will be populated with the addresses of the found
* devices.
* @param addr_count Number of slots in the `addr_list` array. At most this
* many addresses will be returned.
* @param found The number of devices found. Note that this may be less
* than, equal to, or more than `addr_count`, depending on
* how many ds18x20 devices are attached to the bus.
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found);
/**
* @brief Tell one or more sensors to perform a temperature measurement and
* conversion (CONVERT_T) operation.
*
* This operation can take up to 750ms to complete.
*
* If `wait=true`, this routine will automatically drive the pin high for the
* necessary 750ms after issuing the command to ensure parasitically-powered
* devices have enough power to perform the conversion operation (for
* non-parasitically-powered devices, this is not necessary but does not
* hurt). If `wait=false`, this routine will drive the pin high, but will
* then return immediately. It is up to the caller to wait the requisite time
* and then depower the bus using onewire_depower() or by issuing another
* command once conversion is done.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device on the bus. This can be set
* to ::DS18X20_ANY to send the command to all devices on the bus
* at the same time.
* @param wait Whether to wait for the necessary 750ms for the ds18x20 to
* finish performing the conversion before returning to the
* caller (You will normally want to do this).
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait);
/**
* @brief Read the value from the last CONVERT_T operation.
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation (ds18b20 version).
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation (ds18s20 version).
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation for multiple devices.
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A list of addresses for devices to read.
* @param addr_count The number of entries in `addr_list`.
* @param result_list An array of int16_ts to hold the returned temperature
* values. It should have at least `addr_count` entries.
*
* @returns `ESP_OK` if all temperatures were fetched successfully
*/
esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list);
/** Perform a ds18x20_measure() followed by ds18s20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18s20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/** Perform a ds18x20_measure() followed by ds18b20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/** Perform a ds18x20_measure() followed by ds18x20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi()
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A list of addresses for devices to read.
* @param addr_count The number of entries in `addr_list`.
* @param result_list An array of int16_ts to hold the returned temperature
* values. It should have at least `addr_count` entries.
*
* @returns `ESP_OK` if all temperatures were fetched successfully
*/
esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list);
/**
* @brief Read the scratchpad data for a particular ds18x20 device.
*
* This is not generally necessary to do directly. It is done automatically
* as part of ds18x20_read_temperature().
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param buffer An 8-byte buffer to hold the read data.
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
/**
* @brief Write the scratchpad data for a particular ds18x20 device.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to write. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param buffer An 3-byte buffer to hold the data to write
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
/**
* @brief Issue the copy scratchpad command, copying current scratchpad to
* EEPROM.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to command. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr);
#endif /* _DS18X20_H */

View File

@@ -1,211 +0,0 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "led.h"
#include "board_config.h"
#include "evse_events.h"
#include "evse_state.h"
#define BLOCK_TIME pdMS_TO_TICKS(10)
static const char *TAG = "led";
typedef struct {
gpio_num_t gpio;
bool on : 1;
uint16_t ontime;
uint16_t offtime;
TimerHandle_t timer;
led_pattern_t pattern;
uint8_t blink_count;
} led_t;
static led_t leds[LED_ID_MAX] = {0};
// ----------------------------
// Funções Internas
// ----------------------------
static void led_timer_callback(TimerHandle_t xTimer)
{
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
led->on = !led->on;
gpio_set_level(led->gpio, led->on);
uint32_t next_time = led->on ? led->ontime : led->offtime;
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
}
// ----------------------------
// Event Handler: EVSE State
// ----------------------------
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) {
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return;
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
// Log do evento recebido
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF);
switch (evt->state) {
case EVSE_STATE_EVENT_IDLE:
ESP_LOGI(TAG, "EVSE_STATE_EVENT_IDLE");
led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON);
break;
case EVSE_STATE_EVENT_WAITING:
ESP_LOGI(TAG, "EVSE_STATE_EVENT_WAITING");
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON);
break;
case EVSE_STATE_EVENT_CHARGING:
ESP_LOGI(TAG, "EVSE_STATE_EVENT_CHARGING");
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT);
break;
case EVSE_STATE_EVENT_FAULT:
ESP_LOGI(TAG, "EVSE_STATE_EVENT_FAULT");
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
break;
default:
ESP_LOGW(TAG, "Unknown state: %d", evt->state);
break;
}
}
// ----------------------------
// Inicialização
// ----------------------------
void led_init(void)
{
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.pin_bit_mask = 0
};
for (int i = 0; i < LED_ID_MAX; i++) {
leds[i].gpio = GPIO_NUM_NC;
}
if (board_config.led_stop) {
leds[LED_ID_STOP].gpio = board_config.led_stop_gpio;
io_conf.pin_bit_mask |= BIT64(board_config.led_stop_gpio);
}
if (board_config.led_charging) {
leds[LED_ID_CHARGING].gpio = board_config.led_charging_gpio;
io_conf.pin_bit_mask |= BIT64(board_config.led_charging_gpio);
}
if (board_config.led_error) {
leds[LED_ID_ERROR].gpio = board_config.led_error_gpio;
io_conf.pin_bit_mask |= BIT64(board_config.led_error_gpio);
}
if (io_conf.pin_bit_mask != 0) {
ESP_ERROR_CHECK(gpio_config(&io_conf));
}
// Registra handler de evento EVSE
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
evse_led_event_handler,
NULL));
ESP_LOGI(TAG, "LED system initialized");
// Aplica o estado atual do EVSE aos LEDs
evse_state_event_data_t evt = {
.state = EVSE_STATE_EVENT_IDLE
};
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
}
// ----------------------------
// API Pública
// ----------------------------
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
{
if (led_id >= LED_ID_MAX) return;
led_t *led = &leds[led_id];
if (led->gpio == GPIO_NUM_NC) return;
if (led->ontime == ontime && led->offtime == offtime)
return;
if (led->timer) {
xTimerStop(led->timer, BLOCK_TIME);
}
led->ontime = ontime;
led->offtime = offtime;
if (ontime == 0) {
led->on = false;
gpio_set_level(led->gpio, 0);
} else if (offtime == 0) {
led->on = true;
gpio_set_level(led->gpio, 1);
} else {
led->on = true;
gpio_set_level(led->gpio, 1);
if (!led->timer) {
led->timer = xTimerCreate("led_timer", pdMS_TO_TICKS(ontime),
pdFALSE, (void *)led, led_timer_callback);
}
if (led->timer) {
xTimerStart(led->timer, BLOCK_TIME);
}
}
}
void led_apply_pattern(led_id_t id, led_pattern_t pattern)
{
if (id >= LED_ID_MAX) return;
led_t *led = &leds[id];
if (led->gpio == GPIO_NUM_NC) return;
if (led->pattern == pattern) return;
if (led->timer) {
xTimerStop(led->timer, BLOCK_TIME);
}
led->pattern = pattern;
led->blink_count = 0;
switch (pattern) {
case LED_PATTERN_OFF:
led_set_state(id, 0, 0);
break;
case LED_PATTERN_ON:
led_set_state(id, 1, 0);
break;
case LED_PATTERN_BLINK:
led_set_state(id, 500, 500);
break;
case LED_PATTERN_BLINK_FAST:
led_set_state(id, 200, 200);
break;
case LED_PATTERN_BLINK_SLOW:
led_set_state(id, 300, 1700);
break;
case LED_PATTERN_CHARGING_EFFECT:
led_set_state(id, 2000, 1000);
break;
}
}

View File

@@ -1,498 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 zeroday nodemcu.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -------------------------------------------------------------------------------
* Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
* following additional terms:
*
* Except as contained in this notice, the name of Dallas Semiconductor
* shall not be used except as stated in the Dallas Semiconductor
* Branding Policy.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "rom/ets_sys.h"
#include "onewire.h"
#define ONEWIRE_SELECT_ROM 0x55
#define ONEWIRE_SKIP_ROM 0xcc
#define ONEWIRE_SEARCH 0xf0
#define ONEWIRE_CRC8_TABLE
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
// Waits up to `max_wait` microseconds for the specified pin to go high.
// Returns true if successful, false if the bus never comes high (likely
// shorted).
static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait)
{
bool state;
for (int i = 0; i < ((max_wait + 4) / 5); i++) {
if (gpio_get_level(pin))
break;
ets_delay_us(5);
}
state = gpio_get_level(pin);
// Wait an extra 1us to make sure the devices have an adequate recovery
// time before we drive things low again.
ets_delay_us(1);
return state;
}
static void setup_pin(gpio_num_t pin, bool open_drain)
{
gpio_set_direction(pin, open_drain ? GPIO_MODE_INPUT_OUTPUT_OD : GPIO_MODE_OUTPUT);
// gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY);
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return false;
//
// Returns true if a device asserted a presence pulse, false otherwise.
//
bool onewire_reset(gpio_num_t pin)
{
setup_pin(pin, true);
gpio_set_level(pin, 1);
// wait until the wire is high... just in case
if (!_onewire_wait_for_bus(pin, 250))
return false;
gpio_set_level(pin, 0);
ets_delay_us(480);
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 1); // allow it to float
ets_delay_us(70);
bool r = !gpio_get_level(pin);
portEXIT_CRITICAL(&mux);
// Wait for all devices to finish pulling the bus low before returning
if (!_onewire_wait_for_bus(pin, 410))
return false;
return r;
}
static bool _onewire_write_bit(gpio_num_t pin, bool v)
{
if (!_onewire_wait_for_bus(pin, 10))
return false;
portENTER_CRITICAL(&mux);
if (v) {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(10);
gpio_set_level(pin, 1); // allow output high
ets_delay_us(55);
} else {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(65);
gpio_set_level(pin, 1); // allow output high
}
ets_delay_us(1);
portEXIT_CRITICAL(&mux);
return true;
}
static int _onewire_read_bit(gpio_num_t pin)
{
if (!_onewire_wait_for_bus(pin, 10))
return -1;
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 0);
ets_delay_us(2);
gpio_set_level(pin, 1); // let pin float, pull up will raise
ets_delay_us(11);
int r = gpio_get_level(pin); // Must sample within 15us of start
ets_delay_us(48);
portEXIT_CRITICAL(&mux);
return r;
}
// Write a byte. The writing code uses open-drain mode and expects the pullup
// resistor to pull the line high when not driven low. If you need strong
// power after the write (e.g. DS18B20 in parasite power mode) then call
// onewire_power() after this is complete to actively drive the line high.
//
bool onewire_write(gpio_num_t pin, uint8_t v)
{
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1)
if (!_onewire_write_bit(pin, (bitMask & v)))
return false;
return true;
}
bool onewire_write_bytes(gpio_num_t pin, const uint8_t* buf, size_t count)
{
for (size_t i = 0; i < count; i++)
if (!onewire_write(pin, buf[i]))
return false;
return true;
}
// Read a byte
//
int onewire_read(gpio_num_t pin)
{
int r = 0;
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
int bit = _onewire_read_bit(pin);
if (bit < 0)
return -1;
else if (bit)
r |= bitMask;
}
return r;
}
bool onewire_read_bytes(gpio_num_t pin, uint8_t* buf, size_t count)
{
size_t i;
int b;
for (i = 0; i < count; i++) {
b = onewire_read(pin);
if (b < 0)
return false;
buf[i] = b;
}
return true;
}
bool onewire_select(gpio_num_t pin, onewire_addr_t addr)
{
uint8_t i;
if (!onewire_write(pin, ONEWIRE_SELECT_ROM))
return false;
for (i = 0; i < 8; i++) {
if (!onewire_write(pin, addr & 0xff))
return false;
addr >>= 8;
}
return true;
}
bool onewire_skip_rom(gpio_num_t pin)
{
return onewire_write(pin, ONEWIRE_SKIP_ROM);
}
bool onewire_power(gpio_num_t pin)
{
// Make sure the bus is not being held low before driving it high, or we
// may end up shorting ourselves out.
if (!_onewire_wait_for_bus(pin, 10))
return false;
setup_pin(pin, false);
gpio_set_level(pin, 1);
return true;
}
void onewire_depower(gpio_num_t pin)
{
setup_pin(pin, true);
}
void onewire_search_start(onewire_search_t* search)
{
// reset the search state
memset(search, 0, sizeof(*search));
}
void onewire_search_prefix(onewire_search_t* search, uint8_t family_code)
{
uint8_t i;
search->rom_no[0] = family_code;
for (i = 1; i < 8; i++) {
search->rom_no[i] = 0;
}
search->last_discrepancy = 64;
search->last_device_found = false;
}
// Perform a search. If the next device has been successfully enumerated, its
// ROM address will be returned. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return 1 : device found, ROM number in ROM_NO buffer
// 0 : device not found, end of search
//
onewire_addr_t onewire_search_next(onewire_search_t* search, gpio_num_t pin)
{
//TODO: add more checking for read/write errors
uint8_t id_bit_number;
uint8_t last_zero, search_result;
int rom_byte_number;
int8_t id_bit, cmp_id_bit;
onewire_addr_t addr;
unsigned char rom_byte_mask;
bool search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = 0;
// if the last call was not the last one
if (!search->last_device_found) {
// 1-Wire reset
if (!onewire_reset(pin)) {
// reset the search
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
}
// issue the search command
onewire_write(pin, ONEWIRE_SEARCH);
// loop to do the search
do {
// read a bit and its complement
id_bit = _onewire_read_bit(pin);
cmp_id_bit = _onewire_read_bit(pin);
if ((id_bit == 1) && (cmp_id_bit == 1))
break;
else {
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit)
search_direction = id_bit; // bit write value for search
else {
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < search->last_discrepancy)
search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0);
else
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == search->last_discrepancy);
// if 0 was picked then record its position in LastZero
if (!search_direction)
last_zero = id_bit_number;
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction)
search->rom_no[rom_byte_number] |= rom_byte_mask;
else
search->rom_no[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
_onewire_write_bit(pin, search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0) {
rom_byte_number++;
rom_byte_mask = 1;
}
}
} while (rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65)) {
// search successful so set last_discrepancy,last_device_found,search_result
search->last_discrepancy = last_zero;
// check for last device
if (search->last_discrepancy == 0)
search->last_device_found = true;
search_result = 1;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !search->rom_no[0]) {
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
} else {
addr = 0;
for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) {
addr = (addr << 8) | search->rom_no[rom_byte_number];
}
//printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr);
}
return addr;
}
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
//
#ifdef ONEWIRE_CRC8_TABLE
// This table comes from Dallas sample code where it is freely reusable,
// though Copyright (c) 2000 Dallas Semiconductor Corporation
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
};
//
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
// and the registers. (note: this might better be done without to
// table, it would probably be smaller and certainly fast enough
// compared to all those delayMicrosecond() calls. But I got
// confused, so I use this table from the examples.)
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
crc = dscrc_table[crc ^ *data++];
return crc;
}
#else
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but much smaller, than the lookup table.
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
{
uint8_t inbyte = *data++;
for (int i = 8; i; i--)
{
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
#endif /* ONEWIRE_CRC8_TABLE */
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return 1, iff the CRC matches.
bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv)
{
uint16_t crc = ~onewire_crc16(input, len, crc_iv);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv)
{
uint16_t crc = crc_iv;
static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
uint16_t i;
for (i = 0; i < len; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
return crc;
}

View File

@@ -1,277 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 zeroday nodemcu.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -------------------------------------------------------------------------------
* Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
* following additional terms:
*
* Except as contained in this notice, the name of Dallas Semiconductor
* shall not be used except as stated in the Dallas Semiconductor
* Branding Policy.
*/
#ifndef ONEWIRE_H_
#define ONEWIRE_H_
#include <stdbool.h>
#include <stdint.h>
#include "driver/gpio.h"
/**
* Type used to hold all 1-Wire device ROM addresses (64-bit)
*/
typedef uint64_t onewire_addr_t;
/**
* Structure to contain the current state for onewire_search_next(), etc
*/
typedef struct
{
uint8_t rom_no[8];
uint8_t last_discrepancy;
bool last_device_found;
} onewire_search_t;
/**
* ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device
* (CRC mismatch), and so can be useful as an indicator for "no-such-device",
* etc.
*/
#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL))
/**
* @brief Perform a 1-Wire reset cycle.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` if at least one device responds with a presence pulse,
* `false` if no devices were detected (or the bus is shorted, etc)
*/
bool onewire_reset(gpio_num_t pin);
/**
* @brief Issue a 1-Wire "ROM select" command to select a particular device.
*
* It is necessary to call ::onewire_reset() before calling this function.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param addr The ROM address of the device to select
*
* @return `true` if the "ROM select" command could be successfully issued,
* `false` if there was an error.
*/
bool onewire_select(gpio_num_t pin, const onewire_addr_t addr);
/**
* @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus.
*
* It is necessary to call ::onewire_reset() before calling this function.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` if the "skip ROM" command could be successfully issued,
* `false` if there was an error.
*/
bool onewire_skip_rom(gpio_num_t pin);
/**
* @brief Write a byte on the onewire bus.
*
* The writing code uses open-drain mode and expects the pullup resistor to
* pull the line high when not driven low. If you need strong power after the
* write (e.g. DS18B20 in parasite power mode) then call ::onewire_power()
* after this is complete to actively drive the line high.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param v The byte value to write
*
* @return `true` if successful, `false` on error.
*/
bool onewire_write(gpio_num_t pin, uint8_t v);
/**
* @brief Write multiple bytes on the 1-Wire bus.
*
* See ::onewire_write() for more info.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param buf A pointer to the buffer of bytes to be written
* @param count Number of bytes to write
*
* @return `true` if all bytes written successfully, `false` on error.
*/
bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count);
/**
* @brief Read a byte from a 1-Wire device.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return the read byte on success, negative value on error.
*/
int onewire_read(gpio_num_t pin);
/**
* @brief Read multiple bytes from a 1-Wire device.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param[out] buf A pointer to the buffer to contain the read bytes
* @param count Number of bytes to read
*
* @return `true` on success, `false` on error.
*/
bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count);
/**
* @brief Actively drive the bus high to provide extra power for certain
* operations of parasitically-powered devices.
*
* For parasitically-powered devices which need more power than can be
* provided via the normal pull-up resistor, it may be necessary for some
* operations to drive the bus actively high. This function can be used to
* perform that operation.
*
* The bus can be depowered once it is no longer needed by calling
* ::onewire_depower(), or it will be depowered automatically the next time
* ::onewire_reset() is called to start another command.
*
* @note Make sure the device(s) you are powering will not pull more current
* than the ESP32/ESP8266 is able to supply via its GPIO pins (this is
* especially important when multiple devices are on the same bus and
* they are all performing a power-intensive operation at the same time
* (i.e. multiple DS18B20 sensors, which have all been given a
* "convert T" operation by using ::onewire_skip_rom())).
*
* @note This routine will check to make sure that the bus is already high
* before driving it, to make sure it doesn't attempt to drive it high
* while something else is pulling it low (which could cause a reset or
* damage the ESP32/ESP8266).
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` on success, `false` on error.
*/
bool onewire_power(gpio_num_t pin);
/**
* @brief Stop forcing power onto the bus.
*
* You only need to do this if you previously called ::onewire_power() to drive
* the bus high and now want to allow it to float instead. Note that
* onewire_reset() will also automatically depower the bus first, so you do
* not need to call this first if you just want to start a new operation.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*/
void onewire_depower(gpio_num_t pin);
/**
* @brief Clear the search state so that it will start from the beginning on
* the next call to ::onewire_search_next().
*
* @param[out] search The onewire_search_t structure to reset.
*/
void onewire_search_start(onewire_search_t *search);
/**
* @brief Setup the search to search for devices with the specified
* "family code".
*
* @param[out] search The onewire_search_t structure to update.
* @param family_code The "family code" to search for.
*/
void onewire_search_prefix(onewire_search_t *search, uint8_t family_code);
/**
* @brief Search for the next device on the bus.
*
* The order of returned device addresses is deterministic. You will always
* get the same devices in the same order.
*
* @note It might be a good idea to check the CRC to make sure you didn't get
* garbage.
*
* @return the address of the next device on the bus, or ::ONEWIRE_NONE if
* there is no next address. ::ONEWIRE_NONE might also mean that
* the bus is shorted, there are no devices, or you have already
* retrieved all of them.
*/
onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin);
/**
* @brief Compute a Dallas Semiconductor 8 bit CRC.
*
* These are used in the ROM address and scratchpad registers to verify the
* transmitted data is correct.
*/
uint8_t onewire_crc8(const uint8_t *data, uint8_t len);
/**
* @brief Compute the 1-Wire CRC16 and compare it against the received CRC.
*
* Example usage (reading a DS2408):
* @code{.c}
* // Put everything in a buffer so we can compute the CRC easily.
* uint8_t buf[13];
* buf[0] = 0xF0; // Read PIO Registers
* buf[1] = 0x88; // LSB address
* buf[2] = 0x00; // MSB address
* onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes
* onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
* if (!onewire_check_crc16(buf, 11, &buf[11])) {
* // TODO: Handle error.
* }
* @endcode
*
* @param input Array of bytes to checksum.
* @param len Number of bytes in `input`
* @param inverted_crc The two CRC16 bytes in the received data.
* This should just point into the received data,
* *not* at a 16-bit integer.
* @param crc_iv The crc starting value (optional)
*
* @return `true` if the CRC matches, `false` otherwise.
*/
bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv);
/**
* @brief Compute a Dallas Semiconductor 16 bit CRC.
*
* This is required to check the integrity of data received from many 1-Wire
* devices. Note that the CRC computed here is *not* what you'll get from the
* 1-Wire network, for two reasons:
*
* 1. The CRC is transmitted bitwise inverted.
* 2. Depending on the endian-ness of your processor, the binary
* representation of the two-byte return value may have a different
* byte order than the two bytes you get from 1-Wire.
*
* @param input Array of bytes to checksum.
* @param len How many bytes are in `input`.
* @param crc_iv The crc starting value (optional)
*
* @return the CRC16, as defined by Dallas Semiconductor.
*/
uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv);
#endif /* ONEWIRE_H_ */

View File

@@ -1,24 +1,17 @@
#include "peripherals.h" #include "peripherals.h"
#include "adc.h" #include "adc.h"
#include "led.h"
//#include "buzzer.h"
#include "proximity.h" #include "proximity.h"
#include "ac_relay.h" #include "ac_relay.h"
#include "socket_lock.h" #include "socket_lock.h"
#include "rcm.h" #include "rcm.h"
#include "aux_io.h"
#include "ntc_sensor.h" #include "ntc_sensor.h"
void peripherals_init(void) void peripherals_init(void)
{ {
ac_relay_init(); ac_relay_init();
led_init();
//buzzer_init();
adc_init(); adc_init();
proximity_init(); proximity_init();
// socket_lock_init(); // socket_lock_init();
// rcm_init(); // rcm_init();
//energy_meter_init();
// aux_init();
ntc_sensor_init(); ntc_sensor_init();
} }

View File

@@ -4,12 +4,15 @@
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/timers.h" #include "freertos/timers.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "nvs.h"
#include "socket_lock.h" #include "socket_lock.h"
#include "board_config.h" #include "board_config.h"
// NEW:
#include "storage_service.h"
#define NVS_NAMESPACE "socket_lock" #define NVS_NAMESPACE "socket_lock"
#define NVS_OPERATING_TIME "op_time" #define NVS_OPERATING_TIME "op_time"
#define NVS_BREAK_TIME "break_time" #define NVS_BREAK_TIME "break_time"
@@ -27,20 +30,68 @@
static const char* TAG = "socket_lock"; static const char* TAG = "socket_lock";
static nvs_handle_t nvs; // Storage timeouts (ajusta se quiseres)
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
static uint16_t operating_time = 300; static uint16_t operating_time = 300;
static uint16_t break_time = 1000; static uint16_t break_time = 1000;
static bool detection_high = false;
static bool detection_high;
static uint8_t retry_count = 5; static uint8_t retry_count = 5;
static socket_lock_status_t status; static socket_lock_status_t status;
static TaskHandle_t socket_lock_task; static TaskHandle_t socket_lock_task;
// -----------------------------------------------------------------------------
// Helpers storage (best effort) - iguais ao estilo do wifi.c
// -----------------------------------------------------------------------------
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK) return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_u16_best_effort(const char *ns, const char *key, uint16_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u16_async(ns, key, v);
if (e == ESP_OK) return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// -----------------------------------------------------------------------------
// Lock logic
// -----------------------------------------------------------------------------
static bool is_locked(void) static bool is_locked(void)
{ {
gpio_set_level(board_config.socket_lock_a_gpio, 1); gpio_set_level(board_config.socket_lock_a_gpio, 1);
@@ -58,31 +109,42 @@ bool socket_lock_is_locked_state(void)
static void socket_lock_task_func(void* param) static void socket_lock_task_func(void* param)
{ {
uint32_t notification; (void)param;
uint32_t notification;
TickType_t previous_tick = 0; TickType_t previous_tick = 0;
uint8_t attempt = 0; uint8_t attempt = 0;
while (true) { while (true)
if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY)) { {
if (notification & (LOCK_BIT | UNLOCK_BIT)) { if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY))
{
if (notification & (LOCK_BIT | UNLOCK_BIT))
{
attempt = retry_count; attempt = retry_count;
} }
if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT)) { if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT))
{
gpio_set_level(board_config.socket_lock_a_gpio, 0); gpio_set_level(board_config.socket_lock_a_gpio, 0);
gpio_set_level(board_config.socket_lock_b_gpio, 1); gpio_set_level(board_config.socket_lock_b_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(operating_time)); vTaskDelay(pdMS_TO_TICKS(operating_time));
if (!is_locked()) { if (!is_locked())
{
ESP_LOGI(TAG, "Unlock OK"); ESP_LOGI(TAG, "Unlock OK");
status = SOCKED_LOCK_STATUS_IDLE; status = SOCKED_LOCK_STATUS_IDLE;
} else { }
if (attempt > 1) { else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not unlocked yet, repeating..."); ESP_LOGW(TAG, "Not unlocked yet, repeating...");
attempt--; attempt--;
xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits); xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits);
} else { }
else
{
ESP_LOGE(TAG, "Not unlocked"); ESP_LOGE(TAG, "Not unlocked");
status = SOCKED_LOCK_STATUS_UNLOCKING_FAIL; status = SOCKED_LOCK_STATUS_UNLOCKING_FAIL;
} }
@@ -90,23 +152,33 @@ static void socket_lock_task_func(void* param)
gpio_set_level(board_config.socket_lock_a_gpio, 0); gpio_set_level(board_config.socket_lock_a_gpio, 0);
gpio_set_level(board_config.socket_lock_b_gpio, 0); gpio_set_level(board_config.socket_lock_b_gpio, 0);
} else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT)) { }
if (notification & LOCK_BIT) { else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT))
{
if (notification & LOCK_BIT)
{
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); // delay before first lock attempt vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); // delay before first lock attempt
} }
gpio_set_level(board_config.socket_lock_a_gpio, 1); gpio_set_level(board_config.socket_lock_a_gpio, 1);
gpio_set_level(board_config.socket_lock_b_gpio, 0); gpio_set_level(board_config.socket_lock_b_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(operating_time)); vTaskDelay(pdMS_TO_TICKS(operating_time));
if (is_locked()) { if (is_locked())
{
ESP_LOGI(TAG, "Lock OK"); ESP_LOGI(TAG, "Lock OK");
status = SOCKED_LOCK_STATUS_IDLE; status = SOCKED_LOCK_STATUS_IDLE;
} else { }
if (attempt > 1) { else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not locked yet, repeating..."); ESP_LOGW(TAG, "Not locked yet, repeating...");
attempt--; attempt--;
xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits); xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits);
} else { }
else
{
ESP_LOGE(TAG, "Not locked"); ESP_LOGE(TAG, "Not locked");
status = SOCKED_LOCK_STATUS_LOCKING_FAIL; status = SOCKED_LOCK_STATUS_LOCKING_FAIL;
} }
@@ -117,7 +189,8 @@ static void socket_lock_task_func(void* param)
} }
TickType_t delay_tick = xTaskGetTickCount() - previous_tick; TickType_t delay_tick = xTaskGetTickCount() - previous_tick;
if (delay_tick < pdMS_TO_TICKS(break_time)) { if (delay_tick < pdMS_TO_TICKS(break_time))
{
vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick); vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick);
} }
previous_tick = xTaskGetTickCount(); previous_tick = xTaskGetTickCount();
@@ -125,20 +198,43 @@ static void socket_lock_task_func(void* param)
} }
} }
// -----------------------------------------------------------------------------
// Init / API pública
// -----------------------------------------------------------------------------
void socket_lock_init(void) void socket_lock_init(void)
{ {
if (board_config.socket_lock) { if (!board_config.socket_lock)
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); return;
nvs_get_u16(nvs, NVS_OPERATING_TIME, &operating_time); // garante storage pronto
esp_err_t se = storage_service_init();
if (se != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(se));
nvs_get_u16(nvs, NVS_BREAK_TIME, &break_time); // Load config (best effort; se não existir, fica default)
{
uint16_t u16 = 0;
uint8_t u8 = 0;
nvs_get_u8(nvs, NVS_RETRY_COUNT, &retry_count); esp_err_t e = storage_get_u16_sync(NVS_NAMESPACE, NVS_OPERATING_TIME, &u16, STORE_TO);
if (e == ESP_OK) operating_time = u16;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_OPERATING_TIME, esp_err_to_name(e));
uint8_t u8; e = storage_get_u16_sync(NVS_NAMESPACE, NVS_BREAK_TIME, &u16, STORE_TO);
if (nvs_get_u8(nvs, NVS_DETECTION_HIGH, &u8) == ESP_OK) { if (e == ESP_OK) break_time = u16;
detection_high = u8; else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_BREAK_TIME, esp_err_to_name(e));
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_RETRY_COUNT, &u8, STORE_TO);
if (e == ESP_OK) retry_count = u8;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_RETRY_COUNT, esp_err_to_name(e));
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_DETECTION_HIGH, &u8, STORE_TO);
if (e == ESP_OK) detection_high = (u8 != 0);
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_DETECTION_HIGH, esp_err_to_name(e));
} }
gpio_config_t io_conf = {}; gpio_config_t io_conf = {};
@@ -151,8 +247,7 @@ void socket_lock_init(void)
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio); io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf)); ESP_ERROR_CHECK(gpio_config(&io_conf));
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task); xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 2, &socket_lock_task);
}
} }
bool socket_lock_is_detection_high(void) bool socket_lock_is_detection_high(void)
@@ -164,8 +259,11 @@ void socket_lock_set_detection_high(bool _detection_high)
{ {
detection_high = _detection_high; detection_high = _detection_high;
nvs_set_u8(nvs, NVS_DETECTION_HIGH, detection_high); esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_DETECTION_HIGH, detection_high ? 1 : 0);
nvs_commit(nvs); if (e != ESP_OK)
ESP_LOGW(TAG, "persist detect_hi failed: %s", esp_err_to_name(e));
(void)store_flush_best_effort();
} }
uint16_t socket_lock_get_operating_time(void) uint16_t socket_lock_get_operating_time(void)
@@ -175,15 +273,22 @@ uint16_t socket_lock_get_operating_time(void)
esp_err_t socket_lock_set_operating_time(uint16_t _operating_time) esp_err_t socket_lock_set_operating_time(uint16_t _operating_time)
{ {
if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX) { if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX)
{
ESP_LOGE(TAG, "Operating time out of range"); ESP_LOGE(TAG, "Operating time out of range");
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
operating_time = _operating_time; operating_time = _operating_time;
nvs_set_u16(nvs, NVS_OPERATING_TIME, operating_time);
nvs_commit(nvs);
esp_err_t e = store_set_u16_best_effort(NVS_NAMESPACE, NVS_OPERATING_TIME, operating_time);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "persist op_time failed: %s", esp_err_to_name(e));
return e;
}
(void)store_flush_best_effort();
return ESP_OK; return ESP_OK;
} }
@@ -195,8 +300,12 @@ uint8_t socket_lock_get_retry_count(void)
void socket_lock_set_retry_count(uint8_t _retry_count) void socket_lock_set_retry_count(uint8_t _retry_count)
{ {
retry_count = _retry_count; retry_count = _retry_count;
nvs_set_u8(nvs, NVS_RETRY_COUNT, retry_count);
nvs_commit(nvs); esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_RETRY_COUNT, retry_count);
if (e != ESP_OK)
ESP_LOGW(TAG, "persist retry_count failed: %s", esp_err_to_name(e));
(void)store_flush_best_effort();
} }
uint16_t socket_lock_get_break_time(void) uint16_t socket_lock_get_break_time(void)
@@ -206,15 +315,22 @@ uint16_t socket_lock_get_break_time(void)
esp_err_t socket_lock_set_break_time(uint16_t _break_time) esp_err_t socket_lock_set_break_time(uint16_t _break_time)
{ {
if (_break_time < board_config.socket_lock_min_break_time) { if (_break_time < board_config.socket_lock_min_break_time)
ESP_LOGE(TAG, "Operating time out of range"); {
ESP_LOGE(TAG, "Break time out of range");
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
break_time = _break_time; break_time = _break_time;
nvs_set_u16(nvs, NVS_BREAK_TIME, break_time);
nvs_commit(nvs);
esp_err_t e = store_set_u16_best_effort(NVS_NAMESPACE, NVS_BREAK_TIME, break_time);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "persist break_time failed: %s", esp_err_to_name(e));
return e;
}
(void)store_flush_best_effort();
return ESP_OK; return ESP_OK;
} }

View File

@@ -38,7 +38,7 @@ void temp_sensor_init(void)
lm75a_init(); lm75a_init();
xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 5, NULL); xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 2, NULL);
} }
uint8_t temp_sensor_get_count(void) uint8_t temp_sensor_get_count(void)

View File

@@ -1,14 +1,12 @@
idf_component_register( idf_component_register(
SRCS SRCS
"src/protocols.c" "src/protocols.c"
"src/json.c"
"src/mqtt.c" "src/mqtt.c"
INCLUDE_DIRS INCLUDE_DIRS
"include" "include"
PRIV_INCLUDE_DIRS PRIV_INCLUDE_DIRS
"src" "src"
PRIV_REQUIRES PRIV_REQUIRES
nvs_flash
mqtt mqtt
cjson cjson
vfs vfs
@@ -19,6 +17,7 @@ idf_component_register(
config config
evse evse
peripherals peripherals
meter_manager
ocpp ocpp
auth auth
) )

Some files were not shown because too many files have changed in this diff Show More