new module

This commit is contained in:
2025-12-09 11:48:31 +00:00
parent 4820d9111e
commit e6e2622a95
98 changed files with 5349 additions and 8607 deletions

View File

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

View File

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

View File

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

View File

@@ -4,4 +4,4 @@ idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src" PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash driver esp_timer PRIV_REQUIRES nvs_flash driver esp_timer
REQUIRES esp_event esp_idf_lib_helpers evse ocpp) REQUIRES esp_event evse ocpp evse_link)

View File

@@ -1,5 +1,3 @@
// components/auth/src/auth.c
#include "auth.h" #include "auth.h"
#include "auth_events.h" #include "auth_events.h"
#include "esp_event.h" #include "esp_event.h"
@@ -9,11 +7,13 @@
#include <esp_log.h> #include <esp_log.h>
#include <string.h> #include <string.h>
#include <strings.h> // <-- necessário para strcasecmp #include <strings.h> // strcasecmp
#include "wiegand_reader.h" #include "wiegand_reader.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "nvs.h" #include "nvs.h"
#include "esp_random.h"
#include "evse_link.h"
#include "evse_link_events.h"
#define MAX_TAGS 50 #define MAX_TAGS 50
@@ -25,63 +25,100 @@ static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN]; static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0; static int tag_count = 0;
static uint32_t s_next_req_id = 1; static uint32_t s_next_req_id = 1;
static bool s_wiegand_started = false; // controla se o Wiegand já foi iniciado
/* ===== NVS keys ===== */ /* ===== NVS keys ===== */
#define NVS_NAMESPACE "auth" #define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_" #define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count" #define NVS_TAG_COUNT_KEY "count"
#define NVS_MODE_KEY "mode" // uint8_t #define NVS_MODE_KEY "mode" // uint8_t
/* ========================= /* =========================
* NVS Persistence (tags) * NVS Persistence (tags)
* ========================= */ * ========================= */
static void load_tags_from_nvs(void) { static void load_tags_from_nvs(void)
{
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) { esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
ESP_LOGW(TAG, "No stored tags in NVS"); if (err != ESP_OK)
{
ESP_LOGW(TAG, "No stored tags in NVS (nvs_open: %s)", esp_err_to_name(err));
return; return;
} }
uint8_t count = 0; uint8_t count = 0;
if (nvs_get_u8(handle, NVS_TAG_COUNT_KEY, &count) != ESP_OK) { err = nvs_get_u8(handle, NVS_TAG_COUNT_KEY, &count);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No tag count key in NVS (nvs_get_u8: %s)", esp_err_to_name(err));
nvs_close(handle); nvs_close(handle);
return; return;
} }
tag_count = 0; tag_count = 0;
for (int i = 0; i < count && i < MAX_TAGS; i++) { for (int i = 0; i < count && i < MAX_TAGS; i++)
{
char key[16]; char key[16];
char tag_buf[AUTH_TAG_MAX_LEN]; char tag_buf[AUTH_TAG_MAX_LEN];
size_t len = sizeof(tag_buf); size_t len = sizeof(tag_buf);
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i); snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
if (nvs_get_str(handle, key, tag_buf, &len) == ESP_OK) { err = nvs_get_str(handle, key, tag_buf, &len);
if (err == ESP_OK)
{
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1); strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++; tag_count++;
} }
else
{
ESP_LOGW(TAG, "Failed to load tag %d from NVS (%s)", i, esp_err_to_name(err));
}
} }
nvs_close(handle); nvs_close(handle);
ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count); ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count);
} }
static void save_tags_to_nvs(void) { static void save_tags_to_nvs(void)
{
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) != ESP_OK) { esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
ESP_LOGE(TAG, "Failed to open NVS to save tags"); if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS to save tags: %s", esp_err_to_name(err));
return; return;
} }
nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count); err = nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
if (err != ESP_OK)
for (int i = 0; i < tag_count; i++) { {
char key[16]; ESP_LOGE(TAG, "nvs_set_u8(count) failed: %s", esp_err_to_name(err));
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i); nvs_close(handle);
nvs_set_str(handle, key, valid_tags[i]); return;
}
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = nvs_set_str(handle, key, valid_tags[i]);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_str(%s) failed: %s", key, esp_err_to_name(err));
nvs_close(handle);
return;
}
}
err = nvs_commit(handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving tags: %s", esp_err_to_name(err));
nvs_close(handle);
return;
} }
nvs_commit(handle);
nvs_close(handle); nvs_close(handle);
ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count); ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count);
} }
@@ -89,95 +126,220 @@ static void save_tags_to_nvs(void) {
/* ========================= /* =========================
* NVS Persistence (mode) * NVS Persistence (mode)
* ========================= */ * ========================= */
static void load_mode_from_nvs(void) { static void load_mode_from_nvs(void)
{
nvs_handle_t h; nvs_handle_t h;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &h) == ESP_OK) { esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_OK)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN; uint8_t u = (uint8_t)AUTH_MODE_OPEN;
if (nvs_get_u8(h, NVS_MODE_KEY, &u) == ESP_OK) { err = nvs_get_u8(h, NVS_MODE_KEY, &u);
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID) s_mode = (auth_mode_t)u; if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
}
else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_get_u8: %s). Default OPEN", esp_err_to_name(err));
} }
nvs_close(h); nvs_close(h);
} else { }
ESP_LOGW(TAG, "No stored auth mode in NVS; default OPEN"); else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_open: %s). Default OPEN", esp_err_to_name(err));
} }
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode)); 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) { static void save_mode_to_nvs(auth_mode_t mode)
{
nvs_handle_t h; nvs_handle_t h;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) { esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode); if (err != ESP_OK)
nvs_commit(h); {
nvs_close(h); ESP_LOGE(TAG, "Failed to open NVS to save auth mode: %s", esp_err_to_name(err));
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode)); return;
} else {
ESP_LOGE(TAG, "Failed to save auth mode to NVS");
} }
err = nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving mode: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
nvs_close(h);
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
} }
/* ========================= /* =========================
* Helpers * Helpers
* ========================= */ * ========================= */
static bool is_tag_valid(const char *tag) { static bool is_tag_valid(const char *tag)
for (int i = 0; i < tag_count; i++) { {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
{
return true; return true;
} }
} }
return false; return false;
} }
/* =========================
* 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)
{
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;
// Só faz sentido em SLAVE; em MASTER este evento não deve aparecer
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_LOGI(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
esp_err_t err = esp_event_post(
AUTH_EVENTS,
AUTH_EVENT_TAG_PROCESSED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to post AUTH_EVENT_TAG_PROCESSED (remote grant): %s",
esp_err_to_name(err));
}
}
/* ========================= /* =========================
* Public API * Public API
* ========================= */ * ========================= */
void auth_init(void) { void auth_init(void)
{
load_mode_from_nvs(); load_mode_from_nvs();
load_tags_from_nvs(); load_tags_from_nvs();
if (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID) { bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand)
{
initWiegand(); initWiegand();
s_wiegand_started = true;
ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode)); ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
} else { }
else
{
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started"); ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
} }
auth_mode_event_data_t evt = { .mode = s_mode }; // Registar bridge para autorizações remotas vindas do EVSE-Link
{
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_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode)); ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
} }
void auth_set_mode(auth_mode_t mode) { void auth_set_mode(auth_mode_t mode)
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID) { {
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID)
{
ESP_LOGW(TAG, "Invalid mode: %d", (int)mode); ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
return; return;
} }
if (mode == s_mode) {
if (mode == s_mode)
{
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode)); ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return; return;
} }
auth_mode_t old = s_mode;
s_mode = mode; s_mode = mode;
save_mode_to_nvs(mode); save_mode_to_nvs(mode);
// Nota: se precisares, aqui podes parar/iniciar o Wiegand consoante o modo. bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (s_mode == AUTH_MODE_OPEN) {
ESP_LOGI(TAG, "Mode set to OPEN"); if (need_wiegand && !s_wiegand_started)
} else { {
ESP_LOGI(TAG, "Mode set to %s; ensure Wiegand reader is running", auth_mode_to_str(s_mode)); ESP_LOGI(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)
{
// Aqui poderias implementar um wiegand_deinit() se o driver o expuser.
ESP_LOGI(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
else
{
ESP_LOGI(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 }; if (s_mode == AUTH_MODE_OPEN)
{
ESP_LOGI(TAG, "Mode set to OPEN");
}
else
{
ESP_LOGI(TAG, "Mode set to %s", auth_mode_to_str(s_mode));
}
auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY);
} }
auth_mode_t auth_get_mode(void) { auth_mode_t auth_get_mode(void)
{
return s_mode; return s_mode;
} }
bool auth_add_tag(const char *tag) { bool auth_add_tag(const char *tag)
if (tag_count >= MAX_TAGS) return false; {
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false; if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN)
if (is_tag_valid(tag)) return true; // já existe return false;
if (tag_count >= MAX_TAGS)
return false;
if (is_tag_valid(tag))
return true; // já existe
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1); strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
@@ -188,11 +350,19 @@ bool auth_add_tag(const char *tag) {
return true; return true;
} }
bool auth_remove_tag(const char *tag) { bool auth_remove_tag(const char *tag)
for (int i = 0; i < tag_count; i++) { {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { if (!tag)
for (int j = i; j < tag_count - 1; j++) { return false;
for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
{
for (int j = i; j < tag_count - 1; j++)
{
strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN); strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN);
valid_tags[j][AUTH_TAG_MAX_LEN - 1] = '\0';
} }
tag_count--; tag_count--;
@@ -204,19 +374,26 @@ bool auth_remove_tag(const char *tag) {
return false; return false;
} }
bool auth_tag_exists(const char *tag) { bool auth_tag_exists(const char *tag)
{
if (!tag)
return false;
return is_tag_valid(tag); return is_tag_valid(tag);
} }
void auth_list_tags(void) { void auth_list_tags(void)
{
ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); ESP_LOGI(TAG, "Registered Tags (%d):", tag_count);
for (int i = 0; i < tag_count; i++) { for (int i = 0; i < tag_count; i++)
{
ESP_LOGI(TAG, "- %s", valid_tags[i]); ESP_LOGI(TAG, "- %s", valid_tags[i]);
} }
} }
void auth_wait_for_tag_registration(void) { void auth_wait_for_tag_registration(void)
if (s_mode != AUTH_MODE_LOCAL_RFID) { {
if (s_mode != AUTH_MODE_LOCAL_RFID)
{
ESP_LOGW(TAG, "Registration is only available in LOCAL mode"); ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
return; return;
} }
@@ -224,61 +401,82 @@ void auth_wait_for_tag_registration(void) {
ESP_LOGI(TAG, "Tag registration mode enabled."); ESP_LOGI(TAG, "Tag registration mode enabled.");
} }
void auth_process_tag(const char *tag) { void auth_process_tag(const char *tag)
if (!tag || !*tag) { {
if (!tag || !*tag)
{
ESP_LOGW(TAG, "NULL/empty tag received"); ESP_LOGW(TAG, "NULL/empty tag received");
return; return;
} }
switch (s_mode) { switch (s_mode)
case AUTH_MODE_OPEN: { {
// Sem verificação; normalmente nem é necessário evento. case AUTH_MODE_OPEN:
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag); {
break; // Sem verificação; normalmente nem é necessário evento.
} ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break;
}
case AUTH_MODE_LOCAL_RFID: { case AUTH_MODE_LOCAL_RFID:
if (waiting_for_registration) { {
waiting_for_registration = false; if (waiting_for_registration)
{
waiting_for_registration = false;
if (auth_add_tag(tag)) { if (auth_add_tag(tag))
auth_tag_event_data_t ev = {0}; {
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1); auth_tag_event_data_t ev = {0};
ev.authorized = true; strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &ev, sizeof(ev), portMAX_DELAY); ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ESP_LOGI(TAG, "Tag registered: %s", tag); ev.authorized = true;
} else { esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
ESP_LOGW(TAG, "Failed to register tag: %s", tag); &ev, sizeof(ev), portMAX_DELAY);
} ESP_LOGI(TAG, "Tag registered: %s", tag);
return;
} }
else
auth_tag_event_data_t ev = {0}; {
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1); ESP_LOGW(TAG, "Failed to register tag: %s", tag);
ev.authorized = is_tag_valid(tag); }
return;
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;
} }
case AUTH_MODE_OCPP_RFID: { auth_tag_event_data_t ev = {0};
// Não decide localmente. Pede validação ao OCPP. strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
auth_tag_verify_event_t rq = {0}; ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1); ev.authorized = is_tag_valid(tag);
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_LOGI(TAG, "LOCAL tag %s: %s", tag,
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, &rq, sizeof(rq), portMAX_DELAY); ev.authorized ? "AUTHORIZED" : "DENIED");
break; esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
} &ev, sizeof(ev), portMAX_DELAY);
break;
}
case AUTH_MODE_OCPP_RFID:
{
// Não decide localmente. Pede validação ao OCPP.
auth_tag_verify_event_t rq = {0};
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
rq.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
rq.req_id = s_next_req_id++;
ESP_LOGI(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
&rq, sizeof(rq), portMAX_DELAY);
break;
}
} }
} }
int auth_get_tag_count(void) { int auth_get_tag_count(void)
{
return tag_count; return tag_count;
} }
const char *auth_get_tag_by_index(int index) { const char *auth_get_tag_by_index(int index)
if (index < 0 || index >= tag_count) return NULL; {
if (index < 0 || index >= tag_count)
return NULL;
return valid_tags[index]; return valid_tags[index];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,5 +18,5 @@ idf_component_register(
SRCS ${srcs} SRCS ${srcs}
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver PRIV_REQUIRES nvs_flash driver
REQUIRES peripherals auth loadbalancer REQUIRES peripherals auth loadbalancer scheduler
) )

View File

@@ -2,7 +2,7 @@
#include "evse_config.h" #include "evse_config.h"
#include "board_config.h" #include "board_config.h"
#include "evse_limits.h" #include "evse_limits.h"
#include "evse_api.h" // <— para evse_get_state / evse_state_is_charging #include "evse_api.h"
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "nvs.h"
#include "esp_timer.h" #include "esp_timer.h"
@@ -74,8 +74,8 @@ void evse_check_defaults(void)
charging_current = u16; charging_current = u16;
} }
// Runtime charging current initialized from persisted default // Runtime charging current inicializado a partir do default persistido
charging_current_runtime = max_charging_current; charging_current_runtime = charging_current;
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime); ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime);
// Auth required // Auth required
@@ -150,7 +150,6 @@ void evse_check_defaults(void)
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted)."); ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
} }
// Save to NVS if needed
if (needs_commit) if (needs_commit)
{ {
err = nvs_commit(nvs); err = nvs_commit(nvs);
@@ -218,7 +217,6 @@ esp_err_t evse_set_default_charging_current(uint16_t value)
// ======================== // ========================
void evse_set_runtime_charging_current(uint16_t value) void evse_set_runtime_charging_current(uint16_t value)
{ {
if (value > max_charging_current) if (value > max_charging_current)
{ {
value = max_charging_current; value = max_charging_current;
@@ -232,7 +230,6 @@ void evse_set_runtime_charging_current(uint16_t value)
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime); ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
evse_config_event_data_t evt = { evse_config_event_data_t evt = {
.charging = evse_state_is_charging(evse_get_state()), .charging = evse_state_is_charging(evse_get_state()),
.hw_max_current = (float)evse_get_max_charging_current(), .hw_max_current = (float)evse_get_max_charging_current(),
@@ -314,7 +311,6 @@ void evse_config_set_available(bool available)
{ {
is_available = available ? true : false; is_available = available ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available); esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
if (err == ESP_OK) if (err == ESP_OK)
err = nvs_commit(nvs); err = nvs_commit(nvs);
@@ -323,7 +319,6 @@ void evse_config_set_available(bool available)
ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err));
} }
// AVAILABLE_UPDATED
evse_available_event_data_t e = { evse_available_event_data_t e = {
.available = is_available, .available = is_available,
.timestamp_us = esp_timer_get_time()}; .timestamp_us = esp_timer_get_time()};
@@ -342,7 +337,6 @@ void evse_config_set_enabled(bool enabled)
{ {
is_enabled = enabled ? true : false; is_enabled = enabled ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled); esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
if (err == ESP_OK) if (err == ESP_OK)
err = nvs_commit(nvs); err = nvs_commit(nvs);
@@ -351,7 +345,6 @@ void evse_config_set_enabled(bool enabled)
ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err));
} }
// ENABLE_UPDATED
evse_enable_event_data_t e = { evse_enable_event_data_t e = {
.enabled = is_enabled, .enabled = is_enabled,
.timestamp_us = esp_timer_get_time()}; .timestamp_us = esp_timer_get_time()};

View File

@@ -1,5 +1,3 @@
// evse_core.c - Main EVSE control logic
#include "evse_fsm.h" #include "evse_fsm.h"
#include "evse_error.h" #include "evse_error.h"
#include "evse_limits.h" #include "evse_limits.h"
@@ -16,29 +14,34 @@ static const char *TAG = "evse_core";
static SemaphoreHandle_t mutex; static SemaphoreHandle_t mutex;
static evse_state_t last_state = EVSE_STATE_A; static evse_state_t last_state = EVSE_STATE_A;
static void evse_process(void);
static void evse_core_task(void *arg); static void evse_core_task(void *arg);
// ================================ void evse_init(void)
// Initialization {
// ================================
void evse_init(void) {
ESP_LOGI(TAG, "EVSE Init"); ESP_LOGI(TAG, "EVSE Init");
mutex = xSemaphoreCreateMutex(); // Optional: use static version for deterministic memory mutex = xSemaphoreCreateMutex();
if (!mutex)
{
ESP_LOGE(TAG, "Failed to create EVSE core mutex");
return;
}
evse_check_defaults(); evse_check_defaults();
evse_fsm_reset(); evse_fsm_reset();
pilot_set_level(true); // Enable pilot output pilot_set_level(true);
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL);
} }
// ================================ static void evse_process(void)
// Main Processing Logic {
// ================================ if (!mutex)
{
return;
}
void evse_process(void) {
xSemaphoreTake(mutex, portMAX_DELAY); xSemaphoreTake(mutex, portMAX_DELAY);
pilot_voltage_t pilot_voltage; pilot_voltage_t pilot_voltage;
@@ -49,34 +52,38 @@ void evse_process(void) {
evse_error_check(pilot_voltage, is_n12v); evse_error_check(pilot_voltage, is_n12v);
// Só chama FSM, que decide tudo
evse_fsm_process( evse_fsm_process(
pilot_voltage, pilot_voltage,
evse_state_get_authorized(), evse_state_get_authorized(),
evse_config_is_available(), evse_config_is_available(),
evse_config_is_enabled() evse_config_is_enabled());
);
evse_limits_check(); evse_limits_check();
evse_state_t current = evse_get_state(); if (evse_is_limit_reached())
if (current != last_state) { {
//ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current)); if (evse_state_get_authorized())
last_state = current; {
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
evse_state_set_authorized(false);
}
} }
evse_mark_error_cleared(); evse_state_t current = evse_get_state();
if (current != last_state)
{
last_state = current;
}
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
} }
// ================================ static void evse_core_task(void *arg)
// Background Task {
// ================================ (void)arg;
while (true)
static void evse_core_task(void *arg) { {
while (true) {
evse_process(); evse_process();
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz cycle vTaskDelay(pdMS_TO_TICKS(100));
} }
} }

View File

@@ -3,118 +3,227 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h" #include "esp_log.h"
#include "ntc_sensor.h" #include "ntc_sensor.h"
static const char *TAG = "evse_error"; static const char *TAG = "evse_error";
// Estado global de erros
static uint32_t error_bits = 0; static uint32_t error_bits = 0;
static TickType_t auto_clear_timeout = 0; static TickType_t auto_clear_timeout = 0;
// Sticky flag: "todos erros foram limpos"
static bool error_cleared = false; static bool error_cleared = false;
void evse_error_init(void) { // Proteção contra concorrência
// Inicialização do sistema de erros static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
void evse_error_init(void)
{
portENTER_CRITICAL(&error_mux);
error_bits = 0;
auto_clear_timeout = 0;
error_cleared = false;
portEXIT_CRITICAL(&error_mux);
} }
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) { void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
ESP_LOGD(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", {
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
pilot_voltage, is_n12v ? "true" : "false"); pilot_voltage, is_n12v ? "true" : "false");
// Falha elétrica geral no pilot // 1) Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1) { 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); bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_PILOT_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
} }
} }
else
{
// Pilot voltou a nível válido → limpa erro de pilot fault
evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
}
// Falta de -12V durante PWM (C ou D) // 2) Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) { 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); bool first_time = false;
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"); portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_DIODE_SHORT_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
} }
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
}
else
{
// Se já não estamos em C/D sem -12V, limpa o erro de diodo curto
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
} }
} }
void evse_temperature_check(void) { 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 float temp_c = ntc_temp_sensor();
uint8_t threshold = evse_get_temp_threshold();
// Log informativo com os valores ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold); temp_c, threshold);
// Se a temperatura parecer inválida, aplica erro de sensor // Temperatura inválida -> erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f) { 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); bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado"); ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
} }
return; return;
} }
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida // Leitura válida -> limpa erro de sensor
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
if (temp_c >= threshold) { // Temperatura máxima
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado if (temp_c >= threshold)
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); {
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold); bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_HIGH_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
} }
} else { portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
temp_c, threshold);
}
}
else
{
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
} }
} }
uint32_t evse_get_error(void) { uint32_t evse_get_error(void)
return error_bits; {
portENTER_CRITICAL(&error_mux);
uint32_t val = error_bits;
portEXIT_CRITICAL(&error_mux);
return val;
} }
bool evse_is_error_cleared(void) { bool evse_error_cleared_flag(void)
return error_cleared; {
portENTER_CRITICAL(&error_mux);
bool v = error_cleared;
portEXIT_CRITICAL(&error_mux);
return v;
} }
void evse_mark_error_cleared(void) { void evse_error_reset_flag(void)
{
portENTER_CRITICAL(&error_mux);
error_cleared = false; error_cleared = false;
portEXIT_CRITICAL(&error_mux);
} }
// Já existentes void evse_error_set(uint32_t bitmask)
void evse_error_set(uint32_t bitmask) { {
portENTER_CRITICAL(&error_mux);
error_cleared = false;
error_bits |= bitmask; error_bits |= bitmask;
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) { if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS)
{
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
} }
portEXIT_CRITICAL(&error_mux);
} }
void evse_error_clear(uint32_t bitmask) { void evse_error_clear(uint32_t bitmask)
bool had_error = error_bits != 0; {
portENTER_CRITICAL(&error_mux);
bool had_error = (error_bits != 0);
error_bits &= ~bitmask; error_bits &= ~bitmask;
if (had_error && error_bits == 0) { if (had_error && error_bits == 0)
{
error_cleared = true; error_cleared = true;
} }
portEXIT_CRITICAL(&error_mux);
} }
void evse_error_tick(void) { 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); portENTER_CRITICAL(&error_mux);
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) &&
auto_clear_timeout != 0 &&
xTaskGetTickCount() >= auto_clear_timeout)
{
error_bits &= ~EVSE_ERR_AUTO_CLEAR_BITS;
if (error_bits == 0)
{
error_cleared = true;
}
auto_clear_timeout = 0; auto_clear_timeout = 0;
} }
portEXIT_CRITICAL(&error_mux);
} }
bool evse_error_is_active(void) { bool evse_error_is_active(void)
return error_bits != 0; {
return evse_get_error() != 0;
} }
uint32_t evse_error_get_bits(void) { uint32_t evse_error_get_bits(void)
return error_bits; {
} return evse_get_error();
void evse_error_reset_flag(void) {
error_cleared = false;
}
bool evse_error_cleared_flag(void) {
return error_cleared;
} }

View File

@@ -20,94 +20,108 @@ static const char *TAG = "evse_fsm";
static bool c1_d1_waiting = false; static bool c1_d1_waiting = false;
static TickType_t c1_d1_relay_to = 0; static TickType_t c1_d1_relay_to = 0;
void evse_fsm_reset(void) { void evse_fsm_reset(void)
{
evse_set_state(EVSE_STATE_A); evse_set_state(EVSE_STATE_A);
c1_d1_waiting = false; c1_d1_waiting = false;
c1_d1_relay_to = 0; c1_d1_relay_to = 0;
} }
// ... includes e defines como já estão static void update_outputs(evse_state_t state)
{
static void update_outputs(evse_state_t state) {
const uint16_t current = evse_get_runtime_charging_current(); const uint16_t current = evse_get_runtime_charging_current();
uint8_t cable_max_current = evse_get_max_charging_current(); uint8_t cable_max_current = evse_get_max_charging_current();
const bool socket_outlet = evse_get_socket_outlet(); const bool socket_outlet = evse_get_socket_outlet();
if (socket_outlet) { if (socket_outlet)
{
cable_max_current = proximity_get_max_current(); cable_max_current = proximity_get_max_current();
} }
// Segurança: relé sempre off e outputs seguros em caso de erro // Segurança: relé sempre off e outputs seguros em caso de erro
if (evse_get_error() != 0) { if (evse_get_error() != 0)
if (ac_relay_get_state()) { {
if (ac_relay_get_state())
{
ac_relay_set_state(false); ac_relay_set_state(false);
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!"); ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
} }
ac_relay_set_state(false); // redundância tolerável ac_relay_set_state(false);
pilot_set_level(true); // sinal pilot sempre 12V (A) pilot_set_level(true);
if (board_config.socket_lock && socket_outlet) { if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false); socket_lock_set_locked(false);
} }
return; return;
} }
// Fluxo normal // Fluxo normal
switch (state) { switch (state)
case EVSE_STATE_A: {
case EVSE_STATE_E: case EVSE_STATE_A:
case EVSE_STATE_F: case EVSE_STATE_E:
ac_relay_set_state(false); case EVSE_STATE_F:
pilot_set_level(state == EVSE_STATE_A); ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet) { pilot_set_level(state == EVSE_STATE_A);
socket_lock_set_locked(false); if (board_config.socket_lock && socket_outlet)
} {
break; socket_lock_set_locked(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);
}
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;
} }
case EVSE_STATE_C2: break;
case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current)); case EVSE_STATE_B1:
ac_relay_set_state(true); // Só chega aqui se não há erro! pilot_set_level(true);
break; ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
if (rcm_test())
{
// ESP_LOGI(TAG, "RCM self test passed");
}
else
{
// ESP_LOGW(TAG, "RCM self test failed");
}
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));
ac_relay_set_state(false);
c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000);
break;
}
case EVSE_STATE_C2:
case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true);
break;
} }
} }
// FSM principal - centraliza a lógica de erro e de todos os estados // FSM principal
void evse_fsm_process( void evse_fsm_process(
pilot_voltage_t pilot_voltage, pilot_voltage_t pilot_voltage,
bool authorized, bool authorized,
bool available, bool available,
bool enabled bool enabled)
) { {
// Proteção total: erro força F sempre! // Proteção total: erro força F sempre!
if (evse_get_error() != 0) { if (evse_get_error() != 0)
if (evse_get_state() != EVSE_STATE_F) { {
if (evse_get_state() != EVSE_STATE_F)
{
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)"); ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)");
evse_set_state(EVSE_STATE_F); evse_set_state(EVSE_STATE_F);
} }
@@ -119,91 +133,97 @@ void evse_fsm_process(
evse_state_t prev = evse_get_state(); evse_state_t prev = evse_get_state();
evse_state_t curr = prev; evse_state_t curr = prev;
switch (curr) { switch (curr)
case EVSE_STATE_A: {
if (!available) { case EVSE_STATE_A:
evse_set_state(EVSE_STATE_F); if (!available)
} else if (pilot_voltage == PILOT_VOLTAGE_9) { {
evse_set_state(EVSE_STATE_B1); evse_set_state(EVSE_STATE_F);
} }
break; else if (pilot_voltage == PILOT_VOLTAGE_9)
{
evse_set_state(EVSE_STATE_B1);
}
break;
case EVSE_STATE_B1: case EVSE_STATE_B1:
case EVSE_STATE_B2: case EVSE_STATE_B2:
if (!available) { if (!available)
{
evse_set_state(EVSE_STATE_F);
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;
}
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); evse_set_state(EVSE_STATE_F);
break; break;
} }
switch (pilot_voltage) { }
case PILOT_VOLTAGE_12: __attribute__((fallthrough));
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;
}
break;
case EVSE_STATE_C1: case EVSE_STATE_C2:
case EVSE_STATE_D1: case EVSE_STATE_D2:
if (c1_d1_waiting && now >= c1_d1_relay_to) { if (!enabled || !available)
ac_relay_set_state(false); {
c1_d1_waiting = false; evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1)
if (!available) { ? EVSE_STATE_D1
evse_set_state(EVSE_STATE_F); : EVSE_STATE_C1);
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;
}
break; break;
}
case EVSE_STATE_E: switch (pilot_voltage)
// Estado elétrico grave: só reset manual {
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break; break;
case PILOT_VOLTAGE_3:
case EVSE_STATE_F: evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
// Fault: só sai se disponível e sem erro
if (available && evse_get_error() == 0) {
evse_set_state(EVSE_STATE_A);
}
break; break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
default:
break;
}
break;
case EVSE_STATE_E:
// Estado elétrico grave: só reset manual
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);
}
break;
} }
evse_state_t next = evse_get_state(); evse_state_t next = evse_get_state();
update_outputs(next); update_outputs(next);
if (next != prev) {
ESP_LOGI(TAG, "State changed: %s -> %s",
evse_state_to_str(prev),
evse_state_to_str(next));
}
} }

View File

@@ -7,14 +7,8 @@
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_err.h"
#include "nvs.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 // Concurrency protection
@@ -26,24 +20,17 @@ static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
// Runtime state (volatile) // Runtime state (volatile)
// ======================== // ========================
static bool limit_reached = false; static bool limit_reached = false;
static uint32_t consumption_limit = 0; // Energy limit in Wh static uint32_t consumption_limit = 0; // Energy limit in Wh
static uint32_t charging_time_limit = 0; // Time limit in seconds static uint32_t charging_time_limit = 0; // Time limit in seconds
static uint16_t under_power_limit = 0; // Minimum acceptable power in W static uint16_t under_power_limit = 0; // Minimum acceptable power in W
// ========================
// Default (persistent) limits
// ========================
static uint32_t default_consumption_limit = 0;
static uint32_t default_charging_time_limit = 0;
static uint16_t default_under_power_limit = 0;
// ======================== // ========================
// Limit status flag // Limit status flag
// ======================== // ========================
bool evse_get_limit_reached(void) { bool evse_get_limit_reached(void)
{
bool val; bool val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = limit_reached; val = limit_reached;
@@ -51,17 +38,24 @@ bool evse_get_limit_reached(void) {
return val; return val;
} }
void evse_set_limit_reached(bool v) { void evse_set_limit_reached(bool v)
{
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
limit_reached = v; limit_reached = v;
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
} }
bool evse_is_limit_reached(void)
{
return evse_get_limit_reached();
}
// ======================== // ========================
// Runtime limit accessors // Runtime limit accessors
// ======================== // ========================
uint32_t evse_get_consumption_limit(void) { uint32_t evse_get_consumption_limit(void)
{
uint32_t val; uint32_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = consumption_limit; val = consumption_limit;
@@ -69,13 +63,47 @@ uint32_t evse_get_consumption_limit(void) {
return val; return val;
} }
void evse_set_consumption_limit(uint32_t value) { void evse_set_consumption_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
consumption_limit = value; if (consumption_limit != value)
{
consumption_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
nvs_handle_t h;
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
if (err == ESP_OK)
{
err = nvs_set_u32(h, "def_cons_lim", value);
if (err == ESP_OK)
err = nvs_commit(h);
nvs_close(h);
if (err != ESP_OK)
{
ESP_LOGE("EVSE_LIMITS",
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
value, esp_err_to_name(err));
}
}
else
{
ESP_LOGE("EVSE_LIMITS",
"Failed to open NVS for consumption limit: %s",
esp_err_to_name(err));
}
} }
uint32_t evse_get_charging_time_limit(void) { uint32_t evse_get_charging_time_limit(void)
{
uint32_t val; uint32_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = charging_time_limit; val = charging_time_limit;
@@ -83,13 +111,47 @@ uint32_t evse_get_charging_time_limit(void) {
return val; return val;
} }
void evse_set_charging_time_limit(uint32_t value) { void evse_set_charging_time_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
charging_time_limit = value; if (charging_time_limit != value)
{
charging_time_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
nvs_handle_t h;
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
if (err == ESP_OK)
{
err = nvs_set_u32(h, "def_ch_time_lim", value);
if (err == ESP_OK)
err = nvs_commit(h);
nvs_close(h);
if (err != ESP_OK)
{
ESP_LOGE("EVSE_LIMITS",
"Failed to persist charging time limit (%" PRIu32 " s): %s",
value, esp_err_to_name(err));
}
}
else
{
ESP_LOGE("EVSE_LIMITS",
"Failed to open NVS for charging time limit: %s",
esp_err_to_name(err));
}
} }
uint16_t evse_get_under_power_limit(void) { uint16_t evse_get_under_power_limit(void)
{
uint16_t val; uint16_t val;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
val = under_power_limit; val = under_power_limit;
@@ -97,92 +159,97 @@ uint16_t evse_get_under_power_limit(void) {
return val; return val;
} }
void evse_set_under_power_limit(uint16_t value) { void evse_set_under_power_limit(uint16_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux); portENTER_CRITICAL(&evse_mux);
under_power_limit = value; if (under_power_limit != value)
{
under_power_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux); portEXIT_CRITICAL(&evse_mux);
}
// ======================== if (!changed)
// Default (persistent) limit accessors return;
// These values can be stored/restored via NVS
// ========================
uint32_t evse_get_default_consumption_limit(void) { nvs_handle_t h;
return default_consumption_limit; esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
} if (err == ESP_OK)
{
err = nvs_set_u16(h, "def_un_pwr_lim", value);
if (err == ESP_OK)
err = nvs_commit(h);
nvs_close(h);
void evse_set_default_consumption_limit(uint32_t value) { if (err != ESP_OK)
default_consumption_limit = value; {
} ESP_LOGE("EVSE_LIMITS",
"Failed to persist under-power limit (%" PRIu32 " W): %s",
uint32_t evse_get_default_charging_time_limit(void) { (uint32_t)value, esp_err_to_name(err));
return default_charging_time_limit; }
} }
else
void evse_set_default_charging_time_limit(uint32_t value) { {
default_charging_time_limit = value; ESP_LOGE("EVSE_LIMITS",
} "Failed to open NVS for under-power limit: %s",
esp_err_to_name(err));
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 // 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 void evse_limits_check(void)
if (!evse_state_is_charging(evse_get_state())) { {
// Só faz sentido durante carregamento
if (!evse_state_is_charging(evse_get_state()))
{
return; return;
} }
evse_session_t sess; evse_session_t sess;
// Retrieve accumulated data for the current session if (!evse_session_get(&sess) || !sess.is_current)
if (!evse_session_get(&sess) || !sess.is_current) { {
// If there's no active session, abort // Sem sessão ativa → nada a fazer
return; return;
} }
bool reached = false; bool reached = false;
// 1) Energy consumption limit (Wh) // 1) Limite de energia (Wh)
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) { if (consumption_limit > 0 && sess.energy_wh >= consumption_limit)
{
ESP_LOGW("EVSE_LIMITS", ESP_LOGW("EVSE_LIMITS",
"Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh", "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
sess.energy_wh, consumption_limit); sess.energy_wh, consumption_limit);
reached = true; reached = true;
} }
// 2) Charging time limit (seconds) // 2) Limite de tempo (s)
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) { if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit)
{
ESP_LOGW("EVSE_LIMITS", ESP_LOGW("EVSE_LIMITS",
"Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s", "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
sess.duration_s, charging_time_limit); sess.duration_s, charging_time_limit);
reached = true; reached = true;
} }
// 3) Under-power limit (instantaneous power) // 3) Under-power (potência instantânea)
uint32_t inst_power = evse_meter_get_instant_power(); uint32_t inst_power = evse_meter_get_instant_power();
if (under_power_limit > 0 && inst_power < under_power_limit) { if (under_power_limit > 0 && inst_power < under_power_limit)
{
ESP_LOGW("EVSE_LIMITS", ESP_LOGW("EVSE_LIMITS",
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
(uint32_t)inst_power, (uint32_t)inst_power,
(uint32_t)under_power_limit); (uint32_t)under_power_limit);
reached = true; reached = true;
} }
if (reached) { if (reached)
{
evse_set_limit_reached(true); evse_set_limit_reached(true);
} }
} }

View File

@@ -7,29 +7,35 @@
#include "evse_api.h" #include "evse_api.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "evse_session.h" #include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_event.h"
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
#include "auth_events.h" #include "auth_events.h"
#include "loadbalancer_events.h" #include "loadbalancer_events.h"
#include "ocpp_events.h" #include "ocpp_events.h"
#include "esp_event.h" #include "scheduler_events.h"
static const char *TAG = "EVSE_Manager"; static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex; static SemaphoreHandle_t evse_mutex;
static bool auth_enabled = false; static volatile bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer // Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false; static volatile bool lb_paused = false;
static bool lb_prev_authorized = false; static volatile bool lb_prev_authorized = false;
// Estado de janela do scheduler
static volatile bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
@@ -41,8 +47,20 @@ static void lb_clear_pause_state(void)
lb_prev_authorized = false; lb_prev_authorized = false;
} }
// Exposto para outros módulos (se quiserem saber se o scheduler permite)
bool evse_sched_is_allowed(void)
{
bool v;
portENTER_CRITICAL(&s_sched_mux);
v = s_sched_allowed;
portEXIT_CRITICAL(&s_sched_mux);
return v;
}
static void evse_manager_handle_auth_on_tick(void) static void evse_manager_handle_auth_on_tick(void)
{ {
bool sched_allowed = evse_sched_is_allowed();
if (auth_enabled) if (auth_enabled)
{ {
// Se o carro foi desconectado, revoga autorização // Se o carro foi desconectado, revoga autorização
@@ -53,23 +71,38 @@ static void evse_manager_handle_auth_on_tick(void)
// Desconexão física invalida qualquer pausa pendente do LB // Desconexão física invalida qualquer pausa pendente do LB
lb_clear_pause_state(); lb_clear_pause_state();
} }
// Em modos RFID/OCPP, o scheduler pode também forçar paragem
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
} }
else else
{ {
// Se autenticação está desativada, garante autorização sempre ativa // Modo OPEN: só autoriza se LB e Scheduler permitirem
if (!evse_state_get_authorized()) if (!lb_paused && sched_allowed && !evse_state_get_authorized())
{ {
evse_state_set_authorized(true); evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization."); ESP_LOGI(TAG, "Authentication disabled → forced authorization (within schedule).");
// Em modo OPEN, pausa do LB não é tão relevante, mas limpamos mesmo assim
lb_clear_pause_state(); lb_clear_pause_state();
} }
// Fora da janela, garantir que não fica autorizado
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (OPEN mode) → revoking authorization.");
evse_state_set_authorized(false);
}
} }
} }
// ===== Task de ciclo principal ===== // ===== Task de ciclo principal =====
static void evse_manager_task(void *arg) static void evse_manager_task(void *arg)
{ {
(void)arg;
while (true) while (true)
{ {
evse_manager_tick(); evse_manager_tick();
@@ -77,8 +110,11 @@ static void evse_manager_task(void *arg)
} }
} }
// ===== Tratador de eventos de AUTH =====
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data) static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)arg;
if (base != AUTH_EVENTS || !data) if (base != AUTH_EVENTS || !data)
return; return;
@@ -105,7 +141,9 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(g_mode)); ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(g_mode));
if (g_mode == AUTH_MODE_OPEN) if (g_mode == AUTH_MODE_OPEN)
{ {
evse_state_set_authorized(true); // Em OPEN, a autorização passa a ser gerida por evse_manager_handle_auth_on_tick(),
// que também respeita o scheduler.
evse_state_set_authorized(false); // vai ser forçado no próximo tick se permitido
auth_enabled = false; auth_enabled = false;
} }
else else
@@ -121,17 +159,22 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
} }
} }
// ===== Tratador de eventos de loadbalancer ===== // ===== Tratador de eventos de Load Balancer =====
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base, static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) int32_t event_id, void *event_data)
{ {
(void)handler_arg;
(void)event_base;
if (!event_data)
return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED) if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{ {
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data; const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)", ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED", evt->enabled ? "ENABLED" : "DISABLED",
(long long)evt->timestamp_us); (long long)evt->timestamp_us);
// Ações adicionais podem ser adicionadas aqui conforme necessário
} }
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT) else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{ {
@@ -167,16 +210,17 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
{ {
lb_paused = false; lb_paused = false;
// Só retomamos se EVSE estiver operacional // Só retomamos se EVSE estiver operacional e scheduler permitir
bool can_resume = bool can_resume =
(evse_get_error() == 0) && (evse_get_error() == 0) &&
evse_config_is_available() && evse_config_is_available() &&
evse_config_is_enabled(); evse_config_is_enabled() &&
evse_sched_is_allowed();
if (!can_resume) if (!can_resume)
{ {
ESP_LOGW(TAG, ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado)", "[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado/fora de horário)",
evt->max_current); evt->max_current);
lb_clear_pause_state(); lb_clear_pause_state();
return; return;
@@ -184,7 +228,7 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
if (!auth_enabled) if (!auth_enabled)
{ {
// Modo OPEN: retoma sempre // Modo OPEN: retoma sempre (se dentro da janela do scheduler)
ESP_LOGI(TAG, ESP_LOGI(TAG,
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)", "[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
evt->max_current); evt->max_current);
@@ -222,8 +266,11 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
} }
} }
// ===== Tratador de eventos de OCPP =====
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data) static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{ {
(void)arg;
if (base != OCPP_EVENTS) if (base != OCPP_EVENTS)
return; return;
@@ -261,7 +308,6 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
case OCPP_EVENT_START_TX: case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx"); ESP_LOGI(TAG, "[OCPP] StartTx");
// StartTx em si não precisa mexer em auth, mas limpamos estado de pausa por segurança
lb_clear_pause_state(); lb_clear_pause_state();
break; break;
@@ -285,8 +331,6 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
// Mapear operative → enabled local (persiste e emite EVSE_EVENT_ENABLE_UPDATED) // Mapear operative → enabled local (persiste e emite EVSE_EVENT_ENABLE_UPDATED)
evse_config_set_enabled(ev->operative); evse_config_set_enabled(ev->operative);
// Opcional: poderias também limpar a pausa aqui, dependendo da política
// lb_clear_pause_state();
break; break;
} }
@@ -296,10 +340,44 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
} }
} }
// ===== Tratador de eventos de Scheduler =====
static void on_sched_event(void *arg,
esp_event_base_t base,
int32_t id,
void *data)
{
(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] event id=%" PRIi32 " allowed_now=%d",
id, (int)ev->allowed_now);
// Se a janela fechou, parar sessão (revogar autorização)
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
// Se a janela abriu de novo, não auto-reautorizamos aqui.
// Deixamos que o utilizador / OCPP decida iniciar nova sessão.
// (Em modo OPEN, o tick trata disso respeitando o scheduler.)
}
// ===== Inicialização ===== // ===== Inicialização =====
void evse_manager_init(void) void evse_manager_init(void)
{ {
evse_mutex = xSemaphoreCreateMutex(); evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
evse_config_init(); evse_config_init();
evse_error_init(); evse_error_init();
@@ -311,9 +389,12 @@ void evse_manager_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
ESP_LOGI(TAG, "EVSE Manager inicializado."); ESP_LOGI(TAG, "EVSE Manager inicializado.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
configASSERT(rc == pdPASS);
} }
// ===== Main Tick ===== // ===== Main Tick =====
@@ -331,4 +412,5 @@ void evse_manager_tick(void)
xSemaphoreGive(evse_mutex); xSemaphoreGive(evse_mutex);
} }
// === Fim de: components/evse/evse_manager.c === // === Fim de: components/evse/evse_manager.c ===

View File

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

View File

@@ -23,24 +23,20 @@
#define MAX_SAMPLE_ATTEMPTS 1000 #define MAX_SAMPLE_ATTEMPTS 1000
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior #define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
// ADC121S021 setup #define ADC121_VREF_MV 3300
#define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware! #define ADC121_MAX 4095
#define ADC121_MAX 4095 // 12 bits
static const char *TAG = "evse_pilot"; static const char *TAG = "evse_pilot";
// Memoização de estado para evitar comandos/logs desnecessários
static int last_pilot_level = -1; static int last_pilot_level = -1;
static uint32_t last_pwm_duty = 0; static uint32_t last_pwm_duty = 0;
// Função para converter leitura bruta do ADC para mV
static int adc_raw_to_mv(uint16_t raw) { static int adc_raw_to_mv(uint16_t raw) {
return (raw * ADC121_VREF_MV) / ADC121_MAX; return (raw * ADC121_VREF_MV) / ADC121_MAX;
} }
void pilot_init(void) void pilot_init(void)
{ {
// PWM (LEDC) configuração
ledc_timer_config_t ledc_timer = { ledc_timer_config_t ledc_timer = {
.speed_mode = PILOT_PWM_SPEED_MODE, .speed_mode = PILOT_PWM_SPEED_MODE,
.timer_num = PILOT_PWM_TIMER, .timer_num = PILOT_PWM_TIMER,
@@ -61,20 +57,18 @@ void pilot_init(void)
}; };
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0)); ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
//ESP_ERROR_CHECK(ledc_fade_func_install(0));
// Inicializa ADC121S021 externo
adc121s021_dma_init(); adc121s021_dma_init();
} }
void pilot_set_level(bool level) void pilot_set_level(bool level)
{ {
if (last_pilot_level == level) return; // só muda se necessário if (last_pilot_level == level) return;
last_pilot_level = level; last_pilot_level = level;
ESP_LOGI(TAG, "Set level %d", level); ESP_LOGI(TAG, "Set level %d", level);
ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0);
last_pwm_duty = 0; // PWM parado last_pwm_duty = 0;
} }
void pilot_set_amps(uint16_t amps) void pilot_set_amps(uint16_t amps)
@@ -85,13 +79,11 @@ void pilot_set_amps(uint16_t amps)
} }
uint32_t duty_percent; uint32_t duty_percent;
if (amps <= 51) { if (amps <= 51) {
duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6 duty_percent = (amps * 10) / 6;
} else { } else {
duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64 duty_percent = (amps * 10) / 25 + 64;
} }
if (duty_percent > 100) duty_percent = 100; if (duty_percent > 100) duty_percent = 100;
uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100; uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;
@@ -101,40 +93,18 @@ void pilot_set_amps(uint16_t amps)
last_pwm_duty = duty; last_pwm_duty = duty;
ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)", ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)",
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent); amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty); ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty);
ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL);
} }
bool pilot_get_state(void) { bool pilot_get_state(void) {
// true se estamos em 12V fixo; false se PWM ou -12V
// Se quiser diferenciar PWM, guarde um flag quando chamar set_amps.
return (last_pilot_level == 1) && (last_pwm_duty == 0); return (last_pilot_level == 1) && (last_pwm_duty == 0);
} }
static int compare_int(const void *a, const void *b) { static int compare_int(const void *a, const void *b) {
return (*(int *)a - *(int *)b); return (*(const int *)a - *(const 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)];
} }
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
@@ -145,7 +115,6 @@ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
int collected = 0, attempts = 0; int collected = 0, attempts = 0;
uint16_t adc_sample = 0; uint16_t adc_sample = 0;
// Lê samples usando ADC121S021 externo
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) {
adc_sample = 0; adc_sample = 0;
if (adc121s021_dma_get_sample(&adc_sample)) { if (adc121s021_dma_get_sample(&adc_sample)) {
@@ -164,13 +133,21 @@ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
return; return;
} }
int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); qsort(samples, collected, sizeof(int), compare_int);
int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
int k = (collected * PILOT_EXTREME_PERCENT) / 100;
if (k == 0) k = 1;
int low_index = k / 2;
int high_index = collected - k + (k / 2);
if (high_index >= collected) high_index = collected - 1;
int low_raw = samples[low_index];
int high_raw = samples[high_index];
int high_mv = adc_raw_to_mv(high_raw); int high_mv = adc_raw_to_mv(high_raw);
int low_mv = adc_raw_to_mv(low_raw); int low_mv = adc_raw_to_mv(low_raw);
// Aplica thresholds definidos em board_config (em mV)
if (high_mv >= board_config.pilot_down_threshold_12) if (high_mv >= board_config.pilot_down_threshold_12)
*up_voltage = PILOT_VOLTAGE_12; *up_voltage = PILOT_VOLTAGE_12;
else if (high_mv >= board_config.pilot_down_threshold_9) else if (high_mv >= board_config.pilot_down_threshold_9)

View File

@@ -1,82 +1,164 @@
/* #include <inttypes.h>
* evse_session.c
* Implementation of evse_session module using instantaneous power accumulation
*/
#include <inttypes.h> // for PRIu32
#include "evse_session.h" #include "evse_session.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_log.h" #include "esp_log.h"
#include "evse_events.h"
#include "esp_event.h"
#include "evse_limits.h"
static const char *TAG = "evse_session"; static const char *TAG = "evse_session";
// Internal state
static TickType_t session_start_tick = 0; static TickType_t session_start_tick = 0;
static uint32_t watt_seconds = 0; // accumulator of W·s static uint32_t watt_seconds = 0;
static evse_session_t last_session; static evse_session_t last_session;
static bool last_session_valid = false; static bool last_session_valid = false;
static uint32_t session_counter = 0;
void evse_session_init(void) { static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
session_start_tick = 0;
watt_seconds = 0; void evse_session_init(void)
last_session_valid = false; {
portENTER_CRITICAL(&session_mux);
session_start_tick = 0;
watt_seconds = 0;
last_session_valid = false;
session_counter = 0;
portEXIT_CRITICAL(&session_mux);
} }
void evse_session_start(void) { void evse_session_start(void)
session_start_tick = xTaskGetTickCount(); {
watt_seconds = 0; TickType_t tick = xTaskGetTickCount();
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick);
portENTER_CRITICAL(&session_mux);
session_start_tick = tick;
watt_seconds = 0;
session_counter++;
portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)tick);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED,
.session_id = session_counter,
.duration_s = 0,
.energy_wh = 0,
.avg_power_w = 0,
.is_current = true,
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
void evse_session_end(void) { void evse_session_end(void)
{
TickType_t start_tick;
uint32_t ws;
uint32_t id;
portENTER_CRITICAL(&session_mux);
if (session_start_tick == 0) { if (session_start_tick == 0) {
portEXIT_CRITICAL(&session_mux);
ESP_LOGW(TAG, "evse_session_end called without active session"); ESP_LOGW(TAG, "evse_session_end called without active session");
return; return;
} }
TickType_t now = xTaskGetTickCount(); start_tick = session_start_tick;
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ; ws = watt_seconds;
uint32_t energy_wh = watt_seconds / 3600U; id = session_counter;
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;
session_start_tick = 0; session_start_tick = 0;
ESP_LOGI(TAG, "Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg_power=%" PRIu32 " W", portEXIT_CRITICAL(&session_mux);
(uint32_t)duration_s, (uint32_t)energy_wh, (uint32_t)avg_power);
TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - start_tick) / configTICK_RATE_HZ;
uint32_t energy_wh = ws / 3600U;
uint32_t avg_power = duration_s > 0 ? ws / 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: duration=%" PRIu32 " s, energy=%" PRIu32
" Wh, avg_power=%" PRIu32 " W",
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,
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
} }
void evse_session_tick(void) { 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(); uint32_t power_w = evse_meter_get_instant_power();
watt_seconds += power_w;
portENTER_CRITICAL(&session_mux);
if (session_start_tick != 0) {
watt_seconds += power_w;
}
portEXIT_CRITICAL(&session_mux);
} }
bool evse_session_get(evse_session_t *out) { bool evse_session_get(evse_session_t *out)
if (out == NULL) return false; {
if (out == NULL)
return false;
if (session_start_tick != 0) { TickType_t start;
uint32_t ws;
bool has_current;
evse_session_t last_copy;
bool last_valid;
portENTER_CRITICAL(&session_mux);
start = session_start_tick;
ws = watt_seconds;
has_current = (session_start_tick != 0);
last_copy = last_session;
last_valid = last_session_valid;
portEXIT_CRITICAL(&session_mux);
if (has_current)
{
TickType_t now = xTaskGetTickCount(); TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ; uint32_t duration_s = (now - start) / configTICK_RATE_HZ;
uint32_t energy_wh = watt_seconds / 3600U; uint32_t energy_wh = ws / 3600U;
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0; uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0;
out->start_tick = session_start_tick; out->start_tick = start;
out->duration_s = duration_s; out->duration_s = duration_s;
out->energy_wh = energy_wh; out->energy_wh = energy_wh;
out->avg_power_w = avg_power; out->avg_power_w = avg_power;
out->is_current = true; out->is_current = true;
return true; return true;
} }
if (last_session_valid) { if (last_valid)
*out = last_session; {
*out = last_copy;
return true; return true;
} }

View File

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

View File

@@ -1,3 +1,4 @@
// === Início de: components/evse/include/evse_error.h ===
#ifndef EVSE_ERROR_H #ifndef EVSE_ERROR_H
#define EVSE_ERROR_H #define EVSE_ERROR_H
@@ -5,41 +6,46 @@
#include <stdbool.h> #include <stdbool.h>
#include "evse_pilot.h" #include "evse_pilot.h"
// Bits que auto-limpam passado um timeout
#define EVSE_ERR_AUTO_CLEAR_BITS ( \ #define EVSE_ERR_AUTO_CLEAR_BITS ( \
EVSE_ERR_DIODE_SHORT_BIT | \ EVSE_ERR_DIODE_SHORT_BIT | \
EVSE_ERR_TEMPERATURE_HIGH_BIT | \ EVSE_ERR_TEMPERATURE_HIGH_BIT | \
EVSE_ERR_RCM_TRIGGERED_BIT ) EVSE_ERR_RCM_TRIGGERED_BIT)
// Error bits // Error bits
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0) #define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1) #define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2) #define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3) #define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4) #define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5) #define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6) #define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7) #define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
// Inicialização do módulo de erros // Inicialização do módulo de erros
void evse_error_init(void); void evse_error_init(void);
// Verificações e monitoramento // Verificações e monitoramento
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v); void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
void evse_temperature_check(void); void evse_temperature_check(void);
void evse_error_tick(void); void evse_error_tick(void);
// Leitura e controle de erros // Leitura e controle de erros
uint32_t evse_get_error(void); uint32_t evse_get_error(void);
bool evse_is_error_cleared(void);
void evse_mark_error_cleared(void);
void evse_error_set(uint32_t bitmask); void evse_error_set(uint32_t bitmask);
void evse_error_clear(uint32_t bitmask); void evse_error_clear(uint32_t bitmask);
bool evse_error_is_active(void); bool evse_error_is_active(void);
uint32_t evse_error_get_bits(void); uint32_t evse_error_get_bits(void);
void evse_error_reset_flag(void);
// ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos"
// ----------------------------------------------------
// Fica true quando TODOS os erros são limpos.
// Volta a false assim que qualquer erro novo aparece.
// Permanece true até o consumidor limpar explicitamente.
bool evse_error_cleared_flag(void); bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H #endif // EVSE_ERROR_H
// === Fim de: components/evse/include/evse_error.h ===

View File

@@ -2,6 +2,9 @@
#define EVSE_EVENTS_H #define EVSE_EVENTS_H
#pragma once #pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_event.h" #include "esp_event.h"
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
@@ -12,8 +15,12 @@ typedef enum {
EVSE_EVENT_CONFIG_UPDATED, EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED, EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED, EVSE_EVENT_AVAILABLE_UPDATED,
EVSE_EVENT_SESSION,
} evse_event_id_t; } evse_event_id_t;
// -----------------
// Eventos de STATE
// -----------------
typedef enum { typedef enum {
EVSE_STATE_EVENT_IDLE, EVSE_STATE_EVENT_IDLE,
EVSE_STATE_EVENT_WAITING, EVSE_STATE_EVENT_WAITING,
@@ -25,11 +32,35 @@ typedef struct {
evse_state_event_t state; evse_state_event_t state;
} evse_state_event_data_t; } evse_state_event_data_t;
// -----------------
// Eventos de SESSÃO
// -----------------
typedef enum {
EVSE_SESSION_EVENT_STARTED = 0,
EVSE_SESSION_EVENT_FINISHED,
} evse_session_event_type_t;
typedef struct { typedef struct {
bool charging; // Estado de carregamento evse_session_event_type_t type; ///< STARTED / FINISHED
float hw_max_current; // Corrente máxima suportada pelo hardware
float runtime_current; // Corrente de carregamento em uso // campos básicos da sessão, em tipos simples:
int64_t timestamp_us; // Momento da atualização uint32_t session_id; ///< opcional, se tiveres um ID
uint32_t duration_s; ///< duração em segundos (0 no STARTED)
uint32_t energy_wh; ///< energia em Wh (0 no STARTED)
uint32_t avg_power_w; ///< potência média em W (0 no STARTED)
bool is_current; ///< true se ainda estiver em curso
} evse_session_event_data_t;
// -----------------
// Eventos de CONFIG
// -----------------
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_config_event_data_t; } evse_config_event_data_t;
// Eventos simples e específicos // Eventos simples e específicos

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,8 +25,8 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Config // Config
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC) #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 MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC)
#define NVS_NAMESPACE "wifi" #define NVS_NAMESPACE "wifi"
#define NVS_ENABLED "enabled" #define NVS_ENABLED "enabled"
@@ -125,6 +125,17 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
esp_wifi_connect(); esp_wifi_connect();
break; break;
} }
case WIFI_EVENT_STA_CONNECTED:
{
ESP_LOGI(TAG, "STA associated (L2 connected)");
// dispara evento genérico de STA_CONNECTED
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_CONNECTED,
NULL,
0,
portMAX_DELAY);
break;
}
case WIFI_EVENT_STA_DISCONNECTED: case WIFI_EVENT_STA_DISCONNECTED:
{ {
xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
@@ -133,6 +144,13 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data; wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1); ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1);
// dispara evento genérico de “STA desconectou”
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_DISCONNECTED,
ev,
ev ? sizeof(*ev) : 0,
portMAX_DELAY);
// NÃO bloquear o event loop com vTaskDelay // NÃO bloquear o event loop com vTaskDelay
if (s_retry_count < s_retry_max) if (s_retry_count < s_retry_max)
{ {
@@ -166,6 +184,13 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0; s_retry_count = 0;
// Dispara evento “STA GOT IP”
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_GOT_IP6)
{ {
@@ -174,6 +199,16 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0; s_retry_count = 0;
// se quiseres, podes também propagar um NETWORK_EVENT_STA_GOT_IP aqui
}
else if (event_id == IP_EVENT_STA_LOST_IP)
{
ESP_LOGW(TAG, "STA lost IP");
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_LOST_IP,
NULL,
0,
portMAX_DELAY);
} }
} }
} }
@@ -390,7 +425,7 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++) for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++)
{ {
// garante que scan_aps[i].ssid tenha pelo menos 33 bytes // 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].rssi = ap_info[i].rssi;
scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN; scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN;
} }
@@ -464,4 +499,4 @@ bool wifi_is_ap(void)
EventBits_t bits = xEventGroupGetBits(wifi_event_group); EventBits_t bits = xEventGroupGetBits(wifi_event_group);
return (bits & WIFI_AP_MODE_BIT) != 0; return (bits & WIFI_AP_MODE_BIT) != 0;
} }

View File

@@ -2,12 +2,10 @@ set(srcs
"src/adc.c" "src/adc.c"
"src/adc121s021_dma.c" "src/adc121s021_dma.c"
"src/peripherals.c" "src/peripherals.c"
"src/led.c"
"src/proximity.c" "src/proximity.c"
"src/ac_relay.c" "src/ac_relay.c"
"src/socket_lock.c" "src/socket_lock.c"
"src/rcm.c" "src/rcm.c"
"src/aux_io.c"
"src/onewire.c" "src/onewire.c"
"src/ds18x20.c" "src/ds18x20.c"
"src/temp_sensor.c" "src/temp_sensor.c"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,11 +43,11 @@ cJSON *json_get_evse_config(void)
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm()); cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold()); cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit()); cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit()); //cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit()); cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit()); //cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit()); cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit()); //cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time()); cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time()); cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
@@ -99,11 +99,11 @@ esp_err_t json_set_evse_config(cJSON *root)
SET_BOOL("rcm", evse_set_rcm); SET_BOOL("rcm", evse_set_rcm);
SET_NUM("temperatureThreshold", evse_set_temp_threshold); SET_NUM("temperatureThreshold", evse_set_temp_threshold);
SET_NUM("consumptionLimit", evse_set_consumption_limit); SET_NUM("consumptionLimit", evse_set_consumption_limit);
SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit); //SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit);
SET_NUM("chargingTimeLimit", evse_set_charging_time_limit); SET_NUM("chargingTimeLimit", evse_set_charging_time_limit);
SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit); //SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit);
SET_NUM("underPowerLimit", evse_set_under_power_limit); SET_NUM("underPowerLimit", evse_set_under_power_limit);
SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit); //SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit);
SET_NUM("socketLockOperatingTime", socket_lock_set_operating_time); SET_NUM("socketLockOperatingTime", socket_lock_set_operating_time);
SET_NUM("socketLockBreakTime", socket_lock_set_break_time); SET_NUM("socketLockBreakTime", socket_lock_set_break_time);

View File

@@ -197,7 +197,7 @@ static void client_task_func(void *param)
} }
else else
{ {
ESP_LOGW(TAG, "[MQTT] Not connected — skipping state publish"); ESP_LOGD(TAG, "[MQTT] Not connected — skipping state publish");
} }
vTaskDelay(pdMS_TO_TICKS(periodicity * 1000)); vTaskDelay(pdMS_TO_TICKS(periodicity * 1000));

View File

@@ -8,6 +8,7 @@ set(srcs
"src/loadbalancing_settings_api.c" "src/loadbalancing_settings_api.c"
"src/evse_link_config_api.c" "src/evse_link_config_api.c"
"src/dashboard_api.c" "src/dashboard_api.c"
"src/scheduler_settings_api.c"
"src/static_file_api.c" "src/static_file_api.c"
) )
@@ -15,7 +16,7 @@ idf_component_register(
SRCS ${srcs} SRCS ${srcs}
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src" PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash esp_http_server esp_netif vfs spiffs json evse meter_manager protocols loadbalancer evse_link PRIV_REQUIRES nvs_flash esp_http_server esp_netif vfs spiffs json evse meter_manager protocols loadbalancer evse_link scheduler
) )
# SPIFFS image (opcional) # SPIFFS image (opcional)

View File

@@ -0,0 +1,23 @@
// =========================
// scheduler_settings_api.h
// =========================
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers de configuração do scheduler
*
* Endpoints:
* GET /api/v1/config/scheduler
* POST /api/v1/config/scheduler
*/
void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif

View File

@@ -7,6 +7,7 @@
#include "ocpp_api.h" #include "ocpp_api.h"
#include "auth_api.h" #include "auth_api.h"
#include "dashboard_api.h" #include "dashboard_api.h"
#include "scheduler_settings_api.h"
#include "static_file_api.h" #include "static_file_api.h"
#include "esp_log.h" #include "esp_log.h"
@@ -49,8 +50,8 @@ esp_err_t rest_server_init(const char *base_path)
register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_link_config_handlers(server, ctx); register_link_config_handlers(server, ctx);
register_meters_data_handlers(server, ctx); register_meters_data_handlers(server, ctx);
register_scheduler_settings_handlers(server, ctx);
register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação

View File

@@ -0,0 +1,224 @@
#include "scheduler_settings_api.h"
#include "scheduler.h"
#include "scheduler_types.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "cJSON.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h> // sscanf, snprintf
static const char *TAG = "scheduler_api";
/* =========================
* Helpers HH:MM <-> minutos
* ========================= */
static bool parse_hhmm(const char *s, uint16_t *out_min)
{
if (!s || !out_min)
return false;
// formato esperado: "HH:MM"
int h = 0, m = 0;
if (sscanf(s, "%d:%d", &h, &m) != 2)
{
return false;
}
if (h < 0 || h > 23 || m < 0 || m > 59)
{
return false;
}
*out_min = (uint16_t)(h * 60 + m);
return true;
}
static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz)
{
if (!buf || buf_sz < 6)
return;
minutes %= (24 * 60);
int h = minutes / 60;
int m = minutes % 60;
snprintf(buf, buf_sz, "%02d:%02d", h, m);
}
/* =========================
* GET /api/v1/config/scheduler
* ========================= */
static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "GET /api/v1/config/scheduler");
httpd_resp_set_type(req, "application/json");
sched_config_t cfg = scheduler_get_config();
bool allowed_now = scheduler_is_allowed_now();
cJSON *root = cJSON_CreateObject();
if (!root)
{
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON alloc failed");
return ESP_FAIL;
}
cJSON_AddBoolToObject(root, "enabled", cfg.enabled);
cJSON_AddStringToObject(root, "mode", sched_mode_to_str(cfg.mode));
char buf[8];
format_hhmm(cfg.start_min, buf, sizeof(buf));
cJSON_AddStringToObject(root, "startTime", buf);
format_hhmm(cfg.end_min, buf, sizeof(buf));
cJSON_AddStringToObject(root, "endTime", buf);
cJSON_AddBoolToObject(root, "allowedNow", allowed_now);
char *json_str = cJSON_PrintUnformatted(root);
if (!json_str)
{
cJSON_Delete(root);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON print failed");
return ESP_FAIL;
}
httpd_resp_sendstr(req, json_str);
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
/* =========================
* POST /api/v1/config/scheduler
* ========================= */
static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "POST /api/v1/config/scheduler");
// NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total,
// usa req->content_len e faz um loop com httpd_req_recv.
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0)
{
ESP_LOGE(TAG, "Empty body / recv error");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Body: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json)
{
ESP_LOGE(TAG, "Invalid JSON");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// Começa a partir da config atual
sched_config_t cfg = scheduler_get_config();
// enabled
cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(j_enabled))
{
cfg.enabled = cJSON_IsTrue(j_enabled);
ESP_LOGI(TAG, " enabled = %d", cfg.enabled);
}
// mode
cJSON *j_mode = cJSON_GetObjectItem(json, "mode");
if (cJSON_IsString(j_mode) && j_mode->valuestring)
{
sched_mode_t m;
if (!sched_mode_from_str(j_mode->valuestring, &m))
{
ESP_LOGW(TAG, "Invalid mode: %s", j_mode->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid mode (use: disabled|simple|weekly)");
return ESP_FAIL;
}
cfg.mode = m;
ESP_LOGI(TAG, " mode = %s", sched_mode_to_str(cfg.mode));
}
// startTime (string "HH:MM")
cJSON *j_start = cJSON_GetObjectItem(json, "startTime");
if (cJSON_IsString(j_start) && j_start->valuestring)
{
uint16_t minutes = 0;
if (!parse_hhmm(j_start->valuestring, &minutes))
{
ESP_LOGW(TAG, "Invalid startTime: %s", j_start->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid startTime (use HH:MM)");
return ESP_FAIL;
}
cfg.start_min = minutes;
ESP_LOGI(TAG, " start_min = %u", (unsigned)cfg.start_min);
}
// endTime (string "HH:MM")
cJSON *j_end = cJSON_GetObjectItem(json, "endTime");
if (cJSON_IsString(j_end) && j_end->valuestring)
{
uint16_t minutes = 0;
if (!parse_hhmm(j_end->valuestring, &minutes))
{
ESP_LOGW(TAG, "Invalid endTime: %s", j_end->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid endTime (use HH:MM)");
return ESP_FAIL;
}
cfg.end_min = minutes;
ESP_LOGI(TAG, " end_min = %u", (unsigned)cfg.end_min);
}
// (Opcional) validações extra:
// exemplo: impedir janela vazia quando ativo
/*
if (cfg.enabled && cfg.mode != SCHED_MODE_DISABLED &&
cfg.start_min == cfg.end_min) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"startTime and endTime cannot be equal");
return ESP_FAIL;
}
*/
// Aplica config no módulo scheduler (ele trata de NVS + eventos)
scheduler_set_config(&cfg);
cJSON_Delete(json);
httpd_resp_sendstr(req, "Scheduler config atualizada com sucesso");
return ESP_OK;
}
/* =========================
* Registo dos handlers
* ========================= */
void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
{
httpd_uri_t get_uri = {
.uri = "/api/v1/config/scheduler",
.method = HTTP_GET,
.handler = scheduler_config_get_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = {
.uri = "/api/v1/config/scheduler",
.method = HTTP_POST,
.handler = scheduler_config_post_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &post_uri);
ESP_LOGI(TAG, "Scheduler REST handlers registered");
}

View File

@@ -0,0 +1,7 @@
set(srcs "src/scheduler_types.c" "src/scheduler.c" "src/scheduler_events.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash esp_timer
REQUIRES esp_event evse)

View File

@@ -0,0 +1,2 @@
version: "2.6.0"
description: Authentication component

View File

@@ -0,0 +1,18 @@
// components/scheduler/include/scheduler.h
#pragma once
#include <stdbool.h>
#include "scheduler_types.h"
#ifdef __cplusplus
extern "C" {
#endif
void scheduler_init(void);
void scheduler_set_config(const sched_config_t *cfg);
sched_config_t scheduler_get_config(void);
bool scheduler_is_allowed_now(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,17 @@
// scheduler_events.h
#pragma once
#include "esp_event.h"
#include <stdbool.h>
ESP_EVENT_DECLARE_BASE(SCHED_EVENTS);
typedef enum
{
SCHED_EVENT_INIT = 0, // envia estado inicial
SCHED_EVENT_WINDOW_CHANGED, // allowed_now mudou
} sched_event_id_t;
typedef struct
{
bool allowed_now;
} sched_event_state_t;

View File

@@ -0,0 +1,31 @@
// components/scheduler/include/scheduler_types.h
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
SCHED_MODE_DISABLED = 0, // não faz gating
SCHED_MODE_SIMPLE, // por ex: janela diária única
SCHED_MODE_WEEKLY // por ex: janelas por dia da semana
} sched_mode_t;
typedef struct {
bool enabled; // gating ativo?
sched_mode_t mode;
// exemplo bem simples: uma janela diária [start_min..end_min[
// minutos desde meia-noite, 0..1439
uint16_t start_min;
uint16_t end_min;
} sched_config_t;
const char *sched_mode_to_str(sched_mode_t mode);
bool sched_mode_from_str(const char *s, sched_mode_t *out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,251 @@
// components/scheduler/src/scheduler.c
#include "scheduler.h"
#include "scheduler_types.h"
#include "scheduler_events.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <time.h>
#include <string.h>
static const char *TAG = "scheduler";
/* ===== Estado interno ===== */
static sched_config_t s_cfg = {
.enabled = false,
.mode = SCHED_MODE_DISABLED,
.start_min = 0,
.end_min = 24 * 60};
static bool s_allowed_now = true;
static TaskHandle_t s_task_handle = NULL;
/* ===== NVS ===== */
#define NVS_NAMESPACE "scheduler"
#define NVS_KEY_ENABLED "enabled"
#define NVS_KEY_MODE "mode"
#define NVS_KEY_START_MIN "start_min"
#define NVS_KEY_END_MIN "end_min"
static void load_config_from_nvs(sched_config_t *out)
{
if (!out)
return;
*out = s_cfg; // defaults
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No scheduler namespace in NVS (%s), using defaults",
esp_err_to_name(err));
return;
}
uint8_t u8;
uint16_t u16;
if (nvs_get_u8(h, NVS_KEY_ENABLED, &u8) == ESP_OK)
{
out->enabled = (u8 != 0);
}
if (nvs_get_u8(h, NVS_KEY_MODE, &u8) == ESP_OK)
{
if (u8 <= (uint8_t)SCHED_MODE_SIMPLE)
{
out->mode = (sched_mode_t)u8;
}
}
if (nvs_get_u16(h, NVS_KEY_START_MIN, &u16) == ESP_OK)
{
out->start_min = u16;
}
if (nvs_get_u16(h, NVS_KEY_END_MIN, &u16) == ESP_OK)
{
out->end_min = u16;
}
nvs_close(h);
ESP_LOGI(TAG, "Loaded from NVS: enabled=%d mode=%d start=%u end=%u",
out->enabled, out->mode,
(unsigned)out->start_min, (unsigned)out->end_min);
}
static void save_config_to_nvs(const sched_config_t *cfg)
{
if (!cfg)
return;
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(h, NVS_KEY_ENABLED, cfg->enabled ? 1 : 0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(enabled) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u8(h, NVS_KEY_MODE, (uint8_t)cfg->mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_START_MIN, cfg->start_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(start_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_END_MIN, cfg->end_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(end_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed: %s", esp_err_to_name(err));
}
else
{
ESP_LOGI(TAG, "Scheduler config saved to NVS");
}
out:
nvs_close(h);
}
/* ===== Lógica de janelas ===== */
static bool compute_allowed_now(const sched_config_t *cfg)
{
if (!cfg->enabled || cfg->mode == SCHED_MODE_DISABLED)
{
// se desativado, não limita nada
return true;
}
time_t now = time(NULL);
struct tm tm_now = {0};
localtime_r(&now, &tm_now);
uint16_t now_min = (uint16_t)(tm_now.tm_hour * 60 + tm_now.tm_min);
// janela [start_min, end_min)
if (cfg->start_min <= cfg->end_min)
{
// Ex: 08:0018:00
return (now_min >= cfg->start_min && now_min < cfg->end_min);
}
else
{
// janela que passa pela meia-noite, ex: 22:0006:00
return (now_min >= cfg->start_min || now_min < cfg->end_min);
}
}
static void scheduler_recompute_and_emit(void)
{
bool new_allowed = compute_allowed_now(&s_cfg);
if (new_allowed != s_allowed_now)
{
s_allowed_now = new_allowed;
ESP_LOGI(TAG, "allowed_now changed -> %d", s_allowed_now);
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_WINDOW_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
}
}
/* ===== Task do scheduler ===== */
static void scheduler_task(void *arg)
{
const TickType_t period = pdMS_TO_TICKS(60000); // 60s
ESP_LOGI(TAG, "Scheduler task started");
for (;;)
{
scheduler_recompute_and_emit();
vTaskDelay(period);
}
}
/* ===== API pública ===== */
void scheduler_init(void)
{
// 1) carregar config
load_config_from_nvs(&s_cfg);
// 2) calcular estado inicial
s_allowed_now = compute_allowed_now(&s_cfg);
// 3) enviar evento INIT
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_INIT,
&ev,
sizeof(ev),
portMAX_DELAY);
ESP_LOGI(TAG, "Init: allowed_now=%d", s_allowed_now);
// 4) criar a task
if (s_task_handle == NULL)
{
xTaskCreate(
scheduler_task,
"scheduler_task",
4 * 1024,
NULL,
3, // prioridade razoável
&s_task_handle);
}
}
void scheduler_set_config(const sched_config_t *cfg)
{
if (!cfg)
return;
s_cfg = *cfg;
save_config_to_nvs(&s_cfg);
// recomputa imediatamente para refletir mudança
scheduler_recompute_and_emit();
}
sched_config_t scheduler_get_config(void)
{
return s_cfg;
}
bool scheduler_is_allowed_now(void)
{
return s_allowed_now;
}

View File

@@ -0,0 +1,4 @@
// scheduler_events.c
#include "scheduler_events.h"
ESP_EVENT_DEFINE_BASE(SCHED_EVENTS);

View File

@@ -0,0 +1,22 @@
// components/scheduler/src/scheduler_types.c
#include "scheduler_types.h"
#include <strings.h>
const char *sched_mode_to_str(sched_mode_t mode)
{
switch (mode) {
case SCHED_MODE_DISABLED: return "disabled";
case SCHED_MODE_SIMPLE: return "simple";
case SCHED_MODE_WEEKLY: return "weekly";
default: return "disabled";
}
}
bool sched_mode_from_str(const char *s, sched_mode_t *out)
{
if (!s || !out) return false;
if (!strcasecmp(s, "disabled")) { *out = SCHED_MODE_DISABLED; return true; }
if (!strcasecmp(s, "simple")) { *out = SCHED_MODE_SIMPLE; return true; }
if (!strcasecmp(s, "weekly")) { *out = SCHED_MODE_WEEKLY; return true; }
return false;
}

View File

@@ -1,18 +0,0 @@
set(srcs
"protobuf/nanopb/pb_common.c"
"protobuf/nanopb/pb_decode.c"
"protobuf/nanopb/pb_encode.c"
"protobuf/LoToHi.pb.c"
"protobuf/HiToLo.pb.c"
"src/sync_slave.c"
"src/sync_master.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" "protobuf"
PRIV_REQUIRES driver esp_timer
REQUIRES config evse loadbalancer)

View File

@@ -1,23 +0,0 @@
#ifndef SYNC_MASTER_H_
#define SYNC_MASTER_H_
/**
* @brief Send Grid Current
*
*/
void send_grid_current(uint32_t grid_current);
/**
* @brief Start master SYNC
*
*/
void master_sync_start();
/**
* @brief Stop master SYNC
*
*/
void master_sync_stop(void);
#endif /* SYNC_MASTER_H_ */

View File

@@ -1,18 +0,0 @@
#ifndef SYNC_SLAVE_H_
#define SYNC_SLAVE_H_
/**
* @brief Start slave SYNC
*
*/
void slave_sync_start();
/**
* @brief Stop slave SYNC
*
*/
void slave_sync_stop(void);
#endif /* SYNC_SLAVE_H_ */

View File

@@ -1,12 +0,0 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9-dev */
#include "HiToLo.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(HiToLo, HiToLo, AUTO)

View File

@@ -1,70 +0,0 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9-dev */
#ifndef PB_HITOLO_PB_H_INCLUDED
#define PB_HITOLO_PB_H_INCLUDED
#include <nanopb/pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
/* This container message is send from Hi To Lo and may contain any allowed message in that direction. */
typedef struct _HiToLo {
pb_size_t which_payload;
union {
uint32_t time_stamp;
bool connector_lock; /* false: unlock, true: lock */
uint32_t max_charging_current;
bool allow_power_on;
bool reset;
uint32_t grid_current;
uint32_t max_grid_current;
} payload;
} HiToLo;
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define HiToLo_init_default {0, {0}}
#define HiToLo_init_zero {0, {0}}
/* Field tags (for use in manual encoding/decoding) */
#define HiToLo_time_stamp_tag 1
#define HiToLo_connector_lock_tag 2
#define HiToLo_max_charging_current_tag 3
#define HiToLo_allow_power_on_tag 4
#define HiToLo_reset_tag 5
#define HiToLo_grid_current_tag 6
#define HiToLo_max_grid_current_tag 7
/* Struct field encoding specification for nanopb */
#define HiToLo_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, UINT32, (payload,time_stamp,payload.time_stamp), 1) \
X(a, STATIC, ONEOF, BOOL, (payload,connector_lock,payload.connector_lock), 2) \
X(a, STATIC, ONEOF, UINT32, (payload,max_charging_current,payload.max_charging_current), 3) \
X(a, STATIC, ONEOF, BOOL, (payload,allow_power_on,payload.allow_power_on), 4) \
X(a, STATIC, ONEOF, BOOL, (payload,reset,payload.reset), 5) \
X(a, STATIC, ONEOF, UINT32, (payload,grid_current,payload.grid_current), 6) \
X(a, STATIC, ONEOF, UINT32, (payload,max_grid_current,payload.max_grid_current), 7)
#define HiToLo_CALLBACK NULL
#define HiToLo_DEFAULT NULL
extern const pb_msgdesc_t HiToLo_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define HiToLo_fields &HiToLo_msg
/* Maximum encoded size of messages (where known) */
#define HITOLO_PB_H_MAX_SIZE HiToLo_size
#define HiToLo_size 6
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -1,16 +0,0 @@
syntax = "proto3";
/*
This container message is send from Hi To Lo and may contain any allowed message in that direction.
*/
message HiToLo {
oneof payload {
uint32 time_stamp = 1;
bool connector_lock = 2; // false: unlock, true: lock
uint32 max_charging_current = 3;
bool allow_power_on = 4;
bool reset = 5;
uint32 grid_current = 6;
uint32 max_grid_current = 7;
}
}

View File

@@ -1,21 +0,0 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9-dev */
#include "LoToHi.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(LoToHi, LoToHi, AUTO)
PB_BIND(ErrorFlags, ErrorFlags, AUTO)
PB_BIND(PowerMeter, PowerMeter, AUTO)

View File

@@ -1,237 +0,0 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9-dev */
#ifndef PB_LOTOHI_PB_H_INCLUDED
#define PB_LOTOHI_PB_H_INCLUDED
#include <nanopb/pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _CpState {
CpState_EVSE_STATE_A = 0,
CpState_EVSE_STATE_B1 = 1,
CpState_EVSE_STATE_B2 = 2,
CpState_EVSE_STATE_C1 = 3,
CpState_EVSE_STATE_C2 = 4,
CpState_EVSE_STATE_D1 = 5,
CpState_EVSE_STATE_D2 = 6,
CpState_EVSE_STATE_E = 7,
CpState_EVSE_STATE_F = 8
} CpState;
typedef enum _PpState {
PpState_STATE_NC = 0,
PpState_STATE_13A = 1,
PpState_STATE_20A = 2,
PpState_STATE_32A = 3,
PpState_STATE_70A = 4,
PpState_STATE_FAULT = 5
} PpState;
typedef enum _LockState {
LockState_UNDEFINED = 0,
LockState_UNLOCKED = 1,
LockState_LOCKED = 2
} LockState;
/* Struct definitions */
typedef struct _ErrorFlags {
bool diode_fault;
bool rcd_selftest_failed;
bool rcd_triggered;
bool ventilation_not_available;
bool connector_lock_failed;
bool cp_signal_fault;
} ErrorFlags;
typedef struct _PowerMeter {
uint32_t time_stamp;
float vrmsL1;
float vrmsL2;
float vrmsL3;
float irmsL1;
float irmsL2;
float irmsL3;
float irmsN;
float wattHrL1;
float wattHrL2;
float wattHrL3;
float totalWattHr;
float tempL1;
float tempL2;
float tempL3;
float tempN;
float wattL1;
float wattL2;
float wattL3;
float freqL1;
float freqL2;
float freqL3;
bool phaseSeqError;
} PowerMeter;
/* This container message is send from Lo To Hi and may contain any allowed message in that direction. */
typedef struct _LoToHi {
pb_size_t which_payload;
union {
uint32_t time_stamp;
bool relais_state; /* false: relais are off, true: relais are on */
ErrorFlags error_flags;
CpState cp_state;
PpState pp_state;
uint32_t max_charging_current;
uint32_t max_grid_current;
LockState lock_state;
PowerMeter power_meter;
} payload;
} LoToHi;
#ifdef __cplusplus
extern "C" {
#endif
/* Helper constants for enums */
#define _CpState_MIN CpState_EVSE_STATE_A
#define _CpState_MAX CpState_EVSE_STATE_F
#define _CpState_ARRAYSIZE ((CpState)(CpState_EVSE_STATE_F+1))
#define _PpState_MIN PpState_STATE_NC
#define _PpState_MAX PpState_STATE_FAULT
#define _PpState_ARRAYSIZE ((PpState)(PpState_STATE_FAULT+1))
#define _LockState_MIN LockState_UNDEFINED
#define _LockState_MAX LockState_LOCKED
#define _LockState_ARRAYSIZE ((LockState)(LockState_LOCKED+1))
#define LoToHi_payload_cp_state_ENUMTYPE CpState
#define LoToHi_payload_pp_state_ENUMTYPE PpState
#define LoToHi_payload_lock_state_ENUMTYPE LockState
/* Initializer values for message structs */
#define LoToHi_init_default {0, {0}}
#define ErrorFlags_init_default {0, 0, 0, 0, 0, 0}
#define PowerMeter_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define LoToHi_init_zero {0, {0}}
#define ErrorFlags_init_zero {0, 0, 0, 0, 0, 0}
#define PowerMeter_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define ErrorFlags_diode_fault_tag 1
#define ErrorFlags_rcd_selftest_failed_tag 2
#define ErrorFlags_rcd_triggered_tag 3
#define ErrorFlags_ventilation_not_available_tag 4
#define ErrorFlags_connector_lock_failed_tag 5
#define ErrorFlags_cp_signal_fault_tag 6
#define PowerMeter_time_stamp_tag 1
#define PowerMeter_vrmsL1_tag 2
#define PowerMeter_vrmsL2_tag 3
#define PowerMeter_vrmsL3_tag 4
#define PowerMeter_irmsL1_tag 5
#define PowerMeter_irmsL2_tag 6
#define PowerMeter_irmsL3_tag 7
#define PowerMeter_irmsN_tag 8
#define PowerMeter_wattHrL1_tag 9
#define PowerMeter_wattHrL2_tag 10
#define PowerMeter_wattHrL3_tag 11
#define PowerMeter_totalWattHr_tag 12
#define PowerMeter_tempL1_tag 13
#define PowerMeter_tempL2_tag 14
#define PowerMeter_tempL3_tag 15
#define PowerMeter_tempN_tag 16
#define PowerMeter_wattL1_tag 17
#define PowerMeter_wattL2_tag 18
#define PowerMeter_wattL3_tag 19
#define PowerMeter_freqL1_tag 20
#define PowerMeter_freqL2_tag 21
#define PowerMeter_freqL3_tag 22
#define PowerMeter_phaseSeqError_tag 23
#define LoToHi_time_stamp_tag 1
#define LoToHi_relais_state_tag 2
#define LoToHi_error_flags_tag 3
#define LoToHi_cp_state_tag 4
#define LoToHi_pp_state_tag 5
#define LoToHi_max_charging_current_tag 6
#define LoToHi_max_grid_current_tag 7
#define LoToHi_lock_state_tag 8
#define LoToHi_power_meter_tag 9
/* Struct field encoding specification for nanopb */
#define LoToHi_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, UINT32, (payload,time_stamp,payload.time_stamp), 1) \
X(a, STATIC, ONEOF, BOOL, (payload,relais_state,payload.relais_state), 2) \
X(a, STATIC, ONEOF, MESSAGE, (payload,error_flags,payload.error_flags), 3) \
X(a, STATIC, ONEOF, UENUM, (payload,cp_state,payload.cp_state), 4) \
X(a, STATIC, ONEOF, UENUM, (payload,pp_state,payload.pp_state), 5) \
X(a, STATIC, ONEOF, UINT32, (payload,max_charging_current,payload.max_charging_current), 6) \
X(a, STATIC, ONEOF, UINT32, (payload,max_grid_current,payload.max_grid_current), 7) \
X(a, STATIC, ONEOF, UENUM, (payload,lock_state,payload.lock_state), 8) \
X(a, STATIC, ONEOF, MESSAGE, (payload,power_meter,payload.power_meter), 9)
#define LoToHi_CALLBACK NULL
#define LoToHi_DEFAULT NULL
#define LoToHi_payload_error_flags_MSGTYPE ErrorFlags
#define LoToHi_payload_power_meter_MSGTYPE PowerMeter
#define ErrorFlags_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, diode_fault, 1) \
X(a, STATIC, SINGULAR, BOOL, rcd_selftest_failed, 2) \
X(a, STATIC, SINGULAR, BOOL, rcd_triggered, 3) \
X(a, STATIC, SINGULAR, BOOL, ventilation_not_available, 4) \
X(a, STATIC, SINGULAR, BOOL, connector_lock_failed, 5) \
X(a, STATIC, SINGULAR, BOOL, cp_signal_fault, 6)
#define ErrorFlags_CALLBACK NULL
#define ErrorFlags_DEFAULT NULL
#define PowerMeter_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, time_stamp, 1) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL1, 2) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL2, 3) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL3, 4) \
X(a, STATIC, SINGULAR, FLOAT, irmsL1, 5) \
X(a, STATIC, SINGULAR, FLOAT, irmsL2, 6) \
X(a, STATIC, SINGULAR, FLOAT, irmsL3, 7) \
X(a, STATIC, SINGULAR, FLOAT, irmsN, 8) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL1, 9) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL2, 10) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL3, 11) \
X(a, STATIC, SINGULAR, FLOAT, totalWattHr, 12) \
X(a, STATIC, SINGULAR, FLOAT, tempL1, 13) \
X(a, STATIC, SINGULAR, FLOAT, tempL2, 14) \
X(a, STATIC, SINGULAR, FLOAT, tempL3, 15) \
X(a, STATIC, SINGULAR, FLOAT, tempN, 16) \
X(a, STATIC, SINGULAR, FLOAT, wattL1, 17) \
X(a, STATIC, SINGULAR, FLOAT, wattL2, 18) \
X(a, STATIC, SINGULAR, FLOAT, wattL3, 19) \
X(a, STATIC, SINGULAR, FLOAT, freqL1, 20) \
X(a, STATIC, SINGULAR, FLOAT, freqL2, 21) \
X(a, STATIC, SINGULAR, FLOAT, freqL3, 22) \
X(a, STATIC, SINGULAR, BOOL, phaseSeqError, 23)
#define PowerMeter_CALLBACK NULL
#define PowerMeter_DEFAULT NULL
extern const pb_msgdesc_t LoToHi_msg;
extern const pb_msgdesc_t ErrorFlags_msg;
extern const pb_msgdesc_t PowerMeter_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define LoToHi_fields &LoToHi_msg
#define ErrorFlags_fields &ErrorFlags_msg
#define PowerMeter_fields &PowerMeter_msg
/* Maximum encoded size of messages (where known) */
#define ErrorFlags_size 12
#define LOTOHI_PB_H_MAX_SIZE LoToHi_size
#define LoToHi_size 123
#define PowerMeter_size 121
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -1,80 +0,0 @@
syntax = "proto3";
/*
This container message is send from Lo To Hi and may contain any allowed message in that direction.
*/
message LoToHi {
oneof payload {
uint32 time_stamp = 1;
bool relais_state = 2; // false: relais are off, true: relais are on
ErrorFlags error_flags = 3;
CpState cp_state = 4;
PpState pp_state = 5;
uint32 max_charging_current = 6;
uint32 max_grid_current = 7;
LockState lock_state = 8;
PowerMeter power_meter = 9;
}
}
message ErrorFlags {
bool diode_fault = 1;
bool rcd_selftest_failed = 2;
bool rcd_triggered = 3;
bool ventilation_not_available = 4;
bool connector_lock_failed = 5;
bool cp_signal_fault = 6;
}
enum CpState {
EVSE_STATE_A = 0;
EVSE_STATE_B1 = 1;
EVSE_STATE_B2 = 2;
EVSE_STATE_C1 = 3;
EVSE_STATE_C2 = 4;
EVSE_STATE_D1 = 5;
EVSE_STATE_D2 = 6;
EVSE_STATE_E = 7;
EVSE_STATE_F = 8;
}
enum PpState {
STATE_NC = 0;
STATE_13A = 1;
STATE_20A = 2;
STATE_32A = 3;
STATE_70A = 4;
STATE_FAULT = 5;
}
enum LockState {
UNDEFINED = 0;
UNLOCKED = 1;
LOCKED = 2;
}
message PowerMeter {
uint32 time_stamp = 1;
float vrmsL1 = 2;
float vrmsL2 = 3;
float vrmsL3 = 4;
float irmsL1 = 5;
float irmsL2 = 6;
float irmsL3 = 7;
float irmsN = 8;
float wattHrL1 = 9;
float wattHrL2 = 10;
float wattHrL3 = 11;
float totalWattHr = 12;
float tempL1 = 13;
float tempL2 = 14;
float tempL3 = 15;
float tempN = 16;
float wattL1 = 17;
float wattL2 = 18;
float wattL3 = 19;
float freqL1 = 20;
float freqL2 = 21;
float freqL3 = 22;
bool phaseSeqError = 23;
}

View File

@@ -1,2 +0,0 @@
#!/bin/sh
/home/ricar/Transferências/nanopb-master/generator/nanopb_generator.py -L "#include <nanopb/%s>" -I . -D . LoToHi.proto HiToLo.proto

View File

@@ -1,7 +0,0 @@
#!/bin/bash
VERSION=$(git describe --dirty --always --tags)
echo "#ifndef _VERSION_AUTOGEN_H_" > ../Yeti/Inc/version_autogen.h
echo "#define _VERSION_AUTOGEN_H_" >> ../Yeti/Inc/version_autogen.h
echo "#define VERSION_STRING \"$VERSION\"" >> ../Yeti/Inc/version_autogen.h
echo "#endif" >> ../Yeti/Inc/version_autogen.h

View File

@@ -1,875 +0,0 @@
/* Common parts of the nanopb library. Most of these are quite low-level
* stuff. For the high-level interface, see pb_encode.h and pb_decode.h.
*/
#ifndef PB_H_INCLUDED
#define PB_H_INCLUDED
/*****************************************************************
* Nanopb compilation time options. You can change these here by *
* uncommenting the lines, or on the compiler command line. *
*****************************************************************/
/* Enable support for dynamically allocated fields */
/* #define PB_ENABLE_MALLOC 1 */
/* Define this if your CPU / compiler combination does not support
* unaligned memory access to packed structures. */
/* #define PB_NO_PACKED_STRUCTS 1 */
/* Increase the number of required fields that are tracked.
* A compiler warning will tell if you need this. */
/* #define PB_MAX_REQUIRED_FIELDS 256 */
/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */
/* #define PB_FIELD_32BIT 1 */
/* Disable support for error messages in order to save some code space. */
/* #define PB_NO_ERRMSG 1 */
/* Disable support for custom streams (support only memory buffers). */
/* #define PB_BUFFER_ONLY 1 */
/* Disable support for 64-bit datatypes, for compilers without int64_t
or to save some code space. */
/* #define PB_WITHOUT_64BIT 1 */
/* Don't encode scalar arrays as packed. This is only to be used when
* the decoder on the receiving side cannot process packed scalar arrays.
* Such example is older protobuf.js. */
/* #define PB_ENCODE_ARRAYS_UNPACKED 1 */
/* Enable conversion of doubles to floats for platforms that do not
* support 64-bit doubles. Most commonly AVR. */
/* #define PB_CONVERT_DOUBLE_FLOAT 1 */
/* Check whether incoming strings are valid UTF-8 sequences. Slows down
* the string processing slightly and slightly increases code size. */
/* #define PB_VALIDATE_UTF8 1 */
/******************************************************************
* You usually don't need to change anything below this line. *
* Feel free to look around and use the defined macros, though. *
******************************************************************/
/* Version of the nanopb library. Just in case you want to check it in
* your own program. */
#define NANOPB_VERSION nanopb-0.4.5
/* Include all the system headers needed by nanopb. You will need the
* definitions of the following:
* - strlen, memcpy, memset functions
* - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t
* - size_t
* - bool
*
* If you don't have the standard header files, you can instead provide
* a custom header that defines or includes all this. In that case,
* define PB_SYSTEM_HEADER to the path of this file.
*/
#ifdef PB_SYSTEM_HEADER
#include PB_SYSTEM_HEADER
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#ifdef PB_ENABLE_MALLOC
#include <stdlib.h>
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Macro for defining packed structures (compiler dependent).
* This just reduces memory requirements, but is not required.
*/
#if defined(PB_NO_PACKED_STRUCTS)
/* Disable struct packing */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
#elif defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed __attribute__((packed))
#elif defined(__ICCARM__) || defined(__CC_ARM)
/* For IAR ARM and Keil MDK-ARM compilers */
# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
# define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
# define pb_packed
#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
/* For Microsoft Visual C++ */
# define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
# define PB_PACKED_STRUCT_END __pragma(pack(pop))
# define pb_packed
#else
/* Unknown compiler */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
#endif
/* Handly macro for suppressing unreferenced-parameter compiler warnings. */
#ifndef PB_UNUSED
#define PB_UNUSED(x) (void)(x)
#endif
/* Harvard-architecture processors may need special attributes for storing
* field information in program memory. */
#ifndef PB_PROGMEM
#ifdef __AVR__
#include <avr/pgmspace.h>
#define PB_PROGMEM PROGMEM
#define PB_PROGMEM_READU32(x) pgm_read_dword(&x)
#else
#define PB_PROGMEM
#define PB_PROGMEM_READU32(x) (x)
#endif
#endif
/* Compile-time assertion, used for checking compatible compilation options.
* If this does not work properly on your compiler, use
* #define PB_NO_STATIC_ASSERT to disable it.
*
* But before doing that, check carefully the error message / place where it
* comes from to see if the error has a real cause. Unfortunately the error
* message is not always very clear to read, but you can see the reason better
* in the place where the PB_STATIC_ASSERT macro was called.
*/
#ifndef PB_NO_STATIC_ASSERT
# ifndef PB_STATIC_ASSERT
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* C11 standard _Static_assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG);
# else
/* Classic negative-size-array static assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1];
# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER
# endif
# endif
#else
/* Static asserts disabled by PB_NO_STATIC_ASSERT */
# define PB_STATIC_ASSERT(COND,MSG)
#endif
/* Number of required fields to keep track of. */
#ifndef PB_MAX_REQUIRED_FIELDS
#define PB_MAX_REQUIRED_FIELDS 64
#endif
#if PB_MAX_REQUIRED_FIELDS < 64
#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64).
#endif
#ifdef PB_WITHOUT_64BIT
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Cannot use doubles without 64-bit types */
#undef PB_CONVERT_DOUBLE_FLOAT
#endif
#endif
/* List of possible field types. These are used in the autogenerated code.
* Least-significant 4 bits tell the scalar type
* Most-significant 4 bits specify repeated/required/packed etc.
*/
typedef uint_least8_t pb_type_t;
/**** Field data types ****/
/* Numeric types */
#define PB_LTYPE_BOOL 0x00U /* bool */
#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */
#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */
#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */
#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */
#define PB_LTYPE_FIXED64 0x05U /* fixed64, sfixed64, double */
/* Marker for last packable field type. */
#define PB_LTYPE_LAST_PACKABLE 0x05U
/* Byte array with pre-allocated buffer.
* data_size is the length of the allocated PB_BYTES_ARRAY structure. */
#define PB_LTYPE_BYTES 0x06U
/* String with pre-allocated buffer.
* data_size is the maximum length. */
#define PB_LTYPE_STRING 0x07U
/* Submessage
* submsg_fields is pointer to field descriptions */
#define PB_LTYPE_SUBMESSAGE 0x08U
/* Submessage with pre-decoding callback
* The pre-decoding callback is stored as pb_callback_t right before pSize.
* submsg_fields is pointer to field descriptions */
#define PB_LTYPE_SUBMSG_W_CB 0x09U
/* Extension pseudo-field
* The field contains a pointer to pb_extension_t */
#define PB_LTYPE_EXTENSION 0x0AU
/* Byte array with inline, pre-allocated byffer.
* data_size is the length of the inline, allocated buffer.
* This differs from PB_LTYPE_BYTES by defining the element as
* pb_byte_t[data_size] rather than pb_bytes_array_t. */
#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0BU
/* Number of declared LTYPES */
#define PB_LTYPES_COUNT 0x0CU
#define PB_LTYPE_MASK 0x0FU
/**** Field repetition rules ****/
#define PB_HTYPE_REQUIRED 0x00U
#define PB_HTYPE_OPTIONAL 0x10U
#define PB_HTYPE_SINGULAR 0x10U
#define PB_HTYPE_REPEATED 0x20U
#define PB_HTYPE_FIXARRAY 0x20U
#define PB_HTYPE_ONEOF 0x30U
#define PB_HTYPE_MASK 0x30U
/**** Field allocation types ****/
#define PB_ATYPE_STATIC 0x00U
#define PB_ATYPE_POINTER 0x80U
#define PB_ATYPE_CALLBACK 0x40U
#define PB_ATYPE_MASK 0xC0U
#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK)
#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK)
#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK)
#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \
PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB)
/* Data type used for storing sizes of struct fields
* and array counts.
*/
#if defined(PB_FIELD_32BIT)
typedef uint32_t pb_size_t;
typedef int32_t pb_ssize_t;
#else
typedef uint_least16_t pb_size_t;
typedef int_least16_t pb_ssize_t;
#endif
#define PB_SIZE_MAX ((pb_size_t)-1)
/* Data type for storing encoded data and other byte streams.
* This typedef exists to support platforms where uint8_t does not exist.
* You can regard it as equivalent on uint8_t on other platforms.
*/
typedef uint_least8_t pb_byte_t;
/* Forward declaration of struct types */
typedef struct pb_istream_s pb_istream_t;
typedef struct pb_ostream_s pb_ostream_t;
typedef struct pb_field_iter_s pb_field_iter_t;
/* This structure is used in auto-generated constants
* to specify struct fields.
*/
typedef struct pb_msgdesc_s pb_msgdesc_t;
struct pb_msgdesc_s {
const uint32_t *field_info;
const pb_msgdesc_t * const * submsg_info;
const pb_byte_t *default_value;
bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field);
pb_size_t field_count;
pb_size_t required_field_count;
pb_size_t largest_tag;
};
/* Iterator for message descriptor */
struct pb_field_iter_s {
const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */
void *message; /* Pointer to start of the structure */
pb_size_t index; /* Index of the field */
pb_size_t field_info_index; /* Index to descriptor->field_info array */
pb_size_t required_field_index; /* Index that counts only the required fields */
pb_size_t submessage_index; /* Index that counts only submessages */
pb_size_t tag; /* Tag of current field */
pb_size_t data_size; /* sizeof() of a single item */
pb_size_t array_size; /* Number of array entries */
pb_type_t type; /* Type of current field */
void *pField; /* Pointer to current field in struct */
void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */
void *pSize; /* Pointer to count/has field */
const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */
};
/* For compatibility with legacy code */
typedef pb_field_iter_t pb_field_t;
/* Make sure that the standard integer types are of the expected sizes.
* Otherwise fixed32/fixed64 fields can break.
*
* If you get errors here, it probably means that your stdint.h is not
* correct for your platform.
*/
#ifndef PB_WITHOUT_64BIT
PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE)
PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE)
#endif
/* This structure is used for 'bytes' arrays.
* It has the number of bytes in the beginning, and after that an array.
* Note that actual structs used will have a different length of bytes array.
*/
#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; }
#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes))
struct pb_bytes_array_s {
pb_size_t size;
pb_byte_t bytes[1];
};
typedef struct pb_bytes_array_s pb_bytes_array_t;
/* This structure is used for giving the callback function.
* It is stored in the message structure and filled in by the method that
* calls pb_decode.
*
* The decoding callback will be given a limited-length stream
* If the wire type was string, the length is the length of the string.
* If the wire type was a varint/fixed32/fixed64, the length is the length
* of the actual value.
* The function may be called multiple times (especially for repeated types,
* but also otherwise if the message happens to contain the field multiple
* times.)
*
* The encoding callback will receive the actual output stream.
* It should write all the data in one call, including the field tag and
* wire type. It can write multiple fields.
*
* The callback can be null if you want to skip a field.
*/
typedef struct pb_callback_s pb_callback_t;
struct pb_callback_s {
/* Callback functions receive a pointer to the arg field.
* You can access the value of the field as *arg, and modify it if needed.
*/
union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
} funcs;
/* Free arg for use by callback */
void *arg;
};
extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
/* Wire types. Library user needs these only in encoder callbacks. */
typedef enum {
PB_WT_VARINT = 0,
PB_WT_64BIT = 1,
PB_WT_STRING = 2,
PB_WT_32BIT = 5
} pb_wire_type_t;
/* Structure for defining the handling of unknown/extension fields.
* Usually the pb_extension_type_t structure is automatically generated,
* while the pb_extension_t structure is created by the user. However,
* if you want to catch all unknown fields, you can also create a custom
* pb_extension_type_t with your own callback.
*/
typedef struct pb_extension_type_s pb_extension_type_t;
typedef struct pb_extension_s pb_extension_t;
struct pb_extension_type_s {
/* Called for each unknown field in the message.
* If you handle the field, read off all of its data and return true.
* If you do not handle the field, do not read anything and return true.
* If you run into an error, return false.
* Set to NULL for default handler.
*/
bool (*decode)(pb_istream_t *stream, pb_extension_t *extension,
uint32_t tag, pb_wire_type_t wire_type);
/* Called once after all regular fields have been encoded.
* If you have something to write, do so and return true.
* If you do not have anything to write, just return true.
* If you run into an error, return false.
* Set to NULL for default handler.
*/
bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension);
/* Free field for use by the callback. */
const void *arg;
};
struct pb_extension_s {
/* Type describing the extension field. Usually you'll initialize
* this to a pointer to the automatically generated structure. */
const pb_extension_type_t *type;
/* Destination for the decoded data. This must match the datatype
* of the extension field. */
void *dest;
/* Pointer to the next extension handler, or NULL.
* If this extension does not match a field, the next handler is
* automatically called. */
pb_extension_t *next;
/* The decoder sets this to true if the extension was found.
* Ignored for encoding. */
bool found;
};
#define pb_extension_init_zero {NULL,NULL,NULL,false}
/* Memory allocation functions to use. You can define pb_realloc and
* pb_free to custom functions if you want. */
#ifdef PB_ENABLE_MALLOC
# ifndef pb_realloc
# define pb_realloc(ptr, size) realloc(ptr, size)
# endif
# ifndef pb_free
# define pb_free(ptr) free(ptr)
# endif
#endif
/* This is used to inform about need to regenerate .pb.h/.pb.c files. */
#define PB_PROTO_HEADER_VERSION 40
/* These macros are used to declare pb_field_t's in the constant array. */
/* Size of a structure member, in bytes. */
#define pb_membersize(st, m) (sizeof ((st*)0)->m)
/* Number of entries in an array. */
#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0]))
/* Delta from start of one member to the start of another member. */
#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2))
/* Force expansion of macro value */
#define PB_EXPAND(x) x
/* Binding of a message field set into a specific structure */
#define PB_BIND(msgname, structname, width) \
const uint32_t structname ## _field_info[] PB_PROGMEM = \
{ \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \
0 \
}; \
const pb_msgdesc_t* const structname ## _submsg_info[] = \
{ \
msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \
NULL \
}; \
const pb_msgdesc_t structname ## _msg = \
{ \
structname ## _field_info, \
structname ## _submsg_info, \
msgname ## _DEFAULT, \
msgname ## _CALLBACK, \
0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \
}; \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname)
#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1
#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \
+ (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED)
#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \
* 0 + tag
/* X-macro for generating the entries in struct_field_info[] array. */
#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
/* X-macro for generating asserts that entries fit in struct_field_info[] array.
* The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(),
* but it is not easily reused because of how macro substitutions work. */
#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname)
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname)
#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname)
#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname)
#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname)
#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count)
#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname)
#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1
#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1
#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1
#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1
#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1
#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname)
#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname)
#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0])
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname)
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname)
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname)
#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0])
#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0])
#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple)
#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname
#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername
#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname
#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \
PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname)
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname))
#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername)
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE)
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SI_PB_LTYPE_BOOL(t)
#define PB_SI_PB_LTYPE_BYTES(t)
#define PB_SI_PB_LTYPE_DOUBLE(t)
#define PB_SI_PB_LTYPE_ENUM(t)
#define PB_SI_PB_LTYPE_UENUM(t)
#define PB_SI_PB_LTYPE_FIXED32(t)
#define PB_SI_PB_LTYPE_FIXED64(t)
#define PB_SI_PB_LTYPE_FLOAT(t)
#define PB_SI_PB_LTYPE_INT32(t)
#define PB_SI_PB_LTYPE_INT64(t)
#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_SFIXED32(t)
#define PB_SI_PB_LTYPE_SFIXED64(t)
#define PB_SI_PB_LTYPE_SINT32(t)
#define PB_SI_PB_LTYPE_SINT64(t)
#define PB_SI_PB_LTYPE_STRING(t)
#define PB_SI_PB_LTYPE_UINT32(t)
#define PB_SI_PB_LTYPE_UINT64(t)
#define PB_SI_PB_LTYPE_EXTENSION(t)
#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t)
#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg),
/* The field descriptors use a variable width format, with width of either
* 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always
* encode the descriptor size, 6 lowest bits of field tag number, and 8 bits
* of the field type.
*
* Descriptor size is encoded as 0 = 1 word, 1 = 2 words, 2 = 4 words, 3 = 8 words.
*
* Formats, listed starting with the least significant bit of the first word.
* 1 word: [2-bit len] [6-bit tag] [8-bit type] [8-bit data_offset] [4-bit size_offset] [4-bit data_size]
*
* 2 words: [2-bit len] [6-bit tag] [8-bit type] [12-bit array_size] [4-bit size_offset]
* [16-bit data_offset] [12-bit data_size] [4-bit tag>>6]
*
* 4 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit array_size]
* [8-bit size_offset] [24-bit tag>>6]
* [32-bit data_offset]
* [32-bit data_size]
*
* 8 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit reserved]
* [8-bit size_offset] [24-bit tag>>6]
* [32-bit data_offset]
* [32-bit data_size]
* [32-bit array_size]
* [32-bit reserved]
* [32-bit reserved]
* [32-bit reserved]
*/
#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \
(0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \
(((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)),
#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \
(1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \
(((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)),
#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \
(2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size),
#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \
(3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size), (array_size), 0, 0, 0,
/* These assertions verify that the field information fits in the allocated space.
* The generator tries to automatically determine the correct width that can fit all
* data associated with a message. These asserts will fail only if there has been a
* problem in the automatic logic - this may be worth reporting as a bug. As a workaround,
* you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting
* descriptorsize option in .options file.
*/
#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<<bits))
#define PB_FIELDINFO_ASSERT_1(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,6) && PB_FITS(data_offset,8) && PB_FITS(size_offset,4) && PB_FITS(data_size,4) && PB_FITS(array_size,1), FIELDINFO_DOES_NOT_FIT_width1_field ## tag)
#define PB_FIELDINFO_ASSERT_2(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,10) && PB_FITS(data_offset,16) && PB_FITS(size_offset,4) && PB_FITS(data_size,12) && PB_FITS(array_size,12), FIELDINFO_DOES_NOT_FIT_width2_field ## tag)
#ifndef PB_FIELD_32BIT
/* Maximum field sizes are still 16-bit if pb_size_t is 16-bit */
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#else
/* Up to 32-bit fields supported.
* Note that the checks are against 31 bits to avoid compiler warnings about shift wider than type in the test.
* I expect that there is no reasonable use for >2GB messages with nanopb anyway.
*/
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#endif
/* Automatic picking of FIELDINFO width:
* Uses width 1 when possible, otherwise resorts to width 2.
* This is used when PB_BIND() is called with "AUTO" as the argument.
* The generator will give explicit size argument when it knows that a message
* structure grows beyond 1-word format limits.
*/
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype)
#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2
#define PB_FI_WIDTH_PB_LTYPE_BOOL 1
#define PB_FI_WIDTH_PB_LTYPE_BYTES 2
#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1
#define PB_FI_WIDTH_PB_LTYPE_ENUM 1
#define PB_FI_WIDTH_PB_LTYPE_UENUM 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1
#define PB_FI_WIDTH_PB_LTYPE_INT32 1
#define PB_FI_WIDTH_PB_LTYPE_INT64 1
#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2
#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2
#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_SINT32 1
#define PB_FI_WIDTH_PB_LTYPE_SINT64 1
#define PB_FI_WIDTH_PB_LTYPE_STRING 2
#define PB_FI_WIDTH_PB_LTYPE_UINT32 1
#define PB_FI_WIDTH_PB_LTYPE_UINT64 1
#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2
/* The mapping from protobuf types to LTYPEs is done using these macros. */
#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL
#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES
#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT
#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE
#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB
#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING
#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION
#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES
/* These macros are used for giving out error messages.
* They are mostly a debugging aid; the main error information
* is the true/false return value from functions.
* Some code space can be saved by disabling the error
* messages if not used.
*
* PB_SET_ERROR() sets the error message if none has been set yet.
* msg must be a constant string literal.
* PB_GET_ERROR() always returns a pointer to a string.
* PB_RETURN_ERROR() sets the error and returns false from current
* function.
*/
#ifdef PB_NO_ERRMSG
#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream)
#define PB_GET_ERROR(stream) "(errmsg disabled)"
#else
#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg))
#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)")
#endif
#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false
#ifdef __cplusplus
} /* extern "C" */
#endif
#ifdef __cplusplus
#if __cplusplus >= 201103L
#define PB_CONSTEXPR constexpr
#else // __cplusplus >= 201103L
#define PB_CONSTEXPR
#endif // __cplusplus >= 201103L
#if __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR inline constexpr
#else // __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR PB_CONSTEXPR
#endif // __cplusplus >= 201703L
namespace nanopb {
// Each type will be partially specialized by the generator.
template <typename GenMessageT> struct MessageDescriptor;
} // namespace nanopb
#endif /* __cplusplus */
#endif

View File

@@ -1,388 +0,0 @@
/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c.
*
* 2014 Petteri Aimonen <jpa@kapsi.fi>
*/
#include "pb_common.h"
static bool load_descriptor_values(pb_field_iter_t *iter)
{
uint32_t word0;
uint32_t data_offset;
int_least8_t size_offset;
if (iter->index >= iter->descriptor->field_count)
return false;
word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
iter->type = (pb_type_t)((word0 >> 8) & 0xFF);
switch(word0 & 3)
{
case 0: {
/* 1-word format */
iter->array_size = 1;
iter->tag = (pb_size_t)((word0 >> 2) & 0x3F);
size_offset = (int_least8_t)((word0 >> 24) & 0x0F);
data_offset = (word0 >> 16) & 0xFF;
iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F);
break;
}
case 1: {
/* 2-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF);
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 28) << 6));
size_offset = (int_least8_t)((word0 >> 28) & 0x0F);
data_offset = word1 & 0xFFFF;
iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF);
break;
}
case 2: {
/* 4-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
iter->array_size = (pb_size_t)(word0 >> 16);
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
size_offset = (int_least8_t)(word1 & 0xFF);
data_offset = word2;
iter->data_size = (pb_size_t)word3;
break;
}
default: {
/* 8-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]);
iter->array_size = (pb_size_t)word4;
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
size_offset = (int_least8_t)(word1 & 0xFF);
data_offset = word2;
iter->data_size = (pb_size_t)word3;
break;
}
}
if (!iter->message)
{
/* Avoid doing arithmetic on null pointers, it is undefined */
iter->pField = NULL;
iter->pSize = NULL;
}
else
{
iter->pField = (char*)iter->message + data_offset;
if (size_offset)
{
iter->pSize = (char*)iter->pField - size_offset;
}
else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED &&
(PB_ATYPE(iter->type) == PB_ATYPE_STATIC ||
PB_ATYPE(iter->type) == PB_ATYPE_POINTER))
{
/* Fixed count array */
iter->pSize = &iter->array_size;
}
else
{
iter->pSize = NULL;
}
if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL)
{
iter->pData = *(void**)iter->pField;
}
else
{
iter->pData = iter->pField;
}
}
if (PB_LTYPE_IS_SUBMSG(iter->type))
{
iter->submsg_desc = iter->descriptor->submsg_info[iter->submessage_index];
}
else
{
iter->submsg_desc = NULL;
}
return true;
}
static void advance_iterator(pb_field_iter_t *iter)
{
iter->index++;
if (iter->index >= iter->descriptor->field_count)
{
/* Restart */
iter->index = 0;
iter->field_info_index = 0;
iter->submessage_index = 0;
iter->required_field_index = 0;
}
else
{
/* Increment indexes based on previous field type.
* All field info formats have the following fields:
* - lowest 2 bits tell the amount of words in the descriptor (2^n words)
* - bits 2..7 give the lowest bits of tag number.
* - bits 8..15 give the field type.
*/
uint32_t prev_descriptor = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF;
pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3));
/* Add to fields.
* The cast to pb_size_t is needed to avoid -Wconversion warning.
* Because the data is is constants from generator, there is no danger of overflow.
*/
iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len);
iter->required_field_index = (pb_size_t)(iter->required_field_index + (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED));
iter->submessage_index = (pb_size_t)(iter->submessage_index + PB_LTYPE_IS_SUBMSG(prev_type));
}
}
bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message)
{
memset(iter, 0, sizeof(*iter));
iter->descriptor = desc;
iter->message = message;
return load_descriptor_values(iter);
}
bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension)
{
const pb_msgdesc_t *msg = (const pb_msgdesc_t*)extension->type->arg;
bool status;
uint32_t word0 = PB_PROGMEM_READU32(msg->field_info[0]);
if (PB_ATYPE(word0 >> 8) == PB_ATYPE_POINTER)
{
/* For pointer extensions, the pointer is stored directly
* in the extension structure. This avoids having an extra
* indirection. */
status = pb_field_iter_begin(iter, msg, &extension->dest);
}
else
{
status = pb_field_iter_begin(iter, msg, extension->dest);
}
iter->pSize = &extension->found;
return status;
}
bool pb_field_iter_next(pb_field_iter_t *iter)
{
advance_iterator(iter);
(void)load_descriptor_values(iter);
return iter->index != 0;
}
bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag)
{
if (iter->tag == tag)
{
return true; /* Nothing to do, correct field already. */
}
else if (tag > iter->descriptor->largest_tag)
{
return false;
}
else
{
pb_size_t start = iter->index;
uint32_t fieldinfo;
if (tag < iter->tag)
{
/* Fields are in tag number order, so we know that tag is between
* 0 and our start position. Setting index to end forces
* advance_iterator() call below to restart from beginning. */
iter->index = iter->descriptor->field_count;
}
do
{
/* Advance iterator but don't load values yet */
advance_iterator(iter);
/* Do fast check for tag number match */
fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
if (((fieldinfo >> 2) & 0x3F) == (tag & 0x3F))
{
/* Good candidate, check further */
(void)load_descriptor_values(iter);
if (iter->tag == tag &&
PB_LTYPE(iter->type) != PB_LTYPE_EXTENSION)
{
/* Found it */
return true;
}
}
} while (iter->index != start);
/* Searched all the way back to start, and found nothing. */
(void)load_descriptor_values(iter);
return false;
}
}
bool pb_field_iter_find_extension(pb_field_iter_t *iter)
{
if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION)
{
return true;
}
else
{
pb_size_t start = iter->index;
uint32_t fieldinfo;
do
{
/* Advance iterator but don't load values yet */
advance_iterator(iter);
/* Do fast check for field type */
fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
if (PB_LTYPE((fieldinfo >> 8) & 0xFF) == PB_LTYPE_EXTENSION)
{
return load_descriptor_values(iter);
}
} while (iter->index != start);
/* Searched all the way back to start, and found nothing. */
(void)load_descriptor_values(iter);
return false;
}
}
static void *pb_const_cast(const void *p)
{
/* Note: this casts away const, in order to use the common field iterator
* logic for both encoding and decoding. The cast is done using union
* to avoid spurious compiler warnings. */
union {
void *p1;
const void *p2;
} t;
t.p2 = p;
return t.p1;
}
bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message)
{
return pb_field_iter_begin(iter, desc, pb_const_cast(message));
}
bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension)
{
return pb_field_iter_begin_extension(iter, (pb_extension_t*)pb_const_cast(extension));
}
bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field)
{
if (field->data_size == sizeof(pb_callback_t))
{
pb_callback_t *pCallback = (pb_callback_t*)field->pData;
if (pCallback != NULL)
{
if (istream != NULL && pCallback->funcs.decode != NULL)
{
return pCallback->funcs.decode(istream, field, &pCallback->arg);
}
if (ostream != NULL && pCallback->funcs.encode != NULL)
{
return pCallback->funcs.encode(ostream, field, &pCallback->arg);
}
}
}
return true; /* Success, but didn't do anything */
}
#ifdef PB_VALIDATE_UTF8
/* This function checks whether a string is valid UTF-8 text.
*
* Algorithm is adapted from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
* Original copyright: Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> 2005-03-30
* Licensed under "Short code license", which allows use under MIT license or
* any compatible with it.
*/
bool pb_validate_utf8(const char *str)
{
const pb_byte_t *s = (const pb_byte_t*)str;
while (*s)
{
if (*s < 0x80)
{
/* 0xxxxxxx */
s++;
}
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
{
return false;
}
}
return true;
}
#endif

View File

@@ -1,49 +0,0 @@
/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c.
* These functions are rarely needed by applications directly.
*/
#ifndef PB_COMMON_H_INCLUDED
#define PB_COMMON_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Initialize the field iterator structure to beginning.
* Returns false if the message type is empty. */
bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message);
/* Get a field iterator for extension field. */
bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension);
/* Same as pb_field_iter_begin(), but for const message pointer.
* Note that the pointers in pb_field_iter_t will be non-const but shouldn't
* be written to when using these functions. */
bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message);
bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension);
/* Advance the iterator to the next field.
* Returns false when the iterator wraps back to the first field. */
bool pb_field_iter_next(pb_field_iter_t *iter);
/* Advance the iterator until it points at a field with the given tag.
* Returns false if no such field exists. */
bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag);
/* Find a field with type PB_LTYPE_EXTENSION, or return false if not found.
* There can be only one extension range field per message. */
bool pb_field_iter_find_extension(pb_field_iter_t *iter);
#ifdef PB_VALIDATE_UTF8
/* Validate UTF-8 text string */
bool pb_validate_utf8(const char *s);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,199 +0,0 @@
/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c.
* The main function is pb_decode. You also need an input stream, and the
* field descriptions created by nanopb_generator.py.
*/
#ifndef PB_DECODE_H_INCLUDED
#define PB_DECODE_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Structure for defining custom input streams. You will need to provide
* a callback function to read the bytes from your storage, which can be
* for example a file or a network socket.
*
* The callback must conform to these rules:
*
* 1) Return false on IO errors. This will cause decoding to abort.
* 2) You can use state to store your own data (e.g. buffer pointer),
* and rely on pb_read to verify that no-body reads past bytes_left.
* 3) Your callback may be used with substreams, in which case bytes_left
* is different than from the main stream. Don't use bytes_left to compute
* any pointers.
*/
struct pb_istream_s
{
#ifdef PB_BUFFER_ONLY
/* Callback pointer is not used in buffer-only configuration.
* Having an int pointer here allows binary compatibility but
* gives an error if someone tries to assign callback function.
*/
int *callback;
#else
bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count);
#endif
void *state; /* Free field for use by callback implementation */
size_t bytes_left;
#ifndef PB_NO_ERRMSG
const char *errmsg;
#endif
};
#ifndef PB_NO_ERRMSG
#define PB_ISTREAM_EMPTY {0,0,0,0}
#else
#define PB_ISTREAM_EMPTY {0,0,0}
#endif
/***************************
* Main decoding functions *
***************************/
/* Decode a single protocol buffers message from input stream into a C structure.
* Returns true on success, false on any failure.
* The actual struct pointed to by dest must match the description in fields.
* Callback fields of the destination structure must be initialized by caller.
* All other fields will be initialized by this function.
*
* Example usage:
* MyMessage msg = {};
* uint8_t buffer[64];
* pb_istream_t stream;
*
* // ... read some data into buffer ...
*
* stream = pb_istream_from_buffer(buffer, count);
* pb_decode(&stream, MyMessage_fields, &msg);
*/
bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct);
/* Extended version of pb_decode, with several options to control
* the decoding process:
*
* PB_DECODE_NOINIT: Do not initialize the fields to default values.
* This is slightly faster if you do not need the default
* values and instead initialize the structure to 0 using
* e.g. memset(). This can also be used for merging two
* messages, i.e. combine already existing data with new
* values.
*
* PB_DECODE_DELIMITED: Input message starts with the message size as varint.
* Corresponds to parseDelimitedFrom() in Google's
* protobuf API.
*
* PB_DECODE_NULLTERMINATED: Stop reading when field tag is read as 0. This allows
* reading null terminated messages.
* NOTE: Until nanopb-0.4.0, pb_decode() also allows
* null-termination. This behaviour is not supported in
* most other protobuf implementations, so PB_DECODE_DELIMITED
* is a better option for compatibility.
*
* Multiple flags can be combined with bitwise or (| operator)
*/
#define PB_DECODE_NOINIT 0x01U
#define PB_DECODE_DELIMITED 0x02U
#define PB_DECODE_NULLTERMINATED 0x04U
bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags);
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define pb_decode_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NOINIT)
#define pb_decode_delimited(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED)
#define pb_decode_delimited_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED | PB_DECODE_NOINIT)
#define pb_decode_nullterminated(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NULLTERMINATED)
#ifdef PB_ENABLE_MALLOC
/* Release any allocated pointer fields. If you use dynamic allocation, you should
* call this for any successfully decoded message when you are done with it. If
* pb_decode() returns with an error, the message is already released.
*/
void pb_release(const pb_msgdesc_t *fields, void *dest_struct);
#else
/* Allocation is not supported, so release is no-op */
#define pb_release(fields, dest_struct) PB_UNUSED(fields); PB_UNUSED(dest_struct);
#endif
/**************************************
* Functions for manipulating streams *
**************************************/
/* Create an input stream for reading from a memory buffer.
*
* msglen should be the actual length of the message, not the full size of
* allocated buffer.
*
* Alternatively, you can use a custom stream that reads directly from e.g.
* a file or a network socket.
*/
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen);
/* Function to read from a pb_istream_t. You can use this if you need to
* read some custom header data, or to read data in field callbacks.
*/
bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
/************************************************
* Helper functions for writing field callbacks *
************************************************/
/* Decode the tag for the next field in the stream. Gives the wire type and
* field tag. At end of the message, returns false and sets eof to true. */
bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof);
/* Skip the field payload data, given the wire type. */
bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type);
/* Decode an integer in the varint format. This works for enum, int32,
* int64, uint32 and uint64 field types. */
#ifndef PB_WITHOUT_64BIT
bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest);
#else
#define pb_decode_varint pb_decode_varint32
#endif
/* Decode an integer in the varint format. This works for enum, int32,
* and uint32 field types. */
bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest);
/* Decode a bool value in varint format. */
bool pb_decode_bool(pb_istream_t *stream, bool *dest);
/* Decode an integer in the zig-zagged svarint format. This works for sint32
* and sint64. */
#ifndef PB_WITHOUT_64BIT
bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest);
#else
bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest);
#endif
/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to
* a 4-byte wide C variable. */
bool pb_decode_fixed32(pb_istream_t *stream, void *dest);
#ifndef PB_WITHOUT_64BIT
/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to
* a 8-byte wide C variable. */
bool pb_decode_fixed64(pb_istream_t *stream, void *dest);
#endif
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Decode a double value into float variable. */
bool pb_decode_double_as_float(pb_istream_t *stream, float *dest);
#endif
/* Make a limited-length substream for reading a PB_WT_STRING field. */
bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream);
bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -1,987 +0,0 @@
/* pb_encode.c -- encode a protobuf using minimal resources
*
* 2011 Petteri Aimonen <jpa@kapsi.fi>
*/
#include "pb.h"
#include "pb_encode.h"
#include "pb_common.h"
/* Use the GCC warn_unused_result attribute to check that all return values
* are propagated correctly. On other compilers and gcc before 3.4.0 just
* ignore the annotation.
*/
#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
#define checkreturn
#else
#define checkreturn __attribute__((warn_unused_result))
#endif
/**************************************
* Declarations internal to this file *
**************************************/
static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field);
static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field);
static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field);
static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension);
static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high);
static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field);
static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field);
#ifdef PB_WITHOUT_64BIT
#define pb_int64_t int32_t
#define pb_uint64_t uint32_t
#else
#define pb_int64_t int64_t
#define pb_uint64_t uint64_t
#endif
/*******************************
* pb_ostream_t implementation *
*******************************/
static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
{
size_t i;
pb_byte_t *dest = (pb_byte_t*)stream->state;
stream->state = dest + count;
for (i = 0; i < count; i++)
dest[i] = buf[i];
return true;
}
pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize)
{
pb_ostream_t stream;
#ifdef PB_BUFFER_ONLY
stream.callback = (void*)1; /* Just a marker value */
#else
stream.callback = &buf_write;
#endif
stream.state = buf;
stream.max_size = bufsize;
stream.bytes_written = 0;
#ifndef PB_NO_ERRMSG
stream.errmsg = NULL;
#endif
return stream;
}
bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
{
if (count > 0 && stream->callback != NULL)
{
if (stream->bytes_written + count < stream->bytes_written ||
stream->bytes_written + count > stream->max_size)
{
PB_RETURN_ERROR(stream, "stream full");
}
#ifdef PB_BUFFER_ONLY
if (!buf_write(stream, buf, count))
PB_RETURN_ERROR(stream, "io error");
#else
if (!stream->callback(stream, buf, count))
PB_RETURN_ERROR(stream, "io error");
#endif
}
stream->bytes_written += count;
return true;
}
/*************************
* Encode a single field *
*************************/
/* Read a bool value without causing undefined behavior even if the value
* is invalid. See issue #434 and
* https://stackoverflow.com/questions/27661768/weird-results-for-conditional
*/
static bool safe_read_bool(const void *pSize)
{
const char *p = (const char *)pSize;
size_t i;
for (i = 0; i < sizeof(bool); i++)
{
if (p[i] != 0)
return true;
}
return false;
}
/* Encode a static array. Handles the size calculations and possible packing. */
static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field)
{
pb_size_t i;
pb_size_t count;
#ifndef PB_ENCODE_ARRAYS_UNPACKED
size_t size;
#endif
count = *(pb_size_t*)field->pSize;
if (count == 0)
return true;
if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size)
PB_RETURN_ERROR(stream, "array max size exceeded");
#ifndef PB_ENCODE_ARRAYS_UNPACKED
/* We always pack arrays if the datatype allows it. */
if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE)
{
if (!pb_encode_tag(stream, PB_WT_STRING, field->tag))
return false;
/* Determine the total size of packed array. */
if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32)
{
size = 4 * (size_t)count;
}
else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64)
{
size = 8 * (size_t)count;
}
else
{
pb_ostream_t sizestream = PB_OSTREAM_SIZING;
void *pData_orig = field->pData;
for (i = 0; i < count; i++)
{
if (!pb_enc_varint(&sizestream, field))
PB_RETURN_ERROR(stream, PB_GET_ERROR(&sizestream));
field->pData = (char*)field->pData + field->data_size;
}
field->pData = pData_orig;
size = sizestream.bytes_written;
}
if (!pb_encode_varint(stream, (pb_uint64_t)size))
return false;
if (stream->callback == NULL)
return pb_write(stream, NULL, size); /* Just sizing.. */
/* Write the data */
for (i = 0; i < count; i++)
{
if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32 || PB_LTYPE(field->type) == PB_LTYPE_FIXED64)
{
if (!pb_enc_fixed(stream, field))
return false;
}
else
{
if (!pb_enc_varint(stream, field))
return false;
}
field->pData = (char*)field->pData + field->data_size;
}
}
else /* Unpacked fields */
#endif
{
for (i = 0; i < count; i++)
{
/* Normally the data is stored directly in the array entries, but
* for pointer-type string and bytes fields, the array entries are
* actually pointers themselves also. So we have to dereference once
* more to get to the actual data. */
if (PB_ATYPE(field->type) == PB_ATYPE_POINTER &&
(PB_LTYPE(field->type) == PB_LTYPE_STRING ||
PB_LTYPE(field->type) == PB_LTYPE_BYTES))
{
bool status;
void *pData_orig = field->pData;
field->pData = *(void* const*)field->pData;
if (!field->pData)
{
/* Null pointer in array is treated as empty string / bytes */
status = pb_encode_tag_for_field(stream, field) &&
pb_encode_varint(stream, 0);
}
else
{
status = encode_basic_field(stream, field);
}
field->pData = pData_orig;
if (!status)
return false;
}
else
{
if (!encode_basic_field(stream, field))
return false;
}
field->pData = (char*)field->pData + field->data_size;
}
}
return true;
}
/* In proto3, all fields are optional and are only encoded if their value is "non-zero".
* This function implements the check for the zero value. */
static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field)
{
pb_type_t type = field->type;
if (PB_ATYPE(type) == PB_ATYPE_STATIC)
{
if (PB_HTYPE(type) == PB_HTYPE_REQUIRED)
{
/* Required proto2 fields inside proto3 submessage, pretty rare case */
return false;
}
else if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
{
/* Repeated fields inside proto3 submessage: present if count != 0 */
return *(const pb_size_t*)field->pSize == 0;
}
else if (PB_HTYPE(type) == PB_HTYPE_ONEOF)
{
/* Oneof fields */
return *(const pb_size_t*)field->pSize == 0;
}
else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL)
{
/* Proto2 optional fields inside proto3 message, or proto3
* submessage fields. */
return safe_read_bool(field->pSize) == false;
}
else if (field->descriptor->default_value)
{
/* Proto3 messages do not have default values, but proto2 messages
* can contain optional fields without has_fields (generator option 'proto3').
* In this case they must always be encoded, to make sure that the
* non-zero default value is overwritten.
*/
return false;
}
/* Rest is proto3 singular fields */
if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE)
{
/* Simple integer / float fields */
pb_size_t i;
const char *p = (const char*)field->pData;
for (i = 0; i < field->data_size; i++)
{
if (p[i] != 0)
{
return false;
}
}
return true;
}
else if (PB_LTYPE(type) == PB_LTYPE_BYTES)
{
const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData;
return bytes->size == 0;
}
else if (PB_LTYPE(type) == PB_LTYPE_STRING)
{
return *(const char*)field->pData == '\0';
}
else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES)
{
/* Fixed length bytes is only empty if its length is fixed
* as 0. Which would be pretty strange, but we can check
* it anyway. */
return field->data_size == 0;
}
else if (PB_LTYPE_IS_SUBMSG(type))
{
/* Check all fields in the submessage to find if any of them
* are non-zero. The comparison cannot be done byte-per-byte
* because the C struct may contain padding bytes that must
* be skipped. Note that usually proto3 submessages have
* a separate has_field that is checked earlier in this if.
*/
pb_field_iter_t iter;
if (pb_field_iter_begin(&iter, field->submsg_desc, field->pData))
{
do
{
if (!pb_check_proto3_default_value(&iter))
{
return false;
}
} while (pb_field_iter_next(&iter));
}
return true;
}
}
else if (PB_ATYPE(type) == PB_ATYPE_POINTER)
{
return field->pData == NULL;
}
else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK)
{
if (PB_LTYPE(type) == PB_LTYPE_EXTENSION)
{
const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData;
return extension == NULL;
}
else if (field->descriptor->field_callback == pb_default_field_callback)
{
pb_callback_t *pCallback = (pb_callback_t*)field->pData;
return pCallback->funcs.encode == NULL;
}
else
{
return field->descriptor->field_callback == NULL;
}
}
return false; /* Not typically reached, safe default for weird special cases. */
}
/* Encode a field with static or pointer allocation, i.e. one whose data
* is available to the encoder directly. */
static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field)
{
if (!field->pData)
{
/* Missing pointer field */
return true;
}
if (!pb_encode_tag_for_field(stream, field))
return false;
switch (PB_LTYPE(field->type))
{
case PB_LTYPE_BOOL:
return pb_enc_bool(stream, field);
case PB_LTYPE_VARINT:
case PB_LTYPE_UVARINT:
case PB_LTYPE_SVARINT:
return pb_enc_varint(stream, field);
case PB_LTYPE_FIXED32:
case PB_LTYPE_FIXED64:
return pb_enc_fixed(stream, field);
case PB_LTYPE_BYTES:
return pb_enc_bytes(stream, field);
case PB_LTYPE_STRING:
return pb_enc_string(stream, field);
case PB_LTYPE_SUBMESSAGE:
case PB_LTYPE_SUBMSG_W_CB:
return pb_enc_submessage(stream, field);
case PB_LTYPE_FIXED_LENGTH_BYTES:
return pb_enc_fixed_length_bytes(stream, field);
default:
PB_RETURN_ERROR(stream, "invalid field type");
}
}
/* Encode a field with callback semantics. This means that a user function is
* called to provide and encode the actual data. */
static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field)
{
if (field->descriptor->field_callback != NULL)
{
if (!field->descriptor->field_callback(NULL, stream, field))
PB_RETURN_ERROR(stream, "callback error");
}
return true;
}
/* Encode a single field of any callback, pointer or static type. */
static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field)
{
/* Check field presence */
if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF)
{
if (*(const pb_size_t*)field->pSize != field->tag)
{
/* Different type oneof field */
return true;
}
}
else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL)
{
if (field->pSize)
{
if (safe_read_bool(field->pSize) == false)
{
/* Missing optional field */
return true;
}
}
else if (PB_ATYPE(field->type) == PB_ATYPE_STATIC)
{
/* Proto3 singular field */
if (pb_check_proto3_default_value(field))
return true;
}
}
if (!field->pData)
{
if (PB_HTYPE(field->type) == PB_HTYPE_REQUIRED)
PB_RETURN_ERROR(stream, "missing required field");
/* Pointer field set to NULL */
return true;
}
/* Then encode field contents */
if (PB_ATYPE(field->type) == PB_ATYPE_CALLBACK)
{
return encode_callback_field(stream, field);
}
else if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED)
{
return encode_array(stream, field);
}
else
{
return encode_basic_field(stream, field);
}
}
/* Default handler for extension fields. Expects to have a pb_msgdesc_t
* pointer in the extension->type->arg field, pointing to a message with
* only one field in it. */
static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension)
{
pb_field_iter_t iter;
if (!pb_field_iter_begin_extension_const(&iter, extension))
PB_RETURN_ERROR(stream, "invalid extension");
return encode_field(stream, &iter);
}
/* Walk through all the registered extensions and give them a chance
* to encode themselves. */
static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field)
{
const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData;
while (extension)
{
bool status;
if (extension->type->encode)
status = extension->type->encode(stream, extension);
else
status = default_extension_encoder(stream, extension);
if (!status)
return false;
extension = extension->next;
}
return true;
}
/*********************
* Encode all fields *
*********************/
bool checkreturn pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct)
{
pb_field_iter_t iter;
if (!pb_field_iter_begin_const(&iter, fields, src_struct))
return true; /* Empty message type */
do {
if (PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION)
{
/* Special case for the extension field placeholder */
if (!encode_extension_field(stream, &iter))
return false;
}
else
{
/* Regular field */
if (!encode_field(stream, &iter))
return false;
}
} while (pb_field_iter_next(&iter));
return true;
}
bool checkreturn pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags)
{
if ((flags & PB_ENCODE_DELIMITED) != 0)
{
return pb_encode_submessage(stream, fields, src_struct);
}
else if ((flags & PB_ENCODE_NULLTERMINATED) != 0)
{
const pb_byte_t zero = 0;
if (!pb_encode(stream, fields, src_struct))
return false;
return pb_write(stream, &zero, 1);
}
else
{
return pb_encode(stream, fields, src_struct);
}
}
bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct)
{
pb_ostream_t stream = PB_OSTREAM_SIZING;
if (!pb_encode(&stream, fields, src_struct))
return false;
*size = stream.bytes_written;
return true;
}
/********************
* Helper functions *
********************/
/* This function avoids 64-bit shifts as they are quite slow on many platforms. */
static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high)
{
size_t i = 0;
pb_byte_t buffer[10];
pb_byte_t byte = (pb_byte_t)(low & 0x7F);
low >>= 7;
while (i < 4 && (low != 0 || high != 0))
{
byte |= 0x80;
buffer[i++] = byte;
byte = (pb_byte_t)(low & 0x7F);
low >>= 7;
}
if (high)
{
byte = (pb_byte_t)(byte | ((high & 0x07) << 4));
high >>= 3;
while (high)
{
byte |= 0x80;
buffer[i++] = byte;
byte = (pb_byte_t)(high & 0x7F);
high >>= 7;
}
}
buffer[i++] = byte;
return pb_write(stream, buffer, i);
}
bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value)
{
if (value <= 0x7F)
{
/* Fast path: single byte */
pb_byte_t byte = (pb_byte_t)value;
return pb_write(stream, &byte, 1);
}
else
{
#ifdef PB_WITHOUT_64BIT
return pb_encode_varint_32(stream, value, 0);
#else
return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32));
#endif
}
}
bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value)
{
pb_uint64_t zigzagged;
if (value < 0)
zigzagged = ~((pb_uint64_t)value << 1);
else
zigzagged = (pb_uint64_t)value << 1;
return pb_encode_varint(stream, zigzagged);
}
bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value)
{
uint32_t val = *(const uint32_t*)value;
pb_byte_t bytes[4];
bytes[0] = (pb_byte_t)(val & 0xFF);
bytes[1] = (pb_byte_t)((val >> 8) & 0xFF);
bytes[2] = (pb_byte_t)((val >> 16) & 0xFF);
bytes[3] = (pb_byte_t)((val >> 24) & 0xFF);
return pb_write(stream, bytes, 4);
}
#ifndef PB_WITHOUT_64BIT
bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value)
{
uint64_t val = *(const uint64_t*)value;
pb_byte_t bytes[8];
bytes[0] = (pb_byte_t)(val & 0xFF);
bytes[1] = (pb_byte_t)((val >> 8) & 0xFF);
bytes[2] = (pb_byte_t)((val >> 16) & 0xFF);
bytes[3] = (pb_byte_t)((val >> 24) & 0xFF);
bytes[4] = (pb_byte_t)((val >> 32) & 0xFF);
bytes[5] = (pb_byte_t)((val >> 40) & 0xFF);
bytes[6] = (pb_byte_t)((val >> 48) & 0xFF);
bytes[7] = (pb_byte_t)((val >> 56) & 0xFF);
return pb_write(stream, bytes, 8);
}
#endif
bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number)
{
pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype;
return pb_encode_varint(stream, tag);
}
bool pb_encode_tag_for_field ( pb_ostream_t* stream, const pb_field_iter_t* field )
{
pb_wire_type_t wiretype;
switch (PB_LTYPE(field->type))
{
case PB_LTYPE_BOOL:
case PB_LTYPE_VARINT:
case PB_LTYPE_UVARINT:
case PB_LTYPE_SVARINT:
wiretype = PB_WT_VARINT;
break;
case PB_LTYPE_FIXED32:
wiretype = PB_WT_32BIT;
break;
case PB_LTYPE_FIXED64:
wiretype = PB_WT_64BIT;
break;
case PB_LTYPE_BYTES:
case PB_LTYPE_STRING:
case PB_LTYPE_SUBMESSAGE:
case PB_LTYPE_SUBMSG_W_CB:
case PB_LTYPE_FIXED_LENGTH_BYTES:
wiretype = PB_WT_STRING;
break;
default:
PB_RETURN_ERROR(stream, "invalid field type");
}
return pb_encode_tag(stream, wiretype, field->tag);
}
bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size)
{
if (!pb_encode_varint(stream, (pb_uint64_t)size))
return false;
return pb_write(stream, buffer, size);
}
bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct)
{
/* First calculate the message size using a non-writing substream. */
pb_ostream_t substream = PB_OSTREAM_SIZING;
size_t size;
bool status;
if (!pb_encode(&substream, fields, src_struct))
{
#ifndef PB_NO_ERRMSG
stream->errmsg = substream.errmsg;
#endif
return false;
}
size = substream.bytes_written;
if (!pb_encode_varint(stream, (pb_uint64_t)size))
return false;
if (stream->callback == NULL)
return pb_write(stream, NULL, size); /* Just sizing */
if (stream->bytes_written + size > stream->max_size)
PB_RETURN_ERROR(stream, "stream full");
/* Use a substream to verify that a callback doesn't write more than
* what it did the first time. */
substream.callback = stream->callback;
substream.state = stream->state;
substream.max_size = size;
substream.bytes_written = 0;
#ifndef PB_NO_ERRMSG
substream.errmsg = NULL;
#endif
status = pb_encode(&substream, fields, src_struct);
stream->bytes_written += substream.bytes_written;
stream->state = substream.state;
#ifndef PB_NO_ERRMSG
stream->errmsg = substream.errmsg;
#endif
if (substream.bytes_written != size)
PB_RETURN_ERROR(stream, "submsg size changed");
return status;
}
/* Field encoders */
static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field)
{
uint32_t value = safe_read_bool(field->pData) ? 1 : 0;
PB_UNUSED(field);
return pb_encode_varint(stream, value);
}
static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field)
{
if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT)
{
/* Perform unsigned integer extension */
pb_uint64_t value = 0;
if (field->data_size == sizeof(uint_least8_t))
value = *(const uint_least8_t*)field->pData;
else if (field->data_size == sizeof(uint_least16_t))
value = *(const uint_least16_t*)field->pData;
else if (field->data_size == sizeof(uint32_t))
value = *(const uint32_t*)field->pData;
else if (field->data_size == sizeof(pb_uint64_t))
value = *(const pb_uint64_t*)field->pData;
else
PB_RETURN_ERROR(stream, "invalid data_size");
return pb_encode_varint(stream, value);
}
else
{
/* Perform signed integer extension */
pb_int64_t value = 0;
if (field->data_size == sizeof(int_least8_t))
value = *(const int_least8_t*)field->pData;
else if (field->data_size == sizeof(int_least16_t))
value = *(const int_least16_t*)field->pData;
else if (field->data_size == sizeof(int32_t))
value = *(const int32_t*)field->pData;
else if (field->data_size == sizeof(pb_int64_t))
value = *(const pb_int64_t*)field->pData;
else
PB_RETURN_ERROR(stream, "invalid data_size");
if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT)
return pb_encode_svarint(stream, value);
#ifdef PB_WITHOUT_64BIT
else if (value < 0)
return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1);
#endif
else
return pb_encode_varint(stream, (pb_uint64_t)value);
}
}
static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field)
{
#ifdef PB_CONVERT_DOUBLE_FLOAT
if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64)
{
return pb_encode_float_as_double(stream, *(float*)field->pData);
}
#endif
if (field->data_size == sizeof(uint32_t))
{
return pb_encode_fixed32(stream, field->pData);
}
#ifndef PB_WITHOUT_64BIT
else if (field->data_size == sizeof(uint64_t))
{
return pb_encode_fixed64(stream, field->pData);
}
#endif
else
{
PB_RETURN_ERROR(stream, "invalid data_size");
}
}
static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field)
{
const pb_bytes_array_t *bytes = NULL;
bytes = (const pb_bytes_array_t*)field->pData;
if (bytes == NULL)
{
/* Treat null pointer as an empty bytes field */
return pb_encode_string(stream, NULL, 0);
}
if (PB_ATYPE(field->type) == PB_ATYPE_STATIC &&
bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes))
{
PB_RETURN_ERROR(stream, "bytes size exceeded");
}
return pb_encode_string(stream, bytes->bytes, (size_t)bytes->size);
}
static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field)
{
size_t size = 0;
size_t max_size = (size_t)field->data_size;
const char *str = (const char*)field->pData;
if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
{
max_size = (size_t)-1;
}
else
{
/* pb_dec_string() assumes string fields end with a null
* terminator when the type isn't PB_ATYPE_POINTER, so we
* shouldn't allow more than max-1 bytes to be written to
* allow space for the null terminator.
*/
if (max_size == 0)
PB_RETURN_ERROR(stream, "zero-length string");
max_size -= 1;
}
if (str == NULL)
{
size = 0; /* Treat null pointer as an empty string */
}
else
{
const char *p = str;
/* strnlen() is not always available, so just use a loop */
while (size < max_size && *p != '\0')
{
size++;
p++;
}
if (*p != '\0')
{
PB_RETURN_ERROR(stream, "unterminated string");
}
}
#ifdef PB_VALIDATE_UTF8
if (!pb_validate_utf8(str))
PB_RETURN_ERROR(stream, "invalid utf8");
#endif
return pb_encode_string(stream, (const pb_byte_t*)str, size);
}
static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field)
{
if (field->submsg_desc == NULL)
PB_RETURN_ERROR(stream, "invalid field descriptor");
if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL)
{
/* Message callback is stored right before pSize. */
pb_callback_t *callback = (pb_callback_t*)field->pSize - 1;
if (callback->funcs.encode)
{
if (!callback->funcs.encode(stream, field, &callback->arg))
return false;
}
}
return pb_encode_submessage(stream, field->submsg_desc, field->pData);
}
static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field)
{
return pb_encode_string(stream, (const pb_byte_t*)field->pData, (size_t)field->data_size);
}
#ifdef PB_CONVERT_DOUBLE_FLOAT
bool pb_encode_float_as_double(pb_ostream_t *stream, float value)
{
union { float f; uint32_t i; } in;
uint_least8_t sign;
int exponent;
uint64_t mantissa;
in.f = value;
/* Decompose input value */
sign = (uint_least8_t)((in.i >> 31) & 1);
exponent = (int)((in.i >> 23) & 0xFF) - 127;
mantissa = in.i & 0x7FFFFF;
if (exponent == 128)
{
/* Special value (NaN etc.) */
exponent = 1024;
}
else if (exponent == -127)
{
if (!mantissa)
{
/* Zero */
exponent = -1023;
}
else
{
/* Denormalized */
mantissa <<= 1;
while (!(mantissa & 0x800000))
{
mantissa <<= 1;
exponent--;
}
mantissa &= 0x7FFFFF;
}
}
/* Combine fields */
mantissa <<= 29;
mantissa |= (uint64_t)(exponent + 1023) << 52;
mantissa |= (uint64_t)sign << 63;
return pb_encode_fixed64(stream, &mantissa);
}
#endif

View File

@@ -1,185 +0,0 @@
/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c.
* The main function is pb_encode. You also need an output stream, and the
* field descriptions created by nanopb_generator.py.
*/
#ifndef PB_ENCODE_H_INCLUDED
#define PB_ENCODE_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Structure for defining custom output streams. You will need to provide
* a callback function to write the bytes to your storage, which can be
* for example a file or a network socket.
*
* The callback must conform to these rules:
*
* 1) Return false on IO errors. This will cause encoding to abort.
* 2) You can use state to store your own data (e.g. buffer pointer).
* 3) pb_write will update bytes_written after your callback runs.
* 4) Substreams will modify max_size and bytes_written. Don't use them
* to calculate any pointers.
*/
struct pb_ostream_s
{
#ifdef PB_BUFFER_ONLY
/* Callback pointer is not used in buffer-only configuration.
* Having an int pointer here allows binary compatibility but
* gives an error if someone tries to assign callback function.
* Also, NULL pointer marks a 'sizing stream' that does not
* write anything.
*/
int *callback;
#else
bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
#endif
void *state; /* Free field for use by callback implementation. */
size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */
size_t bytes_written; /* Number of bytes written so far. */
#ifndef PB_NO_ERRMSG
const char *errmsg;
#endif
};
/***************************
* Main encoding functions *
***************************/
/* Encode a single protocol buffers message from C structure into a stream.
* Returns true on success, false on any failure.
* The actual struct pointed to by src_struct must match the description in fields.
* All required fields in the struct are assumed to have been filled in.
*
* Example usage:
* MyMessage msg = {};
* uint8_t buffer[64];
* pb_ostream_t stream;
*
* msg.field1 = 42;
* stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
* pb_encode(&stream, MyMessage_fields, &msg);
*/
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
/* Extended version of pb_encode, with several options to control the
* encoding process:
*
* PB_ENCODE_DELIMITED: Prepend the length of message as a varint.
* Corresponds to writeDelimitedTo() in Google's
* protobuf API.
*
* PB_ENCODE_NULLTERMINATED: Append a null byte to the message for termination.
* NOTE: This behaviour is not supported in most other
* protobuf implementations, so PB_ENCODE_DELIMITED
* is a better option for compatibility.
*/
#define PB_ENCODE_DELIMITED 0x02U
#define PB_ENCODE_NULLTERMINATED 0x04U
bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags);
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define pb_encode_delimited(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_DELIMITED)
#define pb_encode_nullterminated(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_NULLTERMINATED)
/* Encode the message to get the size of the encoded data, but do not store
* the data. */
bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct);
/**************************************
* Functions for manipulating streams *
**************************************/
/* Create an output stream for writing into a memory buffer.
* The number of bytes written can be found in stream.bytes_written after
* encoding the message.
*
* Alternatively, you can use a custom stream that writes directly to e.g.
* a file or a network socket.
*/
pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize);
/* Pseudo-stream for measuring the size of a message without actually storing
* the encoded data.
*
* Example usage:
* MyMessage msg = {};
* pb_ostream_t stream = PB_OSTREAM_SIZING;
* pb_encode(&stream, MyMessage_fields, &msg);
* printf("Message size is %d\n", stream.bytes_written);
*/
#ifndef PB_NO_ERRMSG
#define PB_OSTREAM_SIZING {0,0,0,0,0}
#else
#define PB_OSTREAM_SIZING {0,0,0,0}
#endif
/* Function to write into a pb_ostream_t stream. You can use this if you need
* to append or prepend some custom headers to the message.
*/
bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
/************************************************
* Helper functions for writing field callbacks *
************************************************/
/* Encode field header based on type and field number defined in the field
* structure. Call this from the callback before writing out field contents. */
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field);
/* Encode field header by manually specifying wire type. You need to use this
* if you want to write out packed arrays from a callback field. */
bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number);
/* Encode an integer in the varint format.
* This works for bool, enum, int32, int64, uint32 and uint64 field types. */
#ifndef PB_WITHOUT_64BIT
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value);
#else
bool pb_encode_varint(pb_ostream_t *stream, uint32_t value);
#endif
/* Encode an integer in the zig-zagged svarint format.
* This works for sint32 and sint64. */
#ifndef PB_WITHOUT_64BIT
bool pb_encode_svarint(pb_ostream_t *stream, int64_t value);
#else
bool pb_encode_svarint(pb_ostream_t *stream, int32_t value);
#endif
/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */
bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size);
/* Encode a fixed32, sfixed32 or float value.
* You need to pass a pointer to a 4-byte wide C variable. */
bool pb_encode_fixed32(pb_ostream_t *stream, const void *value);
#ifndef PB_WITHOUT_64BIT
/* Encode a fixed64, sfixed64 or double value.
* You need to pass a pointer to a 8-byte wide C variable. */
bool pb_encode_fixed64(pb_ostream_t *stream, const void *value);
#endif
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Encode a float value so that it appears like a double in the encoded
* message. */
bool pb_encode_float_as_double(pb_ostream_t *stream, float value);
#endif
/* Encode a submessage field.
* You need to pass the pb_field_t array and pointer to struct, just like
* with pb_encode(). This internally encodes the submessage twice, first to
* calculate message size and then to actually write it out.
*/
bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -1,446 +0,0 @@
/* UART asynchronous example, that uses separate RX and TX tasks
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"
#include "nanopb/pb_decode.h"
#include "nanopb/pb_encode.h"
#include "nanopb/pb_common.h"
#include "LoToHi.pb.h"
#include "HiToLo.pb.h"
// #include "inc/version_autogen.h"
#include "sync_master.h"
#include "evse_api.h"
#include "evse_error.h"
#include "evse_state.h"
#include "evse_config.h"
#define VERSION_STRING "2.2"
static const int RX_BUF_SIZE = 1024;
#define TXD_PIN (GPIO_NUM_27)
#define RXD_PIN (GPIO_NUM_26)
static uint8_t msg[2048];
static uint8_t code;
static uint8_t block;
static uint8_t *decode;
static const char *TAG = "MASTER_TASK";
void cobsDecodeReset()
{
code = 0xff;
block = 0;
decode = msg;
}
void handlePacket(uint8_t *buf, int len);
void init(void)
{
const uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
// We won't use a buffer for sending data.
uart_driver_install(UART_NUM_2, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(UART_NUM_2, &uart_config);
uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
cobsDecodeReset();
}
uint32_t crc32(uint8_t *buf, int len)
{
int i, j;
uint32_t b, crc, msk;
i = 0;
crc = 0xFFFFFFFF;
while (i < len)
{
b = buf[i];
crc = crc ^ b;
for (j = 7; j >= 0; j--)
{
msk = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & msk);
}
i = i + 1;
}
// printf("%X",crc);
return crc;
}
void cobsDecodeByte(uint8_t byte)
{
// ESP_LOGI("RX_TASK", "init cobsDecodeByte");
// ESP_LOGI("RX_TASK", "Read bytes: '%02X'", byte);
// check max length
if ((decode - msg == 2048 - 1) && byte != 0x00)
{
ESP_LOGI(TAG, "cobsDecode: Buffer overflow");
cobsDecodeReset();
}
if (block)
{
// ESP_LOGI("RX_TASK", "block");
// we're currently decoding and should not get a 0
if (byte == 0x00)
{
// probably found some garbage -> reset
ESP_LOGI("RX_TASK", "cobsDecode: Garbage detected");
cobsDecodeReset();
return;
}
*decode++ = byte;
}
else
{
// ESP_LOGI("RX_TASK", "not block");
if (code != 0xff)
{
*decode++ = 0;
}
block = code = byte;
if (code == 0x00)
{
// we're finished, reset everything and commit
if (decode == msg)
{
// we received nothing, just a 0x00
ESP_LOGI("RX_TASK", "cobsDecode: Received nothing");
}
else
{
// set back decode with one, as it gets post-incremented
handlePacket(msg, decode - 1 - msg);
}
cobsDecodeReset();
return; // need to return here, because of block--
}
}
block--;
}
void cobsDecode(uint8_t *buf, int len)
{
for (int i = 0; i < len; i++)
{
cobsDecodeByte(buf[i]);
}
}
size_t cobsEncode(const void *data, size_t length, uint8_t *buffer)
{
uint8_t *encode = buffer; // Encoded byte pointer
uint8_t *codep = encode++; // Output code pointer
uint8_t code = 1; // Code value
for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte)
{
if (*byte) // Byte not zero, write it
*encode++ = *byte, ++code;
if (!*byte || code == 0xff) // Input is zero or block completed, restart
{
*codep = code, code = 1, codep = encode;
if (!*byte || length)
++encode;
}
}
*codep = code; // Write final code value
// add final 0
*encode++ = 0x00;
return encode - buffer;
}
int linkWrite(HiToLo *m)
{
ESP_LOGI(TAG, "linkWrite");
uint8_t tx_packet_buf[200];
uint8_t encode_buf[208];
pb_ostream_t ostream = pb_ostream_from_buffer(tx_packet_buf, sizeof(tx_packet_buf) - 4);
bool status = pb_encode(&ostream, HiToLo_fields, m);
if (!status)
{
// couldn't encode
return false;
}
size_t tx_payload_len = ostream.bytes_written;
// add crc32 (CRC-32/JAMCRC)
uint32_t crc = crc32(tx_packet_buf, tx_payload_len);
for (int byte_pos = 0; byte_pos < 4; ++byte_pos)
{
tx_packet_buf[tx_payload_len] = (uint8_t)crc & 0xFF;
crc = crc >> 8;
tx_payload_len++;
}
size_t tx_encode_len = cobsEncode(tx_packet_buf, tx_payload_len, encode_buf);
const int txBytes = uart_write_bytes(UART_NUM_2, encode_buf, tx_encode_len);
return txBytes;
}
void send_time_stamp()
{
ESP_LOGI(TAG, "send_time_stamp");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_time_stamp_tag;
msg_out.payload.time_stamp = esp_timer_get_time() / 1000;
linkWrite(&msg_out);
printf("time stamp sent %i.\n", (int)msg_out.payload.time_stamp);
}
void send_connector_lock(bool on)
{
ESP_LOGI(TAG, "connector_lock");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_connector_lock_tag;
msg_out.payload.connector_lock = on;
linkWrite(&msg_out);
}
void send_max_charging_current(uint32_t max_charging_current)
{
ESP_LOGI(TAG, "send_max_charging_current");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_max_charging_current_tag;
msg_out.payload.max_charging_current = max_charging_current;
linkWrite(&msg_out);
}
void send_allow_power_on(bool allow_power_on)
{
ESP_LOGI(TAG, "allow_power_on");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_allow_power_on_tag;
msg_out.payload.allow_power_on = allow_power_on;
linkWrite(&msg_out);
}
void send_reset(bool reset)
{
ESP_LOGI(TAG, "reset");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_reset_tag;
msg_out.payload.reset = reset;
linkWrite(&msg_out);
}
void send_grid_current(uint32_t grid_current)
{
ESP_LOGI(TAG, "send_grid_current");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_grid_current_tag;
msg_out.payload.grid_current = grid_current;
linkWrite(&msg_out);
}
void send_max_grid_current(uint32_t max_grid_current)
{
ESP_LOGI(TAG, "send_max_grid_current");
HiToLo msg_out = HiToLo_init_default;
msg_out.which_payload = HiToLo_max_grid_current_tag;
msg_out.payload.max_grid_current = max_grid_current;
linkWrite(&msg_out);
}
/*
const evse_state_t cp_state_to_evse_state(CpState cp_state)
{
switch (state)
{
case CpState_EVSE_STATE_A:
return EVSE_STATE_A;
case CpState_EVSE_STATE_B1:
return EVSE_STATE_B1;
case CpState_EVSE_STATE_B2:
return EVSE_STATE_B2;
case CpState_EVSE_STATE_C1:
return EVSE_STATE_C1;
case CpState_EVSE_STATE_C2:
return EVSE_STATE_C2;
case CpState_EVSE_STATE_D1:
return EVSE_STATE_D1;
case CpState_EVSE_STATE_D2:
return EVSE_STATE_D2;
case CpState_EVSE_STATE_E:
return EVSE_STATE_E;
case CpState_EVSE_STATE_F:
return EVSE_STATE_F;
default:
return EVSE_STATE_F;
}
}
*/
void handlePacket(uint8_t *buf, int len)
{
// printf ("packet received len %u\n", len);
// Check CRC32 (last 4 bytes)
// uint32_t crc = calculateCrc(rx_packet_buf, rx_packet_len);
if (crc32(buf, len))
{
printf("CRC mismatch\n");
return;
}
len -= 4;
LoToHi msg_in;
pb_istream_t istream = pb_istream_from_buffer(buf, len);
if (pb_decode(&istream, LoToHi_fields, &msg_in))
{
//printf("LoToHi recvd %i \n", msg_in.which_payload);
switch (msg_in.which_payload)
{
case LoToHi_time_stamp_tag:
printf("Received time stamp %i\n", (int)msg_in.payload.time_stamp);
break;
case LoToHi_relais_state_tag:
printf("Received relais_state %i\n", (int)msg_in.payload.relais_state);
break;
case LoToHi_error_flags_tag:
// printf("Received error_flags %i\n", (int)msg_in.payload.error_flags);
break;
case LoToHi_cp_state_tag:
printf("Received cp_state %i\n", (int)msg_in.payload.cp_state);
// state = cp_state_to_evse_state(msg_in.payload.cp_state);
break;
case LoToHi_pp_state_tag:
printf("Received cp_state %i\n", (int)msg_in.payload.pp_state);
break;
case LoToHi_max_charging_current_tag:
printf("Received max_charging_current %i\n", (int)msg_in.payload.max_charging_current);
int max_charging_current = (int)msg_in.payload.max_charging_current;
if (max_charging_current != evse_get_max_charging_current())
{
send_max_charging_current(evse_get_max_charging_current());
}
break;
case LoToHi_max_grid_current_tag:
printf("Received max_grid_current %i\n", (int)msg_in.payload.max_grid_current);
int max_grid_current = (int)msg_in.payload.max_grid_current;
/*
if (max_grid_current != grid_get_max_current())
{
send_max_grid_current(grid_get_max_current());
}*/
break;
case LoToHi_lock_state_tag:
printf("Received lock_state %i\n", (int)msg_in.payload.lock_state);
break;
case LoToHi_power_meter_tag:
printf("Received power_meter %i\n", (int)msg_in.payload.power_meter.time_stamp);
break;
default:
printf("Not Found Sync Master recvd %i \n", msg_in.which_payload);
break;
}
}
}
static void tx_task(void *arg)
{
while (1)
{
// send_time_stamp();
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// send_connector_lock(true);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
send_max_charging_current(32);
vTaskDelay(1000 / portTICK_PERIOD_MS);
// send_allow_power_on(true);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// send_reset(true);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
static void rx_task(void *arg)
{
uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
while (1)
{
const int rxBytes = uart_read_bytes(UART_NUM_2, data, RX_BUF_SIZE, 1000 / portTICK_PERIOD_MS);
if (rxBytes > 0)
{
data[rxBytes] = 0;
// ESP_LOGI(TAG, "Read %d bytes: '%s'", rxBytes, data);
// ESP_LOG_BUFFER_HEXDUMP(TAG, data, rxBytes, ESP_LOG_INFO);
cobsDecode(data, rxBytes);
}
}
free(data);
}
void master_sync_start()
{
ESP_LOGI(TAG, "Master SYNC Serial");
init();
xTaskCreate(rx_task, "uart_rx_task", 1024 * 5, NULL, 3, NULL);
//xTaskCreate(tx_task, "uart_tx_task", 1024 * 5, NULL, 5, NULL);
}
void master_sync_stop(void)
{
/*
ESP_LOGI(TAG, "Stopping");
if (rx_task)
{
vTaskDelete(rx_task);
rx_task = NULL;
}
if (tx_task)
{
vTaskDelete(tx_task);
tx_task = NULL;
}
if (port != -1)
{
uart_driver_delete(port);
port = -1;
}*/
}

View File

@@ -1,533 +0,0 @@
/* UART asynchronous example, that uses separate RX and TX tasks sync_slave
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"
#include "nanopb/pb_decode.h"
#include "nanopb/pb_encode.h"
#include "nanopb/pb_common.h"
#include "LoToHi.pb.h"
#include "HiToLo.pb.h"
// #include "inc/version_autogen.h"
#include "sync_slave.h"
#include "loadbalancer.h"
#include "evse_api.h"
#include "evse_error.h"
#include "evse_state.h"
#include "evse_config.h"
#define VERSION_STRING "2.2"
static const int RX_BUF_SIZE = 1024;
#define TXD_PIN (GPIO_NUM_27)
#define RXD_PIN (GPIO_NUM_26)
static uint8_t msg[2048];
static uint8_t code;
static uint8_t block;
static uint8_t *decode;
// static ErrorFlags error_flags;
static const char *TAG = "SLAVE";
void cobsDecodeReset()
{
code = 0xff;
block = 0;
decode = msg;
}
void handlePacket(uint8_t *buf, int len);
void init(void)
{
const uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
// We won't use a buffer for sending data.
uart_driver_install(UART_NUM_2, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(UART_NUM_2, &uart_config);
uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
cobsDecodeReset();
}
uint32_t crc32(uint8_t *buf, int len)
{
int i, j;
uint32_t b, crc, msk;
i = 0;
crc = 0xFFFFFFFF;
while (i < len)
{
b = buf[i];
crc = crc ^ b;
for (j = 7; j >= 0; j--)
{
msk = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & msk);
}
i = i + 1;
}
// printf("%X",crc);
return crc;
}
void cobsDecodeByte(uint8_t byte)
{
// ESP_LOGI("RX_TASK", "init cobsDecodeByte");
// ESP_LOGI("RX_TASK", "Read bytes: '%02X'", byte);
// check max length
if ((decode - msg == 2048 - 1) && byte != 0x00)
{
ESP_LOGI(TAG, "cobsDecode: Buffer overflow");
cobsDecodeReset();
}
if (block)
{
// ESP_LOGI("RX_TASK", "block");
// we're currently decoding and should not get a 0
if (byte == 0x00)
{
// probably found some garbage -> reset
ESP_LOGI(TAG, "cobsDecode: Garbage detected");
cobsDecodeReset();
return;
}
*decode++ = byte;
}
else
{
// ESP_LOGI("RX_TASK", "not block");
if (code != 0xff)
{
*decode++ = 0;
}
block = code = byte;
if (code == 0x00)
{
// we're finished, reset everything and commit
if (decode == msg)
{
// we received nothing, just a 0x00
ESP_LOGI(TAG, "cobsDecode: Received nothing");
}
else
{
// set back decode with one, as it gets post-incremented
handlePacket(msg, decode - 1 - msg);
}
cobsDecodeReset();
return; // need to return here, because of block--
}
}
block--;
}
void cobsDecode(uint8_t *buf, int len)
{
for (int i = 0; i < len; i++)
{
cobsDecodeByte(buf[i]);
}
}
size_t cobsEncode(const void *data, size_t length, uint8_t *buffer)
{
uint8_t *encode = buffer; // Encoded byte pointer
uint8_t *codep = encode++; // Output code pointer
uint8_t code = 1; // Code value
for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte)
{
if (*byte) // Byte not zero, write it
*encode++ = *byte, ++code;
if (!*byte || code == 0xff) // Input is zero or block completed, restart
{
*codep = code, code = 1, codep = encode;
if (!*byte || length)
++encode;
}
}
*codep = code; // Write final code value
// add final 0
*encode++ = 0x00;
return encode - buffer;
}
int linkWrite(LoToHi *m)
{
ESP_LOGI(TAG, "linkWrite");
uint8_t tx_packet_buf[200];
uint8_t encode_buf[208];
pb_ostream_t ostream = pb_ostream_from_buffer(tx_packet_buf, sizeof(tx_packet_buf) - 4);
bool status = pb_encode(&ostream, LoToHi_fields, m);
if (!status)
{
// couldn't encode
return false;
}
size_t tx_payload_len = ostream.bytes_written;
// add crc32 (CRC-32/JAMCRC)
uint32_t crc = crc32(tx_packet_buf, tx_payload_len);
for (int byte_pos = 0; byte_pos < 4; ++byte_pos)
{
tx_packet_buf[tx_payload_len] = (uint8_t)crc & 0xFF;
crc = crc >> 8;
tx_payload_len++;
}
size_t tx_encode_len = cobsEncode(tx_packet_buf, tx_payload_len, encode_buf);
const int txBytes = uart_write_bytes(UART_NUM_2, encode_buf, tx_encode_len);
return txBytes;
}
void send_time_stamp()
{
ESP_LOGI(TAG, "send_time_stamp");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_time_stamp_tag;
msg_out.payload.time_stamp = esp_timer_get_time() / 1000;
linkWrite(&msg_out);
printf("time stamp sent %i.\n", (int)msg_out.payload.time_stamp);
}
void send_relais_state(bool on)
{
ESP_LOGI(TAG, "send_relais_state");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_relais_state_tag;
msg_out.payload.relais_state = on;
linkWrite(&msg_out);
}
void send_error_flags(ErrorFlags e)
{
ESP_LOGI(TAG, "send_error_flags");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_error_flags_tag;
msg_out.payload.error_flags = e;
linkWrite(&msg_out);
}
void send_cp_state(CpState cp_state)
{
ESP_LOGI(TAG, "send_cp_state");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_cp_state_tag;
msg_out.payload.cp_state = cp_state;
linkWrite(&msg_out);
}
void send_pp_state(PpState pp_state)
{
ESP_LOGI(TAG, "send_pp_state");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_pp_state_tag;
msg_out.payload.pp_state = pp_state;
linkWrite(&msg_out);
}
void send_max_charging_current(uint32_t max_charging_current)
{
ESP_LOGI(TAG, "send_max_charging_current");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_max_charging_current_tag;
msg_out.payload.max_charging_current = max_charging_current;
linkWrite(&msg_out);
}
void send_max_grid_current(uint32_t max_grid_current)
{
ESP_LOGI(TAG, "send_max_grid_current");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_max_grid_current_tag;
msg_out.payload.max_grid_current = max_grid_current;
linkWrite(&msg_out);
}
void send_lock_state(LockState lock_state)
{
ESP_LOGI(TAG, "send_lock_state");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_lock_state_tag;
msg_out.payload.lock_state = lock_state;
linkWrite(&msg_out);
}
void send_power_meter(PowerMeter p)
{
ESP_LOGI(TAG, "send_power_meter");
LoToHi msg_out = LoToHi_init_default;
msg_out.which_payload = LoToHi_power_meter_tag;
msg_out.payload.power_meter = p;
linkWrite(&msg_out);
}
void handlePacket(uint8_t *buf, int len)
{
// Check CRC32 (last 4 bytes)
// uint32_t crc = calculateCrc(rx_packet_buf, rx_packet_len);
if (crc32(buf, len))
{
printf("CRC mismatch\n");
return;
}
len -= 4;
HiToLo msg_in;
pb_istream_t istream = pb_istream_from_buffer(buf, len);
if (pb_decode(&istream, HiToLo_fields, &msg_in))
{
// printf("RemoteControl recvd %i \n", msg_in.which_payload);
switch (msg_in.which_payload)
{
case HiToLo_time_stamp_tag:
printf("Received time stamp tag \n");
break;
case HiToLo_connector_lock_tag:
printf("Received connector_lock %i\n", (int)msg_in.payload.connector_lock);
break;
case HiToLo_max_charging_current_tag:
printf("Received max_charging_current %i\n",
(int)msg_in.payload.max_charging_current);
evse_set_max_charging_current((int)msg_in.payload.max_charging_current);
break;
case HiToLo_max_grid_current_tag:
printf("Received max_grid_current %i\n",
(int)msg_in.payload.max_grid_current);
//grid_set_max_current((int)msg_in.payload.max_grid_current);
break;
case HiToLo_allow_power_on_tag:
printf("Received allow_poweron %i\n",
(int)msg_in.payload.allow_power_on);
break;
case HiToLo_reset_tag:
printf("Received reset\n");
break;
case HiToLo_grid_current_tag:
printf("Received grid_current %i\n",
(int)msg_in.payload.grid_current);
//setMaxGridCurrent(grid_get_max_current() * 10);
//setLiveGridCurrent((int)msg_in.payload.grid_current);
break;
default:
// printf("not found");
printf("Not Found RemoteControl recvd %i \n", msg_in.which_payload);
break;
}
}
}
/*
const int evse_state_to_int(evse_state_t state)
{
switch (state)
{
case EVSE_STATE_A:
return 1;
case EVSE_STATE_B1:
return 2;
case EVSE_STATE_B2:
return 3;
case EVSE_STATE_C1:
return 4;
case EVSE_STATE_C2:
return 5;
case EVSE_STATE_D1:
return 6;
case EVSE_STATE_D2:
return 7;
case EVSE_STATE_E:
return 8;
case EVSE_STATE_F:
return 9;
default:
return 9;
}
}
*/
CpState evse_state_to_cpState(evse_state_t state)
{
switch (state)
{
case EVSE_STATE_A:
return CpState_EVSE_STATE_A;
case EVSE_STATE_B1:
return CpState_EVSE_STATE_B1;
case EVSE_STATE_B2:
return CpState_EVSE_STATE_B2;
case EVSE_STATE_C1:
return CpState_EVSE_STATE_C1;
case EVSE_STATE_C2:
return CpState_EVSE_STATE_C2;
case EVSE_STATE_D1:
return CpState_EVSE_STATE_D1;
case EVSE_STATE_D2:
return CpState_EVSE_STATE_D2;
case EVSE_STATE_E:
return CpState_EVSE_STATE_E;
case EVSE_STATE_F:
return CpState_EVSE_STATE_F;
default:
return CpState_EVSE_STATE_F;
}
}
static void tx_task(void *arg)
{
/*
error_flags.connector_lock_failed = false;
error_flags.cp_signal_fault = false;
error_flags.diode_fault = false;
error_flags.rcd_selftest_failed = false;
error_flags.rcd_triggered = false;
error_flags.ventilation_not_available = false;
*/
while (1)
{
// send_time_stamp();
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// send_relais_state(false);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// send_error_flags(error_flags);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
send_cp_state(evse_state_to_cpState(evse_get_state()));
vTaskDelay(5000 / portTICK_PERIOD_MS);
// send_pp_state(PpState_STATE_32A);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
send_max_charging_current(evse_get_max_charging_current());
vTaskDelay(5000 / portTICK_PERIOD_MS);
//send_max_grid_current(grid_get_max_current());
vTaskDelay(5000 / portTICK_PERIOD_MS);
// send_lock_state(false);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
/*
PowerMeter p;
p.vrmsL1 = 230;
p.vrmsL2 = 230;
p.vrmsL3 = 230;
p.irmsL1 = 0;
p.irmsL2 = 0;
p.irmsL3 = 0;
p.irmsN = 0;
p.wattHrL1 = 0;
p.wattHrL2 = 0;
p.wattHrL3 = 0;
p.totalWattHr = 0;
p.tempL1 = 20;
p.tempL2 = 20;
p.tempL3 = 20;
p.tempN = 20;
p.wattL1 = 0;
p.wattL2 = 0;
p.wattL3 = 0;
p.freqL1 = 50;
p.freqL2 = 50;
p.freqL3 = 50;
p.phaseSeqError = 0;
// send_power_meter(p);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
*/
}
}
static void rx_task(void *arg)
{
uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
while (1)
{
const int rxBytes = uart_read_bytes(UART_NUM_2, data, RX_BUF_SIZE, 1000 / portTICK_PERIOD_MS);
if (rxBytes > 0)
{
data[rxBytes] = 0;
// ESP_LOGI(TAG, "Read %d bytes: '%s'", rxBytes, data);
// ESP_LOG_BUFFER_HEXDUMP(TAG, data, rxBytes, ESP_LOG_INFO);
cobsDecode(data, rxBytes);
}
}
free(data);
}
void slave_sync_start()
{
ESP_LOGI(TAG, "Starting SYNC Serial");
init();
xTaskCreate(rx_task, "uart_rx_task", 1024 * 5, NULL, 3, NULL);
xTaskCreate(tx_task, "uart_tx_task", 1024 * 5, NULL, 3, NULL);
}
void slave_sync_stop(void)
{
/*
ESP_LOGI(TAG, "Stopping");
if (rx_task)
{
vTaskDelete(rx_task);
rx_task = NULL;
}
if (tx_task)
{
vTaskDelete(tx_task);
tx_task = NULL;
}
if (port != -1)
{
uart_driver_delete(port);
port = -1;
}*/
}

View File

@@ -56,13 +56,13 @@ dependencies:
idf: idf:
source: source:
type: idf type: idf
version: 5.4.2 version: 5.4.3
direct_dependencies: direct_dependencies:
- espressif/cjson - espressif/cjson
- espressif/esp-modbus - espressif/esp-modbus
- espressif/mdns - espressif/mdns
- espressif/ntc_driver - espressif/ntc_driver
- idf - idf
manifest_hash: 4c69c7075a4a2aadc2a98b11d389519cb8621b7522e40f4fa9021acee9d2d03f manifest_hash: dd985fc132a07a7ca76fb5d4ccd1526be3bbb783f2a8ca2db065457e686212bd
target: esp32 target: esp32
version: 2.0.0 version: 2.0.0

View File

@@ -1,3 +1,4 @@
// === Início de: main/main.c ===
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
@@ -30,8 +31,9 @@
#include "buzzer.h" #include "buzzer.h"
#include "evse_link.h" #include "evse_link.h"
#include "ocpp.h" #include "ocpp.h"
#include "led.h"
#include "scheduler.h"
#define EVSE_MANAGER_TICK_PERIOD_MS 1000
#define AP_CONNECTION_TIMEOUT 120000 #define AP_CONNECTION_TIMEOUT 120000
#define RESET_HOLD_TIME 30000 #define RESET_HOLD_TIME 30000
#define DEBOUNCE_TIME_MS 50 #define DEBOUNCE_TIME_MS 50
@@ -41,9 +43,9 @@
static const char *TAG = "app_main"; static const char *TAG = "app_main";
static TaskHandle_t user_input_task; static TaskHandle_t user_input_task = NULL;
static TickType_t press_tick = 0; static TickType_t press_tick = 0;
static TickType_t last_interrupt_tick = 0; static volatile TickType_t last_interrupt_tick = 0;
static bool pressed = false; static bool pressed = false;
// //
@@ -79,6 +81,7 @@ static void fs_init(void)
fs_info(&cfg_conf); fs_info(&cfg_conf);
fs_info(&data_conf); fs_info(&data_conf);
} }
// //
// Wi-Fi event monitoring task // Wi-Fi event monitoring task
// //
@@ -87,17 +90,15 @@ static void wifi_event_task_func(void *param)
EventBits_t mode_bits; EventBits_t mode_bits;
for (;;) for (;;)
{ {
// Wait indefinitely until either AP or STA mode is entered
mode_bits = xEventGroupWaitBits( mode_bits = xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT,
pdFALSE, // do not clear bits on exit pdFALSE,
pdFALSE, // wait for any bit pdFALSE,
portMAX_DELAY); portMAX_DELAY);
if (mode_bits & WIFI_AP_MODE_BIT) if (mode_bits & WIFI_AP_MODE_BIT)
{ {
// We're in AP mode: wait for a client to connect within the timeout
if (xEventGroupWaitBits( if (xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_CONNECTED_BIT, WIFI_AP_CONNECTED_BIT,
@@ -106,7 +107,6 @@ static void wifi_event_task_func(void *param)
pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) &
WIFI_AP_CONNECTED_BIT) WIFI_AP_CONNECTED_BIT)
{ {
// Once connected, block until the client disconnects
xEventGroupWaitBits( xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_DISCONNECTED_BIT, WIFI_AP_DISCONNECTED_BIT,
@@ -116,7 +116,6 @@ static void wifi_event_task_func(void *param)
} }
else else
{ {
// Timeout expired with no client—optionally stop the AP
if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
{ {
// wifi_ap_stop(); // wifi_ap_stop();
@@ -125,7 +124,6 @@ static void wifi_event_task_func(void *param)
} }
else if (mode_bits & WIFI_STA_MODE_BIT) else if (mode_bits & WIFI_STA_MODE_BIT)
{ {
// We're in STA mode: block until disconnected from the AP
xEventGroupWaitBits( xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_STA_DISCONNECTED_BIT, WIFI_STA_DISCONNECTED_BIT,
@@ -133,9 +131,6 @@ static void wifi_event_task_func(void *param)
pdFALSE, pdFALSE,
portMAX_DELAY); portMAX_DELAY);
} }
// Prevent this task from hogging the CPU when idle
// vTaskDelay(pdMS_TO_TICKS(10));
} }
} }
@@ -144,7 +139,6 @@ static void wifi_event_task_func(void *param)
// //
static void handle_button_press(void) static void handle_button_press(void)
{ {
// If not already in AP mode, start it
if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT))
{ {
ESP_LOGI(TAG, "Starting Wi-Fi AP mode"); ESP_LOGI(TAG, "Starting Wi-Fi AP mode");
@@ -158,26 +152,26 @@ static void user_input_task_func(void *param)
uint32_t notification; uint32_t notification;
for (;;) for (;;)
{ {
// Wait for notification bits from ISR
if (xTaskNotifyWait( if (xTaskNotifyWait(
0, // do not clear any bits on entry 0,
UINT32_MAX, // clear all bits on exit UINT32_MAX,
&notification, &notification,
portMAX_DELAY)) portMAX_DELAY))
{ {
// Handle button press event
if (notification & PRESS_BIT) if (notification & PRESS_BIT)
{ {
press_tick = xTaskGetTickCount(); press_tick = xTaskGetTickCount();
pressed = true; pressed = true;
ESP_LOGI(TAG, "Button Pressed"); ESP_LOGI(TAG, "Button Pressed");
handle_button_press(); // só aqui handle_button_press();
} }
if ((notification & RELEASED_BIT) && pressed) if ((notification & RELEASED_BIT) && pressed)
{ {
pressed = false; pressed = false;
TickType_t held = xTaskGetTickCount() - press_tick; TickType_t held = xTaskGetTickCount() - press_tick;
ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held)); ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held));
if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME)) if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME))
{ {
ESP_LOGW(TAG, "Long press: erasing NVS + reboot"); ESP_LOGW(TAG, "Long press: erasing NVS + reboot");
@@ -195,18 +189,20 @@ static void IRAM_ATTR button_isr_handler(void *arg)
BaseType_t higher_task_woken = pdFALSE; BaseType_t higher_task_woken = pdFALSE;
TickType_t now = xTaskGetTickCountFromISR(); TickType_t now = xTaskGetTickCountFromISR();
// Debounce: ignore interrupts occurring too close together
if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
{ {
return; return;
} }
last_interrupt_tick = now; last_interrupt_tick = now;
// Read GPIO level: 0 = button pressed, 1 = button released if (user_input_task == NULL)
{
return;
}
int level = gpio_get_level(board_config.button_wifi_gpio); int level = gpio_get_level(board_config.button_wifi_gpio);
if (level == 0) if (level == 0)
{ {
// Notify task: button pressed
xTaskNotifyFromISR( xTaskNotifyFromISR(
user_input_task, user_input_task,
PRESS_BIT, PRESS_BIT,
@@ -215,7 +211,6 @@ static void IRAM_ATTR button_isr_handler(void *arg)
} }
else else
{ {
// Notify task: button released
xTaskNotifyFromISR( xTaskNotifyFromISR(
user_input_task, user_input_task,
RELEASED_BIT, RELEASED_BIT,
@@ -223,7 +218,6 @@ static void IRAM_ATTR button_isr_handler(void *arg)
&higher_task_woken); &higher_task_woken);
} }
// Yield to higher priority task if unblocked
if (higher_task_woken) if (higher_task_woken)
{ {
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
@@ -238,40 +232,31 @@ static void button_init(void)
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_ENABLE, .pull_up_en = GPIO_PULLUP_ENABLE,
.intr_type = GPIO_INTR_ANYEDGE}; .intr_type = GPIO_INTR_ANYEDGE};
ESP_ERROR_CHECK(gpio_config(&conf)); ESP_ERROR_CHECK(gpio_config(&conf));
ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL)); ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL));
} }
// //
// Inicialização dos módulos do sistema // Inicialização dos módulos do sistema (SEM botão)
// //
static void init_modules(void) static void init_modules(void)
{ {
peripherals_init(); peripherals_init();
led_init();
wifi_ini(); wifi_ini();
buzzer_init(); buzzer_init();
ESP_ERROR_CHECK(rest_server_init("/data")); ESP_ERROR_CHECK(rest_server_init("/data"));
protocols_init(); protocols_init();
evse_manager_init(); evse_manager_init();
evse_init(); // Cria a task para FSM evse_init();
button_init();
auth_init(); auth_init();
loadbalancer_init(); loadbalancer_init();
meter_manager_init(); meter_manager_init();
meter_manager_start(); meter_manager_start();
evse_link_init(); evse_link_init();
ocpp_start(); ocpp_start();
scheduler_init();
// wifi_ap_start();
// Outros módulos (descomente conforme necessário)
// meter_init();
// ocpp_start();
// orno_modbus_start();
// currentshaper_start();
// initWiegand();
// meter_zigbee_start();
// master_sync_start();
// slave_sync_start();
} }
// //
@@ -300,8 +285,18 @@ void app_main(void)
ESP_ERROR_CHECK(gpio_install_isr_service(0)); ESP_ERROR_CHECK(gpio_install_isr_service(0));
board_config_load(); board_config_load();
// 1) cria a task que recebe notificações do botão
xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task);
// 2) agora é seguro registrar ISR do botão
button_init();
// 3) inicia o resto do sistema
init_modules(); init_modules();
// 4) tasks auxiliares
xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL);
xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task);
} }
// === Fim de: main/main.c ===

2272
projeto_parte1.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX):
def main(): def main():
diretorio_main = "main" diretorio_main = "main"
componentes_escolhidos = [ componentes_escolhidos = [
"evse", "loadbalancer" "rest_api"
] ]
diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos] diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]