Compare commits
3 Commits
4820d9111e
...
023644a887
| Author | SHA1 | Date | |
|---|---|---|---|
| 023644a887 | |||
| 82fa194bd8 | |||
| e6e2622a95 |
209
README.md
209
README.md
@@ -1,85 +1,180 @@
|
||||

|
||||
# 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:
|
||||
|
||||

|
||||
[](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
|
||||
|
||||

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

|
||||
### 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)
|
||||
|
||||

|
||||
### 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.
|
||||
|
||||

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

|
||||
- `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):
|
||||
|
||||

|
||||
```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.
|
||||
|
||||
@@ -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=
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,260 +28,422 @@ 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 ===== */
|
||||
#define NVS_NAMESPACE "auth"
|
||||
#define NVS_TAG_PREFIX "tag_"
|
||||
#define NVS_TAG_COUNT_KEY "count"
|
||||
#define NVS_MODE_KEY "mode" // uint8_t
|
||||
/* ===== 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");
|
||||
}
|
||||
|
||||
auth_mode_event_data_t evt = { .mode = s_mode };
|
||||
// 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 };
|
||||
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);
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
waiting_for_registration = false;
|
||||
case AUTH_MODE_LOCAL_RFID:
|
||||
{
|
||||
if (waiting_for_registration)
|
||||
{
|
||||
waiting_for_registration = false;
|
||||
|
||||
if (auth_add_tag(tag)) {
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
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_LOGW(TAG, "Failed to register tag: %s", tag);
|
||||
}
|
||||
return;
|
||||
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_LOGD(TAG, "Tag registered: %s", tag);
|
||||
}
|
||||
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
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);
|
||||
break;
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case AUTH_MODE_OCPP_RFID: {
|
||||
// Não decide localmente. Pede validação ao OCPP.
|
||||
auth_tag_verify_event_t rq = {0};
|
||||
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
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_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:
|
||||
{
|
||||
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_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];
|
||||
}
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -2,5 +2,5 @@ set(srcs
|
||||
"board_config.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
PRIV_REQUIRES nvs_flash
|
||||
PRIV_REQUIRES
|
||||
INCLUDE_DIRS "include")
|
||||
@@ -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);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES freertos
|
||||
)
|
||||
@@ -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.
|
||||
@@ -1,8 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS = .
|
||||
|
||||
ifdef CONFIG_IDF_TARGET_ESP8266
|
||||
COMPONENT_DEPENDS = esp8266 freertos
|
||||
else
|
||||
COMPONENT_DEPENDS = freertos
|
||||
endif
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_available = true; // default
|
||||
nvs_set_u8(nvs, "available", (uint8_t)is_available);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Missing 'available' -> default=true (persisted).");
|
||||
}
|
||||
|
||||
// Enabled (persist)
|
||||
if (nvs_get_u8(nvs, "enabled", &u8_bool) == ESP_OK && u8_bool <= 1)
|
||||
{
|
||||
is_enabled = (u8_bool != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_enabled = true; // default
|
||||
nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
|
||||
}
|
||||
|
||||
// Save to NVS if needed
|
||||
if (needs_commit)
|
||||
{
|
||||
err = nvs_commit(nvs);
|
||||
if (err == ESP_OK)
|
||||
if (wanted && !board_config.proximity)
|
||||
{
|
||||
ESP_LOGD(TAG, "Configuration committed to NVS.");
|
||||
// NVS dizia 1, mas HW não suporta -> runtime false e persistir 0
|
||||
socket_outlet = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist socket_outlet=0 (capability mismatch): %s",
|
||||
esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err));
|
||||
socket_outlet = wanted;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_outlet = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist socket_outlet default=0: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// RCM (persisted) + capability gate
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "rcm", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
bool wanted = (u8 != 0);
|
||||
|
||||
if (wanted && !board_config.rcm)
|
||||
{
|
||||
rcm = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist rcm=0 (capability mismatch): %s",
|
||||
esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "rcm requested but HW has no RCM -> forcing false (sync persisted)");
|
||||
}
|
||||
else
|
||||
{
|
||||
rcm = wanted;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rcm = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist rcm default=0: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid rcm (%s) -> default=false (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Temp threshold (persisted)
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "temp_threshold", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 >= 40 && u8 <= 80)
|
||||
{
|
||||
temp_threshold = u8;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_threshold = 60;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "temp_threshold", temp_threshold, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist temp_threshold=%u: %s",
|
||||
(unsigned)temp_threshold, esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Invalid/missing temp_threshold (%s) -> default=60 (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Availability (persisted) [0/1]
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "available", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
is_available = (u8 != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_available = true;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "available", 1, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist available=1: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid 'available' (%s) -> default=true (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Enabled (persisted) [0/1]
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "enabled", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
is_enabled = (u8 != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_enabled = true;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "enabled", 1, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist enabled=1: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid 'enabled' (%s) -> default=true (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Flush explícito no boot:
|
||||
// - ajuda a garantir commit determinístico antes do resto do sistema avançar
|
||||
// - mantém-se útil mesmo com setters sync se o teu storage ainda estiver com debounce interno
|
||||
esp_err_t fe = storage_flush_sync(wr_to);
|
||||
if (fe != ESP_OK)
|
||||
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Charging current getters/setters
|
||||
// ========================
|
||||
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);
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
// 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");
|
||||
}
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// ----------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
// Ainda há erro visível (holdoff). Arma deadline 1x.
|
||||
if (clear_deadline == 0)
|
||||
{
|
||||
clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
|
||||
old_vis = visible_bits;
|
||||
|
||||
raw_bits = 0;
|
||||
visible_bits = 0;
|
||||
clear_deadline = 0;
|
||||
error_cleared = false;
|
||||
|
||||
new_vis = visible_bits;
|
||||
changed = old_vis ^ new_vis;
|
||||
post = (changed != 0);
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (post)
|
||||
{
|
||||
evse_error_post_event(new_vis, changed);
|
||||
}
|
||||
}
|
||||
|
||||
bool evse_error_is_active(void) {
|
||||
return error_bits != 0;
|
||||
uint32_t evse_get_error(void)
|
||||
{
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
uint32_t val = visible_bits;
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
return val;
|
||||
}
|
||||
|
||||
uint32_t evse_error_get_bits(void) {
|
||||
return error_bits;
|
||||
bool evse_error_is_active(void)
|
||||
{
|
||||
return evse_get_error() != 0;
|
||||
}
|
||||
|
||||
void evse_error_reset_flag(void) {
|
||||
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);
|
||||
}
|
||||
|
||||
bool evse_error_cleared_flag(void) {
|
||||
return error_cleared;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// components/evse/evse_fsm.c
|
||||
#include "evse_fsm.h"
|
||||
#include "evse_api.h"
|
||||
#include "evse_pilot.h"
|
||||
@@ -17,193 +18,245 @@ 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) {
|
||||
case EVSE_STATE_A:
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
ac_relay_set_state(false);
|
||||
pilot_set_level(state == EVSE_STATE_A);
|
||||
if (board_config.socket_lock && socket_outlet) {
|
||||
socket_lock_set_locked(false);
|
||||
}
|
||||
break;
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
ac_relay_set_state(false);
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
pilot_set_level(true);
|
||||
ac_relay_set_state(false);
|
||||
if (board_config.socket_lock && socket_outlet) {
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
// A → pilot alto (+12V), E/F → pilot OFF
|
||||
pilot_set_level(state == EVSE_STATE_A);
|
||||
|
||||
if (rcm_test()) {
|
||||
//ESP_LOGI(TAG, "RCM self test passed");
|
||||
} else {
|
||||
//ESP_LOGW(TAG, "RCM self test failed");
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B2:
|
||||
pilot_set_amps(MIN(current, cable_max_current));
|
||||
ac_relay_set_state(false);
|
||||
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;
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(false);
|
||||
}
|
||||
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!
|
||||
break;
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
pilot_set_level(true);
|
||||
ac_relay_set_state(false);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
|
||||
(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));
|
||||
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);
|
||||
|
||||
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) {
|
||||
case EVSE_STATE_A:
|
||||
if (!available) {
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
} else if (pilot_voltage == PILOT_VOLTAGE_9) {
|
||||
evse_set_state(EVSE_STATE_B1);
|
||||
}
|
||||
switch (curr)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
}
|
||||
else if (pilot_voltage == PILOT_VOLTAGE_9)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_B1);
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pilot_voltage)
|
||||
{
|
||||
case PILOT_VOLTAGE_12:
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
if (!available) {
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case PILOT_VOLTAGE_9:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
|
||||
break;
|
||||
|
||||
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) {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
case PILOT_VOLTAGE_6:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_E:
|
||||
// Estado elétrico grave: só reset manual
|
||||
case PILOT_VOLTAGE_3:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_F:
|
||||
// Fault: só sai se disponível e sem erro
|
||||
if (available && evse_get_error() == 0) {
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_D1:
|
||||
case EVSE_STATE_C2:
|
||||
case EVSE_STATE_D2:
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
if (curr == EVSE_STATE_C2)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_C1);
|
||||
}
|
||||
else if (curr == EVSE_STATE_D2)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_D1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pilot_voltage)
|
||||
{
|
||||
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:
|
||||
// ✅ 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:
|
||||
if (available && evse_get_error() == 0)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; // Wh
|
||||
static uint32_t charging_time_limit = 0; // seconds
|
||||
static uint16_t under_power_limit = 0; // W
|
||||
|
||||
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 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); }
|
||||
|
||||
// ========================
|
||||
// Default (persistent) limits
|
||||
// ========================
|
||||
// ---------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
static uint32_t default_consumption_limit = 0;
|
||||
static uint32_t default_charging_time_limit = 0;
|
||||
static uint16_t default_under_power_limit = 0;
|
||||
void evse_limits_check_defaults(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
bool needs_flush = false;
|
||||
|
||||
// ========================
|
||||
// Limit status flag
|
||||
// ========================
|
||||
uint32_t u32 = 0;
|
||||
uint16_t u16 = 0;
|
||||
|
||||
bool evse_get_limit_reached(void) {
|
||||
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);
|
||||
consumption_limit = value;
|
||||
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);
|
||||
charging_time_limit = value;
|
||||
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);
|
||||
under_power_limit = value;
|
||||
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())) {
|
||||
return;
|
||||
if (under_power_limit != value)
|
||||
{
|
||||
under_power_limit = value;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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}, "
|
||||
|
||||
@@ -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"
|
||||
@@ -13,176 +12,232 @@
|
||||
#include "adc121s021_dma.h"
|
||||
#include "board_config.h"
|
||||
|
||||
#define PILOT_PWM_TIMER LEDC_TIMER_0
|
||||
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
|
||||
#define PILOT_PWM_MAX_DUTY 1023
|
||||
#define PILOT_PWM_TIMER LEDC_TIMER_0
|
||||
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
|
||||
#define PILOT_PWM_MAX_DUTY 1023
|
||||
|
||||
#define NUM_PILOT_SAMPLES 100
|
||||
#define MAX_SAMPLE_ATTEMPTS 1000
|
||||
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
|
||||
// --- Configuração de amostragem do Pilot ---
|
||||
#define NUM_PILOT_SAMPLES 100
|
||||
#define MAX_SAMPLE_ATTEMPTS 1000
|
||||
|
||||
// ADC121S021 setup
|
||||
#define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware!
|
||||
#define ADC121_MAX 4095 // 12 bits
|
||||
#define PILOT_SAMPLE_DELAY_US 10
|
||||
|
||||
// Percentagem para descartar extremos superior/inferior (ruído)
|
||||
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
|
||||
|
||||
// ADC referência
|
||||
#define ADC121_VREF_MV 3300
|
||||
#define ADC121_MAX 4095
|
||||
|
||||
static const char *TAG = "evse_pilot";
|
||||
|
||||
// Memoização de estado para evitar comandos/logs desnecessários
|
||||
static int last_pilot_level = -1;
|
||||
static uint32_t last_pwm_duty = 0;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
|
||||
static uint32_t last_pwm_duty = 0;
|
||||
|
||||
// ---------------------
|
||||
// Helpers internos
|
||||
// ---------------------
|
||||
static int adc_raw_to_mv(uint16_t raw)
|
||||
{
|
||||
return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
|
||||
}
|
||||
|
||||
static int compare_uint16(const void *a, const void *b)
|
||||
{
|
||||
uint16_t va = *(const uint16_t *)a;
|
||||
uint16_t vb = *(const uint16_t *)b;
|
||||
if (va < vb) return -1;
|
||||
if (va > vb) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// Inicialização PWM + ADC
|
||||
// ---------------------
|
||||
void pilot_init(void)
|
||||
{
|
||||
// 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,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
.speed_mode = PILOT_PWM_SPEED_MODE,
|
||||
.timer_num = PILOT_PWM_TIMER,
|
||||
.duty_resolution = PILOT_PWM_DUTY_RES,
|
||||
.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,
|
||||
.timer_sel = PILOT_PWM_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = board_config.pilot_pwm_gpio,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
.channel = PILOT_PWM_CHANNEL,
|
||||
.timer_sel = PILOT_PWM_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = board_config.pilot_pwm_gpio,
|
||||
.duty = 0,
|
||||
.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: 6–80 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)",
|
||||
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,82 +1,220 @@
|
||||
/*
|
||||
* 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 bool last_session_valid = false;
|
||||
static uint32_t session_counter = 0;
|
||||
|
||||
void evse_session_init(void) {
|
||||
session_start_tick = 0;
|
||||
watt_seconds = 0;
|
||||
last_session_valid = false;
|
||||
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_start(void) {
|
||||
session_start_tick = xTaskGetTickCount();
|
||||
watt_seconds = 0;
|
||||
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick);
|
||||
void evse_session_init(void)
|
||||
{
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
session_start_tick = 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_end(void) {
|
||||
if (session_start_tick == 0) {
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
start_tick = session_start_tick;
|
||||
start_us = session_start_us;
|
||||
w_us = watt_microseconds;
|
||||
id = session_counter;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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;
|
||||
out->duration_s = duration_s;
|
||||
out->energy_wh = energy_wh;
|
||||
out->avg_power_w = avg_power;
|
||||
out->is_current = true;
|
||||
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;
|
||||
out->is_current = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (last_session_valid) {
|
||||
*out = last_session;
|
||||
if (last_valid)
|
||||
{
|
||||
*out = last_copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_C1:
|
||||
case EVSE_STATE_C2: 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_B1:
|
||||
case EVSE_STATE_B2:
|
||||
return EVSE_STATE_EVENT_WAITING;
|
||||
|
||||
case EVSE_STATE_C1:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 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;
|
||||
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)) {
|
||||
start_session = true;
|
||||
}
|
||||
// se saiu de charging para qualquer outro
|
||||
else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) {
|
||||
end_session = true;
|
||||
}
|
||||
current_state = new_state;
|
||||
changed = true;
|
||||
}
|
||||
prev_state = current_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;
|
||||
}
|
||||
// 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -5,41 +5,45 @@
|
||||
#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)
|
||||
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
|
||||
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
|
||||
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
|
||||
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
|
||||
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
|
||||
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
|
||||
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
|
||||
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
|
||||
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
|
||||
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
|
||||
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
|
||||
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
|
||||
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
|
||||
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
|
||||
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
|
||||
|
||||
// Inicialização do módulo de erros
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
@@ -2,65 +2,43 @@
|
||||
#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_t;
|
||||
typedef enum
|
||||
{
|
||||
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);
|
||||
void pilot_init(void);
|
||||
|
||||
/**
|
||||
* @brief Define o nível do Pilot: +12V ou -12V
|
||||
*
|
||||
* @param level true = +12V, false = -12V
|
||||
*/
|
||||
void pilot_set_level(bool level);
|
||||
/**
|
||||
* @brief Define o pilot em modo DC.
|
||||
*
|
||||
* @param high true = nível alto (+12V)
|
||||
* false = nível baixo (-12V)
|
||||
*/
|
||||
void pilot_set_level(bool high);
|
||||
|
||||
/**
|
||||
* @brief Ativa o PWM do Pilot com corrente limitada
|
||||
*
|
||||
* @param amps Corrente em ampères (ex: 16 = 16A)
|
||||
*/
|
||||
void pilot_set_amps(uint16_t amps);
|
||||
void pilot_set_amps(uint16_t amps);
|
||||
|
||||
/**
|
||||
* @brief Mede o nível de tensão do Pilot e detecta -12V
|
||||
*
|
||||
* @param up_voltage Valor categórico da tensão positiva
|
||||
* @param down_voltage_n12 true se o nível negativo atingir -12V
|
||||
*/
|
||||
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
|
||||
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
|
||||
|
||||
/**
|
||||
* @brief Retorna o estado lógico atual do Pilot (nível alto = +12V)
|
||||
*
|
||||
* @return true se nível atual for +12V, false se for -12V
|
||||
*/
|
||||
bool pilot_get_state(void);
|
||||
bool pilot_get_state(void);
|
||||
|
||||
/**
|
||||
* @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)
|
||||
} pilot_voltage_cache_t;
|
||||
typedef struct
|
||||
{
|
||||
uint16_t high_mv;
|
||||
uint16_t low_mv;
|
||||
} pilot_voltage_cache_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,90 +6,68 @@
|
||||
#include "evse_events.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// ============================
|
||||
// EVSE Pilot Signal States
|
||||
// ============================
|
||||
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)
|
||||
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
|
||||
EVSE_STATE_C2, // Charging Active (6V, Relay On)
|
||||
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
|
||||
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
|
||||
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
|
||||
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
|
||||
} evse_state_t;
|
||||
|
||||
typedef enum {
|
||||
EVSE_STATE_A, // EV Not Connected (12V)
|
||||
EVSE_STATE_B1, // EV Connected (9V, Not Authorized)
|
||||
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
|
||||
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
|
||||
EVSE_STATE_C2, // Charging Active (6V, Relay On)
|
||||
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
|
||||
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
|
||||
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
|
||||
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
|
||||
} evse_state_t;
|
||||
// Initialization
|
||||
void evse_state_init(void);
|
||||
void evse_state_tick(void);
|
||||
|
||||
// ============================
|
||||
// Initialization
|
||||
// ============================
|
||||
// State Access & Control
|
||||
evse_state_t evse_get_state(void);
|
||||
void evse_set_state(evse_state_t state);
|
||||
const char *evse_state_to_str(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Initializes the EVSE state machine and default state.
|
||||
*/
|
||||
void evse_state_init(void);
|
||||
// ---------------------------
|
||||
// State Evaluation Helpers
|
||||
// ---------------------------
|
||||
|
||||
/**
|
||||
* @brief Periodic tick for state handling (optional hook).
|
||||
*/
|
||||
void evse_state_tick(void);
|
||||
/**
|
||||
* @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);
|
||||
|
||||
// ============================
|
||||
// State Access & Control
|
||||
// ============================
|
||||
/**
|
||||
* @brief True se o EVSE está a fornecer energia (relé ON).
|
||||
* Estados com energia: C2 e D2.
|
||||
*
|
||||
* Nota: isto substitui a antiga interpretação “C1/C2”.
|
||||
*/
|
||||
bool evse_state_is_charging(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Returns the current EVSE state.
|
||||
*/
|
||||
evse_state_t evse_get_state(void);
|
||||
/**
|
||||
* @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 Sets the current EVSE state and emits a change event if needed.
|
||||
*/
|
||||
void evse_set_state(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 Converts the state enum into a human-readable string.
|
||||
*/
|
||||
const char* evse_state_to_str(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);
|
||||
|
||||
// ============================
|
||||
// State Evaluation Helpers
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* @brief True if EV is in an active session (B2, C1, C2).
|
||||
*/
|
||||
bool evse_state_is_session(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief True if EV is actively charging (C1, C2).
|
||||
*/
|
||||
bool evse_state_is_charging(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief True if EV is physically plugged in (B1 and beyond).
|
||||
*/
|
||||
bool evse_state_is_plugged(evse_state_t state);
|
||||
|
||||
// ============================
|
||||
// Authorization Control
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* @brief Sets whether the EV is authorized to charge.
|
||||
*/
|
||||
void evse_state_set_authorized(bool authorized);
|
||||
|
||||
/**
|
||||
* @brief Gets whether the EV is currently authorized.
|
||||
*/
|
||||
bool evse_state_get_authorized(void);
|
||||
// Authorization Control
|
||||
void evse_state_set_authorized(bool authorized);
|
||||
bool evse_state_get_authorized(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,9 +41,7 @@ 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
|
||||
@@ -48,87 +50,117 @@ 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)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK)
|
||||
// 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_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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,11 +168,22 @@ static void evse_link_rx_task(void *arg)
|
||||
// Initialize EVSE-Link component
|
||||
void evse_link_init(void)
|
||||
{
|
||||
load_link_config();
|
||||
// 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,17 +192,13 @@ 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)
|
||||
@@ -167,6 +206,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
|
||||
// CRC covers DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
if (len > 0 && payload != NULL) {
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
}
|
||||
|
||||
// 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:
|
||||
rx_buf[rx_pos++] = b;
|
||||
if (rx_pos >= (rx_len - 1)) { // all payload bytes read
|
||||
if (rx_pos < EVSE_LINK_MAX_PAYLOAD) {
|
||||
rx_buf[rx_pos++] = b;
|
||||
}
|
||||
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;
|
||||
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
|
||||
temp_len += rx_len - 1;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,94 +7,142 @@
|
||||
#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";
|
||||
|
||||
// Link commands
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização a slave
|
||||
|
||||
// payload lengths (exclui byte de opcode)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ]
|
||||
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ]
|
||||
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1
|
||||
|
||||
// polling / heartbeat timers interval
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
TimerHandle_t timer;
|
||||
TickType_t interval;
|
||||
TickType_t interval;
|
||||
} timer_def_t;
|
||||
static timer_def_t poll_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
static timer_def_t hb_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
static timer_def_t poll_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
|
||||
static timer_def_t hb_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
|
||||
|
||||
// --- 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;
|
||||
uint8_t slave_id = evt->slave_id;
|
||||
uint16_t max_current = evt->max_current;
|
||||
|
||||
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;
|
||||
}
|
||||
bool charging = payload[1] != 0;
|
||||
uint16_t hw_max = payload[2] | (payload[3] << 8);
|
||||
uint16_t runtime = payload[4] | (payload[5] << 8);
|
||||
bool charging = payload[1] != 0;
|
||||
uint16_t hw_max = payload[2] | (payload[3] << 8);
|
||||
uint16_t runtime = payload[4] | (payload[5] << 8);
|
||||
|
||||
ESP_LOGI(TAG, "Heartbeat from 0x%02X: charging=%d hw_max=%uA runtime=%uA",
|
||||
src, charging, hw_max, runtime);
|
||||
|
||||
loadbalancer_slave_status_event_t status = {
|
||||
.slave_id = src,
|
||||
.charging = charging,
|
||||
.slave_id = src,
|
||||
.charging = charging,
|
||||
.hw_max_current = (float)hw_max,
|
||||
.runtime_current = (float)runtime, // corrente real medida no slave
|
||||
.timestamp_us = esp_timer_get_time()
|
||||
};
|
||||
.runtime_current = (float)runtime, // corrente real medida no slave
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_STATUS,
|
||||
&status, sizeof(status), portMAX_DELAY);
|
||||
|
||||
// Enviar ACK de volta
|
||||
uint8_t ack[] = { CMD_HEARTBEAT_ACK };
|
||||
uint8_t ack[] = {CMD_HEARTBEAT_ACK};
|
||||
evse_link_send(src, ack, sizeof(ack));
|
||||
ESP_LOGD(TAG, "Sent HEARTBEAT_ACK to 0x%02X", src);
|
||||
break;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "button/button.c" "button/button_obj.cpp"
|
||||
INCLUDE_DIRS "button/include"
|
||||
REQUIRES "driver")
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := ./button/include
|
||||
COMPONENT_SRCDIRS := ./button
|
||||
6
components/led/CMakeLists.txt
Executable file
6
components/led/CMakeLists.txt
Executable 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
78
components/led/include/led.h
Executable 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_ */
|
||||
34
components/led/include/ledc_driver.h
Executable file
34
components/led/include/ledc_driver.h
Executable 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 (0–255 por cor)
|
||||
*
|
||||
* @param red Intensidade do vermelho (0–255)
|
||||
* @param green Intensidade do verde (0–255)
|
||||
* @param blue Intensidade do azul (0–255)
|
||||
*
|
||||
* @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
598
components/led/src/led.c
Executable 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 0–100
|
||||
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 (0–255)
|
||||
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
129
components/led/src/ledc_driver.c
Executable 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);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
set(srcs "ledc_driver.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "driver")
|
||||
@@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
COMPONENT_SRCDIRS := .
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,86 +289,110 @@ 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,
|
||||
&evt, sizeof(evt), portMAX_DELAY);
|
||||
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
&evt, sizeof(evt), portMAX_DELAY);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Só persiste se mudou
|
||||
if (value == max_grid_current)
|
||||
{
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
max_grid_current = value;
|
||||
persist_max_grid_current(value);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t load_balancing_get_max_grid_current(void)
|
||||
{
|
||||
return max_grid_current;
|
||||
}
|
||||
uint8_t load_balancing_get_max_grid_current(void) { return max_grid_current; }
|
||||
bool loadbalancer_is_enabled(void) { return loadbalancer_enabled; }
|
||||
|
||||
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,
|
||||
.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);
|
||||
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};
|
||||
(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));
|
||||
}
|
||||
@@ -3,56 +3,45 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define LOGGER_SERIAL_BIT BIT0
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Logger event group LOGGER_SERIAL_BIT
|
||||
*
|
||||
*/
|
||||
extern EventGroupHandle_t logger_event_group;
|
||||
#define LOGGER_SERIAL_BIT BIT0
|
||||
|
||||
/**
|
||||
* @brief Initialize logger
|
||||
*
|
||||
*/
|
||||
void logger_init(void);
|
||||
extern EventGroupHandle_t logger_event_group;
|
||||
|
||||
/**
|
||||
* @brief Print
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
void logger_print(const char* str);
|
||||
void logger_init(void);
|
||||
|
||||
/**
|
||||
* @brief Print va
|
||||
*
|
||||
* @param str
|
||||
* @param l
|
||||
* @return int
|
||||
*/
|
||||
int logger_vprintf(const char* str, va_list l);
|
||||
void logger_print(const char *str);
|
||||
|
||||
/**
|
||||
* @brief Get entries count
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t logger_count(void);
|
||||
int logger_vprintf(const char *fmt, va_list args);
|
||||
|
||||
/**
|
||||
* @brief Read line from index, set index for reading next entry
|
||||
*
|
||||
* @param index
|
||||
* @param str
|
||||
* @param v
|
||||
* @return true When has next entry
|
||||
* @return false When no entry left
|
||||
*/
|
||||
bool logger_read(uint16_t *index, char **str, uint16_t* len);
|
||||
uint16_t logger_count(void);
|
||||
|
||||
// opcional: quantas mensagens foram dropadas por contenção de mutex
|
||||
uint32_t logger_dropped_count(void);
|
||||
|
||||
/**
|
||||
* ⚠️ 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_ */
|
||||
@@ -4,21 +4,31 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
uint16_t size;
|
||||
uint16_t count;
|
||||
uint8_t* data;
|
||||
uint8_t* append;
|
||||
} output_buffer_t;
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
output_buffer_t* output_buffer_create(uint16_t size);
|
||||
typedef struct
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t count;
|
||||
uint8_t *data;
|
||||
uint8_t *append;
|
||||
} output_buffer_t;
|
||||
|
||||
void output_buffer_delete(output_buffer_t* buffer);
|
||||
output_buffer_t *output_buffer_create(uint16_t size);
|
||||
|
||||
void output_buffer_append_buf(output_buffer_t* buffer, const char* buf, uint16_t len);
|
||||
void output_buffer_delete(output_buffer_t *buffer);
|
||||
|
||||
void output_buffer_append_str(output_buffer_t* buffer, const char* str);
|
||||
void output_buffer_append_buf(output_buffer_t *buffer, const char *buf, uint16_t len);
|
||||
|
||||
bool output_buffer_read(output_buffer_t* buffer, uint16_t *index, char **str, uint16_t* len);
|
||||
void output_buffer_append_str(output_buffer_t *buffer, const char *str);
|
||||
|
||||
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OUTPUT_BUFFER_H_ */
|
||||
@@ -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
|
||||
#define LOG_BUFFER_SIZE 6096 // tamanho total do buffer circular
|
||||
#define MAX_LOG_SIZE 256 // ✅ reduzir stack/CPU; era 512
|
||||
|
||||
|
||||
static SemaphoreHandle_t mutex;
|
||||
|
||||
static output_buffer_t * buffer = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
void logger_print(const char* str)
|
||||
uint32_t logger_dropped_count(void)
|
||||
{
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
return s_dropped;
|
||||
}
|
||||
|
||||
output_buffer_append_str(buffer, str);
|
||||
xEventGroupSetBits(logger_event_group, 0xFF);
|
||||
void logger_print(const char *str)
|
||||
{
|
||||
if (!str || !mutex || !buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool logger_read(uint16_t* index, char** str, uint16_t* 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;
|
||||
}
|
||||
|
||||
@@ -1,86 +1,217 @@
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "output_buffer.h"
|
||||
|
||||
|
||||
output_buffer_t* output_buffer_create(uint16_t size)
|
||||
output_buffer_t *output_buffer_create(uint16_t size)
|
||||
{
|
||||
output_buffer_t* buffer = (output_buffer_t*)malloc(sizeof(output_buffer_t));
|
||||
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)
|
||||
void output_buffer_delete(output_buffer_t *buffer)
|
||||
{
|
||||
free((void*)buffer->data);
|
||||
free((void*)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)
|
||||
void output_buffer_append_buf(output_buffer_t *buffer, const char *str, uint16_t len)
|
||||
{
|
||||
if (((buffer->append - buffer->data) + sizeof(uint16_t) + len) >= buffer->size) {
|
||||
//rotate buffer
|
||||
uint8_t* pos = buffer->data;
|
||||
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++;
|
||||
}
|
||||
|
||||
void output_buffer_append_str(output_buffer_t* buffer, const char* str)
|
||||
void output_buffer_append_str(output_buffer_t *buffer, const char *str)
|
||||
{
|
||||
output_buffer_append_buf(buffer, str, strlen(str));
|
||||
if (!buffer || !str)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// A API pública em logger.c já limita o tamanho, mas aqui fazemos
|
||||
// um clamp defensivo para o caso de uso direto.
|
||||
if (len > UINT16_MAX)
|
||||
{
|
||||
len = UINT16_MAX;
|
||||
}
|
||||
|
||||
output_buffer_append_buf(buffer, str, (uint16_t)len);
|
||||
}
|
||||
|
||||
bool output_buffer_read(output_buffer_t* buffer, uint16_t* index, char** str, uint16_t* len)
|
||||
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len)
|
||||
{
|
||||
if (*index > buffer->count) {
|
||||
if (!buffer || !buffer->data || !index || !str || !len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*index > buffer->count)
|
||||
{
|
||||
*index = buffer->count;
|
||||
}
|
||||
|
||||
bool has_next = false;
|
||||
|
||||
if (*index < buffer->count) {
|
||||
uint8_t* pos = buffer->data;
|
||||
uint16_t current = 0;
|
||||
|
||||
while (current != *index) {
|
||||
uint16_t entry_len;
|
||||
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t));
|
||||
pos += entry_len + sizeof(uint16_t);
|
||||
current++;
|
||||
}
|
||||
|
||||
memcpy((void*)len, (void*)pos, sizeof(uint16_t));
|
||||
pos += sizeof(uint16_t);
|
||||
*str = (char*)pos;
|
||||
|
||||
(*index)++;
|
||||
|
||||
has_next = true;
|
||||
if (*index >= buffer->count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return has_next;
|
||||
uint8_t *pos = buffer->data;
|
||||
uint8_t *end = buffer->data + buffer->size;
|
||||
uint16_t current = 0;
|
||||
|
||||
// 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(&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++;
|
||||
}
|
||||
|
||||
// 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)++;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,20 +17,27 @@ 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
|
||||
typedef struct {
|
||||
const char *source; // "GRID" ou "EVSE"
|
||||
float vrms[3]; // Tensão por fase
|
||||
float irms[3]; // Corrente por fase
|
||||
int watt[3]; // Potência ativa por fase
|
||||
float frequency; // Frequência da rede (Hz)
|
||||
float power_factor; // Fator de potência
|
||||
float total_energy; // Energia acumulada (kWh)
|
||||
float vrms[3]; // Tensão por fase
|
||||
float irms[3]; // Corrente por fase
|
||||
int watt[3]; // Potência ativa por fase
|
||||
float frequency; // Frequência da rede (Hz)
|
||||
float power_factor; // Fator de potência
|
||||
float total_energy; // Energia acumulada (kWh)
|
||||
} meter_event_data_t;
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
ESP_LOGD(TAG, "Loaded meter type %u from storage key '%s/%s'", (unsigned)value, STORE_NAMESPACE, key);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
return ESP_OK;
|
||||
// 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_nvs(const char *key, meter_type_t meter_type)
|
||||
static esp_err_t write_meter_model_to_storage(const char *key, meter_type_t meter_type)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = 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,
|
||||
ESP_EVENT_ANY_ID,
|
||||
meter_manager_network_event_handler,
|
||||
NULL);
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char ssid[32];
|
||||
char ssid[33];
|
||||
int rssi;
|
||||
bool auth;
|
||||
} wifi_scan_ap_t;
|
||||
|
||||
@@ -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)
|
||||
return ESP_OK;
|
||||
if (err != ESP_OK)
|
||||
return err;
|
||||
// buffer grande (sem heap): usa o máximo definido no storage_service
|
||||
char tmp[STORAGE_MAX_VALUE_BYTES + 1];
|
||||
memset(tmp, 0, sizeof(tmp));
|
||||
|
||||
if (need == 0)
|
||||
return ESP_OK; // vazio
|
||||
|
||||
if (need > out_sz)
|
||||
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
|
||||
if (e == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
// 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;
|
||||
out[0] = '\0';
|
||||
return ESP_OK;
|
||||
}
|
||||
return nvs_get_str(h, key, out, &need);
|
||||
if (e != ESP_OK)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return e;
|
||||
}
|
||||
|
||||
size_t n = strnlen(tmp, out_sz - 1); // no máximo out_sz-1
|
||||
memcpy(out, tmp, n);
|
||||
out[n] = '\0';
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -96,6 +144,8 @@ static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, si
|
||||
// -----------------------------------------------------------------------------
|
||||
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
(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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(¬ificationOutput);
|
||||
// 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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ~2–3 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_ */
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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_ */
|
||||
@@ -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"
|
||||
|
||||
#define PIN_NUM_CS 5
|
||||
#define SAMPLE_SIZE_BYTES 2
|
||||
#define ADC_BITS 12
|
||||
#define SPI_CLOCK_HZ (6 * 1000 * 1000) // 6 MHz
|
||||
// Pino de chip-select do ADC121S021 (ajusta se necessário)
|
||||
#define PIN_NUM_CS 5
|
||||
|
||||
// ADC é 12-bit, mas transferimos 16 bits via SPI
|
||||
#define ADC_BITS 12
|
||||
|
||||
// Clock SPI: 1 MHz → ~16 µs de transferência por amostra.
|
||||
// Com um pequeno delay entre leituras, 100 amostras ficam em ~2–3 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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, ¬ification, portMAX_DELAY)) {
|
||||
if (notification & (LOCK_BIT | UNLOCK_BIT)) {
|
||||
while (true)
|
||||
{
|
||||
if (xTaskNotifyWait(0x00, 0xff, ¬ification, 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) {
|
||||
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); //delay before first lock attempt
|
||||
}
|
||||
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,34 +198,56 @@ 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));
|
||||
|
||||
gpio_config_t io_conf = {};
|
||||
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_RETRY_COUNT, &u8, STORE_TO);
|
||||
if (e == ESP_OK) retry_count = u8;
|
||||
else if (e != ESP_ERR_NOT_FOUND)
|
||||
ESP_LOGW(TAG, "load %s failed: %s", NVS_RETRY_COUNT, esp_err_to_name(e));
|
||||
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio);
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task);
|
||||
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 = {};
|
||||
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio);
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 2, &socket_lock_task);
|
||||
}
|
||||
|
||||
bool socket_lock_is_detection_high(void)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user