Compare commits

...

3 Commits

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

209
README.md
View File

@@ -1,85 +1,180 @@
![ESP32 EVSE](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/logo-full.svg)
# ChargeFlow EVSE Firmware (ESP32, ESP-IDF 5.x)
J1772 EVSE firmware for ESP32 based devices.
Firmware for an AC EVSE (EV charger) based on ESP32 and ESP-IDF 5.x, with:
![Build with ESP-IDF](https://github.com/dzurikmiroslav/esp32-evse/workflows/Build%20with%20ESP-IDF/badge.svg)
[![License](https://img.shields.io/github/license/dzurikmiroslav/esp32-evse.svg)](LICENSE.md)
- IEC-style EVSE state machine (Control Pilot A/B/C/D)
- 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.
Source code ist not hardcoded to GPIOs or other hardware design features.
All code is written in ESP-IDF without additional mapping layer like Arduino.
- EVSE manager (`evse_manager`) coordinating:
- Hardware layer (`evse_hardware`)
- 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.
For example, on following scheme is minimal EVSE circuit with ESP32 devkit.
### Networking & REST
![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
#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
```
### Storage
### Web interface
- SPIFFS used for:
- `/cfg` partition: persistent configuration.
- `/data` partition: web assets, runtime data, logs, etc.
- Two separate mounts:
- `cfg_conf``/cfg` (label: `cfg`)
- `data_conf``/data` (label: `data`)
Fully responsive web interface is accessible local network IP address on port 80.
### LED Subsystem
Dashboard page
- RGB LED driven by LEDC:
- `ledc_driver` abstracts LEDC timer + channels.
- `led` module maps EVSE state & sessions to colors/patterns.
- LED patterns per EVSE state:
- **IDLE** → Green solid.
- **WAITING** (vehicle plugged, not charging) → Blue slow blink.
- **CHARGING** → Blue “breathing” effect.
- **FAULT** → Red fast blink.
- Session effects:
- Distinct visual patterns when a session starts/finishes.
- Uses a one-shot timer and a dedicated effect state machine.
![Dashboard](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard.png)
### Buzzer
Settings page
- Buzzer with multiple patterns (`buzzer` + `buzzer_events`):
- Plugged/unplugged, card read/denied, AP start, charging, fault, etc.
- Supported modes:
- Active buzzer (ON/OFF).
- Passive buzzer with LEDC PWM (frequency & duty configurable).
- Features:
- Central queue + dedicated `buzzer_task`.
- Quiet hours support (optionally suppress non-critical sounds at night).
- Anti-spam mechanism to avoid excessively frequent beeps.
- Integrated with:
- EVSE events (state changes & faults)
- Auth events (RFID card success/denied/added)
- Network events (AP/STA up)
![Settings](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-settings.png)
### Load Balancer
Mobile dashboard page
- `loadbalancer` component:
- Monitors GRID meter and EVSE meter via `meter_events`.
- Supports one master + up to 255 slaves (connectors array).
- Fair distribution of current with:
- Headroom calculation based on grid limit and measured current.
- Min current guarantees (e.g. 6 A) using a “water-filling” algorithm.
- Session-age based priority (oldest sessions first).
- Per-connector hysteresis and LB suspension/resume flags.
- Publishes limits via `LOADBALANCER_EVENTS`:
- `LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT`
- `LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT`
- Fail-safe behavior:
- If GRID meter data times out, clamps connectors to minimum safe current instead of ramping up.
![Dashboard mobile](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard-mobile.png)
### Scheduler
## Hardware
- Scheduler component (`scheduler`) emits `SCHED_EVENTS` with `allowed_now` flag:
- EVSE manager revokes authorization when the window closes.
- In OPEN mode, automatic re-authorization only happens when scheduler allows.
### ESP32DevkitC
### OCPP
Dev board with basic functionality, single phase energy meter, RS485. One side pcb, for DIY makers easy to make at home conditions ;-)
- `ocpp` module integration:
- Listens to OCPP events (`OCPP_EVENTS`).
- Handles:
- RemoteStart/Stop
- Authorization results
- ChangeAvailability (operative/inoperative) → mapped into local `enabled` config.
- EVSE manager mediates OCPP decisions with scheduler + load balancer.
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32-devkit-evse)
### Logger
![ESP32DevkitC](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32devkitc.jpg)
- `logger` + `output_buffer` components:
- Central log sink with ring buffer in RAM.
- Thread-safe via FreeRTOS mutex.
- Integrated with ESP log system via `esp_log_set_vprintf(logger_vprintf);`
- Optionally mirrors to UART (controlled via `CONFIG_ESP_CONSOLE_UART`).
- Simple reader API:
- Iterate entries using an index.
- Handy for exposing logs over REST/Web UI.
### ESP32-S2 DIY ALPHA
---
ESP32-S2 based EVSE with advanced functionality, three phase energy meter, RS485, UART, 1WIRE, RCM, socket lock.
## Project Structure (Relevant Parts)
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32s2-diy-evse)
Approximate layout (names may vary slightly in your repo):
![ESP32-S2-DA](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32s2da.jpg)
```text
main/
main.c # System entrypoint, button setup, module init
components/
evse/
evse_manager.c/.h # High-level EVSE orchestration
evse_state.c/.h # State machine & events
evse_error.c/.h # Error handling
evse_hardware.c/.h # Hardware abstraction
evse_session.c/.h # Session metrics
loadbalancer/
src/
loadbalancer.c
loadbalancer_events.c
input_filter.c
include/
loadbalancer.h
loadbalancer_events.h
input_filter.h
buzzer/
src/
buzzer.c
buzzer_events.c
include/
buzzer.h
buzzer_events.h
led/
src/
led.c
ledc_driver.c
include/
led.h
ledc_driver.h
logger/
src/
logger.c
output_buffer.c
include/
logger.h
output_buffer.h
# ... other modules: auth, ocpp, scheduler, meter_manager, evse_link, etc.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,49 +1,125 @@
#include <inttypes.h> // for PRIu32
#include <inttypes.h>
#include <stdbool.h>
#include "evse_state.h"
#include "evse_api.h"
#include "evse_limits.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_check.h"
#include "storage_service.h"
// ========================
// External state references
// ========================
//extern evse_state_t current_state; // Current EVSE FSM state
//extern TickType_t session_start_tick; // Timestamp of charging session start
// ========================
// Concurrency protection
// ========================
#define NVS_NAMESPACE "evse_limits"
static const char *TAG = "evse_limits";
static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
// ========================
// Runtime state (volatile)
// ========================
static bool limit_reached = false;
static uint32_t consumption_limit = 0; // Energy limit in Wh
static uint32_t charging_time_limit = 0; // Time limit in seconds
static uint16_t under_power_limit = 0; // Minimum acceptable power in W
static uint32_t consumption_limit = 0; // Wh
static uint32_t charging_time_limit = 0; // seconds
static uint16_t under_power_limit = 0; // W
// ========================
// Default (persistent) limits
// ========================
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); }
static uint32_t default_consumption_limit = 0;
static uint32_t default_charging_time_limit = 0;
static uint16_t default_under_power_limit = 0;
// ---------------------------------
// 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;
}
// ========================
// Limit status flag
// ========================
void evse_limits_check_defaults(void)
{
esp_err_t err;
bool needs_flush = false;
bool evse_get_limit_reached(void) {
uint32_t u32 = 0;
uint16_t u16 = 0;
ESP_LOGD(TAG, "Checking default limits...");
// Consumption limit (Wh) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
// Charging time limit (s) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
// Under-power limit (W) default = 0 (disabled)
err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = u16;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
if (needs_flush)
{
esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
if (fe != ESP_OK)
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
else
ESP_LOGD(TAG, "Defaults committed (flush).");
}
}
// ---------------------------------
// Limit reached flag
// ---------------------------------
bool evse_get_limit_reached(void)
{
bool val;
portENTER_CRITICAL(&evse_mux);
val = limit_reached;
@@ -51,17 +127,23 @@ bool evse_get_limit_reached(void) {
return val;
}
void evse_set_limit_reached(bool v) {
void evse_set_limit_reached(bool v)
{
portENTER_CRITICAL(&evse_mux);
limit_reached = v;
portEXIT_CRITICAL(&evse_mux);
}
// ========================
// Runtime limit accessors
// ========================
bool evse_is_limit_reached(void)
{
return evse_get_limit_reached();
}
uint32_t evse_get_consumption_limit(void) {
// ---------------------------------
// Consumption limit
// ---------------------------------
uint32_t evse_get_consumption_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = consumption_limit;
@@ -69,13 +151,35 @@ uint32_t evse_get_consumption_limit(void) {
return val;
}
void evse_set_consumption_limit(uint32_t value) {
void evse_set_consumption_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (consumption_limit != value)
{
consumption_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
value, esp_err_to_name(err));
}
}
uint32_t evse_get_charging_time_limit(void) {
// ---------------------------------
// Charging time limit
// ---------------------------------
uint32_t evse_get_charging_time_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = charging_time_limit;
@@ -83,13 +187,35 @@ uint32_t evse_get_charging_time_limit(void) {
return val;
}
void evse_set_charging_time_limit(uint32_t value) {
void evse_set_charging_time_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (charging_time_limit != value)
{
charging_time_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist charging time limit (%" PRIu32 " s): %s",
value, esp_err_to_name(err));
}
}
uint16_t evse_get_under_power_limit(void) {
// ---------------------------------
// Under-power limit
// ---------------------------------
uint16_t evse_get_under_power_limit(void)
{
uint16_t val;
portENTER_CRITICAL(&evse_mux);
val = under_power_limit;
@@ -97,92 +223,79 @@ uint16_t evse_get_under_power_limit(void) {
return val;
}
void evse_set_under_power_limit(uint16_t value) {
void evse_set_under_power_limit(uint16_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (under_power_limit != value)
{
under_power_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
}
// ========================
// Default (persistent) limit accessors
// These values can be stored/restored via NVS
// ========================
uint32_t evse_get_default_consumption_limit(void) {
return default_consumption_limit;
}
void evse_set_default_consumption_limit(uint32_t value) {
default_consumption_limit = value;
}
uint32_t evse_get_default_charging_time_limit(void) {
return default_charging_time_limit;
}
void evse_set_default_charging_time_limit(uint32_t value) {
default_charging_time_limit = value;
}
uint16_t evse_get_default_under_power_limit(void) {
return default_under_power_limit;
}
void evse_set_default_under_power_limit(uint16_t value) {
default_under_power_limit = value;
}
bool evse_is_limit_reached(void) {
return evse_get_limit_reached();
}
// ========================
// Limit checking logic
// This function must be called periodically while charging.
// It will flag the session as "limit reached" when thresholds are violated.
// ========================
void evse_limits_check(void) {
// Only check during an active charging session
if (!evse_state_is_charging(evse_get_state())) {
if (!changed)
return;
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist under-power limit (%" PRIu32 " W): %s",
(uint32_t)value, esp_err_to_name(err));
}
}
// ---------------------------------
// Runtime check
// ---------------------------------
void evse_limits_check(void)
{
// Só faz sentido quando há energia ativa (C2/D2)
if (!evse_state_is_charging(evse_get_state()))
return;
evse_session_t sess;
// Retrieve accumulated data for the current session
if (!evse_session_get(&sess) || !sess.is_current) {
// If there's no active session, abort
if (!evse_session_get(&sess) || !sess.is_current)
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;
// 1) Energy consumption limit (Wh)
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) {
ESP_LOGW("EVSE_LIMITS",
"Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
sess.energy_wh, consumption_limit);
if (cons_lim > 0 && sess.energy_wh >= cons_lim)
{
ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
sess.energy_wh, cons_lim);
reached = true;
}
// 2) Charging time limit (seconds)
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) {
ESP_LOGW("EVSE_LIMITS",
"Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
sess.duration_s, charging_time_limit);
if (time_lim > 0 && sess.duration_s >= time_lim)
{
ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
sess.duration_s, time_lim);
reached = true;
}
// 3) Under-power limit (instantaneous power)
uint32_t inst_power = evse_meter_get_instant_power();
if (under_power_limit > 0 && inst_power < under_power_limit) {
ESP_LOGW("EVSE_LIMITS",
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
(uint32_t)inst_power,
(uint32_t)under_power_limit);
int32_t p = evse_meter_get_instant_power();
uint32_t inst_power = (p > 0) ? (uint32_t)p : 0;
if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
{
ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
inst_power, (uint32_t)unp_lim);
reached = true;
}
if (reached) {
if (reached)
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_state.h"
#include "evse_error.h"
@@ -7,69 +6,124 @@
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_err.h"
#include <string.h>
#include <inttypes.h>
#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "esp_event.h"
#include "scheduler_events.h"
static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
static bool lb_prev_authorized = false;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
// Estado de janela do scheduler
static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
// ================= Helpers internos =================
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
static void lb_clear_pause_state(void)
{
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = false;
lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
bool evse_sched_is_allowed(void)
{
bool v;
portENTER_CRITICAL(&s_sched_mux);
v = s_sched_allowed;
portEXIT_CRITICAL(&s_sched_mux);
return v;
}
static void evse_manager_handle_auth_on_tick(void)
{
if (auth_enabled)
bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
bool local_auth_enabled;
bool local_lb_paused;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
local_lb_paused = lb_paused;
portEXIT_CRITICAL(&s_mgr_mux);
if (local_auth_enabled)
{
// Se o carro foi desconectado, revoga autorização
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_state_set_authorized(false);
// Desconexão física invalida qualquer pausa pendente do LB
lb_clear_pause_state();
}
if (has_error && evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
err_bits);
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
}
else
{
// Se autenticação está desativada, garante autorização sempre ativa
if (!evse_state_get_authorized())
bool limit_hit = evse_is_limit_reached();
bool can_operate = evse_config_is_available() && evse_config_is_enabled();
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
(int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
evse_state_set_authorized(false);
}
if (!local_lb_paused && sched_allowed && can_operate &&
!has_error && !limit_hit &&
!evse_state_get_authorized())
{
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization.");
// Em modo OPEN, pausa do LB não é tão relevante, mas limpamos mesmo assim
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)
{
(void)arg;
while (true)
{
evse_manager_tick();
@@ -79,10 +133,8 @@ static void evse_manager_task(void *arg)
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != AUTH_EVENTS || !data)
return;
auth_mode_t g_mode = AUTH_MODE_OPEN;
(void)arg;
if (base != AUTH_EVENTS || !data) return;
switch (id)
{
@@ -91,8 +143,6 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
evse_state_set_authorized(evt->authorized);
// Qualquer alteração explícita de auth invalida pausa do LB
lb_clear_pause_state();
break;
}
@@ -101,122 +151,110 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
case AUTH_EVENT_INIT:
{
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(g_mode));
if (g_mode == AUTH_MODE_OPEN)
{
evse_state_set_authorized(true);
auth_enabled = false;
}
else
{
evse_state_set_authorized(false);
auth_enabled = true;
}
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));
// 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();
break;
}
}
}
// ===== Tratador de eventos de loadbalancer =====
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
(void)handler_arg;
(void)event_base;
if (!event_data) return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED",
(long long)evt->timestamp_us);
// Ações adicionais podem ser adicionadas aqui conforme necessário
}
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{
const loadbalancer_master_limit_event_t *evt =
(const loadbalancer_master_limit_event_t *)event_data;
ESP_LOGI(TAG,
"Novo limite de corrente (master): %u A (ts: %lld)",
ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
evt->max_current, (long long)evt->timestamp_us);
if (evt->max_current == 0)
{
// Suspensão por LB (não interessa se é OPEN ou RFID/OCPP)
lb_paused = true;
lb_prev_authorized = evse_state_get_authorized();
bool prev_auth = 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)");
evse_state_set_authorized(false);
}
else
{
ESP_LOGD(TAG, "[LB] limit=0A → já não estava autorizado");
}
}
else
{
// Ajusta corrente em runtime
evse_set_runtime_charging_current(evt->max_current);
if (lb_paused)
{
lb_paused = false;
bool was_paused;
bool prev_auth;
// Só retomamos se EVSE estiver operacional
portENTER_CRITICAL(&s_mgr_mux);
was_paused = lb_paused;
prev_auth = lb_prev_authorized;
portEXIT_CRITICAL(&s_mgr_mux);
if (was_paused)
{
bool can_resume =
(evse_get_error() == 0) &&
evse_config_is_available() &&
evse_config_is_enabled();
evse_config_is_enabled() &&
evse_sched_is_allowed() &&
!evse_is_limit_reached();
if (!can_resume)
{
ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado)",
"[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
evt->max_current);
lb_clear_pause_state();
return;
}
if (!auth_enabled)
bool local_auth_enabled;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
lb_paused = false; // já vai tentar retomar
portEXIT_CRITICAL(&s_mgr_mux);
if (!local_auth_enabled)
{
// Modo OPEN: retoma sempre
ESP_LOGI(TAG,
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
evt->max_current);
ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
evse_state_set_authorized(true);
}
else
{
// RFID/OCPP: só retoma se havia autorização antes da pausa
if (lb_prev_authorized)
if (prev_auth)
{
ESP_LOGI(TAG,
"[LB] limit=%uA → RFID/OCPP, retomando autorização anterior (auto-resume)",
evt->max_current);
ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
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;
}
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);
portEXIT_CRITICAL(&s_mgr_mux);
}
}
}
@@ -224,8 +262,8 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != OCPP_EVENTS)
return;
(void)arg;
if (base != OCPP_EVENTS) return;
switch (id)
{
@@ -236,13 +274,10 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
break;
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:
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);
lb_clear_pause_state();
break;
@@ -253,25 +288,11 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
lb_clear_pause_state();
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:
ESP_LOGI(TAG, "[OCPP] StartTx");
// StartTx em si não precisa mexer em auth, mas limpamos estado de pausa por segurança
lb_clear_pause_state();
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:
{
if (!data)
@@ -283,10 +304,7 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
(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);
// Opcional: poderias também limpar a pausa aqui, dependendo da política
// lb_clear_pause_state();
break;
}
@@ -296,12 +314,37 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
}
}
// ===== Inicialização =====
static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
const sched_event_state_t *ev = (const sched_event_state_t *)data;
portENTER_CRITICAL(&s_sched_mux);
s_sched_allowed = ev->allowed_now;
portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
}
void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
esp_err_t err = evse_config_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
}
evse_config_init();
evse_error_init();
evse_hardware_init();
evse_state_init();
@@ -311,12 +354,14 @@ void evse_manager_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
ESP_LOGI(TAG, "EVSE Manager inicializado.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
configASSERT(rc == pdPASS);
}
// ===== Main Tick =====
void evse_manager_tick(void)
{
xSemaphoreTake(evse_mutex, portMAX_DELAY);
@@ -331,4 +376,3 @@ void evse_manager_tick(void)
xSemaphoreGive(evse_mutex);
}
// === Fim de: components/evse/evse_manager.c ===

View File

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

View File

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

View File

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

View File

@@ -5,150 +5,203 @@
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "esp_event.h"
// =========================
// Internal State Variables
// =========================
#define EVSE_EVENT_POST_TIMEOUT_MS 50
static evse_state_t current_state = EVSE_STATE_A;
static bool is_authorized = false;
static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED;
static const char *TAG = "evse_state";
// =========================
// Internal Mapping
// =========================
static evse_state_event_t map_state_to_event(evse_state_t s)
{
switch (s)
{
case EVSE_STATE_A:
return EVSE_STATE_EVENT_IDLE;
static evse_state_event_t map_state_to_event(evse_state_t s) {
switch (s) {
case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE;
case EVSE_STATE_B1:
case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING;
case EVSE_STATE_B2:
return EVSE_STATE_EVENT_WAITING;
case EVSE_STATE_C1:
case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING;
case EVSE_STATE_C2:
case EVSE_STATE_D1:
case EVSE_STATE_D2:
return EVSE_STATE_EVENT_CHARGING;
case EVSE_STATE_E:
case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT;
default: return EVSE_STATE_EVENT_IDLE;
case EVSE_STATE_F:
return EVSE_STATE_EVENT_FAULT;
default:
return EVSE_STATE_EVENT_IDLE;
}
}
// =========================
// Public API
// =========================
static void post_evse_event(evse_event_id_t id, const void *data, size_t len)
{
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;
evse_state_t prev_state;
bool start_session = false;
bool end_session = false;
// 1) Detecta transição de estado dentro da região crítica
portENTER_CRITICAL(&state_mux);
prev_state = current_state;
if (new_state != current_state) {
// se entrou em charging pela primeira vez
if (evse_state_is_charging(new_state) && !evse_state_is_charging(prev_state)) {
if (new_state != current_state)
{
// Sessão começa quando entra em energia (relé ON)
if (evse_state_is_power_flowing(new_state) && !evse_state_is_power_flowing(prev_state))
{
start_session = true;
}
// se saiu de charging para qualquer outro
else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) {
// Sessão termina quando sai de energia
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);
// 2) Executa start/end de sessão FORA da região crítica, evitando logs/alloc dentro dela
if (start_session) {
// Fora da região crítica
if (start_session)
{
evse_session_start();
}
if (end_session) {
if (end_session)
{
evse_session_end();
}
// 3) Se mudou o estado, faz log e dispara evento
if (changed) {
const char *prev_str = evse_state_to_str(prev_state);
const char *curr_str = evse_state_to_str(new_state);
ESP_LOGI(TAG, "State changed: %s → %s", prev_str, curr_str);
if (changed)
{
ESP_LOGI(TAG, "State changed: %s → %s",
evse_state_to_str(prev_state),
evse_state_to_str(new_state));
evse_state_event_data_t evt = {
.state = map_state_to_event(new_state)
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
&evt,
sizeof(evt),
portMAX_DELAY);
.state = map_state_to_event(new_state)};
post_evse_event(EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt));
}
}
evse_state_t evse_get_state(void) {
evse_state_t evse_get_state(void)
{
portENTER_CRITICAL(&state_mux);
evse_state_t s = current_state;
portEXIT_CRITICAL(&state_mux);
return s;
}
const char* evse_state_to_str(evse_state_t state) {
switch (state) {
case EVSE_STATE_A: return "A - EV Not Connected (12V)";
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_C1: 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";
const char *evse_state_to_str(evse_state_t state)
{
switch (state)
{
case EVSE_STATE_A:
return "A - EV Not Connected (12V)";
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_C1:
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);
current_state = EVSE_STATE_A;
is_authorized = true;
is_authorized = false;
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 = {
.state = map_state_to_event(current_state)
};
esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
.state = map_state_to_event(current_state)};
post_evse_event(EVSE_EVENT_INIT, &evt, sizeof(evt));
}
void evse_state_tick(void) {
// Placeholder for future state logic
void evse_state_tick(void)
{
// placeholder
}
bool evse_state_is_charging(evse_state_t state) {
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) {
void evse_state_set_authorized(bool authorized)
{
portENTER_CRITICAL(&state_mux);
is_authorized = authorized;
portEXIT_CRITICAL(&state_mux);
}
bool evse_state_get_authorized(void) {
bool evse_state_get_authorized(void)
{
portENTER_CRITICAL(&state_mux);
bool result = is_authorized;
portEXIT_CRITICAL(&state_mux);

View File

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

View File

@@ -15,11 +15,9 @@ extern "C" {
// Limites Globais (Defines)
// ========================
// Corrente máxima de carregamento (configurável pelo usuário)
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
// Corrente via cabo (proximity) — se configurável
#define MIN_CABLE_CURRENT_LIMIT 6 // A
#define MAX_CABLE_CURRENT_LIMIT 63 // A
@@ -31,23 +29,20 @@ extern "C" {
esp_err_t evse_config_init(void);
void evse_check_defaults(void);
// Corrente de carregamento
// Corrente máxima de hardware (fixa)
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);
esp_err_t evse_set_charging_current(uint16_t value);
uint16_t evse_get_default_charging_current(void);
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);
// Corrente runtime (RAM) <= max hardware (load balancer pode alterar)
void evse_set_runtime_charging_current(uint16_t value);
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
bool evse_is_rcm(void);

View File

@@ -5,11 +5,13 @@
#include <stdbool.h>
#include "evse_pilot.h"
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
EVSE_ERR_DIODE_SHORT_BIT | \
EVSE_ERR_TEMPERATURE_HIGH_BIT | \
EVSE_ERR_RCM_TRIGGERED_BIT )
// ----------------------------------------------------
// Holdoff interno pós-erro (sem expor "cooldown" ao resto)
// ----------------------------------------------------
// Após TODOS os erros reais desaparecerem (raw_bits == 0),
// 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
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
@@ -26,20 +28,22 @@ void evse_error_init(void);
// Verificações e monitoramento
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
void evse_temperature_check(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);
bool evse_is_error_cleared(void);
void evse_mark_error_cleared(void);
void evse_error_set(uint32_t bitmask);
void evse_error_clear(uint32_t bitmask);
bool evse_error_is_active(void);
uint32_t evse_error_get_bits(void);
void evse_error_reset_flag(void);
// ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos"
// (fica true quando o erro visível chega a 0; pode ser útil para UI/logs)
// ----------------------------------------------------
bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H

View File

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

View File

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

View File

@@ -2,64 +2,42 @@
#define PILOT_H_
#ifdef __cplusplus
extern "C" {
extern "C"
{
#endif
#include <stdbool.h>
#include <stdint.h>
/**
* @brief Níveis categóricos de tensão no sinal CP (Control Pilot)
*/
typedef enum
{
PILOT_VOLTAGE_12, ///< Estado A: +12V
PILOT_VOLTAGE_9, ///< Estado B: +9V
PILOT_VOLTAGE_6, ///< Estado C: +6V
PILOT_VOLTAGE_3, ///< Estado D: +3V
PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V
PILOT_VOLTAGE_12,
PILOT_VOLTAGE_9,
PILOT_VOLTAGE_6,
PILOT_VOLTAGE_3,
PILOT_VOLTAGE_1
} pilot_voltage_t;
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @brief Cache interno opcional dos níveis de tensão reais do Pilot
*/
typedef struct {
uint16_t high_mv; ///< Pico positivo medido (mV)
uint16_t low_mv; ///< Pico negativo medido (mV)
typedef struct
{
uint16_t high_mv;
uint16_t low_mv;
} pilot_voltage_cache_t;
#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
#define EVSE_SESSION_H
@@ -15,39 +9,23 @@
* @brief Charging session statistics
*/
typedef struct {
TickType_t start_tick; ///< tick when session began
uint32_t duration_s; ///< total duration in seconds
uint32_t energy_wh; ///< total energy consumed in Wh
TickType_t start_tick; ///< tick when session began (debug/trace)
uint32_t duration_s; ///< total duration in seconds (tempo real)
uint32_t energy_wh; ///< total energy consumed in Wh (tempo real)
uint32_t avg_power_w; ///< average power in W
bool is_current; ///< true if session still in progress
} evse_session_t;
/**
* @brief Initialize the session module
*/
void evse_session_init(void);
/**
* @brief Mark the beginning of a charging session
*/
void evse_session_start(void);
/**
* @brief Mark the end of the charging session and store it as "last session"
*/
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);
/**
* @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);
#endif // EVSE_SESSION_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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));
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)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

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

View File

@@ -7,6 +7,7 @@
#include "driver/uart.h"
#include <stddef.h>
#include <string.h>
#include "meter_ea777.h"
#define TAG "serial_mdb_ea777"
@@ -148,7 +149,7 @@ static void meter_ea777_post_event(float *voltage, float *current, int *power_w,
memcpy(evt.watt, power_w, sizeof(evt.watt));
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)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

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

View File

@@ -8,11 +8,12 @@
#define TAG "serial_mdb_orno513"
#define MB_PORT_NUM 2
#define MB_DEV_SPEED 9600
#define MB_UART_TXD 17
#define MB_UART_RXD 16
#define MB_UART_RTS 5
#define MB_UART_RTS 2
#define UPDATE_INTERVAL (3000 / 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.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);

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));
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) {
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.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);
}
}

View File

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

View File

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

View File

@@ -10,10 +10,15 @@
#include "meter_zigbee.h"
#include "meter_ea777.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.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";
@@ -21,179 +26,200 @@ static const char *TAG = "meter_manager";
static meter_type_t meter_evse_type = METER_TYPE_NONE;
static meter_type_t meter_grid_type = METER_TYPE_NONE;
#define NVS_NAMESPACE "meterconfig"
#define NVS_EVSE_MODEL "evse_model"
#define NVS_GRID_MODEL "grid_model"
#define STORE_NAMESPACE "meterconfig"
#define STORE_EVSE_MODEL "evse_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)
{
(void)arg;
(void)data;
if (base != NETWORK_EVENTS)
return;
switch (event_id)
{
case NETWORK_EVENT_AP_STARTED:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid");
// meter_manager_grid_stop();
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid");
break;
case NETWORK_EVENT_AP_STOP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid");
// meter_manager_grid_start();
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid");
break;
case NETWORK_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP");
// opcional: reiniciar ou logar
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP");
break;
default:
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)
{
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 handle for %s: %s", key, esp_err_to_name(err));
return err;
}
if (!type)
return ESP_ERR_INVALID_ARG;
// tenta ler
uint8_t value = 0xFF;
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)
{
*type = (meter_type_t)value;
ESP_LOGI(TAG, "Loaded meter type %d from NVS key '%s'", value, key);
}
else
{
*type = METER_TYPE_NONE;
nvs_set_u8(handle, key, *type);
nvs_commit(handle);
ESP_LOGW(TAG, "Invalid or missing key '%s', setting default (NONE)", key);
}
nvs_close(handle);
ESP_LOGD(TAG, "Loaded meter type %u from storage key '%s/%s'", (unsigned)value, STORE_NAMESPACE, key);
return ESP_OK;
}
static esp_err_t write_meter_model_to_nvs(const char *key, meter_type_t meter_type)
// se não existir / inválido -> default NONE e grava
*type = METER_TYPE_NONE;
esp_err_t w = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)(*type));
if (w != ESP_OK)
ESP_LOGE(TAG, "Failed to init key '%s/%s' to NONE: %s", STORE_NAMESPACE, key, esp_err_to_name(w));
(void)storage_try_flush();
ESP_LOGW(TAG, "Invalid/missing key '%s/%s' (read=%s), setting default (NONE)",
STORE_NAMESPACE, key, esp_err_to_name(err));
return ESP_OK; // seguimos com default
}
static esp_err_t write_meter_model_to_storage(const char *key, meter_type_t meter_type)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
esp_err_t err = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)meter_type);
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;
}
err = nvs_set_u8(handle, key, (uint8_t)meter_type);
if (err == 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;
(void)storage_try_flush();
ESP_LOGD(TAG, "Saved meter type %d to storage key '%s/%s'", (int)meter_type, STORE_NAMESPACE, key);
return ESP_OK;
}
/**
* @brief Initializes the meter manager system.
*
* 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.
* @brief Inicializa o sistema de meter manager.
*/
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)
// err = meter_manager_evse_init();
// if (err != ESP_OK) return err;
esp_err_t err;
// Initialize GRID meter
err = meter_manager_grid_init();
if (err != ESP_OK)
return err;
// Register handler for custom network events
ESP_LOGI(TAG, "Registering network event handler");
return esp_event_handler_register(NETWORK_EVENTS,
// Regista handler para eventos de rede
/*
ESP_LOGD(TAG, "Registering network event handler");
err = esp_event_handler_register(
NETWORK_EVENTS,
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 err;
// Start EVSE meter (habilite quando quiser)
// err = meter_manager_evse_start();
// if (err != ESP_OK) return err;
// Start GRID meter
err = meter_manager_grid_start();
if (err != ESP_OK)
return err;
return ESP_OK;
return meter_manager_grid_start();
}
/**
* @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 err;
// Stop EVSE meter
// err = meter_manager_evse_stop();
// if (err != ESP_OK) return err;
// Stop GRID meter
err = meter_manager_grid_stop();
if (err != ESP_OK)
return err;
return ESP_OK;
return meter_manager_grid_stop();
}
// ---------- EVSE ----------
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)
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)
{
@@ -292,13 +318,13 @@ esp_err_t meter_manager_evse_stop(void)
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)
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:
return ESP_OK;
@@ -334,7 +360,7 @@ esp_err_t meter_manager_grid_start()
case METER_TYPE_ADE7758:
return meter_ade7758_start();
case METER_TYPE_ORNO513:
return meter_orno513_start(); // corrigido
return meter_orno513_start();
case METER_TYPE_ORNO516:
return meter_orno516_start();
case METER_TYPE_ORNO526:
@@ -364,7 +390,7 @@ esp_err_t meter_manager_grid_stop(void)
meter_ade7758_stop();
break;
case METER_TYPE_ORNO513:
meter_orno513_stop(); // corrigido
meter_orno513_stop();
break;
case METER_TYPE_ORNO516:
meter_orno516_stop();
@@ -391,18 +417,64 @@ esp_err_t meter_manager_grid_stop(void)
return ESP_OK;
}
// ---------- Utilidades ----------
// ---------- Utilidades / setters com evento ----------
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;
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 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;
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)
@@ -410,15 +482,8 @@ bool meter_manager_evse_is_enabled(void)
return meter_manager_evse_get_model() != METER_TYPE_NONE;
}
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;
}
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; }
const char *meter_type_to_str(meter_type_t type)
{
@@ -453,6 +518,7 @@ meter_type_t string_to_meter_type(const char *str)
{
if (!str)
return METER_TYPE_NONE;
if (strcmp(str, "IC ADE") == 0)
return METER_TYPE_ADE7758;
if (strcmp(str, "ORNO-513") == 0)
@@ -471,5 +537,6 @@ meter_type_t string_to_meter_type(const char *str)
return METER_TYPE_TRIF_ZIGBEE;
if (strcmp(str, "EA-777") == 0)
return METER_TYPE_EA777;
return METER_TYPE_NONE;
}

View File

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

View File

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

View File

@@ -11,22 +11,24 @@
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "mdns.h"
#include "network_events.h"
#include "network.h"
// NEW:
#include "storage_service.h"
// -----------------------------------------------------------------------------
// Config
// -----------------------------------------------------------------------------
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC)
#define MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC)
#define AP_SSID "plx-%02x%02x%02x"
#define MDNS_SSID "plx%02x"
#define NVS_NAMESPACE "wifi"
#define NVS_ENABLED "enabled"
@@ -35,60 +37,106 @@
// Comprimentos com terminador
#define SSID_MAX_LEN 32
#define PASS_MAX_LEN 64 // 63 chars + '\0'
#define SSID_BUF_SZ (SSID_MAX_LEN + 1) // 33
#define PASS_BUF_SZ (PASS_MAX_LEN + 1) // 65
#define PASS_MAX_LEN 64
#define SSID_BUF_SZ (SSID_MAX_LEN + 1)
#define PASS_BUF_SZ (PASS_MAX_LEN + 1)
static const char *TAG = "wifi";
// Storage timeouts
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
// -----------------------------------------------------------------------------
// Estado global
// -----------------------------------------------------------------------------
static nvs_handle_t nvs;
static esp_netif_t *sta_netif;
static esp_netif_t *ap_netif;
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 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 nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, size_t out_sz)
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);
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)
return ESP_ERR_INVALID_ARG;
out[0] = '\0';
size_t need = 0;
esp_err_t err = nvs_get_str(h, key, NULL, &need);
if (err == ESP_ERR_NVS_NOT_FOUND)
// buffer grande (sem heap): usa o máximo definido no storage_service
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)
{
out[0] = '\0';
return ESP_OK;
if (err != ESP_OK)
return err;
if (need == 0)
return ESP_OK; // vazio
if (need > out_sz)
{
// Truncar de forma segura
char *tmp = (char *)malloc(need);
if (!tmp)
return ESP_ERR_NO_MEM;
err = nvs_get_str(h, key, tmp, &need);
if (err == ESP_OK)
{
snprintf(out, out_sz, "%s", tmp);
}
free(tmp);
return err;
if (e != ESP_OK)
{
out[0] = '\0';
return e;
}
return nvs_get_str(h, key, out, &need);
size_t n = strnlen(tmp, out_sz - 1); // no máximo out_sz-1
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
}
// -----------------------------------------------------------------------------
@@ -96,6 +144,8 @@ static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, si
// -----------------------------------------------------------------------------
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
(void)arg;
if (event_base == WIFI_EVENT)
{
switch (event_id)
@@ -125,6 +175,16 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
esp_wifi_connect();
break;
}
case WIFI_EVENT_STA_CONNECTED:
{
ESP_LOGI(TAG, "STA associated (L2 connected)");
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_CONNECTED,
NULL,
0,
portMAX_DELAY);
break;
}
case WIFI_EVENT_STA_DISCONNECTED:
{
xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
@@ -133,7 +193,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1);
// NÃO bloquear o event loop com vTaskDelay
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_DISCONNECTED,
ev,
ev ? sizeof(*ev) : 0,
portMAX_DELAY);
if (s_retry_count < s_retry_max)
{
esp_err_t err = esp_wifi_connect();
@@ -144,7 +209,7 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
}
else
{
ESP_LOGW(TAG, "esp_wifi_connect failed (%d)", err);
ESP_LOGW(TAG, "esp_wifi_connect failed (%s)", esp_err_to_name(err));
}
}
else
@@ -166,14 +231,21 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0;
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_GOT_IP,
&event->ip_info,
sizeof(event->ip_info),
portMAX_DELAY);
}
else if (event_id == IP_EVENT_GOT_IP6)
else if (event_id == IP_EVENT_STA_LOST_IP)
{
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
ESP_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;
ESP_LOGW(TAG, "STA lost IP");
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_LOST_IP,
NULL,
0,
portMAX_DELAY);
}
}
}
@@ -192,28 +264,21 @@ static void sta_set_config(void)
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
// buffers "seguros" vindos da NVS (estes sim têm terminador)
char ssid_buf[SSID_BUF_SZ] = {0}; // 33 (32 + '\0')
char pass_buf[PASS_BUF_SZ] = {0}; // 65 (64 + '\0')
char ssid_buf[SSID_BUF_SZ] = {0};
char pass_buf[PASS_BUF_SZ] = {0};
wifi_get_ssid(ssid_buf);
wifi_get_password(pass_buf);
// Copiar **sem** terminador para os campos do esp_wifi (32/64 bytes)
// SSID: max 32
size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN); // até 32
size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN);
memcpy(wifi_config.sta.ssid, ssid_buf, ssid_len);
// Password WPA/WPA2: 8..63 chars (campo tem 64 bytes)
// (se usares rede aberta, pass_len pode ser 0)
size_t pass_len = strnlen(pass_buf, 63); // até 63
size_t pass_len = strnlen(pass_buf, 63);
memcpy(wifi_config.sta.password, pass_buf, pass_len);
if (pass_len <= 63)
{
wifi_config.sta.password[pass_len] = '\0';
}
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)
@@ -222,14 +287,13 @@ static void ap_set_config(void)
wifi_config_t wifi_ap_config = {0};
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];
esp_wifi_get_mac(WIFI_IF_AP, mac);
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_config(WIFI_IF_AP, &wifi_ap_config));
}
@@ -248,7 +312,7 @@ static void sta_try_start(void)
esp_err_t e = esp_wifi_start();
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);
}
@@ -261,8 +325,10 @@ void wifi_ini(void)
{
ESP_LOGI(TAG, "Wifi init");
// Abre NVS (assume que nvs_flash_init() já foi chamado no boot geral)
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs));
// garante storage pronto (não assume NVS handle aberto aqui)
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();
@@ -275,14 +341,11 @@ void wifi_ini(void)
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// mDNS: usa dois bytes do MAC para identificar
uint8_t mac[6];
esp_wifi_get_mac(WIFI_IF_STA, mac);
char chargeid[16];
// ex.: "plx00"
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(ap_netif, chargeid));
@@ -300,10 +363,9 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
{
ESP_LOGI(TAG, "wifi_set_config(enabled=%d)", enabled);
// Validação (quando habilitar STA)
// Validação
if (enabled)
{
// SSID 1..32
if (ssid && (strlen(ssid) == 0 || strlen(ssid) > SSID_MAX_LEN))
{
ESP_LOGE(TAG, "SSID out of range");
@@ -311,15 +373,15 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
}
if (!ssid)
{
size_t len = 0;
esp_err_t e = nvs_get_str(nvs, NVS_SSID, NULL, &len);
if (e != ESP_OK || len <= 1)
char cur_ssid[SSID_BUF_SZ] = {0};
wifi_get_ssid(cur_ssid);
if (strlen(cur_ssid) == 0)
{
ESP_LOGE(TAG, "Required SSID");
return ESP_ERR_INVALID_ARG;
}
}
// Password: 8..63 (se não for vazia). Aceita "" para rede open (caso uses).
if (password)
{
size_t lp = strlen(password);
@@ -331,13 +393,27 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
}
}
// Persiste no NVS
ESP_ERROR_CHECK(nvs_set_u8(nvs, NVS_ENABLED, enabled));
// Persiste via storage_service
esp_err_t err = store_set_u8_best_effort(NVS_NAMESPACE, NVS_ENABLED, enabled ? 1 : 0);
if (err != ESP_OK)
return err;
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)
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
ESP_LOGI(TAG, "Stopping AP/STA");
@@ -345,7 +421,7 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
esp_err_t e = esp_wifi_stop();
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();
@@ -367,21 +443,21 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
esp_err_t err = esp_wifi_scan_start(NULL, true);
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;
}
err = esp_wifi_scan_get_ap_records(&number, ap_info);
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;
}
err = esp_wifi_scan_get_ap_num(&ap_count);
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;
}
@@ -389,8 +465,7 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++)
{
// garante que scan_aps[i].ssid tenha pelo menos 33 bytes
snprintf(scan_aps[i].ssid, SSID_BUF_SZ, "%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].auth = ap_info[i].authmode != WIFI_AUTH_OPEN;
}
@@ -401,29 +476,29 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
bool wifi_get_enabled(void)
{
uint8_t value = 0;
esp_err_t e = nvs_get_u8(nvs, NVS_ENABLED, &value);
if (e == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = storage_get_u8_sync(NVS_NAMESPACE, NVS_ENABLED, &value, STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
return false;
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 value;
return (value != 0);
}
void wifi_get_ssid(char *value)
{
if (!value)
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)
{
if (!value)
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)
@@ -439,7 +514,7 @@ void wifi_ap_start(void)
esp_err_t e = esp_wifi_start();
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);

View File

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

View File

@@ -1,32 +1,37 @@
// components/ocpp/src/ocpp.c
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include "esp_log.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_events.h"
#include "esp_wifi.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "evse_error.h"
#include "auth_events.h"
#include "evse_events.h"
#include "evse_state.h"
#include "meter_events.h"
#include "esp_timer.h"
#include <math.h>
/* MicroOcpp includes */
#include <mongoose.h>
#include <MicroOcpp_c.h> // C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> // WebSocket integration for ESP-IDF
// NEW storage layer
#include "storage_service.h"
#define NVS_NAMESPACE "ocpp"
#define NVS_OCPP_ENABLED "enabled"
#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 ---
typedef struct
{
// dados por fase
float vrms[3];
float irms[3];
int32_t watt[3]; // ativo por fase (W)
float frequency;
float power_factor;
// acumulados
float total_energy_Wh;
// derivados práticos
int32_t sum_watt; // soma das 3 fases
float avg_voltage; // média das 3 fases
float sum_current; // soma das 3 fases
// flag de validade
int32_t sum_watt;
float avg_voltage;
float sum_current;
bool have_data;
} ocpp_meter_cache_t;
@@ -82,18 +86,101 @@ static esp_event_handler_instance_t s_meter_inst = NULL;
// valor de oferta (A por conector)
static float s_current_offered_A = 16.0f;
// novo input apropriado
static float getCurrentOffered(void)
{
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)
{
(void)param;
while (true)
{
if (enabled)
@@ -106,7 +193,6 @@ static void ocpp_task_func(void *param)
{
s_evse_enabled = operative;
// >>> enviar OCPP_EVENT (remoto → local)
ocpp_operative_event_t ev = {
.operative = operative,
.timestamp_us = esp_timer_get_time()};
@@ -115,8 +201,7 @@ static void ocpp_task_func(void *param)
&ev, sizeof(ev),
portMAX_DELAY);
ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d",
(int)operative);
ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative);
}
}
else
@@ -126,33 +211,21 @@ static void ocpp_task_func(void *param)
}
}
/* =========================
* NVS GETs
* ========================= */
// -----------------------------------------------------------------------------
// Storage GETs
// -----------------------------------------------------------------------------
bool ocpp_get_enabled(void)
{
nvs_handle_t h;
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;
}
storage_init_best_effort();
uint8_t value = 0;
err = nvs_get_u8(h, NVS_OCPP_ENABLED, &value);
nvs_close(h);
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO);
if (err == ESP_ERR_NVS_NOT_FOUND)
return false; // default
if (err == ESP_ERR_NOT_FOUND)
return false;
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 value != 0;
@@ -164,31 +237,12 @@ void ocpp_get_server(char *value /* out, size>=64 */)
return;
value[0] = '\0';
nvs_handle_t h;
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;
}
storage_init_best_effort();
size_t len = 64;
err = nvs_get_str(h, NVS_OCPP_SERVER, value, &len);
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64);
if (e != ESP_OK)
{
value[0] = '\0';
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(server) failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
@@ -199,92 +253,81 @@ void ocpp_get_charge_id(char *value /* out, size>=64 */)
return;
value[0] = '\0';
nvs_handle_t h;
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;
}
storage_init_best_effort();
size_t len = 64;
err = nvs_get_str(h, NVS_OCPP_CHARGE_ID, value, &len);
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64);
if (e != ESP_OK)
{
value[0] = '\0';
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(charge_id) failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
/* =========================
* NVS SETs
* ========================= */
// --- SETTERS: RW (cria o namespace na 1ª escrita), commit e fecha ---
// -----------------------------------------------------------------------------
// Storage SETs
// -----------------------------------------------------------------------------
void ocpp_set_enabled(bool value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
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;
}
ESP_LOGI(TAG, "set enabled %d", value);
ESP_ERROR_CHECK(nvs_set_u8(h, NVS_OCPP_ENABLED, value ? 1 : 0));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
enabled = value;
}
void ocpp_set_server(char *value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
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;
}
ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_SERVER, value ? value : ""));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
}
void ocpp_set_charge_id(char *value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
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;
}
ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_CHARGE_ID, value ? value : ""));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
}
// -----------------------------------------------------------------------------
// Event handlers (AUTH / EVSE / METER)
// -----------------------------------------------------------------------------
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;
if (!rq)
return;
// Sanitizar/copiar a idTag
char idtag[AUTH_TAG_MAX_LEN];
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);
// Se não está pronto, apenas regista e sai (podes adaptar conforme política)
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;
}
// Regra pedida:
// - se já existe transação/charge em andamento -> terminar
// - senão -> iniciar com a IDTAG recebida
if (ocpp_isTransactionActive())
{
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)
{
(void)arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
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:
s_ev_plugged = true;
s_ev_ready = true; // EV está a pedir/receber energia
s_ev_ready = true;
break;
case EVSE_STATE_EVENT_FAULT:
default:
// em falha, considera não pronto (mantém plugged se quiseres)
s_ev_ready = false;
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)
{
(void)arg;
if (base != EVSE_EVENTS || data == NULL)
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;
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;
}
@@ -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;
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;
}
}
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)
return;
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)
return;
// Derivados simples
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 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);
}
/* =========================
* MicroOCPP Inputs/CBs
* ========================= */
// -----------------------------------------------------------------------------
// MicroOCPP Inputs/CBs
// -----------------------------------------------------------------------------
bool setConnectorPluggedInput(void)
{
return s_ev_plugged; // EV fisicamente ligado
return s_ev_plugged;
}
bool setEvReadyInput(void)
{
return s_ev_ready; // EV pede / pronto a carregar
return s_ev_ready;
}
bool setEvseReadyInput(void)
{
// EVSE autorizado / operacional
return s_evse_enabled && s_evse_available;
}
@@ -439,13 +481,10 @@ float setPowerMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w);
}
return (float)w;
}
@@ -461,14 +500,11 @@ float setEnergyMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh);
}
return wh; // agora devolve Wh
return wh;
}
int setEnergyInput(void)
@@ -491,13 +527,10 @@ float setCurrentInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a);
}
return a;
}
@@ -513,19 +546,16 @@ float setVoltageInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v);
}
return v;
}
float setPowerInput(void)
{
float w = setPowerMeterInput(); // alias
float w = setPowerMeterInput();
ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w);
return w;
}
@@ -598,111 +628,63 @@ bool setOnResetNotify(bool value)
void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification)
{
(void)transaction;
ESP_LOGI(TAG, "TxNotification: %d", txNotification);
switch (txNotification)
{
case 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);
break;
case 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);
break;
case AuthorizationTimeout:
ESP_LOGI(TAG, "AuthorizationTimeout");
// TODO: send event ocpp AuthorizationTimeout
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY);
break;
case ReservationConflict:
ESP_LOGI(TAG, "ReservationConflict");
// TODO: send event ocpp ReservationConflict
// (Se quiseres, cria um ID específico no enum e publica aqui)
break;
case ConnectionTimeout:
ESP_LOGI(TAG, "ConnectionTimeout");
// TODO: send event ocpp ConnectionTimeout
// (Se quiseres, cria um ID específico no enum e publica aqui)
break;
case 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;
case 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);
break;
case RemoteStop:
ESP_LOGI(TAG, "RemoteStop");
// TODO: send event ocpp RemoteStop
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY);
break;
case 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);
break;
case 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);
break;
}
}
// Estado de conexão simples do OCPP
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;
}
@@ -728,12 +710,12 @@ const char *addErrorCodeInput(void)
else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
ptr = "OtherError";
return ptr; // NULL => sem erro
return ptr;
}
/* =========================
* Start / Stop OCPP
* ========================= */
// -----------------------------------------------------------------------------
// Start / Stop OCPP
// -----------------------------------------------------------------------------
void ocpp_start(void)
{
ESP_LOGI(TAG, "Starting OCPP");
@@ -744,6 +726,8 @@ void ocpp_start(void)
return;
}
storage_init_best_effort();
enabled = ocpp_get_enabled();
if (!enabled)
{
@@ -767,15 +751,14 @@ void ocpp_start(void)
return;
}
/* Inicializar Mongoose + MicroOcpp */
mg_mgr_init(&mgr);
mg_log_set(MG_LL_ERROR);
struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true};
g_ocpp_conn = ocpp_makeConnection(&mgr,
serverstr, /* ex: ws://host:port/OCPP16/... */
charge_id, /* ChargeBoxId / identity */
serverstr,
charge_id,
"",
"",
fsopt);
@@ -788,19 +771,16 @@ void ocpp_start(void)
ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false);
/* Inputs/outputs e callbacks */
ocpp_setEvReadyInput(&setEvReadyInput);
ocpp_setEvseReadyInput(&setEvseReadyInput);
ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);
ocpp_setOnResetExecute(&OnResetExecute);
ocpp_setTxNotificationOutput(&notificationOutput);
// ocpp_setStartTxReadyInput(&setStartTxReadyInput);
ocpp_setStopTxReadyInput(&setStopTxReadyInput);
ocpp_setOnResetNotify(&setOnResetNotify);
ocpp_setEnergyMeterInput(&setEnergyInput); // inteiro Wh
ocpp_setEnergyMeterInput(&setEnergyInput);
/* Metering */
ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL);
@@ -810,8 +790,7 @@ void ocpp_start(void)
ocpp_addErrorCodeInput(&addErrorCodeInput);
/* Task */
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 5, &ocpp_task);
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task);
if (!s_auth_verify_inst)
{
@@ -821,7 +800,6 @@ void ocpp_start(void)
ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener");
}
// ouvir mudanças de estado do EVSE
if (!s_evse_state_inst)
{
ESP_ERROR_CHECK(esp_event_handler_instance_register(
@@ -829,7 +807,6 @@ void ocpp_start(void)
&evse_event_handler, NULL, &s_evse_state_inst));
}
// ouvir mudanças de ENABLE / AVAILABLE do EVSE (Local → OCPP)
if (!s_evse_enable_inst)
{
ESP_ERROR_CHECK(esp_event_handler_instance_register(

View File

@@ -2,19 +2,15 @@ set(srcs
"src/adc.c"
"src/adc121s021_dma.c"
"src/peripherals.c"
"src/led.c"
"src/proximity.c"
"src/ac_relay.c"
"src/socket_lock.c"
"src/rcm.c"
"src/aux_io.c"
"src/onewire.c"
"src/ds18x20.c"
"src/temp_sensor.c"
"src/ntc_sensor.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver esp_adc esp_timer
REQUIRES config evse ntc_driver spi_bus_manager)
PRIV_REQUIRES driver esp_adc esp_timer
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_
#define ADC_DMA_H_
#include <stdint.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);
/**
* @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);
#ifdef __cplusplus
}
#endif
#endif /* ADC_DMA_h_ */
#endif /* ADC_DMA_H_ */

View File

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

View File

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

View File

@@ -1,28 +1,41 @@
// components/peripherals/src/adc121s021_dma.c
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_err.h"
#include "adc121s021_dma.h"
#include "spi_bus_manager.h"
#define TAG "adc_dma"
// Pino de chip-select do ADC121S021 (ajusta se necessário)
#define PIN_NUM_CS 5
#define SAMPLE_SIZE_BYTES 2
// ADC é 12-bit, mas transferimos 16 bits via SPI
#define ADC_BITS 12
#define SPI_CLOCK_HZ (6 * 1000 * 1000) // 6 MHz
// Clock SPI: 1 MHz → ~16 µs de transferência por amostra.
// Com um pequeno delay entre leituras, 100 amostras ficam em ~23 ms,
// o que é perfeito para analisar um PWM de 1 kHz a cada 100 ms.
#define SPI_CLOCK_HZ (1 * 1000 * 1000)
static spi_device_handle_t adc_spi = NULL;
void adc121s021_dma_init(void)
{
if (adc_spi) {
if (adc_spi)
{
ESP_LOGW(TAG, "ADC121S021 já foi inicializado.");
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_err_t err = spi_bus_manager_init(); // 🔧 CORRIGIDO: sem argumentos
if (err != ESP_OK) {
esp_err_t err = spi_bus_manager_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Falha ao inicializar o SPI bus: %s", esp_err_to_name(err));
return;
}
@@ -32,44 +45,55 @@ void adc121s021_dma_init(void)
.clock_speed_hz = SPI_CLOCK_HZ,
.mode = 0,
.spics_io_num = PIN_NUM_CS,
.queue_size = 2,
.queue_size = 2, // suficiente para uso em burst
.flags = SPI_DEVICE_NO_DUMMY,
.pre_cb = NULL,
.post_cb = NULL,
};
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));
adc_spi = NULL;
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)
{
if (!adc_spi) {
ESP_LOGE(TAG, "ADC SPI não inicializado!");
if (!sample)
{
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};
spi_transaction_t t = {
.length = 16,
.length = 16, // 16 bits
.tx_buffer = tx_buffer,
.rx_buffer = rx_buffer,
.flags = 0
};
.flags = 0};
esp_err_t err = spi_device_transmit(adc_spi, &t);
if (err != ESP_OK) {
// Polling transmit → menor overhead que fila + espera.
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));
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;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,498 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 zeroday nodemcu.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -------------------------------------------------------------------------------
* Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
* following additional terms:
*
* Except as contained in this notice, the name of Dallas Semiconductor
* shall not be used except as stated in the Dallas Semiconductor
* Branding Policy.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "rom/ets_sys.h"
#include "onewire.h"
#define ONEWIRE_SELECT_ROM 0x55
#define ONEWIRE_SKIP_ROM 0xcc
#define ONEWIRE_SEARCH 0xf0
#define ONEWIRE_CRC8_TABLE
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
// Waits up to `max_wait` microseconds for the specified pin to go high.
// Returns true if successful, false if the bus never comes high (likely
// shorted).
static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait)
{
bool state;
for (int i = 0; i < ((max_wait + 4) / 5); i++) {
if (gpio_get_level(pin))
break;
ets_delay_us(5);
}
state = gpio_get_level(pin);
// Wait an extra 1us to make sure the devices have an adequate recovery
// time before we drive things low again.
ets_delay_us(1);
return state;
}
static void setup_pin(gpio_num_t pin, bool open_drain)
{
gpio_set_direction(pin, open_drain ? GPIO_MODE_INPUT_OUTPUT_OD : GPIO_MODE_OUTPUT);
// gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY);
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return false;
//
// Returns true if a device asserted a presence pulse, false otherwise.
//
bool onewire_reset(gpio_num_t pin)
{
setup_pin(pin, true);
gpio_set_level(pin, 1);
// wait until the wire is high... just in case
if (!_onewire_wait_for_bus(pin, 250))
return false;
gpio_set_level(pin, 0);
ets_delay_us(480);
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 1); // allow it to float
ets_delay_us(70);
bool r = !gpio_get_level(pin);
portEXIT_CRITICAL(&mux);
// Wait for all devices to finish pulling the bus low before returning
if (!_onewire_wait_for_bus(pin, 410))
return false;
return r;
}
static bool _onewire_write_bit(gpio_num_t pin, bool v)
{
if (!_onewire_wait_for_bus(pin, 10))
return false;
portENTER_CRITICAL(&mux);
if (v) {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(10);
gpio_set_level(pin, 1); // allow output high
ets_delay_us(55);
} else {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(65);
gpio_set_level(pin, 1); // allow output high
}
ets_delay_us(1);
portEXIT_CRITICAL(&mux);
return true;
}
static int _onewire_read_bit(gpio_num_t pin)
{
if (!_onewire_wait_for_bus(pin, 10))
return -1;
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 0);
ets_delay_us(2);
gpio_set_level(pin, 1); // let pin float, pull up will raise
ets_delay_us(11);
int r = gpio_get_level(pin); // Must sample within 15us of start
ets_delay_us(48);
portEXIT_CRITICAL(&mux);
return r;
}
// Write a byte. The writing code uses open-drain mode and expects the pullup
// resistor to pull the line high when not driven low. If you need strong
// power after the write (e.g. DS18B20 in parasite power mode) then call
// onewire_power() after this is complete to actively drive the line high.
//
bool onewire_write(gpio_num_t pin, uint8_t v)
{
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1)
if (!_onewire_write_bit(pin, (bitMask & v)))
return false;
return true;
}
bool onewire_write_bytes(gpio_num_t pin, const uint8_t* buf, size_t count)
{
for (size_t i = 0; i < count; i++)
if (!onewire_write(pin, buf[i]))
return false;
return true;
}
// Read a byte
//
int onewire_read(gpio_num_t pin)
{
int r = 0;
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
int bit = _onewire_read_bit(pin);
if (bit < 0)
return -1;
else if (bit)
r |= bitMask;
}
return r;
}
bool onewire_read_bytes(gpio_num_t pin, uint8_t* buf, size_t count)
{
size_t i;
int b;
for (i = 0; i < count; i++) {
b = onewire_read(pin);
if (b < 0)
return false;
buf[i] = b;
}
return true;
}
bool onewire_select(gpio_num_t pin, onewire_addr_t addr)
{
uint8_t i;
if (!onewire_write(pin, ONEWIRE_SELECT_ROM))
return false;
for (i = 0; i < 8; i++) {
if (!onewire_write(pin, addr & 0xff))
return false;
addr >>= 8;
}
return true;
}
bool onewire_skip_rom(gpio_num_t pin)
{
return onewire_write(pin, ONEWIRE_SKIP_ROM);
}
bool onewire_power(gpio_num_t pin)
{
// Make sure the bus is not being held low before driving it high, or we
// may end up shorting ourselves out.
if (!_onewire_wait_for_bus(pin, 10))
return false;
setup_pin(pin, false);
gpio_set_level(pin, 1);
return true;
}
void onewire_depower(gpio_num_t pin)
{
setup_pin(pin, true);
}
void onewire_search_start(onewire_search_t* search)
{
// reset the search state
memset(search, 0, sizeof(*search));
}
void onewire_search_prefix(onewire_search_t* search, uint8_t family_code)
{
uint8_t i;
search->rom_no[0] = family_code;
for (i = 1; i < 8; i++) {
search->rom_no[i] = 0;
}
search->last_discrepancy = 64;
search->last_device_found = false;
}
// Perform a search. If the next device has been successfully enumerated, its
// ROM address will be returned. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return 1 : device found, ROM number in ROM_NO buffer
// 0 : device not found, end of search
//
onewire_addr_t onewire_search_next(onewire_search_t* search, gpio_num_t pin)
{
//TODO: add more checking for read/write errors
uint8_t id_bit_number;
uint8_t last_zero, search_result;
int rom_byte_number;
int8_t id_bit, cmp_id_bit;
onewire_addr_t addr;
unsigned char rom_byte_mask;
bool search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = 0;
// if the last call was not the last one
if (!search->last_device_found) {
// 1-Wire reset
if (!onewire_reset(pin)) {
// reset the search
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
}
// issue the search command
onewire_write(pin, ONEWIRE_SEARCH);
// loop to do the search
do {
// read a bit and its complement
id_bit = _onewire_read_bit(pin);
cmp_id_bit = _onewire_read_bit(pin);
if ((id_bit == 1) && (cmp_id_bit == 1))
break;
else {
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit)
search_direction = id_bit; // bit write value for search
else {
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < search->last_discrepancy)
search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0);
else
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == search->last_discrepancy);
// if 0 was picked then record its position in LastZero
if (!search_direction)
last_zero = id_bit_number;
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction)
search->rom_no[rom_byte_number] |= rom_byte_mask;
else
search->rom_no[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
_onewire_write_bit(pin, search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0) {
rom_byte_number++;
rom_byte_mask = 1;
}
}
} while (rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65)) {
// search successful so set last_discrepancy,last_device_found,search_result
search->last_discrepancy = last_zero;
// check for last device
if (search->last_discrepancy == 0)
search->last_device_found = true;
search_result = 1;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !search->rom_no[0]) {
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
} else {
addr = 0;
for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) {
addr = (addr << 8) | search->rom_no[rom_byte_number];
}
//printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr);
}
return addr;
}
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
//
#ifdef ONEWIRE_CRC8_TABLE
// This table comes from Dallas sample code where it is freely reusable,
// though Copyright (c) 2000 Dallas Semiconductor Corporation
static const uint8_t dscrc_table[] = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
//
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
// and the registers. (note: this might better be done without to
// table, it would probably be smaller and certainly fast enough
// compared to all those delayMicrosecond() calls. But I got
// confused, so I use this table from the examples.)
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
crc = dscrc_table[crc ^ *data++];
return crc;
}
#else
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but much smaller, than the lookup table.
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
{
uint8_t inbyte = *data++;
for (int i = 8; i; i--)
{
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
#endif /* ONEWIRE_CRC8_TABLE */
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return 1, iff the CRC matches.
bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv)
{
uint16_t crc = ~onewire_crc16(input, len, crc_iv);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv)
{
uint16_t crc = crc_iv;
static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
uint16_t i;
for (i = 0; i < len; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
return crc;
}

View File

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

View File

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

View File

@@ -4,12 +4,15 @@
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "socket_lock.h"
#include "board_config.h"
// NEW:
#include "storage_service.h"
#define NVS_NAMESPACE "socket_lock"
#define NVS_OPERATING_TIME "op_time"
#define NVS_BREAK_TIME "break_time"
@@ -27,20 +30,68 @@
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 break_time = 1000;
static bool detection_high;
static bool detection_high = false;
static uint8_t retry_count = 5;
static socket_lock_status_t status;
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)
{
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)
{
uint32_t notification;
(void)param;
uint32_t notification;
TickType_t previous_tick = 0;
uint8_t attempt = 0;
while (true) {
if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY)) {
if (notification & (LOCK_BIT | UNLOCK_BIT)) {
while (true)
{
if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY))
{
if (notification & (LOCK_BIT | UNLOCK_BIT))
{
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_b_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(operating_time));
if (!is_locked()) {
if (!is_locked())
{
ESP_LOGI(TAG, "Unlock OK");
status = SOCKED_LOCK_STATUS_IDLE;
} else {
if (attempt > 1) {
}
else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not unlocked yet, repeating...");
attempt--;
xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits);
} else {
}
else
{
ESP_LOGE(TAG, "Not unlocked");
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_b_gpio, 0);
} else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT)) {
if (notification & LOCK_BIT) {
}
else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT))
{
if (notification & LOCK_BIT)
{
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); // delay before first lock attempt
}
gpio_set_level(board_config.socket_lock_a_gpio, 1);
gpio_set_level(board_config.socket_lock_b_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(operating_time));
if (is_locked()) {
if (is_locked())
{
ESP_LOGI(TAG, "Lock OK");
status = SOCKED_LOCK_STATUS_IDLE;
} else {
if (attempt > 1) {
}
else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not locked yet, repeating...");
attempt--;
xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits);
} else {
}
else
{
ESP_LOGE(TAG, "Not locked");
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;
if (delay_tick < pdMS_TO_TICKS(break_time)) {
if (delay_tick < pdMS_TO_TICKS(break_time))
{
vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick);
}
previous_tick = xTaskGetTickCount();
@@ -125,20 +198,43 @@ static void socket_lock_task_func(void* param)
}
}
// -----------------------------------------------------------------------------
// Init / API pública
// -----------------------------------------------------------------------------
void socket_lock_init(void)
{
if (board_config.socket_lock) {
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs));
if (!board_config.socket_lock)
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;
if (nvs_get_u8(nvs, NVS_DETECTION_HIGH, &u8) == ESP_OK) {
detection_high = u8;
e = storage_get_u16_sync(NVS_NAMESPACE, NVS_BREAK_TIME, &u16, STORE_TO);
if (e == ESP_OK) break_time = u16;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_BREAK_TIME, esp_err_to_name(e));
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_RETRY_COUNT, &u8, STORE_TO);
if (e == ESP_OK) retry_count = u8;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_RETRY_COUNT, esp_err_to_name(e));
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_DETECTION_HIGH, &u8, STORE_TO);
if (e == ESP_OK) detection_high = (u8 != 0);
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_DETECTION_HIGH, esp_err_to_name(e));
}
gpio_config_t io_conf = {};
@@ -151,8 +247,7 @@ void socket_lock_init(void)
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);
}
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 2, &socket_lock_task);
}
bool socket_lock_is_detection_high(void)
@@ -164,8 +259,11 @@ void socket_lock_set_detection_high(bool _detection_high)
{
detection_high = _detection_high;
nvs_set_u8(nvs, NVS_DETECTION_HIGH, detection_high);
nvs_commit(nvs);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_DETECTION_HIGH, detection_high ? 1 : 0);
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)
@@ -175,15 +273,22 @@ uint16_t socket_lock_get_operating_time(void)
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");
return ESP_ERR_INVALID_ARG;
}
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;
}
@@ -195,8 +300,12 @@ uint8_t socket_lock_get_retry_count(void)
void socket_lock_set_retry_count(uint8_t _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)
@@ -206,15 +315,22 @@ uint16_t socket_lock_get_break_time(void)
esp_err_t socket_lock_set_break_time(uint16_t _break_time)
{
if (_break_time < board_config.socket_lock_min_break_time) {
ESP_LOGE(TAG, "Operating time out of range");
if (_break_time < board_config.socket_lock_min_break_time)
{
ESP_LOGE(TAG, "Break time out of range");
return ESP_ERR_INVALID_ARG;
}
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;
}

View File

@@ -38,7 +38,7 @@ void temp_sensor_init(void)
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)

View File

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

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