new module
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
DEVICE_NAME=Custom EVSE
|
||||
DEVICE_NAME=ChargeFlow
|
||||
|
||||
#LEDs
|
||||
LED_CHARGING=n
|
||||
LED_CHARGING_GPIO=
|
||||
LED_ERROR=n
|
||||
LED_ERROR_GPIO=
|
||||
LED_STOP=n
|
||||
LED_STOP_GPIO=
|
||||
led_blue=n
|
||||
led_blue_GPIO=
|
||||
led_red=n
|
||||
led_red_GPIO=
|
||||
led_green=n
|
||||
led_green_GPIO=
|
||||
|
||||
#Button
|
||||
BUTTON_WIFI_GPIO=32
|
||||
@@ -34,33 +34,4 @@ AC_RELAY_GPIO=25
|
||||
SOCKET_LOCK=n
|
||||
SOCKET_LOCK_A_GPIO=
|
||||
SOCKET_LOCK_B_GPIO=
|
||||
SOCKET_LOCK_DETECTION_GPIO=
|
||||
|
||||
#Energy meter (none | cur | cur_vlt)
|
||||
ENERGY_METER=none
|
||||
ENERGY_METER_THREE_PHASES=n
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
|
||||
ENERGY_METER_L1_CUR_ADC_CHANNEL=
|
||||
ENERGY_METER_L2_CUR_ADC_CHANNEL=
|
||||
ENERGY_METER_L3_CUR_ADC_CHANNEL=
|
||||
ENERGY_METER_CUR_SCALE=
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur_vlt)
|
||||
ENERGY_METER_L1_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_L2_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_L3_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_VLT_SCALE=
|
||||
|
||||
#Serial (SERIAL_X=none|uart|rs485)
|
||||
SERIAL_1=none
|
||||
SERIAL_1_NAME=UART 1
|
||||
SERIAL_1_RXD_GPIO=
|
||||
SERIAL_1_TXD_GPIO=
|
||||
SERIAL_1_RTS_GPIO=
|
||||
|
||||
SERIAL_2=none
|
||||
SERIAL_2_NAME=UART 2
|
||||
SERIAL_2_RXD_GPIO=
|
||||
SERIAL_2_TXD_GPIO=
|
||||
SERIAL_2_RTS_GPIO=
|
||||
SOCKET_LOCK_DETECTION_GPIO=
|
||||
@@ -1,12 +1,12 @@
|
||||
DEVICE_NAME=Plixin Evse
|
||||
DEVICE_NAME=ChargeFlow
|
||||
|
||||
#LEDs
|
||||
LED_CHARGING=y
|
||||
LED_CHARGING_GPIO=14
|
||||
LED_ERROR=y
|
||||
LED_ERROR_GPIO=26
|
||||
LED_STOP=y
|
||||
LED_STOP_GPIO=12
|
||||
led_blue=y
|
||||
led_blue_GPIO=14
|
||||
led_red=y
|
||||
led_red_GPIO=26
|
||||
led_green=y
|
||||
led_green_GPIO=12
|
||||
|
||||
#BUZZER
|
||||
BUZZER=y
|
||||
@@ -41,85 +41,4 @@ AC_RELAY_GPIO=25
|
||||
SOCKET_LOCK=n
|
||||
SOCKET_LOCK_A_GPIO=
|
||||
SOCKET_LOCK_B_GPIO=
|
||||
SOCKET_LOCK_DETECTION_GPIO=
|
||||
|
||||
#Energy meter (none | cur | cur_vlt)
|
||||
ENERGY_METER=none
|
||||
ENERGY_METER_THREE_PHASES=n
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
|
||||
ENERGY_METER_L1_CUR_ADC_CHANNEL=7
|
||||
ENERGY_METER_L2_CUR_ADC_CHANNEL=
|
||||
ENERGY_METER_L3_CUR_ADC_CHANNEL=
|
||||
ENERGY_METER_CUR_SCALE=0.090909091
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur_vlt)
|
||||
ENERGY_METER_L1_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_L2_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_L3_VLT_ADC_CHANNEL=
|
||||
ENERGY_METER_VLT_SCALE=0.47
|
||||
|
||||
#AUX
|
||||
AUX_IN_1=n
|
||||
AUX_IN_1_NAME=
|
||||
AUX_IN_1_GPIO=
|
||||
|
||||
AUX_IN_2=n
|
||||
AUX_IN_2_NAME=
|
||||
AUX_IN_2_GPIO=
|
||||
|
||||
AUX_IN_3=n
|
||||
AUX_IN_3_NAME=
|
||||
AUX_IN_3_GPIO=
|
||||
|
||||
AUX_IN_4=n
|
||||
AUX_IN_4_NAME=
|
||||
AUX_IN_4_GPIO=
|
||||
|
||||
AUX_OUT_1=n
|
||||
AUX_OUT_1_NAME=
|
||||
AUX_OUT_1_GPIO=
|
||||
|
||||
AUX_OUT_2=n
|
||||
AUX_OUT_2_NAME=
|
||||
AUX_OUT_2_GPIO=
|
||||
|
||||
AUX_OUT_3=n
|
||||
AUX_OUT_3_NAME=
|
||||
AUX_OUT_3_GPIO=
|
||||
|
||||
AUX_OUT_4=n
|
||||
AUX_OUT_4_NAME=
|
||||
AUX_OUT_4_GPIO=
|
||||
|
||||
AUX_AIN_1=n
|
||||
AUX_AIN_1_NAME=
|
||||
AUX_AIN_1_ADC_CHANNEL=
|
||||
|
||||
AUX_AIN_2=n
|
||||
AUX_AIN_2_NAME=
|
||||
AUX_AIN_2_ADC_CHANNEL=
|
||||
|
||||
#Serial (SERIAL_X=none|uart|rs485)
|
||||
SERIAL_1=none
|
||||
SERIAL_1_NAME=UART via USB
|
||||
SERIAL_1_RXD_GPIO=
|
||||
SERIAL_1_TXD_GPIO=
|
||||
SERIAL_1_RTS_GPIO=
|
||||
|
||||
SERIAL_2=none
|
||||
SERIAL_2_NAME=RS485
|
||||
SERIAL_2_RXD_GPIO=
|
||||
SERIAL_2_TXD_GPIO=
|
||||
SERIAL_2_RTS_GPIO=
|
||||
|
||||
SERIAL_3=none
|
||||
SERIAL_3_NAME=UART
|
||||
SERIAL_3_RXD_GPIO=
|
||||
SERIAL_3_TXD_GPIO=
|
||||
SERIAL_3_RTS_GPIO=
|
||||
|
||||
#Onewire devices
|
||||
ONEWIRE=n
|
||||
ONEWIRE_GPIO=
|
||||
ONEWIRE_TEMP_SENSOR=n
|
||||
SOCKET_LOCK_DETECTION_GPIO=
|
||||
@@ -1,12 +1,12 @@
|
||||
DEVICE_NAME=ESP32-S2-DA EVSE
|
||||
DEVICE_NAME=ChargeFlow
|
||||
|
||||
#LEDs
|
||||
LED_CHARGING=y
|
||||
LED_CHARGING_GPIO=36
|
||||
LED_ERROR=y
|
||||
LED_ERROR_GPIO=37
|
||||
LED_STOP=y
|
||||
LED_STOP_GPIO=35
|
||||
led_blue=y
|
||||
led_blue_GPIO=36
|
||||
led_red=y
|
||||
led_red_GPIO=37
|
||||
led_green=y
|
||||
led_green_GPIO=35
|
||||
|
||||
#Button
|
||||
BUTTON_WIFI_GPIO=32
|
||||
@@ -42,78 +42,3 @@ SOCKET_LOCK_MIN_BREAK_TIME=1000
|
||||
RCM=n
|
||||
RCM_GPIO=41
|
||||
RCM_TEST_GPIO=26
|
||||
|
||||
#Energy meter (none | cur | cur_vlt)
|
||||
ENERGY_METER=cur_vlt
|
||||
ENERGY_METER_THREE_PHASES=y
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
|
||||
ENERGY_METER_L1_CUR_ADC_CHANNEL=4
|
||||
ENERGY_METER_L2_CUR_ADC_CHANNEL=5
|
||||
ENERGY_METER_L3_CUR_ADC_CHANNEL=6
|
||||
ENERGY_METER_CUR_SCALE=0.090909091
|
||||
|
||||
#Energy meter internal (ENERGY_METER=cur_vlt)
|
||||
ENERGY_METER_L1_VLT_ADC_CHANNEL=7
|
||||
ENERGY_METER_L2_VLT_ADC_CHANNEL=8
|
||||
ENERGY_METER_L3_VLT_ADC_CHANNEL=9
|
||||
ENERGY_METER_VLT_SCALE=0.47
|
||||
|
||||
#AUX
|
||||
AUX_IN_1=n
|
||||
AUX_IN_1_NAME=
|
||||
AUX_IN_1_GPIO=
|
||||
|
||||
AUX_IN_2=n
|
||||
AUX_IN_2_NAME=
|
||||
AUX_IN_2_GPIO=
|
||||
|
||||
AUX_IN_3=n
|
||||
AUX_IN_3_NAME=
|
||||
AUX_IN_3_GPIO=
|
||||
|
||||
AUX_IN_4=n
|
||||
AUX_IN_4_NAME=
|
||||
AUX_IN_4_GPIO=
|
||||
|
||||
AUX_OUT_1=n
|
||||
AUX_OUT_1_NAME=
|
||||
AUX_OUT_1_GPIO=
|
||||
|
||||
AUX_OUT_2=n
|
||||
AUX_OUT_2_NAME=
|
||||
AUX_OUT_2_GPIO=
|
||||
|
||||
AUX_OUT_3=n
|
||||
AUX_OUT_3_NAME=
|
||||
AUX_OUT_3_GPIO=
|
||||
|
||||
AUX_OUT_4=n
|
||||
AUX_OUT_4_NAME=
|
||||
AUX_OUT_4_GPIO=
|
||||
|
||||
AUX_AIN_1=n
|
||||
AUX_AIN_1_NAME=
|
||||
AUX_AIN_1_ADC_CHANNEL=
|
||||
|
||||
AUX_AIN_2=n
|
||||
AUX_AIN_2_NAME=
|
||||
AUX_AIN_2_ADC_CHANNEL=
|
||||
|
||||
#Serial (SERIAL_X=none|uart|rs485)
|
||||
SERIAL_1=none
|
||||
SERIAL_1_NAME=UART
|
||||
SERIAL_1_RXD_GPIO=
|
||||
SERIAL_1_TXD_GPIO=
|
||||
SERIAL_1_RTS_GPIO=
|
||||
|
||||
SERIAL_2=none
|
||||
SERIAL_2_NAME=RS-485
|
||||
SERIAL_2_RXD_GPIO=
|
||||
SERIAL_2_TXD_GPIO=
|
||||
SERIAL_2_RTS_GPIO=
|
||||
|
||||
#Onewire devices
|
||||
ONEWIRE=n
|
||||
ONEWIRE_GPIO=
|
||||
ONEWIRE_TEMP_SENSOR=n
|
||||
@@ -4,4 +4,4 @@ idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "src"
|
||||
PRIV_REQUIRES nvs_flash driver esp_timer
|
||||
REQUIRES esp_event esp_idf_lib_helpers evse ocpp)
|
||||
REQUIRES esp_event evse ocpp evse_link)
|
||||
@@ -1,5 +1,3 @@
|
||||
// components/auth/src/auth.c
|
||||
|
||||
#include "auth.h"
|
||||
#include "auth_events.h"
|
||||
#include "esp_event.h"
|
||||
@@ -9,11 +7,13 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <string.h>
|
||||
#include <strings.h> // <-- necessário para strcasecmp
|
||||
#include <strings.h> // strcasecmp
|
||||
#include "wiegand_reader.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "evse_link_events.h"
|
||||
|
||||
#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 int tag_count = 0;
|
||||
static uint32_t s_next_req_id = 1;
|
||||
static bool s_wiegand_started = false; // controla se o Wiegand já foi iniciado
|
||||
|
||||
/* ===== NVS keys ===== */
|
||||
#define NVS_NAMESPACE "auth"
|
||||
#define NVS_TAG_PREFIX "tag_"
|
||||
#define NVS_TAG_COUNT_KEY "count"
|
||||
#define NVS_MODE_KEY "mode" // uint8_t
|
||||
#define NVS_NAMESPACE "auth"
|
||||
#define NVS_TAG_PREFIX "tag_"
|
||||
#define NVS_TAG_COUNT_KEY "count"
|
||||
#define NVS_MODE_KEY "mode" // uint8_t
|
||||
|
||||
/* =========================
|
||||
* NVS Persistence (tags)
|
||||
* ========================= */
|
||||
static void load_tags_from_nvs(void) {
|
||||
static void load_tags_from_nvs(void)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "No stored tags in NVS");
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "No stored tags in NVS (nvs_open: %s)", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t count = 0;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
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 tag_buf[AUTH_TAG_MAX_LEN];
|
||||
size_t len = sizeof(tag_buf);
|
||||
|
||||
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
|
||||
if (nvs_get_str(handle, key, tag_buf, &len) == ESP_OK) {
|
||||
err = nvs_get_str(handle, key, tag_buf, &len);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
|
||||
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
|
||||
tag_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to load tag %d from NVS (%s)", i, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count);
|
||||
}
|
||||
|
||||
static void save_tags_to_nvs(void) {
|
||||
static void save_tags_to_nvs(void)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS to save tags");
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS to save tags: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
|
||||
|
||||
for (int i = 0; i < tag_count; i++) {
|
||||
char key[16];
|
||||
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
|
||||
nvs_set_str(handle, key, valid_tags[i]);
|
||||
err = nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_set_u8(count) failed: %s", esp_err_to_name(err));
|
||||
nvs_close(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tag_count; i++)
|
||||
{
|
||||
char key[16];
|
||||
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
|
||||
err = nvs_set_str(handle, key, valid_tags[i]);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_set_str(%s) failed: %s", key, esp_err_to_name(err));
|
||||
nvs_close(handle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
err = nvs_commit(handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_commit failed when saving tags: %s", esp_err_to_name(err));
|
||||
nvs_close(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
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)
|
||||
* ========================= */
|
||||
static void load_mode_from_nvs(void) {
|
||||
static void load_mode_from_nvs(void)
|
||||
{
|
||||
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;
|
||||
if (nvs_get_u8(h, NVS_MODE_KEY, &u) == ESP_OK) {
|
||||
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID) s_mode = (auth_mode_t)u;
|
||||
err = nvs_get_u8(h, NVS_MODE_KEY, &u);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
|
||||
s_mode = (auth_mode_t)u;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_get_u8: %s). Default OPEN", esp_err_to_name(err));
|
||||
}
|
||||
nvs_close(h);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "No stored auth mode in NVS; 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));
|
||||
}
|
||||
|
||||
static void save_mode_to_nvs(auth_mode_t mode) {
|
||||
static void save_mode_to_nvs(auth_mode_t mode)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
|
||||
nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to save auth mode to NVS");
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS to save auth mode: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
|
||||
nvs_close(h);
|
||||
return;
|
||||
}
|
||||
|
||||
err = nvs_commit(h);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_commit failed when saving mode: %s", esp_err_to_name(err));
|
||||
nvs_close(h);
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_close(h);
|
||||
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* Helpers
|
||||
* ========================= */
|
||||
static bool is_tag_valid(const char *tag) {
|
||||
for (int i = 0; i < tag_count; i++) {
|
||||
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) {
|
||||
static bool is_tag_valid(const char *tag)
|
||||
{
|
||||
for (int i = 0; i < tag_count; i++)
|
||||
{
|
||||
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* 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
|
||||
* ========================= */
|
||||
void auth_init(void) {
|
||||
void auth_init(void)
|
||||
{
|
||||
load_mode_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();
|
||||
s_wiegand_started = true;
|
||||
ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
|
||||
}
|
||||
|
||||
auth_mode_event_data_t evt = { .mode = s_mode };
|
||||
// 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_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
|
||||
}
|
||||
|
||||
void auth_set_mode(auth_mode_t mode) {
|
||||
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID) {
|
||||
void auth_set_mode(auth_mode_t mode)
|
||||
{
|
||||
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID)
|
||||
{
|
||||
ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
|
||||
return;
|
||||
}
|
||||
if (mode == s_mode) {
|
||||
|
||||
if (mode == s_mode)
|
||||
{
|
||||
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
|
||||
return;
|
||||
}
|
||||
|
||||
auth_mode_t old = s_mode;
|
||||
s_mode = mode;
|
||||
save_mode_to_nvs(mode);
|
||||
|
||||
// Nota: se precisares, aqui podes parar/iniciar o Wiegand consoante o modo.
|
||||
if (s_mode == AUTH_MODE_OPEN) {
|
||||
ESP_LOGI(TAG, "Mode set to OPEN");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Mode set to %s; ensure Wiegand reader is running", auth_mode_to_str(s_mode));
|
||||
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
|
||||
|
||||
if (need_wiegand && !s_wiegand_started)
|
||||
{
|
||||
ESP_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);
|
||||
}
|
||||
|
||||
auth_mode_t auth_get_mode(void) {
|
||||
auth_mode_t auth_get_mode(void)
|
||||
{
|
||||
return s_mode;
|
||||
}
|
||||
|
||||
bool auth_add_tag(const char *tag) {
|
||||
if (tag_count >= MAX_TAGS) return false;
|
||||
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false;
|
||||
if (is_tag_valid(tag)) return true; // já existe
|
||||
bool auth_add_tag(const char *tag)
|
||||
{
|
||||
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN)
|
||||
return false;
|
||||
if (tag_count >= MAX_TAGS)
|
||||
return false;
|
||||
if (is_tag_valid(tag))
|
||||
return true; // já existe
|
||||
|
||||
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
|
||||
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
|
||||
@@ -188,11 +350,19 @@ bool auth_add_tag(const char *tag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool auth_remove_tag(const char *tag) {
|
||||
for (int i = 0; i < tag_count; i++) {
|
||||
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) {
|
||||
for (int j = i; j < tag_count - 1; j++) {
|
||||
bool auth_remove_tag(const char *tag)
|
||||
{
|
||||
if (!tag)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < tag_count; i++)
|
||||
{
|
||||
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
|
||||
{
|
||||
for (int j = i; j < tag_count - 1; j++)
|
||||
{
|
||||
strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN);
|
||||
valid_tags[j][AUTH_TAG_MAX_LEN - 1] = '\0';
|
||||
}
|
||||
tag_count--;
|
||||
|
||||
@@ -204,19 +374,26 @@ bool auth_remove_tag(const char *tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool auth_tag_exists(const char *tag) {
|
||||
bool auth_tag_exists(const char *tag)
|
||||
{
|
||||
if (!tag)
|
||||
return false;
|
||||
return is_tag_valid(tag);
|
||||
}
|
||||
|
||||
void auth_list_tags(void) {
|
||||
void auth_list_tags(void)
|
||||
{
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
void auth_wait_for_tag_registration(void) {
|
||||
if (s_mode != AUTH_MODE_LOCAL_RFID) {
|
||||
void auth_wait_for_tag_registration(void)
|
||||
{
|
||||
if (s_mode != AUTH_MODE_LOCAL_RFID)
|
||||
{
|
||||
ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
|
||||
return;
|
||||
}
|
||||
@@ -224,61 +401,82 @@ void auth_wait_for_tag_registration(void) {
|
||||
ESP_LOGI(TAG, "Tag registration mode enabled.");
|
||||
}
|
||||
|
||||
void auth_process_tag(const char *tag) {
|
||||
if (!tag || !*tag) {
|
||||
void auth_process_tag(const char *tag)
|
||||
{
|
||||
if (!tag || !*tag)
|
||||
{
|
||||
ESP_LOGW(TAG, "NULL/empty tag received");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (s_mode) {
|
||||
case AUTH_MODE_OPEN: {
|
||||
// Sem verificação; normalmente nem é necessário evento.
|
||||
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
|
||||
break;
|
||||
}
|
||||
switch (s_mode)
|
||||
{
|
||||
case AUTH_MODE_OPEN:
|
||||
{
|
||||
// Sem verificação; normalmente nem é necessário evento.
|
||||
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUTH_MODE_LOCAL_RFID: {
|
||||
if (waiting_for_registration) {
|
||||
waiting_for_registration = false;
|
||||
case AUTH_MODE_LOCAL_RFID:
|
||||
{
|
||||
if (waiting_for_registration)
|
||||
{
|
||||
waiting_for_registration = false;
|
||||
|
||||
if (auth_add_tag(tag)) {
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
ev.authorized = true;
|
||||
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &ev, sizeof(ev), portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Tag registered: %s", tag);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
|
||||
}
|
||||
return;
|
||||
if (auth_add_tag(tag))
|
||||
{
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
|
||||
ev.authorized = true;
|
||||
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
|
||||
&ev, sizeof(ev), portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Tag registered: %s", tag);
|
||||
}
|
||||
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
ev.authorized = is_tag_valid(tag);
|
||||
|
||||
ESP_LOGI(TAG, "LOCAL tag %s: %s", tag, ev.authorized ? "AUTHORIZED" : "DENIED");
|
||||
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, &ev, sizeof(ev), portMAX_DELAY);
|
||||
break;
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case AUTH_MODE_OCPP_RFID: {
|
||||
// Não decide localmente. Pede validação ao OCPP.
|
||||
auth_tag_verify_event_t rq = {0};
|
||||
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
rq.req_id = s_next_req_id++;
|
||||
ESP_LOGI(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)", rq.tag, (unsigned)rq.req_id);
|
||||
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, &rq, sizeof(rq), portMAX_DELAY);
|
||||
break;
|
||||
}
|
||||
auth_tag_event_data_t ev = {0};
|
||||
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
|
||||
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
|
||||
ev.authorized = is_tag_valid(tag);
|
||||
|
||||
ESP_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:
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
const char *auth_get_tag_by_index(int index) {
|
||||
if (index < 0 || index >= tag_count) return NULL;
|
||||
const char *auth_get_tag_by_index(int index)
|
||||
{
|
||||
if (index < 0 || index >= tag_count)
|
||||
return NULL;
|
||||
return valid_tags[index];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// === Início de: components/auth/src/wiegand.c ===
|
||||
/**
|
||||
* @file wiegand.c
|
||||
*
|
||||
@@ -6,9 +7,13 @@
|
||||
#include <esp_log.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <esp_idf_lib_helpers.h>
|
||||
#include <esp_attr.h> // <- para IRAM_ATTR
|
||||
#include "wiegand.h"
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR
|
||||
#endif
|
||||
|
||||
static const char *TAG = "wiegand";
|
||||
|
||||
#define TIMER_INTERVAL_US 50000 // 50ms
|
||||
@@ -39,11 +44,7 @@ static void isr_enable(wiegand_reader_t *reader)
|
||||
gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE);
|
||||
}
|
||||
|
||||
#if HELPER_TARGET_IS_ESP32
|
||||
static void IRAM_ATTR isr_handler(void *arg)
|
||||
#else
|
||||
static void isr_handler(void *arg)
|
||||
#endif
|
||||
{
|
||||
wiegand_reader_t *reader = (wiegand_reader_t *)arg;
|
||||
if (!reader->enabled)
|
||||
@@ -181,3 +182,5 @@ esp_err_t wiegand_reader_done(wiegand_reader_t *reader)
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// === Fim de: components/auth/src/wiegand.c ===
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <wiegand.h>
|
||||
#include "auth.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CONFIG_EXAMPLE_BUF_SIZE 50
|
||||
#define IDTAG_MAX_LEN 20
|
||||
|
||||
static const char *TAG = "WiegandReader";
|
||||
|
||||
// ---- Parâmetro global/locale para controlar a convenção de paridade ----
|
||||
// ---- Parâmetro global/local para controlar a convenção de paridade ----
|
||||
static const bool PARITY_INVERTED = false; // mude para true se o seu leitor vier "invertido"
|
||||
|
||||
static wiegand_reader_t reader;
|
||||
@@ -35,7 +35,7 @@ static inline uint8_t get_bit_msb_first(const uint8_t *buf, size_t bit_index)
|
||||
return (buf[bit_index / 8] >> (7 - (bit_index % 8))) & 0x01;
|
||||
}
|
||||
|
||||
// Versão parametrizável
|
||||
// Versão parametrizável de verificação de paridade
|
||||
static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
|
||||
{
|
||||
if (bits != 26 && bits != 34)
|
||||
@@ -55,8 +55,8 @@ static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
|
||||
if (get_bit_msb_first(buf, i))
|
||||
right++;
|
||||
}
|
||||
else
|
||||
{ // 34
|
||||
else // 34 bits
|
||||
{
|
||||
for (int i = 1; i <= 16; i++)
|
||||
if (get_bit_msb_first(buf, i))
|
||||
left++;
|
||||
@@ -117,24 +117,28 @@ static bool wiegand_extract_fc_cn(const uint8_t *buf, size_t bits, uint32_t *fc,
|
||||
{
|
||||
if (bits != 26 && bits != 34)
|
||||
return false;
|
||||
|
||||
uint32_t payload = 0;
|
||||
size_t payload_bits = bits - 2;
|
||||
|
||||
for (size_t i = 0; i < payload_bits; i++)
|
||||
{
|
||||
size_t bit = 1 + i; // ignora bit de paridade inicial
|
||||
uint8_t bitval = (buf[bit / 8] >> (7 - (bit % 8))) & 0x01;
|
||||
payload = (payload << 1) | bitval;
|
||||
}
|
||||
|
||||
if (bits == 26)
|
||||
{
|
||||
*fc = (payload >> 16) & 0xFF; // 8b
|
||||
*cn = payload & 0xFFFF; // 16b
|
||||
}
|
||||
else
|
||||
else // 34 bits
|
||||
{
|
||||
*fc = (payload >> 16) & 0xFFFF; // 16b
|
||||
*cn = payload & 0xFFFF; // 16b
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -145,10 +149,14 @@ static bool build_idtag_w26_4B(uint32_t fc, uint32_t cn, char *out, size_t outle
|
||||
(uint8_t)((cn >> 8) & 0xFF),
|
||||
(uint8_t)(cn & 0xFF),
|
||||
0};
|
||||
|
||||
raw[3] = crc8_atm(raw, 3);
|
||||
|
||||
if (outlen < 9)
|
||||
return false;
|
||||
int n = snprintf(out, outlen, "%02X%02X%02X%02X", raw[0], raw[1], raw[2], raw[3]);
|
||||
|
||||
int n = snprintf(out, outlen, "%02X%02X%02X%02X",
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
return (n > 0 && (size_t)n < outlen);
|
||||
}
|
||||
|
||||
@@ -160,31 +168,42 @@ static bool build_idtag_w34_7B(uint32_t fc, uint32_t cn, char *out, size_t outle
|
||||
raw[2] = (uint8_t)(fc & 0xFF);
|
||||
raw[3] = (uint8_t)((cn >> 8) & 0xFF);
|
||||
raw[4] = (uint8_t)(cn & 0xFF);
|
||||
|
||||
uint16_t crc = crc16_ibm(raw, 5);
|
||||
raw[5] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
raw[6] = (uint8_t)(crc & 0xFF);
|
||||
|
||||
if (outlen < 15)
|
||||
return false;
|
||||
|
||||
int n = snprintf(out, outlen, "%02X%02X%02X%02X%02X%02X%02X",
|
||||
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]);
|
||||
raw[0], raw[1], raw[2], raw[3],
|
||||
raw[4], raw[5], raw[6]);
|
||||
return (n > 0 && (size_t)n < outlen);
|
||||
}
|
||||
|
||||
// Se o callback for de ISR, troque para xQueueSendToBackFromISR.
|
||||
// Callback chamado pelo esp_timer_task (contexto de task, NÃO ISR)
|
||||
static void reader_callback(wiegand_reader_t *r)
|
||||
{
|
||||
if (!queue)
|
||||
{
|
||||
ESP_LOGW(TAG, "Queue not ready, dropping packet");
|
||||
return;
|
||||
}
|
||||
|
||||
data_packet_t p = {0};
|
||||
p.bits = r->bits;
|
||||
p.bytes = (r->bits + 7) / 8;
|
||||
|
||||
if (p.bytes > sizeof(p.data))
|
||||
p.bytes = sizeof(p.data);
|
||||
|
||||
memcpy(p.data, r->buf, p.bytes);
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
// Se NÃO for ISR, use xQueueSendToBack(queue, &p, 0);
|
||||
xQueueSendToBackFromISR(queue, &p, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken)
|
||||
portYIELD_FROM_ISR();
|
||||
if (xQueueSendToBack(queue, &p, 0) != pdPASS)
|
||||
{
|
||||
ESP_LOGW(TAG, "Queue full, dropping Wiegand packet");
|
||||
}
|
||||
}
|
||||
|
||||
static void wiegand_task(void *arg)
|
||||
@@ -197,13 +216,20 @@ static void wiegand_task(void *arg)
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(wiegand_reader_init(&reader, 21, 22,
|
||||
true, CONFIG_EXAMPLE_BUF_SIZE, reader_callback, WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST));
|
||||
ESP_ERROR_CHECK(wiegand_reader_init(&reader,
|
||||
21, 22, // GPIO D0, D1
|
||||
true, // internal pullups
|
||||
CONFIG_EXAMPLE_BUF_SIZE,
|
||||
reader_callback,
|
||||
WIEGAND_MSB_FIRST,
|
||||
WIEGAND_LSB_FIRST));
|
||||
|
||||
data_packet_t p;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ESP_LOGI(TAG, "Waiting for Wiegand data...");
|
||||
|
||||
if (xQueueReceive(queue, &p, portMAX_DELAY) != pdPASS)
|
||||
continue;
|
||||
|
||||
@@ -216,7 +242,7 @@ static void wiegand_task(void *arg)
|
||||
continue;
|
||||
}
|
||||
|
||||
char tag[21] = {0}; // OCPP 1.6: máx 20 chars (+NUL)
|
||||
char tag[IDTAG_MAX_LEN + 1] = {0}; // OCPP 1.6: máx 20 chars (+NUL)
|
||||
uint32_t fc = 0, cn = 0;
|
||||
|
||||
if (!wiegand_extract_fc_cn(p.data, p.bits, &fc, &cn))
|
||||
@@ -230,8 +256,8 @@ static void wiegand_task(void *arg)
|
||||
{
|
||||
ok = build_idtag_w26_4B(fc, cn, tag, sizeof(tag)); // 8 hex
|
||||
}
|
||||
else
|
||||
{ // 34
|
||||
else // 34
|
||||
{
|
||||
ok = build_idtag_w34_7B(fc, cn, tag, sizeof(tag)); // 14 hex
|
||||
}
|
||||
|
||||
@@ -250,9 +276,9 @@ static void wiegand_sim_task(void *arg)
|
||||
{
|
||||
// lista fixa de idTags simuladas
|
||||
static const char *idtaglist[] = {
|
||||
"0000004134962107",
|
||||
"00000041349",
|
||||
"W2602312345",
|
||||
"W34ABCDE12345",
|
||||
"W34ABCDE123",
|
||||
};
|
||||
const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]);
|
||||
|
||||
@@ -263,7 +289,7 @@ static void wiegand_sim_task(void *arg)
|
||||
ESP_LOGI(TAG, "Simulação -> idTag: %s", idtaglist[i]);
|
||||
auth_process_tag(idtaglist[i]);
|
||||
|
||||
// espera 20 segundos entre cada tag
|
||||
// espera 30 segundos entre cada tag
|
||||
vTaskDelay(pdMS_TO_TICKS(30000));
|
||||
}
|
||||
}
|
||||
@@ -274,6 +300,8 @@ void initWiegand(void)
|
||||
ESP_LOGI(TAG, "Initializing Wiegand reader");
|
||||
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);
|
||||
|
||||
// ESP_LOGI(TAG, "Inicializando Wiegand simulado");
|
||||
// xTaskCreate(wiegand_sim_task, "WiegandSim", configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
|
||||
// Para testes, podes ativar o simulador:
|
||||
//ESP_LOGI(TAG, "Inicializando Wiegand simulado");
|
||||
//xTaskCreate(wiegand_sim_task, "WiegandSim",
|
||||
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
|
||||
}
|
||||
|
||||
@@ -18,50 +18,32 @@
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
// ===================== Configuração padrão (pode migrar para Kconfig) =====================
|
||||
#ifndef CONFIG_BUZZER_GPIO
|
||||
// ===================== Configuração padrão =====================
|
||||
#define CONFIG_BUZZER_GPIO GPIO_NUM_27
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_MODE_PASSIVE // 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
|
||||
#define CONFIG_BUZZER_MODE_PASSIVE 1
|
||||
#endif
|
||||
// 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
|
||||
#define CONFIG_BUZZER_MODE_PASSIVE 0
|
||||
|
||||
#ifndef CONFIG_BUZZER_FREQ_HZ
|
||||
#define CONFIG_BUZZER_FREQ_HZ 3500
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_DUTY_PCT
|
||||
#define CONFIG_BUZZER_DUTY_PCT 70
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_QUEUE_LEN
|
||||
#define CONFIG_BUZZER_QUEUE_LEN 8
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_TASK_STACK
|
||||
#define CONFIG_BUZZER_TASK_STACK 2048
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_TASK_PRIO
|
||||
#define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1)
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_MIN_GAP_MS // anti-spam (gap mínimo entre toques)
|
||||
// anti-spam (gap mínimo entre toques)
|
||||
#define CONFIG_BUZZER_MIN_GAP_MS 70
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_ENABLE_DEFAULT
|
||||
#define CONFIG_BUZZER_ENABLE_DEFAULT 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_QUIET_START_MIN // quiet hours start (minutos desde 00:00)
|
||||
// quiet hours start (minutos desde 00:00)
|
||||
#define CONFIG_BUZZER_QUIET_START_MIN (22 * 60)
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BUZZER_QUIET_END_MIN // quiet hours end (minutos desde 00:00)
|
||||
// quiet hours end (minutos desde 00:00)
|
||||
#define CONFIG_BUZZER_QUIET_END_MIN (7 * 60)
|
||||
#endif
|
||||
|
||||
// ===================== Tipos e estado =====================
|
||||
static const char *TAG = "Buzzer";
|
||||
@@ -415,11 +397,25 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
|
||||
{
|
||||
if (base != NETWORK_EVENTS)
|
||||
return;
|
||||
if (id == NETWORK_EVENT_AP_STARTED)
|
||||
|
||||
ESP_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};
|
||||
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY);
|
||||
case NETWORK_EVENT_AP_STARTED:
|
||||
case NETWORK_EVENT_STA_CONNECTED:
|
||||
// Usa padrão de AP_START para indicar rede disponível
|
||||
evt.pattern = BUZZER_PATTERN_AP_START;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Para já, ignorar outros eventos de rede
|
||||
return;
|
||||
}
|
||||
|
||||
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
|
||||
@@ -506,7 +502,7 @@ void buzzer_init(void)
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, network_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler, NULL));
|
||||
|
||||
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d",
|
||||
s_buzzer_cfg.gpio,
|
||||
@@ -522,7 +518,7 @@ void buzzer_deinit(void)
|
||||
(void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler);
|
||||
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler);
|
||||
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler);
|
||||
(void)esp_event_handler_unregister(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, network_event_handler);
|
||||
(void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler);
|
||||
|
||||
if (s_buzzer_q)
|
||||
{
|
||||
|
||||
@@ -15,32 +15,6 @@ bool atob(const char *value)
|
||||
return value[0] == 'y';
|
||||
}
|
||||
|
||||
board_config_energy_meter_t atoem(const char *value)
|
||||
{
|
||||
if (!strcmp(value, "cur"))
|
||||
{
|
||||
return BOARD_CONFIG_ENERGY_METER_CUR;
|
||||
}
|
||||
if (!strcmp(value, "cur_vlt"))
|
||||
{
|
||||
return BOARD_CONFIG_ENERGY_METER_CUR_VLT;
|
||||
}
|
||||
return BOARD_CONFIG_ENERGY_METER_NONE;
|
||||
}
|
||||
|
||||
board_config_serial_t atoser(const char *value)
|
||||
{
|
||||
if (!strcmp(value, "uart"))
|
||||
{
|
||||
return BOARD_CONFIG_SERIAL_UART;
|
||||
}
|
||||
if (!strcmp(value, "rs485"))
|
||||
{
|
||||
return BOARD_CONFIG_SERIAL_RS485;
|
||||
}
|
||||
return BOARD_CONFIG_SERIAL_NONE;
|
||||
}
|
||||
|
||||
#define SET_CONFIG_VALUE(name, prop, convert_fn) \
|
||||
if (!strcmp(key, name)) \
|
||||
{ \
|
||||
@@ -94,16 +68,14 @@ void board_config_load()
|
||||
if (value != NULL)
|
||||
{
|
||||
SET_CONFIG_VALUE_STR("DEVICE_NAME", device_name);
|
||||
SET_CONFIG_VALUE("LED_CHARGING", led_charging, atob);
|
||||
SET_CONFIG_VALUE("LED_CHARGING_GPIO", led_charging_gpio, atoi);
|
||||
SET_CONFIG_VALUE("LED_ERROR", led_error, atob);
|
||||
SET_CONFIG_VALUE("LED_ERROR_GPIO", led_error_gpio, atoi);
|
||||
SET_CONFIG_VALUE("LED_STOP", led_stop, atob);
|
||||
SET_CONFIG_VALUE("LED_STOP_GPIO", led_stop_gpio, atoi);
|
||||
|
||||
SET_CONFIG_VALUE("led_blue", led_blue, atob);
|
||||
SET_CONFIG_VALUE("led_blue_GPIO", led_blue_gpio, atoi);
|
||||
SET_CONFIG_VALUE("led_red", led_red, atob);
|
||||
SET_CONFIG_VALUE("led_red_GPIO", led_red_gpio, atoi);
|
||||
SET_CONFIG_VALUE("led_green", led_green, atob);
|
||||
SET_CONFIG_VALUE("led_green_GPIO", led_green_gpio, atoi);
|
||||
SET_CONFIG_VALUE("BUZZER", buzzer, atob);
|
||||
SET_CONFIG_VALUE("BUZZER_GPIO", buzzer_gpio, atoi);
|
||||
|
||||
SET_CONFIG_VALUE("BUTTON_WIFI_GPIO", button_wifi_gpio, atoi);
|
||||
SET_CONFIG_VALUE("PILOT_PWM_GPIO", pilot_pwm_gpio, atoi);
|
||||
SET_CONFIG_VALUE("PILOT_ADC_CHANNEL", pilot_adc_channel, atoi);
|
||||
@@ -130,76 +102,6 @@ void board_config_load()
|
||||
SET_CONFIG_VALUE("RCM", rcm, atob);
|
||||
SET_CONFIG_VALUE("RCM_GPIO", rcm_gpio, atoi);
|
||||
SET_CONFIG_VALUE("RCM_TEST_GPIO", rcm_test_gpio, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER", energy_meter, atoem);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_THREE_PHASES", energy_meter_three_phases, atob);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L1_CUR_ADC_CHANNEL", energy_meter_l1_cur_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L2_CUR_ADC_CHANNEL", energy_meter_l2_cur_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L3_CUR_ADC_CHANNEL", energy_meter_l3_cur_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_CUR_SCALE", energy_meter_cur_scale, atoff);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L1_VLT_ADC_CHANNEL", energy_meter_l1_vlt_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L2_VLT_ADC_CHANNEL", energy_meter_l2_vlt_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_L3_VLT_ADC_CHANNEL", energy_meter_l3_vlt_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("ENERGY_METER_VLT_SCALE", energy_meter_vlt_scale, atoff);
|
||||
SET_CONFIG_VALUE("AUX_IN_1", aux_in_1, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_IN_1_NAME", aux_in_1_name);
|
||||
SET_CONFIG_VALUE("AUX_IN_1_GPIO", aux_in_1_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_IN_2", aux_in_2, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_IN_2_NAME", aux_in_2_name);
|
||||
SET_CONFIG_VALUE("AUX_IN_2_GPIO", aux_in_2_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_IN_3", aux_in_3, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_IN_3_NAME", aux_in_3_name);
|
||||
SET_CONFIG_VALUE("AUX_IN_3_GPIO", aux_in_3_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_IN_4", aux_in_4, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_IN_4_NAME", aux_in_4_name);
|
||||
SET_CONFIG_VALUE("AUX_IN_4_GPIO", aux_in_4_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_OUT_1", aux_out_1, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_OUT_1_NAME", aux_out_1_name);
|
||||
SET_CONFIG_VALUE("AUX_OUT_1_GPIO", aux_out_1_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_OUT_2", aux_out_2, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_OUT_2_NAME", aux_out_2_name);
|
||||
SET_CONFIG_VALUE("AUX_OUT_2_GPIO", aux_out_2_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_OUT_3", aux_out_3, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_OUT_3_NAME", aux_out_3_name);
|
||||
SET_CONFIG_VALUE("AUX_OUT_3_GPIO", aux_out_3_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_OUT_4", aux_out_4, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_OUT_4_NAME", aux_out_4_name);
|
||||
SET_CONFIG_VALUE("AUX_OUT_4_GPIO", aux_out_4_gpio, atoi);
|
||||
SET_CONFIG_VALUE("AUX_AIN_1", aux_ain_1, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_AIN_1_NAME", aux_ain_1_name);
|
||||
SET_CONFIG_VALUE("AUX_AIN_1_ADC_CHANNEL", aux_ain_1_adc_channel, atoi);
|
||||
SET_CONFIG_VALUE("AUX_AIN_2", aux_ain_2, atob);
|
||||
SET_CONFIG_VALUE_STR("AUX_AIN_2_NAME", aux_ain_2_name);
|
||||
SET_CONFIG_VALUE("AUX_AIN_2_ADC_CHANNEL", aux_ain_2_adc_channel, atoi);
|
||||
/*
|
||||
#if CONFIG_ESP_CONSOLE_UART_NUM != 0
|
||||
SET_CONFIG_VALUE("SERIAL_1", serial_1, atoser);
|
||||
SET_CONFIG_VALUE_STR("SERIAL_1_NAME", serial_1_name);
|
||||
SET_CONFIG_VALUE("SERIAL_1_RXD_GPIO", serial_1_rxd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_1_TXD_GPIO", serial_1_txd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_1_RTS_GPIO", serial_1_rts_gpio, atoi);
|
||||
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 0
|
||||
#if CONFIG_ESP_CONSOLE_UART_NUM != 1
|
||||
SET_CONFIG_VALUE("SERIAL_2", serial_2, atoser);
|
||||
SET_CONFIG_VALUE_STR("SERIAL_2_NAME", serial_2_name);
|
||||
SET_CONFIG_VALUE("SERIAL_2_RXD_GPIO", serial_2_rxd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_2_TXD_GPIO", serial_2_txd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_2_RTS_GPIO", serial_2_rts_gpio, atoi);
|
||||
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 1
|
||||
#if SOC_UART_NUM > 2
|
||||
#if CONFIG_ESP_CONSOLE_UART_NUM != 2
|
||||
SET_CONFIG_VALUE("SERIAL_3", serial_3, atoser);
|
||||
SET_CONFIG_VALUE_STR("SERIAL_3_NAME", serial_3_name);
|
||||
SET_CONFIG_VALUE("SERIAL_3_RXD_GPIO", serial_3_rxd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_3_TXD_GPIO", serial_3_txd_gpio, atoi);
|
||||
SET_CONFIG_VALUE("SERIAL_3_RTS_GPIO", serial_3_rts_gpio, atoi);
|
||||
#endif / CONFIG_ESP_CONSOLE_UART_NUM != 2
|
||||
#endif // SOC_UART_NUM > 2
|
||||
SET_CONFIG_VALUE("ONEWIRE", onewire, atob);
|
||||
SET_CONFIG_VALUE("ONEWIRE_GPIO", onewire_gpio, atoi);
|
||||
SET_CONFIG_VALUE("ONEWIRE_TEMP_SENSOR", onewire_temp_sensor, atob);
|
||||
|
||||
ESP_LOGE(TAG, "Unknown config value %s=%s", key, value);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
#include "hal/gpio_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
BOARD_CONFIG_ENERGY_METER_NONE,
|
||||
BOARD_CONFIG_ENERGY_METER_CUR,
|
||||
BOARD_CONFIG_ENERGY_METER_CUR_VLT
|
||||
} board_config_energy_meter_t;
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
BOARD_CONFIG_SERIAL_NONE,
|
||||
BOARD_CONFIG_SERIAL_UART,
|
||||
BOARD_CONFIG_SERIAL_RS485
|
||||
@@ -21,12 +23,12 @@ typedef struct
|
||||
{
|
||||
char device_name[32];
|
||||
|
||||
bool led_charging : 1;
|
||||
gpio_num_t led_charging_gpio;
|
||||
bool led_error : 1;
|
||||
gpio_num_t led_error_gpio;
|
||||
bool led_stop : 1;
|
||||
gpio_num_t led_stop_gpio;
|
||||
bool led_blue : 1;
|
||||
gpio_num_t led_blue_gpio;
|
||||
bool led_red : 1;
|
||||
gpio_num_t led_red_gpio;
|
||||
bool led_green : 1;
|
||||
gpio_num_t led_green_gpio;
|
||||
|
||||
bool buzzer : 1;
|
||||
gpio_num_t buzzer_gpio;
|
||||
@@ -63,80 +65,6 @@ typedef struct
|
||||
gpio_num_t rcm_gpio;
|
||||
gpio_num_t rcm_test_gpio;
|
||||
|
||||
board_config_energy_meter_t energy_meter;
|
||||
bool energy_meter_three_phases : 1;
|
||||
|
||||
adc_channel_t energy_meter_l1_cur_adc_channel;
|
||||
adc_channel_t energy_meter_l2_cur_adc_channel;
|
||||
adc_channel_t energy_meter_l3_cur_adc_channel;
|
||||
float energy_meter_cur_scale;
|
||||
adc_channel_t energy_meter_l1_vlt_adc_channel;
|
||||
adc_channel_t energy_meter_l2_vlt_adc_channel;
|
||||
adc_channel_t energy_meter_l3_vlt_adc_channel;
|
||||
float energy_meter_vlt_scale;
|
||||
|
||||
|
||||
bool aux_in_1 : 1;
|
||||
char aux_in_1_name[8];
|
||||
gpio_num_t aux_in_1_gpio;
|
||||
|
||||
bool aux_in_2 : 1;
|
||||
char aux_in_2_name[8];
|
||||
gpio_num_t aux_in_2_gpio;
|
||||
|
||||
bool aux_in_3 : 1;
|
||||
char aux_in_3_name[8];
|
||||
gpio_num_t aux_in_3_gpio;
|
||||
|
||||
bool aux_in_4 : 1;
|
||||
char aux_in_4_name[8];
|
||||
gpio_num_t aux_in_4_gpio;
|
||||
|
||||
bool aux_out_1 : 1;
|
||||
char aux_out_1_name[8];
|
||||
gpio_num_t aux_out_1_gpio;
|
||||
|
||||
bool aux_out_2 : 1;
|
||||
char aux_out_2_name[8];
|
||||
gpio_num_t aux_out_2_gpio;
|
||||
|
||||
bool aux_out_3 : 1;
|
||||
char aux_out_3_name[8];
|
||||
gpio_num_t aux_out_3_gpio;
|
||||
|
||||
bool aux_out_4 : 1;
|
||||
char aux_out_4_name[8];
|
||||
gpio_num_t aux_out_4_gpio;
|
||||
|
||||
bool aux_ain_1 : 1;
|
||||
char aux_ain_1_name[8];
|
||||
adc_channel_t aux_ain_1_adc_channel;
|
||||
|
||||
bool aux_ain_2 : 1;
|
||||
char aux_ain_2_name[8];
|
||||
adc_channel_t aux_ain_2_adc_channel;
|
||||
|
||||
board_config_serial_t serial_1;
|
||||
char serial_1_name[16];
|
||||
gpio_num_t serial_1_rxd_gpio;
|
||||
gpio_num_t serial_1_txd_gpio;
|
||||
gpio_num_t serial_1_rts_gpio;
|
||||
board_config_serial_t serial_2;
|
||||
char serial_2_name[16];
|
||||
gpio_num_t serial_2_rxd_gpio;
|
||||
gpio_num_t serial_2_txd_gpio;
|
||||
gpio_num_t serial_2_rts_gpio;
|
||||
#if SOC_UART_NUM > 2
|
||||
board_config_serial_t serial_3;
|
||||
char serial_3_name[16];
|
||||
gpio_num_t serial_3_rxd_gpio;
|
||||
gpio_num_t serial_3_txd_gpio;
|
||||
gpio_num_t serial_3_rts_gpio;
|
||||
#endif /* SOC_UART_NUM > 2 */
|
||||
|
||||
bool onewire : 1;
|
||||
gpio_num_t onewire_gpio;
|
||||
bool onewire_temp_sensor : 1;
|
||||
} board_config_t;
|
||||
|
||||
extern board_config_t board_config;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: esp_idf_lib_helpers
|
||||
description: Common support library for esp-idf-lib
|
||||
version: 1.2.0
|
||||
groups:
|
||||
- common
|
||||
code_owners:
|
||||
- trombik
|
||||
- UncleRus
|
||||
depends:
|
||||
- freertos
|
||||
thread_safe: n/a
|
||||
targets:
|
||||
- esp32
|
||||
- esp8266
|
||||
- esp32s2
|
||||
- esp32c3
|
||||
license: ISC
|
||||
copyrights:
|
||||
- name: trombik
|
||||
year: 2019
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES freertos
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,8 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS = .
|
||||
|
||||
ifdef CONFIG_IDF_TARGET_ESP8266
|
||||
COMPONENT_DEPENDS = esp8266 freertos
|
||||
else
|
||||
COMPONENT_DEPENDS = freertos
|
||||
endif
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#if !defined(__ESP_IDF_LIB_HELPERS__H__)
|
||||
#define __ESP_IDF_LIB_HELPERS__H__
|
||||
|
||||
/* XXX this header file does not need to include freertos/FreeRTOS.h.
|
||||
* but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct
|
||||
* order. as this header depends on sdkconfig.h, sdkconfig.h must be included
|
||||
* first. however, the SDK includes this header first, then includes
|
||||
* `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an
|
||||
* evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed
|
||||
* build.
|
||||
*/
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <esp_idf_version.h>
|
||||
|
||||
#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL)
|
||||
#error Unknown ESP-IDF/ESP8266 RTOS SDK version
|
||||
#endif
|
||||
|
||||
/* Minimal supported version for ESP32, ESP32S2 */
|
||||
#define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5)
|
||||
/* Minimal supported version for ESP8266 */
|
||||
#define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||
|
||||
/* HELPER_TARGET_IS_ESP32
|
||||
* 1 when the target is esp32
|
||||
*/
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32S2) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32S3) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32C2) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32C3) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32C6) \
|
||||
|| defined(CONFIG_IDF_TARGET_ESP32H2)
|
||||
#define HELPER_TARGET_IS_ESP32 (1)
|
||||
#define HELPER_TARGET_IS_ESP8266 (0)
|
||||
|
||||
/* HELPER_TARGET_IS_ESP8266
|
||||
* 1 when the target is esp8266
|
||||
*/
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP8266)
|
||||
#define HELPER_TARGET_IS_ESP32 (0)
|
||||
#define HELPER_TARGET_IS_ESP8266 (1)
|
||||
#else
|
||||
#error BUG: cannot determine the target
|
||||
#endif
|
||||
|
||||
#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER
|
||||
#error Unsupported ESP-IDF version. Please update!
|
||||
#endif
|
||||
|
||||
#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER
|
||||
#error Unsupported ESP8266 RTOS SDK version. Please update!
|
||||
#endif
|
||||
|
||||
/* show the actual values for debugging */
|
||||
#if DEBUG
|
||||
#define VALUE_TO_STRING(x) #x
|
||||
#define VALUE(x) VALUE_TO_STRING(x)
|
||||
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
|
||||
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3))
|
||||
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32H2))
|
||||
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2))
|
||||
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32))
|
||||
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266))
|
||||
#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include <esp32/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32C2
|
||||
#include <esp32c2/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#include <esp32c3/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32C6
|
||||
#include <esp32c6/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32H2
|
||||
#include <esp32h2/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32H4
|
||||
#include <esp32h4/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include <esp32s2/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#include <esp32s3/rom/ets_sys.h>
|
||||
#elif CONFIG_IDF_TARGET_ESP8266
|
||||
#include <rom/ets_sys.h>
|
||||
#else
|
||||
#error "ets_sys: Unknown target"
|
||||
#endif
|
||||
@@ -18,5 +18,5 @@ idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES nvs_flash driver
|
||||
REQUIRES peripherals auth loadbalancer
|
||||
REQUIRES peripherals auth loadbalancer scheduler
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "evse_config.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_limits.h"
|
||||
#include "evse_api.h" // <— para evse_get_state / evse_state_is_charging
|
||||
#include "evse_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_timer.h"
|
||||
@@ -74,8 +74,8 @@ void evse_check_defaults(void)
|
||||
charging_current = u16;
|
||||
}
|
||||
|
||||
// Runtime charging current initialized from persisted default
|
||||
charging_current_runtime = max_charging_current;
|
||||
// Runtime charging current inicializado a partir do default persistido
|
||||
charging_current_runtime = charging_current;
|
||||
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime);
|
||||
|
||||
// Auth required
|
||||
@@ -150,7 +150,6 @@ void evse_check_defaults(void)
|
||||
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
|
||||
}
|
||||
|
||||
// Save to NVS if needed
|
||||
if (needs_commit)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
if (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);
|
||||
|
||||
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
|
||||
evse_config_event_data_t evt = {
|
||||
.charging = evse_state_is_charging(evse_get_state()),
|
||||
.hw_max_current = (float)evse_get_max_charging_current(),
|
||||
@@ -314,7 +311,6 @@ void evse_config_set_available(bool available)
|
||||
{
|
||||
is_available = available ? true : false;
|
||||
|
||||
// Persist
|
||||
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
|
||||
if (err == ESP_OK)
|
||||
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));
|
||||
}
|
||||
|
||||
// AVAILABLE_UPDATED
|
||||
evse_available_event_data_t e = {
|
||||
.available = is_available,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
@@ -342,7 +337,6 @@ void evse_config_set_enabled(bool enabled)
|
||||
{
|
||||
is_enabled = enabled ? true : false;
|
||||
|
||||
// Persist
|
||||
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
|
||||
if (err == ESP_OK)
|
||||
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));
|
||||
}
|
||||
|
||||
// ENABLE_UPDATED
|
||||
evse_enable_event_data_t e = {
|
||||
.enabled = is_enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// evse_core.c - Main EVSE control logic
|
||||
|
||||
#include "evse_fsm.h"
|
||||
#include "evse_error.h"
|
||||
#include "evse_limits.h"
|
||||
@@ -16,29 +14,34 @@ static const char *TAG = "evse_core";
|
||||
static SemaphoreHandle_t mutex;
|
||||
static evse_state_t last_state = EVSE_STATE_A;
|
||||
|
||||
static void evse_process(void);
|
||||
static void evse_core_task(void *arg);
|
||||
|
||||
// ================================
|
||||
// Initialization
|
||||
// ================================
|
||||
|
||||
void evse_init(void) {
|
||||
void evse_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "EVSE Init");
|
||||
|
||||
mutex = xSemaphoreCreateMutex(); // Optional: use static version for deterministic memory
|
||||
mutex = xSemaphoreCreateMutex();
|
||||
if (!mutex)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to create EVSE core mutex");
|
||||
return;
|
||||
}
|
||||
|
||||
evse_check_defaults();
|
||||
evse_fsm_reset();
|
||||
pilot_set_level(true); // Enable pilot output
|
||||
pilot_set_level(true);
|
||||
|
||||
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Main Processing Logic
|
||||
// ================================
|
||||
static void evse_process(void)
|
||||
{
|
||||
if (!mutex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void evse_process(void) {
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
|
||||
pilot_voltage_t pilot_voltage;
|
||||
@@ -49,34 +52,38 @@ void evse_process(void) {
|
||||
|
||||
evse_error_check(pilot_voltage, is_n12v);
|
||||
|
||||
// Só chama FSM, que decide tudo
|
||||
evse_fsm_process(
|
||||
pilot_voltage,
|
||||
evse_state_get_authorized(),
|
||||
evse_config_is_available(),
|
||||
evse_config_is_enabled()
|
||||
);
|
||||
evse_config_is_enabled());
|
||||
|
||||
evse_limits_check();
|
||||
|
||||
evse_state_t current = evse_get_state();
|
||||
if (current != last_state) {
|
||||
//ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current));
|
||||
last_state = current;
|
||||
if (evse_is_limit_reached())
|
||||
{
|
||||
if (evse_state_get_authorized())
|
||||
{
|
||||
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
|
||||
evse_state_set_authorized(false);
|
||||
}
|
||||
}
|
||||
|
||||
evse_mark_error_cleared();
|
||||
evse_state_t current = evse_get_state();
|
||||
if (current != last_state)
|
||||
{
|
||||
last_state = current;
|
||||
}
|
||||
|
||||
xSemaphoreGive(mutex);
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Background Task
|
||||
// ================================
|
||||
|
||||
static void evse_core_task(void *arg) {
|
||||
while (true) {
|
||||
static void evse_core_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (true)
|
||||
{
|
||||
evse_process();
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz cycle
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,118 +3,227 @@
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "esp_log.h"
|
||||
#include "ntc_sensor.h"
|
||||
|
||||
static const char *TAG = "evse_error";
|
||||
|
||||
// Estado global de erros
|
||||
static uint32_t error_bits = 0;
|
||||
static TickType_t auto_clear_timeout = 0;
|
||||
|
||||
// Sticky flag: "todos erros foram limpos"
|
||||
static bool error_cleared = false;
|
||||
|
||||
void evse_error_init(void) {
|
||||
// Inicialização do sistema de erros
|
||||
// Proteção contra concorrência
|
||||
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void evse_error_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) {
|
||||
ESP_LOGD(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s",
|
||||
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
|
||||
{
|
||||
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
|
||||
pilot_voltage, is_n12v ? "true" : "false");
|
||||
|
||||
// Falha elétrica geral no pilot
|
||||
if (pilot_voltage == PILOT_VOLTAGE_1) {
|
||||
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT)) { // Verifica se o erro já foi registrado
|
||||
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
|
||||
// 1) Falha elétrica geral no pilot
|
||||
if (pilot_voltage == PILOT_VOLTAGE_1)
|
||||
{
|
||||
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)");
|
||||
}
|
||||
}
|
||||
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)
|
||||
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) {
|
||||
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT)) { // Verifica se o erro já foi registrado
|
||||
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
|
||||
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
|
||||
ESP_LOGW(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", pilot_voltage, is_n12v ? "true" : "false");
|
||||
// 2) Falta de -12V durante PWM (C ou D)
|
||||
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
|
||||
{
|
||||
bool first_time = false;
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT))
|
||||
{
|
||||
error_cleared = false;
|
||||
error_bits |= EVSE_ERR_DIODE_SHORT_BIT;
|
||||
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
|
||||
first_time = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (first_time)
|
||||
{
|
||||
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Se já não estamos em C/D sem -12V, limpa o erro de diodo curto
|
||||
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_temperature_check(void) {
|
||||
float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida)
|
||||
uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável
|
||||
void evse_temperature_check(void)
|
||||
{
|
||||
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", temp_c, threshold);
|
||||
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
|
||||
temp_c, threshold);
|
||||
|
||||
// Se a temperatura parecer inválida, aplica erro de sensor
|
||||
if (temp_c < -40.0f || temp_c > 150.0f) {
|
||||
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado
|
||||
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
|
||||
// Temperatura inválida -> erro de sensor
|
||||
if (temp_c < -40.0f || temp_c > 150.0f)
|
||||
{
|
||||
bool first_time = false;
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT))
|
||||
{
|
||||
error_cleared = false;
|
||||
error_bits |= EVSE_ERR_TEMPERATURE_FAULT_BIT;
|
||||
first_time = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (first_time)
|
||||
{
|
||||
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado
|
||||
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
|
||||
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold);
|
||||
// Temperatura máxima
|
||||
if (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);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t evse_get_error(void) {
|
||||
return error_bits;
|
||||
uint32_t evse_get_error(void)
|
||||
{
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
uint32_t val = error_bits;
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
return val;
|
||||
}
|
||||
|
||||
bool evse_is_error_cleared(void) {
|
||||
return error_cleared;
|
||||
bool evse_error_cleared_flag(void)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
|
||||
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) {
|
||||
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS)
|
||||
{
|
||||
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
}
|
||||
|
||||
void evse_error_clear(uint32_t bitmask) {
|
||||
bool had_error = error_bits != 0;
|
||||
void evse_error_clear(uint32_t bitmask)
|
||||
{
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
|
||||
bool had_error = (error_bits != 0);
|
||||
error_bits &= ~bitmask;
|
||||
|
||||
if (had_error && error_bits == 0) {
|
||||
if (had_error && error_bits == 0)
|
||||
{
|
||||
error_cleared = true;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
}
|
||||
|
||||
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);
|
||||
void evse_error_tick(void)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
}
|
||||
|
||||
bool evse_error_is_active(void) {
|
||||
return error_bits != 0;
|
||||
bool evse_error_is_active(void)
|
||||
{
|
||||
return evse_get_error() != 0;
|
||||
}
|
||||
|
||||
uint32_t evse_error_get_bits(void) {
|
||||
return error_bits;
|
||||
}
|
||||
|
||||
void evse_error_reset_flag(void) {
|
||||
error_cleared = false;
|
||||
}
|
||||
|
||||
bool evse_error_cleared_flag(void) {
|
||||
return error_cleared;
|
||||
uint32_t evse_error_get_bits(void)
|
||||
{
|
||||
return evse_get_error();
|
||||
}
|
||||
|
||||
@@ -20,94 +20,108 @@ static const char *TAG = "evse_fsm";
|
||||
static bool c1_d1_waiting = false;
|
||||
static TickType_t c1_d1_relay_to = 0;
|
||||
|
||||
void evse_fsm_reset(void) {
|
||||
void evse_fsm_reset(void)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
c1_d1_waiting = false;
|
||||
c1_d1_relay_to = 0;
|
||||
}
|
||||
|
||||
// ... includes e defines como já estão
|
||||
|
||||
static void update_outputs(evse_state_t state) {
|
||||
static void update_outputs(evse_state_t state)
|
||||
{
|
||||
const uint16_t current = evse_get_runtime_charging_current();
|
||||
uint8_t cable_max_current = evse_get_max_charging_current();
|
||||
const bool socket_outlet = evse_get_socket_outlet();
|
||||
|
||||
if (socket_outlet) {
|
||||
if (socket_outlet)
|
||||
{
|
||||
cable_max_current = proximity_get_max_current();
|
||||
}
|
||||
|
||||
// Segurança: relé sempre off e outputs seguros em caso de erro
|
||||
if (evse_get_error() != 0) {
|
||||
if (ac_relay_get_state()) {
|
||||
if (evse_get_error() != 0)
|
||||
{
|
||||
if (ac_relay_get_state())
|
||||
{
|
||||
ac_relay_set_state(false);
|
||||
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
|
||||
}
|
||||
ac_relay_set_state(false); // redundância tolerável
|
||||
pilot_set_level(true); // sinal pilot sempre 12V (A)
|
||||
if (board_config.socket_lock && socket_outlet) {
|
||||
ac_relay_set_state(false);
|
||||
pilot_set_level(true);
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fluxo normal
|
||||
switch (state) {
|
||||
case EVSE_STATE_A:
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
ac_relay_set_state(false);
|
||||
pilot_set_level(state == EVSE_STATE_A);
|
||||
if (board_config.socket_lock && socket_outlet) {
|
||||
socket_lock_set_locked(false);
|
||||
}
|
||||
break;
|
||||
|
||||
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;
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
ac_relay_set_state(false);
|
||||
pilot_set_level(state == EVSE_STATE_A);
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(false);
|
||||
}
|
||||
case EVSE_STATE_C2:
|
||||
case EVSE_STATE_D2:
|
||||
pilot_set_amps(MIN(current, cable_max_current));
|
||||
ac_relay_set_state(true); // Só chega aqui se não há erro!
|
||||
break;
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
pilot_set_level(true);
|
||||
ac_relay_set_state(false);
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
|
||||
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(
|
||||
pilot_voltage_t pilot_voltage,
|
||||
bool authorized,
|
||||
bool available,
|
||||
bool enabled
|
||||
) {
|
||||
bool enabled)
|
||||
{
|
||||
// Proteção total: erro força F sempre!
|
||||
if (evse_get_error() != 0) {
|
||||
if (evse_get_state() != EVSE_STATE_F) {
|
||||
if (evse_get_error() != 0)
|
||||
{
|
||||
if (evse_get_state() != EVSE_STATE_F)
|
||||
{
|
||||
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)");
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
}
|
||||
@@ -119,91 +133,97 @@ void evse_fsm_process(
|
||||
evse_state_t prev = evse_get_state();
|
||||
evse_state_t curr = prev;
|
||||
|
||||
switch (curr) {
|
||||
case EVSE_STATE_A:
|
||||
if (!available) {
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
} else if (pilot_voltage == PILOT_VOLTAGE_9) {
|
||||
evse_set_state(EVSE_STATE_B1);
|
||||
}
|
||||
break;
|
||||
switch (curr)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
}
|
||||
else if (pilot_voltage == PILOT_VOLTAGE_9)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_B1);
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
if (!available) {
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
switch (pilot_voltage)
|
||||
{
|
||||
case PILOT_VOLTAGE_12:
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
break;
|
||||
case PILOT_VOLTAGE_9:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_6:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_D1:
|
||||
if (c1_d1_waiting && now >= c1_d1_relay_to)
|
||||
{
|
||||
ac_relay_set_state(false);
|
||||
c1_d1_waiting = false;
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
__attribute__((fallthrough));
|
||||
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_D1:
|
||||
if (c1_d1_waiting && now >= c1_d1_relay_to) {
|
||||
ac_relay_set_state(false);
|
||||
c1_d1_waiting = false;
|
||||
if (!available) {
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
}
|
||||
__attribute__((fallthrough));
|
||||
|
||||
case EVSE_STATE_C2:
|
||||
case EVSE_STATE_D2:
|
||||
if (!enabled || !available) {
|
||||
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1)
|
||||
? EVSE_STATE_D1 : EVSE_STATE_C1);
|
||||
break;
|
||||
}
|
||||
switch (pilot_voltage) {
|
||||
case PILOT_VOLTAGE_6:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_3:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_9:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_12:
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case 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;
|
||||
|
||||
case EVSE_STATE_E:
|
||||
// Estado elétrico grave: só reset manual
|
||||
}
|
||||
switch (pilot_voltage)
|
||||
{
|
||||
case PILOT_VOLTAGE_6:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
|
||||
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);
|
||||
}
|
||||
case PILOT_VOLTAGE_3:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_9:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
|
||||
break;
|
||||
case PILOT_VOLTAGE_12:
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_E:
|
||||
// Estado elétrico grave: só reset manual
|
||||
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();
|
||||
update_outputs(next);
|
||||
|
||||
if (next != prev) {
|
||||
ESP_LOGI(TAG, "State changed: %s -> %s",
|
||||
evse_state_to_str(prev),
|
||||
evse_state_to_str(next));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,8 @@
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.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
|
||||
#include "esp_err.h"
|
||||
#include "nvs.h"
|
||||
|
||||
// ========================
|
||||
// Concurrency protection
|
||||
@@ -26,24 +20,17 @@ static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
// Runtime state (volatile)
|
||||
// ========================
|
||||
|
||||
static bool limit_reached = false;
|
||||
static uint32_t consumption_limit = 0; // Energy limit in Wh
|
||||
static uint32_t charging_time_limit = 0; // Time limit in seconds
|
||||
static uint16_t under_power_limit = 0; // Minimum acceptable power in W
|
||||
|
||||
// ========================
|
||||
// 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;
|
||||
static bool limit_reached = false;
|
||||
static uint32_t consumption_limit = 0; // Energy limit in Wh
|
||||
static uint32_t charging_time_limit = 0; // Time limit in seconds
|
||||
static uint16_t under_power_limit = 0; // Minimum acceptable power in W
|
||||
|
||||
// ========================
|
||||
// Limit status flag
|
||||
// ========================
|
||||
|
||||
bool evse_get_limit_reached(void) {
|
||||
bool evse_get_limit_reached(void)
|
||||
{
|
||||
bool val;
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
val = limit_reached;
|
||||
@@ -51,17 +38,24 @@ bool evse_get_limit_reached(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void evse_set_limit_reached(bool v) {
|
||||
void evse_set_limit_reached(bool v)
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
limit_reached = v;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
}
|
||||
|
||||
bool evse_is_limit_reached(void)
|
||||
{
|
||||
return evse_get_limit_reached();
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Runtime limit accessors
|
||||
// ========================
|
||||
|
||||
uint32_t evse_get_consumption_limit(void) {
|
||||
uint32_t evse_get_consumption_limit(void)
|
||||
{
|
||||
uint32_t val;
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
val = consumption_limit;
|
||||
@@ -69,13 +63,47 @@ uint32_t evse_get_consumption_limit(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void evse_set_consumption_limit(uint32_t value) {
|
||||
void evse_set_consumption_limit(uint32_t value)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
consumption_limit = value;
|
||||
if (consumption_limit != value)
|
||||
{
|
||||
consumption_limit = value;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
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;
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
val = charging_time_limit;
|
||||
@@ -83,13 +111,47 @@ uint32_t evse_get_charging_time_limit(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void evse_set_charging_time_limit(uint32_t value) {
|
||||
void evse_set_charging_time_limit(uint32_t value)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
charging_time_limit = value;
|
||||
if (charging_time_limit != value)
|
||||
{
|
||||
charging_time_limit = value;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
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;
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
val = under_power_limit;
|
||||
@@ -97,92 +159,97 @@ uint16_t evse_get_under_power_limit(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void evse_set_under_power_limit(uint16_t value) {
|
||||
void evse_set_under_power_limit(uint16_t value)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
under_power_limit = value;
|
||||
if (under_power_limit != value)
|
||||
{
|
||||
under_power_limit = value;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Default (persistent) limit accessors
|
||||
// These values can be stored/restored via NVS
|
||||
// ========================
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
uint32_t evse_get_default_consumption_limit(void) {
|
||||
return default_consumption_limit;
|
||||
}
|
||||
nvs_handle_t h;
|
||||
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) {
|
||||
default_consumption_limit = value;
|
||||
}
|
||||
|
||||
uint32_t evse_get_default_charging_time_limit(void) {
|
||||
return default_charging_time_limit;
|
||||
}
|
||||
|
||||
void evse_set_default_charging_time_limit(uint32_t value) {
|
||||
default_charging_time_limit = value;
|
||||
}
|
||||
|
||||
uint16_t evse_get_default_under_power_limit(void) {
|
||||
return default_under_power_limit;
|
||||
}
|
||||
|
||||
void evse_set_default_under_power_limit(uint16_t value) {
|
||||
default_under_power_limit = value;
|
||||
}
|
||||
|
||||
bool evse_is_limit_reached(void) {
|
||||
return evse_get_limit_reached();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE("EVSE_LIMITS",
|
||||
"Failed to persist under-power limit (%" PRIu32 " W): %s",
|
||||
(uint32_t)value, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE("EVSE_LIMITS",
|
||||
"Failed to open NVS for under-power limit: %s",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Limit checking logic
|
||||
// This function must be called periodically while charging.
|
||||
// It will flag the session as "limit reached" when thresholds are violated.
|
||||
// ========================
|
||||
void evse_limits_check(void) {
|
||||
// Only check during an active charging session
|
||||
if (!evse_state_is_charging(evse_get_state())) {
|
||||
|
||||
void evse_limits_check(void)
|
||||
{
|
||||
// Só faz sentido durante carregamento
|
||||
if (!evse_state_is_charging(evse_get_state()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
evse_session_t sess;
|
||||
// Retrieve accumulated data for the current session
|
||||
if (!evse_session_get(&sess) || !sess.is_current) {
|
||||
// If there's no active session, abort
|
||||
if (!evse_session_get(&sess) || !sess.is_current)
|
||||
{
|
||||
// Sem sessão ativa → nada a fazer
|
||||
return;
|
||||
}
|
||||
|
||||
bool reached = false;
|
||||
|
||||
// 1) Energy consumption limit (Wh)
|
||||
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) {
|
||||
// 1) Limite de energia (Wh)
|
||||
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit)
|
||||
{
|
||||
ESP_LOGW("EVSE_LIMITS",
|
||||
"Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
|
||||
sess.energy_wh, consumption_limit);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
// 2) Charging time limit (seconds)
|
||||
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) {
|
||||
// 2) Limite de tempo (s)
|
||||
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit)
|
||||
{
|
||||
ESP_LOGW("EVSE_LIMITS",
|
||||
"Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
|
||||
sess.duration_s, charging_time_limit);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
// 3) Under-power limit (instantaneous power)
|
||||
// 3) Under-power (potência instantânea)
|
||||
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",
|
||||
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
|
||||
(uint32_t)inst_power,
|
||||
(uint32_t)under_power_limit);
|
||||
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
|
||||
(uint32_t)inst_power,
|
||||
(uint32_t)under_power_limit);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
if (reached) {
|
||||
if (reached)
|
||||
{
|
||||
evse_set_limit_reached(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,29 +7,35 @@
|
||||
#include "evse_api.h"
|
||||
#include "evse_meter.h"
|
||||
#include "evse_session.h"
|
||||
#include "evse_config.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "auth_events.h"
|
||||
#include "loadbalancer_events.h"
|
||||
#include "ocpp_events.h"
|
||||
#include "esp_event.h"
|
||||
#include "scheduler_events.h"
|
||||
|
||||
static const char *TAG = "EVSE_Manager";
|
||||
|
||||
static SemaphoreHandle_t evse_mutex;
|
||||
static bool auth_enabled = false;
|
||||
static volatile bool auth_enabled = false;
|
||||
|
||||
// Estado de pausa controlado pelo Load Balancer
|
||||
static bool lb_paused = false;
|
||||
static bool lb_prev_authorized = false;
|
||||
static volatile bool lb_paused = 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
|
||||
|
||||
@@ -41,8 +47,20 @@ static void lb_clear_pause_state(void)
|
||||
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)
|
||||
{
|
||||
bool sched_allowed = evse_sched_is_allowed();
|
||||
|
||||
if (auth_enabled)
|
||||
{
|
||||
// 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
|
||||
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
|
||||
{
|
||||
// Se autenticação está desativada, garante autorização sempre ativa
|
||||
if (!evse_state_get_authorized())
|
||||
// Modo OPEN: só autoriza se LB e Scheduler permitirem
|
||||
if (!lb_paused && sched_allowed && !evse_state_get_authorized())
|
||||
{
|
||||
evse_state_set_authorized(true);
|
||||
ESP_LOGI(TAG, "Authentication disabled → forced authorization.");
|
||||
// Em modo OPEN, pausa do LB não é tão relevante, mas limpamos mesmo assim
|
||||
ESP_LOGI(TAG, "Authentication disabled → forced authorization (within schedule).");
|
||||
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 =====
|
||||
static void evse_manager_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
while (true)
|
||||
{
|
||||
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)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
if (base != AUTH_EVENTS || !data)
|
||||
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));
|
||||
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;
|
||||
}
|
||||
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,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
(void)handler_arg;
|
||||
(void)event_base;
|
||||
|
||||
if (!event_data)
|
||||
return;
|
||||
|
||||
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
|
||||
{
|
||||
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
|
||||
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
|
||||
evt->enabled ? "ENABLED" : "DISABLED",
|
||||
(long long)evt->timestamp_us);
|
||||
// Ações adicionais podem ser adicionadas aqui conforme necessário
|
||||
}
|
||||
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
|
||||
{
|
||||
@@ -167,16 +210,17 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
|
||||
{
|
||||
lb_paused = false;
|
||||
|
||||
// Só retomamos se EVSE estiver operacional
|
||||
// Só retomamos se EVSE estiver operacional e scheduler permitir
|
||||
bool can_resume =
|
||||
(evse_get_error() == 0) &&
|
||||
evse_config_is_available() &&
|
||||
evse_config_is_enabled();
|
||||
evse_config_is_enabled() &&
|
||||
evse_sched_is_allowed();
|
||||
|
||||
if (!can_resume)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado)",
|
||||
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado/fora de horário)",
|
||||
evt->max_current);
|
||||
lb_clear_pause_state();
|
||||
return;
|
||||
@@ -184,7 +228,7 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
|
||||
|
||||
if (!auth_enabled)
|
||||
{
|
||||
// Modo OPEN: retoma sempre
|
||||
// Modo OPEN: retoma sempre (se dentro da janela do scheduler)
|
||||
ESP_LOGI(TAG,
|
||||
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
|
||||
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)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
if (base != OCPP_EVENTS)
|
||||
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:
|
||||
ESP_LOGI(TAG, "[OCPP] StartTx");
|
||||
// StartTx em si não precisa mexer em auth, mas limpamos estado de pausa por segurança
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
@@ -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)
|
||||
evse_config_set_enabled(ev->operative);
|
||||
// Opcional: poderias também limpar a pausa aqui, dependendo da política
|
||||
// lb_clear_pause_state();
|
||||
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 =====
|
||||
void evse_manager_init(void)
|
||||
{
|
||||
evse_mutex = xSemaphoreCreateMutex();
|
||||
configASSERT(evse_mutex != NULL);
|
||||
|
||||
evse_config_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(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
|
||||
|
||||
ESP_LOGI(TAG, "EVSE Manager inicializado.");
|
||||
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
|
||||
|
||||
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
|
||||
configASSERT(rc == pdPASS);
|
||||
}
|
||||
|
||||
// ===== Main Tick =====
|
||||
@@ -331,4 +412,5 @@ void evse_manager_tick(void)
|
||||
|
||||
xSemaphoreGive(evse_mutex);
|
||||
}
|
||||
|
||||
// === Fim de: components/evse/evse_manager.c ===
|
||||
|
||||
@@ -48,7 +48,7 @@ void evse_meter_on_meter_event(void *arg, void *event_data)
|
||||
meter_data.energy_wh = (uint32_t)(evt->total_energy);
|
||||
xSemaphoreGive(meter_mutex);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
ESP_LOGD(TAG,
|
||||
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
|
||||
"voltage[V]={%.2f,%.2f,%.2f}, "
|
||||
"current[A]={%.2f,%.2f,%.2f}, "
|
||||
|
||||
@@ -23,24 +23,20 @@
|
||||
#define MAX_SAMPLE_ATTEMPTS 1000
|
||||
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
|
||||
|
||||
// ADC121S021 setup
|
||||
#define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware!
|
||||
#define ADC121_MAX 4095 // 12 bits
|
||||
#define ADC121_VREF_MV 3300
|
||||
#define ADC121_MAX 4095
|
||||
|
||||
static const char *TAG = "evse_pilot";
|
||||
|
||||
// Memoização de estado para evitar comandos/logs desnecessários
|
||||
static int last_pilot_level = -1;
|
||||
static uint32_t last_pwm_duty = 0;
|
||||
|
||||
// Função para converter leitura bruta do ADC para mV
|
||||
static int adc_raw_to_mv(uint16_t raw) {
|
||||
return (raw * ADC121_VREF_MV) / ADC121_MAX;
|
||||
}
|
||||
|
||||
void pilot_init(void)
|
||||
{
|
||||
// PWM (LEDC) configuração
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.speed_mode = PILOT_PWM_SPEED_MODE,
|
||||
.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_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
|
||||
//ESP_ERROR_CHECK(ledc_fade_func_install(0));
|
||||
|
||||
// Inicializa ADC121S021 externo
|
||||
adc121s021_dma_init();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ESP_LOGI(TAG, "Set level %d", level);
|
||||
ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0);
|
||||
last_pwm_duty = 0; // PWM parado
|
||||
last_pwm_duty = 0;
|
||||
}
|
||||
|
||||
void pilot_set_amps(uint16_t amps)
|
||||
@@ -85,13 +79,11 @@ void pilot_set_amps(uint16_t amps)
|
||||
}
|
||||
|
||||
uint32_t duty_percent;
|
||||
|
||||
if (amps <= 51) {
|
||||
duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6
|
||||
duty_percent = (amps * 10) / 6;
|
||||
} else {
|
||||
duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64
|
||||
duty_percent = (amps * 10) / 25 + 64;
|
||||
}
|
||||
|
||||
if (duty_percent > 100) 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;
|
||||
|
||||
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_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL);
|
||||
}
|
||||
|
||||
bool pilot_get_state(void) {
|
||||
// true se estamos em 12V fixo; false se PWM ou -12V
|
||||
// Se quiser diferenciar PWM, guarde um flag quando chamar set_amps.
|
||||
return (last_pilot_level == 1) && (last_pwm_duty == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int compare_int(const void *a, const void *b) {
|
||||
return (*(int *)a - *(int *)b);
|
||||
}
|
||||
|
||||
static int select_low_median_qsort(int *src, int n, int percent) {
|
||||
int k = (n * percent) / 100;
|
||||
if (k == 0) k = 1;
|
||||
int *copy = alloca(n * sizeof(int));
|
||||
memcpy(copy, src, n * sizeof(int));
|
||||
qsort(copy, n, sizeof(int), compare_int);
|
||||
return copy[k / 2];
|
||||
}
|
||||
|
||||
static int select_high_median_qsort(int *src, int n, int percent) {
|
||||
int k = (n * percent) / 100;
|
||||
if (k == 0) k = 1;
|
||||
int *copy = alloca(n * sizeof(int));
|
||||
memcpy(copy, src, n * sizeof(int));
|
||||
qsort(copy, n, sizeof(int), compare_int);
|
||||
return copy[n - k + (k / 2)];
|
||||
return (*(const int *)a - *(const int *)b);
|
||||
}
|
||||
|
||||
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;
|
||||
uint16_t adc_sample = 0;
|
||||
|
||||
// Lê samples usando ADC121S021 externo
|
||||
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) {
|
||||
adc_sample = 0;
|
||||
if (adc121s021_dma_get_sample(&adc_sample)) {
|
||||
@@ -164,13 +133,21 @@ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
|
||||
return;
|
||||
}
|
||||
|
||||
int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
|
||||
int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
|
||||
qsort(samples, collected, sizeof(int), compare_int);
|
||||
|
||||
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 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)
|
||||
*up_voltage = PILOT_VOLTAGE_12;
|
||||
else if (high_mv >= board_config.pilot_down_threshold_9)
|
||||
|
||||
@@ -1,82 +1,164 @@
|
||||
/*
|
||||
* evse_session.c
|
||||
* Implementation of evse_session module using instantaneous power accumulation
|
||||
*/
|
||||
#include <inttypes.h> // for PRIu32
|
||||
#include <inttypes.h>
|
||||
#include "evse_session.h"
|
||||
#include "evse_meter.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "evse_events.h"
|
||||
#include "esp_event.h"
|
||||
#include "evse_limits.h"
|
||||
|
||||
static const char *TAG = "evse_session";
|
||||
|
||||
// Internal state
|
||||
static TickType_t session_start_tick = 0;
|
||||
static uint32_t watt_seconds = 0; // accumulator of W·s
|
||||
static uint32_t watt_seconds = 0;
|
||||
static evse_session_t last_session;
|
||||
static bool last_session_valid = false;
|
||||
static bool last_session_valid = false;
|
||||
static uint32_t session_counter = 0;
|
||||
|
||||
void evse_session_init(void) {
|
||||
session_start_tick = 0;
|
||||
watt_seconds = 0;
|
||||
last_session_valid = false;
|
||||
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void evse_session_init(void)
|
||||
{
|
||||
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) {
|
||||
session_start_tick = xTaskGetTickCount();
|
||||
watt_seconds = 0;
|
||||
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick);
|
||||
void evse_session_start(void)
|
||||
{
|
||||
TickType_t tick = xTaskGetTickCount();
|
||||
|
||||
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) {
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
ESP_LOGW(TAG, "evse_session_end called without active session");
|
||||
return;
|
||||
}
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ;
|
||||
uint32_t energy_wh = watt_seconds / 3600U;
|
||||
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0;
|
||||
|
||||
last_session.start_tick = session_start_tick;
|
||||
last_session.duration_s = duration_s;
|
||||
last_session.energy_wh = energy_wh;
|
||||
last_session.avg_power_w = avg_power;
|
||||
last_session.is_current = false;
|
||||
last_session_valid = true;
|
||||
|
||||
start_tick = session_start_tick;
|
||||
ws = watt_seconds;
|
||||
id = session_counter;
|
||||
session_start_tick = 0;
|
||||
ESP_LOGI(TAG, "Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg_power=%" PRIu32 " W",
|
||||
(uint32_t)duration_s, (uint32_t)energy_wh, (uint32_t)avg_power);
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
|
||||
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) {
|
||||
if (session_start_tick == 0) return;
|
||||
// Should be called every second (or known interval)
|
||||
void evse_session_tick(void)
|
||||
{
|
||||
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) {
|
||||
if (out == NULL) return false;
|
||||
bool evse_session_get(evse_session_t *out)
|
||||
{
|
||||
if (out == NULL)
|
||||
return false;
|
||||
|
||||
if (session_start_tick != 0) {
|
||||
TickType_t 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();
|
||||
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ;
|
||||
uint32_t energy_wh = watt_seconds / 3600U;
|
||||
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0;
|
||||
uint32_t duration_s = (now - start) / configTICK_RATE_HZ;
|
||||
uint32_t energy_wh = ws / 3600U;
|
||||
uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0;
|
||||
|
||||
out->start_tick = session_start_tick;
|
||||
out->duration_s = duration_s;
|
||||
out->energy_wh = energy_wh;
|
||||
out->avg_power_w = avg_power;
|
||||
out->is_current = true;
|
||||
out->start_tick = start;
|
||||
out->duration_s = duration_s;
|
||||
out->energy_wh = energy_wh;
|
||||
out->avg_power_w = avg_power;
|
||||
out->is_current = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (last_session_valid) {
|
||||
*out = last_session;
|
||||
if (last_valid)
|
||||
{
|
||||
*out = last_copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,24 @@ void evse_state_set_authorized(bool authorized);
|
||||
*/
|
||||
bool evse_state_get_authorized(void);
|
||||
|
||||
// ===============================
|
||||
// Configuration / Availability
|
||||
// ===============================
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the EVSE (software flag, persisted in NVS).
|
||||
*/
|
||||
void evse_set_enabled(bool value);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the EVSE is currently available for use.
|
||||
*/
|
||||
bool evse_is_available(void);
|
||||
|
||||
/**
|
||||
* @brief Set EVSE availability flag (may be persisted in NVS).
|
||||
*/
|
||||
void evse_set_available(bool value);
|
||||
|
||||
// ===============================
|
||||
// Limit Status
|
||||
@@ -85,4 +103,4 @@ bool evse_is_limit_reached(void);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // EVSE_API_H
|
||||
#endif // EVSE_API_H
|
||||
@@ -1,3 +1,4 @@
|
||||
// === Início de: components/evse/include/evse_error.h ===
|
||||
#ifndef EVSE_ERROR_H
|
||||
#define EVSE_ERROR_H
|
||||
|
||||
@@ -5,41 +6,46 @@
|
||||
#include <stdbool.h>
|
||||
#include "evse_pilot.h"
|
||||
|
||||
|
||||
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
|
||||
EVSE_ERR_DIODE_SHORT_BIT | \
|
||||
// Bits que auto-limpam passado um timeout
|
||||
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
|
||||
EVSE_ERR_DIODE_SHORT_BIT | \
|
||||
EVSE_ERR_TEMPERATURE_HIGH_BIT | \
|
||||
EVSE_ERR_RCM_TRIGGERED_BIT )
|
||||
EVSE_ERR_RCM_TRIGGERED_BIT)
|
||||
|
||||
// Error bits
|
||||
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
|
||||
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
|
||||
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
|
||||
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
|
||||
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
|
||||
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
|
||||
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
|
||||
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
|
||||
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
|
||||
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
|
||||
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
|
||||
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
|
||||
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
|
||||
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
|
||||
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
|
||||
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
|
||||
|
||||
// Inicialização do módulo de erros
|
||||
void evse_error_init(void);
|
||||
|
||||
// Verificações e monitoramento
|
||||
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
|
||||
|
||||
void evse_temperature_check(void);
|
||||
|
||||
void evse_error_tick(void);
|
||||
|
||||
// Leitura e controle de erros
|
||||
uint32_t evse_get_error(void);
|
||||
bool evse_is_error_cleared(void);
|
||||
void evse_mark_error_cleared(void);
|
||||
void evse_error_set(uint32_t bitmask);
|
||||
void evse_error_clear(uint32_t bitmask);
|
||||
|
||||
bool evse_error_is_active(void);
|
||||
uint32_t evse_error_get_bits(void);
|
||||
void evse_error_reset_flag(void);
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Semântica sticky: flag "todos erros limpos"
|
||||
// ----------------------------------------------------
|
||||
// Fica true quando 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);
|
||||
void evse_error_reset_flag(void);
|
||||
|
||||
#endif // EVSE_ERROR_H
|
||||
// === Fim de: components/evse/include/evse_error.h ===
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#define EVSE_EVENTS_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_event.h"
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
|
||||
@@ -12,8 +15,12 @@ typedef enum {
|
||||
EVSE_EVENT_CONFIG_UPDATED,
|
||||
EVSE_EVENT_ENABLE_UPDATED,
|
||||
EVSE_EVENT_AVAILABLE_UPDATED,
|
||||
EVSE_EVENT_SESSION,
|
||||
} evse_event_id_t;
|
||||
|
||||
// -----------------
|
||||
// Eventos de STATE
|
||||
// -----------------
|
||||
typedef enum {
|
||||
EVSE_STATE_EVENT_IDLE,
|
||||
EVSE_STATE_EVENT_WAITING,
|
||||
@@ -25,11 +32,35 @@ typedef struct {
|
||||
evse_state_event_t state;
|
||||
} evse_state_event_data_t;
|
||||
|
||||
// -----------------
|
||||
// Eventos de SESSÃO
|
||||
// -----------------
|
||||
typedef enum {
|
||||
EVSE_SESSION_EVENT_STARTED = 0,
|
||||
EVSE_SESSION_EVENT_FINISHED,
|
||||
} evse_session_event_type_t;
|
||||
|
||||
typedef struct {
|
||||
bool charging; // Estado de carregamento
|
||||
float hw_max_current; // Corrente máxima suportada pelo hardware
|
||||
float runtime_current; // Corrente de carregamento em uso
|
||||
int64_t timestamp_us; // Momento da atualização
|
||||
evse_session_event_type_t type; ///< STARTED / FINISHED
|
||||
|
||||
// campos básicos da sessão, em tipos simples:
|
||||
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;
|
||||
|
||||
// Eventos simples e específicos
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// === Início de: components/evse/include/evse_limits.h ===
|
||||
#ifndef EVSE_LIMITS_H
|
||||
#define EVSE_LIMITS_H
|
||||
|
||||
@@ -15,7 +16,6 @@ extern "C" {
|
||||
|
||||
/**
|
||||
* @brief Sets the internal 'limit reached' flag.
|
||||
* Called internally when a limit condition is triggered.
|
||||
*/
|
||||
void evse_set_limit_reached(bool value);
|
||||
|
||||
@@ -24,6 +24,11 @@ void evse_set_limit_reached(bool value);
|
||||
*/
|
||||
bool evse_get_limit_reached(void);
|
||||
|
||||
/**
|
||||
* @brief Convenience alias for evse_get_limit_reached().
|
||||
*/
|
||||
bool evse_is_limit_reached(void);
|
||||
|
||||
/**
|
||||
* @brief Checks if any session limit has been exceeded (energy, time or power).
|
||||
* Should be called periodically during charging.
|
||||
@@ -48,30 +53,13 @@ void evse_set_charging_time_limit(uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Get/set minimum acceptable power level (in Watts).
|
||||
* If the power remains below this for a long time, the session may be interrupted.
|
||||
*/
|
||||
uint16_t evse_get_under_power_limit(void);
|
||||
void evse_set_under_power_limit(uint16_t value);
|
||||
|
||||
// ============================
|
||||
// Default (Persistent) Limits
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* @brief Default values used after system boot or reset.
|
||||
* These can be restored from NVS or fallback values.
|
||||
*/
|
||||
uint32_t evse_get_default_consumption_limit(void);
|
||||
void evse_set_default_consumption_limit(uint32_t value);
|
||||
|
||||
uint32_t evse_get_default_charging_time_limit(void);
|
||||
void evse_set_default_charging_time_limit(uint32_t value);
|
||||
|
||||
uint16_t evse_get_default_under_power_limit(void);
|
||||
void evse_set_default_under_power_limit(uint16_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // EVSE_LIMITS_H
|
||||
// === Fim de: components/evse/include/evse_limits.h ===
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
// === Início de: components/evse_link/include/evse_link_events.h ===
|
||||
#ifndef EVSE_LINK_EVENTS_H_
|
||||
#define EVSE_LINK_EVENTS_H_
|
||||
|
||||
#include "esp_event.h"
|
||||
|
||||
// Base de eventos do EVSE-Link
|
||||
ESP_EVENT_DECLARE_BASE(EVSE_LINK_EVENTS);
|
||||
|
||||
// Tamanho máximo de tag propagada via EVSE-Link (inclui NUL)
|
||||
#define EVSE_LINK_TAG_MAX_LEN 32
|
||||
|
||||
// IDs de eventos EVSE-Link
|
||||
typedef enum {
|
||||
LINK_EVENT_FRAME_RECEIVED, // qualquer frame válido
|
||||
LINK_EVENT_SLAVE_ONLINE, // heartbeat recebido primeira vez
|
||||
LINK_EVENT_SLAVE_OFFLINE, // sem heartbeat no timeout
|
||||
LINK_EVENT_MASTER_POLL_SENT, // opcional: poll enviado pelo master
|
||||
LINK_EVENT_CURRENT_LIMIT_APPLIED,
|
||||
LINK_EVENT_SLAVE_CONFIG_UPDATED // <- NOVO evento
|
||||
LINK_EVENT_SLAVE_CONFIG_UPDATED, // config atualizada pelo master
|
||||
LINK_EVENT_REMOTE_AUTH_GRANTED // autorização remota (master -> slave)
|
||||
} evse_link_event_t;
|
||||
|
||||
// Payload para LINK_EVENT_REMOTE_AUTH_GRANTED
|
||||
typedef struct {
|
||||
char tag[EVSE_LINK_TAG_MAX_LEN]; // idTag enviada pelo master
|
||||
} evse_link_auth_grant_event_t;
|
||||
|
||||
#endif // EVSE_LINK_EVENTS_H_
|
||||
|
||||
// === Fim de: components/evse_link/include/evse_link_events.h ===
|
||||
|
||||
@@ -5,14 +5,20 @@
|
||||
#include <stdbool.h>
|
||||
#include "driver/uart.h"
|
||||
|
||||
// UART configuration
|
||||
#define UART_PORT UART_NUM_2
|
||||
// UART instance and configuration
|
||||
#define UART_PORT UART_NUM_2 // Usa a UART2
|
||||
#define UART_BAUDRATE 115200
|
||||
#define UART_RX_BUF_SIZE 256
|
||||
|
||||
// GPIO pin assignments for UART
|
||||
#define TX_PIN 21 // GPIO21 -> RX on other board
|
||||
#define RX_PIN 22 // GPIO22 -> TX on other board
|
||||
// GPIO pin assignments for UART (ajuste conforme o hardware)
|
||||
#define UART_TXD 17 // TX -> DI do MAX3485
|
||||
#define UART_RXD 16 // RX -> RO do MAX3485
|
||||
#define UART_RTS 2 // RTS -> DE+RE do MAX3485
|
||||
|
||||
// Conveniência: nomes usados no .c
|
||||
#define TX_PIN UART_TXD
|
||||
#define RX_PIN UART_RXD
|
||||
#define RTS_PIN UART_RTS
|
||||
|
||||
// Frame delimiters
|
||||
#define MAGIC_START 0x7E
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// components/evse_link_framing/src/evse_link_framing.c
|
||||
|
||||
#include "evse_link_framing.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/semphr.h"
|
||||
@@ -8,75 +6,124 @@
|
||||
|
||||
static const char *TAG = "evse_framing";
|
||||
|
||||
static SemaphoreHandle_t tx_mutex;
|
||||
static SemaphoreHandle_t tx_mutex = NULL;
|
||||
static uint8_t seq = 0;
|
||||
static evse_link_frame_cb_t rx_cb = NULL;
|
||||
|
||||
// CRC-8 (polynomial 0x07)
|
||||
static uint8_t crc8(const uint8_t *data, uint8_t len) {
|
||||
static uint8_t crc8(const uint8_t *data, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
for (uint8_t i = 0; i < len; ++i) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t b = 0; b < 8; ++b) {
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
|
||||
if (crc & 0x80) {
|
||||
crc = (uint8_t)((crc << 1) ^ 0x07);
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void evse_link_framing_init(void) {
|
||||
// Create mutex for TX
|
||||
void evse_link_framing_init(void)
|
||||
{
|
||||
// Mutex para proteger TX (framings de várias tasks)
|
||||
tx_mutex = xSemaphoreCreateMutex();
|
||||
// Install UART driver
|
||||
uart_driver_install(UART_PORT, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0);
|
||||
uart_param_config(UART_PORT, &(uart_config_t){
|
||||
|
||||
// Instala driver UART
|
||||
uart_driver_install(UART_PORT,
|
||||
UART_RX_BUF_SIZE * 2, // RX buffer
|
||||
0, // TX buffer (0 = usa buffer interno)
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
|
||||
uart_config_t cfg = {
|
||||
.baud_rate = UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
|
||||
});
|
||||
uart_set_pin(UART_PORT, TX_PIN, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
uart_param_config(UART_PORT, &cfg);
|
||||
|
||||
// Define pinos: TX, RX e RTS (RTS ligado a DE+RE do transceiver RS485)
|
||||
uart_set_pin(UART_PORT,
|
||||
TX_PIN, // MB_UART_TXD (ex: GPIO17)
|
||||
RX_PIN, // MB_UART_RXD (ex: GPIO16)
|
||||
RTS_PIN, // MB_UART_RTS (ex: GPIO2, DE+RE)
|
||||
UART_PIN_NO_CHANGE);
|
||||
|
||||
// Modo RS485 half-duplex: driver controla RTS/DE/RE automaticamente
|
||||
uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
|
||||
|
||||
ESP_LOGI(TAG, "Framing init: UART%d TX=%d RX=%d RTS(DE/RE)=%d baud=%d",
|
||||
UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE);
|
||||
}
|
||||
|
||||
bool evse_link_framing_send(uint8_t dest, uint8_t src,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
if (len > EVSE_LINK_MAX_PAYLOAD) return false;
|
||||
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) return false;
|
||||
const uint8_t *payload, uint8_t len)
|
||||
{
|
||||
if (len > EVSE_LINK_MAX_PAYLOAD) {
|
||||
ESP_LOGW(TAG, "Payload too large: %u (max=%u)",
|
||||
len, EVSE_LINK_MAX_PAYLOAD);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to take TX mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END
|
||||
uint8_t frame[EVSE_LINK_MAX_PAYLOAD + 7];
|
||||
int idx = 0;
|
||||
|
||||
frame[idx++] = MAGIC_START;
|
||||
frame[idx++] = dest;
|
||||
frame[idx++] = src;
|
||||
frame[idx++] = len + 1; // +1 for SEQ
|
||||
frame[idx++] = (uint8_t)(len + 1); // LEN = SEQ + payload
|
||||
frame[idx++] = seq;
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
|
||||
// CRC covers DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
|
||||
if (len > 0 && payload != NULL) {
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
}
|
||||
|
||||
// CRC cobre: DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
|
||||
memcpy(crc_input, &frame[1], 3 + 1 + len);
|
||||
frame[idx++] = crc8(crc_input, 3 + 1 + len);
|
||||
uint8_t crc = crc8(crc_input, (uint8_t)(3 + 1 + len));
|
||||
frame[idx++] = crc;
|
||||
|
||||
frame[idx++] = MAGIC_END;
|
||||
|
||||
uart_write_bytes(UART_PORT, (const char *)frame, idx);
|
||||
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(10));
|
||||
// Envia frame completo
|
||||
int written = uart_write_bytes(UART_PORT, (const char *)frame, idx);
|
||||
if (written != idx) {
|
||||
ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx);
|
||||
}
|
||||
|
||||
// Aguarda TX terminar (o driver controla DE/RE via RTS)
|
||||
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(20));
|
||||
|
||||
xSemaphoreGive(tx_mutex);
|
||||
|
||||
ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
|
||||
ESP_LOGI(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
|
||||
dest, src, len, seq);
|
||||
|
||||
seq++; // increment sequence after sending
|
||||
seq++; // incrementa sequência após envio
|
||||
return true;
|
||||
}
|
||||
|
||||
void evse_link_framing_recv_byte(uint8_t b) {
|
||||
// State machine for frame parsing
|
||||
void evse_link_framing_recv_byte(uint8_t b)
|
||||
{
|
||||
// Máquina de estados para parsing do frame
|
||||
static enum {
|
||||
ST_WAIT_START,
|
||||
ST_WAIT_START = 0,
|
||||
ST_WAIT_DEST,
|
||||
ST_WAIT_SRC,
|
||||
ST_WAIT_LEN,
|
||||
@@ -88,7 +135,7 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
|
||||
static uint8_t rx_dest;
|
||||
static uint8_t rx_src;
|
||||
static uint8_t rx_len;
|
||||
static uint8_t rx_len; // inclui SEQ + payload
|
||||
static uint8_t rx_seq;
|
||||
static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD];
|
||||
static uint8_t rx_pos;
|
||||
@@ -112,19 +159,25 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
break;
|
||||
|
||||
case ST_WAIT_LEN:
|
||||
rx_len = b; // includes SEQ + payload
|
||||
rx_len = b; // LEN = SEQ + payload
|
||||
rx_pos = 0;
|
||||
rx_state = ST_WAIT_SEQ;
|
||||
break;
|
||||
|
||||
case ST_WAIT_SEQ:
|
||||
rx_seq = b;
|
||||
rx_state = (rx_len > 1) ? ST_READING : ST_WAIT_CRC;
|
||||
if (rx_len > 1) {
|
||||
rx_state = ST_READING;
|
||||
} else {
|
||||
rx_state = ST_WAIT_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ST_READING:
|
||||
rx_buf[rx_pos++] = b;
|
||||
if (rx_pos >= (rx_len - 1)) { // all payload bytes read
|
||||
if (rx_pos < EVSE_LINK_MAX_PAYLOAD) {
|
||||
rx_buf[rx_pos++] = b;
|
||||
}
|
||||
if (rx_pos >= (uint8_t)(rx_len - 1)) { // payload completo
|
||||
rx_state = ST_WAIT_CRC;
|
||||
}
|
||||
break;
|
||||
@@ -136,33 +189,43 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
|
||||
case ST_WAIT_END:
|
||||
if (b == MAGIC_END) {
|
||||
// Build data for CRC calculation
|
||||
// Monta buffer para verificar CRC:
|
||||
// DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
uint8_t temp[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
|
||||
int temp_len = 0;
|
||||
temp[temp_len++] = rx_dest;
|
||||
temp[temp_len++] = rx_src;
|
||||
temp[temp_len++] = rx_len;
|
||||
temp[temp_len++] = rx_seq;
|
||||
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
|
||||
temp_len += rx_len - 1;
|
||||
if (rx_len > 1) {
|
||||
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
|
||||
temp_len += rx_len - 1;
|
||||
}
|
||||
|
||||
uint8_t expected = crc8(temp, temp_len);
|
||||
uint8_t expected = crc8(temp, (uint8_t)temp_len);
|
||||
if (expected == rx_crc) {
|
||||
uint8_t payload_len = (uint8_t)(rx_len - 1); // exclui SEQ
|
||||
if (rx_cb) {
|
||||
rx_cb(rx_src, rx_dest, rx_buf, rx_len - 1);
|
||||
rx_cb(rx_src, rx_dest, rx_buf, payload_len);
|
||||
}
|
||||
ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u",
|
||||
rx_src, rx_dest, rx_len - 1, rx_seq);
|
||||
rx_src, rx_dest, payload_len, rx_seq);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
|
||||
expected, rx_crc);
|
||||
}
|
||||
}
|
||||
// Em qualquer caso, volta a esperar novo frame
|
||||
rx_state = ST_WAIT_START;
|
||||
break;
|
||||
|
||||
default:
|
||||
rx_state = ST_WAIT_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb) {
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb)
|
||||
{
|
||||
rx_cb = cb;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// === components/evse_link/src/evse_link_master.c ===
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "evse_link_events.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
@@ -8,94 +7,142 @@
|
||||
#include "esp_event.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "loadbalancer_events.h"
|
||||
#include "auth_events.h"
|
||||
|
||||
static const char *TAG = "evse_link_master";
|
||||
|
||||
// Link commands
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização a slave
|
||||
|
||||
// payload lengths (exclui byte de opcode)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ]
|
||||
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ]
|
||||
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1
|
||||
|
||||
// polling / heartbeat timers interval
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
TimerHandle_t timer;
|
||||
TickType_t interval;
|
||||
TickType_t interval;
|
||||
} timer_def_t;
|
||||
static timer_def_t poll_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
static timer_def_t hb_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
static timer_def_t poll_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
|
||||
static timer_def_t hb_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(30000)};
|
||||
|
||||
// --- Send new limit to slave ---
|
||||
static void on_new_limit(void* arg, esp_event_base_t base, int32_t id, void* data) {
|
||||
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT) return;
|
||||
static void on_new_limit(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT)
|
||||
return;
|
||||
const loadbalancer_slave_limit_event_t *evt = data;
|
||||
uint8_t slave_id = evt->slave_id;
|
||||
uint8_t slave_id = evt->slave_id;
|
||||
uint16_t max_current = evt->max_current;
|
||||
|
||||
uint8_t buf[LEN_SET_CURRENT] = {
|
||||
CMD_SET_CURRENT,
|
||||
(uint8_t)(max_current & 0xFF),
|
||||
(uint8_t)(max_current >> 8)
|
||||
};
|
||||
(uint8_t)(max_current >> 8)};
|
||||
evse_link_send(slave_id, buf, sizeof(buf));
|
||||
ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, max_current);
|
||||
}
|
||||
|
||||
// --- Bridge AUTH -> EVSE-Link: enviar AUTH_GRANTED para slaves ---
|
||||
static void on_auth_result(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (base != AUTH_EVENTS || id != AUTH_EVENT_TAG_PROCESSED || data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data;
|
||||
|
||||
if (!ev->authorized) {
|
||||
ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag);
|
||||
return;
|
||||
}
|
||||
|
||||
// Construir payload: [ CMD_AUTH_GRANTED, tag..., '\0' ]
|
||||
uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN];
|
||||
buf[0] = CMD_AUTH_GRANTED;
|
||||
|
||||
// Copiar tag e garantir NUL
|
||||
strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1);
|
||||
((char *)&buf[1])[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
|
||||
|
||||
uint8_t payload_len = 1 + (uint8_t)(strlen((char *)&buf[1]) + 1); // opcode + tag + '\0'
|
||||
|
||||
// Neste exemplo: broadcast para todos os slaves (0xFF)
|
||||
uint8_t dest = 0xFF;
|
||||
|
||||
if (!evse_link_send(dest, buf, payload_len)) {
|
||||
ESP_LOGW(TAG, "Failed to send CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
|
||||
dest, (char *)&buf[1]);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Sent CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
|
||||
dest, (char *)&buf[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Polling broadcast callback ---
|
||||
static void poll_timer_cb(TimerHandle_t xTimer) {
|
||||
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");;
|
||||
static void poll_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");
|
||||
;
|
||||
// Optionally post event LINK_EVENT_MASTER_POLL_SENT
|
||||
}
|
||||
|
||||
// --- Heartbeat timeout callback ---
|
||||
static void hb_timer_cb(TimerHandle_t xTimer) {
|
||||
static void hb_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGW(TAG, "Heartbeat timeout: possible slave offline");
|
||||
// post event LINK_EVENT_SLAVE_OFFLINE ???
|
||||
}
|
||||
|
||||
static void on_frame_master(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
if (len < 1) return;
|
||||
const uint8_t *payload, uint8_t len)
|
||||
{
|
||||
if (len < 1)
|
||||
return;
|
||||
uint8_t cmd = payload[0];
|
||||
|
||||
switch (cmd) {
|
||||
case CMD_HEARTBEAT: {
|
||||
if (len != 6) { // CMD + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_HEARTBEAT:
|
||||
{
|
||||
if (len != LEN_HEARTBEAT)
|
||||
{ // CMD + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi
|
||||
ESP_LOGW(TAG, "HEARTBEAT len invalid from 0x%02X: %u bytes", src, len);
|
||||
return;
|
||||
}
|
||||
bool charging = payload[1] != 0;
|
||||
uint16_t hw_max = payload[2] | (payload[3] << 8);
|
||||
uint16_t runtime = payload[4] | (payload[5] << 8);
|
||||
bool charging = payload[1] != 0;
|
||||
uint16_t hw_max = payload[2] | (payload[3] << 8);
|
||||
uint16_t runtime = payload[4] | (payload[5] << 8);
|
||||
|
||||
ESP_LOGI(TAG, "Heartbeat from 0x%02X: charging=%d hw_max=%uA runtime=%uA",
|
||||
src, charging, hw_max, runtime);
|
||||
|
||||
loadbalancer_slave_status_event_t status = {
|
||||
.slave_id = src,
|
||||
.charging = charging,
|
||||
.slave_id = src,
|
||||
.charging = charging,
|
||||
.hw_max_current = (float)hw_max,
|
||||
.runtime_current = (float)runtime, // corrente real medida no slave
|
||||
.timestamp_us = esp_timer_get_time()
|
||||
};
|
||||
|
||||
.runtime_current = (float)runtime, // corrente real medida no slave
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_STATUS,
|
||||
&status, sizeof(status), portMAX_DELAY);
|
||||
|
||||
// Enviar ACK de volta
|
||||
uint8_t ack[] = { CMD_HEARTBEAT_ACK };
|
||||
uint8_t ack[] = {CMD_HEARTBEAT_ACK};
|
||||
evse_link_send(src, ack, sizeof(ack));
|
||||
ESP_LOGD(TAG, "Sent HEARTBEAT_ACK to 0x%02X", src);
|
||||
break;
|
||||
@@ -115,10 +162,11 @@ static void on_frame_master(uint8_t src, uint8_t dest,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Master initialization ---
|
||||
void evse_link_master_init(void) {
|
||||
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled()) {
|
||||
void evse_link_master_init(void)
|
||||
{
|
||||
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Initializing MASTER (ID=0x%02X)", evse_link_get_self_id());
|
||||
@@ -132,9 +180,15 @@ void evse_link_master_init(void) {
|
||||
LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
on_new_limit,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
NULL));
|
||||
|
||||
// escutar resultado do AUTH para propagar autorização aos slaves
|
||||
ESP_ERROR_CHECK(
|
||||
esp_event_handler_register(
|
||||
AUTH_EVENTS,
|
||||
AUTH_EVENT_TAG_PROCESSED,
|
||||
on_auth_result,
|
||||
NULL));
|
||||
|
||||
// create and start poll timer
|
||||
poll_timer.timer = xTimerCreate("poll_tmr",
|
||||
@@ -150,3 +204,4 @@ void evse_link_master_init(void) {
|
||||
hb_timer_cb);
|
||||
xTimerStart(hb_timer.timer, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ static const char *TAG = "evse_link_slave";
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização
|
||||
|
||||
// payload lengths (exclui seq byte)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
@@ -82,6 +83,11 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
|
||||
break;
|
||||
|
||||
case CMD_SET_CURRENT: {
|
||||
if (len < LEN_SET_CURRENT) {
|
||||
ESP_LOGW(TAG, "SET_CURRENT from 0x%02X with invalid length %u", src, len);
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t amps = payload[1] | (payload[2] << 8);
|
||||
evse_set_runtime_charging_current(amps);
|
||||
ESP_LOGI(TAG, "Applied runtime limit: %uA from master 0x%02X", amps, src);
|
||||
@@ -103,6 +109,33 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_AUTH_GRANTED: {
|
||||
if (len < 2) {
|
||||
ESP_LOGW(TAG, "CMD_AUTH_GRANTED from 0x%02X with invalid length %u", src, len);
|
||||
break;
|
||||
}
|
||||
|
||||
const char *tag = (const char *)&payload[1];
|
||||
|
||||
evse_link_auth_grant_event_t ev = {0};
|
||||
strncpy(ev.tag, tag, EVSE_LINK_TAG_MAX_LEN - 1);
|
||||
ev.tag[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
|
||||
|
||||
ESP_LOGI(TAG, "Received CMD_AUTH_GRANTED from master 0x%02X, tag='%s'", src, ev.tag);
|
||||
|
||||
esp_err_t err = esp_event_post(
|
||||
EVSE_LINK_EVENTS,
|
||||
LINK_EVENT_REMOTE_AUTH_GRANTED,
|
||||
&ev,
|
||||
sizeof(ev),
|
||||
portMAX_DELAY);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to post LINK_EVENT_REMOTE_AUTH_GRANTED: %s", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src);
|
||||
}
|
||||
@@ -162,3 +195,5 @@ void evse_link_slave_init(void) {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// === Fim de: components/evse_link/src/evse_link_slave.c ===
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "button/button.c" "button/button_obj.cpp"
|
||||
INCLUDE_DIRS "button/include"
|
||||
REQUIRES "driver")
|
||||
@@ -1,6 +0,0 @@
|
||||
menu "GPIO Button"
|
||||
config IO_GLITCH_FILTER_TIME_MS
|
||||
int "IO glitch filter timer ms (10~100)"
|
||||
range 10 100
|
||||
default 50
|
||||
endmenu
|
||||
@@ -1,353 +0,0 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <iot_button.h>
|
||||
|
||||
#define IOT_CHECK(tag, a, ret) if(!(a)) { \
|
||||
ESP_LOGE(tag,"%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \
|
||||
return (ret); \
|
||||
}
|
||||
#define ERR_ASSERT(tag, param) IOT_CHECK(tag, (param) == ESP_OK, ESP_FAIL)
|
||||
#define POINT_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) != NULL, (ret))
|
||||
|
||||
typedef enum {
|
||||
BUTTON_STATE_IDLE = 0,
|
||||
BUTTON_STATE_PUSH,
|
||||
BUTTON_STATE_PRESSED,
|
||||
} button_status_t;
|
||||
|
||||
typedef struct button_dev button_dev_t;
|
||||
typedef struct btn_cb button_cb_t;
|
||||
|
||||
struct btn_cb{
|
||||
TickType_t interval;
|
||||
button_cb cb;
|
||||
void* arg;
|
||||
uint8_t on_press;
|
||||
TimerHandle_t tmr;
|
||||
button_dev_t *pbtn;
|
||||
button_cb_t *next_cb;
|
||||
};
|
||||
|
||||
struct button_dev{
|
||||
uint8_t io_num;
|
||||
uint8_t active_level;
|
||||
uint32_t serial_thres_sec;
|
||||
uint8_t taskq_on;
|
||||
QueueHandle_t taskq;
|
||||
QueueHandle_t argq;
|
||||
button_status_t state;
|
||||
button_cb_t tap_short_cb;
|
||||
button_cb_t tap_psh_cb;
|
||||
button_cb_t tap_rls_cb;
|
||||
button_cb_t press_serial_cb;
|
||||
button_cb_t* cb_head;
|
||||
};
|
||||
|
||||
#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_IO_GLITCH_FILTER_TIME_MS
|
||||
static const char* TAG = "button";
|
||||
|
||||
static void button_press_cb(TimerHandle_t tmr)
|
||||
{
|
||||
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
|
||||
button_dev_t* btn = btn_cb->pbtn;
|
||||
// low, then restart
|
||||
if (btn->active_level == gpio_get_level(btn->io_num)) {
|
||||
btn->state = BUTTON_STATE_PRESSED;
|
||||
if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && !btn_cb->on_press) {
|
||||
void *tmp = btn_cb->cb;
|
||||
xQueueOverwrite(btn->taskq, &tmp);
|
||||
xQueueOverwrite(btn->argq, &btn_cb->arg);
|
||||
} else if (btn_cb->cb) {
|
||||
btn_cb->cb(btn_cb->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void button_tap_psh_cb(TimerHandle_t tmr)
|
||||
{
|
||||
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
|
||||
button_dev_t* btn = btn_cb->pbtn;
|
||||
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
|
||||
int lv = gpio_get_level(btn->io_num);
|
||||
|
||||
if (btn->active_level == lv) {
|
||||
// True implies key is pressed
|
||||
btn->state = BUTTON_STATE_PUSH;
|
||||
if (btn->press_serial_cb.tmr) {
|
||||
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
|
||||
xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
|
||||
}
|
||||
if (btn->tap_psh_cb.cb) {
|
||||
btn->tap_psh_cb.cb(btn->tap_psh_cb.arg);
|
||||
}
|
||||
} else {
|
||||
// 50ms, check if this is a real key up
|
||||
if (btn->tap_rls_cb.tmr) {
|
||||
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
|
||||
xTimerReset(btn->tap_rls_cb.tmr, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void button_tap_rls_cb(TimerHandle_t tmr)
|
||||
{
|
||||
button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
|
||||
button_dev_t* btn = btn_cb->pbtn;
|
||||
xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
|
||||
if (btn->active_level == gpio_get_level(btn->io_num)) {
|
||||
|
||||
} else {
|
||||
// high, then key is up
|
||||
button_cb_t *pcb = btn->cb_head;
|
||||
while (pcb != NULL) {
|
||||
if (pcb->tmr != NULL) {
|
||||
xTimerStop(pcb->tmr, portMAX_DELAY);
|
||||
}
|
||||
pcb = pcb->next_cb;
|
||||
}
|
||||
if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && uxQueueMessagesWaiting(btn->taskq) != 0 && btn->state != BUTTON_STATE_IDLE) {
|
||||
void (*task)(void*);
|
||||
void *arg;
|
||||
xQueueReceive(btn->taskq, &task, 0);
|
||||
xQueueReceive(btn->argq, &arg, 0);
|
||||
task(arg);
|
||||
}
|
||||
if (btn->press_serial_cb.tmr && btn->press_serial_cb.tmr != NULL) {
|
||||
xTimerStop(btn->press_serial_cb.tmr, portMAX_DELAY);
|
||||
}
|
||||
if (btn->tap_short_cb.cb && btn->state == BUTTON_STATE_PUSH) {
|
||||
btn->tap_short_cb.cb(btn->tap_short_cb.arg);
|
||||
}
|
||||
if(btn->tap_rls_cb.cb && btn->state != BUTTON_STATE_IDLE) {
|
||||
btn->tap_rls_cb.cb(btn->tap_rls_cb.arg);
|
||||
}
|
||||
btn->state = BUTTON_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
static void button_press_serial_cb(TimerHandle_t tmr)
|
||||
{
|
||||
button_dev_t* btn = (button_dev_t*) pvTimerGetTimerID(tmr);
|
||||
if (btn->press_serial_cb.cb) {
|
||||
btn->press_serial_cb.cb(btn->press_serial_cb.arg);
|
||||
}
|
||||
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->press_serial_cb.interval, portMAX_DELAY);
|
||||
xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void button_gpio_isr_handler(void* arg)
|
||||
{
|
||||
button_dev_t* btn = (button_dev_t*) arg;
|
||||
portBASE_TYPE HPTaskAwoken = pdFALSE;
|
||||
int level = gpio_get_level(btn->io_num);
|
||||
if (level == btn->active_level) {
|
||||
if (btn->tap_psh_cb.tmr) {
|
||||
xTimerStopFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
|
||||
xTimerResetFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
|
||||
}
|
||||
|
||||
button_cb_t *pcb = btn->cb_head;
|
||||
while (pcb != NULL) {
|
||||
if (pcb->tmr != NULL) {
|
||||
xTimerStopFromISR(pcb->tmr, &HPTaskAwoken);
|
||||
xTimerResetFromISR(pcb->tmr, &HPTaskAwoken);
|
||||
}
|
||||
pcb = pcb->next_cb;
|
||||
}
|
||||
} else {
|
||||
// 50ms, check if this is a real key up
|
||||
if (btn->tap_rls_cb.tmr) {
|
||||
xTimerStopFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
|
||||
xTimerResetFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
|
||||
}
|
||||
}
|
||||
if(HPTaskAwoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
static void button_free_tmr(TimerHandle_t* tmr)
|
||||
{
|
||||
if (tmr && *tmr) {
|
||||
xTimerStop(*tmr, portMAX_DELAY);
|
||||
xTimerDelete(*tmr, portMAX_DELAY);
|
||||
*tmr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t iot_button_delete(button_handle_t btn_handle)
|
||||
{
|
||||
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
gpio_set_intr_type(btn->io_num, GPIO_INTR_DISABLE);
|
||||
gpio_isr_handler_remove(btn->io_num);
|
||||
|
||||
button_free_tmr(&btn->tap_rls_cb.tmr);
|
||||
button_free_tmr(&btn->tap_psh_cb.tmr);
|
||||
button_free_tmr(&btn->tap_short_cb.tmr);
|
||||
button_free_tmr(&btn->press_serial_cb.tmr);
|
||||
|
||||
button_cb_t *pcb = btn->cb_head;
|
||||
while (pcb != NULL) {
|
||||
button_cb_t *cb_next = pcb->next_cb;
|
||||
button_free_tmr(&pcb->tmr);
|
||||
free(pcb);
|
||||
pcb = cb_next;
|
||||
}
|
||||
free(btn);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level)
|
||||
{
|
||||
IOT_CHECK(TAG, gpio_num < GPIO_NUM_MAX, NULL);
|
||||
button_dev_t* btn = (button_dev_t*) calloc(1, sizeof(button_dev_t));
|
||||
POINT_ASSERT(TAG, btn, NULL);
|
||||
btn->active_level = active_level;
|
||||
btn->io_num = gpio_num;
|
||||
btn->state = BUTTON_STATE_IDLE;
|
||||
btn->taskq_on = 0;
|
||||
btn->taskq = xQueueCreate(1, sizeof(void*));
|
||||
btn->argq = xQueueCreate(1, sizeof(void *));
|
||||
btn->tap_rls_cb.arg = NULL;
|
||||
btn->tap_rls_cb.cb = NULL;
|
||||
btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
|
||||
btn->tap_rls_cb.pbtn = btn;
|
||||
btn->tap_rls_cb.tmr = xTimerCreate("btn_rls_tmr", btn->tap_rls_cb.interval, pdFALSE,
|
||||
&btn->tap_rls_cb, button_tap_rls_cb);
|
||||
btn->tap_psh_cb.arg = NULL;
|
||||
btn->tap_psh_cb.cb = NULL;
|
||||
btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
|
||||
btn->tap_psh_cb.pbtn = btn;
|
||||
btn->tap_psh_cb.tmr = xTimerCreate("btn_psh_tmr", btn->tap_psh_cb.interval, pdFALSE,
|
||||
&btn->tap_psh_cb, button_tap_psh_cb);
|
||||
gpio_install_isr_service(0);
|
||||
gpio_config_t gpio_conf;
|
||||
gpio_conf.intr_type = GPIO_INTR_ANYEDGE;
|
||||
gpio_conf.mode = GPIO_MODE_INPUT;
|
||||
gpio_conf.pin_bit_mask = (uint64_t)1 << gpio_num;
|
||||
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_conf);
|
||||
gpio_isr_handler_add(gpio_num, button_gpio_isr_handler, btn);
|
||||
return (button_handle_t) btn;
|
||||
}
|
||||
|
||||
esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type)
|
||||
{
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
button_cb_t* btn_cb = NULL;
|
||||
if (type == BUTTON_CB_PUSH) {
|
||||
btn_cb = &btn->tap_psh_cb;
|
||||
} else if (type == BUTTON_CB_RELEASE) {
|
||||
btn_cb = &btn->tap_rls_cb;
|
||||
} else if (type == BUTTON_CB_TAP) {
|
||||
btn_cb = &btn->tap_short_cb;
|
||||
} else if (type == BUTTON_CB_SERIAL) {
|
||||
btn_cb = &btn->press_serial_cb;
|
||||
}
|
||||
btn_cb->cb = NULL;
|
||||
btn_cb->arg = NULL;
|
||||
btn_cb->pbtn = btn;
|
||||
button_free_tmr(&btn_cb->tmr);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg)
|
||||
{
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
btn->serial_thres_sec = start_after_sec;
|
||||
if (btn->press_serial_cb.tmr == NULL) {
|
||||
btn->press_serial_cb.tmr = xTimerCreate("btn_serial_tmr", btn->serial_thres_sec*1000 / portTICK_PERIOD_MS,
|
||||
pdFALSE, btn, button_press_serial_cb);
|
||||
}
|
||||
btn->press_serial_cb.arg = arg;
|
||||
btn->press_serial_cb.cb = cb;
|
||||
btn->press_serial_cb.interval = interval_tick;
|
||||
btn->press_serial_cb.pbtn = btn;
|
||||
xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg)
|
||||
{
|
||||
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
if (type == BUTTON_CB_PUSH) {
|
||||
btn->tap_psh_cb.arg = arg;
|
||||
btn->tap_psh_cb.cb = cb;
|
||||
btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
|
||||
btn->tap_psh_cb.pbtn = btn;
|
||||
xTimerChangePeriod(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
|
||||
} else if (type == BUTTON_CB_RELEASE) {
|
||||
btn->tap_rls_cb.arg = arg;
|
||||
btn->tap_rls_cb.cb = cb;
|
||||
btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
|
||||
btn->tap_rls_cb.pbtn = btn;
|
||||
xTimerChangePeriod(btn->tap_rls_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
|
||||
} else if (type == BUTTON_CB_TAP) {
|
||||
btn->tap_short_cb.arg = arg;
|
||||
btn->tap_short_cb.cb = cb;
|
||||
btn->tap_short_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
|
||||
btn->tap_short_cb.pbtn = btn;
|
||||
} else if (type == BUTTON_CB_SERIAL) {
|
||||
iot_button_set_serial_cb(btn_handle, 1, 1000 / portTICK_PERIOD_MS, cb, arg);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
|
||||
{
|
||||
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
|
||||
IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
|
||||
POINT_ASSERT(TAG, cb_new, ESP_FAIL);
|
||||
cb_new->on_press = 1;
|
||||
cb_new->arg = arg;
|
||||
cb_new->cb = cb;
|
||||
cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
|
||||
cb_new->pbtn = btn;
|
||||
cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
|
||||
cb_new->next_cb = btn->cb_head;
|
||||
btn->cb_head = cb_new;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
|
||||
{
|
||||
POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
|
||||
IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
|
||||
button_dev_t* btn = (button_dev_t*) btn_handle;
|
||||
button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
|
||||
POINT_ASSERT(TAG, cb_new, ESP_FAIL);
|
||||
btn->taskq_on = 1;
|
||||
cb_new->arg = arg;
|
||||
cb_new->cb = cb;
|
||||
cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
|
||||
cb_new->pbtn = btn;
|
||||
cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
|
||||
cb_new->next_cb = btn->cb_head;
|
||||
btn->cb_head = cb_new;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_system.h>
|
||||
#include <iot_button.h>
|
||||
|
||||
CButton::CButton(gpio_num_t gpio_num, button_active_t active_level)
|
||||
{
|
||||
m_btn_handle = iot_button_create(gpio_num, active_level);
|
||||
}
|
||||
|
||||
CButton::~CButton()
|
||||
{
|
||||
iot_button_delete(m_btn_handle);
|
||||
m_btn_handle = NULL;
|
||||
}
|
||||
|
||||
esp_err_t CButton::set_evt_cb(button_cb_type_t type, button_cb cb, void* arg)
|
||||
{
|
||||
return iot_button_set_evt_cb(m_btn_handle, type, cb, arg);
|
||||
}
|
||||
|
||||
esp_err_t CButton::set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec)
|
||||
{
|
||||
return iot_button_set_serial_cb(m_btn_handle, start_after_sec, interval_tick, cb, arg);
|
||||
}
|
||||
|
||||
esp_err_t CButton::add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg)
|
||||
{
|
||||
return iot_button_add_on_press_cb(m_btn_handle, press_sec, cb, arg);
|
||||
}
|
||||
|
||||
esp_err_t CButton::add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg)
|
||||
{
|
||||
return iot_button_add_on_release_cb(m_btn_handle, press_sec, cb, arg);
|
||||
}
|
||||
|
||||
esp_err_t CButton::rm_cb(button_cb_type_t type)
|
||||
{
|
||||
return iot_button_rm_cb(m_btn_handle, type);
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#ifndef _IOT_BUTTON_H_
|
||||
#define _IOT_BUTTON_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/portmacro.h>
|
||||
typedef void (* button_cb)(void*);
|
||||
typedef void* button_handle_t;
|
||||
|
||||
typedef enum {
|
||||
BUTTON_ACTIVE_HIGH = 1, /*!<button active level: high level*/
|
||||
BUTTON_ACTIVE_LOW = 0, /*!<button active level: low level*/
|
||||
} button_active_t;
|
||||
|
||||
typedef enum {
|
||||
BUTTON_CB_PUSH = 0, /*!<button push callback event */
|
||||
BUTTON_CB_RELEASE, /*!<button release callback event */
|
||||
BUTTON_CB_TAP, /*!<button quick tap callback event(will not trigger if there already is a "PRESS" event) */
|
||||
BUTTON_CB_SERIAL, /*!<button serial trigger callback event */
|
||||
} button_cb_type_t;
|
||||
|
||||
/**
|
||||
* @brief Init button functions
|
||||
*
|
||||
* @param gpio_num GPIO index of the pin that the button uses
|
||||
* @param active_level button hardware active level.
|
||||
* For "BUTTON_ACTIVE_LOW" it means when the button pressed, the GPIO will read low level.
|
||||
*
|
||||
* @return A button_handle_t handle to the created button object, or NULL in case of error.
|
||||
*/
|
||||
button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level);
|
||||
|
||||
/**
|
||||
* @brief Register a callback function for a serial trigger event.
|
||||
*
|
||||
* @param btn_handle handle of the button object
|
||||
* @param start_after_sec define the time after which to start serial trigger action
|
||||
* @param interval_tick serial trigger interval
|
||||
* @param cb callback function for "TAP" action.
|
||||
* @param arg Parameter for callback function
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Register a callback function for a button_cb_type_t action.
|
||||
*
|
||||
* @param btn_handle handle of the button object
|
||||
* @param type callback function type
|
||||
* @param cb callback function for "TAP" action.
|
||||
* @param arg Parameter for callback function
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Callbacks invoked as timer events occur while button is pressed.
|
||||
* Example: If a button is configured for 2 sec, 5 sec and 7 sec callbacks and if the button is pressed for 6 sec then 2 sec callback would be invoked at 2 sec event and 5 sec callback would be invoked at 5 sec event
|
||||
*
|
||||
* @param btn_handle handle of the button object
|
||||
* @param press_sec the callback function would be called if you press the button for a specified period of time
|
||||
* @param cb callback function for "PRESS and HOLD" action.
|
||||
* @param arg Parameter for callback function
|
||||
*
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Single callback invoked according to the latest timer event on button release.
|
||||
* Example: If a button is configured for 2 sec, 5 sec and 7 sec callbacks and if the button is released at 6 sec then only 5 sec callback would be invoked
|
||||
*
|
||||
* @param btn_handle handle of the button object
|
||||
* @param press_sec the callback function would be called if you press the button for a specified period of time
|
||||
* @param cb callback function for "PRESS and RELEASE" action.
|
||||
* @param arg Parameter for callback function
|
||||
*
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Delete button object and free memory
|
||||
* @param btn_handle handle of the button object
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t iot_button_delete(button_handle_t btn_handle);
|
||||
|
||||
/**
|
||||
* @brief Remove callback
|
||||
*
|
||||
* @param btn_handle The handle of the button object
|
||||
* @param type callback function event type
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
*/
|
||||
esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
/**
|
||||
* class of button
|
||||
* simple usage:
|
||||
* CButton* btn = new CButton(BUTTON_IO_NUM, BUTTON_ACTIVE_LEVEL, BUTTON_SERIAL_TRIGGER, 3);
|
||||
* btn->add_cb(BUTTON_CB_PUSH, button_tap_cb, (void*) push, 50 / portTICK_PERIOD_MS);
|
||||
* btn->add_custom_cb(5, button_press_5s_cb, NULL);
|
||||
* ......
|
||||
* delete btn;
|
||||
*/
|
||||
class CButton
|
||||
{
|
||||
private:
|
||||
button_handle_t m_btn_handle;
|
||||
|
||||
/**
|
||||
* prevent copy constructing
|
||||
*/
|
||||
CButton(const CButton&);
|
||||
CButton& operator = (const CButton&);
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief constructor of CButton
|
||||
*
|
||||
* @param gpio_num GPIO index of the pin that the button uses
|
||||
* @param active_level button hardware active level.
|
||||
* For "BUTTON_ACTIVE_LOW" it means when the button pressed, the GPIO will read low level.
|
||||
*/
|
||||
CButton(gpio_num_t gpio_num, button_active_t active_level = BUTTON_ACTIVE_LOW);
|
||||
|
||||
~CButton();
|
||||
|
||||
/**
|
||||
* @brief Register a callback function for a button_cb_type_t action.
|
||||
*
|
||||
* @param type callback function type
|
||||
* @param cb callback function for "TAP" action.
|
||||
* @param arg Parameter for callback function
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t set_evt_cb(button_cb_type_t type, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Register a callback function for a serial trigger event.
|
||||
*
|
||||
* @param btn_handle handle of the button object
|
||||
* @param start_after_sec define the time after which to start serial trigger action
|
||||
* @param interval_tick serial trigger interval
|
||||
* @param cb callback function for "TAP" action.
|
||||
* @param arg Parameter for callback function
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec);
|
||||
|
||||
/**
|
||||
* @brief Callbacks invoked as timer events occur while button is pressed
|
||||
*
|
||||
* @param press_sec the callback function would be called if you press the button for a specified period of time
|
||||
* @param cb callback function for "PRESS and HOLD" action.
|
||||
* @param arg Parameter for callback function
|
||||
*
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Single callback invoked according to the latest timer event on button release.
|
||||
*
|
||||
* @param press_sec the callback function would be called if you press the button for a specified period of time
|
||||
* @param cb callback function for "PRESS and RELEASE" action.
|
||||
* @param arg Parameter for callback function
|
||||
*
|
||||
* @note
|
||||
* Button callback functions execute in the context of the timer service task.
|
||||
* It is therefore essential that button callback functions never attempt to block.
|
||||
* For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(),
|
||||
* or specify a non zero block time when accessing a queue or a semaphore.
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Parameter error
|
||||
*/
|
||||
esp_err_t add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Remove callback
|
||||
*
|
||||
* @param type callback function event type
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
*/
|
||||
esp_err_t rm_cb(button_cb_type_t type);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := ./button/include
|
||||
COMPONENT_SRCDIRS := ./button
|
||||
6
components/led/CMakeLists.txt
Executable file
6
components/led/CMakeLists.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
set(srcs "src/led.c" "src/ledc_driver.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES driver esp_timer
|
||||
REQUIRES config evse)
|
||||
78
components/led/include/led.h
Executable file
78
components/led/include/led.h
Executable file
@@ -0,0 +1,78 @@
|
||||
#ifndef LED_H_
|
||||
#define LED_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief Identificadores dos LEDs disponíveis no hardware (por cor)
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
LED_ID_GREEN = 0,
|
||||
LED_ID_BLUE,
|
||||
LED_ID_RED,
|
||||
LED_ID_MAX
|
||||
} led_id_t;
|
||||
|
||||
/**
|
||||
* @brief Padrões de comportamento possíveis para os LEDs.
|
||||
*
|
||||
* Estes padrões são usados tanto pela lógica interna (estado EVSE, efeitos
|
||||
* de sessão, etc.) como por código externo que queira forçar um determinado
|
||||
* comportamento num LED.
|
||||
*
|
||||
* Nota:
|
||||
* - LED_PATTERN_BREATHING é implementado por uma task interna (HSV).
|
||||
* - LED_PATTERN_CHARGING_EFFECT é legado/experimental e pode não ser usado
|
||||
* diretamente na implementação atual.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
LED_PATTERN_OFF, ///< LED sempre desligado
|
||||
LED_PATTERN_ON, ///< LED sempre ligado
|
||||
LED_PATTERN_BLINK, ///< Pisca com ciclo padrão (500ms on / 500ms off)
|
||||
LED_PATTERN_BLINK_FAST, ///< Pisca rápido (250ms on / 250ms off)
|
||||
LED_PATTERN_BLINK_SLOW, ///< Pisca lento (500ms on / 1500ms off)
|
||||
LED_PATTERN_CHARGING_EFFECT, ///< Efeito de carregamento (2s on / 1s off) - legado/opcional
|
||||
LED_PATTERN_BREATHING ///< Efeito "breathing" (brilho suave via HSV)
|
||||
} led_pattern_t;
|
||||
|
||||
/**
|
||||
* @brief Inicializa o subsistema de LEDs com base na configuração da placa.
|
||||
*
|
||||
* - Configura o driver LEDC com os GPIOs definidos em board_config.
|
||||
* - Cria a task de efeitos (para padrões como BREATHING).
|
||||
* - Regista handlers dos eventos EVSE (estado e sessão) para aplicar
|
||||
* padrões automáticos em função do estado do carregador.
|
||||
*
|
||||
* Deve ser chamada uma única vez na inicialização do sistema.
|
||||
*/
|
||||
void led_init(void);
|
||||
|
||||
/**
|
||||
* @brief Define diretamente o tempo ligado/desligado de um LED.
|
||||
*
|
||||
* Esta função permite criar padrões personalizados à parte da tabela
|
||||
* led_pattern_t. Normalmente, código de alto nível deve preferir
|
||||
* led_apply_pattern(), deixando a lógica de tempos a cargo do módulo.
|
||||
*
|
||||
* @param led_id Identificador do LED (ver led_id_t)
|
||||
* @param ontime Tempo ligado em milissegundos (0 = sempre off)
|
||||
* @param offtime Tempo desligado em milissegundos (0 = sempre on)
|
||||
*/
|
||||
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime);
|
||||
|
||||
/**
|
||||
* @brief Aplica um padrão de piscar/efeito definido ao LED.
|
||||
*
|
||||
* Esta é a API recomendada para controlar LEDs externamente. A implementação
|
||||
* interna do módulo também a usa para reagir a eventos EVSE (estado de
|
||||
* carregamento, início/fim de sessão, etc.).
|
||||
*
|
||||
* @param led_id Identificador do LED (ver led_id_t)
|
||||
* @param pattern Padrão desejado (ver led_pattern_t)
|
||||
*/
|
||||
void led_apply_pattern(led_id_t led_id, led_pattern_t pattern);
|
||||
|
||||
#endif /* LED_H_ */
|
||||
34
components/led/include/ledc_driver.h
Executable file
34
components/led/include/ledc_driver.h
Executable file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
/**
|
||||
* @brief Inicializa o controlador LEDC para os 3 LEDs (R,G,B)
|
||||
*
|
||||
* @param gpio_red GPIO ligado ao LED vermelho (via ULN2003)
|
||||
* @param gpio_green GPIO ligado ao LED verde (via ULN2003)
|
||||
* @param gpio_blue GPIO ligado ao LED azul (via ULN2003)
|
||||
*
|
||||
* @return ESP_OK em sucesso, erro caso contrário
|
||||
*/
|
||||
esp_err_t ledc_init(gpio_num_t gpio_red,
|
||||
gpio_num_t gpio_green,
|
||||
gpio_num_t gpio_blue);
|
||||
|
||||
/**
|
||||
* @brief Define a intensidade RGB (0–255 por cor)
|
||||
*
|
||||
* @param red Intensidade do vermelho (0–255)
|
||||
* @param green Intensidade do verde (0–255)
|
||||
* @param blue Intensidade do azul (0–255)
|
||||
*
|
||||
* @return ESP_OK em sucesso, erro caso contrário
|
||||
*/
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue);
|
||||
|
||||
/**
|
||||
* @brief Desliga todos os LEDs (R,G,B)
|
||||
*/
|
||||
esp_err_t ledc_clear(void);
|
||||
598
components/led/src/led.c
Executable file
598
components/led/src/led.c
Executable file
@@ -0,0 +1,598 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include <inttypes.h> // para PRIu32
|
||||
|
||||
#include "led.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_state.h"
|
||||
#include "ledc_driver.h"
|
||||
|
||||
#define BLOCK_TIME 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 0–100
|
||||
uint32_t v = 0;
|
||||
bool up = true;
|
||||
|
||||
const uint32_t v_min = 0;
|
||||
const uint32_t v_max = 100;
|
||||
const uint32_t step = 3;
|
||||
const TickType_t delay_breathe = pdMS_TO_TICKS(50); // velocidade da respiração
|
||||
const TickType_t delay_idle = pdMS_TO_TICKS(1000); // quando não há LED em BREATHING
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Verifica se algum LED está em BREATHING
|
||||
bool has_breath = false;
|
||||
led_id_t breath_id = LED_ID_MAX;
|
||||
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
if (leds[i].present && leds[i].pattern == LED_PATTERN_BREATHING)
|
||||
{
|
||||
has_breath = true;
|
||||
breath_id = (led_id_t)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_breath)
|
||||
{
|
||||
// Usa o valor atual de v para calcular o brilho (0–255)
|
||||
uint32_t brightness = (v * 255U) / 100U;
|
||||
uint32_t r = 0, g = 0, b = 0;
|
||||
|
||||
switch (breath_id)
|
||||
{
|
||||
case LED_ID_RED:
|
||||
r = brightness;
|
||||
break;
|
||||
case LED_ID_GREEN:
|
||||
g = brightness;
|
||||
break;
|
||||
case LED_ID_BLUE:
|
||||
b = brightness;
|
||||
break;
|
||||
default:
|
||||
// fallback: usa azul se algo estranho acontecer
|
||||
b = brightness;
|
||||
break;
|
||||
}
|
||||
|
||||
// Aplica só um canal, sem misturar cores
|
||||
ledc_set_rgb(r, g, b);
|
||||
|
||||
// Se estiver completamente apagado (v == 0),
|
||||
// queremos que fique 500 ms off antes de voltar a subir
|
||||
TickType_t delay = (v == v_min) ? pdMS_TO_TICKS(500) : delay_breathe;
|
||||
|
||||
// Atualiza v para a próxima iteração (triângulo 0→100→0)
|
||||
if (up)
|
||||
{
|
||||
if (v + step >= v_max)
|
||||
{
|
||||
v = v_max;
|
||||
up = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
v += step;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v <= v_min + step)
|
||||
{
|
||||
v = v_min;
|
||||
up = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
v -= step;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ninguém em BREATHING: deixa padrões normais controlarem o LED
|
||||
led_update_rgb_from_state();
|
||||
vTaskDelay(delay_idle);
|
||||
|
||||
// Opcional: quando sair de BREATHING e voltar mais tarde,
|
||||
// começa de novo a partir de apagado
|
||||
v = v_min;
|
||||
up = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Aplica padrão em função do estado EVSE (base)
|
||||
// GARANTINDO apenas 1 LED por estado
|
||||
// ----------------------------
|
||||
static void led_apply_state_mode(evse_state_event_t state)
|
||||
{
|
||||
// Desliga padrões anteriores para todos os canais
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
// IDLE → verde fixo (claro e visível)
|
||||
led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
// WAITING → azul a piscar lento
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_SLOW);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
// CHARGING → azul "breathing"
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BREATHING);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
// FAULT → vermelho a piscar rápido
|
||||
led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
led_update_rgb_from_state();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Timer callback do efeito de sessão
|
||||
// Efeitos usam sempre UM LED forte
|
||||
// ----------------------------
|
||||
static void session_effect_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
switch (session_effect_type)
|
||||
{
|
||||
case SESSION_EFFECT_START:
|
||||
session_effect_phase++;
|
||||
switch (session_effect_phase)
|
||||
{
|
||||
case 1:
|
||||
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
// Mantém piscar rápido mais um bocado
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(5000),
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
// Fim do efeito de START → volta ao estado base (tipicamente CHARGING)
|
||||
session_effect_active = false;
|
||||
session_effect_type = SESSION_EFFECT_NONE;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SESSION_EFFECT_FINISH:
|
||||
session_effect_phase++;
|
||||
switch (session_effect_phase)
|
||||
{
|
||||
case 1:
|
||||
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
// Mantém piscar rápido mais tempo (destaque de fim de sessão)
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(5000),
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
// Fim do efeito de FINISH → volta ao estado base (IDLE/WAITING)
|
||||
session_effect_active = false;
|
||||
session_effect_type = SESSION_EFFECT_NONE;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SESSION_EFFECT_NONE:
|
||||
default:
|
||||
session_effect_active = false;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Event Handler: EVSE State
|
||||
// ----------------------------
|
||||
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
|
||||
|
||||
ESP_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
129
components/led/src/ledc_driver.c
Executable file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* LEDC driver para 3 LEDs (R,G,B) controlados via ULN2003
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "ledc_driver.h"
|
||||
|
||||
// Pode ser futuramente ligado a uma opção de Kconfig.
|
||||
// Para o teu hardware (comum a 12 V via ULN2003), visto do ESP é ativo-alto.
|
||||
#define IS_ACTIVE_HIGH 1
|
||||
|
||||
#define LEDC_LS_TIMER LEDC_TIMER_2
|
||||
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
|
||||
|
||||
// Canais usados: 2, 3, 4
|
||||
#define LEDC_CH_RED LEDC_CHANNEL_2
|
||||
#define LEDC_CH_GREEN LEDC_CHANNEL_3
|
||||
#define LEDC_CH_BLUE LEDC_CHANNEL_4
|
||||
|
||||
#define LEDC_NUM_CHANNELS (3)
|
||||
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
|
||||
#define LEDC_DUTY_MAX (8192 - 1)
|
||||
#define LEDC_FREQUENCY (5000) // 5 kHz
|
||||
|
||||
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
|
||||
{
|
||||
.channel = LEDC_CH_RED,
|
||||
.duty = 0,
|
||||
.gpio_num = -1, // preenchido em runtime
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
{
|
||||
.channel = LEDC_CH_GREEN,
|
||||
.duty = 0,
|
||||
.gpio_num = -1,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
{
|
||||
.channel = LEDC_CH_BLUE,
|
||||
.duty = 0,
|
||||
.gpio_num = -1,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
};
|
||||
|
||||
esp_err_t ledc_init(gpio_num_t gpio_red,
|
||||
gpio_num_t gpio_green,
|
||||
gpio_num_t gpio_blue)
|
||||
{
|
||||
// Configuração do timer
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.duty_resolution = LEDC_DUTY_RES,
|
||||
.freq_hz = LEDC_FREQUENCY,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.timer_num = LEDC_LS_TIMER,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
|
||||
esp_err_t err = ledc_timer_config(&ledc_timer);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// Atribuir GPIOs aos canais
|
||||
ledc_channel[0].gpio_num = gpio_red;
|
||||
ledc_channel[1].gpio_num = gpio_green;
|
||||
ledc_channel[2].gpio_num = gpio_blue;
|
||||
|
||||
// Configurar canais
|
||||
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++)
|
||||
{
|
||||
err = ledc_channel_config(&ledc_channel[ch]);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
|
||||
{
|
||||
if (red > 255)
|
||||
red = 255;
|
||||
if (green > 255)
|
||||
green = 255;
|
||||
if (blue > 255)
|
||||
blue = 255;
|
||||
|
||||
red = red * LEDC_DUTY_MAX / 255;
|
||||
green = green * LEDC_DUTY_MAX / 255;
|
||||
blue = blue * LEDC_DUTY_MAX / 255;
|
||||
|
||||
if (!IS_ACTIVE_HIGH)
|
||||
{
|
||||
red = LEDC_DUTY_MAX - red;
|
||||
green = LEDC_DUTY_MAX - green;
|
||||
blue = LEDC_DUTY_MAX - blue;
|
||||
}
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_RED, red);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_RED);
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_GREEN, green);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_GREEN);
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_BLUE, blue);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_BLUE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ledc_clear(void)
|
||||
{
|
||||
return ledc_set_rgb(0, 0, 0);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
set(srcs "ledc_driver.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "driver")
|
||||
@@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
COMPONENT_SRCDIRS := .
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
#include "ledc_driver.h"
|
||||
|
||||
/**
|
||||
* @brief LEDC driver: Basic LEDC driver
|
||||
*/
|
||||
|
||||
#define IS_ACTIVE_HIGH 0
|
||||
#define LEDC_LS_TIMER LEDC_TIMER_0
|
||||
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
|
||||
|
||||
#define LEDC_LS_CH0_GPIO (0)
|
||||
#define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0
|
||||
#define LEDC_LS_CH1_GPIO (1)
|
||||
#define LEDC_LS_CH1_CHANNEL LEDC_CHANNEL_1
|
||||
#define LEDC_LS_CH2_GPIO (8)
|
||||
#define LEDC_LS_CH2_CHANNEL LEDC_CHANNEL_2
|
||||
|
||||
#define LEDC_NUM_CHANNELS (3)
|
||||
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Set duty resolution to 13 bits
|
||||
#define LEDC_DUTY_MAX (8192 - 1) // (2 ** 13) - 1
|
||||
#define LEDC_FREQUENCY (5000) // Frequency in Hertz. Set frequency at 5 kHz
|
||||
|
||||
/*
|
||||
* Prepare individual configuration
|
||||
* for each channel of LED Controller
|
||||
* by selecting:
|
||||
* - controller's channel number
|
||||
* - output duty cycle, set initially to 0
|
||||
* - GPIO number where LED is connected to
|
||||
* - speed mode, either high or low
|
||||
* - timer servicing selected channel
|
||||
* Note: if different channels use one timer,
|
||||
* then frequency and bit_num of these channels
|
||||
* will be the same
|
||||
*/
|
||||
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
|
||||
{
|
||||
.channel = LEDC_LS_CH0_CHANNEL,
|
||||
.duty = 0,
|
||||
.gpio_num = LEDC_LS_CH0_GPIO,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
{
|
||||
.channel = LEDC_LS_CH1_CHANNEL,
|
||||
.duty = 0,
|
||||
.gpio_num = LEDC_LS_CH1_GPIO,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER, },
|
||||
{
|
||||
.channel = LEDC_LS_CH2_CHANNEL,
|
||||
.duty = 0,
|
||||
.gpio_num = LEDC_LS_CH2_GPIO,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
};
|
||||
|
||||
esp_err_t ledc_init(void)
|
||||
{
|
||||
/*
|
||||
* Prepare and set configuration of timers
|
||||
* that will be used by LED Controller
|
||||
*/
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.duty_resolution = LEDC_DUTY_RES, // resolution of PWM duty
|
||||
.freq_hz = LEDC_FREQUENCY, // frequency of PWM signal
|
||||
.speed_mode = LEDC_LS_MODE, // timer mode
|
||||
.timer_num = LEDC_LS_TIMER, // timer index
|
||||
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
|
||||
};
|
||||
// Set configuration of timer0 for high speed channels
|
||||
ledc_timer_config(&ledc_timer);
|
||||
|
||||
// Set LED Controller with previously prepared configuration
|
||||
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++) {
|
||||
ledc_channel_config(&ledc_channel[ch]);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void ledc_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
|
||||
{
|
||||
h %= 360; // h -> [0,360]
|
||||
uint32_t rgb_max = v * 2.55f;
|
||||
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
|
||||
|
||||
uint32_t i = h / 60;
|
||||
uint32_t diff = h % 60;
|
||||
|
||||
// RGB adjustment amount by hue
|
||||
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
*r = rgb_max;
|
||||
*g = rgb_min + rgb_adj;
|
||||
*b = rgb_min;
|
||||
break;
|
||||
case 1:
|
||||
*r = rgb_max - rgb_adj;
|
||||
*g = rgb_max;
|
||||
*b = rgb_min;
|
||||
break;
|
||||
case 2:
|
||||
*r = rgb_min;
|
||||
*g = rgb_max;
|
||||
*b = rgb_min + rgb_adj;
|
||||
break;
|
||||
case 3:
|
||||
*r = rgb_min;
|
||||
*g = rgb_max - rgb_adj;
|
||||
*b = rgb_max;
|
||||
break;
|
||||
case 4:
|
||||
*r = rgb_min + rgb_adj;
|
||||
*g = rgb_min;
|
||||
*b = rgb_max;
|
||||
break;
|
||||
default:
|
||||
*r = rgb_max;
|
||||
*g = rgb_min;
|
||||
*b = rgb_max - rgb_adj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
|
||||
{
|
||||
red = red * LEDC_DUTY_MAX / 255;
|
||||
green = green * LEDC_DUTY_MAX / 255;
|
||||
blue = blue * LEDC_DUTY_MAX / 255;
|
||||
|
||||
if (!IS_ACTIVE_HIGH) {
|
||||
red = LEDC_DUTY_MAX - red;
|
||||
green = LEDC_DUTY_MAX - green;
|
||||
blue = LEDC_DUTY_MAX - blue;
|
||||
}
|
||||
|
||||
ledc_set_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel, red);
|
||||
ledc_update_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel);
|
||||
|
||||
ledc_set_duty(ledc_channel[1].speed_mode, ledc_channel[1].channel, green);
|
||||
ledc_update_duty(ledc_channel[1].speed_mode, ledc_channel[1].channel);
|
||||
|
||||
ledc_set_duty(ledc_channel[2].speed_mode, ledc_channel[2].channel, blue);
|
||||
ledc_update_duty(ledc_channel[2].speed_mode, ledc_channel[2].channel);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ledc_set_hsv(uint32_t hue, uint32_t saturation, uint32_t value)
|
||||
{
|
||||
uint32_t red = 0;
|
||||
uint32_t green = 0;
|
||||
uint32_t blue = 0;
|
||||
ledc_hsv2rgb(hue, saturation, value, &red, &green, &blue);
|
||||
return ledc_set_rgb(red, green, blue);
|
||||
}
|
||||
|
||||
esp_err_t ledc_clear()
|
||||
{
|
||||
return ledc_set_rgb(0, 0, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize the LEDC RGB LED
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* @return error in case of failure.
|
||||
*/
|
||||
esp_err_t ledc_init(void);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Set RGB value for the LED
|
||||
*
|
||||
* @param[in] red Intensity of Red color (0-100)
|
||||
* @param[in] green Intensity of Green color (0-100)
|
||||
* @param[in] blue Intensity of Green color (0-100)
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* @return error in case of failure.
|
||||
*/
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue);
|
||||
|
||||
/**
|
||||
* @brief Set HSV value for the LED
|
||||
*
|
||||
* @param[in] hue Value of hue in arc degrees (0-360)
|
||||
* @param[in] saturation Saturation in percentage (0-100)
|
||||
* @param[in] value Value (also called Intensity) in percentage (0-100)
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* @return error in case of failure.
|
||||
*/
|
||||
esp_err_t ledc_set_hsv(uint32_t hue, uint32_t saturation, uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Clear (turn off) the LED
|
||||
* @return ESP_OK on success.
|
||||
* @return error in case of failure.
|
||||
*/
|
||||
esp_err_t ledc_clear();
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char ssid[32];
|
||||
char ssid[33];
|
||||
int rssi;
|
||||
bool auth;
|
||||
} wifi_scan_ap_t;
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Config
|
||||
// -----------------------------------------------------------------------------
|
||||
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC)
|
||||
#define MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC)
|
||||
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC)
|
||||
#define MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC)
|
||||
|
||||
#define NVS_NAMESPACE "wifi"
|
||||
#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();
|
||||
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:
|
||||
{
|
||||
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;
|
||||
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
|
||||
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);
|
||||
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
|
||||
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)
|
||||
{
|
||||
@@ -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);
|
||||
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
|
||||
s_retry_count = 0;
|
||||
// se quiseres, podes também propagar um NETWORK_EVENT_STA_GOT_IP aqui
|
||||
}
|
||||
else if (event_id == IP_EVENT_STA_LOST_IP)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
// garante que scan_aps[i].ssid tenha pelo menos 33 bytes
|
||||
snprintf(scan_aps[i].ssid, SSID_BUF_SZ, "%s", (const char *)ap_info[i].ssid);
|
||||
snprintf(scan_aps[i].ssid, sizeof(scan_aps[i].ssid), "%s", (const char *)ap_info[i].ssid);
|
||||
scan_aps[i].rssi = ap_info[i].rssi;
|
||||
scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN;
|
||||
}
|
||||
@@ -464,4 +499,4 @@ bool wifi_is_ap(void)
|
||||
|
||||
EventBits_t bits = xEventGroupGetBits(wifi_event_group);
|
||||
return (bits & WIFI_AP_MODE_BIT) != 0;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@ set(srcs
|
||||
"src/adc.c"
|
||||
"src/adc121s021_dma.c"
|
||||
"src/peripherals.c"
|
||||
"src/led.c"
|
||||
"src/proximity.c"
|
||||
"src/ac_relay.c"
|
||||
"src/socket_lock.c"
|
||||
"src/rcm.c"
|
||||
"src/aux_io.c"
|
||||
"src/onewire.c"
|
||||
"src/ds18x20.c"
|
||||
"src/temp_sensor.c"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef AUX_IO_H_
|
||||
#define AUX_IO_H_
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Initialize aux
|
||||
*
|
||||
*/
|
||||
void aux_init(void);
|
||||
|
||||
/**
|
||||
* @brief Read digital input
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t aux_read(const char *name, bool *value);
|
||||
|
||||
/**
|
||||
* @brief Write digial output
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t aux_write(const char *name, bool value);
|
||||
|
||||
/**
|
||||
* @brief Read analog input
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t aux_analog_read(const char *name, int *value);
|
||||
|
||||
#endif /* AUX_IO_H_ */
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef LED_H_
|
||||
#define LED_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief Identificadores dos LEDs disponíveis no hardware
|
||||
*/
|
||||
typedef enum {
|
||||
LED_ID_STOP,
|
||||
LED_ID_CHARGING,
|
||||
LED_ID_ERROR,
|
||||
LED_ID_MAX
|
||||
} led_id_t;
|
||||
|
||||
/**
|
||||
* @brief Padrões de comportamento possíveis para os LEDs
|
||||
*/
|
||||
typedef enum {
|
||||
LED_PATTERN_OFF, ///< LED sempre desligado
|
||||
LED_PATTERN_ON, ///< LED sempre ligado
|
||||
LED_PATTERN_BLINK, ///< Pisca com ciclo padrão (500ms on / 500ms off)
|
||||
LED_PATTERN_BLINK_FAST, ///< Pisca rápido (200ms / 200ms)
|
||||
LED_PATTERN_BLINK_SLOW, ///< Pisca lento (300ms / 1700ms)
|
||||
LED_PATTERN_CHARGING_EFFECT ///< Efeito visual para carregamento (2s on / 1s off)
|
||||
} led_pattern_t;
|
||||
|
||||
/**
|
||||
* @brief Inicializa os LEDs com base na configuração da placa
|
||||
* Deve ser chamada uma única vez na inicialização do sistema.
|
||||
*/
|
||||
void led_init(void);
|
||||
|
||||
/**
|
||||
* @brief Define diretamente o tempo ligado/desligado de um LED.
|
||||
* Pode ser usado para padrões personalizados.
|
||||
*
|
||||
* @param led_id Identificador do LED (ver enum led_id_t)
|
||||
* @param ontime Tempo ligado em milissegundos
|
||||
* @param offtime Tempo desligado em milissegundos
|
||||
*/
|
||||
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime);
|
||||
|
||||
/**
|
||||
* @brief Aplica um dos padrões de piscar definidos ao LED
|
||||
*
|
||||
* @param led_id Identificador do LED (ver enum led_id_t)
|
||||
* @param pattern Padrão desejado (ver enum led_pattern_t)
|
||||
*/
|
||||
void led_apply_pattern(led_id_t led_id, led_pattern_t pattern);
|
||||
|
||||
#endif /* LED_H_ */
|
||||
@@ -1,174 +0,0 @@
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs.h"
|
||||
|
||||
#include "aux_io.h"
|
||||
#include "board_config.h"
|
||||
#include "adc.h"
|
||||
|
||||
#define MAX_AUX_IN 4
|
||||
#define MAX_AUX_OUT 4
|
||||
#define MAX_AUX_AIN 4
|
||||
|
||||
//static const char* TAG = "aux";
|
||||
|
||||
static int aux_in_count = 0;
|
||||
static int aux_out_count = 0;
|
||||
static int aux_ain_count = 0;
|
||||
|
||||
static struct aux_gpio_s
|
||||
{
|
||||
gpio_num_t gpio;
|
||||
const char* name;
|
||||
} aux_in[MAX_AUX_IN], aux_out[MAX_AUX_OUT];
|
||||
|
||||
static struct aux_adc_s
|
||||
{
|
||||
adc_channel_t adc;
|
||||
const char* name;
|
||||
} aux_ain[MAX_AUX_AIN];
|
||||
|
||||
|
||||
void aux_init(void)
|
||||
{
|
||||
// IN
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.pin_bit_mask = 0
|
||||
};
|
||||
|
||||
if (board_config.aux_in_1) {
|
||||
aux_in[aux_in_count].gpio = board_config.aux_in_1_gpio;
|
||||
aux_in[aux_in_count].name = board_config.aux_in_1_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_1_gpio);
|
||||
aux_in_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_in_2) {
|
||||
aux_in[aux_in_count].gpio = board_config.aux_in_2_gpio;
|
||||
aux_in[aux_in_count].name = board_config.aux_in_2_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_2_gpio);
|
||||
aux_in_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_in_3) {
|
||||
aux_in[aux_in_count].gpio = board_config.aux_in_3_gpio;
|
||||
aux_in[aux_in_count].name = board_config.aux_in_3_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_3_gpio);
|
||||
aux_in_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_in_4) {
|
||||
aux_in[aux_in_count].gpio = board_config.aux_in_4_gpio;
|
||||
aux_in[aux_in_count].name = board_config.aux_in_4_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_in_4_gpio);
|
||||
aux_in_count++;
|
||||
}
|
||||
|
||||
if (io_conf.pin_bit_mask > 0) {
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
}
|
||||
|
||||
// OUT
|
||||
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = 0;
|
||||
|
||||
if (board_config.aux_out_1) {
|
||||
aux_out[aux_out_count].gpio = board_config.aux_out_1_gpio;
|
||||
aux_out[aux_out_count].name = board_config.aux_out_1_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_1_gpio);
|
||||
aux_out_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_out_2) {
|
||||
aux_out[aux_out_count].gpio = board_config.aux_out_2_gpio;
|
||||
aux_out[aux_out_count].name = board_config.aux_out_2_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_2_gpio);
|
||||
aux_out_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_out_3) {
|
||||
aux_out[aux_out_count].gpio = board_config.aux_out_3_gpio;
|
||||
aux_out[aux_out_count].name = board_config.aux_out_3_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_3_gpio);
|
||||
aux_out_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_out_4) {
|
||||
aux_out[aux_out_count].gpio = board_config.aux_out_4_gpio;
|
||||
aux_out[aux_out_count].name = board_config.aux_out_4_name;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.aux_out_4_gpio);
|
||||
aux_out_count++;
|
||||
}
|
||||
|
||||
if (io_conf.pin_bit_mask > 0) {
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
}
|
||||
|
||||
// AIN
|
||||
|
||||
adc_oneshot_chan_cfg_t config = {
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.atten = ADC_ATTEN_DB_12
|
||||
};
|
||||
|
||||
if (board_config.aux_ain_1) {
|
||||
aux_ain[aux_ain_count].adc = board_config.aux_ain_1_adc_channel;
|
||||
aux_ain[aux_ain_count].name = board_config.aux_out_1_name;
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.aux_ain_1_adc_channel, &config));
|
||||
aux_ain_count++;
|
||||
}
|
||||
|
||||
if (board_config.aux_ain_2) {
|
||||
aux_ain[aux_ain_count].adc = board_config.aux_ain_2_adc_channel;
|
||||
aux_ain[aux_ain_count].name = board_config.aux_out_2_name;
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.aux_ain_2_adc_channel, &config));
|
||||
aux_ain_count++;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t aux_read(const char* name, bool* value)
|
||||
{
|
||||
for (int i = 0; i < aux_in_count; i++) {
|
||||
if (strcmp(aux_in[i].name, name) == 0) {
|
||||
*value = gpio_get_level(aux_in[i].gpio) == 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t aux_write(const char* name, bool value)
|
||||
{
|
||||
for (int i = 0; i < aux_out_count; i++) {
|
||||
if (strcmp(aux_out[i].name, name) == 0) {
|
||||
return gpio_set_level(aux_out[i].gpio, value);
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t aux_analog_read(const char* name, int* value)
|
||||
{
|
||||
for (int i = 0; i < aux_ain_count; i++) {
|
||||
if (strcmp(aux_ain[i].name, name) == 0) {
|
||||
int raw = 0;
|
||||
esp_err_t ret = adc_oneshot_read(adc_handle, aux_ain[i].adc, &raw);
|
||||
if (ret == ESP_OK) {
|
||||
return adc_cali_raw_to_voltage(adc_cali_handle, raw, value);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "led.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_state.h"
|
||||
|
||||
#define BLOCK_TIME pdMS_TO_TICKS(10)
|
||||
|
||||
static const char *TAG = "led";
|
||||
|
||||
typedef struct {
|
||||
gpio_num_t gpio;
|
||||
bool on : 1;
|
||||
uint16_t ontime;
|
||||
uint16_t offtime;
|
||||
TimerHandle_t timer;
|
||||
led_pattern_t pattern;
|
||||
uint8_t blink_count;
|
||||
} led_t;
|
||||
|
||||
static led_t leds[LED_ID_MAX] = {0};
|
||||
|
||||
// ----------------------------
|
||||
// Funções Internas
|
||||
// ----------------------------
|
||||
|
||||
static void led_timer_callback(TimerHandle_t xTimer)
|
||||
{
|
||||
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
|
||||
led->on = !led->on;
|
||||
gpio_set_level(led->gpio, led->on);
|
||||
uint32_t next_time = led->on ? led->ontime : led->offtime;
|
||||
|
||||
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Event Handler: EVSE State
|
||||
// ----------------------------
|
||||
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) {
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return;
|
||||
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
|
||||
|
||||
// Log do evento recebido
|
||||
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
|
||||
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF);
|
||||
|
||||
switch (evt->state) {
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_IDLE");
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_WAITING");
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_CHARGING");
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_FAULT");
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown state: %d", evt->state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Inicialização
|
||||
// ----------------------------
|
||||
|
||||
void led_init(void)
|
||||
{
|
||||
gpio_config_t io_conf = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
||||
.pin_bit_mask = 0
|
||||
};
|
||||
|
||||
for (int i = 0; i < LED_ID_MAX; i++) {
|
||||
leds[i].gpio = GPIO_NUM_NC;
|
||||
}
|
||||
|
||||
if (board_config.led_stop) {
|
||||
leds[LED_ID_STOP].gpio = board_config.led_stop_gpio;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.led_stop_gpio);
|
||||
}
|
||||
|
||||
if (board_config.led_charging) {
|
||||
leds[LED_ID_CHARGING].gpio = board_config.led_charging_gpio;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.led_charging_gpio);
|
||||
}
|
||||
|
||||
if (board_config.led_error) {
|
||||
leds[LED_ID_ERROR].gpio = board_config.led_error_gpio;
|
||||
io_conf.pin_bit_mask |= BIT64(board_config.led_error_gpio);
|
||||
}
|
||||
|
||||
if (io_conf.pin_bit_mask != 0) {
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
}
|
||||
|
||||
// Registra handler de evento EVSE
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
evse_led_event_handler,
|
||||
NULL));
|
||||
|
||||
ESP_LOGI(TAG, "LED system initialized");
|
||||
|
||||
// Aplica o estado atual do EVSE aos LEDs
|
||||
evse_state_event_data_t evt = {
|
||||
.state = EVSE_STATE_EVENT_IDLE
|
||||
};
|
||||
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// API Pública
|
||||
// ----------------------------
|
||||
|
||||
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
|
||||
{
|
||||
if (led_id >= LED_ID_MAX) return;
|
||||
|
||||
led_t *led = &leds[led_id];
|
||||
if (led->gpio == GPIO_NUM_NC) return;
|
||||
|
||||
if (led->ontime == ontime && led->offtime == offtime)
|
||||
return;
|
||||
|
||||
if (led->timer) {
|
||||
xTimerStop(led->timer, BLOCK_TIME);
|
||||
}
|
||||
|
||||
led->ontime = ontime;
|
||||
led->offtime = offtime;
|
||||
|
||||
if (ontime == 0) {
|
||||
led->on = false;
|
||||
gpio_set_level(led->gpio, 0);
|
||||
} else if (offtime == 0) {
|
||||
led->on = true;
|
||||
gpio_set_level(led->gpio, 1);
|
||||
} else {
|
||||
led->on = true;
|
||||
gpio_set_level(led->gpio, 1);
|
||||
|
||||
if (!led->timer) {
|
||||
led->timer = xTimerCreate("led_timer", pdMS_TO_TICKS(ontime),
|
||||
pdFALSE, (void *)led, led_timer_callback);
|
||||
}
|
||||
|
||||
if (led->timer) {
|
||||
xTimerStart(led->timer, BLOCK_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void led_apply_pattern(led_id_t id, led_pattern_t pattern)
|
||||
{
|
||||
if (id >= LED_ID_MAX) return;
|
||||
|
||||
led_t *led = &leds[id];
|
||||
if (led->gpio == GPIO_NUM_NC) return;
|
||||
|
||||
if (led->pattern == pattern) return;
|
||||
|
||||
if (led->timer) {
|
||||
xTimerStop(led->timer, BLOCK_TIME);
|
||||
}
|
||||
|
||||
led->pattern = pattern;
|
||||
led->blink_count = 0;
|
||||
|
||||
switch (pattern) {
|
||||
case LED_PATTERN_OFF:
|
||||
led_set_state(id, 0, 0);
|
||||
break;
|
||||
case LED_PATTERN_ON:
|
||||
led_set_state(id, 1, 0);
|
||||
break;
|
||||
case LED_PATTERN_BLINK:
|
||||
led_set_state(id, 500, 500);
|
||||
break;
|
||||
case LED_PATTERN_BLINK_FAST:
|
||||
led_set_state(id, 200, 200);
|
||||
break;
|
||||
case LED_PATTERN_BLINK_SLOW:
|
||||
led_set_state(id, 300, 1700);
|
||||
break;
|
||||
case LED_PATTERN_CHARGING_EFFECT:
|
||||
led_set_state(id, 2000, 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
#include "peripherals.h"
|
||||
#include "adc.h"
|
||||
#include "led.h"
|
||||
//#include "buzzer.h"
|
||||
//#include "led.h"
|
||||
// #include "buzzer.h"
|
||||
#include "proximity.h"
|
||||
#include "ac_relay.h"
|
||||
#include "socket_lock.h"
|
||||
#include "rcm.h"
|
||||
#include "aux_io.h"
|
||||
#include "ntc_sensor.h"
|
||||
|
||||
void peripherals_init(void)
|
||||
{
|
||||
ac_relay_init();
|
||||
led_init();
|
||||
//buzzer_init();
|
||||
// led_init();
|
||||
// buzzer_init();
|
||||
adc_init();
|
||||
proximity_init();
|
||||
// socket_lock_init();
|
||||
// rcm_init();
|
||||
//energy_meter_init();
|
||||
// energy_meter_init();
|
||||
// aux_init();
|
||||
ntc_sensor_init();
|
||||
}
|
||||
@@ -43,11 +43,11 @@ cJSON *json_get_evse_config(void)
|
||||
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
|
||||
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
|
||||
cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
|
||||
cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
|
||||
//cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
|
||||
cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
|
||||
cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
|
||||
//cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
|
||||
cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
|
||||
cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
|
||||
//cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
|
||||
|
||||
cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
|
||||
cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
|
||||
@@ -99,11 +99,11 @@ esp_err_t json_set_evse_config(cJSON *root)
|
||||
SET_BOOL("rcm", evse_set_rcm);
|
||||
SET_NUM("temperatureThreshold", evse_set_temp_threshold);
|
||||
SET_NUM("consumptionLimit", evse_set_consumption_limit);
|
||||
SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit);
|
||||
//SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit);
|
||||
SET_NUM("chargingTimeLimit", evse_set_charging_time_limit);
|
||||
SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit);
|
||||
//SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit);
|
||||
SET_NUM("underPowerLimit", evse_set_under_power_limit);
|
||||
SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit);
|
||||
//SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit);
|
||||
SET_NUM("socketLockOperatingTime", socket_lock_set_operating_time);
|
||||
SET_NUM("socketLockBreakTime", socket_lock_set_break_time);
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ static void client_task_func(void *param)
|
||||
}
|
||||
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));
|
||||
|
||||
@@ -8,6 +8,7 @@ set(srcs
|
||||
"src/loadbalancing_settings_api.c"
|
||||
"src/evse_link_config_api.c"
|
||||
"src/dashboard_api.c"
|
||||
"src/scheduler_settings_api.c"
|
||||
"src/static_file_api.c"
|
||||
)
|
||||
|
||||
@@ -15,7 +16,7 @@ idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
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)
|
||||
|
||||
23
components/rest_api/include/scheduler_settings_api.h
Executable file
23
components/rest_api/include/scheduler_settings_api.h
Executable 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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "ocpp_api.h"
|
||||
#include "auth_api.h"
|
||||
#include "dashboard_api.h"
|
||||
#include "scheduler_settings_api.h"
|
||||
#include "static_file_api.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_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
|
||||
register_link_config_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
|
||||
|
||||
|
||||
224
components/rest_api/src/scheduler_settings_api.c
Executable file
224
components/rest_api/src/scheduler_settings_api.c
Executable 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");
|
||||
}
|
||||
7
components/scheduler/CMakeLists.txt
Executable file
7
components/scheduler/CMakeLists.txt
Executable 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)
|
||||
2
components/scheduler/idf_component.yml
Executable file
2
components/scheduler/idf_component.yml
Executable file
@@ -0,0 +1,2 @@
|
||||
version: "2.6.0"
|
||||
description: Authentication component
|
||||
18
components/scheduler/include/scheduler.h
Executable file
18
components/scheduler/include/scheduler.h
Executable 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
|
||||
17
components/scheduler/include/scheduler_events.h
Normal file
17
components/scheduler/include/scheduler_events.h
Normal 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;
|
||||
31
components/scheduler/include/scheduler_types.h
Normal file
31
components/scheduler/include/scheduler_types.h
Normal 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
|
||||
251
components/scheduler/src/scheduler.c
Executable file
251
components/scheduler/src/scheduler.c
Executable 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:00–18:00
|
||||
return (now_min >= cfg->start_min && now_min < cfg->end_min);
|
||||
}
|
||||
else
|
||||
{
|
||||
// janela que passa pela meia-noite, ex: 22:00–06: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;
|
||||
}
|
||||
4
components/scheduler/src/scheduler_events.c
Normal file
4
components/scheduler/src/scheduler_events.c
Normal file
@@ -0,0 +1,4 @@
|
||||
// scheduler_events.c
|
||||
#include "scheduler_events.h"
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(SCHED_EVENTS);
|
||||
22
components/scheduler/src/scheduler_types.c
Executable file
22
components/scheduler/src/scheduler_types.c
Executable 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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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_ */
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}*/
|
||||
}
|
||||
@@ -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;
|
||||
}*/
|
||||
}
|
||||
@@ -56,13 +56,13 @@ dependencies:
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.4.2
|
||||
version: 5.4.3
|
||||
direct_dependencies:
|
||||
- espressif/cjson
|
||||
- espressif/esp-modbus
|
||||
- espressif/mdns
|
||||
- espressif/ntc_driver
|
||||
- idf
|
||||
manifest_hash: 4c69c7075a4a2aadc2a98b11d389519cb8621b7522e40f4fa9021acee9d2d03f
|
||||
manifest_hash: dd985fc132a07a7ca76fb5d4ccd1526be3bbb783f2a8ca2db065457e686212bd
|
||||
target: esp32
|
||||
version: 2.0.0
|
||||
|
||||
73
main/main.c
73
main/main.c
@@ -1,3 +1,4 @@
|
||||
// === Início de: main/main.c ===
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
@@ -30,8 +31,9 @@
|
||||
#include "buzzer.h"
|
||||
#include "evse_link.h"
|
||||
#include "ocpp.h"
|
||||
#include "led.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#define EVSE_MANAGER_TICK_PERIOD_MS 1000
|
||||
#define AP_CONNECTION_TIMEOUT 120000
|
||||
#define RESET_HOLD_TIME 30000
|
||||
#define DEBOUNCE_TIME_MS 50
|
||||
@@ -41,9 +43,9 @@
|
||||
|
||||
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 last_interrupt_tick = 0;
|
||||
static volatile TickType_t last_interrupt_tick = 0;
|
||||
static bool pressed = false;
|
||||
|
||||
//
|
||||
@@ -79,6 +81,7 @@ static void fs_init(void)
|
||||
fs_info(&cfg_conf);
|
||||
fs_info(&data_conf);
|
||||
}
|
||||
|
||||
//
|
||||
// Wi-Fi event monitoring task
|
||||
//
|
||||
@@ -87,17 +90,15 @@ static void wifi_event_task_func(void *param)
|
||||
EventBits_t mode_bits;
|
||||
for (;;)
|
||||
{
|
||||
// Wait indefinitely until either AP or STA mode is entered
|
||||
mode_bits = xEventGroupWaitBits(
|
||||
wifi_event_group,
|
||||
WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT,
|
||||
pdFALSE, // do not clear bits on exit
|
||||
pdFALSE, // wait for any bit
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
|
||||
if (mode_bits & WIFI_AP_MODE_BIT)
|
||||
{
|
||||
// We're in AP mode: wait for a client to connect within the timeout
|
||||
if (xEventGroupWaitBits(
|
||||
wifi_event_group,
|
||||
WIFI_AP_CONNECTED_BIT,
|
||||
@@ -106,7 +107,6 @@ static void wifi_event_task_func(void *param)
|
||||
pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) &
|
||||
WIFI_AP_CONNECTED_BIT)
|
||||
{
|
||||
// Once connected, block until the client disconnects
|
||||
xEventGroupWaitBits(
|
||||
wifi_event_group,
|
||||
WIFI_AP_DISCONNECTED_BIT,
|
||||
@@ -116,7 +116,6 @@ static void wifi_event_task_func(void *param)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timeout expired with no client—optionally stop the AP
|
||||
if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
|
||||
{
|
||||
// wifi_ap_stop();
|
||||
@@ -125,7 +124,6 @@ static void wifi_event_task_func(void *param)
|
||||
}
|
||||
else if (mode_bits & WIFI_STA_MODE_BIT)
|
||||
{
|
||||
// We're in STA mode: block until disconnected from the AP
|
||||
xEventGroupWaitBits(
|
||||
wifi_event_group,
|
||||
WIFI_STA_DISCONNECTED_BIT,
|
||||
@@ -133,9 +131,6 @@ static void wifi_event_task_func(void *param)
|
||||
pdFALSE,
|
||||
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)
|
||||
{
|
||||
// If not already in AP mode, start it
|
||||
if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT))
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting Wi-Fi AP mode");
|
||||
@@ -158,26 +152,26 @@ static void user_input_task_func(void *param)
|
||||
uint32_t notification;
|
||||
for (;;)
|
||||
{
|
||||
// Wait for notification bits from ISR
|
||||
if (xTaskNotifyWait(
|
||||
0, // do not clear any bits on entry
|
||||
UINT32_MAX, // clear all bits on exit
|
||||
0,
|
||||
UINT32_MAX,
|
||||
¬ification,
|
||||
portMAX_DELAY))
|
||||
{
|
||||
// Handle button press event
|
||||
if (notification & PRESS_BIT)
|
||||
{
|
||||
press_tick = xTaskGetTickCount();
|
||||
pressed = true;
|
||||
ESP_LOGI(TAG, "Button Pressed");
|
||||
handle_button_press(); // só aqui
|
||||
handle_button_press();
|
||||
}
|
||||
|
||||
if ((notification & RELEASED_BIT) && pressed)
|
||||
{
|
||||
pressed = false;
|
||||
TickType_t held = xTaskGetTickCount() - press_tick;
|
||||
ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held));
|
||||
|
||||
if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME))
|
||||
{
|
||||
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;
|
||||
TickType_t now = xTaskGetTickCountFromISR();
|
||||
|
||||
// Debounce: ignore interrupts occurring too close together
|
||||
if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
|
||||
{
|
||||
return;
|
||||
}
|
||||
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);
|
||||
if (level == 0)
|
||||
{
|
||||
// Notify task: button pressed
|
||||
xTaskNotifyFromISR(
|
||||
user_input_task,
|
||||
PRESS_BIT,
|
||||
@@ -215,7 +211,6 @@ static void IRAM_ATTR button_isr_handler(void *arg)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Notify task: button released
|
||||
xTaskNotifyFromISR(
|
||||
user_input_task,
|
||||
RELEASED_BIT,
|
||||
@@ -223,7 +218,6 @@ static void IRAM_ATTR button_isr_handler(void *arg)
|
||||
&higher_task_woken);
|
||||
}
|
||||
|
||||
// Yield to higher priority task if unblocked
|
||||
if (higher_task_woken)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
@@ -238,40 +232,31 @@ static void button_init(void)
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.intr_type = GPIO_INTR_ANYEDGE};
|
||||
|
||||
ESP_ERROR_CHECK(gpio_config(&conf));
|
||||
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)
|
||||
{
|
||||
peripherals_init();
|
||||
led_init();
|
||||
wifi_ini();
|
||||
buzzer_init();
|
||||
ESP_ERROR_CHECK(rest_server_init("/data"));
|
||||
protocols_init();
|
||||
evse_manager_init();
|
||||
evse_init(); // Cria a task para FSM
|
||||
button_init();
|
||||
evse_init();
|
||||
auth_init();
|
||||
loadbalancer_init();
|
||||
meter_manager_init();
|
||||
meter_manager_start();
|
||||
evse_link_init();
|
||||
ocpp_start();
|
||||
|
||||
// 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();
|
||||
scheduler_init();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -300,8 +285,18 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
|
||||
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();
|
||||
|
||||
// 4) tasks auxiliares
|
||||
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
2272
projeto_parte1.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX):
|
||||
def main():
|
||||
diretorio_main = "main"
|
||||
componentes_escolhidos = [
|
||||
"evse", "loadbalancer"
|
||||
"rest_api"
|
||||
]
|
||||
|
||||
diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]
|
||||
|
||||
Reference in New Issue
Block a user