Compare commits

2 Commits
v1.0.0 ... main

Author SHA1 Message Date
286028b6a8 fix evse_link 2026-01-24 16:56:51 +00:00
023644a887 new upgrade 2025-12-21 23:28:26 +00:00
123 changed files with 10151 additions and 7949 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

@@ -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 evse ocpp evse_link) REQUIRES esp_event evse ocpp evse_link storage_service)

View File

@@ -4,19 +4,22 @@
#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> // 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 "evse_link.h" #include "evse_link.h"
#include "evse_link_events.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,162 +28,16 @@ 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; // controla se o Wiegand já foi iniciado 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;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No stored tags in NVS (nvs_open: %s)", esp_err_to_name(err));
return;
}
uint8_t count = 0;
err = nvs_get_u8(handle, NVS_TAG_COUNT_KEY, &count);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No tag count key in NVS (nvs_get_u8: %s)", esp_err_to_name(err));
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);
err = nvs_get_str(handle, key, tag_buf, &len);
if (err == 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++;
}
else
{
ESP_LOGW(TAG, "Failed to load tag %d from NVS (%s)", i, esp_err_to_name(err));
}
}
nvs_close(handle);
ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count);
}
static void save_tags_to_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS to save tags: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(count) failed: %s", esp_err_to_name(err));
nvs_close(handle);
return;
}
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = nvs_set_str(handle, key, valid_tags[i]);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_str(%s) failed: %s", key, esp_err_to_name(err));
nvs_close(handle);
return;
}
}
err = nvs_commit(handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving tags: %s", esp_err_to_name(err));
nvs_close(handle);
return;
}
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;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_OK)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
err = nvs_get_u8(h, NVS_MODE_KEY, &u);
if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
}
else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_get_u8: %s). Default OPEN", esp_err_to_name(err));
}
nvs_close(h);
}
else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_open: %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_nvs(auth_mode_t mode)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS to save auth mode: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving mode: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
nvs_close(h);
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}
/* ========================= /* =========================
* Helpers * Helpers
@@ -190,37 +47,155 @@ static bool is_tag_valid(const char *tag)
for (int i = 0; i < tag_count; i++) for (int i = 0; i < tag_count; i++)
{ {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) 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) * 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) 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) if (base != EVSE_LINK_EVENTS || id != LINK_EVENT_REMOTE_AUTH_GRANTED || data == NULL)
{
return; return;
}
const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data; const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data;
// Só faz sentido em SLAVE; em MASTER este evento não deve aparecer
if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE) if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE)
{
return; return;
}
auth_tag_event_data_t ev = {0}; auth_tag_event_data_t ev = {0};
strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1); strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0'; ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true; ev.authorized = true;
ESP_LOGI(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag); ESP_LOGD(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
esp_err_t err = esp_event_post( esp_err_t err = esp_event_post(
AUTH_EVENTS, AUTH_EVENTS,
@@ -241,8 +216,11 @@ static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, v
* ========================= */ * ========================= */
void auth_init(void) void auth_init(void)
{ {
load_mode_from_nvs(); // garantir que o storage service está pronto
load_tags_from_nvs(); ESP_ERROR_CHECK(storage_service_init());
load_mode_from_storage();
load_tags_from_storage();
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID); bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand) if (need_wiegand)
@@ -256,7 +234,7 @@ void auth_init(void)
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started"); ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
} }
// Registar bridge para autorizações remotas vindas do EVSE-Link // bridge EVSE-Link -> AUTH
{ {
esp_err_t err = esp_event_handler_register( esp_err_t err = esp_event_handler_register(
EVSE_LINK_EVENTS, EVSE_LINK_EVENTS,
@@ -285,44 +263,34 @@ void auth_set_mode(auth_mode_t mode)
if (mode == s_mode) if (mode == s_mode)
{ {
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode)); ESP_LOGD(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return; return;
} }
auth_mode_t old = s_mode; auth_mode_t old = s_mode;
s_mode = mode; s_mode = mode;
save_mode_to_nvs(mode); save_mode_to_storage(mode);
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID); bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand && !s_wiegand_started) if (need_wiegand && !s_wiegand_started)
{ {
ESP_LOGI(TAG, "Mode changed %s -> %s, starting Wiegand", ESP_LOGD(TAG, "Mode changed %s -> %s, starting Wiegand",
auth_mode_to_str(old), auth_mode_to_str(s_mode)); auth_mode_to_str(old), auth_mode_to_str(s_mode));
initWiegand(); initWiegand();
s_wiegand_started = true; s_wiegand_started = true;
} }
else if (!need_wiegand && s_wiegand_started) else if (!need_wiegand && s_wiegand_started)
{ {
// Aqui poderias implementar um wiegand_deinit() se o driver o expuser. ESP_LOGD(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
ESP_LOGI(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
auth_mode_to_str(old), auth_mode_to_str(s_mode)); auth_mode_to_str(old), auth_mode_to_str(s_mode));
} }
else else
{ {
ESP_LOGI(TAG, "Mode changed %s -> %s, no change in Wiegand state", 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_to_str(old), auth_mode_to_str(s_mode));
} }
if (s_mode == AUTH_MODE_OPEN)
{
ESP_LOGI(TAG, "Mode set to OPEN");
}
else
{
ESP_LOGI(TAG, "Mode set to %s", 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);
} }
@@ -338,15 +306,16 @@ bool auth_add_tag(const char *tag)
return false; return false;
if (tag_count >= MAX_TAGS) if (tag_count >= MAX_TAGS)
return false; return false;
if (is_tag_valid(tag)) if (is_tag_valid(tag))
return true; // já existe 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;
} }
@@ -366,8 +335,8 @@ bool auth_remove_tag(const char *tag)
} }
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;
} }
} }
@@ -383,11 +352,9 @@ bool auth_tag_exists(const char *tag)
void auth_list_tags(void) void auth_list_tags(void)
{ {
ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); ESP_LOGD(TAG, "Registered Tags (%d):", tag_count);
for (int i = 0; i < tag_count; i++) for (int i = 0; i < tag_count; i++)
{ ESP_LOGD(TAG, "- %s", valid_tags[i]);
ESP_LOGI(TAG, "- %s", valid_tags[i]);
}
} }
void auth_wait_for_tag_registration(void) void auth_wait_for_tag_registration(void)
@@ -398,7 +365,7 @@ void auth_wait_for_tag_registration(void)
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)
@@ -412,11 +379,8 @@ void auth_process_tag(const char *tag)
switch (s_mode) switch (s_mode)
{ {
case AUTH_MODE_OPEN: case AUTH_MODE_OPEN:
{ ESP_LOGD(TAG, "Mode OPEN: tag=%s (no verification)", tag);
// Sem verificação; normalmente nem é necessário evento.
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break; break;
}
case AUTH_MODE_LOCAL_RFID: case AUTH_MODE_LOCAL_RFID:
{ {
@@ -430,9 +394,10 @@ void auth_process_tag(const char *tag)
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.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true; ev.authorized = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
&ev, sizeof(ev), portMAX_DELAY); &ev, sizeof(ev), portMAX_DELAY);
ESP_LOGI(TAG, "Tag registered: %s", tag); ESP_LOGD(TAG, "Tag registered: %s", tag);
} }
else else
{ {
@@ -446,8 +411,9 @@ void auth_process_tag(const char *tag)
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0'; 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, ESP_LOGD(TAG, "LOCAL tag %s: %s", tag,
ev.authorized ? "AUTHORIZED" : "DENIED"); ev.authorized ? "AUTHORIZED" : "DENIED");
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
&ev, sizeof(ev), portMAX_DELAY); &ev, sizeof(ev), portMAX_DELAY);
break; break;
@@ -455,13 +421,14 @@ void auth_process_tag(const char *tag)
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.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)",
ESP_LOGD(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
rq.tag, (unsigned)rq.req_id); rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
&rq, sizeof(rq), portMAX_DELAY); &rq, sizeof(rq), portMAX_DELAY);
break; break;

View File

@@ -301,7 +301,7 @@ void initWiegand(void)
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: // Para testes, podes ativar o simulador:
//ESP_LOGI(TAG, "Inicializando Wiegand simulado"); // ESP_LOGI(TAG, "Inicializando Wiegand simulado");
//xTaskCreate(wiegand_sim_task, "WiegandSim", // xTaskCreate(wiegand_sim_task, "WiegandSim",
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL); // 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

@@ -398,7 +398,7 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
if (base != NETWORK_EVENTS) if (base != NETWORK_EVENTS)
return; return;
ESP_LOGI(TAG, "Network event id=%d", (int)id); ESP_LOGD(TAG, "Network event id=%d", (int)id);
buzzer_event_data_t evt = {0}; buzzer_event_data_t evt = {0};

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

@@ -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 scheduler REQUIRES peripherals auth loadbalancer scheduler storage_service
) )

View File

@@ -1,215 +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" #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 inicializado a partir do default persistido // runtime inicializa a partir do default
charging_current_runtime = 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;
}
// Socket outlet if (wanted && !board_config.proximity)
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
{
is_available = true; // default
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
{
is_enabled = true; // default
nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
needs_commit = true;
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
}
if (needs_commit)
{
err = nvs_commit(nvs);
if (err == ESP_OK)
{ {
ESP_LOGD(TAG, "Configuration committed to NVS."); // 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));
}
ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
} }
else else
{ {
ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); socket_outlet = wanted;
} }
} }
else
{
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 default=0: %s", esp_err_to_name(se));
}
ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
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)
{
bool wanted = (u8 != 0);
if (wanted && !board_config.rcm)
{
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
{
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);
}
uint16_t evse_get_default_charging_current(void) esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_chrg_curr", value);
{ if (err != ESP_OK)
uint16_t value; {
if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK) // Em runtime, isto pode falhar por fila cheia. RAM fica correta; persistência é best-effort.
return value; ESP_LOGE(TAG, "Failed to persist def_chrg_curr async=%u: %s", (unsigned)value, esp_err_to_name(err));
return charging_current; return err;
} }
esp_err_t evse_set_default_charging_current(uint16_t value) evse_set_runtime_charging_current(value);
{ return ESP_OK;
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,135 +292,119 @@ 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);
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;
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available); is_available = newv;
if (err == ESP_OK)
err = nvs_commit(nvs); esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "available", (uint8_t)is_available);
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));
}
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;
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled); is_enabled = newv;
if (err == ESP_OK)
err = nvs_commit(nvs); esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "enabled", (uint8_t)is_enabled);
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));
}
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,3 +1,4 @@
// 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"
@@ -14,6 +15,36 @@ 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_process(void);
static void evse_core_task(void *arg); static void evse_core_task(void *arg);
@@ -32,7 +63,8 @@ void evse_init(void)
evse_fsm_reset(); evse_fsm_reset();
pilot_set_level(true); 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) static void evse_process(void)
@@ -44,19 +76,27 @@ static 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);
// ✅ 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();

View File

@@ -1,5 +1,4 @@
#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"
@@ -7,151 +6,147 @@
#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";
// Estado global de erros // ----------------------------------------------------
static uint32_t error_bits = 0; // Estado interno
static TickType_t auto_clear_timeout = 0; // ----------------------------------------------------
// 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 foram limpos" // Sticky flag: "todos erros visíveis foram limpos"
static bool error_cleared = false; static bool error_cleared = false;
// Proteção contra concorrência // Proteção contra concorrência
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
void evse_error_init(void) // ----------------------------------------------------
// Helper: publicar evento de alteração de erro (visible_bits)
// ----------------------------------------------------
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(),
};
esp_err_t err = esp_event_post(
EVSE_EVENTS,
EVSE_EVENT_ERROR_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
esp_err_to_name(err));
}
}
// ----------------------------------------------------
// Helpers internos
// ----------------------------------------------------
static bool raw_has_bit(uint32_t bit)
{
bool v;
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
error_bits = 0; v = ((raw_bits & bit) != 0);
auto_clear_timeout = 0;
error_cleared = false;
portEXIT_CRITICAL(&error_mux); portEXIT_CRITICAL(&error_mux);
return v;
} }
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) static void reconcile_visible_locked(TickType_t now)
{ {
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s", // Se existem erros reais, o visível segue imediatamente
pilot_voltage, is_n12v ? "true" : "false"); if (raw_bits != 0)
// 1) Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1)
{ {
bool first_time = false; visible_bits = raw_bits;
clear_deadline = 0;
portENTER_CRITICAL(&error_mux); error_cleared = false;
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_PILOT_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
}
}
else
{
// Pilot voltou a nível válido → limpa erro de pilot fault
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)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_DIODE_SHORT_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
}
else
{
// Se já não estamos em C/D sem -12V, limpa o erro de diodo curto
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)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
return; return;
} }
// Leitura válida -> limpa erro de sensor // raw_bits == 0
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); if (visible_bits == 0)
// Temperatura máxima
if (temp_c >= threshold)
{ {
bool first_time = false; clear_deadline = 0;
return;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_HIGH_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
temp_c, threshold);
}
} }
else
// Ainda há erro visível (holdoff). Arma deadline 1x.
if (clear_deadline == 0)
{ {
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
return;
}
// Expirou -> limpar finalmente
if ((int32_t)(now - clear_deadline) >= 0)
{
visible_bits = 0;
clear_deadline = 0;
error_cleared = true;
}
}
// ----------------------------------------------------
// API pública
// ----------------------------------------------------
void evse_error_init(void)
{
uint32_t old_vis, new_vis, changed;
bool post = false;
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
raw_bits = 0;
visible_bits = 0;
clear_deadline = 0;
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);
} }
} }
uint32_t evse_get_error(void) uint32_t evse_get_error(void)
{ {
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
uint32_t val = error_bits; uint32_t val = visible_bits;
portEXIT_CRITICAL(&error_mux); portEXIT_CRITICAL(&error_mux);
return val; 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) bool evse_error_cleared_flag(void)
{ {
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
@@ -169,61 +164,147 @@ void evse_error_reset_flag(void)
void evse_error_set(uint32_t bitmask) void evse_error_set(uint32_t bitmask)
{ {
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
error_cleared = false; old_vis = visible_bits;
error_bits |= bitmask;
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) raw_bits |= bitmask;
{ // se aparece qualquer erro, o "cleared" deixa de ser verdade
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s error_cleared = false;
}
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux); portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
} }
void evse_error_clear(uint32_t bitmask) void evse_error_clear(uint32_t bitmask)
{ {
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
bool had_error = (error_bits != 0); old_vis = visible_bits;
error_bits &= ~bitmask;
if (had_error && error_bits == 0) raw_bits &= ~bitmask;
{
error_cleared = true; // ✅ 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); portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
} }
void evse_error_tick(void) void evse_error_tick(void)
{ {
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux); portENTER_CRITICAL(&error_mux);
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && old_vis = visible_bits;
auto_clear_timeout != 0 && reconcile_visible_locked(now);
xTaskGetTickCount() >= auto_clear_timeout) new_vis = visible_bits;
{ changed = old_vis ^ new_vis;
error_bits &= ~EVSE_ERR_AUTO_CLEAR_BITS;
if (error_bits == 0)
{
error_cleared = true;
}
auto_clear_timeout = 0;
}
portEXIT_CRITICAL(&error_mux); portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
} }
bool evse_error_is_active(void) // ----------------------------------------------------
// Checks (raw -> set/clear)
// ----------------------------------------------------
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
{ {
return evse_get_error() != 0; 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);
}
} }
uint32_t evse_error_get_bits(void) void evse_temperature_check(void)
{ {
return evse_get_error(); 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,16 +18,14 @@ 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;
static TickType_t c1_d1_relay_to = 0;
void evse_fsm_reset(void) 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;
} }
/**
* @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();
@@ -38,7 +37,7 @@ static void update_outputs(evse_state_t state)
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())
@@ -46,8 +45,14 @@ static void update_outputs(evse_state_t 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); else
{
ac_relay_set_state(false);
}
// Em erro, garantir pilot OFF (não PWM / não +12V)
pilot_set_level(true); pilot_set_level(true);
if (board_config.socket_lock && socket_outlet) if (board_config.socket_lock && socket_outlet)
{ {
socket_lock_set_locked(false); socket_lock_set_locked(false);
@@ -55,14 +60,16 @@ static void update_outputs(evse_state_t state)
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);
@@ -72,66 +79,77 @@ 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)); pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false); 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)
{
socket_lock_set_locked(true);
}
break; 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); ac_relay_set_state(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break; break;
} }
} }
// FSM principal /**
* @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 (err_bits != 0)
{ {
if (evse_get_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 FAULT (F)"); ESP_LOGW(TAG, "Erro ativo detectado: forçando estado %s",
evse_set_state(EVSE_STATE_F); 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)
{ {
@@ -153,17 +171,25 @@ void evse_fsm_process(
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;
} }
@@ -171,52 +197,59 @@ 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)
{
ac_relay_set_state(false);
c1_d1_waiting = false;
if (!available)
{
evse_set_state(EVSE_STATE_F);
break;
}
}
__attribute__((fallthrough));
case EVSE_STATE_C2: case EVSE_STATE_C2:
case EVSE_STATE_D2: case EVSE_STATE_D2:
if (!enabled || !available) if (!available)
{ {
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1) evse_set_state(EVSE_STATE_F);
? EVSE_STATE_D1
: EVSE_STATE_C1);
break; break;
} }
if (!enabled)
{
if (curr == EVSE_STATE_C2)
{
evse_set_state(EVSE_STATE_C1);
}
else if (curr == EVSE_STATE_D2)
{
evse_set_state(EVSE_STATE_D1);
}
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);

View File

@@ -1,34 +1,123 @@
#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_err.h"
#include "nvs.h" #include "esp_check.h"
// ======================== #include "storage_service.h"
// Concurrency protection
// ======================== #define NVS_NAMESPACE "evse_limits"
static const char *TAG = "evse_limits";
static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
// ======================== static bool limit_reached = false;
// Runtime state (volatile) static uint32_t consumption_limit = 0; // Wh
// ======================== static uint32_t charging_time_limit = 0; // seconds
static uint16_t under_power_limit = 0; // W
static bool limit_reached = false; static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static uint32_t consumption_limit = 0; // Energy limit in Wh static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); }
static uint32_t charging_time_limit = 0; // Time limit in seconds
static uint16_t under_power_limit = 0; // Minimum acceptable power in W
// ======================== // ---------------------------------
// Limit status flag // Init + defaults
// ======================== // ---------------------------------
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)
{
esp_err_t err;
bool needs_flush = false;
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 evse_get_limit_reached(void)
{ {
bool val; bool val;
@@ -50,10 +139,9 @@ bool evse_is_limit_reached(void)
return evse_get_limit_reached(); return evse_get_limit_reached();
} }
// ======================== // ---------------------------------
// Runtime limit accessors // Consumption limit
// ======================== // ---------------------------------
uint32_t evse_get_consumption_limit(void) uint32_t evse_get_consumption_limit(void)
{ {
uint32_t val; uint32_t val;
@@ -78,30 +166,18 @@ void evse_set_consumption_limit(uint32_t value)
if (!changed) if (!changed)
return; return;
nvs_handle_t h; esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err != ESP_OK)
if (err == ESP_OK)
{ {
err = nvs_set_u32(h, "def_cons_lim", value); ESP_LOGE(TAG,
if (err == ESP_OK) "Failed to persist consumption limit (%" PRIu32 " Wh): %s",
err = nvs_commit(h); value, esp_err_to_name(err));
nvs_close(h);
if (err != ESP_OK)
{
ESP_LOGE("EVSE_LIMITS",
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
value, esp_err_to_name(err));
}
}
else
{
ESP_LOGE("EVSE_LIMITS",
"Failed to open NVS for consumption limit: %s",
esp_err_to_name(err));
} }
} }
// ---------------------------------
// Charging time limit
// ---------------------------------
uint32_t evse_get_charging_time_limit(void) uint32_t evse_get_charging_time_limit(void)
{ {
uint32_t val; uint32_t val;
@@ -126,30 +202,18 @@ void evse_set_charging_time_limit(uint32_t value)
if (!changed) if (!changed)
return; return;
nvs_handle_t h; esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err != ESP_OK)
if (err == ESP_OK)
{ {
err = nvs_set_u32(h, "def_ch_time_lim", value); ESP_LOGE(TAG,
if (err == ESP_OK) "Failed to persist charging time limit (%" PRIu32 " s): %s",
err = nvs_commit(h); value, esp_err_to_name(err));
nvs_close(h);
if (err != ESP_OK)
{
ESP_LOGE("EVSE_LIMITS",
"Failed to persist charging time limit (%" PRIu32 " s): %s",
value, esp_err_to_name(err));
}
}
else
{
ESP_LOGE("EVSE_LIMITS",
"Failed to open NVS for charging time limit: %s",
esp_err_to_name(err));
} }
} }
// ---------------------------------
// Under-power limit
// ---------------------------------
uint16_t evse_get_under_power_limit(void) uint16_t evse_get_under_power_limit(void)
{ {
uint16_t val; uint16_t val;
@@ -174,82 +238,64 @@ void evse_set_under_power_limit(uint16_t value)
if (!changed) if (!changed)
return; return;
nvs_handle_t h; esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err != ESP_OK)
if (err == ESP_OK)
{ {
err = nvs_set_u16(h, "def_un_pwr_lim", value); ESP_LOGE(TAG,
if (err == ESP_OK) "Failed to persist under-power limit (%" PRIu32 " W): %s",
err = nvs_commit(h); (uint32_t)value, esp_err_to_name(err));
nvs_close(h);
if (err != ESP_OK)
{
ESP_LOGE("EVSE_LIMITS",
"Failed to persist under-power limit (%" PRIu32 " W): %s",
(uint32_t)value, esp_err_to_name(err));
}
}
else
{
ESP_LOGE("EVSE_LIMITS",
"Failed to open NVS for under-power limit: %s",
esp_err_to_name(err));
} }
} }
// ======================== // ---------------------------------
// Limit checking logic // Runtime check
// ======================== // ---------------------------------
void evse_limits_check(void) void evse_limits_check(void)
{ {
// Só faz sentido durante carregamento // Só faz sentido quando há energia ativa (C2/D2)
if (!evse_state_is_charging(evse_get_state())) if (!evse_state_is_charging(evse_get_state()))
{
return; return;
}
evse_session_t sess; evse_session_t sess;
if (!evse_session_get(&sess) || !sess.is_current) if (!evse_session_get(&sess) || !sess.is_current)
{
// Sem sessão ativa → nada a fazer
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) Limite de energia (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) Limite de tempo (s) 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 (potência instantânea) 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)
if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
{ {
ESP_LOGW("EVSE_LIMITS", ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", inst_power, (uint32_t)unp_lim);
(uint32_t)inst_power,
(uint32_t)under_power_limit);
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"
@@ -11,10 +10,10 @@
#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_event.h"
#include "esp_err.h"
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
@@ -27,27 +26,29 @@
static const char *TAG = "EVSE_Manager"; static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex; static SemaphoreHandle_t evse_mutex;
static volatile bool auth_enabled = false;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer // Estado de pausa controlado pelo Load Balancer
static volatile bool lb_paused = false; static bool lb_paused = false;
static volatile bool lb_prev_authorized = false; static bool lb_prev_authorized = false;
// Estado de janela do scheduler // Estado de janela do scheduler
static volatile bool s_sched_allowed = true; static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
// ================= Helpers internos =================
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);
} }
// Exposto para outros módulos (se quiserem saber se o scheduler permite)
bool evse_sched_is_allowed(void) bool evse_sched_is_allowed(void)
{ {
bool v; bool v;
@@ -60,19 +61,35 @@ bool evse_sched_is_allowed(void)
static void evse_manager_handle_auth_on_tick(void) static void evse_manager_handle_auth_on_tick(void)
{ {
bool sched_allowed = evse_sched_is_allowed(); bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
if (auth_enabled) 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();
} }
// Em modos RFID/OCPP, o scheduler pode também forçar paragem 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()) if (!sched_allowed && evse_state_get_authorized())
{ {
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization."); ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
@@ -81,28 +98,32 @@ static void evse_manager_handle_auth_on_tick(void)
} }
else else
{ {
// Modo OPEN: só autoriza se LB e Scheduler permitirem bool limit_hit = evse_is_limit_reached();
if (!lb_paused && sched_allowed && !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())
{ {
evse_state_set_authorized(true); ESP_LOGI(TAG,
ESP_LOGI(TAG, "Authentication disabled → forced authorization (within schedule)."); "[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
lb_clear_pause_state(); (int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
evse_state_set_authorized(false);
} }
// Fora da janela, garantir que não fica autorizado if (!local_lb_paused && sched_allowed && can_operate &&
if (!sched_allowed && evse_state_get_authorized()) !has_error && !limit_hit &&
!evse_state_get_authorized())
{ {
ESP_LOGI(TAG, "[SCHED] window closed (OPEN mode) → revoking authorization."); evse_state_set_authorized(true);
evse_state_set_authorized(false); ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
lb_clear_pause_state();
} }
} }
} }
// ===== Task de ciclo principal =====
static void evse_manager_task(void *arg) static void evse_manager_task(void *arg)
{ {
(void)arg; (void)arg;
while (true) while (true)
{ {
evse_manager_tick(); evse_manager_tick();
@@ -110,15 +131,10 @@ static void evse_manager_task(void *arg)
} }
} }
// ===== Tratador de eventos de AUTH =====
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)
{ {
(void)arg; (void)arg;
if (base != AUTH_EVENTS || !data) return;
if (base != AUTH_EVENTS || !data)
return;
auth_mode_t g_mode = AUTH_MODE_OPEN;
switch (id) switch (id)
{ {
@@ -127,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;
} }
@@ -137,37 +151,26 @@ 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)
{
// Em OPEN, a autorização passa a ser gerida por evse_manager_handle_auth_on_tick(),
// que também respeita o scheduler.
evse_state_set_authorized(false); // vai ser forçado no próximo tick se permitido
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 Load Balancer =====
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)handler_arg;
(void)event_base; (void)event_base;
if (!event_data) if (!event_data) return;
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)
{ {
@@ -181,98 +184,86 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
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 e scheduler permitir 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_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/fora de horário)", "[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 (se dentro da janela do scheduler) 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);
} }
} }
} }
} }
// ===== Tratador de eventos de OCPP =====
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)
{ {
(void)arg; (void)arg;
if (base != OCPP_EVENTS) return;
if (base != OCPP_EVENTS)
return;
switch (id) switch (id)
{ {
@@ -283,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;
@@ -300,24 +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");
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)
@@ -329,7 +304,6 @@ 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);
break; break;
} }
@@ -340,16 +314,10 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
} }
} }
// ===== Tratador de eventos de Scheduler ===== static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
static void on_sched_event(void *arg,
esp_event_base_t base,
int32_t id,
void *data)
{ {
(void)arg; (void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
if (base != SCHED_EVENTS || data == NULL)
return;
const sched_event_state_t *ev = (const sched_event_state_t *)data; const sched_event_state_t *ev = (const sched_event_state_t *)data;
@@ -357,29 +325,26 @@ static void on_sched_event(void *arg,
s_sched_allowed = ev->allowed_now; s_sched_allowed = ev->allowed_now;
portEXIT_CRITICAL(&s_sched_mux); portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
"[SCHED] event id=%" PRIi32 " allowed_now=%d",
id, (int)ev->allowed_now);
// Se a janela fechou, parar sessão (revogar autorização)
if (!ev->allowed_now && evse_state_get_authorized()) if (!ev->allowed_now && evse_state_get_authorized())
{ {
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)"); ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false); evse_state_set_authorized(false);
} }
// Se a janela abriu de novo, não auto-reautorizamos aqui.
// Deixamos que o utilizador / OCPP decida iniciar nova sessão.
// (Em modo OPEN, o tick trata disso respeitando o scheduler.)
} }
// ===== Inicialização =====
void evse_manager_init(void) void evse_manager_init(void)
{ {
evse_mutex = xSemaphoreCreateMutex(); evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL); configASSERT(evse_mutex != NULL);
evse_config_init(); 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_error_init(); evse_error_init();
evse_hardware_init(); evse_hardware_init();
evse_state_init(); evse_state_init();
@@ -393,11 +358,10 @@ void evse_manager_init(void)
ESP_LOGI(TAG, "EVSE Manager inicializado."); ESP_LOGI(TAG, "EVSE Manager inicializado.");
BaseType_t rc = 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); 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);
@@ -412,5 +376,3 @@ void evse_manager_tick(void)
xSemaphoreGive(evse_mutex); xSemaphoreGive(evse_mutex);
} }
// === Fim de: components/evse/evse_manager.c ===

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"
@@ -13,153 +12,232 @@
#include "adc121s021_dma.h" #include "adc121s021_dma.h"
#include "board_config.h" #include "board_config.h"
#define PILOT_PWM_TIMER LEDC_TIMER_0 #define PILOT_PWM_TIMER LEDC_TIMER_0
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0 #define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE #define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
#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
#define NUM_PILOT_SAMPLES 100 // --- Configuração de amostragem do Pilot ---
#define MAX_SAMPLE_ATTEMPTS 1000 #define NUM_PILOT_SAMPLES 100
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior #define MAX_SAMPLE_ATTEMPTS 1000
#define ADC121_VREF_MV 3300 #define PILOT_SAMPLE_DELAY_US 10
#define ADC121_MAX 4095
// Percentagem para descartar extremos superior/inferior (ruído)
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
// ADC referência
#define ADC121_VREF_MV 3300
#define ADC121_MAX 4095
static const char *TAG = "evse_pilot"; static const char *TAG = "evse_pilot";
static int last_pilot_level = -1; typedef enum {
static uint32_t last_pwm_duty = 0; 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 int adc_raw_to_mv(uint16_t raw) { static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
return (raw * ADC121_VREF_MV) / ADC121_MAX; static uint32_t last_pwm_duty = 0;
// ---------------------
// Helpers internos
// ---------------------
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)
{ {
// 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,
.timer_sel = PILOT_PWM_TIMER, .timer_sel = PILOT_PWM_TIMER,
.intr_type = LEDC_INTR_DISABLE, .intr_type = LEDC_INTR_DISABLE,
.gpio_num = board_config.pilot_pwm_gpio, .gpio_num = board_config.pilot_pwm_gpio,
.duty = 0, .duty = 0,
.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));
// 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; 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) {
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; 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_percent = (amps * 10) / 6;
} else { }
else
{
duty_percent = (amps * 10) / 25 + 64; 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)
return (last_pilot_level == 1) && (last_pwm_duty == 0); {
} // "Alto" significa DC +12V (estado A). PWM não conta como DC high.
return (s_mode == PILOT_MODE_DC_HIGH);
static int compare_int(const void *a, const void *b) {
return (*(const int *)a - *(const int *)b);
} }
// ---------------------
// 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;
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
adc_sample = 0; {
if (adc121s021_dma_get_sample(&adc_sample)) { uint16_t 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;
} }
qsort(samples, collected, sizeof(int), compare_int); // Ordena as amostras para eliminar extremos (ruído/espúrios)
qsort(samples, collected, sizeof(uint16_t), compare_uint16);
int k = (collected * PILOT_EXTREME_PERCENT) / 100; int k = (collected * PILOT_EXTREME_PERCENT) / 100;
if (k == 0) k = 1; 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 low_index = k / 2;
int high_index = collected - k + (k / 2); int high_index = collected - 1 - (k / 2);
if (high_index >= collected) high_index = collected - 1;
int low_raw = samples[low_index]; if (low_index < 0) low_index = 0;
int high_raw = samples[high_index]; 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);
// 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

@@ -7,22 +7,49 @@
#include "evse_events.h" #include "evse_events.h"
#include "esp_event.h" #include "esp_event.h"
#include "evse_limits.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";
static TickType_t session_start_tick = 0; static TickType_t session_start_tick = 0;
static uint32_t watt_seconds = 0;
// 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; static uint32_t session_counter = 0;
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED; 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) void evse_session_init(void)
{ {
portENTER_CRITICAL(&session_mux); 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; session_counter = 0;
portEXIT_CRITICAL(&session_mux); portEXIT_CRITICAL(&session_mux);
@@ -31,55 +58,65 @@ void evse_session_init(void)
void evse_session_start(void) void evse_session_start(void)
{ {
TickType_t tick = xTaskGetTickCount(); TickType_t tick = xTaskGetTickCount();
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux); portENTER_CRITICAL(&session_mux);
session_start_tick = tick; session_start_tick = tick;
watt_seconds = 0; session_start_us = now_us;
last_tick_us = now_us;
watt_microseconds = 0;
session_counter++; session_counter++;
uint32_t id = session_counter;
portEXIT_CRITICAL(&session_mux); portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false); evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)tick); ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64,
id, (unsigned)tick, now_us);
evse_session_event_data_t evt = { evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED, .type = EVSE_SESSION_EVENT_STARTED,
.session_id = session_counter, .session_id = id,
.duration_s = 0, .duration_s = 0,
.energy_wh = 0, .energy_wh = 0,
.avg_power_w = 0, .avg_power_w = 0,
.is_current = true, .is_current = true,
}; };
post_session_event(&evt);
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
void evse_session_end(void) void evse_session_end(void)
{ {
TickType_t start_tick; TickType_t start_tick;
uint32_t ws; int64_t start_us;
uint64_t w_us;
uint32_t id; uint32_t id;
int64_t end_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux); portENTER_CRITICAL(&session_mux);
if (session_start_tick == 0) { if (session_start_tick == 0)
{
portEXIT_CRITICAL(&session_mux); 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;
} }
start_tick = session_start_tick; start_tick = session_start_tick;
ws = watt_seconds; start_us = session_start_us;
w_us = watt_microseconds;
id = session_counter; id = session_counter;
session_start_tick = 0; session_start_tick = 0;
session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
portEXIT_CRITICAL(&session_mux); portEXIT_CRITICAL(&session_mux);
TickType_t now = xTaskGetTickCount(); uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
uint32_t duration_s = (now - start_tick) / configTICK_RATE_HZ; uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint32_t energy_wh = ws / 3600U; uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0; uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
portENTER_CRITICAL(&session_mux); portENTER_CRITICAL(&session_mux);
last_session.start_tick = start_tick; last_session.start_tick = start_tick;
@@ -91,9 +128,9 @@ void evse_session_end(void)
portEXIT_CRITICAL(&session_mux); portEXIT_CRITICAL(&session_mux);
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 "Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
" Wh, avg_power=%" PRIu32 " W", " Wh, avg_power=%" PRIu32 " W",
duration_s, energy_wh, avg_power); id, duration_s, energy_wh, avg_power);
evse_session_event_data_t evt = { evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_FINISHED, .type = EVSE_SESSION_EVENT_FINISHED,
@@ -103,21 +140,36 @@ void evse_session_end(void)
.avg_power_w = avg_power, .avg_power_w = avg_power,
.is_current = false, .is_current = false,
}; };
post_session_event(&evt);
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
void evse_session_tick(void) void evse_session_tick(void)
{ {
uint32_t power_w = evse_meter_get_instant_power(); // Potência instantânea pode ser negativa (ruído/overflow de sensor) -> clamp
int p = evse_meter_get_instant_power();
uint32_t power_w = (p > 0) ? (uint32_t)p : 0;
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux); portENTER_CRITICAL(&session_mux);
if (session_start_tick != 0) { if (session_start_tick != 0)
watt_seconds += power_w; {
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); portEXIT_CRITICAL(&session_mux);
} }
@@ -127,15 +179,19 @@ bool evse_session_get(evse_session_t *out)
if (out == NULL) if (out == NULL)
return false; return false;
TickType_t start; TickType_t start_tick;
uint32_t ws; int64_t start_us;
uint64_t w_us;
bool has_current; bool has_current;
evse_session_t last_copy; evse_session_t last_copy;
bool last_valid; bool last_valid;
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux); portENTER_CRITICAL(&session_mux);
start = session_start_tick; start_tick = session_start_tick;
ws = watt_seconds; start_us = session_start_us;
w_us = watt_microseconds;
has_current = (session_start_tick != 0); has_current = (session_start_tick != 0);
last_copy = last_session; last_copy = last_session;
last_valid = last_session_valid; last_valid = last_session_valid;
@@ -143,12 +199,12 @@ bool evse_session_get(evse_session_t *out)
if (has_current) if (has_current)
{ {
TickType_t now = xTaskGetTickCount(); uint32_t duration_s = (now_us > start_us) ? (uint32_t)((now_us - start_us) / 1000000LL) : 0;
uint32_t duration_s = (now - start) / configTICK_RATE_HZ; uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint32_t energy_wh = ws / 3600U; uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0; uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
out->start_tick = start; 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;

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) { case EVSE_STATE_B1:
switch (s) { case EVSE_STATE_B2:
case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE; return EVSE_STATE_EVENT_WAITING;
case EVSE_STATE_B1:
case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING; case EVSE_STATE_C1:
case EVSE_STATE_C1: case EVSE_STATE_C2:
case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING; case EVSE_STATE_D1:
case EVSE_STATE_E: case EVSE_STATE_D2:
case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT; return EVSE_STATE_EVENT_CHARGING;
default: return EVSE_STATE_EVENT_IDLE;
case EVSE_STATE_E:
case EVSE_STATE_F:
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)) { {
start_session = true;
} // Sessão começa quando entra em energia (relé ON)
// se saiu de charging para qualquer outro if (evse_state_is_power_flowing(new_state) && !evse_state_is_power_flowing(prev_state))
else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) { {
end_session = true; start_session = true;
} }
current_state = new_state; // Sessão termina quando sai de energia
changed = true; else if (!evse_state_is_power_flowing(new_state) && evse_state_is_power_flowing(prev_state))
} {
end_session = true;
}
current_state = new_state;
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

@@ -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

@@ -1,4 +1,3 @@
// === Início de: components/evse/include/evse_error.h ===
#ifndef EVSE_ERROR_H #ifndef EVSE_ERROR_H
#define EVSE_ERROR_H #define EVSE_ERROR_H
@@ -6,21 +5,23 @@
#include <stdbool.h> #include <stdbool.h>
#include "evse_pilot.h" #include "evse_pilot.h"
// Bits que auto-limpam passado um timeout // ----------------------------------------------------
#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)
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1) #define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2) #define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3) #define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4) #define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5) #define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6) #define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7) #define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
// Inicialização do módulo de erros // Inicialização do módulo de erros
void evse_error_init(void); void evse_error_init(void);
@@ -30,7 +31,7 @@ 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);
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);
@@ -40,12 +41,9 @@ uint32_t evse_error_get_bits(void);
// ---------------------------------------------------- // ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos" // Semântica sticky: flag "todos erros limpos"
// (fica true quando o erro visível chega a 0; pode ser útil para UI/logs)
// ---------------------------------------------------- // ----------------------------------------------------
// Fica true quando TODOS os erros são limpos.
// Volta a false assim que qualquer erro novo aparece.
// Permanece true até o consumidor limpar explicitamente.
bool evse_error_cleared_flag(void); bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void); void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H #endif // EVSE_ERROR_H
// === Fim de: components/evse/include/evse_error.h ===

View File

@@ -16,6 +16,7 @@ typedef enum {
EVSE_EVENT_ENABLE_UPDATED, EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED, EVSE_EVENT_AVAILABLE_UPDATED,
EVSE_EVENT_SESSION, EVSE_EVENT_SESSION,
EVSE_EVENT_ERROR_CHANGED,
} evse_event_id_t; } evse_event_id_t;
// ----------------- // -----------------
@@ -43,35 +44,41 @@ typedef enum {
typedef struct { typedef struct {
evse_session_event_type_t type; ///< STARTED / FINISHED evse_session_event_type_t type; ///< STARTED / FINISHED
// campos básicos da sessão, em tipos simples: uint32_t session_id;
uint32_t session_id; ///< opcional, se tiveres um ID uint32_t duration_s;
uint32_t duration_s; ///< duração em segundos (0 no STARTED) uint32_t energy_wh;
uint32_t energy_wh; ///< energia em Wh (0 no STARTED) uint32_t avg_power_w;
uint32_t avg_power_w; ///< potência média em W (0 no STARTED)
bool is_current; ///< true se ainda estiver em curso bool is_current;
} evse_session_event_data_t; } evse_session_event_data_t;
// ----------------- // -----------------
// Eventos de CONFIG // Eventos de CONFIG
// ----------------- // -----------------
typedef struct { typedef struct {
bool charging; // Estado de carregamento bool charging;
float hw_max_current; // Corrente máxima suportada pelo hardware float hw_max_current;
float runtime_current; // Corrente de carregamento em uso float runtime_current;
int64_t timestamp_us; // Momento da atualização 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

@@ -2,65 +2,43 @@
#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>
/** typedef enum
* @brief Níveis categóricos de tensão no sinal CP (Control Pilot) {
*/ PILOT_VOLTAGE_12,
typedef enum PILOT_VOLTAGE_9,
{ PILOT_VOLTAGE_6,
PILOT_VOLTAGE_12, ///< Estado A: +12V PILOT_VOLTAGE_3,
PILOT_VOLTAGE_9, ///< Estado B: +9V PILOT_VOLTAGE_1
PILOT_VOLTAGE_6, ///< Estado C: +6V } pilot_voltage_t;
PILOT_VOLTAGE_3, ///< Estado D: +3V
PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V
} pilot_voltage_t;
/** void pilot_init(void);
* @brief Inicializa o driver do sinal Pilot
*/
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);
/** void pilot_set_amps(uint16_t amps);
* @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_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
* @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);
/** bool pilot_get_state(void);
* @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);
/** 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) } pilot_voltage_cache_t;
uint16_t low_mv; ///< Pico negativo medido (mV)
} 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,90 +6,68 @@
#include "evse_events.h" #include "evse_events.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C"
{
#endif #endif
// ============================ typedef enum
// EVSE Pilot Signal States {
// ============================ EVSE_STATE_A, // EV Not Connected (12V)
EVSE_STATE_B1, // EV Connected (9V, Not Authorized)
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
EVSE_STATE_C2, // Charging Active (6V, Relay On)
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
} evse_state_t;
typedef enum { // Initialization
EVSE_STATE_A, // EV Not Connected (12V) void evse_state_init(void);
EVSE_STATE_B1, // EV Connected (9V, Not Authorized) void evse_state_tick(void);
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
EVSE_STATE_C2, // Charging Active (6V, Relay On)
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
} evse_state_t;
// ============================ // State Access & Control
// Initialization evse_state_t evse_get_state(void);
// ============================ void evse_set_state(evse_state_t state);
const char *evse_state_to_str(evse_state_t state);
/** // ---------------------------
* @brief Initializes the EVSE state machine and default state. // State Evaluation Helpers
*/ // ---------------------------
void evse_state_init(void);
/** /**
* @brief Periodic tick for state handling (optional hook). * @brief True se existe uma sessão "lógica" ativa (carro ligado e autorizado/pronto ou a carregar).
*/ * Inclui B2, C1/C2, D1/D2.
void evse_state_tick(void); */
bool evse_state_is_session(evse_state_t state);
// ============================ /**
// State Access & Control * @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);
/** /**
* @brief Returns the current EVSE state. * @brief True se o EV pediu carga mas o relé ainda está OFF (C1/D1).
*/ */
evse_state_t evse_get_state(void); bool evse_state_is_requesting(evse_state_t state);
/** /**
* @brief Sets the current EVSE state and emits a change event if needed. * @brief True se há fluxo de energia (alias explícito para charging).
*/ */
void evse_set_state(evse_state_t state); bool evse_state_is_power_flowing(evse_state_t state);
/** /**
* @brief Converts the state enum into a human-readable string. * @brief True se o EV está fisicamente ligado (B1 e além).
*/ */
const char* evse_state_to_str(evse_state_t state); bool evse_state_is_plugged(evse_state_t state);
// ============================ // Authorization Control
// State Evaluation Helpers void evse_state_set_authorized(bool authorized);
// ============================ bool evse_state_get_authorized(void);
/**
* @brief True if EV is in an active session (B2, C1, C2).
*/
bool evse_state_is_session(evse_state_t state);
/**
* @brief True if EV is actively charging (C1, C2).
*/
bool evse_state_is_charging(evse_state_t state);
/**
* @brief True if EV is physically plugged in (B1 and beyond).
*/
bool evse_state_is_plugged(evse_state_t state);
// ============================
// Authorization Control
// ============================
/**
* @brief Sets whether the EV is authorized to charge.
*/
void evse_state_set_authorized(bool authorized);
/**
* @brief Gets whether the EV is currently authorized.
*/
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,3 +1,4 @@
// === Início de: components/evse_link/include/evse_link.h ===
#ifndef EVSE_LINK_H_ #ifndef EVSE_LINK_H_
#define EVSE_LINK_H_ #define EVSE_LINK_H_
@@ -43,3 +44,5 @@ void evse_link_set_enabled(bool enabled);
bool evse_link_is_enabled(void); bool evse_link_is_enabled(void);
#endif // EVSE_LINK_H_ #endif // EVSE_LINK_H_
// === Fim de: components/evse_link/include/evse_link.h ===

View File

@@ -1,31 +1,29 @@
// === 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"
#include <stdint.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 #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,
LINK_EVENT_SLAVE_ONLINE, // heartbeat recebido primeira vez LINK_EVENT_SLAVE_ONLINE, // payload: evse_link_slave_presence_event_t
LINK_EVENT_SLAVE_OFFLINE, // sem heartbeat no timeout LINK_EVENT_SLAVE_OFFLINE, // payload: evse_link_slave_presence_event_t (master-side) ou NULL (slave-side fallback)
LINK_EVENT_MASTER_POLL_SENT, // opcional: poll enviado pelo master LINK_EVENT_MASTER_POLL_SENT,
LINK_EVENT_CURRENT_LIMIT_APPLIED, LINK_EVENT_CURRENT_LIMIT_APPLIED,
LINK_EVENT_SLAVE_CONFIG_UPDATED, // config atualizada pelo master LINK_EVENT_SLAVE_CONFIG_UPDATED,
LINK_EVENT_REMOTE_AUTH_GRANTED // autorização remota (master -> slave) LINK_EVENT_REMOTE_AUTH_GRANTED
} evse_link_event_t; } evse_link_event_t;
// Payload para LINK_EVENT_REMOTE_AUTH_GRANTED
typedef struct { typedef struct {
char tag[EVSE_LINK_TAG_MAX_LEN]; // idTag enviada pelo master char tag[EVSE_LINK_TAG_MAX_LEN];
} evse_link_auth_grant_event_t; } evse_link_auth_grant_event_t;
#endif // EVSE_LINK_EVENTS_H_ typedef struct {
uint8_t slave_id;
} evse_link_slave_presence_event_t;
// === Fim de: components/evse_link/include/evse_link_events.h === #endif // EVSE_LINK_EVENTS_H_

View File

@@ -6,23 +6,23 @@
#include "driver/uart.h" #include "driver/uart.h"
// UART instance and configuration // UART instance and configuration
#define UART_PORT UART_NUM_2 // Usa a UART2 #define UART_PORT UART_NUM_2
#define UART_BAUDRATE 115200 #define UART_BAUDRATE 9600
#define UART_RX_BUF_SIZE 256 #define UART_RX_BUF_SIZE 256
// GPIO pin assignments for UART (ajuste conforme o hardware) // GPIO pin assignments for RS-485 UART
#define UART_TXD 17 // TX -> DI do MAX3485 // Ajuste conforme seu hardware
#define UART_RXD 16 // RX -> RO do MAX3485 #define MB_UART_TXD 17
#define UART_RTS 2 // RTS -> DE+RE do MAX3485 #define MB_UART_RXD 16
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485
// Conveniência: nomes usados no .c #define TX_PIN MB_UART_TXD
#define TX_PIN UART_TXD #define RX_PIN MB_UART_RXD
#define RX_PIN UART_RXD #define RTS_PIN MB_UART_RTS
#define RTS_PIN UART_RTS
// Frame delimiters // Frame delimiters
#define MAGIC_START 0x7E #define MAGIC_START 0x7E
#define MAGIC_END 0x7F #define MAGIC_END 0x7F
// Maximum payload (excluding sequence byte) // Maximum payload (excluding sequence byte)
#define EVSE_LINK_MAX_PAYLOAD 254 #define EVSE_LINK_MAX_PAYLOAD 254

View File

@@ -1,177 +1,200 @@
// components/evse_link/src/evse_link.c // components/evse_link/src/evse_link.c
//
// Camada de transporte EVSE-Link:
// - carrega config (mode/self_id/enabled)
// - init do framing
// - task RX (UART -> framing)
// - entrega frames completos ao callback registado
//
// NOTA: a logica de protocolo (CMD_POLL / ACK / etc.) deve ficar em
// evse_link_master.c / evse_link_slave.c.
#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/FreeRTOS.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
#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
#define UART_PORT UART_NUM_2
#define UART_RX_BUF_SIZE 256
// Runtime config
static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER; static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER;
static uint8_t _self_id = 0x01; static uint8_t _self_id = 0x01;
static bool _enabled = false; static bool _enabled = false;
// Registered Rx callback
static evse_link_rx_cb_t _rx_cb = NULL; static evse_link_rx_cb_t _rx_cb = NULL;
static bool s_evse_link_inited = false;
// Forward declarations
extern void evse_link_master_init(void); extern void evse_link_master_init(void);
extern void evse_link_slave_init(void); extern void evse_link_slave_init(void);
static void framing_rx_cb(uint8_t src, uint8_t dest, static void framing_rx_cb(uint8_t src, uint8_t dest,
const uint8_t *payload, uint8_t len) const uint8_t *payload, uint8_t 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
void evse_link_register_rx_cb(evse_link_rx_cb_t cb) void evse_link_register_rx_cb(evse_link_rx_cb_t cb)
{ {
_rx_cb = cb; _rx_cb = cb;
} }
// Load config from NVS
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 esp_err_t err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_MODE, &u8, pdMS_TO_TICKS(500));
static void save_link_config(void) if (err == ESP_OK && (u8 == (uint8_t)EVSE_LINK_MODE_MASTER || u8 == (uint8_t)EVSE_LINK_MODE_SLAVE))
{ _mode = (evse_link_mode_t)u8;
nvs_handle_t handle;
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"); _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", esp_err_to_name(err));
}
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", esp_err_to_name(err), _self_id);
}
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", esp_err_to_name(err));
} }
} }
// Getters/setters static void save_link_config(void)
{
(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);
}
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
static void evse_link_rx_task(void *arg) static void evse_link_rx_task(void *arg)
{ {
(void)arg;
ESP_LOGI(TAG, "evse_link_rx_task started");
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)
{ {
ESP_LOGD(TAG, "UART RX: len=%d first=0x%02X last=0x%02X", len, buf[0], buf[len - 1]);
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
void evse_link_init(void) void evse_link_init(void)
{ {
load_link_config(); if (s_evse_link_inited)
{
ESP_LOGW(TAG, "evse_link_init called twice; ignoring");
return;
}
s_evse_link_inited = true;
esp_err_t se = storage_service_init();
if (se == ESP_OK)
load_link_config();
else
ESP_LOGE(TAG, "storage_service_init failed: %s (defaults in RAM)", esp_err_to_name(se));
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;
// 1) framing layer init (sets up mutex, UART driver, etc.)
evse_link_framing_init(); evse_link_framing_init();
evse_link_framing_register_cb(framing_rx_cb); evse_link_framing_register_cb(framing_rx_cb);
// 2) start RX task if (xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL) != pdPASS)
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL); {
ESP_LOGE(TAG, "Failed to create evse_link_rx task");
return;
}
// 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)
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);
} }
// Receive byte (delegates to framing module)
void evse_link_recv_byte(uint8_t byte) void evse_link_recv_byte(uint8_t byte)
{ {
evse_link_framing_recv_byte(byte); evse_link_framing_recv_byte(byte);

View File

@@ -1,8 +1,29 @@
// components/evse_link/src/evse_link_framing.c
//
// EVSE-Link framing (RS-485 HALF DUPLEX via UART driver)
// - Usa UART_MODE_RS485_HALF_DUPLEX (driver controla RTS => DE//RE)
// - Configura RX timeout + RX full threshold para evitar “len=120”
// - Remove controlo manual de GPIO do RTS
//
// Requisitos:
// - MAX3485 com DE e /RE juntos ligados ao RTS_PIN
// - RTS_PIN definido em evse_link_framing.h (ex.: GPIO2; recomendado mudar no futuro)
//
// Notas:
// - Se o teu hardware inverter a lógica do RTS (raro), define EVSE_LINK_RTS_INVERT=1
#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"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"
#include <string.h> #include <string.h>
#include <stdint.h>
#include <stdbool.h>
static const char *TAG = "evse_framing"; static const char *TAG = "evse_framing";
@@ -10,118 +31,252 @@ 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) static bool s_framing_inited = false;
static uint8_t crc8(const uint8_t *data, uint8_t len)
// ---- Tunables (fallbacks) ----
#ifndef EVSE_LINK_INTERBYTE_TIMEOUT_US
// Timeout inter-byte: se um frame morrer a meio, reseta o parser
#define EVSE_LINK_INTERBYTE_TIMEOUT_US 5000
#endif
// Rate-limit para warnings (em microsegundos)
#define LOG_RATELIMIT_US 1000000 // 1s
// RX tuning (evita acumular ~120 bytes antes de "acordar")
#ifndef EVSE_LINK_RX_TIMEOUT
// Timeout do UART TOUT feature (em "character times"). 3..10 funciona bem.
#define EVSE_LINK_RX_TIMEOUT 3
#endif
#ifndef EVSE_LINK_RX_FULL_THRESH
// Gera interrupção quando FIFO tem pelo menos N bytes (1..120). 4 é um bom default.
#define EVSE_LINK_RX_FULL_THRESH 1
#endif
#ifndef EVSE_LINK_RTS_INVERT
// Se precisares inverter RTS (muito raro), define para 1 no build.
#define EVSE_LINK_RTS_INVERT 0
#endif
static inline bool log_ratelimit_ok(int64_t *last_us, int64_t interval_us)
{ {
uint8_t crc = 0; const int64_t now = esp_timer_get_time();
for (uint8_t i = 0; i < len; ++i) { if (*last_us == 0 || (now - *last_us) > interval_us)
crc ^= data[i]; {
for (uint8_t b = 0; b < 8; ++b) { *last_us = now;
if (crc & 0x80) { return true;
crc = (uint8_t)((crc << 1) ^ 0x07); }
} else { return false;
crc <<= 1; }
}
} // CRC-8 (poly 0x07), MSB-first, init=0x00
static uint8_t crc8_update(uint8_t crc, uint8_t data)
{
crc ^= data;
for (uint8_t b = 0; b < 8; ++b)
{
if (crc & 0x80)
crc = (uint8_t)((crc << 1) ^ 0x07);
else
crc <<= 1;
} }
return crc; return crc;
} }
void evse_link_framing_init(void) static esp_err_t configure_uart(void)
{ {
// Mutex para proteger TX (framings de várias tasks)
tx_mutex = xSemaphoreCreateMutex();
// Instala driver UART
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 = { 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, .source_clk = UART_SCLK_DEFAULT,
}; };
uart_param_config(UART_PORT, &cfg);
// Define pinos: TX, RX e RTS (RTS ligado a DE+RE do transceiver RS485) esp_err_t err = uart_param_config(UART_PORT, &cfg);
uart_set_pin(UART_PORT, if (err != ESP_OK)
TX_PIN, // MB_UART_TXD (ex: GPIO17) {
RX_PIN, // MB_UART_RXD (ex: GPIO16) ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
RTS_PIN, // MB_UART_RTS (ex: GPIO2, DE+RE) return err;
UART_PIN_NO_CHANGE); }
// Modo RS485 half-duplex: driver controla RTS/DE/RE automaticamente // TX/RX/RTS na UART (RTS controla DE//RE em RS485 half-duplex)
uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX); err = uart_set_pin(UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_PIN_NO_CHANGE);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "Framing init: UART%d TX=%d RX=%d RTS(DE/RE)=%d baud=%d", // RS-485 HALF DUPLEX (driver controla RTS automaticamente)
err = uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "uart_set_mode(RS485_HALF_DUPLEX) failed: %s", esp_err_to_name(err));
return err;
}
// Ajustes para RX responsivo (evita "len=120")
(void)uart_set_rx_full_threshold(UART_PORT, EVSE_LINK_RX_FULL_THRESH);
(void)uart_set_rx_timeout(UART_PORT, EVSE_LINK_RX_TIMEOUT);
// Opcional: inverter RTS se hardware exigir
if (EVSE_LINK_RTS_INVERT)
{
(void)uart_set_line_inverse(UART_PORT, UART_SIGNAL_RTS_INV);
ESP_LOGW(TAG, "RS485 driver: RTS inverted");
}
ESP_LOGW(TAG, "RS485 driver enabled: UART%d TX=%d RX=%d RTS=%d baud=%d rx_to=%d rx_thresh=%d",
UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE,
EVSE_LINK_RX_TIMEOUT, EVSE_LINK_RX_FULL_THRESH);
return ESP_OK;
}
void evse_link_framing_init(void)
{
if (s_framing_inited)
{
ESP_LOGI(TAG, "Framing already initialized");
return;
}
if (!tx_mutex)
{
tx_mutex = xSemaphoreCreateMutex();
if (!tx_mutex)
{
ESP_LOGE(TAG, "Failed to create TX mutex");
return;
}
}
// Se o driver já estiver instalado, só reconfigura e aplica RX tuning.
if (uart_is_driver_installed(UART_PORT))
{
esp_err_t err = configure_uart();
if (err == ESP_OK)
{
s_framing_inited = true;
ESP_LOGW(TAG, "UART%d driver already installed -> configured for RS485 HALF DUPLEX", UART_PORT);
(void)uart_flush_input(UART_PORT);
}
else
{
ESP_LOGE(TAG, "Failed to configure already-installed UART%d", UART_PORT);
}
return;
}
// Instala driver UART
esp_err_t err = uart_driver_install(UART_PORT,
UART_RX_BUF_SIZE * 2, // RX buffer (ringbuffer)
0, // TX buffer (não usado)
0, // event queue size
NULL, // event queue
0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
return;
}
err = configure_uart();
if (err != ESP_OK)
return;
(void)uart_flush_input(UART_PORT);
s_framing_inited = true;
ESP_LOGI(TAG, "Framing init (RS485 driver): UART%d TX=%d RX=%d RTS=%d baud=%d",
UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE); 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) { if (!s_framing_inited)
ESP_LOGW(TAG, "Payload too large: %u (max=%u)", {
len, EVSE_LINK_MAX_PAYLOAD); ESP_LOGE(TAG, "Framing not initialized");
return false; return false;
} }
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) { if (len > EVSE_LINK_MAX_PAYLOAD)
{
ESP_LOGW(TAG, "Payload too large: %u (max=%u)", len, EVSE_LINK_MAX_PAYLOAD);
return false;
}
if (len > 0 && payload == NULL)
{
ESP_LOGW(TAG, "Invalid send: len=%u but payload=NULL", len);
return false;
}
if (!tx_mutex)
{
ESP_LOGE(TAG, "TX mutex is NULL (framing_init not called?)");
return false;
}
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE)
{
ESP_LOGW(TAG, "Failed to take TX mutex"); ESP_LOGW(TAG, "Failed to take TX mutex");
return false; return false;
} }
// Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END // Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END
// LEN on wire = SEQ + PAYLOAD (>=1)
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++] = (uint8_t)(len + 1); // LEN = SEQ + payload frame[idx++] = (uint8_t)(len + 1);
frame[idx++] = seq; frame[idx++] = seq;
if (len > 0 && payload != NULL) { if (len > 0)
{
memcpy(&frame[idx], payload, len); memcpy(&frame[idx], payload, len);
idx += len; idx += len;
} }
// CRC cobre: DEST + SRC + LEN + SEQ + PAYLOAD // CRC: DEST + SRC + LEN + SEQ + PAYLOAD
uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD]; uint8_t crc = 0;
memcpy(crc_input, &frame[1], 3 + 1 + len); crc = crc8_update(crc, dest);
uint8_t crc = crc8(crc_input, (uint8_t)(3 + 1 + len)); crc = crc8_update(crc, src);
frame[idx++] = crc; crc = crc8_update(crc, (uint8_t)(len + 1));
crc = crc8_update(crc, seq);
for (uint8_t i = 0; i < len; ++i)
crc = crc8_update(crc, payload[i]);
frame[idx++] = crc;
frame[idx++] = MAGIC_END; frame[idx++] = MAGIC_END;
// Envia frame completo
int written = uart_write_bytes(UART_PORT, (const char *)frame, idx); int written = uart_write_bytes(UART_PORT, (const char *)frame, idx);
if (written != idx) { if (written != idx)
{
ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx); ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx);
} }
// Aguarda TX terminar (o driver controla DE/RE via RTS) // Aguarda TX terminar; driver RS485 devolve RTS para RX automaticamente.
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(20)); (void)uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(700));
xSemaphoreGive(tx_mutex); xSemaphoreGive(tx_mutex);
ESP_LOGI(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u", ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
dest, src, len, seq); dest, src, len, seq);
seq++; // incrementa sequência após envio seq++;
return true; return true;
} }
void evse_link_framing_recv_byte(uint8_t b) void evse_link_framing_recv_byte(uint8_t b)
{ {
// Máquina de estados para parsing do frame
static enum { static enum {
ST_WAIT_START = 0, ST_WAIT_START = 0,
ST_WAIT_DEST, ST_WAIT_DEST,
@@ -133,17 +288,46 @@ void evse_link_framing_recv_byte(uint8_t b)
ST_WAIT_END ST_WAIT_END
} rx_state = ST_WAIT_START; } rx_state = ST_WAIT_START;
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; // inclui SEQ + payload static uint8_t rx_len; // inclui SEQ + payload (>=1)
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;
static uint8_t rx_crc; static uint8_t rx_crc;
switch (rx_state) { static int64_t s_last_byte_us = 0;
static int64_t s_last_bad_len_log_us = 0;
static int64_t s_last_bad_crc_log_us = 0;
#define RESET_PARSER() \
do \
{ \
rx_state = ST_WAIT_START; \
rx_dest = 0; \
rx_src = 0; \
rx_len = 0; \
rx_seq = 0; \
rx_pos = 0; \
rx_crc = 0; \
} while (0)
const int64_t now_us = esp_timer_get_time();
// Timeout inter-byte: frame morreu a meio -> reseta
if (rx_state != ST_WAIT_START && s_last_byte_us != 0 &&
(now_us - s_last_byte_us) > EVSE_LINK_INTERBYTE_TIMEOUT_US)
{
RESET_PARSER();
}
s_last_byte_us = now_us;
switch (rx_state)
{
case ST_WAIT_START: case ST_WAIT_START:
if (b == MAGIC_START) { if (b == MAGIC_START)
{
rx_pos = 0;
rx_state = ST_WAIT_DEST; rx_state = ST_WAIT_DEST;
} }
break; break;
@@ -159,28 +343,50 @@ void evse_link_framing_recv_byte(uint8_t b)
break; break;
case ST_WAIT_LEN: case ST_WAIT_LEN:
rx_len = b; // LEN = SEQ + payload rx_len = b;
// rx_len = SEQ + payload => >=1 e <= MAX+1
if (rx_len < 1 || rx_len > (uint8_t)(EVSE_LINK_MAX_PAYLOAD + 1))
{
if (log_ratelimit_ok(&s_last_bad_len_log_us, LOG_RATELIMIT_US))
{
ESP_LOGW(TAG, "Invalid LEN=%u (max=%u), dropping frame",
rx_len, (unsigned)(EVSE_LINK_MAX_PAYLOAD + 1));
}
RESET_PARSER();
break;
}
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;
if (rx_len > 1) { rx_state = (rx_len > 1) ? ST_READING : ST_WAIT_CRC;
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) { {
const uint8_t payload_len = (uint8_t)(rx_len - 1);
if (payload_len > EVSE_LINK_MAX_PAYLOAD)
{
if (log_ratelimit_ok(&s_last_bad_len_log_us, LOG_RATELIMIT_US))
{
ESP_LOGW(TAG, "Payload len too big: %u", (unsigned)payload_len);
}
RESET_PARSER();
break;
}
if (rx_pos < EVSE_LINK_MAX_PAYLOAD)
rx_buf[rx_pos++] = b; rx_buf[rx_pos++] = b;
}
if (rx_pos >= (uint8_t)(rx_len - 1)) { // payload completo if (rx_pos >= payload_len)
rx_state = ST_WAIT_CRC; rx_state = ST_WAIT_CRC;
}
break; break;
}
case ST_WAIT_CRC: case ST_WAIT_CRC:
rx_crc = b; rx_crc = b;
@@ -188,41 +394,44 @@ void evse_link_framing_recv_byte(uint8_t b)
break; break;
case ST_WAIT_END: case ST_WAIT_END:
if (b == MAGIC_END) { if (b == MAGIC_END)
// Monta buffer para verificar CRC: {
// DEST + SRC + LEN + SEQ + PAYLOAD uint8_t expected = 0;
uint8_t temp[3 + 1 + EVSE_LINK_MAX_PAYLOAD]; expected = crc8_update(expected, rx_dest);
int temp_len = 0; expected = crc8_update(expected, rx_src);
temp[temp_len++] = rx_dest; expected = crc8_update(expected, rx_len);
temp[temp_len++] = rx_src; expected = crc8_update(expected, rx_seq);
temp[temp_len++] = rx_len;
temp[temp_len++] = rx_seq;
if (rx_len > 1) {
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
temp_len += rx_len - 1;
}
uint8_t expected = crc8(temp, (uint8_t)temp_len); const uint8_t payload_len = (uint8_t)(rx_len - 1);
if (expected == rx_crc) { for (uint8_t i = 0; i < payload_len; ++i)
uint8_t payload_len = (uint8_t)(rx_len - 1); // exclui SEQ expected = crc8_update(expected, rx_buf[i]);
if (rx_cb) {
if (expected == rx_crc)
{
if (rx_cb)
rx_cb(rx_src, rx_dest, rx_buf, payload_len); 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, payload_len, rx_seq); rx_src, rx_dest, payload_len, rx_seq);
} else { }
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X", else
expected, rx_crc); {
if (log_ratelimit_ok(&s_last_bad_crc_log_us, LOG_RATELIMIT_US))
{
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
expected, rx_crc);
}
} }
} }
// Em qualquer caso, volta a esperar novo frame RESET_PARSER();
rx_state = ST_WAIT_START;
break; break;
default: default:
rx_state = ST_WAIT_START; RESET_PARSER();
break; break;
} }
#undef RESET_PARSER
} }
void evse_link_framing_register_cb(evse_link_frame_cb_t cb) void evse_link_framing_register_cb(evse_link_frame_cb_t cb)

View File

@@ -1,10 +1,27 @@
// components/evse_link/src/evse_link_master.c
//
// Correções aplicadas:
// 1) Evitar TX dentro do callback RX (ACK deferido para task -> não bloqueia RX)
// 2) Dedupe de ACK por slave (evita rajadas se RX vier em chunks / queue acumular)
// 3) Log quando ACK queue enche (antes era silencioso)
// 4) Proteção concorrente no s_presence (int64_t não é atómico no ESP32 32-bit)
// 5) Comentário do poll corrigido + opção de jitter no ACK
//
// NOTA: Mantive o teu comportamento (POLL a cada 10s). Se quiseres, muda para 30s.
#include "evse_link.h" #include "evse_link.h"
#include "evse_link_events.h" #include "evse_link_events.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h" #include "freertos/timers.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_timer.h"
#include "esp_random.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
@@ -14,37 +31,180 @@
static const char *TAG = "evse_link_master"; static const char *TAG = "evse_link_master";
// Link commands // Link commands (opcode no payload[0])
#define CMD_POLL 0x01 #define CMD_POLL 0x01
#define CMD_HEARTBEAT 0x02 #define CMD_HEARTBEAT 0x02
#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 #define CMD_HEARTBEAT_ACK 0x09
#define CMD_AUTH_GRANTED 0x0A
// payload lengths (exclui byte de opcode) // payload lengths (INCLUI opcode)
#define LEN_POLL_REQ 1 // [ CMD_POLL ] #define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ] #define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, ... ]
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ] #define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, amps_lo, amps_hi ]
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ] // Presence monitoring
#define LEN_HEARTBEAT_ACK 1 #define PRESENCE_CHECK_MS 5000
#define SLAVE_OFFLINE_TIMEOUT_MS 180000
// ACK defer
#define ACK_QUEUE_LEN 16
// backoff para espaçar ACK (evita burst)
// antes era 0..15ms; aqui fica ligeiramente mais suave.
#define ACK_BACKOFF_MIN_MS 5
#define ACK_BACKOFF_MAX_MS 35 // jitter 5..35ms
typedef struct
{
int64_t last_seen_us;
bool online;
} slave_presence_t;
static slave_presence_t s_presence[256];
static TimerHandle_t s_presence_timer = NULL;
// Proteção concorrente (int64_t não é atómico no ESP32)
static portMUX_TYPE s_presence_mux = portMUX_INITIALIZER_UNLOCKED;
// 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;
static timer_def_t poll_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
static timer_def_t hb_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)}; // POLL a cada 60s (comentário corrigido)
static timer_def_t poll_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(60000)};
static bool s_handlers_registered = false;
// ACK task/queue
static QueueHandle_t s_ack_q = NULL;
static TaskHandle_t s_ack_task = NULL;
// Dedupe: 1 ACK pendente por slave
static bool s_ack_pending[256] = {0};
static void post_presence_event(evse_link_event_t evt, uint8_t slave_id)
{
evse_link_slave_presence_event_t p = {.slave_id = slave_id};
(void)esp_event_post(EVSE_LINK_EVENTS, evt, &p, sizeof(p), portMAX_DELAY);
}
static void mark_slave_seen(uint8_t slave_id)
{
const int64_t now = esp_timer_get_time();
bool was_offline = false;
portENTER_CRITICAL(&s_presence_mux);
slave_presence_t *p = &s_presence[slave_id];
p->last_seen_us = now;
if (!p->online)
{
p->online = true;
was_offline = true;
}
portEXIT_CRITICAL(&s_presence_mux);
if (was_offline)
{
ESP_LOGI(TAG, "Slave 0x%02X ONLINE", slave_id);
post_presence_event(LINK_EVENT_SLAVE_ONLINE, slave_id);
}
}
static void presence_timer_cb(TimerHandle_t xTimer)
{
(void)xTimer;
const int64_t now = esp_timer_get_time();
const int64_t timeout_us = (int64_t)SLAVE_OFFLINE_TIMEOUT_MS * 1000;
const uint8_t self = evse_link_get_self_id();
for (int i = 0; i < 256; ++i)
{
if ((uint8_t)i == self)
continue;
bool online;
int64_t last_seen;
portENTER_CRITICAL(&s_presence_mux);
online = s_presence[i].online;
last_seen = s_presence[i].last_seen_us;
portEXIT_CRITICAL(&s_presence_mux);
if (!online)
continue;
if (last_seen > 0 && (now - last_seen) > timeout_us)
{
portENTER_CRITICAL(&s_presence_mux);
s_presence[i].online = false;
portEXIT_CRITICAL(&s_presence_mux);
ESP_LOGW(TAG, "Slave 0x%02X OFFLINE (no heartbeat for %d ms)", i, SLAVE_OFFLINE_TIMEOUT_MS);
post_presence_event(LINK_EVENT_SLAVE_OFFLINE, (uint8_t)i);
}
}
}
// Enfileira ACK sem duplicar por slave
static void enqueue_ack(uint8_t slave_id)
{
if (!s_ack_q)
return;
// Dedupe: se já existe ACK pendente para este slave, não enfileira outro
if (s_ack_pending[slave_id])
return;
s_ack_pending[slave_id] = true;
if (xQueueSendToBack(s_ack_q, &slave_id, 0) != pdTRUE)
{
s_ack_pending[slave_id] = false;
ESP_LOGW(TAG, "ACK queue full, dropping ACK for 0x%02X", slave_id);
}
}
// --- ACK task (não bloqueia RX) ---
static void ack_task(void *arg)
{
(void)arg;
for (;;)
{
uint8_t slave_id = 0;
if (xQueueReceive(s_ack_q, &slave_id, portMAX_DELAY) != pdTRUE)
continue;
// libera dedupe
s_ack_pending[slave_id] = false;
// backoff com jitter
uint32_t backoff = ACK_BACKOFF_MIN_MS +
(esp_random() % (ACK_BACKOFF_MAX_MS - ACK_BACKOFF_MIN_MS + 1));
vTaskDelay(pdMS_TO_TICKS(backoff));
uint8_t ack[] = {CMD_HEARTBEAT_ACK};
bool ok = evse_link_send(slave_id, ack, sizeof(ack));
ESP_LOGI(TAG, "CMD_HEARTBEAT_ACK to 0x%02X ok=%d", slave_id, ok);
}
}
// --- 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) (void)arg;
(void)base;
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT || data == NULL)
return; return;
const loadbalancer_slave_limit_event_t *evt = data;
const loadbalancer_slave_limit_event_t *evt = (const loadbalancer_slave_limit_event_t *)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;
@@ -52,156 +212,179 @@ static void on_new_limit(void *arg, esp_event_base_t base, int32_t id, void *dat
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));
ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, max_current); (void)evse_link_send(slave_id, buf, sizeof(buf));
ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, (unsigned)max_current);
} }
// --- Bridge AUTH -> EVSE-Link: enviar AUTH_GRANTED para slaves --- // --- Bridge AUTH -> EVSE-Link ---
static void on_auth_result(void *arg, esp_event_base_t base, int32_t id, void *data) 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) { (void)arg;
if (base != AUTH_EVENTS || id != AUTH_EVENT_TAG_PROCESSED || data == NULL)
return; return;
}
const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data; const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data;
if (!ev->authorized) { if (!ev->authorized)
{
ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag); ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag);
return; return;
} }
// Construir payload: [ CMD_AUTH_GRANTED, tag..., '\0' ]
uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN]; uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN];
buf[0] = CMD_AUTH_GRANTED; buf[0] = CMD_AUTH_GRANTED;
// Copiar tag e garantir NUL // Copia tag e garante NUL
strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1); strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1);
((char *)&buf[1])[EVSE_LINK_TAG_MAX_LEN - 1] = '\0'; ((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' // Payload inclui opcode + string + NUL
uint8_t payload_len = 1 + (uint8_t)(strlen((char *)&buf[1]) + 1);
// Neste exemplo: broadcast para todos os slaves (0xFF) (void)evse_link_send(0xFF, buf, payload_len);
uint8_t dest = 0xFF; ESP_LOGI(TAG, "Sent CMD_AUTH_GRANTED (broadcast) tag=%s", (char *)&buf[1]);
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"); (void)xTimer;
;
// Optionally post event LINK_EVENT_MASTER_POLL_SENT
}
// --- Heartbeat timeout callback --- uint8_t poll[] = {CMD_POLL};
static void hb_timer_cb(TimerHandle_t xTimer) bool ok = evse_link_send(0xFF, poll, sizeof(poll));
{ ESP_LOGI(TAG, "POLL send ok=%d", ok);
ESP_LOGW(TAG, "Heartbeat timeout: possible 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) const uint8_t self = evse_link_get_self_id();
// ignora eco do próprio master e frames que não são para nós nem broadcast
if (src == self)
return; return;
if (dest != self && dest != 0xFF)
return;
if (payload == NULL || len < 1)
return;
uint8_t cmd = payload[0]; uint8_t cmd = payload[0];
switch (cmd) switch (cmd)
{ {
case CMD_HEARTBEAT: case CMD_HEARTBEAT:
{ {
ESP_LOGD(TAG, "HEARTBEAT from 0x%02X: %u bytes", src, len);
if (len != LEN_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;
} }
bool charging = payload[1] != 0;
uint16_t hw_max = payload[2] | (payload[3] << 8);
uint16_t runtime = payload[4] | (payload[5] << 8);
ESP_LOGI(TAG, "Heartbeat from 0x%02X: charging=%d hw_max=%uA runtime=%uA", bool charging = payload[1] != 0;
src, charging, hw_max, runtime); uint16_t hw_max = (uint16_t)(payload[2] | ((uint16_t)payload[3] << 8));
uint16_t runtime = (uint16_t)(payload[4] | ((uint16_t)payload[5] << 8));
mark_slave_seen(src);
loadbalancer_slave_status_event_t status = { loadbalancer_slave_status_event_t status = {
.slave_id = src, .slave_id = src,
.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,
.timestamp_us = esp_timer_get_time()}; .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, (void)esp_event_post(LOADBALANCER_EVENTS,
LOADBALANCER_EVENT_SLAVE_STATUS, LOADBALANCER_EVENT_SLAVE_STATUS,
&status, sizeof(status), portMAX_DELAY); &status, sizeof(status), portMAX_DELAY);
// Enviar ACK de volta // ACK deferido e deduplicado
uint8_t ack[] = {CMD_HEARTBEAT_ACK}; enqueue_ack(src);
evse_link_send(src, ack, sizeof(ack));
ESP_LOGD(TAG, "Sent HEARTBEAT_ACK to 0x%02X", src);
break; break;
} }
case CMD_POLL:
ESP_LOGD(TAG, "Received POLL_RESP from 0x%02X", src);
break;
case CMD_CONFIG_BROADCAST: case CMD_CONFIG_BROADCAST:
ESP_LOGI(TAG, "Slave 0x%02X acked CONFIG_BROADCAST: new_max=%uA", if (len >= LEN_CONFIG_BROADCAST)
src, payload[1]); ESP_LOGI(TAG, "Slave 0x%02X acked CONFIG_BROADCAST: new_max=%uA", src, payload[1]);
else
ESP_LOGW(TAG, "CONFIG_BROADCAST ack short len=%u from 0x%02X", len, src);
break; break;
default: default:
ESP_LOGW(TAG, "Unknown cmd 0x%02X from 0x%02X", cmd, src); ESP_LOGD(TAG, "Cmd 0x%02X from 0x%02X (ignored/unknown)", cmd, src);
break;
} }
} }
// --- 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());
// register frame callback
evse_link_register_rx_cb(on_frame_master); evse_link_register_rx_cb(on_frame_master);
// register loadbalancer event // Cria queue/task de ACK uma vez
ESP_ERROR_CHECK( if (s_ack_q == NULL)
esp_event_handler_register( {
s_ack_q = xQueueCreate(ACK_QUEUE_LEN, sizeof(uint8_t));
if (!s_ack_q)
{
ESP_LOGE(TAG, "Failed to create ACK queue");
}
else
{
if (xTaskCreate(ack_task, "evse_ack", 4096, NULL, 4, &s_ack_task) != pdPASS)
{
ESP_LOGE(TAG, "Failed to create ACK task");
vQueueDelete(s_ack_q);
s_ack_q = NULL;
s_ack_task = NULL;
}
}
}
if (!s_handlers_registered)
{
s_handlers_registered = true;
ESP_ERROR_CHECK(esp_event_handler_register(
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(
ESP_ERROR_CHECK(
esp_event_handler_register(
AUTH_EVENTS, AUTH_EVENTS,
AUTH_EVENT_TAG_PROCESSED, AUTH_EVENT_TAG_PROCESSED,
on_auth_result, on_auth_result,
NULL)); NULL));
}
// create and start poll timer if (poll_timer.timer == NULL)
poll_timer.timer = xTimerCreate("poll_tmr", {
poll_timer.interval, poll_timer.timer = xTimerCreate("poll_tmr",
pdTRUE, NULL, poll_timer.interval,
poll_timer_cb); pdTRUE, NULL,
xTimerStart(poll_timer.timer, 0); poll_timer_cb);
if (poll_timer.timer)
(void)xTimerStart(poll_timer.timer, 0);
}
// create and start heartbeat monitor timer if (s_presence_timer == NULL)
hb_timer.timer = xTimerCreate("hb_tmr", {
hb_timer.interval, s_presence_timer = xTimerCreate("presence_tmr",
pdFALSE, NULL, pdMS_TO_TICKS(PRESENCE_CHECK_MS),
hb_timer_cb); pdTRUE, NULL,
xTimerStart(hb_timer.timer, 0); presence_timer_cb);
if (s_presence_timer)
(void)xTimerStart(s_presence_timer, 0);
else
ESP_LOGE(TAG, "Failed to create presence timer");
}
} }

View File

@@ -1,199 +1,477 @@
// === components/evse_link/src/evse_link_slave.c === // components/evse_link/src/evse_link_slave.c
//
// Correções aplicadas:
// 1) Evitar TX dentro do callback RX: confirmação (heartbeat) é deferida via queue/task.
// 2) Proteger safe_mode (e flags relacionadas) com mux (evita race entre RX e timer).
// 3) Opção de política no fallback: por default faz PAUSE (mais seguro). Pode ser alterado por macro.
// 4) Reduzir ruído de logs em caminho quente (RX frames em DEBUG).
// 5) Manter semântica: só sai de safe_mode com comando explícito de potência (SET_CURRENT / RESUME).
// 6) Mantém lógica de pause SET_CURRENT=0 e resume quando >0.
//
// Nota: A enum do evse_state_event_data_t que enviaste está correta para o handler.
#include "evse_link.h" #include "evse_link.h"
#include "evse_link_events.h" #include "evse_link_events.h"
#include "loadbalancer_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"
#include "freertos/queue.h"
#include "freertos/portmacro.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_random.h"
#include "esp_err.h"
#include "evse_events.h" #include "evse_events.h"
#include "evse_state.h" #include "evse_state.h"
#include "evse_config.h" #include "evse_config.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
static const char *TAG = "evse_link_slave"; static const char *TAG = "evse_link_slave";
// Link commands #define MASTER_ID 0x01
#define CMD_POLL 0x01
#define CMD_HEARTBEAT 0x02 // not used by slave
#define CMD_CONFIG_BROADCAST 0x03
#define CMD_SET_CURRENT 0x08
#define CMD_HEARTBEAT_ACK 0x09
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização
// payload lengths (exclui seq byte) // Commands (opcode no payload[0])
#define LEN_POLL_REQ 1 // [ CMD_POLL ] #define CMD_POLL 0x01
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ] #define CMD_HEARTBEAT 0x02
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ] #define CMD_CONFIG_BROADCAST 0x03
#define LEN_HEARTBEAT_ACK 1 // [ CMD_HEARTBEAT_ACK ] #define CMD_SET_CURRENT 0x08
#define LEN_HEARTBEAT 6 // CMD_HEARTBEAT + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi #define CMD_HEARTBEAT_ACK 0x09
#define CMD_AUTH_GRANTED 0x0A
// #define CMD_RESUME 0x0B // se existir
// Timing // lengths (INCLUI opcode)
#define FALLBACK_TIMEOUT_MS 120000 #define LEN_SET_CURRENT 3
#define FALLBACK_TIMEOUT_MS 180000
// --- Política de fallback ---
// 1 = mais seguro: PAUSE (revoga autorização)
// 0 = força corrente mínima (pode continuar a carregar dependendo do core)
#ifndef EVSE_LINK_FALLBACK_PAUSE
#define EVSE_LINK_FALLBACK_PAUSE 1
#endif
// --- Confirmações via heartbeat (deferidas) ---
#define HB_REQ_QUEUE_LEN 8
typedef enum
{
HB_REQ_SEND = 1,
} hb_req_t;
static TimerHandle_t fallback_timer = NULL; static TimerHandle_t fallback_timer = NULL;
static bool safe_mode = false; static TaskHandle_t hb_task_handle = NULL;
// --- Helper to send a heartbeat frame --- // Task para enviar heartbeat sem bloquear RX callback
static void send_heartbeat_frame(void) { static TaskHandle_t hb_sender_task_handle = NULL;
bool charging = evse_state_is_charging(evse_get_state()); static QueueHandle_t hb_req_q = NULL;
uint16_t hw_max = evse_get_max_charging_current();
uint16_t runtime = evse_get_runtime_charging_current();
ESP_LOGI(TAG, "Sending HEARTBEAT: charging=%d hw_max=%uA runtime=%uA", // "safe mode" (master offline): aplica fallback local e nao sai com POLL/ACK.
charging, hw_max, runtime); // Só sai com comando explícito de potência (SET_CURRENT / RESUME).
static bool safe_mode = false;
static uint16_t saved_runtime_limit = 0; // informativo
// "remote pause" (SET_CURRENT=0)
static bool paused_by_master = false;
static bool paused_prev_authorized = false;
static portMUX_TYPE s_state_mux = portMUX_INITIALIZER_UNLOCKED;
static bool evse_handler_registered = false;
static size_t bounded_strlen_u8(const uint8_t *s, size_t max_len)
{
size_t i = 0;
if (!s)
return 0;
while (i < max_len && s[i] != 0)
i++;
return i;
}
static void send_heartbeat_frame_now(void)
{
bool charging = evse_state_is_charging(evse_get_state());
uint16_t hw_max = evse_get_max_charging_current();
uint16_t runtime = evse_get_runtime_charging_current();
uint8_t hb[] = { uint8_t hb[] = {
CMD_HEARTBEAT, CMD_HEARTBEAT,
charging ? 1 : 0, charging ? 1 : 0,
(uint8_t)(hw_max & 0xFF), (uint8_t)(hw_max >> 8), (uint8_t)(hw_max & 0xFF), (uint8_t)(hw_max >> 8),
(uint8_t)(runtime & 0xFF), (uint8_t)(runtime >> 8) (uint8_t)(runtime & 0xFF), (uint8_t)(runtime >> 8)};
};
// Broadcast to master (0xFF) (void)evse_link_send(MASTER_ID, hb, sizeof(hb)); // UNICAST
evse_link_send(0xFF, hb, sizeof(hb)); ESP_LOGI(TAG, "Send Heartbeat Frame");
} }
// pede heartbeat sem bloquear quem chama (RX callback, event handler, etc.)
// --- EVSE state change handler --- static void request_heartbeat_send(void)
static void evse_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; if (hb_req_q)
const evse_state_event_data_t *evt = data; {
if (evt->state==EVSE_STATE_EVENT_IDLE || evt->state==EVSE_STATE_EVENT_CHARGING) { hb_req_t req = HB_REQ_SEND;
send_heartbeat_frame(); // não bloqueia; se encher, apenas ignora (heartbeat periódico já existe)
(void)xQueueSendToBack(hb_req_q, &req, 0);
} }
else
{
// fallback: se queue ainda não existe, manda direto
send_heartbeat_frame_now();
}
}
static void hb_sender_task(void *arg)
{
(void)arg;
hb_req_t req;
for (;;)
{
if (xQueueReceive(hb_req_q, &req, portMAX_DELAY) != pdTRUE)
continue;
if (req == HB_REQ_SEND)
{
send_heartbeat_frame_now();
}
}
}
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)
return;
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
// Enforce pause: se algo tentar voltar a carregar enquanto paused_by_master, revoga auth.
bool paused;
portENTER_CRITICAL(&s_state_mux);
paused = paused_by_master;
portEXIT_CRITICAL(&s_state_mux);
if (paused && evt->state == EVSE_STATE_EVENT_CHARGING)
{
// Garante que não continua a carregar por clamp/auto-auth
evse_state_set_authorized(false);
}
// Envia heartbeat quando entra em IDLE ou CHARGING (estado relevante)
if (evt->state == EVSE_STATE_EVENT_IDLE || evt->state == EVSE_STATE_EVENT_CHARGING)
request_heartbeat_send();
}
// Sai de safe-mode APENAS com comando explícito (SET_CURRENT / RESUME).
static void maybe_exit_safe_mode_on_explicit_power_cmd(uint8_t cmd)
{
bool in_safe;
portENTER_CRITICAL(&s_state_mux);
in_safe = safe_mode;
portEXIT_CRITICAL(&s_state_mux);
if (!in_safe)
return;
if (cmd == CMD_SET_CURRENT /*|| cmd == CMD_RESUME*/)
{
portENTER_CRITICAL(&s_state_mux);
safe_mode = false;
portEXIT_CRITICAL(&s_state_mux);
ESP_LOGI(TAG, "Exiting safe mode due to explicit cmd 0x%02X", cmd);
}
}
static void apply_pause_by_master(void)
{
bool prev_auth = evse_state_get_authorized();
portENTER_CRITICAL(&s_state_mux);
paused_by_master = true;
paused_prev_authorized = prev_auth;
portEXIT_CRITICAL(&s_state_mux);
// Revoga autorização para parar contactor/pilot via core
if (prev_auth)
evse_state_set_authorized(false);
// Mantém runtime num valor seguro (não 0, pois clamp -> 6A).
// O "pause" efetivo é pela autorização=false.
evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
}
static void clear_pause_by_master_if_any(void)
{
bool was_paused;
bool prev_auth;
portENTER_CRITICAL(&s_state_mux);
was_paused = paused_by_master;
prev_auth = paused_prev_authorized;
paused_by_master = false;
paused_prev_authorized = false;
portEXIT_CRITICAL(&s_state_mux);
// Se estava autorizado antes do pause, tenta reautorizar
if (was_paused && prev_auth)
evse_state_set_authorized(true);
} }
static void on_frame_slave(uint8_t src, uint8_t dest, static void on_frame_slave(uint8_t src, uint8_t dest,
const uint8_t *payload, uint8_t len) { const uint8_t *payload, uint8_t len)
if (dest != evse_link_get_self_id() && dest != 0xFF) return; {
if (len < 1) return; const uint8_t self = evse_link_get_self_id();
// Muito verboso em caminho quente; deixa em DEBUG
ESP_LOGD(TAG, "RX frames (src=0x%02X dest=0x%02X len=%u self=0x%02X)", src, dest, len, self);
if (src == self)
return;
if (dest != self && dest != 0xFF)
return;
if (payload == NULL || len < 1)
{
ESP_LOGW(TAG, "RX invalid: payload NULL or len<1 (len=%u)", len);
return;
}
// Só aceitar comandos do master
if (src != MASTER_ID)
{
ESP_LOGW(TAG, "RX ignore: non-master src=0x%02X", src);
return;
}
// Qualquer frame válido do master => link vivo (reset do fallback)
if (fallback_timer)
(void)xTimerReset(fallback_timer, 0);
uint8_t cmd = payload[0]; uint8_t cmd = payload[0];
switch (cmd) {
switch (cmd)
{
case CMD_POLL: case CMD_POLL:
ESP_LOGD(TAG, "Received CMD_POLL from master 0x%02X", src); // Liveness only. Não sai de safe-mode e não restaura limites.
ESP_LOGI(TAG, "CMD_POLL from 0x%02X", src);
break; break;
case CMD_CONFIG_BROADCAST: case CMD_CONFIG_BROADCAST:
ESP_LOGD(TAG, "Received CMD_CONFIG_BROADCAST from master 0x%02X", src); ESP_LOGI(TAG, "CMD_CONFIG_BROADCAST from 0x%02X", src);
break; break;
case CMD_SET_CURRENT: { case CMD_HEARTBEAT_ACK:
if (len < LEN_SET_CURRENT) { ESP_LOGI(TAG, "HEARTBEAT_ACK from 0x%02X", src);
ESP_LOGW(TAG, "SET_CURRENT from 0x%02X with invalid length %u", src, len); break;
case CMD_SET_CURRENT:
{
ESP_LOGI(TAG, "SET_CURRENT from 0x%02X", src);
if (len < LEN_SET_CURRENT)
{
ESP_LOGW(TAG, "SET_CURRENT invalid len=%u from 0x%02X", len, src);
break; break;
} }
uint16_t amps = payload[1] | (payload[2] << 8); uint16_t amps = (uint16_t)(payload[1] | ((uint16_t)payload[2] << 8));
// Comando explícito => pode sair de safe-mode (mesmo se for pause)
maybe_exit_safe_mode_on_explicit_power_cmd(cmd);
if (amps == 0)
{
// PAUSE explícito
ESP_LOGI(TAG, "SET_CURRENT=0 => PAUSE (src=0x%02X)", src);
apply_pause_by_master();
// Confirma sem bloquear RX
request_heartbeat_send();
// Publica evento com 0A (semântica: pause)
(void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED,
&amps, sizeof(amps), portMAX_DELAY);
break;
}
clear_pause_by_master_if_any();
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_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED, // confirma sem bloquear RX
&amps, sizeof(amps), portMAX_DELAY); request_heartbeat_send();
ESP_LOGI(TAG, "Applied runtime limit: %uA from 0x%02X", (unsigned)amps, src);
(void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED,
&amps, sizeof(amps), portMAX_DELAY);
break; break;
} }
case CMD_HEARTBEAT_ACK: case CMD_AUTH_GRANTED:
ESP_LOGI(TAG, "Received HEARTBEAT_ACK from master 0x%02X", src); {
if (fallback_timer) { if (len < 2)
xTimerReset(fallback_timer, 0); {
if (safe_mode) { ESP_LOGW(TAG, "AUTH_GRANTED invalid len=%u from 0x%02X", len, src);
safe_mode = false;
uint16_t current = evse_get_runtime_charging_current();
evse_set_runtime_charging_current(current);
ESP_LOGI(TAG, "Exiting safe mode, restoring %uA", current);
}
}
break;
case CMD_AUTH_GRANTED: {
if (len < 2) {
ESP_LOGW(TAG, "CMD_AUTH_GRANTED from 0x%02X with invalid length %u", src, len);
break; break;
} }
const char *tag = (const char *)&payload[1]; const uint8_t *tag_ptr = &payload[1];
size_t tag_buf_len = (size_t)(len - 1);
size_t tag_len = bounded_strlen_u8(tag_ptr, tag_buf_len);
evse_link_auth_grant_event_t ev = {0}; evse_link_auth_grant_event_t ev = {0};
strncpy(ev.tag, tag, EVSE_LINK_TAG_MAX_LEN - 1); size_t copy_len = tag_len;
ev.tag[EVSE_LINK_TAG_MAX_LEN - 1] = '\0'; if (copy_len > (EVSE_LINK_TAG_MAX_LEN - 1))
copy_len = EVSE_LINK_TAG_MAX_LEN - 1;
ESP_LOGI(TAG, "Received CMD_AUTH_GRANTED from master 0x%02X, tag='%s'", src, ev.tag); if (copy_len > 0)
memcpy(ev.tag, tag_ptr, copy_len);
ev.tag[copy_len] = '\0';
esp_err_t err = esp_event_post( // AUTH_GRANTED não deve sair de safe-mode automaticamente
EVSE_LINK_EVENTS, ESP_LOGI(TAG, "AUTH_GRANTED from 0x%02X tag='%s'", src, ev.tag);
LINK_EVENT_REMOTE_AUTH_GRANTED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK) { 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)); ESP_LOGE(TAG, "Failed to post LINK_EVENT_REMOTE_AUTH_GRANTED: %s", esp_err_to_name(err));
}
break; break;
} }
default: default:
ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src); ESP_LOGW(TAG, "Unknown cmd 0x%02X from 0x%02X", cmd, src);
break;
} }
} }
static void slave_heartbeat_task(void *arg)
{
(void)arg;
const uint32_t period_ms = 60000;
uint8_t id = evse_link_get_self_id();
// --- Periodic heartbeat task --- // desfasamento por ID: 2s, 4s, 6s...
static void slave_heartbeat_task(void *arg) { vTaskDelay(pdMS_TO_TICKS((uint32_t)id * 2000));
const TickType_t interval = pdMS_TO_TICKS(10000);
for (;;) { for (;;)
send_heartbeat_frame(); {
vTaskDelay(interval); request_heartbeat_send();
// jitter opcional
uint32_t jitter_ms = esp_random() % 201; // 0..200ms
vTaskDelay(pdMS_TO_TICKS(period_ms + jitter_ms));
} }
} }
// --- Fallback safe mode callback --- static void fallback_timer_cb(TimerHandle_t xTimer)
static void fallback_timer_cb(TimerHandle_t xTimer) { {
if (!safe_mode) { (void)xTimer;
// entra safe_mode uma vez
bool already_safe;
portENTER_CRITICAL(&s_state_mux);
already_safe = safe_mode;
if (!safe_mode)
safe_mode = true; safe_mode = true;
ESP_LOGW(TAG, "Fallback timeout: entering safe mode"); portEXIT_CRITICAL(&s_state_mux);
evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
esp_event_post(EVSE_LINK_EVENTS, if (already_safe)
LINK_EVENT_SLAVE_OFFLINE, return;
NULL, 0, portMAX_DELAY);
} saved_runtime_limit = evse_get_runtime_charging_current();
#if EVSE_LINK_FALLBACK_PAUSE
ESP_LOGW(TAG, "Fallback timeout: entering safe mode (saved %uA). Policy=PAUSE",
(unsigned)saved_runtime_limit);
// pausar é mais seguro quando o master “morre”
apply_pause_by_master();
#else
ESP_LOGW(TAG, "Fallback timeout: entering safe mode (saved %uA, forcing %uA). Policy=MIN",
(unsigned)saved_runtime_limit, (unsigned)MIN_CHARGING_CURRENT_LIMIT);
evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
#endif
(void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_SLAVE_OFFLINE,
NULL, 0, portMAX_DELAY);
// opcional: manda heartbeat para indicar estado atual
request_heartbeat_send();
} }
// --- Slave initialization --- void evse_link_slave_init(void)
void evse_link_slave_init(void) { {
if (evse_link_get_mode()!=EVSE_LINK_MODE_SLAVE || !evse_link_is_enabled()) return; if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE || !evse_link_is_enabled())
return;
ESP_LOGI(TAG, "Initializing SLAVE mode (ID=0x%02X)", evse_link_get_self_id()); ESP_LOGI(TAG, "Initializing SLAVE mode (ID=0x%02X)", evse_link_get_self_id());
// register frame callback
evse_link_register_rx_cb(on_frame_slave); evse_link_register_rx_cb(on_frame_slave);
// start periodic heartbeat // cria queue/task do sender (para não mandar UART TX no callback RX)
xTaskCreate(slave_heartbeat_task, "slave_hb", 4096, NULL, 5, NULL); if (hb_req_q == NULL)
{
// fallback timer hb_req_q = xQueueCreate(HB_REQ_QUEUE_LEN, sizeof(hb_req_t));
fallback_timer = xTimerCreate("fallback_tmr", if (!hb_req_q)
pdMS_TO_TICKS(FALLBACK_TIMEOUT_MS), {
pdFALSE, NULL, ESP_LOGE(TAG, "Failed to create HB request queue (fallback to direct send)");
fallback_timer_cb); }
if (fallback_timer) { else
xTimerStart(fallback_timer, 0); {
if (xTaskCreate(hb_sender_task, "hb_sender", 3072, NULL, 3, &hb_sender_task_handle) != pdPASS)
{
ESP_LOGE(TAG, "Failed to create hb_sender task");
vQueueDelete(hb_req_q);
hb_req_q = NULL;
hb_sender_task_handle = NULL;
}
}
} }
// react to EVSE state changes if (hb_task_handle == NULL)
ESP_ERROR_CHECK( {
esp_event_handler_register( if (xTaskCreate(slave_heartbeat_task, "slave_hb", 4096, NULL, 3, &hb_task_handle) != pdPASS)
EVSE_EVENTS, {
EVSE_EVENT_STATE_CHANGED, ESP_LOGE(TAG, "Failed to create slave_heartbeat_task");
evse_event_handler, hb_task_handle = NULL;
NULL }
) }
);
}
// === Fim de: components/evse_link/src/evse_link_slave.c === if (fallback_timer == NULL)
{
fallback_timer = xTimerCreate("fallback_tmr",
pdMS_TO_TICKS(FALLBACK_TIMEOUT_MS),
pdFALSE, NULL,
fallback_timer_cb);
if (fallback_timer)
(void)xTimerStart(fallback_timer, 0);
else
ESP_LOGE(TAG, "Failed to create fallback timer");
}
else
{
(void)xTimerReset(fallback_timer, 0);
}
if (!evse_handler_registered)
{
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
evse_event_handler, NULL));
evse_handler_registered = true;
}
}

View File

@@ -13,7 +13,7 @@
#include "evse_state.h" #include "evse_state.h"
#include "ledc_driver.h" #include "ledc_driver.h"
#define BLOCK_TIME pdMS_TO_TICKS(10) #define BLOCK_TIME portMAX_DELAY
static const char *TAG = "led"; static const char *TAG = "led";
@@ -350,7 +350,7 @@ static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id,
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data; const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state); ESP_LOGD(TAG, "EVSE State Changed: state=%d", evt->state);
// Atualiza o estado base // Atualiza o estado base
current_state_mode = evt->state; current_state_mode = evt->state;
@@ -378,7 +378,7 @@ static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int
const evse_session_event_data_t *evt = const evse_session_event_data_t *evt =
(const evse_session_event_data_t *)data; (const evse_session_event_data_t *)data;
ESP_LOGI(TAG, ESP_LOGD(TAG,
"EVSE Session Event: type=%d, id=%" PRIu32 "EVSE Session Event: type=%d, id=%" PRIu32
", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d", ", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
(int)evt->type, (int)evt->type,

View File

@@ -1,8 +1,7 @@
set(srcs set(srcs
"src/input_filter.c" "src/loadbalancer.c" "src/loadbalancer_events.c" "src/input_filter.c" "src/loadbalancer.c" "src/pv_optimizer.c" "src/grid_limiter.c" "src/loadbalancer_events.c"
) )
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

@@ -0,0 +1,42 @@
#ifndef GRID_LIMITER_H_
#define GRID_LIMITER_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "meter_events.h"
void grid_limiter_init(void);
void grid_limiter_set_enabled(bool en);
bool grid_limiter_is_enabled(void);
esp_err_t grid_limiter_set_max_import_a(uint8_t a);
uint8_t grid_limiter_get_max_import_a(void);
/**
* @brief Calcula um novo "total_budget_a" (<= current_total_a) para respeitar max_import_a.
*
* Preferência:
* - Usa watt_total (+import / -export) se existir
* - Caso watt_total==0, usa fallback_grid_current_a (magnitude)
*
* @param grid_evt último evento do GRID
* @param fallback_grid_current_a corrente filtrada (magnitude) como fallback
* @param current_total_a total atual a atribuir aos EVSE (A)
* @return total_budget_a (<= current_total_a)
*/
float grid_limiter_limit_total_a(const meter_event_data_t *grid_evt,
float fallback_grid_current_a,
float current_total_a);
#ifdef __cplusplus
}
#endif
#endif /* GRID_LIMITER_H_ */

View File

@@ -9,36 +9,26 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
#include "esp_err.h" #include "esp_err.h"
/**
* @brief Inicializa o módulo de load balancer
*/
void loadbalancer_init(void); void loadbalancer_init(void);
/** void loadbalancer_set_enabled(bool enabled);
* @brief Task contínua do algoritmo de balanceamento
*/
void loadbalancer_task(void *param);
/**
* @brief Ativa ou desativa o load balancing
*/
void loadbalancer_set_enabled(bool value);
/**
* @brief Verifica se o load balancing está ativo
*/
bool loadbalancer_is_enabled(void); bool loadbalancer_is_enabled(void);
/** // GRID limit (A)
* @brief Define a corrente máxima do grid void loadbalancer_grid_set_enabled(bool en);
*/ bool loadbalancer_grid_is_enabled(void);
esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current); esp_err_t loadbalancer_grid_set_max_import_a(uint8_t a);
uint8_t loadbalancer_grid_get_max_import_a(void);
/** // PV optimizer (W)
* @brief Obtém a corrente máxima do grid void loadbalancer_pv_set_enabled(bool en);
*/ bool loadbalancer_pv_is_enabled(void);
uint8_t load_balancing_get_max_grid_current(void); esp_err_t loadbalancer_pv_set_max_import_w(int32_t w);
int32_t loadbalancer_pv_get_max_import_w(void);
// Aliases legacy (se quiseres manter chamadas antigas)
esp_err_t load_balancing_set_max_grid_current(uint8_t value);
uint8_t load_balancing_get_max_grid_current(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

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

@@ -0,0 +1,40 @@
#ifndef PV_OPTIMIZER_H_
#define PV_OPTIMIZER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "meter_events.h"
void pv_optimizer_init(void);
void pv_optimizer_set_enabled(bool en);
bool pv_optimizer_is_enabled(void);
esp_err_t pv_optimizer_set_max_import_w(int32_t w);
int32_t pv_optimizer_get_max_import_w(void);
/**
* @brief Calcula o budget TOTAL (A) para todos os EVSEs, para manter importação <= max_import_w.
*
* - max_import_w = 0 => modo "Só PV": tenta manter importação ~0 (só consome quando há exportação).
* - max_import_w > 0 => modo "PV-Grid": permite importar até esse valor.
*
* @param grid_evt Último evento do medidor GRID (watt_total assinado).
* @param last_total_cmd_a Soma da corrente comandada no ciclo anterior (A).
* @param total_hw_max_a Soma dos hw_max_current dos conectores ativos (A).
* @return budget_total_a (0..total_hw_max_a)
*/
float pv_optimizer_compute_budget_a(const meter_event_data_t *grid_evt,
float last_total_cmd_a,
float total_hw_max_a);
#ifdef __cplusplus
}
#endif
#endif /* PV_OPTIMIZER_H_ */

View File

@@ -0,0 +1,123 @@
#include "grid_limiter.h"
#include "esp_log.h"
#include <math.h>
static const char *TAG = "grid_limiter";
#define DEFAULT_VOLTAGE_V (230.0f)
typedef struct
{
bool enabled;
uint8_t max_import_a;
} grid_cfg_t;
static grid_cfg_t s_cfg = {
.enabled = false,
.max_import_a = 32};
static float clamp_pf(float pf)
{
if (pf < 0.05f || pf > 1.2f)
return 1.0f;
return pf;
}
static void estimate_v_and_phases(const meter_event_data_t *m, float *v_avg, int *nph)
{
float sum = 0.0f;
int cnt = 0;
if (!m)
{
*v_avg = DEFAULT_VOLTAGE_V;
*nph = 1;
return;
}
for (int i = 0; i < 3; i++)
{
if (m->vrms[i] > 80.0f)
{
sum += m->vrms[i];
cnt++;
}
}
if (cnt == 0)
{
*v_avg = DEFAULT_VOLTAGE_V;
*nph = 1;
return;
}
*v_avg = sum / (float)cnt;
*nph = cnt;
}
void grid_limiter_init(void) { /* nada */ }
void grid_limiter_set_enabled(bool en) { s_cfg.enabled = en; }
bool grid_limiter_is_enabled(void) { return s_cfg.enabled; }
esp_err_t grid_limiter_set_max_import_a(uint8_t a)
{
if (a < 6 || a > 100)
return ESP_ERR_INVALID_ARG;
s_cfg.max_import_a = a;
return ESP_OK;
}
uint8_t grid_limiter_get_max_import_a(void) { return s_cfg.max_import_a; }
float grid_limiter_limit_total_a(const meter_event_data_t *grid_evt,
float fallback_grid_current_a,
float current_total_a)
{
if (!s_cfg.enabled)
return current_total_a;
if (current_total_a <= 0.0f)
return 0.0f;
float i_import = 0.0f;
if (grid_evt && grid_evt->watt_total > 0)
{
float v_avg;
int nph;
estimate_v_and_phases(grid_evt, &v_avg, &nph);
const float pf = clamp_pf(grid_evt->power_factor);
const float denom = v_avg * (float)nph * pf;
if (denom > 10.0f)
{
i_import = ((float)grid_evt->watt_total) / denom;
}
else
{
i_import = fallback_grid_current_a;
}
}
else
{
// export (<=0) => import=0; ou sem potência => fallback
if (grid_evt && grid_evt->watt_total < 0)
i_import = 0.0f;
else
i_import = fallback_grid_current_a;
}
if (i_import <= (float)s_cfg.max_import_a + 0.01f)
return current_total_a;
const float over = i_import - (float)s_cfg.max_import_a;
const float cut_a = ceilf(over); // conservador
float new_total = current_total_a - cut_a;
if (new_total < 0.0f)
new_total = 0.0f;
ESP_LOGD(TAG, "cap: i_import=%.2fA max=%uA over=%.2fA total=%.1fA -> %.1fA",
i_import, (unsigned)s_cfg.max_import_a, over, current_total_a, new_total);
return new_total;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
#include "pv_optimizer.h"
#include "esp_log.h"
#include <math.h>
static const char *TAG = "pv_optimizer";
// internos (fixos, como pediste)
#define PV_MIN_EXPORT_W (50) // deadband export (anti-oscilações)
#define PV_TOTAL_RAMP_STEP_A (2.0f) // step total por ciclo (como tens loop 5s)
#define DEFAULT_VOLTAGE_V (230.0f)
typedef struct
{
bool enabled;
int32_t max_import_w; // >=0
} pv_cfg_t;
static pv_cfg_t s_cfg = {
.enabled = false,
.max_import_w = 0};
static float clamp_pf(float pf)
{
if (pf < 0.05f || pf > 1.2f)
return 1.0f;
return pf;
}
static void estimate_v_and_phases(const meter_event_data_t *m, float *v_avg, int *nph)
{
float sum = 0.0f;
int cnt = 0;
if (!m)
{
*v_avg = DEFAULT_VOLTAGE_V;
*nph = 1;
return;
}
for (int i = 0; i < 3; i++)
{
if (m->vrms[i] > 80.0f)
{
sum += m->vrms[i];
cnt++;
}
}
if (cnt == 0)
{
*v_avg = DEFAULT_VOLTAGE_V;
*nph = 1;
return;
}
*v_avg = sum / (float)cnt;
*nph = cnt;
}
void pv_optimizer_init(void)
{
// nada a fazer
}
void pv_optimizer_set_enabled(bool en) { s_cfg.enabled = en; }
bool pv_optimizer_is_enabled(void) { return s_cfg.enabled; }
esp_err_t pv_optimizer_set_max_import_w(int32_t w)
{
if (w < 0)
return ESP_ERR_INVALID_ARG;
s_cfg.max_import_w = w;
return ESP_OK;
}
int32_t pv_optimizer_get_max_import_w(void) { return s_cfg.max_import_w; }
static float ramp_total(float last_a, float target_a)
{
if (target_a > last_a + PV_TOTAL_RAMP_STEP_A)
return last_a + PV_TOTAL_RAMP_STEP_A;
if (target_a < last_a - PV_TOTAL_RAMP_STEP_A)
return last_a - PV_TOTAL_RAMP_STEP_A;
return target_a;
}
float pv_optimizer_compute_budget_a(const meter_event_data_t *grid_evt,
float last_total_cmd_a,
float total_hw_max_a)
{
if (!s_cfg.enabled)
return total_hw_max_a;
if (!grid_evt)
return 0.0f;
// se meter não fornece potência (fica 0) não dá para PV -> conservador: não importa
// (podes mudar para "mantém last" se preferires)
if (grid_evt->watt_total == 0)
{
return ramp_total(last_total_cmd_a, 0.0f);
}
float v_avg;
int nph;
estimate_v_and_phases(grid_evt, &v_avg, &nph);
const float pf = clamp_pf(grid_evt->power_factor);
const float w_per_a = v_avg * (float)nph * pf;
if (w_per_a < 10.0f)
{
return ramp_total(last_total_cmd_a, 0.0f);
}
const int32_t p_grid_w = grid_evt->watt_total; // +import / -export
const int32_t target_import_w = s_cfg.max_import_w; // >=0
// deadband só para o "Só PV"
if (target_import_w == 0)
{
if (p_grid_w < 0)
{
int32_t export_w = -p_grid_w;
if (export_w < PV_MIN_EXPORT_W)
{
return ramp_total(last_total_cmd_a, 0.0f);
}
}
else
{
// está a importar
if (p_grid_w < PV_MIN_EXPORT_W)
{
return ramp_total(last_total_cmd_a, 0.0f);
}
}
}
// estima base-load com o comando anterior
const float p_evse_last_w = last_total_cmd_a * w_per_a;
const float p_base_w = (float)p_grid_w - p_evse_last_w;
// queremos p_grid -> target_import_w
float p_evse_target_w = (float)target_import_w - p_base_w;
// clamp [0..max]
if (p_evse_target_w < 0.0f)
p_evse_target_w = 0.0f;
const float p_evse_max_w = total_hw_max_a * w_per_a;
if (p_evse_target_w > p_evse_max_w)
p_evse_target_w = p_evse_max_w;
float target_total_a = p_evse_target_w / w_per_a;
if (target_total_a < 0.0f)
target_total_a = 0.0f;
if (target_total_a > total_hw_max_a)
target_total_a = total_hw_max_a;
float ramped = ramp_total(last_total_cmd_a, target_total_a);
ESP_LOGD(TAG, "pv: p_grid=%ldW target_imp=%ldW base=%.1fW last=%.1fA -> target=%.1fA (v=%.1f nph=%d pf=%.2f)",
(long)p_grid_w, (long)target_import_w, p_base_w, last_total_cmd_a, ramped, v_avg, nph, pf);
return ramped;
}

View File

@@ -1,7 +0,0 @@
set(srcs
"src/logger.c"
"src/output_buffer.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include")

View File

@@ -1,58 +0,0 @@
#ifndef LOGGER_H_
#define LOGGER_H_
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#define LOGGER_SERIAL_BIT BIT0
/**
* @brief Logger event group LOGGER_SERIAL_BIT
*
*/
extern EventGroupHandle_t logger_event_group;
/**
* @brief Initialize logger
*
*/
void logger_init(void);
/**
* @brief Print
*
* @param str
*/
void logger_print(const char* str);
/**
* @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);
/**
* @brief Read line from index, set index for reading next entry
*
* @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);
#endif /* LOGGER_H_ */

View File

@@ -1,24 +0,0 @@
#ifndef OUTPUT_BUFFER_H_
#define OUTPUT_BUFFER_H_
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint16_t size;
uint16_t count;
uint8_t* data;
uint8_t* append;
} output_buffer_t;
output_buffer_t* output_buffer_create(uint16_t size);
void output_buffer_delete(output_buffer_t* buffer);
void output_buffer_append_buf(output_buffer_t* buffer, const char* buf, uint16_t len);
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);
#endif /* OUTPUT_BUFFER_H_ */

View File

@@ -1,71 +0,0 @@
#include <stdio.h>
#include <memory.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "logger.h"
#include "output_buffer.h"
#define LOG_BUFFER_SIZE 6096 //4096
#define MAX_LOG_SIZE 512
static SemaphoreHandle_t mutex;
static output_buffer_t * buffer = NULL;
EventGroupHandle_t logger_event_group = NULL;
void logger_init(void)
{
mutex = xSemaphoreCreateMutex();
logger_event_group = xEventGroupCreate();
buffer = output_buffer_create(LOG_BUFFER_SIZE);
}
uint16_t logger_count(void)
{
return buffer->count;
}
void logger_print(const char* str)
{
xSemaphoreTake(mutex, portMAX_DELAY);
output_buffer_append_str(buffer, str);
xEventGroupSetBits(logger_event_group, 0xFF);
xSemaphoreGive(mutex);
}
int logger_vprintf(const char* str, va_list l)
{
#ifdef CONFIG_ESP_CONSOLE_UART
vprintf(str, l);
#endif
xSemaphoreTake(mutex, portMAX_DELAY);
static char log[MAX_LOG_SIZE];
int len = vsnprintf(log, MAX_LOG_SIZE, str, l);
output_buffer_append_buf(buffer, log, len);
xEventGroupSetBits(logger_event_group, 0xFF);
xSemaphoreGive(mutex);
return len;
}
bool logger_read(uint16_t* index, char** str, uint16_t* len)
{
xSemaphoreTake(mutex, portMAX_DELAY);
bool has_next = output_buffer_read(buffer, index, str, len);
xSemaphoreGive(mutex);
return has_next;
}

View File

@@ -1,86 +0,0 @@
#include <memory.h>
#include "output_buffer.h"
output_buffer_t* output_buffer_create(uint16_t size)
{
output_buffer_t* buffer = (output_buffer_t*)malloc(sizeof(output_buffer_t));
buffer->size = size;
buffer->count = 0;
buffer->data = (uint8_t*)malloc(sizeof(uint8_t) * size);
buffer->append = buffer->data;
return buffer;
}
void output_buffer_delete(output_buffer_t* buffer)
{
free((void*)buffer->data);
free((void*)buffer);
}
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) {
//rotate buffer
uint8_t* pos = buffer->data;
uint16_t rotate_count = 0;
while ((pos - buffer->data) < buffer->size / 2) {
//seek first half
uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
rotate_count++;
}
memmove((void*)buffer->data, (void*)pos, buffer->size - (pos - buffer->data));
buffer->count -= rotate_count;
buffer->append -= (pos - buffer->data);
}
memcpy((void*)buffer->append, (void*)&len, sizeof(uint16_t));
buffer->append += sizeof(uint16_t);
memcpy((void*)buffer->append, (void*)str, len);
buffer->append += len;
buffer->count++;
}
void output_buffer_append_str(output_buffer_t* buffer, const char* str)
{
output_buffer_append_buf(buffer, str, strlen(str));
}
bool output_buffer_read(output_buffer_t* buffer, uint16_t* index, char** str, uint16_t* len)
{
if (*index > buffer->count) {
*index = buffer->count;
}
bool has_next = false;
if (*index < buffer->count) {
uint8_t* pos = buffer->data;
uint16_t current = 0;
while (current != *index) {
uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
current++;
}
memcpy((void*)len, (void*)pos, sizeof(uint16_t));
pos += sizeof(uint16_t);
*str = (char*)pos;
(*index)++;
has_next = true;
}
return has_next;
}

View File

@@ -1,29 +1,31 @@
# 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_modbus/meter_orno513.c
"driver/meter_orno/meter_orno526.c" driver/meter_modbus/meter_orno526.c
"driver/meter_orno/meter_orno516.c" driver/meter_modbus/meter_orno516.c
"driver/meter_orno/meter_dts6619.c" driver/meter_modbus/meter_dts6619.c
"driver/meter_orno/meter_dds661.c" driver/meter_modbus/meter_dds661.c
"driver/meter_orno/meter_ea777.c" driver/meter_modbus/meter_ea777.c
"driver/meter_orno/modbus_params.c" driver/meter_modbus/meter_dts024m.c
"driver/meter_zigbee/meter_zigbee.c" driver/meter_modbus/modbus_params.c
"src/meter_manager.c" driver/meter_zigbee/meter_zigbee.c
"src/meter_events.c" src/meter_manager.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_modbus
"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

@@ -0,0 +1,542 @@
// meter_dts024m.c — Driver Modbus RTU para DTS024M (ESP-IDF / esp-modbus)
// Versão PRODUÇÃO (SEM AUTO-PROBE): parâmetros fixos (baud/parity/id/FC/base).
// Ajusta os #defines DTS024M_PROD_* conforme o teu medidor.
#include "meter_events.h"
#include "modbus_params.h"
#include "mbcontroller.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stddef.h>
#include <string.h>
#include "meter_dts024m.h"
#define TAG "serial_mdb_dts024m"
// ===== UART / RS-485 =====
#define MB_PORT_NUM 2
// Ajuste os pinos conforme seu hardware
#define MB_UART_TXD 17
#define MB_UART_RXD 16
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485
// ===== Timings =====
#define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS)
#define POLL_INTERVAL (200 / portTICK_PERIOD_MS)
// ===== Helpers =====
#define STR(fieldname) ((const char *)(fieldname))
#define OPTS(min_val, max_val, step_val) {.opt1 = (min_val), .opt2 = (max_val), .opt3 = (step_val)}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
// ===== Config PRODUÇÃO (sem AUTO-PROBE) =====
// Ajusta estes valores:
#define DTS024M_PROD_BAUD 2400
#define DTS024M_PROD_PARITY UART_PARITY_DISABLE // 0 = none; UART_PARITY_EVEN se 8E1
#define DTS024M_PROD_SLAVE_ID 1 // endereço Modbus (1..247)
#define DTS024M_PROD_AREA MB_PARAM_INPUT // MB_PARAM_INPUT (FC04) ou MB_PARAM_HOLDING (FC03)
#define DTS024M_PROD_BASE_OFFSET 0 // 0 ou 1 (depende se o mapa é 0-based ou 1-based)
// ===== Estado =====
static bool is_initialized = false;
static bool mb_started = false;
static TaskHandle_t meter_task = NULL;
// ============================================================================
// MAPA DE REGISTROS (template) — pode variar conforme firmware.
// Estes endereços são um “perfil” comum.
// ============================================================================
#define DTS024M_L1_VOLTAGE 0x0000 // U32, 0.01 V (2 regs)
#define DTS024M_L2_VOLTAGE 0x0002
#define DTS024M_L3_VOLTAGE 0x0004
#define DTS024M_L1_CURRENT 0x0006 // U32, 0.001 A (2 regs)
#define DTS024M_L2_CURRENT 0x0008
#define DTS024M_L3_CURRENT 0x000A
#define DTS024M_L1_ACTIVE_P 0x000C // I32 (twos complement), (depende do modelo/escala)
#define DTS024M_L2_ACTIVE_P 0x000E
#define DTS024M_L3_ACTIVE_P 0x0010
#define DTS024M_PF_L1 0x001E // I16 (twos complement), 0.001
#define DTS024M_PF_L2 0x001F
#define DTS024M_PF_L3 0x0020
#define DTS024M_FREQUENCY 0x002A // U16, 0.01 Hz
#define DTS024M_TOTAL_ACTIVE_E 0x0404 // U32, 0.01 kWh (2 regs)
// ============================================================================
// Conversões signed (twos complement) — porque o projeto não tem PARAM_TYPE_I*
// ============================================================================
static inline int32_t s32_from_u32(uint32_t x)
{
return (x & 0x80000000u) ? (int32_t)(x - 0x100000000ULL) : (int32_t)x;
}
static inline int16_t s16_from_u16(uint16_t x)
{
return (x & 0x8000u) ? (int16_t)(x - 0x10000u) : (int16_t)x;
}
// ============================================================================
// CIDs
// ============================================================================
enum
{
CID_DTS024M_L1_VOLTAGE = 0,
CID_DTS024M_L2_VOLTAGE,
CID_DTS024M_L3_VOLTAGE,
CID_DTS024M_L1_CURRENT,
CID_DTS024M_L2_CURRENT,
CID_DTS024M_L3_CURRENT,
CID_DTS024M_L1_ACTIVE_P,
CID_DTS024M_L2_ACTIVE_P,
CID_DTS024M_L3_ACTIVE_P,
CID_DTS024M_PF_L1,
CID_DTS024M_PF_L2,
CID_DTS024M_PF_L3,
CID_DTS024M_FREQUENCY,
CID_DTS024M_TOTAL_ACTIVE_E,
};
// ============================================================================
// DESCRIPTORS (TEMPLATE) — copiamos para RAM e ajustamos:
// - slave_id
// - base offset (0/1)
// - mb_param_type (HOLDING/INPUT)
// ============================================================================
static const mb_parameter_descriptor_t device_parameters_dts024m_tmpl[] = {
// Tensões (U32 / 2 regs) — 0.01 V
{CID_DTS024M_L1_VOLTAGE, STR("L1 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L1_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_VOLTAGE, STR("L2 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L2_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_VOLTAGE, STR("L3 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L3_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// Correntes (U32 / 2 regs) — 0.001 A
{CID_DTS024M_L1_CURRENT, STR("L1 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L1_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_CURRENT, STR("L2 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L2_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_CURRENT, STR("L3 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L3_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// Potência ativa por fase (U32 / 2 regs no descriptor; interpretamos como signed I32)
{CID_DTS024M_L1_ACTIVE_P, STR("L1 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L1_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_ACTIVE_P, STR("L2 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L2_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_ACTIVE_P, STR("L3 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L3_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// PF (U16 / 1 reg; interpretamos como signed I16) — 0.001
{CID_DTS024M_PF_L1, STR("L1 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L1, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
{CID_DTS024M_PF_L2, STR("L2 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L2, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
{CID_DTS024M_PF_L3, STR("L3 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L3, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
// Frequência (U16 / 1 reg) — 0.01 Hz
{CID_DTS024M_FREQUENCY, STR("Frequency"), STR("Hz"), 1,
MB_PARAM_HOLDING, DTS024M_FREQUENCY, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 10000, 1), PAR_PERMS_READ},
// Energia ativa total (U32 / 2 regs) — 0.01 kWh
{CID_DTS024M_TOTAL_ACTIVE_E, STR("Total Active Energy"), STR("kWh"), 1,
MB_PARAM_HOLDING, DTS024M_TOTAL_ACTIVE_E, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
};
static mb_parameter_descriptor_t device_parameters_dts024m[ARRAY_SIZE(device_parameters_dts024m_tmpl)];
static const uint16_t num_device_parameters_dts024m = ARRAY_SIZE(device_parameters_dts024m);
static void dts024m_build_descriptors(uint8_t slave_id, uint16_t base_offset, mb_param_type_t area)
{
memcpy(device_parameters_dts024m,
device_parameters_dts024m_tmpl,
sizeof(device_parameters_dts024m));
for (uint16_t i = 0; i < num_device_parameters_dts024m; ++i)
{
device_parameters_dts024m[i].mb_slave_addr = slave_id;
device_parameters_dts024m[i].mb_reg_start =
(uint16_t)(device_parameters_dts024m[i].mb_reg_start + base_offset);
device_parameters_dts024m[i].mb_param_type = area; // HOLDING (FC03) ou INPUT (FC04)
}
}
// ============================================================================
// Modbus master init (fixo) — garante ordem correta (start -> uart_set_mode)
// ============================================================================
static esp_err_t dts024m_master_reinit(uint32_t baud, uart_parity_t parity)
{
if (mb_started)
{
(void)mbc_master_destroy();
mb_started = false;
}
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
}
mb_communication_info_t comm = {
.port = MB_PORT_NUM,
.mode = MB_MODE_RTU,
.baudrate = baud,
.parity = parity};
void *handler = NULL;
esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &handler);
if (err != ESP_OK)
return err;
err = mbc_master_setup(&comm);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
err = uart_set_pin(MB_PORT_NUM, MB_UART_TXD, MB_UART_RXD, MB_UART_RTS, UART_PIN_NO_CHANGE);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
// IMPORTANTE: start antes de uart_set_mode (driver UART costuma ser instalado no start)
err = mbc_master_start();
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
mb_started = true;
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
mb_started = false;
return err;
}
vTaskDelay(pdMS_TO_TICKS(40));
return ESP_OK;
}
// ============================================================================
// Post do evento de medição
// ============================================================================
static void meter_dts024m_post_event(float *voltage, float *current, int *power_w,
float freq_hz, float pf_avg, float total_kwh)
{
meter_event_data_t evt = {
.source = "GRID",
.frequency = freq_hz,
.power_factor = pf_avg,
.total_energy = total_kwh};
memcpy(evt.vrms, voltage, sizeof(evt.vrms));
memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
}
}
// ============================================================================
// Task de polling
// ============================================================================
static void serial_mdb_dts024m_task(void *param)
{
(void)param;
esp_err_t err;
const mb_parameter_descriptor_t *desc = NULL;
float v[3] = {0};
float i[3] = {0};
float pf[3] = {0};
float freq = 0.0f;
float total_kwh = 0.0f;
int p_w[3] = {0};
vTaskDelay(pdMS_TO_TICKS(200)); // settle
while (1)
{
for (uint16_t cid = 0; cid < num_device_parameters_dts024m; cid++)
{
err = mbc_master_get_cid_info(cid, &desc);
if (err != ESP_OK || !desc)
{
continue;
}
uint8_t type = 0;
uint16_t raw_u16 = 0;
uint32_t raw_u32 = 0;
void *value_ptr = &raw_u16;
// U32
switch (cid)
{
case CID_DTS024M_L1_VOLTAGE:
case CID_DTS024M_L2_VOLTAGE:
case CID_DTS024M_L3_VOLTAGE:
case CID_DTS024M_L1_CURRENT:
case CID_DTS024M_L2_CURRENT:
case CID_DTS024M_L3_CURRENT:
case CID_DTS024M_L1_ACTIVE_P:
case CID_DTS024M_L2_ACTIVE_P:
case CID_DTS024M_L3_ACTIVE_P:
case CID_DTS024M_TOTAL_ACTIVE_E:
value_ptr = &raw_u32;
break;
default:
value_ptr = &raw_u16;
break;
}
// 1 retry simples em caso de timeout (podes remover se quiseres menos carga)
err = mbc_master_get_parameter(cid,
(char *)desc->param_key,
(uint8_t *)value_ptr,
&type);
if (err == ESP_ERR_TIMEOUT)
{
vTaskDelay(pdMS_TO_TICKS(60));
err = mbc_master_get_parameter(cid,
(char *)desc->param_key,
(uint8_t *)value_ptr,
&type);
}
if (err == ESP_OK)
{
switch (cid)
{
// V (0.01V)
case CID_DTS024M_L1_VOLTAGE:
v[0] = ((float)raw_u32) * 0.01f;
break;
case CID_DTS024M_L2_VOLTAGE:
v[1] = ((float)raw_u32) * 0.01f;
break;
case CID_DTS024M_L3_VOLTAGE:
v[2] = ((float)raw_u32) * 0.01f;
break;
// I (0.001A)
case CID_DTS024M_L1_CURRENT:
i[0] = ((float)raw_u32) * 0.001f;
break;
case CID_DTS024M_L2_CURRENT:
i[1] = ((float)raw_u32) * 0.001f;
break;
case CID_DTS024M_L3_CURRENT:
i[2] = ((float)raw_u32) * 0.001f;
break;
// P ativa (twos complement I32) — atenção: escala depende do modelo
case CID_DTS024M_L1_ACTIVE_P:
p_w[0] = (int)s32_from_u32(raw_u32);
break;
case CID_DTS024M_L2_ACTIVE_P:
p_w[1] = (int)s32_from_u32(raw_u32);
break;
case CID_DTS024M_L3_ACTIVE_P:
p_w[2] = (int)s32_from_u32(raw_u32);
break;
// PF (twos complement I16; 0.001)
case CID_DTS024M_PF_L1:
pf[0] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
case CID_DTS024M_PF_L2:
pf[1] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
case CID_DTS024M_PF_L3:
pf[2] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
// Freq (0.01Hz)
case CID_DTS024M_FREQUENCY:
freq = ((float)raw_u16) * 0.01f;
break;
// Energia (0.01kWh)
case CID_DTS024M_TOTAL_ACTIVE_E:
total_kwh = ((float)raw_u32) * 0.01f;
break;
default:
break;
}
ESP_LOGD(TAG, "%s (cid=%u) ok (u16=%u u32=%u)",
desc->param_key, cid, (unsigned)raw_u16, (unsigned)raw_u32);
}
else
{
ESP_LOGE(TAG, "CID %u (%s) read failed: %s",
cid, desc->param_key, esp_err_to_name(err));
}
vTaskDelay(POLL_INTERVAL);
}
// PF médio simples (ignora zeros)
float pf_sum = 0.0f;
int pf_cnt = 0;
for (int k = 0; k < 3; ++k)
{
if (pf[k] != 0.0f)
{
pf_sum += pf[k];
pf_cnt++;
}
}
float pf_avg = (pf_cnt ? pf_sum / pf_cnt : 0.0f);
meter_dts024m_post_event(v, i, p_w, freq, pf_avg, total_kwh);
vTaskDelay(UPDATE_INTERVAL);
}
}
// ============================================================================
// Init / Start / Stop
// ============================================================================
esp_err_t meter_dts024m_init(void)
{
if (is_initialized)
{
ESP_LOGW(TAG, "Already initialized");
return ESP_ERR_INVALID_STATE;
}
// init fixo (produção)
esp_err_t err = dts024m_master_reinit(DTS024M_PROD_BAUD, DTS024M_PROD_PARITY);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "master_reinit failed: %s", esp_err_to_name(err));
return err;
}
// monta descriptors reais com ID/offset/area fixos
dts024m_build_descriptors(DTS024M_PROD_SLAVE_ID, DTS024M_PROD_BASE_OFFSET, DTS024M_PROD_AREA);
// aplica descriptors reais
esp_err_t derr = mbc_master_set_descriptor(device_parameters_dts024m,
num_device_parameters_dts024m);
if (derr != ESP_OK)
{
ESP_LOGE(TAG, "set_descriptor failed: %s", esp_err_to_name(derr));
return derr;
}
is_initialized = true;
ESP_LOGI(TAG, "DTS024M initialized (PROD) baud=%d parity=%d id=%d area=%s base=%d",
DTS024M_PROD_BAUD,
(int)DTS024M_PROD_PARITY,
DTS024M_PROD_SLAVE_ID,
(DTS024M_PROD_AREA == MB_PARAM_HOLDING ? "FC03" : "FC04"),
DTS024M_PROD_BASE_OFFSET);
return ESP_OK;
}
esp_err_t meter_dts024m_start(void)
{
if (!is_initialized)
{
ESP_LOGE(TAG, "Not initialized");
return ESP_ERR_INVALID_STATE;
}
if (meter_task == NULL)
{
xTaskCreate(serial_mdb_dts024m_task,
"meter_dts024m_task",
4096, NULL, 3, &meter_task);
ESP_LOGI(TAG, "DTS024M task started");
}
return ESP_OK;
}
void meter_dts024m_stop(void)
{
if (!is_initialized)
{
ESP_LOGW(TAG, "Not initialized, skipping stop");
return;
}
if (meter_task)
{
vTaskDelete(meter_task);
meter_task = NULL;
ESP_LOGI(TAG, "DTS024M task stopped");
}
if (mb_started)
{
(void)mbc_master_destroy();
mb_started = false;
}
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted");
}
is_initialized = false;
ESP_LOGI(TAG, "Meter DTS024M cleaned up");
}

View File

@@ -0,0 +1,35 @@
#ifndef METER_DTS024M_H_
#define METER_DTS024M_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa o driver do medidor DTS024M (UART RS485, Modbus, registradores).
*
* @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro.
*/
esp_err_t meter_dts024m_init(void);
/**
* @brief Inicia a tarefa de leitura de dados do medidor DTS024M.
*
* @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro.
*/
esp_err_t meter_dts024m_start(void);
/**
* @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS024M.
*/
void meter_dts024m_stop(void);
#ifdef __cplusplus
}
#endif
#endif /* METER_DTS024M_H_ */

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

@@ -7,14 +7,14 @@
/** /**
* @brief Inicializa o driver do medidor dts6619 (SPI, mutex, 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_dts6619_init(void); esp_err_t meter_dts6619_init(void);
/** /**
* @brief Inicia a tarefa de leitura de dados do medidor DTS6619. * @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_dts6619_start(void); esp_err_t meter_dts6619_start(void);
@@ -24,7 +24,6 @@ esp_err_t meter_dts6619_start(void);
*/ */
void meter_dts6619_stop(void); void meter_dts6619_stop(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -7,17 +7,18 @@
#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"
// ===== UART / RS-485 ===== // ===== UART / RS-485 =====
#define MB_PORT_NUM 2 #define MB_PORT_NUM 1
#define MB_DEV_SPEED 9600 #define MB_DEV_SPEED 9600
// Ajuste os pinos conforme seu hardware // Ajuste os pinos conforme seu hardware
#define MB_UART_TXD 17 #define MB_UART_TXD 21
#define MB_UART_RXD 16 #define MB_UART_RXD 22
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485 #define MB_UART_RTS UART_PIN_NO_CHANGE // sem DE/RE
// ===== Timings ===== // ===== Timings =====
#define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS) #define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS)
@@ -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));
@@ -321,9 +322,10 @@ esp_err_t meter_ea777_init(void)
ESP_ERROR_CHECK(mbc_master_setup(&comm)); ESP_ERROR_CHECK(mbc_master_setup(&comm));
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM,
MB_UART_TXD, MB_UART_RXD, MB_UART_TXD, MB_UART_RXD,
MB_UART_RTS, UART_PIN_NO_CHANGE)); UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(mbc_master_start()); ESP_ERROR_CHECK(mbc_master_start());
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX)); ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART));
// ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART));
vTaskDelay(pdMS_TO_TICKS(50)); vTaskDelay(pdMS_TO_TICKS(50));
ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_ea777, ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_ea777,

View File

@@ -32,4 +32,4 @@ void meter_ea777_stop(void);
} }
#endif #endif
#endif /* METER_EA777_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,36 +12,37 @@
#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
#define RX_FRAME_SIZE 14 #define RX_FRAME_SIZE 14
// Zigbee Attribute IDs // Zigbee Attribute IDs
#define ATTR_CURRENT_L1 0x0006 #define ATTR_CURRENT_L1 0x0006
#define ATTR_CURRENT_L2 0x0007 #define ATTR_CURRENT_L2 0x0007
#define ATTR_CURRENT_L3 0x0008 #define ATTR_CURRENT_L3 0x0008
#define ATTR_VOLTAGE_L1 0x0266 #define ATTR_VOLTAGE_L1 0x0266
#define ATTR_CURRENT_L1_ALT 0x0267 #define ATTR_CURRENT_L1_ALT 0x0267
#define ATTR_POWER_L1 0x0268 #define ATTR_POWER_L1 0x0268
#define ATTR_VOLTAGE_L2 0x0269 #define ATTR_VOLTAGE_L2 0x0269
#define ATTR_CURRENT_L2_ALT 0x026A #define ATTR_CURRENT_L2_ALT 0x026A
#define ATTR_POWER_L2 0x026B #define ATTR_POWER_L2 0x026B
#define ATTR_VOLTAGE_L3 0x026C #define ATTR_VOLTAGE_L3 0x026C
#define ATTR_CURRENT_L3_ALT 0x026D #define ATTR_CURRENT_L3_ALT 0x026D
#define ATTR_POWER_L3 0x026E #define ATTR_POWER_L3 0x026E
#define ATTR_FREQUENCY 0x0265 #define ATTR_FREQUENCY 0x0265
#define ATTR_POWER_FACTOR 0x020F #define ATTR_POWER_FACTOR 0x020F
#define ATTR_TOTAL_ENERGY 0x0201 #define ATTR_TOTAL_ENERGY 0x0201
#define PHASE_COUNT 3 #define PHASE_COUNT 3
#define PHASE_L1 0 #define PHASE_L1 0
#define PHASE_L2 1 #define PHASE_L2 1
#define PHASE_L3 2 #define PHASE_L3 2
// Internal meter state // Internal meter state
typedef struct { typedef struct
{
float vrms[PHASE_COUNT]; float vrms[PHASE_COUNT];
float irms[PHASE_COUNT]; float irms[PHASE_COUNT];
int watt[PHASE_COUNT]; int watt[PHASE_COUNT];
@@ -58,24 +59,28 @@ static meter_zigbee_data_t meter_data = {0};
static SemaphoreHandle_t meter_mutex = NULL; static SemaphoreHandle_t meter_mutex = NULL;
static TaskHandle_t meter_zigbee_task = NULL; static TaskHandle_t meter_zigbee_task = NULL;
bool meter_zigbee_is_running(void)
bool meter_zigbee_is_running(void) { {
return meter_zigbee_task != NULL; return meter_zigbee_task != NULL;
} }
void send_stop_command(void) { static inline int32_t tuya_power16_to_signed(uint16_t p)
//const char *cmd = "stop\n"; // Comando enviado para o outro lado interpretar e dormir {
//uart_write_bytes(UART_PORT, cmd, strlen(cmd)); // Igual ao quirk multi_dp_to_power()
//uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(100)); // Aguarda envio terminar if (p > 0x7FFF)
{
return (int32_t)((0x999A - p) * -1);
}
return (int32_t)p;
} }
static void meter_zigbee_post_event(void) { static void meter_zigbee_post_event(void)
{
meter_event_data_t evt = { meter_event_data_t evt = {
.source = "GRID", .source = "GRID",
.frequency = meter_data.frequency, .frequency = meter_data.frequency,
.power_factor = meter_data.power_factor, .power_factor = meter_data.power_factor,
.total_energy = meter_data.total_energy .total_energy = meter_data.total_energy};
};
memcpy(evt.vrms, meter_data.vrms, sizeof(evt.vrms)); memcpy(evt.vrms, meter_data.vrms, sizeof(evt.vrms));
memcpy(evt.irms, meter_data.irms, sizeof(evt.irms)); memcpy(evt.irms, meter_data.irms, sizeof(evt.irms));
@@ -85,19 +90,21 @@ 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));
} }
} }
static void handle_zigbee_frame(const uint8_t *buf, size_t len)
{
ESP_LOGD(TAG, "Received UART frame (%d bytes):", len);
// ESP_LOG_BUFFER_HEX(TAG, buf, len);
static void handle_zigbee_frame(const uint8_t *buf, size_t len) { if (len < RX_FRAME_SIZE)
ESP_LOGI(TAG, "Received UART frame (%d bytes):", len); {
ESP_LOG_BUFFER_HEX(TAG, buf, len);
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);
return; return;
} }
@@ -105,70 +112,85 @@ static void handle_zigbee_frame(const uint8_t *buf, size_t len) {
uint16_t attr = buf[2] | (buf[3] << 8); uint16_t attr = buf[2] | (buf[3] << 8);
uint8_t size = buf[5]; uint8_t size = buf[5];
if (size != 8) { if (size != 8)
{
ESP_LOGW(TAG, "Unsupported payload size: %d", size); ESP_LOGW(TAG, "Unsupported payload size: %d", size);
return; return;
} }
uint16_t volt_raw = (buf[6] << 8) | buf[7]; // payload 8 bytes começa em buf[6]
uint32_t current_raw = (buf[8] << 16) | (buf[9] << 8) | buf[10]; const uint8_t *p = &buf[6];
uint32_t power_raw = (buf[11] << 16) | (buf[12] << 8) | buf[13];
float volt = volt_raw / 10.0f; uint16_t volt_raw = ((uint16_t)p[0] << 8) | p[1];
float current = current_raw / 1000.0f;
float power = power_raw;
ESP_LOGI(TAG, "Parsed Attr 0x%04X: V=%.1fV I=%.2fA P=%.1fW", attr, volt, current, power); uint16_t curr_raw_u16 = ((uint16_t)p[3] << 8) | p[4]; // 2 bytes
uint16_t pow_raw_u16 = ((uint16_t)p[6] << 8) | p[7]; // 2 bytes
if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) { int32_t power = tuya_power16_to_signed(pow_raw_u16);
switch (attr) {
case ATTR_CURRENT_L1: float volt = volt_raw / 10.0f;
case ATTR_CURRENT_L1_ALT: float curr = curr_raw_u16 / 1000.0f;
meter_data.irms[PHASE_L1] = current;
meter_data.vrms[PHASE_L1] = volt; // Se queres “corrente com sinal”, deriva pelo sinal da potência:
meter_data.watt[PHASE_L1] = (int)power; float current = (power < 0) ? -curr : curr;
phase_updated[PHASE_L1] = true;
break; ESP_LOGD(TAG, "Attr 0x%04X: V=%.1fV I=%.3fA (signed=%+.3fA) P=%+ldW",
case ATTR_CURRENT_L2: attr, volt, curr, current, (long)power);
case ATTR_CURRENT_L2_ALT:
meter_data.irms[PHASE_L2] = current; if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE)
meter_data.vrms[PHASE_L2] = volt; {
meter_data.watt[PHASE_L2] = (int)power; switch (attr)
phase_updated[PHASE_L2] = true; {
break; case ATTR_CURRENT_L1:
case ATTR_CURRENT_L3: case ATTR_CURRENT_L1_ALT:
case ATTR_CURRENT_L3_ALT: meter_data.irms[PHASE_L1] = current;
meter_data.irms[PHASE_L3] = current; meter_data.vrms[PHASE_L1] = volt;
meter_data.vrms[PHASE_L3] = volt; meter_data.watt[PHASE_L1] = (int)power;
meter_data.watt[PHASE_L3] = (int)power; phase_updated[PHASE_L1] = true;
phase_updated[PHASE_L3] = true; break;
break; case ATTR_CURRENT_L2:
case ATTR_POWER_FACTOR: case ATTR_CURRENT_L2_ALT:
meter_data.power_factor = 0; meter_data.irms[PHASE_L2] = current;
break; meter_data.vrms[PHASE_L2] = volt;
case ATTR_FREQUENCY: meter_data.watt[PHASE_L2] = (int)power;
meter_data.frequency = 0; phase_updated[PHASE_L2] = true;
break; break;
case ATTR_TOTAL_ENERGY: case ATTR_CURRENT_L3:
meter_data.total_energy = 0; case ATTR_CURRENT_L3_ALT:
break; meter_data.irms[PHASE_L3] = current;
default: meter_data.vrms[PHASE_L3] = volt;
ESP_LOGW(TAG, "Unknown attr: 0x%04X", attr); meter_data.watt[PHASE_L3] = (int)power;
break; phase_updated[PHASE_L3] = true;
break;
case ATTR_POWER_FACTOR:
meter_data.power_factor = 0;
break;
case ATTR_FREQUENCY:
meter_data.frequency = 0;
break;
case ATTR_TOTAL_ENERGY:
meter_data.total_energy = 0;
break;
default:
ESP_LOGW(TAG, "Unknown attr: 0x%04X", attr);
break;
} }
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
} }
// Verifica se todas as 3 fases foram atualizadas // Verifica se todas as 3 fases foram atualizadas
if (phase_updated[PHASE_L1] && phase_updated[PHASE_L2] && phase_updated[PHASE_L3]) { if (phase_updated[PHASE_L1] && phase_updated[PHASE_L2] && phase_updated[PHASE_L3])
{
meter_zigbee_post_event(); meter_zigbee_post_event();
memset(phase_updated, 0, sizeof(phase_updated)); memset(phase_updated, 0, sizeof(phase_updated));
} }
} }
static void meter_zigbee_task_func(void *param) { static void meter_zigbee_task_func(void *param)
{
uint8_t *buf = malloc(RX_FRAME_SIZE); uint8_t *buf = malloc(RX_FRAME_SIZE);
if (!buf) { if (!buf)
{
ESP_LOGE(TAG, "Failed to allocate buffer"); ESP_LOGE(TAG, "Failed to allocate buffer");
vTaskDelete(NULL); vTaskDelete(NULL);
return; return;
@@ -176,13 +198,19 @@ static void meter_zigbee_task_func(void *param) {
ESP_LOGI(TAG, "Zigbee meter task started"); ESP_LOGI(TAG, "Zigbee meter task started");
while (1) { while (1)
{
int len = uart_read_bytes(UART_PORT, buf, RX_FRAME_SIZE, pdMS_TO_TICKS(5000)); int len = uart_read_bytes(UART_PORT, buf, RX_FRAME_SIZE, pdMS_TO_TICKS(5000));
if (len == RX_FRAME_SIZE) { if (len == RX_FRAME_SIZE)
{
handle_zigbee_frame(buf, len); handle_zigbee_frame(buf, len);
} else if (len == 0) { }
else if (len == 0)
{
ESP_LOGD(TAG, "UART timeout with no data"); ESP_LOGD(TAG, "UART timeout with no data");
} else { }
else
{
ESP_LOGW(TAG, "Incomplete frame received (%d bytes)", len); ESP_LOGW(TAG, "Incomplete frame received (%d bytes)", len);
} }
} }
@@ -191,22 +219,24 @@ static void meter_zigbee_task_func(void *param) {
vTaskDelete(NULL); vTaskDelete(NULL);
} }
esp_err_t meter_zigbee_init(void) { esp_err_t meter_zigbee_init(void)
{
ESP_LOGI(TAG, "Initializing Zigbee meter"); ESP_LOGI(TAG, "Initializing Zigbee meter");
if (!meter_mutex) { if (!meter_mutex)
{
meter_mutex = xSemaphoreCreateMutex(); meter_mutex = xSemaphoreCreateMutex();
if (!meter_mutex) return ESP_ERR_NO_MEM; if (!meter_mutex)
return ESP_ERR_NO_MEM;
} }
uart_config_t config = { uart_config_t config = {
.baud_rate = 115200, .baud_rate = 115200,
.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 .source_clk = UART_SCLK_DEFAULT};
};
ESP_ERROR_CHECK(uart_param_config(UART_PORT, &config)); ESP_ERROR_CHECK(uart_param_config(UART_PORT, &config));
ESP_ERROR_CHECK(uart_set_pin(UART_PORT, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_set_pin(UART_PORT, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
@@ -215,30 +245,28 @@ esp_err_t meter_zigbee_init(void) {
return ESP_OK; return ESP_OK;
} }
esp_err_t meter_zigbee_start(void) { esp_err_t meter_zigbee_start(void)
if (meter_zigbee_task) return ESP_ERR_INVALID_STATE; {
if (meter_zigbee_task)
return ESP_ERR_INVALID_STATE;
xTaskCreate(meter_zigbee_task_func, "meter_zigbee_task", 4096, NULL, 3, &meter_zigbee_task); xTaskCreate(meter_zigbee_task_func, "meter_zigbee_task", 4096, NULL, 3, &meter_zigbee_task);
return ESP_OK; return ESP_OK;
} }
void meter_zigbee_stop(void)
{
if (meter_zigbee_task)
{
void meter_zigbee_stop(void) {
//send_stop_command();
//vTaskDelay(pdMS_TO_TICKS(100)); // Aguarda o outro lado processar
if (meter_zigbee_task) {
vTaskDelete(meter_zigbee_task); vTaskDelete(meter_zigbee_task);
meter_zigbee_task = NULL; meter_zigbee_task = NULL;
} }
uart_driver_delete(UART_PORT); uart_driver_delete(UART_PORT);
if (meter_mutex) { if (meter_mutex)
{
vSemaphoreDelete(meter_mutex); vSemaphoreDelete(meter_mutex);
meter_mutex = NULL; meter_mutex = NULL;
} }

View File

@@ -2,34 +2,53 @@
#define METER_EVENTS_H #define METER_EVENTS_H
#include "esp_event.h" #include "esp_event.h"
#include "meter_manager.h" // Para meter_type_t #include "meter_manager.h" // meter_type_t
#include <stdint.h> // int32_t, int64_t
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C"
{
#endif #endif
// Base de eventos dos medidores // Base de eventos dos medidores
ESP_EVENT_DECLARE_BASE(METER_EVENT); ESP_EVENT_DECLARE_BASE(METER_EVENT);
// IDs de eventos emitidos por medidores // IDs de eventos emitidos por medidores
typedef enum { typedef enum
METER_EVENT_DATA_READY = 0, {
METER_EVENT_ERROR, METER_EVENT_DATA_READY = 0,
METER_EVENT_STARTED, METER_EVENT_ERROR,
METER_EVENT_STOPPED METER_EVENT_STARTED,
} meter_event_id_t; METER_EVENT_STOPPED,
METER_EVENT_CONFIG_UPDATED
} meter_event_id_t;
// Estrutura de dados enviados com METER_EVENT_DATA_READY // Estrutura de dados enviados com METER_EVENT_DATA_READY
typedef struct { // NOTA: campos não suportados pelo meter devem ficar a 0.
const char *source; // "GRID" ou "EVSE" typedef struct
float vrms[3]; // Tensão por fase {
float irms[3]; // Corrente por fase const char *source; // "GRID" ou "EVSE"
int watt[3]; // Potência ativa por fase
float frequency; // Frequência da rede (Hz)
float power_factor; // Fator de potência
float total_energy; // Energia acumulada (kWh)
} meter_event_data_t;
float vrms[3]; // V por fase (0 se não existir)
float irms[3]; // A por fase (0 se não existir)
int32_t watt[3]; // W por fase (0 se não existir)
int32_t watt_total; // W total ASSINADO: +import / -export (0 se não existir)
float frequency; // Hz (0 se não existir)
float power_factor; // (0 se não existir)
float total_energy; // kWh (0 se não existir)
int64_t timestamp_us; // esp_timer_get_time() (0 => consumidor pode usar "now")
} meter_event_data_t;
// Estrutura de dados enviados com METER_EVENT_CONFIG_UPDATED
typedef struct
{
meter_type_t grid_type;
meter_type_t evse_type;
int64_t timestamp_us; // esp_timer_get_time()
} meter_config_event_t;
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -17,7 +17,8 @@ typedef enum {
METER_TYPE_DTS6619, // dts6619 METER_TYPE_DTS6619, // dts6619
METER_TYPE_MONO_ZIGBEE, // Zigbee single-phase METER_TYPE_MONO_ZIGBEE, // Zigbee single-phase
METER_TYPE_TRIF_ZIGBEE, // Zigbee three-phase METER_TYPE_TRIF_ZIGBEE, // Zigbee three-phase
METER_TYPE_EA777 // EA777 METER_TYPE_EA777, // EA777
METER_TYPE_DTS024M,
} meter_type_t; } meter_type_t;
/** /**

View File

@@ -9,11 +9,17 @@
#include "meter_dds661.h" #include "meter_dds661.h"
#include "meter_zigbee.h" #include "meter_zigbee.h"
#include "meter_ea777.h" #include "meter_ea777.h"
#include "meter_dts024m.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 +27,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);
} return ESP_OK;
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); // se não existir / inválido -> default NONE e grava
return ESP_OK; *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_nvs(const char *key, meter_type_t meter_type) 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");
ESP_EVENT_ANY_ID, err = esp_event_handler_register(
meter_manager_network_event_handler, NETWORK_EVENTS,
NULL); ESP_EVENT_ANY_ID,
meter_manager_network_event_handler,
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)
{ {
@@ -213,6 +240,8 @@ esp_err_t meter_manager_evse_init()
return meter_dds661_init(); return meter_dds661_init();
case METER_TYPE_EA777: case METER_TYPE_EA777:
return meter_ea777_init(); return meter_ea777_init();
case METER_TYPE_DTS024M:
return meter_dts024m_init();
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
return meter_zigbee_init(); return meter_zigbee_init();
@@ -242,6 +271,8 @@ esp_err_t meter_manager_evse_start()
return meter_dds661_start(); return meter_dds661_start();
case METER_TYPE_EA777: case METER_TYPE_EA777:
return meter_ea777_start(); return meter_ea777_start();
case METER_TYPE_DTS024M:
return meter_dts024m_start();
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
return meter_zigbee_start(); return meter_zigbee_start();
@@ -278,6 +309,9 @@ esp_err_t meter_manager_evse_stop(void)
case METER_TYPE_EA777: case METER_TYPE_EA777:
meter_ea777_stop(); meter_ea777_stop();
break; break;
case METER_TYPE_DTS024M:
meter_dts024m_stop();
break;
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
meter_zigbee_stop(); meter_zigbee_stop();
@@ -292,13 +326,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;
@@ -316,6 +350,8 @@ esp_err_t meter_manager_grid_init()
return meter_dds661_init(); return meter_dds661_init();
case METER_TYPE_EA777: case METER_TYPE_EA777:
return meter_ea777_init(); return meter_ea777_init();
case METER_TYPE_DTS024M:
return meter_dts024m_init();
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
return meter_zigbee_init(); return meter_zigbee_init();
@@ -334,7 +370,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:
@@ -345,6 +381,8 @@ esp_err_t meter_manager_grid_start()
return meter_dds661_start(); return meter_dds661_start();
case METER_TYPE_EA777: case METER_TYPE_EA777:
return meter_ea777_start(); return meter_ea777_start();
case METER_TYPE_DTS024M:
return meter_dts024m_start();
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
return meter_zigbee_start(); return meter_zigbee_start();
@@ -364,7 +402,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();
@@ -381,6 +419,9 @@ esp_err_t meter_manager_grid_stop(void)
case METER_TYPE_EA777: case METER_TYPE_EA777:
meter_ea777_stop(); meter_ea777_stop();
break; break;
case METER_TYPE_DTS024M:
meter_dts024m_stop();
break;
case METER_TYPE_MONO_ZIGBEE: case METER_TYPE_MONO_ZIGBEE:
case METER_TYPE_TRIF_ZIGBEE: case METER_TYPE_TRIF_ZIGBEE:
meter_zigbee_stop(); meter_zigbee_stop();
@@ -391,18 +432,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 +497,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)
{ {
@@ -444,6 +524,8 @@ const char *meter_type_to_str(meter_type_t type)
return "TRIF-ZIGBEE"; return "TRIF-ZIGBEE";
case METER_TYPE_EA777: case METER_TYPE_EA777:
return "EA-777"; return "EA-777";
case METER_TYPE_DTS024M:
return "DTS-024M";
default: default:
return "NENHUM"; return "NENHUM";
} }
@@ -453,6 +535,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 +554,8 @@ 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;
if (strcmp(str, "DTS-024M") == 0)
return METER_TYPE_DTS024M;
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

@@ -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));
return ESP_OK;
if (err != ESP_OK)
return err;
if (need == 0) esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
return ESP_OK; // vazio if (e == ESP_ERR_NOT_FOUND)
if (need > out_sz)
{ {
// Truncar de forma segura out[0] = '\0';
char *tmp = (char *)malloc(need); return ESP_OK;
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);
return err;
} }
return nvs_get_str(h, key, out, &need); if (e != ESP_OK)
{
out[0] = '\0';
return e;
}
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)
@@ -128,7 +178,6 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
case WIFI_EVENT_STA_CONNECTED: case WIFI_EVENT_STA_CONNECTED:
{ {
ESP_LOGI(TAG, "STA associated (L2 connected)"); ESP_LOGI(TAG, "STA associated (L2 connected)");
// dispara evento genérico de STA_CONNECTED
esp_event_post(NETWORK_EVENTS, esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_CONNECTED, NETWORK_EVENT_STA_CONNECTED,
NULL, NULL,
@@ -144,14 +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);
// dispara evento genérico de “STA desconectou”
esp_event_post(NETWORK_EVENTS, esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_DISCONNECTED, NETWORK_EVENT_STA_DISCONNECTED,
ev, ev,
ev ? sizeof(*ev) : 0, ev ? sizeof(*ev) : 0,
portMAX_DELAY); portMAX_DELAY);
// NÃO bloquear o event loop com vTaskDelay
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();
@@ -162,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
@@ -185,22 +232,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0; s_retry_count = 0;
// Dispara evento “STA GOT IP”
esp_event_post(NETWORK_EVENTS, esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_GOT_IP, NETWORK_EVENT_STA_GOT_IP,
&event->ip_info, &event->ip_info,
sizeof(event->ip_info), sizeof(event->ip_info),
portMAX_DELAY); portMAX_DELAY);
} }
else if (event_id == IP_EVENT_GOT_IP6)
{
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
ESP_LOGI(TAG, "WiFi STA got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip));
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0;
// se quiseres, podes também propagar um NETWORK_EVENT_STA_GOT_IP aqui
}
else if (event_id == IP_EVENT_STA_LOST_IP) else if (event_id == IP_EVENT_STA_LOST_IP)
{ {
ESP_LOGW(TAG, "STA lost IP"); ESP_LOGW(TAG, "STA lost IP");
@@ -227,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)
@@ -257,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));
} }
@@ -283,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);
} }
@@ -296,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();
@@ -310,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));
@@ -335,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");
@@ -346,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);
@@ -366,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");
@@ -380,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();
@@ -402,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;
} }
@@ -424,7 +465,6 @@ 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, sizeof(scan_aps[i].ssid), "%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;
@@ -436,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)
@@ -474,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);
@@ -499,4 +539,4 @@ bool wifi_is_ap(void)
EventBits_t bits = xEventGroupGetBits(wifi_event_group); EventBits_t bits = xEventGroupGetBits(wifi_event_group);
return (bits & WIFI_AP_MODE_BIT) != 0; return (bits & WIFI_AP_MODE_BIT) != 0;
} }

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);
@@ -786,21 +769,24 @@ void ocpp_start(void)
return; return;
} }
//chargePointModel: "EPower M1"
//chargePointVendor: "Plixin"
//firmwareVersion: "FW-PLXV1.0"
//chargePointSerialNumber: "SN001"
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 +796,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 +806,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 +813,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

@@ -6,13 +6,11 @@ set(srcs
"src/ac_relay.c" "src/ac_relay.c"
"src/socket_lock.c" "src/socket_lock.c"
"src/rcm.c" "src/rcm.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,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,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,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"
#define PIN_NUM_CS 5 // Pino de chip-select do ADC121S021 (ajusta se necessário)
#define SAMPLE_SIZE_BYTES 2 #define PIN_NUM_CS 5
#define ADC_BITS 12
#define SPI_CLOCK_HZ (6 * 1000 * 1000) // 6 MHz // ADC é 12-bit, mas transferimos 16 bits via SPI
#define ADC_BITS 12
// 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,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,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,7 +1,5 @@
#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"
@@ -11,13 +9,9 @@
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))
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); //delay before first lock attempt {
if (notification & LOCK_BIT)
{
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,34 +198,56 @@ 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));
gpio_config_t io_conf = {}; 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));
io_conf.mode = GPIO_MODE_OUTPUT; e = storage_get_u8_sync(NVS_NAMESPACE, NVS_DETECTION_HIGH, &u8, STORE_TO);
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio); if (e == ESP_OK) detection_high = (u8 != 0);
ESP_ERROR_CHECK(gpio_config(&io_conf)); else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_DETECTION_HIGH, esp_err_to_name(e));
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task);
} }
gpio_config_t io_conf = {};
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
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;
} }
@@ -229,4 +345,4 @@ void socket_lock_set_locked(bool locked)
socket_lock_status_t socket_lock_get_status(void) socket_lock_status_t socket_lock_get_status(void)
{ {
return status; return status;
} }

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,24 +1,22 @@
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
spiffs spiffs
REQUIRES REQUIRES
logger
network network
config config
evse evse
peripherals peripherals
meter_manager
ocpp ocpp
auth auth
) )

View File

@@ -1,38 +0,0 @@
#ifndef JSON_H_
#define JSON_H_
#include <stdbool.h>
#include "esp_err.h"
#include "cJSON.h"
/**
* @brief Gera um objeto JSON com a configuração atual do EVSE.
*
* Contém parâmetros como corrente máxima, limites de tempo,
* trava do conector, temperatura e configuração do OCPP.
*
* @return Ponteiro para cJSON (deve ser liberado com cJSON_Delete()).
*/
cJSON* json_get_evse_config(void);
/**
* @brief Define a configuração do EVSE a partir de um objeto JSON.
*
* Aplica valores recebidos de protocolos como MQTT ou REST.
*
* @param root Objeto JSON com os campos válidos.
* @return ESP_OK se todos os parâmetros foram aplicados com sucesso.
*/
esp_err_t json_set_evse_config(cJSON* root);
/**
* @brief Retorna o estado atual do EVSE em formato JSON.
*
* Inclui estado de operação, erros, limites, sessão atual e medições elétricas.
*
* @return Ponteiro para cJSON (deve ser liberado com cJSON_Delete()).
*/
cJSON* json_get_state(void);
#endif /* JSON_H_ */

View File

@@ -1,114 +1,115 @@
#ifndef MQTT_H_ // === Início de: components/mqtt/include/mqtt.h ===
#define MQTT_H_ #pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include "esp_err.h" #include "esp_err.h"
// Tamanhos máximos esperados pelos getters (buffers do chamador)
#define MQTT_SERVER_MAX_LEN 64 ///< host ou URI do broker
#define MQTT_BASE_TOPIC_MAX_LEN 64 ///< tópico base (ex: "evse")
#define MQTT_USERNAME_MAX_LEN 32
#define MQTT_PASSWORD_MAX_LEN 64
/** /**
* @file mqtt.h * @brief Inicializa o módulo MQTT.
* @brief MQTT configuration and control interface.
* *
* This module provides initialization, configuration, * - Carrega configuração da NVS
* and runtime access functions for the MQTT client. * - Regista handlers de eventos (AUTH, SCHED, LOADBALANCER, METER, EVSE, NETWORK)
* - Cria a task de telemetria periódica
* *
* Configuration is persisted in NVS under namespace "mqtt". * Não inicia a ligação ao broker imediatamente; isso acontece quando existir IP
* (NETWORK_EVENT_STA_GOT_IP) e `enabled == true`.
*/ */
esp_err_t mqtt_init(void);
/* -------------------------------------------------------------------------- */
/* Definitions */
/* -------------------------------------------------------------------------- */
#define MQTT_MAX_SERVER_LEN 64 /**< Max length for MQTT server URI */
#define MQTT_MAX_USER_LEN 32 /**< Max length for MQTT username */
#define MQTT_MAX_PASSWORD_LEN 64 /**< Max length for MQTT password */
#define MQTT_MAX_BASE_TOPIC_LEN 64 /**< Max length for MQTT base topic */
/* -------------------------------------------------------------------------- */
/* Public Functions */
/* -------------------------------------------------------------------------- */
/** /**
* @brief Initialize MQTT subsystem. * @brief Força tentativa de ligação ao broker (normalmente não é necessário chamar).
* *
* Loads configuration from NVS and starts background publish task * Útil apenas se quiseres forçar manualmente depois de mudares config.
* if MQTT is enabled. Must be called once during system startup.
*/ */
void mqtt_init(void); esp_err_t mqtt_start(void);
/** /**
* @brief Restart the MQTT client safely. * @brief Pára o cliente MQTT e destrói o handle.
*
* Stops the current MQTT client (if running) and starts a new one
* with the configuration currently stored in NVS.
*
* Useful when changing Wi-Fi networks, credentials, or broker settings.
*
* @return ESP_OK on success, or an ESP_ERR_* code otherwise.
*/ */
esp_err_t mqtt_restart(void); void mqtt_stop(void);
// =============================
// Getters de configuração
// =============================
/** /**
* @brief Set and persist MQTT configuration parameters in NVS. * @brief Indica se o MQTT está ativo (flag em config).
*
* Any NULL parameter will be skipped (the previous value remains stored).
*
* @param enabled Whether MQTT should be enabled (true/false).
* @param server Broker URI (e.g. "mqtt://192.168.1.10").
* @param base_topic Base topic prefix for publish/subscribe.
* @param user MQTT username (optional).
* @param password MQTT password (optional).
* @param periodicity Publish interval (in seconds). Must be >0 when enabled.
*
* @return ESP_OK on success, or an ESP_ERR_* code otherwise.
*/
esp_err_t mqtt_set_config(bool enabled,
const char *server,
const char *base_topic,
const char *user,
const char *password,
uint16_t periodicity);
/**
* @brief Get whether MQTT is enabled.
*
* @return true if enabled, false otherwise.
*/ */
bool mqtt_get_enabled(void); bool mqtt_get_enabled(void);
/** /**
* @brief Get MQTT broker URI stored in NVS. * @brief Obtém o servidor MQTT (host ou URI).
* *
* @param[out] value Buffer to receive the URI (min length: MQTT_MAX_SERVER_LEN). * @param server buffer de saída, com pelo menos MQTT_SERVER_MAX_LEN bytes.
*/ */
void mqtt_get_server(char *value); void mqtt_get_server(char *server);
/** /**
* @brief Get MQTT base topic stored in NVS. * @brief Obtém o tópico base (ex.: "evse").
* *
* @param[out] value Buffer to receive the base topic (min length: MQTT_MAX_BASE_TOPIC_LEN). * @param base_topic buffer de saída, com pelo menos MQTT_BASE_TOPIC_MAX_LEN bytes.
*/ */
void mqtt_get_base_topic(char *value); void mqtt_get_base_topic(char *base_topic);
/** /**
* @brief Get MQTT username stored in NVS. * @brief Obtém o username MQTT (se configurado).
* *
* @param[out] value Buffer to receive the username (min length: MQTT_MAX_USER_LEN). * @param username buffer de saída, com pelo menos MQTT_USERNAME_MAX_LEN bytes.
*/ */
void mqtt_get_user(char *value); void mqtt_get_user(char *username);
/** /**
* @brief Get MQTT password stored in NVS. * @brief Obtém a password MQTT (se configurada).
* *
* @param[out] value Buffer to receive the password (min length: MQTT_MAX_PASSWORD_LEN). * @param password buffer de saída, com pelo menos MQTT_PASSWORD_MAX_LEN bytes.
*/ */
void mqtt_get_password(char *value); void mqtt_get_password(char *password);
/** /**
* @brief Get MQTT publish periodicity in seconds. * @brief Obtém a periodicidade da telemetria periódica (em segundos).
*
* @return Publish interval in seconds (default: 30).
*/ */
uint16_t mqtt_get_periodicity(void); uint16_t mqtt_get_periodicity(void);
#endif /* MQTT_H_ */ // =============================
// Setter de configuração
// =============================
/**
* @brief Atualiza a configuração MQTT (tipicamente chamado pelo endpoint REST).
*
* - Grava em NVS
* - Se já existir cliente ligado, pára e recria com a nova config
* - Se `enabled == true` e houver IP, tenta ligar ao broker
*
* @param enabled true para ativar MQTT
* @param host hostname ou URI (ex: "broker.hivemq.com" ou "mqtt://x.y.z")
* @param topic tópico base (ex: "evse")
* @param username username MQTT (ou NULL para manter/limpar)
* @param password password MQTT (ou NULL para manter/limpar)
* @param periodicity período da task de telemetria em segundos (ex: 30)
*
* @return ESP_OK em caso de sucesso, ou erro de NVS / MQTT.
*/
esp_err_t mqtt_set_config(bool enabled,
const char *host,
const char *topic,
const char *username,
const char *password,
int periodicity);
#ifdef __cplusplus
}
#endif
// === Fim de: components/mqtt/include/mqtt.h ===

View File

@@ -1,204 +0,0 @@
// === Início de: components/protocols/src/json.c ===
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_timer.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "esp_log.h"
#include "json.h"
#include "mqtt.h"
#include "network.h"
#include "evse_error.h"
#include "evse_api.h"
#include "auth.h"
#include "evse_limits.h"
#include "evse_state.h"
#include "evse_config.h"
#include "ocpp.h"
#include "board_config.h"
#include "socket_lock.h"
#include "proximity.h"
#include "temp_sensor.h"
#include "evse_meter.h"
static const char *TAG = "json";
//
// ===== EVSE CONFIG JSON =====
//
cJSON *json_get_evse_config(void)
{
cJSON *root = cJSON_CreateObject();
if (!root)
return NULL;
cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current());
cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current());
cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current());
cJSON_AddBoolToObject(root, "requireAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet());
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
//cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
//cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
//cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
cJSON_AddBoolToObject(root, "socketLockDetectionHigh", socket_lock_is_detection_high());
cJSON_AddNumberToObject(root, "socketLockRetryCount", socket_lock_get_retry_count());
char str[64];
cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled());
ocpp_get_server(str);
cJSON_AddStringToObject(root, "serverocpp", str);
return root;
}
//
// ===== SET EVSE CONFIG (from MQTT or REST) =====
//
esp_err_t json_set_evse_config(cJSON *root)
{
if (!root)
return ESP_ERR_INVALID_ARG;
// Alguns setters retornam esp_err_t, outros void. Para manter compatibilidade,
// chamamos sem propagar erro (se existir, será tratado internamente).
#define SET_NUM(key, fn) \
do \
{ \
const cJSON *item = cJSON_GetObjectItem(root, key); \
if (cJSON_IsNumber(item)) \
{ \
fn(item->valuedouble); \
} \
} while (0)
#define SET_BOOL(key, fn) \
do \
{ \
const cJSON *item = cJSON_GetObjectItem(root, key); \
if (cJSON_IsBool(item)) \
{ \
fn(cJSON_IsTrue(item)); \
} \
} while (0)
SET_NUM("maxChargingCurrent", evse_set_max_charging_current);
SET_NUM("chargingCurrent", evse_set_charging_current);
SET_NUM("defaultChargingCurrent", evse_set_default_charging_current);
SET_BOOL("socketOutlet", evse_set_socket_outlet);
SET_BOOL("rcm", evse_set_rcm);
SET_NUM("temperatureThreshold", evse_set_temp_threshold);
SET_NUM("consumptionLimit", evse_set_consumption_limit);
//SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit);
SET_NUM("chargingTimeLimit", evse_set_charging_time_limit);
//SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit);
SET_NUM("underPowerLimit", evse_set_under_power_limit);
//SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit);
SET_NUM("socketLockOperatingTime", socket_lock_set_operating_time);
SET_NUM("socketLockBreakTime", socket_lock_set_break_time);
const cJSON *retry = cJSON_GetObjectItem(root, "socketLockRetryCount");
if (cJSON_IsNumber(retry))
socket_lock_set_retry_count(retry->valueint);
const cJSON *detect = cJSON_GetObjectItem(root, "socketLockDetectionHigh");
if (cJSON_IsBool(detect))
socket_lock_set_detection_high(cJSON_IsTrue(detect));
const cJSON *ocpp_enabled = cJSON_GetObjectItem(root, "enabledocpp");
if (cJSON_IsBool(ocpp_enabled))
ocpp_set_enabled(cJSON_IsTrue(ocpp_enabled));
const cJSON *ocpp_server = cJSON_GetObjectItem(root, "serverocpp");
if (cJSON_IsString(ocpp_server))
ocpp_set_server(cJSON_GetStringValue(ocpp_server));
ESP_LOGI(TAG, "EVSE configuration updated successfully");
return ESP_OK;
}
//
// ===== EVSE STATE JSON =====
//
cJSON *json_get_state(void)
{
cJSON *root = cJSON_CreateObject();
if (!root)
return NULL;
cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
cJSON_AddBoolToObject(root, "available", evse_config_is_available());
cJSON_AddBoolToObject(root, "enabled", evse_config_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());
// Add error list
uint32_t error = evse_error_get_bits();
if (error == 0)
{
cJSON_AddNullToObject(root, "errors");
}
else
{
cJSON *errors = cJSON_CreateArray();
if (error & EVSE_ERR_PILOT_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("pilot_fault"));
if (error & EVSE_ERR_DIODE_SHORT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("diode_short"));
if (error & EVSE_ERR_LOCK_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("lock_fault"));
if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("unlock_fault"));
if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_triggered"));
if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_selftest_fault"));
if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_high"));
if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_fault"));
cJSON_AddItemToObject(root, "errors", errors);
}
// Session info
evse_session_t sess;
if (evse_get_session(&sess))
{
cJSON_AddNumberToObject(root, "sessionTime", (double)sess.start_tick);
cJSON_AddNumberToObject(root, "chargingTime", (double)sess.duration_s);
cJSON_AddNumberToObject(root, "consumption", (double)sess.energy_wh);
}
else
{
cJSON_AddNullToObject(root, "sessionTime");
cJSON_AddNumberToObject(root, "chargingTime", 0);
cJSON_AddNumberToObject(root, "consumption", 0);
}
// Meter readings
float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT];
int power[EVSE_METER_PHASE_COUNT];
evse_meter_get_voltage(voltage);
evse_meter_get_current(current);
evse_meter_get_power(power);
cJSON_AddItemToObject(root, "power", cJSON_CreateIntArray(power, EVSE_METER_PHASE_COUNT));
cJSON_AddItemToObject(root, "voltage", cJSON_CreateFloatArray(voltage, EVSE_METER_PHASE_COUNT));
cJSON_AddItemToObject(root, "current", cJSON_CreateFloatArray(current, EVSE_METER_PHASE_COUNT));
return root;
}
// === Fim de: components/protocols/src/json.c ===

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ static esp_err_t dashboard_get_handler(httpd_req_t *req) {
cJSON_AddNumberToObject(charger1, "id", 1); cJSON_AddNumberToObject(charger1, "id", 1);
cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state)); cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state));
cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current()); cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current());
cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current()); cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_charging_current());
// Calcular a potência com base na corrente (considerando 230V) // Calcular a potência com base na corrente (considerando 230V)
int power = (evse_get_runtime_charging_current()) * 230; int power = (evse_get_runtime_charging_current()) * 230;

View File

@@ -11,7 +11,7 @@ static esp_err_t link_config_get_handler(httpd_req_t *req) {
uint8_t mode = evse_link_get_mode(); // 0=MASTER,1=SLAVE uint8_t mode = evse_link_get_mode(); // 0=MASTER,1=SLAVE
uint8_t self_id = evse_link_get_self_id(); uint8_t self_id = evse_link_get_self_id();
ESP_LOGI(TAG, "GET link config: enabled=%d mode=%u id=%u", ESP_LOGD(TAG, "GET link config: enabled=%d mode=%u id=%u",
enabled, mode, self_id); enabled, mode, self_id);
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
@@ -23,7 +23,7 @@ static esp_err_t link_config_get_handler(httpd_req_t *req) {
char *s = cJSON_Print(root); char *s = cJSON_Print(root);
httpd_resp_sendstr(req, s); httpd_resp_sendstr(req, s);
ESP_LOGI(TAG, " payload: %s", s); ESP_LOGD(TAG, " payload: %s", s);
free(s); free(s);
cJSON_Delete(root); cJSON_Delete(root);
return ESP_OK; return ESP_OK;
@@ -38,7 +38,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; buf[len] = '\0';
ESP_LOGI(TAG, "POST link config: %s", buf); ESP_LOGD(TAG, "POST link config: %s", buf);
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) { if (!json) {
@@ -50,7 +50,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
cJSON *j_en = cJSON_GetObjectItem(json, "linkEnabled"); cJSON *j_en = cJSON_GetObjectItem(json, "linkEnabled");
if (j_en && cJSON_IsBool(j_en)) { if (j_en && cJSON_IsBool(j_en)) {
evse_link_set_enabled(cJSON_IsTrue(j_en)); evse_link_set_enabled(cJSON_IsTrue(j_en));
ESP_LOGI(TAG, " set enabled = %d", cJSON_IsTrue(j_en)); ESP_LOGD(TAG, " set enabled = %d", cJSON_IsTrue(j_en));
} }
// linkMode // linkMode
@@ -67,7 +67,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
"Invalid linkMode (must be MASTER or SLAVE)"); "Invalid linkMode (must be MASTER or SLAVE)");
return ESP_FAIL; return ESP_FAIL;
} }
ESP_LOGI(TAG, " set mode = %s", m); ESP_LOGD(TAG, " set mode = %s", m);
} }
// linkSelfId // linkSelfId
@@ -81,7 +81,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
return ESP_FAIL; return ESP_FAIL;
} }
evse_link_set_self_id((uint8_t)id); evse_link_set_self_id((uint8_t)id);
ESP_LOGI(TAG, " set self_id = %d", id); ESP_LOGD(TAG, " set self_id = %d", id);
} }
cJSON_Delete(json); cJSON_Delete(json);

View File

@@ -12,7 +12,7 @@ static const char *TAG = "evse_settings_api";
static esp_err_t config_settings_get_handler(httpd_req_t *req) { static esp_err_t config_settings_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject(); cJSON *config = cJSON_CreateObject();
cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current()); cJSON_AddNumberToObject(config, "currentLimit", evse_get_charging_current());
cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold());
const char *json_str = cJSON_Print(config); const char *json_str = cJSON_Print(config);
httpd_resp_sendstr(req, json_str); httpd_resp_sendstr(req, json_str);
@@ -36,7 +36,7 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) {
} }
cJSON *current = cJSON_GetObjectItem(json, "currentLimit"); cJSON *current = cJSON_GetObjectItem(json, "currentLimit");
if (current) evse_set_max_charging_current(current->valueint); if (current) evse_set_charging_current(current->valueint);
cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit"); cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit");
if (temp) evse_set_temp_threshold(temp->valueint); if (temp) evse_set_temp_threshold(temp->valueint);

View File

@@ -1,108 +1,216 @@
#include "loadbalancing_settings_api.h" #include "loadbalancing_settings_api.h"
#include "loadbalancer.h" #include "loadbalancer.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "esp_http_server.h"
#include "cJSON.h" #include "cJSON.h"
#include <stdlib.h>
#include <string.h>
static const char *TAG = "loadbalancing_settings_api"; static const char *TAG = "loadbalancing_settings_api";
// GET Handler: Retorna configurações atuais de load balancing // limites simples
static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) { #define MIN_GRID_A 6
bool enabled = loadbalancer_is_enabled(); #define MAX_GRID_A 100
uint8_t currentLimit = load_balancing_get_max_grid_current();
ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit); #define MIN_PV_W 0
#define MAX_PV_W 100000 // ajusta se quiseres
static esp_err_t send_json(httpd_req_t *req, cJSON *root)
{
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject(); char *json_str = cJSON_PrintUnformatted(root);
cJSON_AddBoolToObject(config, "loadBalancingEnabled", enabled); if (!json_str)
cJSON_AddNumberToObject(config, "loadBalancingCurrentLimit", currentLimit); {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON encode failed");
return ESP_FAIL;
}
const char *json_str = cJSON_Print(config); esp_err_t err = httpd_resp_sendstr(req, json_str);
httpd_resp_sendstr(req, json_str); free(json_str);
return err;
}
ESP_LOGI(TAG, "Returned config: %s", json_str); // GET -> payload novo
static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req)
{
cJSON *root = cJSON_CreateObject();
if (!root)
{
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "No mem");
return ESP_FAIL;
}
free((void *)json_str); cJSON_AddBoolToObject(root, "enabled", loadbalancer_is_enabled());
cJSON_Delete(config);
cJSON *grid = cJSON_CreateObject();
cJSON_AddItemToObject(root, "gridLimit", grid);
cJSON_AddBoolToObject(grid, "enabled", loadbalancer_grid_is_enabled());
cJSON_AddNumberToObject(grid, "maxImportA", loadbalancer_grid_get_max_import_a());
cJSON *pv = cJSON_CreateObject();
cJSON_AddItemToObject(root, "pv", pv);
cJSON_AddBoolToObject(pv, "enabled", loadbalancer_pv_is_enabled());
cJSON_AddNumberToObject(pv, "maxImportW", loadbalancer_pv_get_max_import_w());
esp_err_t err = send_json(req, root);
cJSON_Delete(root);
return err;
}
// lê body de forma robusta (httpd_req_recv pode devolver parcial)
static esp_err_t read_body(httpd_req_t *req, char *buf, size_t buf_sz)
{
if (req->content_len <= 0)
return ESP_FAIL;
if ((size_t)req->content_len >= buf_sz)
return ESP_ERR_NO_MEM;
int remaining = req->content_len;
int off = 0;
while (remaining > 0)
{
int r = httpd_req_recv(req, buf + off, remaining);
if (r <= 0)
return ESP_FAIL;
off += r;
remaining -= r;
}
buf[off] = '\0';
return ESP_OK; return ESP_OK;
} }
// POST Handler: Atualiza configurações de load balancing // POST -> updates parciais aceites
static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) { static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req)
char buf[512]; {
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); if (req->content_len <= 0)
{
if (len <= 0) {
ESP_LOGE(TAG, "Received empty POST body");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; if (req->content_len >= 512)
ESP_LOGI(TAG, "Received POST data: %s", buf); {
httpd_resp_send_err(req, HTTPD_413_CONTENT_TOO_LARGE, "Body too large");
return ESP_FAIL;
}
char buf[512];
esp_err_t rb = read_body(req, buf, sizeof(buf));
if (rb == ESP_ERR_NO_MEM)
{
httpd_resp_send_err(req, HTTPD_413_CONTENT_TOO_LARGE, "Body too large");
return ESP_FAIL;
}
if (rb != ESP_OK)
{
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read body");
return ESP_FAIL;
}
ESP_LOGD(TAG, "POST: %s", buf);
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) { if (!json)
ESP_LOGE(TAG, "Invalid JSON"); {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL; return ESP_FAIL;
} }
// Atualizar estado habilitado // enabled (top-level)
cJSON *enabled_item = cJSON_GetObjectItem(json, "loadBalancingEnabled"); cJSON *enabled_item = cJSON_GetObjectItem(json, "enabled");
if (enabled_item && cJSON_IsBool(enabled_item)) { if (enabled_item && cJSON_IsBool(enabled_item))
bool isEnabled = cJSON_IsTrue(enabled_item); {
loadbalancer_set_enabled(isEnabled); loadbalancer_set_enabled(cJSON_IsTrue(enabled_item));
ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled);
} }
// Atualizar limite de corrente // gridLimit
cJSON *limit_item = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); cJSON *grid = cJSON_GetObjectItem(json, "gridLimit");
if (limit_item && cJSON_IsNumber(limit_item)) { if (grid && cJSON_IsObject(grid))
uint8_t currentLimit = (uint8_t)limit_item->valuedouble; {
cJSON *g_en = cJSON_GetObjectItem(grid, "enabled");
// Validar intervalo if (g_en && cJSON_IsBool(g_en))
if (currentLimit < 6 || currentLimit > 100) { {
ESP_LOGW(TAG, "Rejected invalid currentLimit: %d", currentLimit); loadbalancer_grid_set_enabled(cJSON_IsTrue(g_en));
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid currentLimit (must be 6-100)");
return ESP_FAIL;
} }
esp_err_t err = load_balancing_set_max_grid_current(currentLimit); cJSON *g_maxA = cJSON_GetObjectItem(grid, "maxImportA");
if (err != ESP_OK) { if (g_maxA && cJSON_IsNumber(g_maxA))
ESP_LOGE(TAG, "Failed to save currentLimit: %s", esp_err_to_name(err)); {
cJSON_Delete(json); int maxA = (int)g_maxA->valuedouble;
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save setting");
return ESP_FAIL; if (maxA < MIN_GRID_A || maxA > MAX_GRID_A)
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "gridLimit.maxImportA must be 6-100");
return ESP_FAIL;
}
esp_err_t e = loadbalancer_grid_set_max_import_a((uint8_t)maxA);
if (e != ESP_OK)
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to set gridLimit.maxImportA");
return ESP_FAIL;
}
}
}
// pv
cJSON *pv = cJSON_GetObjectItem(json, "pv");
if (pv && cJSON_IsObject(pv))
{
cJSON *p_en = cJSON_GetObjectItem(pv, "enabled");
if (p_en && cJSON_IsBool(p_en))
{
loadbalancer_pv_set_enabled(cJSON_IsTrue(p_en));
} }
ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit); cJSON *p_maxW = cJSON_GetObjectItem(pv, "maxImportW");
if (p_maxW && cJSON_IsNumber(p_maxW))
{
int32_t maxW = (int32_t)p_maxW->valuedouble;
if (maxW < MIN_PV_W || maxW > MAX_PV_W)
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "pv.maxImportW out of range");
return ESP_FAIL;
}
esp_err_t e = loadbalancer_pv_set_max_import_w(maxW);
if (e != ESP_OK)
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to set pv.maxImportW");
return ESP_FAIL;
}
}
} }
cJSON_Delete(json); cJSON_Delete(json);
httpd_resp_sendstr(req, "Load balancing settings updated successfully"); httpd_resp_sendstr(req, "OK");
return ESP_OK; return ESP_OK;
} }
// Registro dos handlers na API HTTP void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx)
void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx) { {
// GET
httpd_uri_t get_uri = { httpd_uri_t get_uri = {
.uri = "/api/v1/config/loadbalancing", .uri = "/api/v1/config/loadbalancing",
.method = HTTP_GET, .method = HTTP_GET,
.handler = loadbalancing_config_get_handler, .handler = loadbalancing_config_get_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &get_uri); httpd_register_uri_handler(server, &get_uri);
// POST
httpd_uri_t post_uri = { httpd_uri_t post_uri = {
.uri = "/api/v1/config/loadbalancing", .uri = "/api/v1/config/loadbalancing",
.method = HTTP_POST, .method = HTTP_POST,
.handler = loadbalancing_config_post_handler, .handler = loadbalancing_config_post_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &post_uri); httpd_register_uri_handler(server, &post_uri);
} }

View File

@@ -158,13 +158,13 @@ void register_meters_data_handlers(httpd_handle_t server, void *ctx)
.user_ctx = ctx}; .user_ctx = ctx};
httpd_register_uri_handler(server, &live); httpd_register_uri_handler(server, &live);
ESP_LOGI(TAG, "REST /api/v1/meters/live registrado"); ESP_LOGD(TAG, "REST /api/v1/meters/live registrado");
} }
// Função para recuperar as configurações dos contadores // Função para recuperar as configurações dos contadores
static esp_err_t meters_config_get_handler(httpd_req_t *req) static esp_err_t meters_config_get_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters"); ESP_LOGD(TAG, "Received GET request for /api/v1/config/meters");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
@@ -174,8 +174,8 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
meter_type_t gridmeterType = meter_manager_grid_get_model(); meter_type_t gridmeterType = meter_manager_grid_get_model();
meter_type_t evsemeterType = meter_manager_evse_get_model(); meter_type_t evsemeterType = meter_manager_evse_get_model();
ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType)); ESP_LOGD(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType));
ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType)); ESP_LOGD(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType));
// Adicionando os tipos de contadores ao objeto JSON // Adicionando os tipos de contadores ao objeto JSON
cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType)); cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType));
@@ -183,7 +183,7 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
// Convertendo o objeto JSON para uma string // Convertendo o objeto JSON para uma string
const char *json_str = cJSON_Print(config); const char *json_str = cJSON_Print(config);
ESP_LOGI(TAG, "Returning meters config: %s", json_str); ESP_LOGD(TAG, "Returning meters config: %s", json_str);
httpd_resp_sendstr(req, json_str); httpd_resp_sendstr(req, json_str);
@@ -197,7 +197,7 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
// Função para atualizar as configurações dos contadores // Função para atualizar as configurações dos contadores
static esp_err_t meters_config_post_handler(httpd_req_t *req) static esp_err_t meters_config_post_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters"); ESP_LOGD(TAG, "Received POST request for /api/v1/config/meters");
char buf[512]; char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
@@ -211,7 +211,7 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
buf[len] = '\0'; // Garantir que a string está terminada buf[len] = '\0'; // Garantir que a string está terminada
ESP_LOGI(TAG, "Received POST data: %s", buf); ESP_LOGD(TAG, "Received POST data: %s", buf);
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) if (!json)
@@ -227,7 +227,7 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
if (gridmeter) if (gridmeter)
{ {
meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring); ESP_LOGD(TAG, "Updating grid meter type to: %s", gridmeter->valuestring);
meter_manager_grid_set_model(gridType); meter_manager_grid_set_model(gridType);
} }
@@ -235,14 +235,14 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
if (evsemeter) if (evsemeter)
{ {
meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring); ESP_LOGD(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring);
meter_manager_evse_set_model(evseType); meter_manager_evse_set_model(evseType);
} }
cJSON_Delete(json); cJSON_Delete(json);
httpd_resp_sendstr(req, "Meters updated successfully"); httpd_resp_sendstr(req, "Meters updated successfully");
ESP_LOGI(TAG, "Meters configuration updated successfully"); ESP_LOGD(TAG, "Meters configuration updated successfully");
return ESP_OK; return ESP_OK;
} }

View File

@@ -8,29 +8,33 @@
#include "network.h" #include "network.h"
#include "mqtt.h" #include "mqtt.h"
#include <string.h>
#include <stdlib.h>
static const char *TAG = "network_api"; static const char *TAG = "network_api";
typedef struct { typedef struct
{
bool enabled; bool enabled;
char ssid[33]; char ssid[33];
char password[65]; char password[65];
} wifi_task_data_t; } wifi_task_data_t;
static void wifi_apply_config_task(void *param)
static void wifi_apply_config_task(void *param) { {
wifi_task_data_t *data = (wifi_task_data_t *)param; wifi_task_data_t *data = (wifi_task_data_t *)param;
ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task"); ESP_LOGD("wifi_task", "Applying Wi-Fi config in background task");
wifi_set_config(data->enabled, data->ssid, data->password); wifi_set_config(data->enabled, data->ssid, data->password);
free(data); free(data);
vTaskDelete(NULL); vTaskDelete(NULL);
} }
static esp_err_t wifi_get_handler(httpd_req_t *req) { static esp_err_t wifi_get_handler(httpd_req_t *req)
ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi"); {
ESP_LOGD(TAG, "Handling GET /api/v1/config/wifi");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
// Obter dados da NVS via wifi.c
bool enabled = wifi_get_enabled(); bool enabled = wifi_get_enabled();
char ssid[33] = {0}; char ssid[33] = {0};
char password[65] = {0}; char password[65] = {0};
@@ -38,81 +42,85 @@ static esp_err_t wifi_get_handler(httpd_req_t *req) {
wifi_get_ssid(ssid); wifi_get_ssid(ssid);
wifi_get_password(password); wifi_get_password(password);
// Criar JSON
cJSON *json = cJSON_CreateObject(); cJSON *json = cJSON_CreateObject();
cJSON_AddBoolToObject(json, "enabled", enabled); cJSON_AddBoolToObject(json, "enabled", enabled);
cJSON_AddStringToObject(json, "ssid", ssid); cJSON_AddStringToObject(json, "ssid", ssid);
cJSON_AddStringToObject(json, "password", password); cJSON_AddStringToObject(json, "password", password);
// Enviar resposta
char *response = cJSON_Print(json); char *response = cJSON_Print(json);
httpd_resp_sendstr(req, response); httpd_resp_sendstr(req, response);
// Limpeza
free(response); free(response);
cJSON_Delete(json); cJSON_Delete(json);
return ESP_OK; return ESP_OK;
} }
static esp_err_t wifi_post_handler(httpd_req_t *req) { static esp_err_t wifi_post_handler(httpd_req_t *req)
ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi"); {
ESP_LOGD(TAG, "Handling POST /api/v1/config/wifi");
char buf[512]; char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) return ESP_FAIL; if (len <= 0)
return ESP_FAIL;
buf[len] = '\0'; buf[len] = '\0';
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) return ESP_FAIL; if (!json)
return ESP_FAIL;
// Valores padrão
bool enabled = false; bool enabled = false;
const char *ssid = NULL; const char *ssid = NULL;
const char *password = NULL; const char *password = NULL;
cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(j_enabled)) enabled = j_enabled->valueint; if (cJSON_IsBool(j_enabled))
enabled = j_enabled->valueint;
cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid"); cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid");
if (cJSON_IsString(j_ssid)) ssid = j_ssid->valuestring; if (cJSON_IsString(j_ssid))
ssid = j_ssid->valuestring;
cJSON *j_password = cJSON_GetObjectItem(json, "password"); cJSON *j_password = cJSON_GetObjectItem(json, "password");
if (cJSON_IsString(j_password)) password = j_password->valuestring; if (cJSON_IsString(j_password))
password = j_password->valuestring;
// Enviar resposta antes de alterar Wi-Fi // Resposta imediata
httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso"); httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso");
// Alocar struct para passar para a task wifi_task_data_t *task_data = (wifi_task_data_t *)malloc(sizeof(wifi_task_data_t));
wifi_task_data_t *task_data = malloc(sizeof(wifi_task_data_t)); if (!task_data)
if (!task_data) { {
cJSON_Delete(json); cJSON_Delete(json);
ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task"); ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task");
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
} }
task_data->enabled = enabled; task_data->enabled = enabled;
strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid));
strncpy(task_data->password, password ? password : "", sizeof(task_data->password));
// Criar task normal com função C // Copias seguras (garante null-termination)
strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid) - 1);
task_data->ssid[sizeof(task_data->ssid) - 1] = '\0';
strncpy(task_data->password, password ? password : "", sizeof(task_data->password) - 1);
task_data->password[sizeof(task_data->password) - 1] = '\0';
xTaskCreate( xTaskCreate(
wifi_apply_config_task, wifi_apply_config_task,
"wifi_config_task", "wifi_config_task",
4096, 4096,
task_data, task_data,
3, 3,
NULL NULL);
);
cJSON_Delete(json); cJSON_Delete(json);
return ESP_OK; return ESP_OK;
} }
static esp_err_t config_mqtt_get_handler(httpd_req_t *req) static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt"); ESP_LOGD(TAG, "Handling GET /api/v1/config/mqtt");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
@@ -128,85 +136,128 @@ static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
mqtt_get_user(username); mqtt_get_user(username);
mqtt_get_password(password); mqtt_get_password(password);
ESP_LOGI(TAG, "MQTT Config:"); ESP_LOGD(TAG, "MQTT Config:");
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); ESP_LOGD(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGI(TAG, " Server: %s", server); ESP_LOGD(TAG, " Server: %s", server);
ESP_LOGI(TAG, " Topic: %s", base_topic); ESP_LOGD(TAG, " Topic: %s", base_topic);
ESP_LOGI(TAG, " Username: %s", username); ESP_LOGD(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password); ESP_LOGD(TAG, " Password: %s", password);
ESP_LOGI(TAG, " Periodicity: %d", periodicity); ESP_LOGD(TAG, " Periodicity: %d", periodicity);
cJSON *config = cJSON_CreateObject(); cJSON *config = cJSON_CreateObject();
cJSON_AddBoolToObject(config, "enabled", enabled); cJSON_AddBoolToObject(config, "enabled", enabled);
cJSON_AddStringToObject(config, "host", server); cJSON_AddStringToObject(config, "host", server);
cJSON_AddNumberToObject(config, "port", 1883); cJSON_AddNumberToObject(config, "port", 1883); // fixo (se não usas no mqtt_set_config)
cJSON_AddStringToObject(config, "username", username); cJSON_AddStringToObject(config, "username", username);
cJSON_AddStringToObject(config, "password", password); cJSON_AddStringToObject(config, "password", password);
cJSON_AddStringToObject(config, "topic", base_topic); cJSON_AddStringToObject(config, "topic", base_topic);
cJSON_AddNumberToObject(config, "periodicity", periodicity); cJSON_AddNumberToObject(config, "periodicity", periodicity);
const char *config_str = cJSON_Print(config); char *config_str = cJSON_Print(config);
httpd_resp_sendstr(req, config_str); httpd_resp_sendstr(req, config_str);
free((void *)config_str); free(config_str);
cJSON_Delete(config); cJSON_Delete(config);
return ESP_OK; return ESP_OK;
} }
static esp_err_t config_mqtt_post_handler(httpd_req_t *req) static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt"); ESP_LOGD(TAG, "Handling POST /api/v1/config/mqtt");
char buf[512]; char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) { if (len <= 0)
{
ESP_LOGE(TAG, "Failed to read request body"); ESP_LOGE(TAG, "Failed to read request body");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body");
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; buf[len] = '\0';
ESP_LOGI(TAG, "Received JSON: %s", buf); ESP_LOGD(TAG, "Received JSON: %s", buf);
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) { if (!json)
{
ESP_LOGE(TAG, "Invalid JSON format"); ESP_LOGE(TAG, "Invalid JSON format");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL; return ESP_FAIL;
} }
bool enabled = false; // --- Ler config atual (para permitir "partial update" e evitar strings vazias)
const char *host = NULL, *topic = NULL, *username = NULL, *password = NULL; bool current_enabled = mqtt_get_enabled();
int periodicity = 30; char current_host[64] = {0};
char current_topic[32] = {0};
char current_user[32] = {0};
char current_pass[64] = {0};
uint16_t current_periodicity = mqtt_get_periodicity();
if (cJSON_IsBool(cJSON_GetObjectItem(json, "enabled"))) mqtt_get_server(current_host);
enabled = cJSON_GetObjectItem(json, "enabled")->valueint; mqtt_get_base_topic(current_topic);
mqtt_get_user(current_user);
mqtt_get_password(current_pass);
bool enabled = current_enabled;
const char *host_in = NULL, *topic_in = NULL, *user_in = NULL, *pass_in = NULL;
int periodicity = (int)current_periodicity;
cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(j_enabled))
enabled = j_enabled->valueint;
cJSON *j_host = cJSON_GetObjectItem(json, "host"); cJSON *j_host = cJSON_GetObjectItem(json, "host");
if (cJSON_IsString(j_host)) host = j_host->valuestring; if (cJSON_IsString(j_host))
host_in = j_host->valuestring;
cJSON *j_topic = cJSON_GetObjectItem(json, "topic"); cJSON *j_topic = cJSON_GetObjectItem(json, "topic");
if (cJSON_IsString(j_topic)) topic = j_topic->valuestring; if (cJSON_IsString(j_topic))
topic_in = j_topic->valuestring;
cJSON *j_user = cJSON_GetObjectItem(json, "username"); cJSON *j_user = cJSON_GetObjectItem(json, "username");
if (cJSON_IsString(j_user)) username = j_user->valuestring; if (cJSON_IsString(j_user))
user_in = j_user->valuestring;
cJSON *j_pass = cJSON_GetObjectItem(json, "password"); cJSON *j_pass = cJSON_GetObjectItem(json, "password");
if (cJSON_IsString(j_pass)) password = j_pass->valuestring; if (cJSON_IsString(j_pass))
pass_in = j_pass->valuestring;
cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity"); cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity");
if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint; if (cJSON_IsNumber(j_periodicity))
periodicity = j_periodicity->valueint;
ESP_LOGI(TAG, "Applying MQTT config:"); // --- Regras: se vier NULL ou "" mantém o atual; se atual também estiver vazio, usa default
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); const char *host =
ESP_LOGI(TAG, " Host: %s", host); (host_in && host_in[0] != '\0') ? host_in : (current_host[0] != '\0') ? current_host
ESP_LOGI(TAG, " Topic: %s", topic); : "mqtt.plixin.com";
ESP_LOGI(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password); const char *topic =
ESP_LOGI(TAG, " Periodicity: %d", periodicity); (topic_in && topic_in[0] != '\0') ? topic_in : (current_topic[0] != '\0') ? current_topic
: "";
const char *username =
(user_in && user_in[0] != '\0') ? user_in : (current_user[0] != '\0') ? current_user
: "";
const char *password =
(pass_in && pass_in[0] != '\0') ? pass_in : (current_pass[0] != '\0') ? current_pass
: "";
if (periodicity <= 0)
periodicity = (int)current_periodicity;
if (periodicity <= 0)
periodicity = 30;
ESP_LOGD(TAG, "Applying MQTT config:");
ESP_LOGD(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGD(TAG, " Host: %s", host);
ESP_LOGD(TAG, " Topic: %s", topic);
ESP_LOGD(TAG, " Username: %s", username);
ESP_LOGD(TAG, " Password: %s", password);
ESP_LOGD(TAG, " Periodicity: %d", periodicity);
esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity); esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity);
if (err != ESP_OK) { if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err); ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config"); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config");
cJSON_Delete(json); cJSON_Delete(json);
@@ -218,40 +269,33 @@ static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
return ESP_OK; return ESP_OK;
} }
void register_network_handlers(httpd_handle_t server, void *ctx)
{
void register_network_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t wifi_get = { httpd_uri_t wifi_get = {
.uri = "/api/v1/config/wifi", .uri = "/api/v1/config/wifi",
.method = HTTP_GET, .method = HTTP_GET,
.handler = wifi_get_handler, .handler = wifi_get_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &wifi_get); httpd_register_uri_handler(server, &wifi_get);
httpd_uri_t wifi_post = { httpd_uri_t wifi_post = {
.uri = "/api/v1/config/wifi", .uri = "/api/v1/config/wifi",
.method = HTTP_POST, .method = HTTP_POST,
.handler = wifi_post_handler, .handler = wifi_post_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &wifi_post); httpd_register_uri_handler(server, &wifi_post);
// URI handler for getting MQTT config
httpd_uri_t config_mqtt_get_uri = { httpd_uri_t config_mqtt_get_uri = {
.uri = "/api/v1/config/mqtt", .uri = "/api/v1/config/mqtt",
.method = HTTP_GET, .method = HTTP_GET,
.handler = config_mqtt_get_handler, .handler = config_mqtt_get_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &config_mqtt_get_uri); httpd_register_uri_handler(server, &config_mqtt_get_uri);
// URI handler for posting MQTT config
httpd_uri_t config_mqtt_post_uri = { httpd_uri_t config_mqtt_post_uri = {
.uri = "/api/v1/config/mqtt", .uri = "/api/v1/config/mqtt",
.method = HTTP_POST, .method = HTTP_POST,
.handler = config_mqtt_post_handler, .handler = config_mqtt_post_handler,
.user_ctx = ctx .user_ctx = ctx};
};
httpd_register_uri_handler(server, &config_mqtt_post_uri); httpd_register_uri_handler(server, &config_mqtt_post_uri);
} }

View File

@@ -11,6 +11,9 @@
#include "static_file_api.h" #include "static_file_api.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "rest_main"; static const char *TAG = "rest_main";
esp_err_t rest_server_init(const char *base_path) esp_err_t rest_server_init(const char *base_path)
@@ -30,6 +33,13 @@ esp_err_t rest_server_init(const char *base_path)
config.uri_match_fn = httpd_uri_match_wildcard; config.uri_match_fn = httpd_uri_match_wildcard;
config.max_uri_handlers = 32; config.max_uri_handlers = 32;
// Aumenta stack do httpd (handlers + cJSON + etc)
config.stack_size = 8192; // tenta 8192; se necessário 12288
config.task_priority = tskIDLE_PRIORITY + 5;
// Opcional (ajuda com muitas conexões/keep-alive)
config.lru_purge_enable = true;
httpd_handle_t server = NULL; httpd_handle_t server = NULL;
esp_err_t err = httpd_start(&server, &config); esp_err_t err = httpd_start(&server, &config);
if (err != ESP_OK) if (err != ESP_OK)
@@ -41,21 +51,19 @@ esp_err_t rest_server_init(const char *base_path)
ESP_LOGI(TAG, "HTTP server started successfully"); ESP_LOGI(TAG, "HTTP server started successfully");
// Register endpoint groups // Registar handlers
register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação register_evse_settings_handlers(server, ctx);
register_network_handlers(server, ctx); // Apenas chamando a função sem comparação register_network_handlers(server, ctx);
register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação register_ocpp_handlers(server, ctx);
register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação register_auth_handlers(server, ctx);
register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação register_dashboard_handlers(server, ctx);
register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação register_meters_settings_handlers(server, ctx);
register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação register_loadbalancing_settings_handlers(server, ctx);
register_link_config_handlers(server, ctx); register_link_config_handlers(server, ctx);
register_meters_data_handlers(server, ctx); register_meters_data_handlers(server, ctx);
register_scheduler_settings_handlers(server, ctx); register_scheduler_settings_handlers(server, ctx);
register_static_file_handlers(server, ctx);
register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação
ESP_LOGI(TAG, "All REST API endpoint groups registered successfully"); ESP_LOGI(TAG, "All REST API endpoint groups registered successfully");
return ESP_OK; return ESP_OK;
} }

View File

@@ -50,7 +50,7 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz)
* ========================= */ * ========================= */
static esp_err_t scheduler_config_get_handler(httpd_req_t *req) static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "GET /api/v1/config/scheduler"); ESP_LOGD(TAG, "GET /api/v1/config/scheduler");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
@@ -95,7 +95,7 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
* ========================= */ * ========================= */
static esp_err_t scheduler_config_post_handler(httpd_req_t *req) static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
{ {
ESP_LOGI(TAG, "POST /api/v1/config/scheduler"); ESP_LOGD(TAG, "POST /api/v1/config/scheduler");
// NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total, // NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total,
// usa req->content_len e faz um loop com httpd_req_recv. // usa req->content_len e faz um loop com httpd_req_recv.
@@ -108,7 +108,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; buf[len] = '\0';
ESP_LOGI(TAG, "Body: %s", buf); ESP_LOGD(TAG, "Body: %s", buf);
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) if (!json)
@@ -126,7 +126,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
if (cJSON_IsBool(j_enabled)) if (cJSON_IsBool(j_enabled))
{ {
cfg.enabled = cJSON_IsTrue(j_enabled); cfg.enabled = cJSON_IsTrue(j_enabled);
ESP_LOGI(TAG, " enabled = %d", cfg.enabled); ESP_LOGD(TAG, " enabled = %d", cfg.enabled);
} }
// mode // mode
@@ -143,7 +143,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL; return ESP_FAIL;
} }
cfg.mode = m; cfg.mode = m;
ESP_LOGI(TAG, " mode = %s", sched_mode_to_str(cfg.mode)); ESP_LOGD(TAG, " mode = %s", sched_mode_to_str(cfg.mode));
} }
// startTime (string "HH:MM") // startTime (string "HH:MM")
@@ -160,7 +160,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL; return ESP_FAIL;
} }
cfg.start_min = minutes; cfg.start_min = minutes;
ESP_LOGI(TAG, " start_min = %u", (unsigned)cfg.start_min); ESP_LOGD(TAG, " start_min = %u", (unsigned)cfg.start_min);
} }
// endTime (string "HH:MM") // endTime (string "HH:MM")
@@ -177,7 +177,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL; return ESP_FAIL;
} }
cfg.end_min = minutes; cfg.end_min = minutes;
ESP_LOGI(TAG, " end_min = %u", (unsigned)cfg.end_min); ESP_LOGD(TAG, " end_min = %u", (unsigned)cfg.end_min);
} }
// (Opcional) validações extra: // (Opcional) validações extra:
@@ -220,5 +220,5 @@ void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
.user_ctx = ctx}; .user_ctx = ctx};
httpd_register_uri_handler(server, &post_uri); httpd_register_uri_handler(server, &post_uri);
ESP_LOGI(TAG, "Scheduler REST handlers registered"); ESP_LOGD(TAG, "Scheduler REST handlers registered");
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,8 @@
} }
</style> </style>
<title>ChargeFlow</title> <title>ChargeFlow</title>
<script type="module" crossorigin src="/assets/index-19gq1t3T.js"></script> <script type="module" crossorigin src="/assets/index-0q0tbwk5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-SX00HfRO.css"> <link rel="stylesheet" crossorigin href="/assets/index-BIZ-rt0x.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -3,5 +3,5 @@ set(srcs "src/scheduler_types.c" "src/scheduler.c" "src/scheduler_events.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 esp_timer PRIV_REQUIRES esp_timer
REQUIRES esp_event evse) REQUIRES esp_event evse)

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