From a0b2e048d49b49300e8e84a5abd6e25038f0d4ab Mon Sep 17 00:00:00 2001 From: PlxEV Date: Sat, 14 Jun 2025 11:46:10 +0100 Subject: [PATCH] new meter --- components/evse/evse_core.c | 11 +- components/evse/evse_limits.c | 128 +- components/evse/evse_state.c | 49 +- components/evse/include/evse_api.h | 14 +- components/evse/include/evse_limits.h | 43 +- components/evse/include/evse_state.h | 88 +- components/ocpp/src/ocpp.c | 2 +- projeto_parte1.c | 1025 +++ projeto_parte10.c | 46 + projeto_parte2.c | 1166 ++++ projeto_parte3.c | 1058 +++ projeto_parte4.c | 896 +++ projeto_parte5.c | 1066 +++ projeto_parte6.c | 735 +++ projeto_parte7.c | 793 +++ projeto_parte8.c | 783 +++ projeto_parte9.c | 1232 ++++ projeto_unificado.c | 8613 +++++++++++++++++++++++++ projeto_unificado.zip | Bin 0 -> 53746 bytes readproject.py | 67 + 20 files changed, 17741 insertions(+), 74 deletions(-) create mode 100755 projeto_parte1.c create mode 100644 projeto_parte10.c create mode 100755 projeto_parte2.c create mode 100755 projeto_parte3.c create mode 100755 projeto_parte4.c create mode 100755 projeto_parte5.c create mode 100755 projeto_parte6.c create mode 100755 projeto_parte7.c create mode 100755 projeto_parte8.c create mode 100755 projeto_parte9.c create mode 100755 projeto_unificado.c create mode 100644 projeto_unificado.zip create mode 100644 readproject.py diff --git a/components/evse/evse_core.c b/components/evse/evse_core.c index 44141d5..af9cf92 100755 --- a/components/evse/evse_core.c +++ b/components/evse/evse_core.c @@ -50,7 +50,7 @@ void evse_process(void) { evse_config_is_enabled() ); - evse_limits_check(evse_get_state()); + evse_limits_check(); evse_state_t current = evse_get_state(); if (current != last_state) { @@ -99,3 +99,12 @@ static void evse_core_task(void *arg) { vTaskDelay(pdMS_TO_TICKS(100)); } } + +uint32_t evse_get_total_energy(void) { + return 0; // Stub de 1 kWh +} + +uint32_t evse_get_instant_power(void) { + return 0; // Stub de 2 kW +} + diff --git a/components/evse/evse_limits.c b/components/evse/evse_limits.c index be86333..255f962 100755 --- a/components/evse/evse_limits.c +++ b/components/evse/evse_limits.c @@ -1,62 +1,108 @@ +#include "evse_state.h" +#include "evse_api.h" #include "evse_limits.h" -#include -#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + // ======================== -// Estado interno +// External state references // ======================== -static bool limit_reached = false; -static uint32_t consumption_limit = 0; -static uint32_t charging_time_limit = 0; -static uint16_t under_power_limit = 0; +//extern evse_state_t current_state; // Current EVSE FSM state +//extern TickType_t session_start_tick; // Timestamp of charging session start -static uint32_t default_consumption_limit = 0; +// ======================== +// Concurrency protection +// ======================== + +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 uint16_t default_under_power_limit = 0; // ======================== -// Estado de controle +// Limit status flag // ======================== -void evse_set_limit_reached(uint8_t value) { - limit_reached = (value != 0); +bool evse_get_limit_reached(void) { + bool val; + portENTER_CRITICAL(&evse_mux); + val = limit_reached; + portEXIT_CRITICAL(&evse_mux); + return val; } -bool evse_is_limit_reached(void) { - return limit_reached; +void evse_set_limit_reached(bool v) { + portENTER_CRITICAL(&evse_mux); + limit_reached = v; + portEXIT_CRITICAL(&evse_mux); } // ======================== -// Limites em tempo de execução +// Runtime limit accessors // ======================== uint32_t evse_get_consumption_limit(void) { - return consumption_limit; + uint32_t val; + portENTER_CRITICAL(&evse_mux); + val = consumption_limit; + portEXIT_CRITICAL(&evse_mux); + return val; } void evse_set_consumption_limit(uint32_t value) { + portENTER_CRITICAL(&evse_mux); consumption_limit = value; + portEXIT_CRITICAL(&evse_mux); } uint32_t evse_get_charging_time_limit(void) { - return charging_time_limit; + uint32_t val; + portENTER_CRITICAL(&evse_mux); + val = charging_time_limit; + portEXIT_CRITICAL(&evse_mux); + return val; } void evse_set_charging_time_limit(uint32_t value) { + portENTER_CRITICAL(&evse_mux); charging_time_limit = value; + portEXIT_CRITICAL(&evse_mux); } uint16_t evse_get_under_power_limit(void) { - return under_power_limit; + uint16_t val; + portENTER_CRITICAL(&evse_mux); + val = under_power_limit; + portEXIT_CRITICAL(&evse_mux); + return val; } void evse_set_under_power_limit(uint16_t value) { + portENTER_CRITICAL(&evse_mux); under_power_limit = value; + portEXIT_CRITICAL(&evse_mux); } // ======================== -// Limites padrão (persistentes) +// Default (persistent) limit accessors +// These values can be stored/restored via NVS // ======================== uint32_t evse_get_default_consumption_limit(void) { @@ -83,15 +129,45 @@ 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(); +} + + // ======================== -// Lógica de verificação de limites +// 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(evse_state_t state) { - // Se algum limite estiver ativo, verifique o estado - if ((consumption_limit > 0 || charging_time_limit > 0 || under_power_limit > 0) - && evse_state_is_charging(state)) { - // (Lógica real a ser aplicada aqui, ex: medição de consumo, tempo ou potência) - evse_set_limit_reached(1); +void evse_limits_check(void) { + evse_state_t state = evse_get_state(); + if (!evse_state_is_charging(state)) return; + + bool reached = false; + + uint32_t energy = evse_get_total_energy(); + uint32_t power = evse_get_instant_power(); + TickType_t now = xTaskGetTickCount(); + TickType_t start = evse_get_session_start(); + + if (consumption_limit > 0 && energy >= consumption_limit) { + ESP_LOGW("EVSE", "Energy limit reached"); + reached = true; + } + + if (charging_time_limit > 0 && + (now - start) >= pdMS_TO_TICKS(charging_time_limit * 1000)) { + ESP_LOGW("EVSE", "Charging time limit reached"); + reached = true; + } + + if (under_power_limit > 0 && power < under_power_limit) { + ESP_LOGW("EVSE", "Under power limit reached"); + reached = true; + } + + if (reached) { + evse_set_limit_reached(true); } } diff --git a/components/evse/evse_state.c b/components/evse/evse_state.c index 42a511d..fb04c42 100755 --- a/components/evse/evse_state.c +++ b/components/evse/evse_state.c @@ -1,32 +1,41 @@ +#include "evse_api.h" #include "evse_state.h" #include "evse_events.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "esp_log.h" +// ========================= +// Internal State Variables +// ========================= + static evse_state_t current_state = EVSE_STATE_A; static bool is_authorized = false; +static TickType_t session_start_tick = 0; -// Proteção básica para variáveis globais em sistemas concorrentes static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; +// ========================= +// Internal Mapping +// ========================= + static evse_state_event_t map_state_to_event(evse_state_t s) { switch (s) { - case EVSE_STATE_A: - return EVSE_STATE_EVENT_IDLE; - case EVSE_STATE_B1: - return EVSE_STATE_EVENT_WAITING; + case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE; + case EVSE_STATE_B1: return EVSE_STATE_EVENT_WAITING; case EVSE_STATE_B2: case EVSE_STATE_C1: - case EVSE_STATE_C2: - return EVSE_STATE_EVENT_CHARGING; + case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING; case EVSE_STATE_E: - case EVSE_STATE_F: - return EVSE_STATE_EVENT_FAULT; - default: - return EVSE_STATE_EVENT_IDLE; + case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT; + default: return EVSE_STATE_EVENT_IDLE; } } + +// ========================= +// Public API +// ========================= + void evse_set_state(evse_state_t state) { bool changed = false; evse_state_t previous_state; @@ -36,11 +45,15 @@ void evse_set_state(evse_state_t state) { if (state != current_state) { current_state = state; changed = true; + + if (evse_state_is_charging(state) && !evse_state_is_charging(previous_state)) { + session_start_tick = xTaskGetTickCount(); + } } portEXIT_CRITICAL(&state_mux); if (changed) { - ESP_LOGI("EVSE_STATE", "Estado alterado de %s para %s", + ESP_LOGI("EVSE_STATE", "State changed from %s to %s", evse_state_to_str(previous_state), evse_state_to_str(state)); @@ -58,6 +71,13 @@ evse_state_t evse_get_state(void) { return s; } +TickType_t evse_get_session_start(void) { + portENTER_CRITICAL(&state_mux); + TickType_t t = session_start_tick; + portEXIT_CRITICAL(&state_mux); + return t; +} + const char* evse_state_to_str(evse_state_t state) { switch (state) { case EVSE_STATE_A: return "A - EV Not Connected (12V)"; @@ -76,10 +96,11 @@ const char* evse_state_to_str(evse_state_t state) { void evse_state_init(void) { portENTER_CRITICAL(&state_mux); current_state = EVSE_STATE_A; + session_start_tick = xTaskGetTickCount(); is_authorized = true; portEXIT_CRITICAL(&state_mux); - ESP_LOGI("EVSE_STATE", "Inicializado em estado: %s", evse_state_to_str(current_state)); + ESP_LOGI("EVSE_STATE", "Initialized in state: %s", evse_state_to_str(current_state)); evse_state_event_data_t evt = { .state = map_state_to_event(current_state) @@ -88,7 +109,7 @@ void evse_state_init(void) { } void evse_state_tick(void) { - // Tick do estado (placeholder) + // Placeholder for future state logic } bool evse_state_is_charging(evse_state_t state) { diff --git a/components/evse/include/evse_api.h b/components/evse/include/evse_api.h index 9a10e29..00cd71a 100755 --- a/components/evse/include/evse_api.h +++ b/components/evse/include/evse_api.h @@ -50,7 +50,13 @@ void evse_set_charging_time_limit(uint32_t value); uint16_t evse_get_under_power_limit(void); void evse_set_under_power_limit(uint16_t value); -void evse_set_limit_reached(uint8_t value); +void evse_set_limit_reached(bool value); + +// Energia total acumulada da sessão (em Wh) +uint32_t evse_get_total_energy(void); + +// Potência instantânea medida (em W) +uint32_t evse_get_instant_power(void); // Limites default uint32_t evse_get_default_consumption_limit(void); @@ -60,4 +66,10 @@ 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); + +uint32_t evse_get_total_energy(void); +uint32_t evse_get_instant_power(void); + + + #endif // EVSE_API_H diff --git a/components/evse/include/evse_limits.h b/components/evse/include/evse_limits.h index 0933fe4..4d5fb39 100755 --- a/components/evse/include/evse_limits.h +++ b/components/evse/include/evse_limits.h @@ -9,24 +9,47 @@ extern "C" { #endif -/// Estado dos limites -void evse_set_limit_reached(uint8_t value); -bool evse_is_limit_reached(void); +// ======================== +// Limit state control +// ======================== -/// Verifica e aplica lógica de limites com base no estado atual do EVSE -void evse_limits_check(evse_state_t state); +/** + * @brief Sets the 'limit reached' flag. Used internally when a session exceeds defined thresholds. + */ +void evse_set_limit_reached(bool value); + +/** + * @brief Returns whether any session limit has been reached (energy, time or power). + */ +bool evse_get_limit_reached(void); + +// ======================== +// Limit checking +// ======================== + +/** + * @brief Evaluates if the session has exceeded any configured limits. + * Should be called periodically while in charging state. + */ +void evse_limits_check(void); + +// ======================== +// Runtime limit configuration +// ======================== -/// Limites ativos (runtime) uint32_t evse_get_consumption_limit(void); -void evse_set_consumption_limit(uint32_t value); +void evse_set_consumption_limit(uint32_t value); // in Wh uint32_t evse_get_charging_time_limit(void); -void evse_set_charging_time_limit(uint32_t value); +void evse_set_charging_time_limit(uint32_t value); // in seconds uint16_t evse_get_under_power_limit(void); -void evse_set_under_power_limit(uint16_t value); +void evse_set_under_power_limit(uint16_t value); // in Watts + +// ======================== +// Default (persistent) limits +// ======================== -/// Limites padrão (persistentes) uint32_t evse_get_default_consumption_limit(void); void evse_set_default_consumption_limit(uint32_t value); diff --git a/components/evse/include/evse_state.h b/components/evse/include/evse_state.h index 067c778..4b255e0 100755 --- a/components/evse/include/evse_state.h +++ b/components/evse/include/evse_state.h @@ -1,49 +1,95 @@ #ifndef EVSE_STATE_H #define EVSE_STATE_H +#include +#include "freertos/FreeRTOS.h" #include "evse_events.h" +// ============================ +// EVSE Pilot Signal States +// ============================ -#include - -// Estado do EVSE (pilot signal) typedef enum { - EVSE_STATE_A, - EVSE_STATE_B1, - EVSE_STATE_B2, - EVSE_STATE_C1, - EVSE_STATE_C2, - EVSE_STATE_D1, - EVSE_STATE_D2, - EVSE_STATE_E, - EVSE_STATE_F + EVSE_STATE_A, // EV Not Connected (12V) + EVSE_STATE_B1, // EV Connected (9V, Not Authorized) + EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready) + EVSE_STATE_C1, // Charging Requested (6V, Relay Off) + EVSE_STATE_C2, // Charging Active (6V, Relay On) + EVSE_STATE_D1, // Ventilation Required (3V, Relay Off) + EVSE_STATE_D2, // Ventilation Active (3V, Relay On) + EVSE_STATE_E, // Error: Pilot Short to Ground (0V) + EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable } evse_state_t; +// ============================ +// Initialization & Core Control +// ============================ -// Funções públicas necessárias +/** + * @brief Initializes the EVSE state machine. + */ void evse_state_init(void); + +/** + * @brief Periodic tick function for the state machine. + */ void evse_state_tick(void); -void evse_state_set_authorized(bool authorized); -bool evse_state_get_authorized(void); - +// ============================ +// State Access +// ============================ +/** + * @brief Returns the current EVSE state. + */ evse_state_t evse_get_state(void); +/** + * @brief Updates the current EVSE state and triggers events. + */ void evse_set_state(evse_state_t state); -// Converte o estado para string +/** + * @brief Returns the tick count when charging session started. + */ +TickType_t evse_get_session_start(void); + +/** + * @brief Converts the state enum to a human-readable string. + */ const char* evse_state_to_str(evse_state_t state); -// Retorna true se o estado representa sessão ativa +// ============================ +// State Evaluators +// ============================ + +/** + * @brief Returns true if the state represents an active session (B2, C1, C2). + */ bool evse_state_is_session(evse_state_t state); -// Retorna true se o estado representa carregamento ativo +/** + * @brief Returns true if the state represents active charging (C1, C2). + */ bool evse_state_is_charging(evse_state_t state); -// Retorna true se o estado representa veículo conectado +/** + * @brief Returns true if the vehicle is plugged in. + */ bool evse_state_is_plugged(evse_state_t state); -//evse_state_event_t map_state_to_event(evse_state_t state); +// ============================ +// Authorization +// ============================ + +/** + * @brief Sets the vehicle authorization state. + */ +void evse_state_set_authorized(bool authorized); + +/** + * @brief Returns the current vehicle authorization state. + */ +bool evse_state_get_authorized(void); #endif // EVSE_STATE_H diff --git a/components/ocpp/src/ocpp.c b/components/ocpp/src/ocpp.c index cc72521..10edfe8 100755 --- a/components/ocpp/src/ocpp.c +++ b/components/ocpp/src/ocpp.c @@ -311,7 +311,7 @@ void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification case DeAuthorized: ESP_LOGI(TAG, "DeAuthorized ---->"); //evse_set_authorized(false); - evse_set_limit_reached(2); + //evse_set_limit_reached(2); // ocpp_set_charging(false); break; // server rejected StartTx case RemoteStart: diff --git a/projeto_parte1.c b/projeto_parte1.c new file mode 100755 index 0000000..199f3e1 --- /dev/null +++ b/projeto_parte1.c @@ -0,0 +1,1025 @@ + + +// === Início de: main/main.c === +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_spiffs.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "driver/gpio.h" + +#include "wifi.h" +#include "board_config.h" +#include "logger.h" +#include "rest_main.h" + +#include "peripherals.h" +#include "protocols.h" +#include "evse_manager.h" +#include "evse_api.h" +#include "auth.h" +#include "loadbalancer.h" +#include "meter_manager.h" + + +#define EVSE_MANAGER_TICK_PERIOD_MS 1000 +#define AP_CONNECTION_TIMEOUT 120000 +#define RESET_HOLD_TIME 10000 +#define DEBOUNCE_TIME_MS 50 + +#define PRESS_BIT BIT0 +#define RELEASED_BIT BIT1 + +static const char *TAG = "app_main"; + +static TaskHandle_t user_input_task; +static TickType_t press_tick = 0; +static TickType_t last_interrupt_tick = 0; +static bool pressed = false; + + +// +// File system (SPIFFS) init and info +// +static void fs_info(esp_vfs_spiffs_conf_t *conf) { + size_t total = 0, used = 0; + esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used); + if (ret == ESP_OK) + ESP_LOGI(TAG, "Partition %s: total: %d, used: %d", conf->partition_label, total, used); + else + ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", esp_err_to_name(ret)); +} + +static void fs_init(void) { + esp_vfs_spiffs_conf_t cfg_conf = { + .base_path = "/cfg", + .partition_label = "cfg", + .max_files = 1, + .format_if_mount_failed = false + }; + + esp_vfs_spiffs_conf_t data_conf = { + .base_path = "/data", + .partition_label = "data", + .max_files = 5, + .format_if_mount_failed = true + }; + + ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf)); + ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf)); + + fs_info(&cfg_conf); + fs_info(&data_conf); +} + +// +// Wi-Fi event monitoring task +// +static void wifi_event_task_func(void *param) { + EventBits_t mode_bits; + while (1) { + mode_bits = xEventGroupWaitBits(wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + + if (mode_bits & WIFI_AP_MODE_BIT) { + if (xEventGroupWaitBits(wifi_event_group, WIFI_AP_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & WIFI_AP_CONNECTED_BIT) { + xEventGroupWaitBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + } else { + if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { + //wifi_ap_stop(); + } + } + } else if (mode_bits & WIFI_STA_MODE_BIT) { + xEventGroupWaitBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + } + } +} + +// +// Botão e tratamento +// +static void handle_button_press(void) { + ESP_LOGI(TAG, "Ativando modo AP"); + if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { + wifi_ap_start(); + } +} + +static void user_input_task_func(void *param) { + uint32_t notification; + while (1) { + if (xTaskNotifyWait(0x00, 0xFF, ¬ification, portMAX_DELAY)) { + if (notification & PRESS_BIT) { + press_tick = xTaskGetTickCount(); + pressed = true; + ESP_LOGI(TAG, "Botão pressionado"); + } + + if (notification & RELEASED_BIT && pressed) { + pressed = false; + ESP_LOGI(TAG, "Botão liberado"); + handle_button_press(); + } + } + } +} + +static void IRAM_ATTR button_isr_handler(void *arg) { + BaseType_t higher_task_woken = pdFALSE; + TickType_t now = xTaskGetTickCountFromISR(); + + if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) return; + last_interrupt_tick = now; + + if (!gpio_get_level(board_config.button_wifi_gpio)) { + xTaskNotifyFromISR(user_input_task, RELEASED_BIT, eSetBits, &higher_task_woken); + } else { + xTaskNotifyFromISR(user_input_task, PRESS_BIT, eSetBits, &higher_task_woken); + } + + if (higher_task_woken) { + portYIELD_FROM_ISR(); + } +} + +static void button_init(void) { + gpio_config_t conf = { + .pin_bit_mask = BIT64(board_config.button_wifi_gpio), + .mode = GPIO_MODE_INPUT, + .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 +// +static void init_modules(void) { + peripherals_init(); + //api_init(); + ESP_ERROR_CHECK(rest_server_init("/data")); + protocols_init(); + evse_manager_init(); + evse_init(); // Cria a task para FSM + button_init(); + auth_init(); + loadbalancer_init(); + meter_manager_grid_init(); + meter_manager_grid_start(); + //meter_manager_evse_init(); + + // 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(); +} + +// +// Função principal do firmware +// +void app_main(void) { + logger_init(); + esp_log_set_vprintf(logger_vprintf); + + esp_reset_reason_t reason = esp_reset_reason(); + ESP_LOGI(TAG, "Reset reason: %d", reason); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "Erasing NVS flash"); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + fs_init(); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + + board_config_load(); + wifi_ini(); + //wifi_ap_start(); + init_modules(); + + 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 === + + +// === Início de: components/evse/evse_pilot.c === +#include +#include +#include +#include +#include + +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_rom_sys.h" + +#include "evse_pilot.h" +#include "adc.h" +#include "board_config.h" + +#define PILOT_PWM_TIMER LEDC_TIMER_0 +#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0 +#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE +#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT +#define PILOT_PWM_MAX_DUTY 1023 + +#define NUM_PILOT_SAMPLES 100 +#define MAX_SAMPLE_ATTEMPTS 1000 +#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior + +static const char *TAG = "evse_pilot"; + +void pilot_init(void) +{ + ledc_timer_config_t ledc_timer = { + .speed_mode = PILOT_PWM_SPEED_MODE, + .timer_num = PILOT_PWM_TIMER, + .duty_resolution = PILOT_PWM_DUTY_RES, + .freq_hz = 1000, + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + ledc_channel_config_t ledc_channel = { + .speed_mode = PILOT_PWM_SPEED_MODE, + .channel = PILOT_PWM_CHANNEL, + .timer_sel = PILOT_PWM_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = board_config.pilot_pwm_gpio, + .duty = 0, + .hpoint = 0 + }; + 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)); + + adc_oneshot_chan_cfg_t config = { + .bitwidth = ADC_BITWIDTH_DEFAULT, + .atten = ADC_ATTEN_DB_12, + }; + + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.pilot_adc_channel, &config)); +} + +void pilot_set_level(bool level) +{ + ESP_LOGI(TAG, "Set level %d", level); + ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); +} + +void pilot_set_amps(uint16_t amps) +{ + ESP_LOGI(TAG, "Set amps %d", amps); + + if (amps < 60 || amps > 800) { + ESP_LOGE(TAG, "Invalid ampere value: %d A*10", amps); + return; + } + + uint32_t duty; + if (amps <= 510) { + duty = (PILOT_PWM_MAX_DUTY * amps) / 600; + } else { + duty = ((PILOT_PWM_MAX_DUTY * amps) / 2500) + (64 * (PILOT_PWM_MAX_DUTY / 100)); + } + + if (duty > PILOT_PWM_MAX_DUTY) + duty = PILOT_PWM_MAX_DUTY; + + ESP_LOGI(TAG, "Set amp %dA*10 -> duty %lu/%d", amps, duty, PILOT_PWM_MAX_DUTY); + ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty); + ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); +} +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)]; +} + +void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) +{ + ESP_LOGD(TAG, "pilot_measure"); + + int samples[NUM_PILOT_SAMPLES]; + int collected = 0, attempts = 0; + int sample; + + while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { + if (adc_oneshot_read(adc_handle, board_config.pilot_adc_channel, &sample) == ESP_OK) { + samples[collected++] = sample; + esp_rom_delay_us(10); + } else { + esp_rom_delay_us(100); + attempts++; + } + } + + if (collected < NUM_PILOT_SAMPLES) { + ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES); + *up_voltage = PILOT_VOLTAGE_1; + *down_voltage_n12 = false; + return; + } + + int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); + int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); + + + ESP_LOGD(TAG, "Final: high_raw=%d, low_raw=%d", high_raw, low_raw); + + int high_mv = 0; + int low_mv = 0; + + if (adc_cali_raw_to_voltage(adc_cali_handle, high_raw, &high_mv) != ESP_OK || + adc_cali_raw_to_voltage(adc_cali_handle, low_raw, &low_mv) != ESP_OK) { + ESP_LOGW(TAG, "ADC calibration failed"); + *up_voltage = PILOT_VOLTAGE_1; + *down_voltage_n12 = false; + return; + } + + if (high_mv >= board_config.pilot_down_threshold_12) + *up_voltage = PILOT_VOLTAGE_12; + else if (high_mv >= board_config.pilot_down_threshold_9) + *up_voltage = PILOT_VOLTAGE_9; + else if (high_mv >= board_config.pilot_down_threshold_6) + *up_voltage = PILOT_VOLTAGE_6; + else if (high_mv >= board_config.pilot_down_threshold_3) + *up_voltage = PILOT_VOLTAGE_3; + else + *up_voltage = PILOT_VOLTAGE_1; + + *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12); + + ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12); +} + +// === Fim de: components/evse/evse_pilot.c === + + +// === Início de: components/evse/evse_hardware.c === +#include "evse_hardware.h" +#include "evse_pilot.h" +#include "ac_relay.h" +#include "socket_lock.h" +#include "proximity.h" + +static const char *TAG = "evse_hardware"; + +void evse_hardware_init(void) { + pilot_init(); + pilot_set_level(true); // Sinal piloto em 12V (inicial) + ac_relay_set_state(false); // Relé desligado + //socket_lock_set_locked(false); // Destrava o conector +} + +void evse_hardware_tick(void) { + // Tick para atualizações de hardware com polling, se necessário +} + +bool evse_hardware_is_pilot_high(void) { + return pilot_get_state(); // true se sinal piloto estiver em nível alto +} + +bool evse_hardware_is_vehicle_connected(void) { + // Verifica se o veículo está conectado pelo resistor do pino PP + return proximity_get_max_current() > 0; +} + +bool evse_hardware_is_energy_detected(void) { + return false; +} + +void evse_hardware_relay_on(void) { + ac_relay_set_state(true); +} + +void evse_hardware_relay_off(void) { + ac_relay_set_state(false); +} + +bool evse_hardware_relay_status(void) { + return ac_relay_get_state(); +} + +void evse_hardware_lock(void) { + socket_lock_set_locked(true); +} + +void evse_hardware_unlock(void) { + socket_lock_set_locked(false); +} + +bool evse_hardware_is_locked(void) { + return socket_lock_is_locked_state(); +} + +// === Fim de: components/evse/evse_hardware.c === + + +// === Início de: components/evse/evse_state.c === +#include "evse_api.h" +#include "evse_state.h" +#include "evse_events.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "esp_log.h" + +// ========================= +// Internal State Variables +// ========================= + +static evse_state_t current_state = EVSE_STATE_A; +static bool is_authorized = false; +static TickType_t session_start_tick = 0; + +static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; + +// ========================= +// Internal Mapping +// ========================= + +static evse_state_event_t map_state_to_event(evse_state_t s) { + switch (s) { + case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE; + case EVSE_STATE_B1: return EVSE_STATE_EVENT_WAITING; + case EVSE_STATE_B2: + case EVSE_STATE_C1: + case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING; + case EVSE_STATE_E: + case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT; + default: return EVSE_STATE_EVENT_IDLE; + } +} + +// ========================= +// Public API +// ========================= + +void evse_set_state(evse_state_t state) { + bool changed = false; + evse_state_t previous_state; + + portENTER_CRITICAL(&state_mux); + previous_state = current_state; + if (state != current_state) { + current_state = state; + changed = true; + + if (evse_state_is_charging(state) && !evse_state_is_charging(previous_state)) { + session_start_tick = xTaskGetTickCount(); + } + } + portEXIT_CRITICAL(&state_mux); + + if (changed) { + ESP_LOGI("EVSE_STATE", "State changed from %s to %s", + evse_state_to_str(previous_state), + evse_state_to_str(state)); + + evse_state_event_data_t evt = { + .state = map_state_to_event(state) + }; + esp_event_post(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); + } +} + +evse_state_t evse_get_state(void) { + portENTER_CRITICAL(&state_mux); + evse_state_t s = current_state; + portEXIT_CRITICAL(&state_mux); + return s; +} + +TickType_t evse_get_session_start(void) { + portENTER_CRITICAL(&state_mux); + TickType_t t = session_start_tick; + portEXIT_CRITICAL(&state_mux); + return t; +} + +const char* evse_state_to_str(evse_state_t state) { + switch (state) { + case EVSE_STATE_A: return "A - EV Not Connected (12V)"; + case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)"; + case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)"; + case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)"; + case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)"; + case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)"; + case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)"; + case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)"; + case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal"; + default: return "Unknown State"; + } +} + +void evse_state_init(void) { + portENTER_CRITICAL(&state_mux); + current_state = EVSE_STATE_A; + session_start_tick = xTaskGetTickCount(); + is_authorized = true; + portEXIT_CRITICAL(&state_mux); + + ESP_LOGI("EVSE_STATE", "Initialized in state: %s", evse_state_to_str(current_state)); + + evse_state_event_data_t evt = { + .state = map_state_to_event(current_state) + }; + esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); +} + +void evse_state_tick(void) { + // Placeholder for future state logic +} + +bool evse_state_is_charging(evse_state_t state) { + return state == EVSE_STATE_C1 || state == EVSE_STATE_C2; +} + +bool evse_state_is_plugged(evse_state_t state) { + return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 || + state == EVSE_STATE_C1 || state == EVSE_STATE_C2 || + state == EVSE_STATE_D1 || state == EVSE_STATE_D2; +} + +bool evse_state_is_session(evse_state_t state) { + return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2; +} + +void evse_state_set_authorized(bool authorized) { + portENTER_CRITICAL(&state_mux); + is_authorized = authorized; + portEXIT_CRITICAL(&state_mux); +} + +bool evse_state_get_authorized(void) { + portENTER_CRITICAL(&state_mux); + bool result = is_authorized; + portEXIT_CRITICAL(&state_mux); + return result; +} + +// === Fim de: components/evse/evse_state.c === + + +// === Início de: components/evse/evse_fsm.c === +// evse_fsm.c - Máquina de Estados EVSE com controle centralizado + +#include "evse_fsm.h" +#include "evse_api.h" +#include "evse_pilot.h" +#include "evse_config.h" +#include "esp_log.h" +#include "ac_relay.h" +#include "board_config.h" +#include "socket_lock.h" +#include "proximity.h" +#include "rcm.h" +#include "evse_state.h" + +static const char *TAG = "evse_fsm"; + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static bool c1_d1_waiting = false; +static TickType_t c1_d1_relay_to = 0; + +void evse_fsm_reset(void) { + evse_set_state(EVSE_STATE_A); + c1_d1_waiting = false; + c1_d1_relay_to = 0; +} + +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) { + cable_max_current = proximity_get_max_current(); + } + + 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 * 10, cable_max_current * 10)); + ac_relay_set_state(false); + break; + + case EVSE_STATE_C1: + case EVSE_STATE_D1: + pilot_set_level(true); + 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 * 10, cable_max_current * 10)); + ac_relay_set_state(true); + break; + } +} + +void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled) { + TickType_t now = xTaskGetTickCount(); + 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; + + 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; + } + } + __attribute__((fallthrough)); // Evita warning de fallthrough implícito + + case EVSE_STATE_C2: + case EVSE_STATE_D2: + if (!enabled || !available) { + evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1) ? EVSE_STATE_D1 : EVSE_STATE_C1); + break; + } + + switch (pilot_voltage) { + case PILOT_VOLTAGE_6: + evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); + break; + case PILOT_VOLTAGE_3: + evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1); + break; + case PILOT_VOLTAGE_9: + evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); + break; + case PILOT_VOLTAGE_12: + evse_set_state(EVSE_STATE_A); + break; + default: + break; + } + break; + + case EVSE_STATE_E: + break; // Sem transições a partir de E + + case EVSE_STATE_F: + if (available) { + evse_set_state(EVSE_STATE_A); + } + break; + } + + evse_state_t next = evse_get_state(); + if (next != prev) { + ESP_LOGI(TAG, "State changed: %s -> %s", evse_state_to_str(prev), evse_state_to_str(next)); + update_outputs(next); + } +} + +// === Fim de: components/evse/evse_fsm.c === + + +// === Início de: components/evse/evse_error.c === +#include "evse_error.h" +#include "evse_config.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "ntc_sensor.h" + +static const char *TAG = "evse_error"; + +static uint32_t error_bits = 0; +static TickType_t auto_clear_timeout = 0; +static bool error_cleared = false; + +void evse_error_init(void) { + // Inicialização do sistema de erros +} + +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); + ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); + } + } + + // 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"); + } + } +} + +void evse_temperature_check(void) { + float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida) + uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável + + // Log informativo com os valores + ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold); + + // Se a temperatura parecer inválida, aplica erro de sensor + if (temp_c < -40.0f || temp_c > 150.0f) { + if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado + evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT); + ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado"); + } + return; + } + + evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida + + if (temp_c >= threshold) { + if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado + evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); + ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold); + } + } else { + evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); + } +} + +uint32_t evse_get_error(void) { + return error_bits; +} + +bool evse_is_error_cleared(void) { + return error_cleared; +} + +void evse_mark_error_cleared(void) { + error_cleared = false; +} + +// Já existentes +void evse_error_set(uint32_t bitmask) { + error_bits |= bitmask; + + if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) { + auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s + } +} + +void evse_error_clear(uint32_t bitmask) { + bool had_error = error_bits != 0; + error_bits &= ~bitmask; + + if (had_error && error_bits == 0) { + error_cleared = true; + } +} + +void evse_error_tick(void) { + if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) { + evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS); + auto_clear_timeout = 0; + } +} + +bool evse_error_is_active(void) { + return error_bits != 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; +} + +// === Fim de: components/evse/evse_error.c === + + +// === Início de: components/evse/evse_core.c === +// evse_core.c - Função principal de controle do EVSE + +#include "evse_fsm.h" +#include "evse_error.h" +#include "evse_limits.h" +#include "evse_config.h" +#include "evse_api.h" +#include "evse_pilot.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" + +static const char *TAG = "evse_core"; + +static SemaphoreHandle_t mutex; + +static evse_state_t last_state = EVSE_STATE_A; + +static void evse_core_task(void *arg); + +void evse_init(void) { + ESP_LOGI(TAG, "EVSE Init"); + + mutex = xSemaphoreCreateMutex(); + + evse_check_defaults(); + evse_fsm_reset(); + pilot_set_level(true); // Estado inicial do piloto + + xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); +} + +void evse_process(void) { + xSemaphoreTake(mutex, portMAX_DELAY); + + pilot_voltage_t pilot_voltage; + bool is_n12v = false; + + pilot_measure(&pilot_voltage, &is_n12v); + ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no"); + + if (evse_get_error() == 0 && !evse_is_error_cleared()) { + + evse_error_check(pilot_voltage, is_n12v); + + evse_fsm_process( + pilot_voltage, + evse_state_get_authorized(), + evse_config_is_available(), + 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; + } + + evse_mark_error_cleared(); + } + + xSemaphoreGive(mutex); +} + + +// ================================ +// Interface pública +// ================================ + +bool evse_is_enabled(void) { + return evse_config_is_enabled(); +} + +void evse_set_enabled(bool value) { + ESP_LOGI(TAG, "Set enabled %d", value); + evse_config_set_enabled(value); +} + +bool evse_is_available(void) { + return evse_config_is_available(); +} + +void evse_set_available(bool value) { + ESP_LOGI(TAG, "Set available %d", value); + evse_config_set_available(value); +} + +// ================================ +// Tarefa principal +// ================================ + +static void evse_core_task(void *arg) { + while (true) { + evse_process(); + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +uint32_t evse_get_total_energy(void) { + return 0; // Stub de 1 kWh +} + +uint32_t evse_get_instant_power(void) { + return 0; // Stub de 2 kW +} + + +// === Fim de: components/evse/evse_core.c === diff --git a/projeto_parte10.c b/projeto_parte10.c new file mode 100644 index 0000000..24d015a --- /dev/null +++ b/projeto_parte10.c @@ -0,0 +1,46 @@ + + +// === Início de: components/peripherals/include/temp_sensor.h === +#ifndef TEMP_SENSOR_H_ +#define TEMP_SENSOR_H_ + +#include +#include "esp_err.h" + +/** + * @brief Initialize DS18S20 temperature sensor bus + * + */ +void temp_sensor_init(void); + +/** + * @brief Get found sensor count + * + * @return uint8_t + */ +uint8_t temp_sensor_get_count(void); + +/** + * @brief Return lowest temperature after temp_sensor_measure + * + * @return int16_t + */ +int16_t temp_sensor_get_low(void); + +/** + * @brief Return highest temperature after temp_sensor_measure + * + * @return int + */ +int temp_sensor_get_high(void); + +/** + * @brief Return temperature sensor error + * + * @return bool + */ +bool temp_sensor_is_error(void); + +#endif /* TEMP_SENSOR_H_ */ + +// === Fim de: components/peripherals/include/temp_sensor.h === diff --git a/projeto_parte2.c b/projeto_parte2.c new file mode 100755 index 0000000..5dc732c --- /dev/null +++ b/projeto_parte2.c @@ -0,0 +1,1166 @@ + + +// === Início de: components/evse/evse_limits.c === +#include "evse_state.h" +#include "evse_api.h" +#include "evse_limits.h" +#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 + +// ======================== +// Concurrency protection +// ======================== + +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; + +// ======================== +// Limit status flag +// ======================== + +bool evse_get_limit_reached(void) { + bool val; + portENTER_CRITICAL(&evse_mux); + val = limit_reached; + portEXIT_CRITICAL(&evse_mux); + return val; +} + +void evse_set_limit_reached(bool v) { + portENTER_CRITICAL(&evse_mux); + limit_reached = v; + portEXIT_CRITICAL(&evse_mux); +} + +// ======================== +// Runtime limit accessors +// ======================== + +uint32_t evse_get_consumption_limit(void) { + uint32_t val; + portENTER_CRITICAL(&evse_mux); + val = consumption_limit; + portEXIT_CRITICAL(&evse_mux); + return val; +} + +void evse_set_consumption_limit(uint32_t value) { + portENTER_CRITICAL(&evse_mux); + consumption_limit = value; + portEXIT_CRITICAL(&evse_mux); +} + +uint32_t evse_get_charging_time_limit(void) { + uint32_t val; + portENTER_CRITICAL(&evse_mux); + val = charging_time_limit; + portEXIT_CRITICAL(&evse_mux); + return val; +} + +void evse_set_charging_time_limit(uint32_t value) { + portENTER_CRITICAL(&evse_mux); + charging_time_limit = value; + portEXIT_CRITICAL(&evse_mux); +} + +uint16_t evse_get_under_power_limit(void) { + uint16_t val; + portENTER_CRITICAL(&evse_mux); + val = under_power_limit; + portEXIT_CRITICAL(&evse_mux); + return val; +} + +void evse_set_under_power_limit(uint16_t value) { + portENTER_CRITICAL(&evse_mux); + under_power_limit = value; + portEXIT_CRITICAL(&evse_mux); +} + +// ======================== +// Default (persistent) limit accessors +// These values can be stored/restored via NVS +// ======================== + +uint32_t evse_get_default_consumption_limit(void) { + return default_consumption_limit; +} + +void evse_set_default_consumption_limit(uint32_t value) { + default_consumption_limit = value; +} + +uint32_t evse_get_default_charging_time_limit(void) { + return default_charging_time_limit; +} + +void evse_set_default_charging_time_limit(uint32_t value) { + default_charging_time_limit = value; +} + +uint16_t evse_get_default_under_power_limit(void) { + return default_under_power_limit; +} + +void evse_set_default_under_power_limit(uint16_t value) { + default_under_power_limit = value; +} + +bool evse_is_limit_reached(void) { + return evse_get_limit_reached(); +} + + +// ======================== +// Limit checking logic +// This function must be called periodically while charging. +// It will flag the session as "limit reached" when thresholds are violated. +// ======================== + +void evse_limits_check(void) { + evse_state_t state = evse_get_state(); + if (!evse_state_is_charging(state)) return; + + bool reached = false; + + uint32_t energy = evse_get_total_energy(); + uint32_t power = evse_get_instant_power(); + TickType_t now = xTaskGetTickCount(); + TickType_t start = evse_get_session_start(); + + if (consumption_limit > 0 && energy >= consumption_limit) { + ESP_LOGW("EVSE", "Energy limit reached"); + reached = true; + } + + if (charging_time_limit > 0 && + (now - start) >= pdMS_TO_TICKS(charging_time_limit * 1000)) { + ESP_LOGW("EVSE", "Charging time limit reached"); + reached = true; + } + + if (under_power_limit > 0 && power < under_power_limit) { + ESP_LOGW("EVSE", "Under power limit reached"); + reached = true; + } + + if (reached) { + evse_set_limit_reached(true); + } +} + +// === Fim de: components/evse/evse_limits.c === + + +// === Início de: components/evse/evse_config.c === +#include // For PRI macros +#include "evse_config.h" +#include "board_config.h" +#include "evse_limits.h" +#include "esp_log.h" +#include "nvs.h" + +static const char *TAG = "evse_config"; + +static nvs_handle_t nvs; + +// ======================== +// Configurable parameters +// ======================== +static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT; +static uint16_t charging_current; // Persisted (NVS) +static uint16_t charging_current_runtime = 0; // Runtime only +static bool socket_outlet; +static bool rcm; +static uint8_t temp_threshold = 60; +static bool require_auth; + +// ======================== +// Initialization +// ======================== +esp_err_t evse_config_init(void) { + ESP_LOGD(TAG, "Initializing NVS configuration..."); + return nvs_open("evse", NVS_READWRITE, &nvs); +} + +void evse_check_defaults(void) { + esp_err_t err; + uint8_t u8; + uint16_t u16; + uint32_t u32; + bool needs_commit = false; + + ESP_LOGD(TAG, "Checking default parameters..."); + + // Max charging current + err = nvs_get_u8(nvs, "max_chrg_curr", &u8); + if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) { + max_charging_current = MAX_CHARGING_CURRENT_LIMIT; + nvs_set_u8(nvs, "max_chrg_curr", max_charging_current); + needs_commit = true; + ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current); + } else { + max_charging_current = u8; + } + + // Charging current (default, persisted) + err = nvs_get_u16(nvs, "def_chrg_curr", &u16); + if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT * 10) || u16 > (max_charging_current * 10)) { + charging_current = max_charging_current * 10; + nvs_set_u16(nvs, "def_chrg_curr", charging_current); + needs_commit = true; + ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current); + } else { + charging_current = u16; + } + + // Runtime charging current initialized from persisted default + charging_current_runtime = charging_current; + ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime); + + // Auth required + err = nvs_get_u8(nvs, "require_auth", &u8); + require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false; + if (err != ESP_OK) { + nvs_set_u8(nvs, "require_auth", require_auth); + needs_commit = true; + } + + // Socket outlet + err = nvs_get_u8(nvs, "socket_outlet", &u8); + socket_outlet = (err == ESP_OK && u8) && board_config.proximity; + if (err != ESP_OK) { + nvs_set_u8(nvs, "socket_outlet", socket_outlet); + needs_commit = true; + } + + // RCM + err = nvs_get_u8(nvs, "rcm", &u8); + rcm = (err == ESP_OK && u8) && board_config.rcm; + if (err != ESP_OK) { + nvs_set_u8(nvs, "rcm", rcm); + needs_commit = true; + } + + // Temp threshold + err = nvs_get_u8(nvs, "temp_threshold", &u8); + temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60; + if (err != ESP_OK) { + nvs_set_u8(nvs, "temp_threshold", temp_threshold); + needs_commit = true; + } + + // Optional limits + if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK) + evse_set_consumption_limit(u32); + + if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK) + evse_set_charging_time_limit(u32); + + if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) + evse_set_under_power_limit(u16); + + // Save to NVS if needed + if (needs_commit) { + err = nvs_commit(nvs); + if (err == ESP_OK) { + ESP_LOGD(TAG, "Configuration committed to NVS."); + } else { + ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); + } + } +} + +// ======================== +// Charging current getters/setters +// ======================== +uint8_t evse_get_max_charging_current(void) { + return max_charging_current; +} + +esp_err_t evse_set_max_charging_current(uint8_t value) { + if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT) + return ESP_ERR_INVALID_ARG; + max_charging_current = value; + nvs_set_u8(nvs, "max_chrg_curr", value); + return nvs_commit(nvs); +} + +uint16_t evse_get_charging_current(void) { + return charging_current; +} + +esp_err_t evse_set_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) + return ESP_ERR_INVALID_ARG; + charging_current = value; + nvs_set_u16(nvs, "def_chrg_curr", value); + return nvs_commit(nvs); +} + +uint16_t evse_get_default_charging_current(void) { + uint16_t value; + if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK) + return value; + return charging_current; +} + +esp_err_t evse_set_default_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) + return ESP_ERR_INVALID_ARG; + nvs_set_u16(nvs, "def_chrg_curr", value); + return nvs_commit(nvs); +} + +// ======================== +// Runtime current (not saved) +// ======================== +void evse_set_runtime_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) { + ESP_LOGW(TAG, "Rejected runtime charging current (out of bounds): %d", value); + return; + } + charging_current_runtime = value; + ESP_LOGD(TAG, "Runtime charging current updated: %d", charging_current_runtime); +} + +uint16_t evse_get_runtime_charging_current(void) { + return charging_current_runtime; +} + + +// ======================== +// Socket outlet +// ======================== +bool evse_get_socket_outlet(void) { + return socket_outlet; +} + +esp_err_t evse_set_socket_outlet(bool value) { + if (value && !board_config.proximity) + return ESP_ERR_INVALID_ARG; + socket_outlet = value; + nvs_set_u8(nvs, "socket_outlet", value); + return nvs_commit(nvs); +} + +// ======================== +// RCM +// ======================== +bool evse_is_rcm(void) { + return rcm; +} + +esp_err_t evse_set_rcm(bool value) { + if (value && !board_config.rcm) + return ESP_ERR_INVALID_ARG; + rcm = value; + nvs_set_u8(nvs, "rcm", value); + return nvs_commit(nvs); +} + +// ======================== +// Temperature +// ======================== +uint8_t evse_get_temp_threshold(void) { + return temp_threshold; +} + +esp_err_t evse_set_temp_threshold(uint8_t value) { + if (value < 40 || value > 80) + return ESP_ERR_INVALID_ARG; + temp_threshold = value; + nvs_set_u8(nvs, "temp_threshold", value); + return nvs_commit(nvs); +} + +// ======================== +// Authentication +// ======================== +bool evse_is_require_auth(void) { + return require_auth; +} + +void evse_set_require_auth(bool value) { + require_auth = value; + nvs_set_u8(nvs, "require_auth", value); + nvs_commit(nvs); +} + +// ======================== +// Availability +// ======================== +static bool is_available = true; + +bool evse_config_is_available(void) { + return is_available; +} + +void evse_config_set_available(bool available) { + is_available = available; +} + +// ======================== +// Enable/Disable +// ======================== +static bool is_enabled = true; + +bool evse_config_is_enabled(void) { + return is_enabled; +} + +void evse_config_set_enabled(bool enabled) { + is_enabled = enabled; +} + +// === Fim de: components/evse/evse_config.c === + + +// === Início de: components/evse/evse_manager.c === +#include "evse_manager.h" +#include "evse_state.h" +#include "evse_error.h" +#include "evse_hardware.h" +#include "evse_config.h" +#include "evse_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include + +#include "auth_events.h" +#include "loadbalancer_events.h" +#include "esp_event.h" + +static const char *TAG = "EVSE_Manager"; + +static SemaphoreHandle_t evse_mutex; +static bool auth_enabled = false; + +#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo + +// ===== Task de ciclo principal ===== +static void evse_manager_task(void *arg) { + while (true) { + evse_manager_tick(); + vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS)); + } +} + +// ===== Tratador de eventos de autenticação ===== +static void on_auth_event(void* arg, esp_event_base_t base, int32_t id, void* data) { + if (base != AUTH_EVENTS || data == NULL) return; + + switch (id) { + case AUTH_EVENT_TAG_PROCESSED: { + auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data; + ESP_LOGI("EVSE", "Tag: %s | Autorizada: %s", evt->tag, evt->authorized ? "SIM" : "NÃO"); + evse_state_set_authorized(evt->authorized); + break; + } + + case AUTH_EVENT_ENABLED_CHANGED: + case AUTH_EVENT_INIT: { + auth_enabled_event_data_t *evt = (auth_enabled_event_data_t*)data; + auth_enabled = evt->enabled; + + ESP_LOGI("EVSE", "Auth %s (%s)", + id == AUTH_EVENT_ENABLED_CHANGED ? "ficou" : "init", + evt->enabled ? "ATIVO" : "INATIVO"); + + if (!auth_enabled) { + evse_state_set_authorized(true); + ESP_LOGI("EVSE", "Autenticação desativada → autorização forçada."); + } else { + evse_state_set_authorized(false); + ESP_LOGI("EVSE", "Autenticação ativada → aguardando autorização por tag."); + } + break; + } + } +} + +// ===== Tratador de eventos de loadbalancer ===== +static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) { + 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", evt->timestamp_us); + // Ações adicionais podem ser adicionadas aqui conforme necessário + } else if (event_id == LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED) { + const loadbalancer_charging_limit_event_t* evt = (const loadbalancer_charging_limit_event_t*) event_data; + ESP_LOGD(TAG, "Novo limite de corrente: %.1f A (ts: %lld)", evt->limit, evt->timestamp_us); + evse_set_runtime_charging_current((uint16_t)(evt->limit)); + } +} + +// ===== Inicialização ===== +void evse_manager_init(void) { + evse_mutex = xSemaphoreCreateMutex(); + + evse_config_init(); + evse_error_init(); + evse_hardware_init(); + evse_state_init(); + + 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_LOGI(TAG, "EVSE Manager inicializado."); + xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL); +} + +// ===== Main Tick ===== +void evse_manager_tick(void) { + xSemaphoreTake(evse_mutex, portMAX_DELAY); + + evse_hardware_tick(); + evse_error_tick(); + evse_state_tick(); + evse_temperature_check(); + + if (auth_enabled) { + // If the car is disconnected, revoke authorization + if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) { + ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); + evse_state_set_authorized(false); + } + } else { + // If authentication is disabled, ensure authorization is always granted + if (!evse_state_get_authorized()) { + evse_state_set_authorized(true); + ESP_LOGI(TAG, "Authentication disabled → forced authorization."); + } + } + + xSemaphoreGive(evse_mutex); +} + + +// ===== API pública ===== +bool evse_manager_is_available(void) { + return evse_config_is_available(); +} + +void evse_manager_set_available(bool available) { + evse_config_set_available(available); +} + +void evse_manager_set_authorized(bool authorized) { + evse_state_set_authorized(authorized); +} + +bool evse_manager_is_charging(void) { + return evse_state_is_charging(evse_get_state()); +} + +void evse_manager_set_enabled(bool enabled) { + evse_config_set_enabled(enabled); +} + +bool evse_manager_is_enabled(void) { + return evse_config_is_enabled(); +} + +// === Fim de: components/evse/evse_manager.c === + + +// === Início de: components/evse/evse_events.c === +#include "evse_events.h" + +ESP_EVENT_DEFINE_BASE(EVSE_EVENTS); + +// === Fim de: components/evse/evse_events.c === + + +// === Início de: components/evse/include/evse_pilot.h === +#ifndef PILOT_H_ +#define PILOT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Níveis categóricos de tensão no sinal CP (Control Pilot) + */ +typedef enum +{ + PILOT_VOLTAGE_12, ///< Estado A: +12V + PILOT_VOLTAGE_9, ///< Estado B: +9V + PILOT_VOLTAGE_6, ///< Estado C: +6V + PILOT_VOLTAGE_3, ///< Estado D: +3V + PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V +} pilot_voltage_t; + +/** + * @brief Inicializa o driver do sinal Pilot + */ +void pilot_init(void); + +/** + * @brief Define o nível do Pilot: +12V ou -12V + * + * @param level true = +12V, false = -12V + */ +void pilot_set_level(bool level); + +/** + * @brief Ativa o PWM do Pilot com corrente limitada + * + * @param amps Corrente em décimos de ampère (ex: 160 = 16A) + */ +void pilot_set_amps(uint16_t amps); + +/** + * @brief Mede o nível de tensão do Pilot e detecta -12V + * + * @param up_voltage Valor categórico da tensão positiva + * @param down_voltage_n12 true se o nível negativo atingir -12V + */ +void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12); + +/** + * @brief Retorna o estado lógico atual do Pilot (nível alto = +12V) + * + * @return true se nível atual for +12V, false se for -12V + */ +bool pilot_get_state(void); + +/** + * @brief Cache interno opcional dos níveis de tensão reais do Pilot + */ +typedef struct { + uint16_t high_mv; ///< Pico positivo medido (mV) + uint16_t low_mv; ///< Pico negativo medido (mV) +} pilot_voltage_cache_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PILOT_H_ */ + +// === Fim de: components/evse/include/evse_pilot.h === + + +// === Início de: components/evse/include/evse_manager.h === +#ifndef EVSE_MANAGER_H +#define EVSE_MANAGER_H + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/** + * @brief Inicializa os módulos internos do EVSE (hardware, estado, erros, etc.) + * e inicia a tarefa de supervisão periódica (tick). + */ +void evse_manager_init(void); + +/** + * @brief Executa uma iteração do ciclo de controle do EVSE. + * + * Esta função é chamada automaticamente pela task periódica, + * mas pode ser chamada manualmente em testes. + */ +void evse_manager_tick(void); + +/** + * @brief Verifica se o EVSE está disponível para uso. + * + * Isso considera falhas críticas, disponibilidade configurada, etc. + */ +bool evse_manager_is_available(void); + +/** + * @brief Define se o EVSE deve estar disponível (ex: via controle remoto). + */ +void evse_manager_set_available(bool available); + +/** + * @brief Define se o EVSE está autorizado a carregar (ex: após autenticação). + */ +void evse_manager_set_authorized(bool authorized); + +/** + * @brief Verifica se o EVSE está atualmente carregando. + */ +bool evse_manager_is_charging(void); + +/** + * @brief Ativa ou desativa logicamente o EVSE (controla habilitação geral). + */ +void evse_manager_set_enabled(bool enabled); + +/** + * @brief Verifica se o EVSE está ativado logicamente. + */ +bool evse_manager_is_enabled(void); + +#ifdef __cplusplus +} +#endif + + +#endif // EVSE_MANAGER_H + +// === Fim de: components/evse/include/evse_manager.h === + + +// === Início de: components/evse/include/evse_fsm.h === +#ifndef EVSE_FSM_H +#define EVSE_FSM_H + +#include +#include +#include "evse_api.h" +#include "evse_pilot.h" +#include "freertos/FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reinicia a máquina de estados do EVSE para o estado inicial (A). + */ +void evse_fsm_reset(void); + +/** + * @brief Processa uma leitura do sinal de piloto e atualiza a máquina de estados do EVSE. + * + * Esta função deve ser chamada periodicamente pelo núcleo de controle para + * avaliar mudanças no estado do conector, disponibilidade do carregador e + * autorização do usuário. + * + * @param pilot_voltage Leitura atual da tensão do sinal piloto. + * @param authorized Indica se o carregamento foi autorizado. + * @param available Indica se o carregador está disponível (ex: sem falhas). + * @param enabled Indica se o carregador está habilitado via software. + */ +void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_FSM_H + +// === Fim de: components/evse/include/evse_fsm.h === + + +// === Início de: components/evse/include/evse_hardware.h === +#ifndef EVSE_HARDWARE_H +#define EVSE_HARDWARE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Inicializa todos os periféricos de hardware do EVSE (pilot, relé, trava, etc.) + */ +void evse_hardware_init(void); + +/** + * @brief Executa atualizações periódicas no hardware (tick) + */ +void evse_hardware_tick(void); + +/** + * @brief Verifica se o sinal piloto está em nível alto (12V) + */ +bool evse_hardware_is_pilot_high(void); + +/** + * @brief Verifica se o veículo está fisicamente conectado via Proximity + */ +bool evse_hardware_is_vehicle_connected(void); + +/** + * @brief Verifica se há consumo de energia (corrente detectada) + */ +bool evse_hardware_is_energy_detected(void); + +/** + * @brief Liga o relé de fornecimento de energia + */ +void evse_hardware_relay_on(void); + +/** + * @brief Desliga o relé de fornecimento de energia + */ +void evse_hardware_relay_off(void); + +/** + * @brief Consulta o estado atual do relé + * @return true se ligado, false se desligado + */ +bool evse_hardware_relay_status(void); + +/** + * @brief Aciona a trava física do conector + */ +void evse_hardware_lock(void); + +/** + * @brief Libera a trava física do conector + */ +void evse_hardware_unlock(void); + +/** + * @brief Verifica se o conector está travado + */ +bool evse_hardware_is_locked(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_HARDWARE_H + +// === Fim de: components/evse/include/evse_hardware.h === + + +// === Início de: components/evse/include/evse_config.h === +#ifndef EVSE_CONFIG_H +#define EVSE_CONFIG_H + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ======================== +// Limites Globais (Defines) +// ======================== + +// Corrente máxima de carregamento (configurável pelo usuário) +#define MIN_CHARGING_CURRENT_LIMIT 6 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A + +// Corrente via cabo (proximity) — se configurável +#define MIN_CABLE_CURRENT_LIMIT 6 // A +#define MAX_CABLE_CURRENT_LIMIT 63 // A + +// ======================== +// Funções de Configuração +// ======================== + +// Inicialização +esp_err_t evse_config_init(void); +void evse_check_defaults(void); + +// Corrente de carregamento +uint8_t evse_get_max_charging_current(void); +esp_err_t evse_set_max_charging_current(uint8_t value); + +uint16_t evse_get_charging_current(void); +esp_err_t evse_set_charging_current(uint16_t value); + +uint16_t evse_get_default_charging_current(void); +esp_err_t evse_set_default_charging_current(uint16_t value); + +// Configuração de socket outlet +bool evse_get_socket_outlet(void); +esp_err_t evse_set_socket_outlet(bool socket_outlet); + +void evse_set_runtime_charging_current(uint16_t value); +uint16_t evse_get_runtime_charging_current(void); + + +// RCM +bool evse_is_rcm(void); +esp_err_t evse_set_rcm(bool rcm); + +// Temperatura +uint8_t evse_get_temp_threshold(void); +esp_err_t evse_set_temp_threshold(uint8_t threshold); + +// Autenticação +bool evse_is_require_auth(void); +void evse_set_require_auth(bool require); + +// Disponibilidade +bool evse_config_is_available(void); +void evse_config_set_available(bool available); + +// Ativação/desativação do EVSE +bool evse_config_is_enabled(void); +void evse_config_set_enabled(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_CONFIG_H + +// === Fim de: components/evse/include/evse_config.h === + + +// === Início de: components/evse/include/evse_state.h === +#ifndef EVSE_STATE_H +#define EVSE_STATE_H + +#include +#include "freertos/FreeRTOS.h" +#include "evse_events.h" + +// ============================ +// EVSE Pilot Signal States +// ============================ + +typedef enum { + EVSE_STATE_A, // EV Not Connected (12V) + EVSE_STATE_B1, // EV Connected (9V, Not Authorized) + EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready) + EVSE_STATE_C1, // Charging Requested (6V, Relay Off) + EVSE_STATE_C2, // Charging Active (6V, Relay On) + EVSE_STATE_D1, // Ventilation Required (3V, Relay Off) + EVSE_STATE_D2, // Ventilation Active (3V, Relay On) + EVSE_STATE_E, // Error: Pilot Short to Ground (0V) + EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable +} evse_state_t; + +// ============================ +// Initialization & Core Control +// ============================ + +/** + * @brief Initializes the EVSE state machine. + */ +void evse_state_init(void); + +/** + * @brief Periodic tick function for the state machine. + */ +void evse_state_tick(void); + +// ============================ +// State Access +// ============================ + +/** + * @brief Returns the current EVSE state. + */ +evse_state_t evse_get_state(void); + +/** + * @brief Updates the current EVSE state and triggers events. + */ +void evse_set_state(evse_state_t state); + +/** + * @brief Returns the tick count when charging session started. + */ +TickType_t evse_get_session_start(void); + +/** + * @brief Converts the state enum to a human-readable string. + */ +const char* evse_state_to_str(evse_state_t state); + +// ============================ +// State Evaluators +// ============================ + +/** + * @brief Returns true if the state represents an active session (B2, C1, C2). + */ +bool evse_state_is_session(evse_state_t state); + +/** + * @brief Returns true if the state represents active charging (C1, C2). + */ +bool evse_state_is_charging(evse_state_t state); + +/** + * @brief Returns true if the vehicle is plugged in. + */ +bool evse_state_is_plugged(evse_state_t state); + +// ============================ +// Authorization +// ============================ + +/** + * @brief Sets the vehicle authorization state. + */ +void evse_state_set_authorized(bool authorized); + +/** + * @brief Returns the current vehicle authorization state. + */ +bool evse_state_get_authorized(void); + +#endif // EVSE_STATE_H + +// === Fim de: components/evse/include/evse_state.h === + + +// === Início de: components/evse/include/evse_error.h === +#ifndef EVSE_ERROR_H +#define EVSE_ERROR_H + +#include +#include +#include "evse_pilot.h" + + +#define EVSE_ERR_AUTO_CLEAR_BITS ( \ + EVSE_ERR_DIODE_SHORT_BIT | \ + EVSE_ERR_TEMPERATURE_HIGH_BIT | \ + EVSE_ERR_RCM_TRIGGERED_BIT ) + +// 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) + +// 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); +bool evse_error_cleared_flag(void); + +#endif // EVSE_ERROR_H + +// === Fim de: components/evse/include/evse_error.h === + + +// === Início de: components/evse/include/evse_limits.h === +#ifndef EVSE_LIMITS_H +#define EVSE_LIMITS_H + +#include +#include +#include "evse_state.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ======================== +// Limit state control +// ======================== + +/** + * @brief Sets the 'limit reached' flag. Used internally when a session exceeds defined thresholds. + */ +void evse_set_limit_reached(bool value); + +/** + * @brief Returns whether any session limit has been reached (energy, time or power). + */ +bool evse_get_limit_reached(void); + +// ======================== +// Limit checking +// ======================== + +/** + * @brief Evaluates if the session has exceeded any configured limits. + * Should be called periodically while in charging state. + */ +void evse_limits_check(void); + +// ======================== +// Runtime limit configuration +// ======================== + +uint32_t evse_get_consumption_limit(void); +void evse_set_consumption_limit(uint32_t value); // in Wh + +uint32_t evse_get_charging_time_limit(void); +void evse_set_charging_time_limit(uint32_t value); // in seconds + +uint16_t evse_get_under_power_limit(void); +void evse_set_under_power_limit(uint16_t value); // in Watts + +// ======================== +// Default (persistent) limits +// ======================== + +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 === diff --git a/projeto_parte3.c b/projeto_parte3.c new file mode 100755 index 0000000..f3c1ae4 --- /dev/null +++ b/projeto_parte3.c @@ -0,0 +1,1058 @@ + + +// === Início de: components/evse/include/evse_events.h === +#ifndef EVSE_EVENTS_H +#define EVSE_EVENTS_H + +#pragma once +#include "esp_event.h" + +ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); + +typedef enum { + EVSE_EVENT_INIT, + EVSE_EVENT_STATE_CHANGED, + // Outros eventos possíveis futuramente +} evse_event_id_t; + +typedef enum { + EVSE_STATE_EVENT_IDLE, + EVSE_STATE_EVENT_WAITING, + EVSE_STATE_EVENT_CHARGING, + EVSE_STATE_EVENT_FAULT +} evse_state_event_t; + +typedef struct { + evse_state_event_t state; +} evse_state_event_data_t; + + +#endif // EVSE_EVENTS_H + +// === Fim de: components/evse/include/evse_events.h === + + +// === Início de: components/evse/include/evse_api.h === +#ifndef EVSE_API_H +#define EVSE_API_H + +#include +#include +#include "esp_err.h" +#include "evse_state.h" // Define evse_state_t + +// Inicialização +void evse_init(void); +void evse_process(void); + +// Estado +evse_state_t evse_get_state(void); +const char* evse_state_to_str(evse_state_t state); +bool evse_is_connector_plugged(evse_state_t state); +bool evse_is_limit_reached(void); + +// Autorização e disponibilidade +bool evse_is_enabled(void); +void evse_set_enabled(bool value); +bool evse_is_available(void); +void evse_set_available(bool value); +bool evse_is_require_auth(void); +void evse_set_require_auth(bool value); + +// Corrente +uint16_t evse_get_charging_current(void); +esp_err_t evse_set_charging_current(uint16_t value); +uint16_t evse_get_default_charging_current(void); +esp_err_t evse_set_default_charging_current(uint16_t value); +uint8_t evse_get_max_charging_current(void); +esp_err_t evse_set_max_charging_current(uint8_t value); + +// Temperatura +uint8_t evse_get_temp_threshold(void); +esp_err_t evse_set_temp_threshold(uint8_t value); + +// RCM / Socket +bool evse_get_socket_outlet(void); +esp_err_t evse_set_socket_outlet(bool value); +bool evse_is_rcm(void); +esp_err_t evse_set_rcm(bool value); + +// Limites +uint32_t evse_get_consumption_limit(void); +void evse_set_consumption_limit(uint32_t value); +uint32_t evse_get_charging_time_limit(void); +void evse_set_charging_time_limit(uint32_t value); +uint16_t evse_get_under_power_limit(void); +void evse_set_under_power_limit(uint16_t value); + +void evse_set_limit_reached(bool value); + +// Energia total acumulada da sessão (em Wh) +uint32_t evse_get_total_energy(void); + +// Potência instantânea medida (em W) +uint32_t evse_get_instant_power(void); + +// Limites default +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); + + +uint32_t evse_get_total_energy(void); +uint32_t evse_get_instant_power(void); + + + +#endif // EVSE_API_H + +// === Fim de: components/evse/include/evse_api.h === + + +// === Início de: components/loadbalancer/src/loadbalancer_events.c === +#include "loadbalancer_events.h" + +// Define a base de eventos para o loadbalancer +ESP_EVENT_DEFINE_BASE(LOADBALANCER_EVENTS); + +// === Fim de: components/loadbalancer/src/loadbalancer_events.c === + + +// === Início de: components/loadbalancer/src/loadbalancer.c === +#include "loadbalancer.h" +#include "loadbalancer_events.h" +#include "esp_event.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "input_filter.h" +#include "nvs_flash.h" +#include "nvs.h" +#include +#include "meter_events.h" +#include "evse_events.h" + + + +static const char *TAG = "loadbalancer"; + +// Limites configuráveis +#define MIN_CHARGING_CURRENT_LIMIT 6 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A +#define MIN_GRID_CURRENT_LIMIT 6 // A +#define MAX_GRID_CURRENT_LIMIT 100 // A + +// Parâmetros +static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; +static bool loadbalancer_enabled = false; + +static float grid_current = 0.0f; +static float evse_current = 0.0f; +static input_filter_t grid_filter; +static input_filter_t evse_filter; + +#define NVS_NAMESPACE "loadbalancing" +#define NVS_MAX_GRID_CURRENT "max_grid_curr" +#define NVS_LOADBALANCER_ENABLED "enabled" + +static void loadbalancer_meter_event_handler(void *handler_arg, + esp_event_base_t base, + int32_t id, + void *event_data) +{ + if (id != METER_EVENT_DATA_READY || event_data == NULL) + return; + + const meter_event_data_t *evt = (const meter_event_data_t *)event_data; + + ESP_LOGI(TAG, "Received meter event from source: %s", evt->source); + ESP_LOGI(TAG, "Raw IRMS: [%.2f, %.2f, %.2f] A", evt->irms[0], evt->irms[1], evt->irms[2]); + ESP_LOGI(TAG, "Raw VRMS: [%.1f, %.1f, %.1f] V", evt->vrms[0], evt->vrms[1], evt->vrms[2]); + ESP_LOGI(TAG, "Raw Power: [W1=%d, W2=%d, W3=%d]", evt->watt[0], evt->watt[1], evt->watt[2]); + ESP_LOGI(TAG, "Freq: %.2f Hz | PF: %.2f | Energy: %.3f kWh", + evt->frequency, evt->power_factor, evt->total_energy); + + // Calcula a corrente máxima entre as 3 fases + float max_irms = evt->irms[0]; + for (int i = 1; i < 3; ++i) + { + if (evt->irms[i] > max_irms) + { + max_irms = evt->irms[i]; + } + } + + ESP_LOGI(TAG, "Max IRMS detected: %.2f A", max_irms); + + // Atualiza com filtro exponencial dependendo da origem + if (strncmp(evt->source, "GRID", 4) == 0) + { + grid_current = input_filter_update(&grid_filter, max_irms); + ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current); + } + else if (strncmp(evt->source, "EVSE", 4) == 0) + { + evse_current = input_filter_update(&evse_filter, max_irms); + ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current); + } + else + { + ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source); + } +} + +static void loadbalancer_evse_event_handler(void *handler_arg, + esp_event_base_t base, + int32_t id, + void *event_data) +{ + const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data; + + ESP_LOGI(TAG, "EVSE state changed: %d", evt->state); + + switch (evt->state) + { + case EVSE_STATE_EVENT_IDLE: + // Vehicle is disconnected - current flow can be reduced or reset + ESP_LOGI(TAG, "EVSE is IDLE - possible to release current"); + break; + + case EVSE_STATE_EVENT_WAITING: + // EV is connected but not charging yet (e.g., waiting for authorization) + ESP_LOGI(TAG, "EVSE is waiting - connected but not charging"); + break; + + case EVSE_STATE_EVENT_CHARGING: + grid_current = 0.0f; + evse_current = 0.0f; + // Charging has started - maintain or monitor current usage + ESP_LOGI(TAG, "EVSE is charging"); + break; + + case EVSE_STATE_EVENT_FAULT: + // A fault has occurred - safety measures may be needed + ESP_LOGW(TAG, "EVSE is in FAULT - temporarily disabling load balancing"); + // Optional: disable load balancing during fault condition + // loadbalancer_set_enabled(false); + break; + + default: + ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state); + break; + } +} + +// Carrega configuração do NVS +static esp_err_t loadbalancer_load_config() +{ + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS for load/init: %s", esp_err_to_name(err)); + return err; + } + + bool needs_commit = false; + uint8_t temp_u8; + + // max_grid_current + err = nvs_get_u8(handle, NVS_MAX_GRID_CURRENT, &temp_u8); + if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT) + { + max_grid_current = temp_u8; + } + else + { + max_grid_current = MAX_GRID_CURRENT_LIMIT; + nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, max_grid_current); + ESP_LOGW(TAG, "max_grid_current missing or invalid, setting default: %d", max_grid_current); + needs_commit = true; + } + + // loadbalancer_enabled + err = nvs_get_u8(handle, NVS_LOADBALANCER_ENABLED, &temp_u8); + if (err == ESP_OK && temp_u8 <= 1) + { + loadbalancer_enabled = (temp_u8 != 0); + } + else + { + loadbalancer_enabled = false; + nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, 0); + ESP_LOGW(TAG, "loadbalancer_enabled missing or invalid, setting default: 0"); + needs_commit = true; + } + + if (needs_commit) + { + nvs_commit(handle); + } + + nvs_close(handle); + return ESP_OK; +} + +// Salva o estado habilitado no NVS +void loadbalancer_set_enabled(bool enabled) +{ + ESP_LOGI(TAG, "Setting load balancing enabled to %d", enabled); + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err)); + return; + } + + err = nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, enabled ? 1 : 0); + if (err == ESP_OK) + { + nvs_commit(handle); + loadbalancer_enabled = enabled; + ESP_LOGI(TAG, "Load balancing enabled state saved"); + + loadbalancer_state_event_t evt = { + .enabled = enabled, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_STATE_CHANGED, + &evt, + sizeof(evt), + portMAX_DELAY); + } + else + { + ESP_LOGE(TAG, "Failed to save loadbalancer_enabled"); + } + + nvs_close(handle); +} + +// Define e salva o limite de corrente da rede +esp_err_t load_balancing_set_max_grid_current(uint8_t value) +{ + if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT) + { + ESP_LOGE(TAG, "Invalid grid current limit: %d", value); + return ESP_ERR_INVALID_ARG; + } + + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err)); + return err; + } + + err = nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, value); + if (err == ESP_OK) + { + nvs_commit(handle); + max_grid_current = value; + ESP_LOGI(TAG, "max_grid_current set to: %d", value); + } + else + { + ESP_LOGE(TAG, "Failed to save max_grid_current to NVS"); + } + + nvs_close(handle); + return err; +} + +uint8_t load_balancing_get_max_grid_current(void) +{ + return max_grid_current; +} + +bool loadbalancer_is_enabled(void) +{ + return loadbalancer_enabled; +} + +// Tarefa principal com eventos +void loadbalancer_task(void *param) +{ + while (true) + { + if (!loadbalancer_enabled) + { + vTaskDelay(pdMS_TO_TICKS(1000)); + continue; + } + + float available = max_grid_current - grid_current + evse_current; + + if (available < MIN_CHARGING_CURRENT_LIMIT) + { + available = MIN_CHARGING_CURRENT_LIMIT; + } + else if (available > max_grid_current) + { + available = max_grid_current; + } + + ESP_LOGD(TAG, "Setting EVSE current limit: %.1f A", available); + + loadbalancer_charging_limit_event_t evt = { + .limit = available, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED, + &evt, + sizeof(evt), + portMAX_DELAY); + + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + +void loadbalancer_init(void) +{ + ESP_LOGI(TAG, "Initializing load balancer"); + + if (loadbalancer_load_config() != ESP_OK) + { + ESP_LOGW(TAG, "Failed to load/init config. Using in-memory defaults."); + } + + input_filter_init(&grid_filter, 0.3f); + input_filter_init(&evse_filter, 0.3f); + + if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS) + { + ESP_LOGE(TAG, "Failed to create loadbalancer task"); + } + + loadbalancer_state_event_t evt = { + .enabled = loadbalancer_enabled, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_INIT, + &evt, + sizeof(evt), + portMAX_DELAY); + + ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, + &loadbalancer_meter_event_handler, NULL)); + + ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, + EVSE_EVENT_STATE_CHANGED, + &loadbalancer_evse_event_handler, + NULL)); +} + +// === Fim de: components/loadbalancer/src/loadbalancer.c === + + +// === Início de: components/loadbalancer/src/input_filter.c === +#include "input_filter.h" + +void input_filter_init(input_filter_t *filter, float alpha) { + if (filter) { + filter->alpha = alpha; + filter->value = 0.0f; + filter->initialized = 0; + } +} + +float input_filter_update(input_filter_t *filter, float input) { + if (!filter) return input; + + if (!filter->initialized) { + filter->value = input; + filter->initialized = 1; + } else { + filter->value = filter->alpha * input + (1.0f - filter->alpha) * filter->value; + } + + return filter->value; +} + +// === Fim de: components/loadbalancer/src/input_filter.c === + + +// === Início de: components/loadbalancer/include/loadbalancer_events.h === +#pragma once +#include "esp_event.h" +#include +#include +#include "esp_timer.h" + +ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS); + +typedef enum { + LOADBALANCER_EVENT_INIT, + LOADBALANCER_EVENT_STATE_CHANGED, + LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED +} loadbalancer_event_id_t; + +typedef struct { + float limit; + int64_t timestamp_us; +} loadbalancer_charging_limit_event_t; + +typedef struct { + bool enabled; + int64_t timestamp_us; +} loadbalancer_state_event_t; + + +// === Fim de: components/loadbalancer/include/loadbalancer_events.h === + + +// === Início de: components/loadbalancer/include/loadbalancer.h === +#ifndef LOADBALANCER_H_ +#define LOADBALANCER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "esp_err.h" + + +/** + * @brief Initializes the load balancer. + * + * This function configures the load balancer and its resources, including + * any necessary persistence configurations, such as storage in NVS (Non-Volatile Storage). + * This function prepares the system to perform load balancing efficiently. + */ +void loadbalancer_init(void); + +/** + * @brief Continuous task for the load balancer. + * + * This function executes the load balancing logic continuously, typically in a FreeRTOS task. + * It performs balance calculations, checks the grid current and energy conditions, and adjusts + * the outputs as necessary to ensure efficient energy consumption. + * + * @param param Input parameter, usually used to pass additional information or relevant context + * for the task execution. + */ +void loadbalancer_task(void *param); + +/** + * @brief Enables or disables the load balancing system. + * + * This function allows enabling or disabling the load balancing system. When enabled, the load + * balancer starts managing the grid current based on the configured limits. If disabled, the system + * operates without balancing. + * + * The configuration is persisted in NVS, ensuring that the choice is maintained across system restarts. + * + * @param value If true, enables load balancing. If false, disables it. + */ +void loadbalancer_set_enabled(bool value); + +/** + * @brief Checks if load balancing is enabled. + * + * This function returns the current status of the load balancing system. + * + * @return Returns true if load balancing is enabled, otherwise returns false. + */ +bool loadbalancer_is_enabled(void); + +/** + * @brief Sets the maximum grid current. + * + * This function configures the maximum grid current that can be supplied to the load balancing system. + * The value set ensures that the system does not overload the electrical infrastructure and respects + * the safety limits. + * + * @param max_grid_current The maximum allowed current (in amperes) for the load balancing system. + * This value should be appropriate for the grid capacity and the installation. + */ +esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current); + + +/** + * @brief Gets the maximum grid current. + * + * This function retrieves the current maximum grid current limit. + * + * @return The maximum grid current (in amperes). + */ +uint8_t load_balancing_get_max_grid_current(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LOADBALANCER_H_ */ + +// === Fim de: components/loadbalancer/include/loadbalancer.h === + + +// === Início de: components/loadbalancer/include/input_filter.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float alpha; ///< Fator de suavização (0.0 a 1.0) + float value; ///< Último valor filtrado + int initialized; ///< Flag de inicialização +} input_filter_t; + +/** + * @brief Inicializa o filtro com o fator alpha desejado. + * @param filter Ponteiro para a estrutura do filtro + * @param alpha Valor entre 0.0 (mais lento) e 1.0 (sem filtro) + */ +void input_filter_init(input_filter_t *filter, float alpha); + +/** + * @brief Atualiza o valor filtrado com uma nova entrada. + * @param filter Ponteiro para o filtro + * @param input Valor bruto + * @return Valor suavizado + */ +float input_filter_update(input_filter_t *filter, float input); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/loadbalancer/include/input_filter.h === + + +// === Início de: components/auth/src/auth_events.c === +#include "auth_events.h" + +ESP_EVENT_DEFINE_BASE(AUTH_EVENTS); + +// === Fim de: components/auth/src/auth_events.c === + + +// === Início de: components/auth/src/wiegand.c === +/** + * @file wiegand.c + * + * ESP-IDF Wiegand protocol receiver + */ +#include +#include +#include +#include +#include "wiegand.h" + +static const char *TAG = "wiegand"; + +#define TIMER_INTERVAL_US 50000 // 50ms + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0) + +static void isr_disable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_DISABLE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_DISABLE); +} + +static void isr_enable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_NEGEDGE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE); +} + +#if HELPER_TARGET_IS_ESP32 +static void IRAM_ATTR isr_handler(void *arg) +#else +static void isr_handler(void *arg) +#endif +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + if (!reader->enabled) + return; + + int d0 = gpio_get_level(reader->gpio_d0); + int d1 = gpio_get_level(reader->gpio_d1); + + // ignore equal + if (d0 == d1) + return; + // overflow + if (reader->bits >= reader->size * 8) + return; + + esp_timer_stop(reader->timer); + + uint8_t value; + if (reader->bit_order == WIEGAND_MSB_FIRST) + value = (d0 ? 0x80 : 0) >> (reader->bits % 8); + else + value = (d0 ? 1 : 0) << (reader->bits % 8); + + if (reader->byte_order == WIEGAND_MSB_FIRST) + reader->buf[reader->size - reader->bits / 8 - 1] |= value; + else + reader->buf[reader->bits / 8] |= value; + + reader->bits++; + + esp_timer_start_once(reader->timer, TIMER_INTERVAL_US); +} + +static void timer_handler(void *arg) +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + + ESP_LOGI(TAG, "Got %d bits of data", reader->bits); + + wiegand_reader_disable(reader); + + if (reader->callback) + reader->callback(reader); + + wiegand_reader_enable(reader); + + isr_enable(reader); +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order) +{ + CHECK_ARG(reader && buf_size && callback); + + /* + esp_err_t res = gpio_install_isr_service(0); + if (res != ESP_OK && res != ESP_ERR_INVALID_STATE) + return res; + */ + + memset(reader, 0, sizeof(wiegand_reader_t)); + reader->gpio_d0 = gpio_d0; + reader->gpio_d1 = gpio_d1; + reader->size = buf_size; + reader->buf = calloc(buf_size, 1); + reader->bit_order = bit_order; + reader->byte_order = byte_order; + reader->callback = callback; + + esp_timer_create_args_t timer_args = { + .name = TAG, + .arg = reader, + .callback = timer_handler, + .dispatch_method = ESP_TIMER_TASK}; + CHECK(esp_timer_create(&timer_args, &reader->timer)); + + CHECK(gpio_set_direction(gpio_d0, GPIO_MODE_INPUT)); + CHECK(gpio_set_direction(gpio_d1, GPIO_MODE_INPUT)); + CHECK(gpio_set_pull_mode(gpio_d0, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + CHECK(gpio_set_pull_mode(gpio_d1, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + isr_disable(reader); + CHECK(gpio_isr_handler_add(gpio_d0, isr_handler, reader)); + CHECK(gpio_isr_handler_add(gpio_d1, isr_handler, reader)); + isr_enable(reader); + reader->enabled = true; + + ESP_LOGI(TAG, "Reader initialized on D0=%d, D1=%d", gpio_d0, gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + isr_disable(reader); + esp_timer_stop(reader->timer); + reader->enabled = false; + + ESP_LOGI(TAG, "Reader on D0=%d, D1=%d disabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + reader->bits = 0; + memset(reader->buf, 0, reader->size); + + isr_enable(reader); + reader->enabled = true; + + ESP_LOGI(TAG, "Reader on D0=%d, D1=%d enabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_done(wiegand_reader_t *reader) +{ + CHECK_ARG(reader && reader->buf); + + isr_disable(reader); + CHECK(gpio_isr_handler_remove(reader->gpio_d0)); + CHECK(gpio_isr_handler_remove(reader->gpio_d1)); + esp_timer_stop(reader->timer); + CHECK(esp_timer_delete(reader->timer)); + free(reader->buf); + + ESP_LOGI(TAG, "Reader removed"); + + return ESP_OK; +} + +// === Fim de: components/auth/src/wiegand.c === + + +// === Início de: components/auth/src/auth.c === +/* + * auth.c + */ + +#include "auth.h" +#include "auth_events.h" +#include "esp_event.h" +#include +#include +#include +#include +#include +#include "wiegand_reader.h" +#include "nvs_flash.h" +#include "nvs.h" + +#define MAX_TAGS 50 + +static const char *TAG = "Auth"; + +static bool enabled = false; +static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN]; +static int tag_count = 0; + +// =========================== +// Persistência em NVS +// =========================== + +static void load_auth_config(void) { + nvs_handle_t handle; + esp_err_t err = nvs_open("auth", NVS_READONLY, &handle); + if (err == ESP_OK) { + uint8_t val; + if (nvs_get_u8(handle, "enabled", &val) == ESP_OK) { + enabled = val; + ESP_LOGI(TAG, "Loaded auth enabled = %d", enabled); + } + nvs_close(handle); + } else { + ESP_LOGW(TAG, "No stored auth config found. Using default."); + } +} + +static void save_auth_config(void) { + nvs_handle_t handle; + if (nvs_open("auth", NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_u8(handle, "enabled", enabled); + nvs_commit(handle); + nvs_close(handle); + ESP_LOGI(TAG, "Auth config saved: enabled = %d", enabled); + } else { + ESP_LOGE(TAG, "Failed to save auth config."); + } +} + +// =========================== +// Internos +// =========================== + +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 true; + //TODO + //return false; +} + +// =========================== +// API pública +// =========================== + +void auth_set_enabled(bool value) { + enabled = value; + save_auth_config(); + ESP_LOGI(TAG, "Auth %s", enabled ? "ENABLED" : "DISABLED"); + + auth_enabled_event_data_t event = { .enabled = enabled }; + esp_event_post(AUTH_EVENTS, AUTH_EVENT_ENABLED_CHANGED, &event, sizeof(event), portMAX_DELAY); +} + +bool auth_is_enabled(void) { + return enabled; +} + +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; + + strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1); + valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; + tag_count++; + ESP_LOGI(TAG, "Tag added: %s", tag); + return true; +} + +bool auth_remove_tag(const char *tag) { + for (int i = 0; i < tag_count; i++) { + if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { + for (int j = i; j < tag_count - 1; j++) { + strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN); + } + tag_count--; + ESP_LOGI(TAG, "Tag removed: %s", tag); + return true; + } + } + return false; +} + +bool auth_tag_exists(const char *tag) { + return is_tag_valid(tag); +} + +void auth_list_tags(void) { + ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); + for (int i = 0; i < tag_count; i++) { + ESP_LOGI(TAG, "- %s", valid_tags[i]); + } +} + +void auth_init(void) { + load_auth_config(); // carrega estado de ativação + + if (enabled) { + initWiegand(); // só inicia se estiver habilitado + ESP_LOGI(TAG, "Wiegand reader initialized (Auth enabled)"); + } else { + ESP_LOGI(TAG, "Auth disabled, Wiegand reader not started"); + } + + auth_enabled_event_data_t evt = { .enabled = enabled }; + esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); + + ESP_LOGI(TAG, "Estado inicial AUTH enviado (enabled = %d)", enabled); +} + +void auth_process_tag(const char *tag) { + if (!tag || !auth_is_enabled()) { + ESP_LOGW(TAG, "Auth disabled or NULL tag received."); + return; + } + + auth_tag_event_data_t event; + strncpy(event.tag, tag, AUTH_EVENT_TAG_MAX_LEN - 1); + event.tag[AUTH_EVENT_TAG_MAX_LEN - 1] = '\0'; + event.authorized = is_tag_valid(tag); + + ESP_LOGI(TAG, "Tag %s: %s", tag, event.authorized ? "AUTHORIZED" : "DENIED"); + + esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, &event, sizeof(event), portMAX_DELAY); +} + +// === Fim de: components/auth/src/auth.c === + + +// === Início de: components/auth/src/wiegand_reader.c === +#include +#include +#include +#include +#include +#include +#include +#include "auth.h" + +#define CONFIG_EXAMPLE_BUF_SIZE 50 + +static const char *TAG = "WiegandReader"; + +static wiegand_reader_t reader; +static QueueHandle_t queue = NULL; + +typedef struct { + uint8_t data[CONFIG_EXAMPLE_BUF_SIZE]; + size_t bits; +} data_packet_t; + +static void reader_callback(wiegand_reader_t *r) { + data_packet_t p; + p.bits = r->bits; + memcpy(p.data, r->buf, CONFIG_EXAMPLE_BUF_SIZE); + xQueueSendToBack(queue, &p, 0); +} + +static void wiegand_task(void *arg) { + queue = xQueueCreate(5, sizeof(data_packet_t)); + if (!queue) { + ESP_LOGE(TAG, "Failed to create queue"); + vTaskDelete(NULL); + return; + } + + ESP_ERROR_CHECK(wiegand_reader_init(&reader, 19, 18, + true, CONFIG_EXAMPLE_BUF_SIZE, reader_callback, WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST)); + + data_packet_t p; + while (1) { + ESP_LOGI(TAG, "Waiting for Wiegand data..."); + if (xQueueReceive(queue, &p, portMAX_DELAY) == pdPASS) { + ESP_LOGI(TAG, "Bits received: %d", p.bits); + + char tag[20] = {0}; + + if (p.bits == 26) { + snprintf(tag, sizeof(tag), "%03d%03d%03d", p.data[0], p.data[1], p.data[2]); + } else if (p.bits == 34) { + snprintf(tag, sizeof(tag), "%03d%03d%03d%03d", p.data[0], p.data[1], p.data[2], p.data[3]); + } else { + ESP_LOGW(TAG, "Unsupported bit length: %d", (int)p.bits); + continue; + } + + ESP_LOGI(TAG, "Tag read: %s", tag); + auth_process_tag(tag); // agora delega toda a lógica à auth.c + } + } +} + +void initWiegand(void) { + ESP_LOGI(TAG, "Initializing Wiegand reader"); + xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL); +} + +// === Fim de: components/auth/src/wiegand_reader.c === diff --git a/projeto_parte4.c b/projeto_parte4.c new file mode 100755 index 0000000..f67b378 --- /dev/null +++ b/projeto_parte4.c @@ -0,0 +1,896 @@ + + +// === Início de: components/auth/include/auth.h === +#ifndef AUTH_H +#define AUTH_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Tamanho máximo de uma tag RFID (incluindo '\0') +#define AUTH_TAG_MAX_LEN 20 + +/// Estrutura de evento emitida após leitura de uma tag +typedef struct { + char tag[AUTH_TAG_MAX_LEN]; ///< Tag lida + bool authorized; ///< true se a tag for reconhecida como válida +} auth_event_t; + +/** + * @brief Inicializa o sistema de autenticação. + * + * - Carrega a configuração (enabled) da NVS + * - Inicia o leitor Wiegand + * - Emite evento AUTH_EVENT_INIT com estado atual + */ +void auth_init(void); + +/** + * @brief Ativa ou desativa o uso de autenticação via RFID. + * + * Esta configuração é persistida em NVS. Se desativado, o sistema + * considerará todas as autorizações como aceitas. + * + * @param value true para ativar, false para desativar + */ +void auth_set_enabled(bool value); + +/** + * @brief Verifica se o sistema de autenticação está habilitado. + */ +bool auth_is_enabled(void); + +/** + * @brief Adiciona uma nova tag RFID à lista de autorizadas. + * + * @param tag String da tag (máx AUTH_TAG_MAX_LEN-1) + * @return true se a tag foi adicionada, false se já existia ou inválida + */ +bool auth_add_tag(const char *tag); + +/** + * @brief Remove uma tag previamente cadastrada. + * + * @param tag String da tag + * @return true se foi removida, false se não encontrada + */ +bool auth_remove_tag(const char *tag); + +/** + * @brief Verifica se uma tag já está registrada como válida. + */ +bool auth_tag_exists(const char *tag); + +/** + * @brief Lista todas as tags válidas atualmente registradas (via logs). + */ +void auth_list_tags(void); + +/** + * @brief Processa uma tag RFID lida (chamada normalmente pelo leitor). + * + * - Verifica validade + * - Emite evento AUTH_EVENT_TAG_PROCESSED + * - Inicia timer de expiração se autorizada + */ +void auth_process_tag(const char *tag); + + +#ifdef __cplusplus +} +#endif + +#endif // AUTH_H + +// === Fim de: components/auth/include/auth.h === + + +// === Início de: components/auth/include/auth_events.h === +#pragma once +#include "esp_event.h" + +#define AUTH_EVENT_TAG_MAX_LEN 32 + +ESP_EVENT_DECLARE_BASE(AUTH_EVENTS); + +typedef enum { + AUTH_EVENT_TAG_PROCESSED, + AUTH_EVENT_ENABLED_CHANGED, + AUTH_EVENT_INIT, +} auth_event_id_t; + +typedef struct { + char tag[AUTH_EVENT_TAG_MAX_LEN]; + bool authorized; +} auth_tag_event_data_t; + +typedef struct { + bool enabled; +} auth_enabled_event_data_t; + +// === Fim de: components/auth/include/auth_events.h === + + +// === Início de: components/auth/include/wiegand.h === +/* + * Copyright (c) 2021 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file wiegand.h + * @defgroup wiegand wiegand + * @{ + * + * ESP-IDF Wiegand protocol receiver + * + * Copyright (c) 2021 Ruslan V. Uss + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __WIEGAND_H__ +#define __WIEGAND_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wiegand_reader wiegand_reader_t; + +typedef void (*wiegand_callback_t)(wiegand_reader_t *reader); + +/** + * Bit and byte order of data + */ +typedef enum { + WIEGAND_MSB_FIRST = 0, + WIEGAND_LSB_FIRST +} wiegand_order_t; + +/** + * Wiegand reader descriptor + */ +struct wiegand_reader +{ + gpio_num_t gpio_d0, gpio_d1; + wiegand_callback_t callback; + wiegand_order_t bit_order; + wiegand_order_t byte_order; + + uint8_t *buf; + size_t size; + size_t bits; + esp_timer_handle_t timer; + bool start_parity; + bool enabled; +}; + +/** + * @brief Create and initialize reader instance. + * + * @param reader Reader descriptor + * @param gpio_d0 GPIO pin for D0 + * @param gpio_d1 GPIO pin for D0 + * @param internal_pullups Enable internal pull-up resistors for D0 and D1 GPIO + * @param buf_size Reader buffer size in bytes, must be large enough to + * contain entire Wiegand key + * @param callback Callback function for processing received codes + * @param bit_order Bit order of data + * @param byte_order Byte order of data + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order); + +/** + * @brief Disable reader + * + * While reader is disabled, it will not receive new data + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader); + +/** + * @brief Enable reader + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader); + +/** + * @brief Delete reader instance. + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_done(wiegand_reader_t *reader); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __WIEGAND_H__ */ + +// === Fim de: components/auth/include/wiegand.h === + + +// === Início de: components/auth/include/wiegand_reader.h === +#ifndef WIEGAND_READER_H +#define WIEGAND_READER_H + +#ifdef __cplusplus +extern "C" { +#endif + +void initWiegand(void); + +#ifdef __cplusplus +} +#endif + +#endif // WIEGAND_READER_H + +// === Fim de: components/auth/include/wiegand_reader.h === + + +// === Início de: components/rest_api/src/ocpp_api.c === +// ========================= +// ocpp_api.c +// ========================= +#include "ocpp_api.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "ocpp_api"; + +static struct { + char url[256]; + char chargeBoxId[128]; + char certificate[256]; + char privateKey[256]; +} ocpp_config = {"", "", "", ""}; + +static esp_err_t ocpp_get_status_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *status = cJSON_CreateObject(); + cJSON_AddStringToObject(status, "status", "connected"); + char *str = cJSON_Print(status); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(status); + return ESP_OK; +} + +static esp_err_t ocpp_get_config_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "url", ocpp_config.url); + cJSON_AddStringToObject(json, "chargeBoxId", ocpp_config.chargeBoxId); + cJSON_AddStringToObject(json, "certificate", ocpp_config.certificate); + cJSON_AddStringToObject(json, "privateKey", ocpp_config.privateKey); + char *str = cJSON_Print(json); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(json); + return ESP_OK; +} + +static esp_err_t ocpp_post_config_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + cJSON *url = cJSON_GetObjectItem(json, "url"); + if (url) strlcpy(ocpp_config.url, url->valuestring, sizeof(ocpp_config.url)); + cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId"); + if (id) strlcpy(ocpp_config.chargeBoxId, id->valuestring, sizeof(ocpp_config.chargeBoxId)); + cJSON *cert = cJSON_GetObjectItem(json, "certificate"); + if (cert) strlcpy(ocpp_config.certificate, cert->valuestring, sizeof(ocpp_config.certificate)); + cJSON *key = cJSON_GetObjectItem(json, "privateKey"); + if (key) strlcpy(ocpp_config.privateKey, key->valuestring, sizeof(ocpp_config.privateKey)); + cJSON_Delete(json); + httpd_resp_sendstr(req, "OCPP config atualizada com sucesso"); + return ESP_OK; +} + +void register_ocpp_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t status_uri = { + .uri = "/api/v1/ocpp", + .method = HTTP_GET, + .handler = ocpp_get_status_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &status_uri); + + httpd_uri_t get_uri = { + .uri = "/api/v1/config/ocpp", + .method = HTTP_GET, + .handler = ocpp_get_config_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + httpd_uri_t post_uri = { + .uri = "/api/v1/config/ocpp", + .method = HTTP_POST, + .handler = ocpp_post_config_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/ocpp_api.c === + + +// === Início de: components/rest_api/src/static_file_api.c === +#include "static_file_api.h" +#include "esp_log.h" +#include +#include +#include "esp_vfs.h" + +static const char *TAG = "static_file_api"; + +#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128) +#define SCRATCH_BUFSIZE (10240) + +typedef struct rest_server_context { + char base_path[ESP_VFS_PATH_MAX + 1]; + char scratch[SCRATCH_BUFSIZE]; +} rest_server_context_t; + +#define CHECK_FILE_EXTENSION(filename, ext) \ + (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0) + +static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) { + const char *type = "text/plain"; + if (CHECK_FILE_EXTENSION(filepath, ".html")) type = "text/html"; + else if (CHECK_FILE_EXTENSION(filepath, ".js")) type = "application/javascript"; + else if (CHECK_FILE_EXTENSION(filepath, ".css")) type = "text/css"; + else if (CHECK_FILE_EXTENSION(filepath, ".png")) type = "image/png"; + else if (CHECK_FILE_EXTENSION(filepath, ".ico")) type = "image/x-icon"; + else if (CHECK_FILE_EXTENSION(filepath, ".svg")) type = "image/svg+xml"; + return httpd_resp_set_type(req, type); +} + +static esp_err_t static_get_handler(httpd_req_t *req) { + char filepath[FILE_PATH_MAX]; + rest_server_context_t *ctx = (rest_server_context_t *) req->user_ctx; + + strlcpy(filepath, ctx->base_path, sizeof(filepath)); + if (req->uri[strlen(req->uri) - 1] == '/') { + strlcat(filepath, "/index.html", sizeof(filepath)); + } else { + strlcat(filepath, req->uri, sizeof(filepath)); + } + + int fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + // fallback para /index.html (SPA) + ESP_LOGW(TAG, "Arquivo não encontrado: %s. Tentando index.html", filepath); + strlcpy(filepath, ctx->base_path, sizeof(filepath)); + strlcat(filepath, "/index.html", sizeof(filepath)); + fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Arquivo não encontrado"); + return ESP_FAIL; + } + } + + set_content_type_from_file(req, filepath); + + char *chunk = ctx->scratch; + ssize_t read_bytes; + do { + read_bytes = read(fd, chunk, SCRATCH_BUFSIZE); + if (read_bytes == -1) { + ESP_LOGE(TAG, "Erro lendo arquivo: %s", filepath); + close(fd); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao ler arquivo"); + return ESP_FAIL; + } else if (read_bytes > 0) { + if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) { + close(fd); + httpd_resp_sendstr_chunk(req, NULL); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao enviar arquivo"); + return ESP_FAIL; + } + } + } while (read_bytes > 0); + + close(fd); + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + +void register_static_file_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t uri = { + .uri = "/*", + .method = HTTP_GET, + .handler = static_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &uri); +} + +// === Fim de: components/rest_api/src/static_file_api.c === + + +// === Início de: components/rest_api/src/meters_settings_api.c === +#include "meters_settings_api.h" +#include "meter_manager.h" // Atualizado para usar o novo manager +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "meters_settings_api"; + +// Função para recuperar as configurações dos contadores +static esp_err_t meters_config_get_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters"); + + httpd_resp_set_type(req, "application/json"); + + cJSON *config = cJSON_CreateObject(); + + // Recuperando as configurações dos contadores + meter_type_t gridmeterType = meter_manager_grid_get_model(); + meter_type_t evsemeterType = meter_manager_evse_get_model(); + + ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType)); + ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType)); + + // Adicionando os tipos de contadores ao objeto JSON + cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType)); + cJSON_AddStringToObject(config, "evsemeter", meter_type_to_str(evsemeterType)); + + // Convertendo o objeto JSON para uma string + const char *json_str = cJSON_Print(config); + ESP_LOGI(TAG, "Returning meters config: %s", json_str); + + httpd_resp_sendstr(req, json_str); + + // Liberação da memória + free((void *)json_str); + cJSON_Delete(config); + + return ESP_OK; +} + +// Função para atualizar as configurações dos contadores +static esp_err_t meters_config_post_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + + if (len <= 0) { + ESP_LOGE(TAG, "Received empty body in POST request"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + + buf[len] = '\0'; // Garantir que a string está terminada + + ESP_LOGI(TAG, "Received POST data: %s", buf); + + cJSON *json = cJSON_Parse(buf); + if (!json) { + ESP_LOGE(TAG, "Failed to parse JSON data"); + // Resposta detalhada de erro + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format"); + return ESP_FAIL; + } + + // Atualizando os contadores + cJSON *gridmeter = cJSON_GetObjectItem(json, "gridmeter"); + if (gridmeter) { + meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type + ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring); + meter_manager_grid_set_model(gridType); + } + + cJSON *evsemeter = cJSON_GetObjectItem(json, "evsemeter"); + if (evsemeter) { + meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type + ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring); + meter_manager_evse_set_model(evseType); + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Meters updated successfully"); + + ESP_LOGI(TAG, "Meters configuration updated successfully"); + + return ESP_OK; +} + +// Registrando os manipuladores de URI para os contadores +void register_meters_settings_handlers(httpd_handle_t server, void *ctx) { + ESP_LOGI(TAG, "Registering URI handlers for meters settings"); + + // URI para o método GET + httpd_uri_t meters_get_uri = { + .uri = "/api/v1/config/meters", + .method = HTTP_GET, + .handler = meters_config_get_handler, + .user_ctx = ctx + }; + ESP_LOGI(TAG, "Registering GET handler for /api/v1/config/meters"); + httpd_register_uri_handler(server, &meters_get_uri); + + // URI para o método POST + httpd_uri_t meters_post_uri = { + .uri = "/api/v1/config/meters", + .method = HTTP_POST, + .handler = meters_config_post_handler, + .user_ctx = ctx + }; + ESP_LOGI(TAG, "Registering POST handler for /api/v1/config/meters"); + httpd_register_uri_handler(server, &meters_post_uri); +} + +// === Fim de: components/rest_api/src/meters_settings_api.c === + + +// === Início de: components/rest_api/src/rest_main.c === +#include "rest_main.h" +#include "evse_settings_api.h" +#include "meters_settings_api.h" +#include "loadbalancing_settings_api.h" +#include "network_api.h" +#include "ocpp_api.h" +#include "auth_api.h" +#include "dashboard_api.h" +#include "static_file_api.h" +#include "esp_log.h" + + +static const char *TAG = "rest_main"; + +esp_err_t rest_server_init(const char *base_path) { + ESP_LOGI(TAG, "Initializing REST API with base path: %s", base_path); + + rest_server_context_t *ctx = calloc(1, sizeof(rest_server_context_t)); + if (!ctx) { + ESP_LOGE(TAG, "Failed to allocate memory for REST context"); + return ESP_ERR_NO_MEM; + } + + strlcpy(ctx->base_path, base_path, sizeof(ctx->base_path)); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.uri_match_fn = httpd_uri_match_wildcard; + config.max_uri_handlers = 32; + + httpd_handle_t server = NULL; + esp_err_t err = httpd_start(&server, &config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err)); + free(ctx); + return err; + } + + ESP_LOGI(TAG, "HTTP server started successfully"); + + // Register endpoint groups + register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_network_handlers(server, ctx); // Apenas chamando a função sem comparação + register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação + register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação + register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação + register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação + + ESP_LOGI(TAG, "All REST API endpoint groups registered successfully"); + + return ESP_OK; +} + +// === Fim de: components/rest_api/src/rest_main.c === + + +// === Início de: components/rest_api/src/network_api.c === +// ========================= +// network_api.c +// ========================= + +#include "network_api.h" +#include "esp_log.h" +#include "cJSON.h" +#include "wifi.h" +#include "mqtt.h" + +static const char *TAG = "network_api"; + +typedef struct { + bool enabled; + char ssid[33]; + char password[65]; +} wifi_task_data_t; + + +static void wifi_apply_config_task(void *param) { + wifi_task_data_t *data = (wifi_task_data_t *)param; + ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task"); + wifi_set_config(data->enabled, data->ssid, data->password); + free(data); + vTaskDelete(NULL); +} + +static esp_err_t wifi_get_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi"); + + httpd_resp_set_type(req, "application/json"); + + // Obter dados da NVS via wifi.c + bool enabled = wifi_get_enabled(); + char ssid[33] = {0}; + char password[65] = {0}; + + wifi_get_ssid(ssid); + wifi_get_password(password); + + // Criar JSON + cJSON *json = cJSON_CreateObject(); + cJSON_AddBoolToObject(json, "enabled", enabled); + cJSON_AddStringToObject(json, "ssid", ssid); + cJSON_AddStringToObject(json, "password", password); + + // Enviar resposta + char *response = cJSON_Print(json); + httpd_resp_sendstr(req, response); + + // Limpeza + free(response); + cJSON_Delete(json); + + return ESP_OK; +} + +static esp_err_t wifi_post_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) return ESP_FAIL; + buf[len] = '\0'; + + cJSON *json = cJSON_Parse(buf); + if (!json) return ESP_FAIL; + + // Valores padrão + bool enabled = false; + const char *ssid = NULL; + const char *password = NULL; + + cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); + if (cJSON_IsBool(j_enabled)) enabled = j_enabled->valueint; + + cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid"); + if (cJSON_IsString(j_ssid)) ssid = j_ssid->valuestring; + + cJSON *j_password = cJSON_GetObjectItem(json, "password"); + if (cJSON_IsString(j_password)) password = j_password->valuestring; + + // Enviar resposta antes de alterar Wi-Fi + httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso"); + + // Alocar struct para passar para a task + wifi_task_data_t *task_data = malloc(sizeof(wifi_task_data_t)); + if (!task_data) { + cJSON_Delete(json); + ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task"); + return ESP_ERR_NO_MEM; + } + + task_data->enabled = enabled; + strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid)); + strncpy(task_data->password, password ? password : "", sizeof(task_data->password)); + + // Criar task normal com função C + xTaskCreate( + wifi_apply_config_task, + "wifi_config_task", + 4096, + task_data, + 3, + NULL + ); + + cJSON_Delete(json); + return ESP_OK; +} + + +static esp_err_t config_mqtt_get_handler(httpd_req_t *req) +{ + ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt"); + + httpd_resp_set_type(req, "application/json"); + + bool enabled = mqtt_get_enabled(); + char server[64] = {0}; + char base_topic[32] = {0}; + char username[32] = {0}; + char password[64] = {0}; + uint16_t periodicity = mqtt_get_periodicity(); + + mqtt_get_server(server); + mqtt_get_base_topic(base_topic); + mqtt_get_user(username); + mqtt_get_password(password); + + ESP_LOGI(TAG, "MQTT Config:"); + ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); + ESP_LOGI(TAG, " Server: %s", server); + ESP_LOGI(TAG, " Topic: %s", base_topic); + ESP_LOGI(TAG, " Username: %s", username); + ESP_LOGI(TAG, " Password: %s", password); + ESP_LOGI(TAG, " Periodicity: %d", periodicity); + + cJSON *config = cJSON_CreateObject(); + cJSON_AddBoolToObject(config, "enabled", enabled); + cJSON_AddStringToObject(config, "host", server); + cJSON_AddNumberToObject(config, "port", 1883); + cJSON_AddStringToObject(config, "username", username); + cJSON_AddStringToObject(config, "password", password); + cJSON_AddStringToObject(config, "topic", base_topic); + cJSON_AddNumberToObject(config, "periodicity", periodicity); + + const char *config_str = cJSON_Print(config); + httpd_resp_sendstr(req, config_str); + + free((void *)config_str); + cJSON_Delete(config); + return ESP_OK; +} + + +static esp_err_t config_mqtt_post_handler(httpd_req_t *req) +{ + ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + ESP_LOGE(TAG, "Failed to read request body"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body"); + return ESP_FAIL; + } + buf[len] = '\0'; + ESP_LOGI(TAG, "Received JSON: %s", buf); + + cJSON *json = cJSON_Parse(buf); + if (!json) { + ESP_LOGE(TAG, "Invalid JSON format"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + + bool enabled = false; + const char *host = NULL, *topic = NULL, *username = NULL, *password = NULL; + int periodicity = 30; + + if (cJSON_IsBool(cJSON_GetObjectItem(json, "enabled"))) + enabled = cJSON_GetObjectItem(json, "enabled")->valueint; + + cJSON *j_host = cJSON_GetObjectItem(json, "host"); + if (cJSON_IsString(j_host)) host = j_host->valuestring; + + cJSON *j_topic = cJSON_GetObjectItem(json, "topic"); + if (cJSON_IsString(j_topic)) topic = j_topic->valuestring; + + cJSON *j_user = cJSON_GetObjectItem(json, "username"); + if (cJSON_IsString(j_user)) username = j_user->valuestring; + + cJSON *j_pass = cJSON_GetObjectItem(json, "password"); + if (cJSON_IsString(j_pass)) password = j_pass->valuestring; + + cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity"); + if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint; + + ESP_LOGI(TAG, "Applying MQTT config:"); + ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); + ESP_LOGI(TAG, " Host: %s", host); + ESP_LOGI(TAG, " Topic: %s", topic); + ESP_LOGI(TAG, " Username: %s", username); + ESP_LOGI(TAG, " Password: %s", password); + ESP_LOGI(TAG, " Periodicity: %d", periodicity); + + esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config"); + cJSON_Delete(json); + return ESP_FAIL; + } + + httpd_resp_sendstr(req, "Configuração MQTT atualizada com sucesso"); + cJSON_Delete(json); + return ESP_OK; +} + + + +void register_network_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t wifi_get = { + .uri = "/api/v1/config/wifi", + .method = HTTP_GET, + .handler = wifi_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &wifi_get); + + httpd_uri_t wifi_post = { + .uri = "/api/v1/config/wifi", + .method = HTTP_POST, + .handler = wifi_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &wifi_post); + + // URI handler for getting MQTT config + httpd_uri_t config_mqtt_get_uri = { + .uri = "/api/v1/config/mqtt", + .method = HTTP_GET, + .handler = config_mqtt_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &config_mqtt_get_uri); + + // URI handler for posting MQTT config + httpd_uri_t config_mqtt_post_uri = { + .uri = "/api/v1/config/mqtt", + .method = HTTP_POST, + .handler = config_mqtt_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &config_mqtt_post_uri); +} + +// === Fim de: components/rest_api/src/network_api.c === diff --git a/projeto_parte5.c b/projeto_parte5.c new file mode 100755 index 0000000..af3f4a7 --- /dev/null +++ b/projeto_parte5.c @@ -0,0 +1,1066 @@ + + +// === Início de: components/rest_api/src/dashboard_api.c === +#include "dashboard_api.h" +#include "esp_log.h" +#include "cJSON.h" +#include "evse_api.h" +#include "evse_error.h" + +static const char *TAG = "dashboard_api"; + +static esp_err_t dashboard_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + + // Cria o objeto JSON principal do dashboard + cJSON *dashboard = cJSON_CreateObject(); + + // Status do sistema + evse_state_t state = evse_get_state(); + cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state)); + + // Carregador - informação do carregador 1 (adapte conforme necessário) + cJSON *chargers = cJSON_CreateArray(); + cJSON *charger1 = cJSON_CreateObject(); + cJSON_AddNumberToObject(charger1, "id", 1); + cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state)); + cJSON_AddNumberToObject(charger1, "current", evse_get_charging_current() / 10); + cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current()); + + // Calcular a potência com base na corrente (considerando 230V) + int power = (evse_get_charging_current() / 10) * 230; + cJSON_AddNumberToObject(charger1, "power", power); + + cJSON_AddItemToArray(chargers, charger1); + cJSON_AddItemToObject(dashboard, "chargers", chargers); + + // Consumo e tempo de carregamento + cJSON_AddNumberToObject(dashboard, "energyConsumed", evse_get_consumption_limit()); + cJSON_AddNumberToObject(dashboard, "chargingTime", evse_get_charging_time_limit()); + + // Alertas + cJSON *alerts = cJSON_CreateArray(); + if (evse_is_limit_reached()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido.")); + } + if (!evse_is_available()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível.")); + } + if (!evse_is_enabled()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado.")); + } + cJSON_AddItemToObject(dashboard, "alerts", alerts); + + // Erros + uint32_t error_bits = evse_get_error(); + cJSON *errors = cJSON_CreateArray(); + if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado")); + if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento")); + if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento")); + if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM")); + if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado")); + if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada")); + if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto")); + if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura")); + cJSON_AddItemToObject(dashboard, "errors", errors); + + // Enviar resposta JSON + const char *json_str = cJSON_Print(dashboard); + httpd_resp_sendstr(req, json_str); + + // Liberar memória + free((void *)json_str); + cJSON_Delete(dashboard); + + return ESP_OK; +} + +void register_dashboard_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t uri = { + .uri = "/api/v1/dashboard", + .method = HTTP_GET, + .handler = dashboard_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &uri); +} + +// === Fim de: components/rest_api/src/dashboard_api.c === + + +// === Início de: components/rest_api/src/auth_api.c === +// ========================= +// auth_api.c +// ========================= +#include "auth_api.h" +#include "auth.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "auth_api"; + +static struct { + char username[128]; +} users[10] = { /*{"admin"}, {"user1"}*/ }; +static int num_users = 2; + +static esp_err_t auth_methods_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *json = cJSON_CreateObject(); + cJSON_AddBoolToObject(json, "RFID", auth_is_enabled() ); + char *str = cJSON_PrintUnformatted(json); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(json); + return ESP_OK; +} + +static esp_err_t auth_methods_post_handler(httpd_req_t *req) { + char buf[256]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados"); + return ESP_FAIL; + } + + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido"); + return ESP_FAIL; + } + + cJSON *rfid = cJSON_GetObjectItem(json, "RFID"); + if (rfid && cJSON_IsBool(rfid)) { + auth_set_enabled(cJSON_IsTrue(rfid)); + } else { + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'RFID' inválido ou ausente"); + return ESP_FAIL; + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Métodos de autenticação atualizados"); + return ESP_OK; +} + + +static esp_err_t users_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *root = cJSON_CreateObject(); + cJSON *list = cJSON_CreateArray(); + for (int i = 0; i < num_users; ++i) { + cJSON *u = cJSON_CreateObject(); + cJSON_AddStringToObject(u, "username", users[i].username); + cJSON_AddItemToArray(list, u); + } + cJSON_AddItemToObject(root, "users", list); + char *str = cJSON_Print(root); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(root); + return ESP_OK; +} + +static esp_err_t users_post_handler(httpd_req_t *req) { + char buf[128]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) return ESP_FAIL; + buf[len] = '\0'; + if (num_users < 10) { + strlcpy(users[num_users].username, buf, sizeof(users[num_users].username)); + num_users++; + httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); + } else { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido"); + } + return ESP_OK; +} + +static esp_err_t users_delete_handler(httpd_req_t *req) { + char query[128]; + if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { + char username[128]; + if (httpd_query_key_value(query, "username", username, sizeof(username)) == ESP_OK) { + for (int i = 0; i < num_users; i++) { + if (strcmp(users[i].username, username) == 0) { + for (int j = i; j < num_users - 1; j++) { + users[j] = users[j + 1]; + } + num_users--; + httpd_resp_sendstr(req, "Usuário removido com sucesso"); + return ESP_OK; + } + } + } + } + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário não encontrado"); + return ESP_FAIL; +} + +void register_auth_handlers(httpd_handle_t server, void *ctx) { + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/auth-methods", + .method = HTTP_GET, + .handler = auth_methods_get_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/auth-methods", + .method = HTTP_POST, + .handler = auth_methods_post_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_GET, + .handler = users_get_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_POST, + .handler = users_post_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_DELETE, + .handler = users_delete_handler, + .user_ctx = ctx + }); +} + +// === Fim de: components/rest_api/src/auth_api.c === + + +// === Início de: components/rest_api/src/loadbalancing_settings_api.c === +#include "loadbalancing_settings_api.h" +#include "loadbalancer.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "loadbalancing_settings_api"; + +// GET Handler: Retorna configurações atuais de load balancing +static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) { + bool enabled = loadbalancer_is_enabled(); + uint8_t currentLimit = load_balancing_get_max_grid_current(); + + ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit); + + httpd_resp_set_type(req, "application/json"); + + cJSON *config = cJSON_CreateObject(); + cJSON_AddBoolToObject(config, "loadBalancingEnabled", enabled); + cJSON_AddNumberToObject(config, "loadBalancingCurrentLimit", currentLimit); + + const char *json_str = cJSON_Print(config); + httpd_resp_sendstr(req, json_str); + + ESP_LOGI(TAG, "Returned config: %s", json_str); + + free((void *)json_str); + cJSON_Delete(config); + return ESP_OK; +} + +// POST Handler: Atualiza configurações de load balancing +static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + + if (len <= 0) { + ESP_LOGE(TAG, "Received empty POST body"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + + buf[len] = '\0'; + ESP_LOGI(TAG, "Received POST data: %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; + } + + // Atualizar estado habilitado + cJSON *enabled_item = cJSON_GetObjectItem(json, "loadBalancingEnabled"); + if (enabled_item && cJSON_IsBool(enabled_item)) { + bool isEnabled = cJSON_IsTrue(enabled_item); + loadbalancer_set_enabled(isEnabled); + ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled); + } + + // Atualizar limite de corrente + cJSON *limit_item = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); + if (limit_item && cJSON_IsNumber(limit_item)) { + uint8_t currentLimit = (uint8_t)limit_item->valuedouble; + + // Validar intervalo + if (currentLimit < 6 || currentLimit > 100) { + ESP_LOGW(TAG, "Rejected invalid currentLimit: %d", currentLimit); + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid currentLimit (must be 6-100)"); + return ESP_FAIL; + } + + esp_err_t err = load_balancing_set_max_grid_current(currentLimit); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to save currentLimit: %s", esp_err_to_name(err)); + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save setting"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit); + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Load balancing settings updated successfully"); + return ESP_OK; +} + +// Registro dos handlers na API HTTP +void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx) { + // GET + httpd_uri_t get_uri = { + .uri = "/api/v1/config/loadbalancing", + .method = HTTP_GET, + .handler = loadbalancing_config_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + // POST + httpd_uri_t post_uri = { + .uri = "/api/v1/config/loadbalancing", + .method = HTTP_POST, + .handler = loadbalancing_config_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/loadbalancing_settings_api.c === + + +// === Início de: components/rest_api/src/evse_settings_api.c === +// ========================= +// evse_settings_api.c +// ========================= +#include "evse_settings_api.h" +#include "evse_api.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "evse_settings_api"; + +static esp_err_t config_settings_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *config = cJSON_CreateObject(); + cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current()); + cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); + const char *json_str = cJSON_Print(config); + httpd_resp_sendstr(req, json_str); + free((void *)json_str); + cJSON_Delete(config); + return ESP_OK; +} + +static esp_err_t config_settings_post_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + + cJSON *current = cJSON_GetObjectItem(json, "currentLimit"); + if (current) evse_set_max_charging_current(current->valueint); + cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit"); + if (temp) evse_set_temp_threshold(temp->valueint); + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Configurações atualizadas com sucesso"); + return ESP_OK; +} + +void register_evse_settings_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t get_uri = { + .uri = "/api/v1/config/settings", + .method = HTTP_GET, + .handler = config_settings_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + httpd_uri_t post_uri = { + .uri = "/api/v1/config/settings", + .method = HTTP_POST, + .handler = config_settings_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/evse_settings_api.c === + + +// === Início de: components/rest_api/include/dashboard_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra o handler da dashboard (status geral do sistema) + */ +void register_dashboard_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/dashboard_api.h === + + +// === Início de: components/rest_api/include/static_file_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra o handler para servir arquivos estáticos da web (SPA) + */ +void register_static_file_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/static_file_api.h === + + +// === Início de: components/rest_api/include/network_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de configuração Wi-Fi e MQTT + */ +void register_network_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/network_api.h === + + +// === Início de: components/rest_api/include/auth_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de autenticação e gerenciamento de usuários + */ +void register_auth_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/auth_api.h === + + +// === Início de: components/rest_api/include/loadbalancing_settings_api.h === +// ========================= +// loadbalancing_settings_api.h +// ========================= + +#ifndef LOADBALANCING_SETTINGS_API_H +#define LOADBALANCING_SETTINGS_API_H + +#include "esp_err.h" +#include "esp_http_server.h" + +// Função para registrar os manipuladores de URI para as configurações de load balancing e solar +void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx); + +#endif // LOADBALANCING_SETTINGS_API_H + +// === Fim de: components/rest_api/include/loadbalancing_settings_api.h === + + +// === Início de: components/rest_api/include/rest_main.h === +#pragma once + +#include +#include + +#define SCRATCH_BUFSIZE (10240) + +typedef struct rest_server_context { + char base_path[ESP_VFS_PATH_MAX + 1]; + char scratch[SCRATCH_BUFSIZE]; +} rest_server_context_t; + +esp_err_t rest_server_init(const char *base_path); + +// === Fim de: components/rest_api/include/rest_main.h === + + +// === Início de: components/rest_api/include/meters_settings_api.h === +// ========================= +// meters_settings_api.h +// ========================= + +#ifndef METERS_SETTINGS_API_H +#define METERS_SETTINGS_API_H + +#include "esp_err.h" +#include "esp_http_server.h" + +// Função para registrar os manipuladores de URI para as configurações dos contadores +void register_meters_settings_handlers(httpd_handle_t server, void *ctx); + +#endif // METERS_SETTINGS_API_H + +// === Fim de: components/rest_api/include/meters_settings_api.h === + + +// === Início de: components/rest_api/include/ocpp_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers da configuração e status do OCPP + */ +void register_ocpp_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/ocpp_api.h === + + +// === Início de: components/rest_api/include/evse_settings_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de configuração elétrica e limites de carregamento + */ +void register_evse_settings_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/evse_settings_api.h === + + +// === Início de: components/network/src/wifi_2.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_mac.h" +#include "nvs.h" +#include "mdns.h" + +#include "wifi.h" + + +#include "nvs_flash.h" +#include + +#define WIFI_STORAGE_NAMESPACE "wifi_config" + + + +#define TAG "wifi" +#define AP_SSID "plx-%02x%02x%02x" +#define MDNS_HOSTNAME "plx%02x" + +#define NVS_NAMESPACE "wifi" + +static nvs_handle_t nvs; +static esp_netif_t *ap_netif; +EventGroupHandle_t wifi_event_group; + +// +// Event handler para modo AP +// +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT) { + switch (event_id) { + case WIFI_EVENT_AP_STACONNECTED: { + wifi_event_ap_staconnected_t *event = event_data; + ESP_LOGI(TAG, "STA " MACSTR " conectou, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupSetBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + break; + } + case WIFI_EVENT_AP_STADISCONNECTED: { + wifi_event_ap_stadisconnected_t *event = event_data; + ESP_LOGI(TAG, "STA " MACSTR " desconectou, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + break; + } + } + } +} + +// +// Iniciar o AP com SSID baseado no MAC +// +void wifi_ap_start(void) +{ + ESP_LOGI(TAG, "Iniciando AP"); + + ESP_ERROR_CHECK(esp_wifi_stop()); + + wifi_config_t ap_config = { + .ap = { + .ssid = "", + .ssid_len = 0, + .channel = 1, + .password = "", + .max_connection = 4, + .authmode = WIFI_AUTH_OPEN + } + }; + + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP)); + snprintf((char *)ap_config.ap.ssid, sizeof(ap_config.ap.ssid), AP_SSID, mac[3], mac[4], mac[5]); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); +} + +// +// Inicializar Wi-Fi em modo AP +// +void wifi_ini(void) +{ + ESP_LOGI(TAG, "Inicializando Wi-Fi (modo AP)"); + + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + /* + if (!esp_event_loop_is_running()) { + ESP_ERROR_CHECK(esp_event_loop_create_default()); + }*/ + + ap_netif = esp_netif_create_default_wifi_ap(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP)); + char hostname[16]; + snprintf(hostname, sizeof(hostname), MDNS_HOSTNAME, mac[5]); + + ESP_ERROR_CHECK(mdns_init()); + ESP_ERROR_CHECK(mdns_hostname_set(hostname)); + ESP_ERROR_CHECK(mdns_instance_name_set("EVSE Controller")); + + wifi_ap_start(); +} + +esp_netif_t *wifi_get_ap_netif(void) +{ + return ap_netif; +} + +esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password) { + + return ESP_OK; +} + +void wifi_get_ssid(char *value) { + // Your implementation here +} + +void wifi_get_password(char *value) { + // Your implementation here +} + +bool wifi_get_enabled(void) +{ + return true; +} + +// === Fim de: components/network/src/wifi_2.c === + + +// === Início de: components/network/src/wifi.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_mac.h" +#include "nvs.h" +#include "mdns.h" + +#include "wifi.h" + +#define AP_SSID "plx-%02x%02x%02x" + +#define MDNS_SSID "plx%02x" + +#define NVS_NAMESPACE "wifi" +#define NVS_ENABLED "enabled" +#define NVS_SSID "ssid" +#define NVS_PASSWORD "password" + +static const char *TAG = "wifi"; + +static nvs_handle_t nvs; + +static esp_netif_t *sta_netif; + +static esp_netif_t *ap_netif; + +EventGroupHandle_t wifi_event_group; + +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "event_handler"); + + if (event_base == WIFI_EVENT) + { + if (event_id == WIFI_EVENT_AP_STACONNECTED) + { + ESP_LOGI(TAG, "STA connected"); + wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; + ESP_LOGI(TAG, "WiFi AP " MACSTR " join, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + } + if (event_id == WIFI_EVENT_AP_STADISCONNECTED) + { + ESP_LOGI(TAG, "AP STA disconnected"); + wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; + ESP_LOGI(TAG, "WiFi AP " MACSTR " leave, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); + } + if (event_id == WIFI_EVENT_STA_DISCONNECTED) + { + ESP_LOGI(TAG, "STA disconnected"); + xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); + esp_wifi_connect(); + } + if (event_id == WIFI_EVENT_STA_START) + { + ESP_LOGI(TAG, "STA start"); + esp_wifi_connect(); + } + } + else if (event_base == IP_EVENT) + { + ESP_LOGI(TAG, "event_base == IP_EVENT"); + + if (event_id == IP_EVENT_STA_GOT_IP || event_id == IP_EVENT_GOT_IP6) + { + if (event_id == IP_EVENT_STA_GOT_IP) + { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "WiFi STA got ip: " IPSTR, IP2STR(&event->ip_info.ip)); + } + else + { + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + ESP_LOGI(TAG, "WiFi STA got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip)); + } + xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); + } + } +} + +static void sta_set_config(void) +{ + + ESP_LOGI(TAG, "sta_set_config"); + + if (wifi_get_enabled()) + { + wifi_config_t wifi_config = { + .sta = { + .pmf_cfg = { + .capable = true, + .required = false}}}; + wifi_get_ssid((char *)wifi_config.sta.ssid); + wifi_get_password((char *)wifi_config.sta.password); + + esp_wifi_set_mode(WIFI_MODE_STA); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); + } +} + +static void ap_set_config(void) +{ + + ESP_LOGI(TAG, "ap_set_config"); + + wifi_config_t wifi_ap_config = { + .ap = { + .max_connection = 1, + .authmode = WIFI_AUTH_OPEN}}; + uint8_t mac[6]; + esp_wifi_get_mac(ESP_IF_WIFI_AP, mac); + sprintf((char *)wifi_ap_config.ap.ssid, AP_SSID, mac[3], mac[4], mac[5]); + + wifi_config_t wifi_sta_config = {0}; + + esp_wifi_set_mode(WIFI_MODE_APSTA); + esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_ap_config); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config); +} + +static void sta_try_start(void) +{ + + ESP_LOGI(TAG, "sta_try_start"); + + sta_set_config(); + if (wifi_get_enabled()) + { + ESP_LOGI(TAG, "Starting STA"); + esp_wifi_start(); + xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT); + } +} + +void wifi_ini(void) +{ + + + ESP_LOGI(TAG, "Wifi init"); + + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + wifi_event_group = xEventGroupCreate(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + + ap_netif = esp_netif_create_default_wifi_ap(); + sta_netif = esp_netif_create_default_wifi_sta(); + + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + + char chargeid[6]; + uint8_t mac[6]; + esp_wifi_get_mac(ESP_IF_WIFI_AP, mac); + sprintf((char *)chargeid, MDNS_SSID, mac[5]); + + ESP_ERROR_CHECK(mdns_init()); + ESP_ERROR_CHECK(mdns_hostname_set(chargeid)); + ESP_ERROR_CHECK(mdns_instance_name_set("EVSE controller")); + + sta_try_start(); + +} + +esp_netif_t *wifi_get_sta_netif(void) +{ + return sta_netif; +} + +esp_netif_t *wifi_get_ap_netif(void) +{ + return ap_netif; +} + +esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password) +{ + + ESP_LOGI(TAG, "Wifi set config"); + + if (enabled) + { + if (ssid == NULL || strlen(ssid) == 0) + { + size_t len = 0; + nvs_get_str(nvs, NVS_SSID, NULL, &len); + if (len <= 1) + { + ESP_LOGE(TAG, "Required SSID"); + return ESP_ERR_INVALID_ARG; + } + } + } + + if (ssid != NULL && strlen(ssid) > 32) + { + ESP_LOGE(TAG, "SSID out of range"); + return ESP_ERR_INVALID_ARG; + } + + if (password != NULL && strlen(password) > 32) + { + ESP_LOGE(TAG, "Password out of range"); + return ESP_ERR_INVALID_ARG; + } + + nvs_set_u8(nvs, NVS_ENABLED, enabled); + if (ssid != NULL) + { + nvs_set_str(nvs, NVS_SSID, ssid); + } + if (password != NULL) + { + nvs_set_str(nvs, NVS_PASSWORD, password); + } + nvs_commit(nvs); + + ESP_LOGI(TAG, "Stopping AP/STA"); + xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT); + esp_wifi_stop(); + + sta_try_start(); + + return ESP_OK; +} + +uint16_t wifi_scan(wifi_scan_ap_t *scan_aps) +{ + + ESP_LOGI(TAG, "wifi_scan"); + + uint16_t number = WIFI_SCAN_SCAN_LIST_SIZE; + wifi_ap_record_t ap_info[WIFI_SCAN_SCAN_LIST_SIZE]; + uint16_t ap_count = 0; + memset(ap_info, 0, sizeof(ap_info)); + + esp_wifi_scan_start(NULL, true); + esp_wifi_scan_get_ap_records(&number, ap_info); + esp_wifi_scan_get_ap_num(&ap_count); + + ESP_LOGI(TAG, "wifi_scan --- %d", ap_count); + + for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++) + { + + ESP_LOGI(TAG, "wifi_scan ---"); + + strcpy(scan_aps[i].ssid, (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; + } + + return ap_count; +} + +bool wifi_get_enabled(void) +{ + uint8_t value = false; + nvs_get_u8(nvs, NVS_ENABLED, &value); + return value; +} + +void wifi_get_ssid(char *value) +{ + size_t len = 32; + value[0] = '\0'; + nvs_get_str(nvs, NVS_SSID, value, &len); +} + +void wifi_get_password(char *value) +{ + size_t len = 64; + value[0] = '\0'; + nvs_get_str(nvs, NVS_PASSWORD, value, &len); +} + +void wifi_ap_start(void) +{ + ESP_LOGI(TAG, "Starting AP"); + + xEventGroupClearBits(wifi_event_group, WIFI_STA_MODE_BIT); + esp_wifi_stop(); + + ap_set_config(); + esp_wifi_start(); + + xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); +} + +void wifi_ap_stop(void) +{ + ESP_LOGI(TAG, "Stopping AP"); + xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT); + esp_wifi_stop(); + + sta_try_start(); +} + +bool wifi_is_ap(void) +{ + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + return mode == WIFI_MODE_APSTA; +} + +// === Fim de: components/network/src/wifi.c === diff --git a/projeto_parte6.c b/projeto_parte6.c new file mode 100755 index 0000000..2c303ad --- /dev/null +++ b/projeto_parte6.c @@ -0,0 +1,735 @@ + + +// === Início de: components/network/include/wifi.h === +#ifndef WIFI_H_ +#define WIFI_H_ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_err.h" +#include "esp_netif.h" + +#define WIFI_SCAN_SCAN_LIST_SIZE 10 + +#define WIFI_AP_CONNECTED_BIT BIT0 +#define WIFI_AP_DISCONNECTED_BIT BIT1 +#define WIFI_STA_CONNECTED_BIT BIT2 +#define WIFI_STA_DISCONNECTED_BIT BIT3 +#define WIFI_AP_MODE_BIT BIT4 +#define WIFI_STA_MODE_BIT BIT5 + +typedef struct +{ + char ssid[32]; + int rssi; + bool auth; +} wifi_scan_ap_t; + +/** + * @brief WiFi event group WIFI_AP_CONNECTED_BIT | WIFI_AP_DISCONNECTED_BIT | WIFI_STA_CONNECTED_BIT | WIFI_STA_DISCONNECTED_BIT | WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT + * + */ +extern EventGroupHandle_t wifi_event_group; + +/** + * @brief Initialize WiFi + * + */ +void wifi_ini(void); + +/** + * @brief Return WiFi STA network interface + * + * @return esp_netif_t* + */ +esp_netif_t* wifi_get_sta_netif(void); + +/** + * @brief Return WiFi AP network interface + * + * @return esp_netif_t* + */ +esp_netif_t* wifi_get_ap_netif(void); + +/** + * @brief Set WiFi config + * + * @param enabled + * @param ssid NULL value will be skiped + * @param password NULL value will be skiped + * @return esp_err_t + */ +esp_err_t wifi_set_config(bool enabled, const char* ssid, const char* password); + +/** + * @brief Get WiFi STA enabled, stored in NVS + * + * @return true + * @return false + */ +bool wifi_get_enabled(void); + +/** + * @brief Scan for AP + * + * @param scan_aps array with length WIFI_SCAN_SCAN_LIST_SIZE + * @return uint16_t number of available AP + */ +uint16_t wifi_scan(wifi_scan_ap_t *scan_aps); + +/** + * @brief Get WiFi STA ssid, string length 32, stored in NVS + * + * @param value + */ +void wifi_get_ssid(char* value); + +/** + * @brief Get WiFi STA password, string length 32, stored in NVS + * + * @param value + */ +void wifi_get_password(char* value); + +/** + * @brief Start WiFi AP mode + * + */ +void wifi_ap_start(void); + +/** + * @brief Stop WiFi AP mode + * + */ +void wifi_ap_stop(void); + +#endif /* WIFI_H_ */ + +// === Fim de: components/network/include/wifi.h === + + +// === Início de: components/peripherals/src/ac_relay.c === +#include "esp_log.h" +#include "driver/gpio.h" + +#include "ac_relay.h" +#include "board_config.h" + +static const char* TAG = "ac_relay"; + +/** + * @brief Initialize the AC relay GPIO. + * + * Configures the specified GPIO pin as an output and sets its initial state to OFF (low). + */ +void ac_relay_init(void) +{ + gpio_config_t conf = { + .pin_bit_mask = BIT64(board_config.ac_relay_gpio), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_DISABLE, ///< Disabled unless required + .pull_up_en = GPIO_PULLUP_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + + esp_err_t ret = gpio_config(&conf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure GPIO (error: %s)", esp_err_to_name(ret)); + return; + } + + gpio_set_level(board_config.ac_relay_gpio, false); ///< Ensure relay starts OFF + ESP_LOGI(TAG, "AC relay initialized. Pin: %d", board_config.ac_relay_gpio); +} + +/** + * @brief Set the state of the AC relay. + * + * @param state True to turn the relay ON, False to turn it OFF. + */ +void ac_relay_set_state(bool state) +{ + ESP_LOGI(TAG, "Setting AC relay state: Pin: %d, State: %d", board_config.ac_relay_gpio, state); + + esp_err_t ret = gpio_set_level(board_config.ac_relay_gpio, state); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set GPIO level (error: %s)", esp_err_to_name(ret)); + } +} + +/** + * @brief Get the current state of the AC relay. + * + * @return true if the relay is ON, false if OFF. + */ +bool ac_relay_get_state(void) +{ + int level = gpio_get_level(board_config.ac_relay_gpio); + ESP_LOGD(TAG, "Current AC relay state: Pin: %d, State: %d", board_config.ac_relay_gpio, level); + return level; +} + +// === Fim de: components/peripherals/src/ac_relay.c === + + +// === Início de: components/peripherals/src/ntc_sensor.c === +#include +#include +#include "freertos/task.h" +#include "esp_log.h" +#include "ntc_sensor.h" +#include "ntc_driver.h" + +#include "adc.h" + +static const char *TAG = "temp_sensor"; + +#define MEASURE_PERIOD 15000 // 10s + +static float temp = 0.0; + +static ntc_device_handle_t ntc = NULL; + +static portMUX_TYPE temp_mux = portMUX_INITIALIZER_UNLOCKED; + +static void ntc_sensor_task_func(void *param) { + float t; + while (true) { + if (ntc_dev_get_temperature(ntc, &t) == ESP_OK) { + portENTER_CRITICAL(&temp_mux); + temp = t; + portEXIT_CRITICAL(&temp_mux); + } + vTaskDelay(pdMS_TO_TICKS(MEASURE_PERIOD)); + } +} + +float ntc_temp_sensor(void) { + float t; + portENTER_CRITICAL(&temp_mux); + t = temp; + portEXIT_CRITICAL(&temp_mux); + return t; +} + +void ntc_sensor_init(void) +{ + + ESP_LOGI(TAG, "ntc_sensor_init"); + + // Select the NTC sensor and initialize the hardware parameters + ntc_config_t ntc_config = { + .b_value = 3950, + .r25_ohm = 10000, + .fixed_ohm = 4700, + .vdd_mv = 3300, + .circuit_mode = CIRCUIT_MODE_NTC_GND, + .atten = ADC_ATTEN_DB_12, + .channel = ADC_CHANNEL_0, + .unit = ADC_UNIT_1}; + + // Create the NTC Driver and Init ADC + // ntc_device_handle_t ntc = NULL; + // adc_oneshot_unit_handle_t adc_handle = NULL; + ESP_ERROR_CHECK(ntc_dev_create(&ntc_config, &ntc, &adc_handle)); + ESP_ERROR_CHECK(ntc_dev_get_adc_handle(ntc, &adc_handle)); + + xTaskCreate(ntc_sensor_task_func, "ntc_sensor_task", 5 * 1024, NULL, 3, NULL); +} + +// === Fim de: components/peripherals/src/ntc_sensor.c === + + +// === Início de: components/peripherals/src/proximity.c === +#include "esp_log.h" + +#include "proximity.h" +#include "board_config.h" +#include "adc.h" + +static const char *TAG = "proximity"; + +void proximity_init(void) +{ + if (board_config.proximity) + { + adc_oneshot_chan_cfg_t config = { + .bitwidth = ADC_BITWIDTH_DEFAULT, + .atten = ADC_ATTEN_DB_12}; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.proximity_adc_channel, &config)); + } +} + +uint8_t proximity_get_max_current(void) +{ + int voltage; + adc_oneshot_read(adc_handle, board_config.proximity_adc_channel, &voltage); + adc_cali_raw_to_voltage(adc_cali_handle, voltage, &voltage); + + ESP_LOGI(TAG, "Measured: %dmV", voltage); + + uint8_t current; + + if (voltage >= board_config.proximity_down_threshold_8) + { + current = 8; + } + else if (voltage >= board_config.proximity_down_threshold_10) + { + current = 10; + } + + else if (voltage >= board_config.proximity_down_threshold_13) + { + current = 13; + } + else if (voltage >= board_config.proximity_down_threshold_20) + { + current = 20; + } + + else if (voltage >= board_config.proximity_down_threshold_25) + { + current = 25; + } + else if (voltage >= board_config.proximity_down_threshold_32) + { + current = 32; + } + else + { + current = 32; + } + + ESP_LOGI(TAG, "Max current: %dA", current); + + return current; +} +// === Fim de: components/peripherals/src/proximity.c === + + +// === Início de: components/peripherals/src/buzzer.c === +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "board_config.h" +#include "buzzer.h" +#include "evse_api.h" + +static gpio_num_t buzzer_gpio = GPIO_NUM_NC; +static evse_state_t last_buzzer_state = -1; +static QueueHandle_t buzzer_queue = NULL; + +void buzzer_on(void) { + if (buzzer_gpio != GPIO_NUM_NC) + gpio_set_level(buzzer_gpio, 1); +} + +void buzzer_off(void) { + if (buzzer_gpio != GPIO_NUM_NC) + gpio_set_level(buzzer_gpio, 0); +} + +// ---------------------- +// Padrões de Buzzer +// ---------------------- + +typedef struct { + uint16_t on_ms; + uint16_t off_ms; +} buzzer_pattern_step_t; + +typedef enum { + BUZZER_PATTERN_NONE = 0, + BUZZER_PATTERN_PLUGGED, + BUZZER_PATTERN_UNPLUGGED, + BUZZER_PATTERN_CHARGING, +} buzzer_pattern_id_t; + +static const buzzer_pattern_step_t pattern_plugged[] = { + {100, 100}, {200, 0} +}; + +static const buzzer_pattern_step_t pattern_unplugged[] = { + {150, 150}, {150, 150}, {150, 0} +}; + +static const buzzer_pattern_step_t pattern_charging[] = { + {80, 150}, {100, 120}, {120, 100}, {140, 0} +}; + +// ---------------------- +// Executor de padrões +// ---------------------- + +static void buzzer_execute_pattern(buzzer_pattern_id_t pattern_id) { + const buzzer_pattern_step_t *pattern = NULL; + size_t length = 0; + + switch (pattern_id) { + case BUZZER_PATTERN_PLUGGED: + pattern = pattern_plugged; + length = sizeof(pattern_plugged) / sizeof(pattern_plugged[0]); + break; + case BUZZER_PATTERN_UNPLUGGED: + pattern = pattern_unplugged; + length = sizeof(pattern_unplugged) / sizeof(pattern_unplugged[0]); + break; + case BUZZER_PATTERN_CHARGING: + pattern = pattern_charging; + length = sizeof(pattern_charging) / sizeof(pattern_charging[0]); + break; + default: + return; + } + + for (size_t i = 0; i < length; i++) { + buzzer_on(); + vTaskDelay(pdMS_TO_TICKS(pattern[i].on_ms)); + buzzer_off(); + if (pattern[i].off_ms > 0) + vTaskDelay(pdMS_TO_TICKS(pattern[i].off_ms)); + } +} + +// ---------------------- +// Task que toca o buzzer +// ---------------------- + +static void buzzer_worker_task(void *arg) { + buzzer_pattern_id_t pattern_id; + + while (true) { + if (xQueueReceive(buzzer_queue, &pattern_id, portMAX_DELAY)) { + //buzzer_execute_pattern(pattern_id); + } + } +} + +// ---------------------- +// Task de monitoramento +// ---------------------- + +static void buzzer_monitor_task(void *arg) { + while (true) { + evse_state_t current = evse_get_state(); + + if (current != last_buzzer_state) { + buzzer_pattern_id_t pattern_id = BUZZER_PATTERN_NONE; + + switch (current) { + case EVSE_STATE_A: + if (last_buzzer_state != EVSE_STATE_A) + pattern_id = BUZZER_PATTERN_UNPLUGGED; + break; + case EVSE_STATE_B1: + case EVSE_STATE_B2: + if (last_buzzer_state != EVSE_STATE_B1 && last_buzzer_state != EVSE_STATE_B2) + pattern_id = BUZZER_PATTERN_PLUGGED; + break; + case EVSE_STATE_C2: + case EVSE_STATE_D2: + if (last_buzzer_state != EVSE_STATE_C2 && last_buzzer_state != EVSE_STATE_D2) + pattern_id = BUZZER_PATTERN_CHARGING; + break; + default: + break; + } + + if (pattern_id != BUZZER_PATTERN_NONE) { + xQueueSend(buzzer_queue, &pattern_id, 0); // Não bloqueia + } + + last_buzzer_state = current; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +// ---------------------- +// Inicialização +// ---------------------- + +void buzzer_init(void) { + if (board_config.buzzer) { + buzzer_gpio = board_config.buzzer_gpio; + + gpio_config_t io_conf = { + .pin_bit_mask = BIT64(buzzer_gpio), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .pull_up_en = GPIO_PULLUP_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + gpio_set_level(buzzer_gpio, 0); + } + + buzzer_queue = xQueueCreate(4, sizeof(buzzer_pattern_id_t)); + + xTaskCreate(buzzer_monitor_task, "buzzer_monitor", 2048, NULL, 3, NULL); + xTaskCreate(buzzer_worker_task, "buzzer_worker", 2048, NULL, 3, NULL); +} + +// === Fim de: components/peripherals/src/buzzer.c === + + +// === Início de: components/peripherals/src/ds18x20.h === +/* + * Copyright (c) 2016 Grzegorz Hetman + * Copyright (c) 2016 Alex Stewart + * Copyright (c) 2018 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DS18X20_H +#define _DS18X20_H + +#include +#include "onewire.h" + +typedef onewire_addr_t ds18x20_addr_t; + +/** An address value which can be used to indicate "any device on the bus" */ +#define DS18X20_ANY ONEWIRE_NONE + +/** Family ID (lower address byte) of DS18B20 sensors */ +#define DS18B20_FAMILY_ID 0x28 + +/** Family ID (lower address byte) of DS18S20 sensors */ +#define DS18S20_FAMILY_ID 0x10 + +/** + * @brief Find the addresses of all ds18x20 devices on the bus. + * + * Scans the bus for all devices and places their addresses in the supplied + * array. If there are more than `addr_count` devices on the bus, only the + * first `addr_count` are recorded. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A pointer to an array of ::ds18x20_addr_t values. + * This will be populated with the addresses of the found + * devices. + * @param addr_count Number of slots in the `addr_list` array. At most this + * many addresses will be returned. + * @param found The number of devices found. Note that this may be less + * than, equal to, or more than `addr_count`, depending on + * how many ds18x20 devices are attached to the bus. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found); + +/** + * @brief Tell one or more sensors to perform a temperature measurement and + * conversion (CONVERT_T) operation. + * + * This operation can take up to 750ms to complete. + * + * If `wait=true`, this routine will automatically drive the pin high for the + * necessary 750ms after issuing the command to ensure parasitically-powered + * devices have enough power to perform the conversion operation (for + * non-parasitically-powered devices, this is not necessary but does not + * hurt). If `wait=false`, this routine will drive the pin high, but will + * then return immediately. It is up to the caller to wait the requisite time + * and then depower the bus using onewire_depower() or by issuing another + * command once conversion is done. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device on the bus. This can be set + * to ::DS18X20_ANY to send the command to all devices on the bus + * at the same time. + * @param wait Whether to wait for the necessary 750ms for the ds18x20 to + * finish performing the conversion before returning to the + * caller (You will normally want to do this). + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait); + +/** + * @brief Read the value from the last CONVERT_T operation. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18b20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18s20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation for multiple devices. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of int16_ts to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list); + +/** Perform a ds18x20_measure() followed by ds18s20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18s20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** Perform a ds18x20_measure() followed by ds18b20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** Perform a ds18x20_measure() followed by ds18x20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi() + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of int16_ts to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list); + +/** + * @brief Read the scratchpad data for a particular ds18x20 device. + * + * This is not generally necessary to do directly. It is done automatically + * as part of ds18x20_read_temperature(). + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 8-byte buffer to hold the read data. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Write the scratchpad data for a particular ds18x20 device. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to write. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 3-byte buffer to hold the data to write + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Issue the copy scratchpad command, copying current scratchpad to + * EEPROM. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to command. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr); + + +#endif /* _DS18X20_H */ +// === Fim de: components/peripherals/src/ds18x20.h === diff --git a/projeto_parte7.c b/projeto_parte7.c new file mode 100755 index 0000000..e598611 --- /dev/null +++ b/projeto_parte7.c @@ -0,0 +1,793 @@ + + +// === Início de: components/peripherals/src/socket_lock.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "nvs.h" + +#include "socket_lock.h" +#include "board_config.h" + +#define NVS_NAMESPACE "socket_lock" +#define NVS_OPERATING_TIME "op_time" +#define NVS_BREAK_TIME "break_time" +#define NVS_RETRY_COUNT "retry_count" +#define NVS_DETECTION_HIGH "detect_hi" + +#define OPERATING_TIME_MIN 100 +#define OPERATING_TIME_MAX 1000 +#define LOCK_DELAY 500 + +#define LOCK_BIT BIT0 +#define UNLOCK_BIT BIT1 +#define REPEAT_LOCK_BIT BIT2 +#define REPEAT_UNLOCK_BIT BIT3 + +static const char* TAG = "socket_lock"; + +static nvs_handle_t nvs; + +static uint16_t operating_time = 300; + +static uint16_t break_time = 1000; + +static bool detection_high; + +static uint8_t retry_count = 5; + +static socket_lock_status_t status; + +static TaskHandle_t socket_lock_task; + +static bool is_locked(void) +{ + gpio_set_level(board_config.socket_lock_a_gpio, 1); + gpio_set_level(board_config.socket_lock_b_gpio, 1); + + vTaskDelay(pdMS_TO_TICKS(board_config.socket_lock_detection_delay)); + + return gpio_get_level(board_config.socket_lock_detection_gpio) == detection_high; +} + +bool socket_lock_is_locked_state(void) +{ + return is_locked(); +} + +static void socket_lock_task_func(void* param) +{ + uint32_t notification; + + TickType_t previous_tick = 0; + uint8_t attempt = 0; + + while (true) { + if (xTaskNotifyWait(0x00, 0xff, ¬ification, portMAX_DELAY)) { + if (notification & (LOCK_BIT | UNLOCK_BIT)) { + attempt = retry_count; + } + + if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT)) { + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 1); + vTaskDelay(pdMS_TO_TICKS(operating_time)); + + if (!is_locked()) { + ESP_LOGI(TAG, "Unlock OK"); + status = SOCKED_LOCK_STATUS_IDLE; + } else { + if (attempt > 1) { + ESP_LOGW(TAG, "Not unlocked yet, repeating..."); + attempt--; + xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits); + } else { + ESP_LOGE(TAG, "Not unlocked"); + status = SOCKED_LOCK_STATUS_UNLOCKING_FAIL; + } + } + + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + } else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT)) { + if (notification & LOCK_BIT) { + vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); //delay before first lock attempt + } + gpio_set_level(board_config.socket_lock_a_gpio, 1); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + vTaskDelay(pdMS_TO_TICKS(operating_time)); + + if (is_locked()) { + ESP_LOGI(TAG, "Lock OK"); + status = SOCKED_LOCK_STATUS_IDLE; + } else { + if (attempt > 1) { + ESP_LOGW(TAG, "Not locked yet, repeating..."); + attempt--; + xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits); + } else { + ESP_LOGE(TAG, "Not locked"); + status = SOCKED_LOCK_STATUS_LOCKING_FAIL; + } + } + + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + } + + TickType_t delay_tick = xTaskGetTickCount() - previous_tick; + if (delay_tick < pdMS_TO_TICKS(break_time)) { + vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick); + } + previous_tick = xTaskGetTickCount(); + } + } +} + +void socket_lock_init(void) +{ + if (board_config.socket_lock) { + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + nvs_get_u16(nvs, NVS_OPERATING_TIME, &operating_time); + + nvs_get_u16(nvs, NVS_BREAK_TIME, &break_time); + + nvs_get_u8(nvs, NVS_RETRY_COUNT, &retry_count); + + uint8_t u8; + if (nvs_get_u8(nvs, NVS_DETECTION_HIGH, &u8) == ESP_OK) { + detection_high = u8; + } + + gpio_config_t io_conf = {}; + + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task); + } +} + +bool socket_lock_is_detection_high(void) +{ + return detection_high; +} + +void socket_lock_set_detection_high(bool _detection_high) +{ + detection_high = _detection_high; + + nvs_set_u8(nvs, NVS_DETECTION_HIGH, detection_high); + nvs_commit(nvs); +} + +uint16_t socket_lock_get_operating_time(void) +{ + return operating_time; +} + +esp_err_t socket_lock_set_operating_time(uint16_t _operating_time) +{ + if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX) { + ESP_LOGE(TAG, "Operating time out of range"); + return ESP_ERR_INVALID_ARG; + } + + operating_time = _operating_time; + nvs_set_u16(nvs, NVS_OPERATING_TIME, operating_time); + nvs_commit(nvs); + + return ESP_OK; +} + +uint8_t socket_lock_get_retry_count(void) +{ + return retry_count; +} + +void socket_lock_set_retry_count(uint8_t _retry_count) +{ + retry_count = _retry_count; + nvs_set_u8(nvs, NVS_RETRY_COUNT, retry_count); + nvs_commit(nvs); +} + +uint16_t socket_lock_get_break_time(void) +{ + return break_time; +} + +esp_err_t socket_lock_set_break_time(uint16_t _break_time) +{ + if (_break_time < board_config.socket_lock_min_break_time) { + ESP_LOGE(TAG, "Operating time out of range"); + return ESP_ERR_INVALID_ARG; + } + + break_time = _break_time; + nvs_set_u16(nvs, NVS_BREAK_TIME, break_time); + nvs_commit(nvs); + + return ESP_OK; +} + +void socket_lock_set_locked(bool locked) +{ + ESP_LOGI(TAG, "Set locked %d", locked); + + xTaskNotify(socket_lock_task, locked ? LOCK_BIT : UNLOCK_BIT, eSetBits); + status = SOCKED_LOCK_STATUS_OPERATING; +} + +socket_lock_status_t socket_lock_get_status(void) +{ + return status; +} +// === Fim de: components/peripherals/src/socket_lock.c === + + +// === Início de: components/peripherals/src/temp_sensor.c === +#include +#include +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/gpio.h" + +#include "temp_sensor.h" +#include "lm75a.h" + +#define MAX_SENSORS 5 +#define MEASURE_PERIOD 10000 // 10s +#define MEASURE_ERR_THRESHOLD 3 + +static const char *TAG = "temp_sensor"; + +static uint8_t sensor_count = 0; + +static int16_t low_temp = 0; + +static int high_temp = 0; + +static uint8_t measure_err_count = 0; + +static void temp_sensor_task_func(void *param) +{ + while (true) + { + high_temp = lm75a_read_temperature(0); + + vTaskDelay(pdMS_TO_TICKS(MEASURE_PERIOD)); + } +} + +void temp_sensor_init(void) +{ + + ESP_LOGW(TAG, "temp_sensor_init"); + + lm75a_init(); + + xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 5, NULL); +} + +uint8_t temp_sensor_get_count(void) +{ + return sensor_count; +} + +int16_t temp_sensor_get_low(void) +{ + return low_temp; +} + +int temp_sensor_get_high(void) +{ + return high_temp; +} + +bool temp_sensor_is_error(void) +{ + return sensor_count == 0 || measure_err_count > MEASURE_ERR_THRESHOLD; +} +// === Fim de: components/peripherals/src/temp_sensor.c === + + +// === Início de: components/peripherals/src/aux_io.c === +#include +#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; +} +// === Fim de: components/peripherals/src/aux_io.c === + + +// === Início de: components/peripherals/src/lm75a.c === +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define I2C_MASTER_NUM I2C_NUM_1 +#define I2C_MASTER_SCL_IO GPIO_NUM_22 // CONFIG_EXAMPLE_I2C_SCL /*!< gpio number for I2C master clock */ +#define I2C_MASTER_SDA_IO GPIO_NUM_21 // CONFIG_EXAMPLE_I2C_SDA /*!< gpio number for I2C master data */ +#define I2C_MASTER_FREQ_HZ 100000 // CONFIG_I2C_TRANS_SPEED /*!< I2C master clock frequency */ +#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ +#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ +#define LM75A_SLAVE_ADDR 0x48 // CONFIG_LM75A_SLAVE_ADDR /*!< LM75A slave address, you can set any 7bit value */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ + +/* +#define GPIO_INPUT_IO_0 CONFIG_LM75A_OS_PIN +#define GPIO_OUTPUT_IO_0 CONFIG_LM75A_VCC_PIN +#define GPIO_OUTPUT_PIN_SEL (1ULL << GPIO_OUTPUT_IO_0) +#define GPIO_INPUT_PIN_SEL (1ULL << GPIO_INPUT_IO_0) +#define ESP_INTR_FLAG_DEFAULT 0 +*/ + +// static xQueueHandle gpio_evt_queue = NULL; +// static int gpio_int_task_enable = 0; +// static TaskHandle_t gpio_int_task_handle = NULL; + +/** + * @brief test code to read esp-i2c-slave + * We need to fill the buffer of esp slave device, then master can read them out. + * + * _______________________________________________________________________________________ + * | start | slave_addr + rd_bit +ack | read n-1 bytes + ack | read 1 byte + nack | stop | + * --------|--------------------------|----------------------|--------------------|------| + * + */ +static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t *data_rd, size_t size) +{ + if (size == 0) + { + return ESP_OK; + } + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (LM75A_SLAVE_ADDR << 1) | READ_BIT, ACK_CHECK_EN); + if (size > 1) + { + i2c_master_read(cmd, data_rd, size - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief Test code to write esp-i2c-slave + * Master device write data to slave(both esp32), + * the data will be stored in slave buffer. + * We can read them out from slave buffer. + * + * ___________________________________________________________________ + * | start | slave_addr + wr_bit + ack | write n bytes + ack | stop | + * --------|---------------------------|----------------------|------| + * + */ +static esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t *data_wr, size_t size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (LM75A_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief i2c master initialization + */ +static void i2c_master_init() +{ + int i2c_master_port = I2C_MASTER_NUM; + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = I2C_MASTER_SDA_IO; + conf.sda_pullup_en = GPIO_PULLUP_DISABLE; + conf.scl_io_num = I2C_MASTER_SCL_IO; + conf.scl_pullup_en = GPIO_PULLUP_DISABLE; + conf.master.clk_speed = I2C_MASTER_FREQ_HZ; + conf.clk_flags = 0; + + i2c_param_config(i2c_master_port, &conf); + i2c_driver_install(i2c_master_port, conf.mode, + I2C_MASTER_RX_BUF_DISABLE, + I2C_MASTER_TX_BUF_DISABLE, 0); +} + +int lm75a_read_temperature(int show) +{ + uint8_t buf[2]; + float tmp; + buf[0] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + if (show) + printf("lm75a_read_temperature=%.1f\n", tmp); + return tmp; +} + +/* +static void IRAM_ATTR gpio_isr_handler(void *arg) +{ + uint32_t gpio_num = (uint32_t)arg; + xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); +} + +static void gpio_int_task(void *arg) +{ + uint32_t io_num; + gpio_int_task_enable = 1; + while (gpio_int_task_enable) + { + if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) + { + + // read temperature to clean int; + if (io_num == GPIO_INPUT_IO_0) + { + printf("GPIO[%d] intr, val: %d\n\n", io_num, gpio_get_level(io_num)); + lm75a_read_temperature(0); // read to clean interrupt. + } + } + } + printf("quit gpio_int_task\n"); + if (gpio_evt_queue) + { + vQueueDelete(gpio_evt_queue); + gpio_evt_queue = NULL; + } + gpio_int_task_handle = NULL; + vTaskDelete(NULL); +} + +void init_os_gpio() +{ + printf("init_os_gpio!\n"); + + if (gpio_evt_queue == NULL) + gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); + + if (gpio_int_task_handle == NULL) + { + xTaskCreate(gpio_int_task, "gpio_int_task", 2048, NULL, 10, &gpio_int_task_handle); + // install gpio isr service + gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); + // hook isr handler for specific gpio pin again + gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void *)GPIO_INPUT_IO_0); + } +} + +static void deinit_os_gpio() +{ + printf("deinit_os_gpio!\n"); + + if (gpio_int_task_handle) + { + gpio_isr_handler_remove(GPIO_INPUT_IO_0); + gpio_uninstall_isr_service(); + gpio_int_task_enable = 0; + int io = 0; + xQueueSend(gpio_evt_queue, &io, 0); // send a fake signal to quit task. + } +} + +static void lm75a_vcc_enable() +{ + gpio_config_t io_conf; + // enable output for vcc + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + // enable input for interrupt + io_conf.intr_type = GPIO_PIN_INTR_NEGEDGE; // GPIO_PIN_INTR_ANYEDGE; + io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + gpio_set_pull_mode(GPIO_INPUT_IO_0, GPIO_FLOATING); + gpio_config(&io_conf); + gpio_set_level(GPIO_OUTPUT_IO_0, 1); +} + +static void lm75a_vcc_disable() +{ + gpio_set_level(GPIO_OUTPUT_IO_0, 0); +} +*/ + +void lm75a_init() +{ + // lm75a_vcc_enable(); + i2c_master_init(); +} + +void lm75a_deinit() +{ + // deinit_os_gpio(); + i2c_driver_delete(I2C_MASTER_NUM); + // lm75a_vcc_disable(); +} + +void lm75a_set_tos(int tos) +{ + uint8_t buf[4]; + printf("lm75a_set_tos: %d\n", tos); + // set Tos: + buf[0] = 0x3; + buf[1] = (tos & 0xff); + buf[2] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 3); +} + +void lm75a_set_thys(int thys) +{ + uint8_t buf[4]; + printf("lm75a_set_thys: %d\n", thys); + // set Thyst: + buf[0] = 0x2; + buf[1] = (thys & 0xff); + buf[2] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 3); +} + +void lm75a_get_tos() +{ + uint8_t buf[4]; + float tmp; + buf[0] = 0x3; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + + printf("lm75a_get_tos: %.1f\n", tmp); +} + +void lm75a_get_thys() +{ + uint8_t buf[4]; + float tmp; + buf[0] = 0x2; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + + printf("lm75a_get_thys: %.1f\n", tmp); +} + +void lm75a_set_int(int en) +{ + uint8_t buf[2]; + + en = !!en; + if (en) + { + printf("lm75a_set_int: %d\n", en); + buf[0] = 0x1; + buf[1] = (1 << 1); // D1 set to 1; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 2); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); // do one time read to clean interrupt before enter interrupt mode. + // gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_NEGEDGE); + // init_os_gpio(); + } + else + { + printf("lm75a_set_int: %d\n", en); + // deinit_os_gpio(); + buf[0] = 0x1; + buf[1] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 2); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); // do one time read to clean interrupt before enter interrupt mode. + } +} + +void lm75a_get_osio() +{ + // printf("os_io: %d\n", gpio_get_level(GPIO_INPUT_IO_0)); +} + +// === Fim de: components/peripherals/src/lm75a.c === diff --git a/projeto_parte8.c b/projeto_parte8.c new file mode 100755 index 0000000..683d4f6 --- /dev/null +++ b/projeto_parte8.c @@ -0,0 +1,783 @@ + + +// === Início de: components/peripherals/src/onewire.c === +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +#include +#include +#include +#include "rom/ets_sys.h" + +#include "onewire.h" + +#define ONEWIRE_SELECT_ROM 0x55 +#define ONEWIRE_SKIP_ROM 0xcc +#define ONEWIRE_SEARCH 0xf0 +#define ONEWIRE_CRC8_TABLE + +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + +// Waits up to `max_wait` microseconds for the specified pin to go high. +// Returns true if successful, false if the bus never comes high (likely +// shorted). +static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait) +{ + bool state; + for (int i = 0; i < ((max_wait + 4) / 5); i++) { + if (gpio_get_level(pin)) + break; + ets_delay_us(5); + } + state = gpio_get_level(pin); + // Wait an extra 1us to make sure the devices have an adequate recovery + // time before we drive things low again. + ets_delay_us(1); + return state; +} + +static void setup_pin(gpio_num_t pin, bool open_drain) +{ + gpio_set_direction(pin, open_drain ? GPIO_MODE_INPUT_OUTPUT_OD : GPIO_MODE_OUTPUT); + // gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY); +} + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return false; +// +// Returns true if a device asserted a presence pulse, false otherwise. +// +bool onewire_reset(gpio_num_t pin) +{ + setup_pin(pin, true); + + gpio_set_level(pin, 1); + // wait until the wire is high... just in case + if (!_onewire_wait_for_bus(pin, 250)) + return false; + + gpio_set_level(pin, 0); + ets_delay_us(480); + + portENTER_CRITICAL(&mux); + gpio_set_level(pin, 1); // allow it to float + ets_delay_us(70); + bool r = !gpio_get_level(pin); + portEXIT_CRITICAL(&mux); + + // Wait for all devices to finish pulling the bus low before returning + if (!_onewire_wait_for_bus(pin, 410)) + return false; + + return r; +} + +static bool _onewire_write_bit(gpio_num_t pin, bool v) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return false; + + portENTER_CRITICAL(&mux); + if (v) { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(10); + gpio_set_level(pin, 1); // allow output high + ets_delay_us(55); + } else { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(65); + gpio_set_level(pin, 1); // allow output high + } + ets_delay_us(1); + portEXIT_CRITICAL(&mux); + + return true; +} + +static int _onewire_read_bit(gpio_num_t pin) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return -1; + + portENTER_CRITICAL(&mux); + gpio_set_level(pin, 0); + ets_delay_us(2); + gpio_set_level(pin, 1); // let pin float, pull up will raise + ets_delay_us(11); + int r = gpio_get_level(pin); // Must sample within 15us of start + ets_delay_us(48); + portEXIT_CRITICAL(&mux); + + return r; +} + +// Write a byte. The writing code uses open-drain mode and expects the pullup +// resistor to pull the line high when not driven low. If you need strong +// power after the write (e.g. DS18B20 in parasite power mode) then call +// onewire_power() after this is complete to actively drive the line high. +// +bool onewire_write(gpio_num_t pin, uint8_t v) +{ + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) + if (!_onewire_write_bit(pin, (bitMask & v))) + return false; + + return true; +} + +bool onewire_write_bytes(gpio_num_t pin, const uint8_t* buf, size_t count) +{ + for (size_t i = 0; i < count; i++) + if (!onewire_write(pin, buf[i])) + return false; + + return true; +} + +// Read a byte +// +int onewire_read(gpio_num_t pin) +{ + int r = 0; + + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + int bit = _onewire_read_bit(pin); + if (bit < 0) + return -1; + else if (bit) + r |= bitMask; + } + return r; +} + +bool onewire_read_bytes(gpio_num_t pin, uint8_t* buf, size_t count) +{ + size_t i; + int b; + + for (i = 0; i < count; i++) { + b = onewire_read(pin); + if (b < 0) + return false; + buf[i] = b; + } + return true; +} + +bool onewire_select(gpio_num_t pin, onewire_addr_t addr) +{ + uint8_t i; + + if (!onewire_write(pin, ONEWIRE_SELECT_ROM)) + return false; + + for (i = 0; i < 8; i++) { + if (!onewire_write(pin, addr & 0xff)) + return false; + addr >>= 8; + } + + return true; +} + +bool onewire_skip_rom(gpio_num_t pin) +{ + return onewire_write(pin, ONEWIRE_SKIP_ROM); +} + +bool onewire_power(gpio_num_t pin) +{ + // Make sure the bus is not being held low before driving it high, or we + // may end up shorting ourselves out. + if (!_onewire_wait_for_bus(pin, 10)) + return false; + + setup_pin(pin, false); + gpio_set_level(pin, 1); + + return true; +} + +void onewire_depower(gpio_num_t pin) +{ + setup_pin(pin, true); +} + +void onewire_search_start(onewire_search_t* search) +{ + // reset the search state + memset(search, 0, sizeof(*search)); +} + +void onewire_search_prefix(onewire_search_t* search, uint8_t family_code) +{ + uint8_t i; + + search->rom_no[0] = family_code; + for (i = 1; i < 8; i++) { + search->rom_no[i] = 0; + } + search->last_discrepancy = 64; + search->last_device_found = false; +} + +// Perform a search. If the next device has been successfully enumerated, its +// ROM address will be returned. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return 1 : device found, ROM number in ROM_NO buffer +// 0 : device not found, end of search +// +onewire_addr_t onewire_search_next(onewire_search_t* search, gpio_num_t pin) +{ + //TODO: add more checking for read/write errors + uint8_t id_bit_number; + uint8_t last_zero, search_result; + int rom_byte_number; + int8_t id_bit, cmp_id_bit; + onewire_addr_t addr; + unsigned char rom_byte_mask; + bool search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!search->last_device_found) { + // 1-Wire reset + if (!onewire_reset(pin)) { + // reset the search + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } + + // issue the search command + onewire_write(pin, ONEWIRE_SEARCH); + + // loop to do the search + do { + // read a bit and its complement + id_bit = _onewire_read_bit(pin); + cmp_id_bit = _onewire_read_bit(pin); + + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < search->last_discrepancy) + search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == search->last_discrepancy); + + // if 0 was picked then record its position in LastZero + if (!search_direction) + last_zero = id_bit_number; + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction) + search->rom_no[rom_byte_number] |= rom_byte_mask; + else + search->rom_no[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + _onewire_write_bit(pin, search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set last_discrepancy,last_device_found,search_result + search->last_discrepancy = last_zero; + + // check for last device + if (search->last_discrepancy == 0) + search->last_device_found = true; + + search_result = 1; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !search->rom_no[0]) { + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } else { + addr = 0; + for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) { + addr = (addr << 8) | search->rom_no[rom_byte_number]; + } + //printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr); + } + return addr; +} + +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#ifdef ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (c) 2000 Dallas Semiconductor Corporation +static const uint8_t dscrc_table[] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, + 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, + 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, + 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, + 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, + 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, + 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, + 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, + 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, + 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, + 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, + 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, + 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, + 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 +}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t onewire_crc8(const uint8_t* data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + crc = dscrc_table[crc ^ *data++]; + + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t onewire_crc8(const uint8_t* data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + { + uint8_t inbyte = *data++; + for (int i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) + crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif /* ONEWIRE_CRC8_TABLE */ + +// Compute the 1-Wire CRC16 and compare it against the received CRC. +// Example usage (reading a DS2408): +// // Put everything in a buffer so we can compute the CRC easily. +// uint8_t buf[13]; +// buf[0] = 0xF0; // Read PIO Registers +// buf[1] = 0x88; // LSB address +// buf[2] = 0x00; // MSB address +// WriteBytes(net, buf, 3); // Write 3 cmd bytes +// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 +// if (!CheckCRC16(buf, 11, &buf[11])) { +// // Handle error. +// } +// +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param inverted_crc - The two CRC16 bytes in the received data. +// This should just point into the received data, +// *not* at a 16-bit integer. +// @param crc - The crc starting value (optional) +// @return 1, iff the CRC matches. +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv) +{ + uint16_t crc = ~onewire_crc16(input, len, crc_iv); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +// Compute a Dallas Semiconductor 16 bit CRC. This is required to check +// the integrity of data received from many 1-Wire devices. Note that the +// CRC computed here is *not* what you'll get from the 1-Wire network, +// for two reasons: +// 1) The CRC is transmitted bitwise inverted. +// 2) Depending on the endian-ness of your processor, the binary +// representation of the two-byte return value may have a different +// byte order than the two bytes you get from 1-Wire. +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param crc - The crc starting value (optional) +// @return The CRC16, as defined by Dallas Semiconductor. +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv) +{ + uint16_t crc = crc_iv; + static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + uint16_t i; + for (i = 0; i < len; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +} +// === Fim de: components/peripherals/src/onewire.c === + + +// === Início de: components/peripherals/src/onewire.h === +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +#ifndef ONEWIRE_H_ +#define ONEWIRE_H_ + +#include +#include +#include "driver/gpio.h" + +/** + * Type used to hold all 1-Wire device ROM addresses (64-bit) + */ +typedef uint64_t onewire_addr_t; + +/** + * Structure to contain the current state for onewire_search_next(), etc + */ +typedef struct +{ + uint8_t rom_no[8]; + uint8_t last_discrepancy; + bool last_device_found; +} onewire_search_t; + +/** + * ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device + * (CRC mismatch), and so can be useful as an indicator for "no-such-device", + * etc. + */ +#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL)) + +/** + * @brief Perform a 1-Wire reset cycle. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if at least one device responds with a presence pulse, + * `false` if no devices were detected (or the bus is shorted, etc) + */ +bool onewire_reset(gpio_num_t pin); + +/** + * @brief Issue a 1-Wire "ROM select" command to select a particular device. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param addr The ROM address of the device to select + * + * @return `true` if the "ROM select" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_select(gpio_num_t pin, const onewire_addr_t addr); + +/** + * @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if the "skip ROM" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_skip_rom(gpio_num_t pin); + +/** + * @brief Write a byte on the onewire bus. + * + * The writing code uses open-drain mode and expects the pullup resistor to + * pull the line high when not driven low. If you need strong power after the + * write (e.g. DS18B20 in parasite power mode) then call ::onewire_power() + * after this is complete to actively drive the line high. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param v The byte value to write + * + * @return `true` if successful, `false` on error. + */ +bool onewire_write(gpio_num_t pin, uint8_t v); + +/** + * @brief Write multiple bytes on the 1-Wire bus. + * + * See ::onewire_write() for more info. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param buf A pointer to the buffer of bytes to be written + * @param count Number of bytes to write + * + * @return `true` if all bytes written successfully, `false` on error. + */ +bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count); + +/** + * @brief Read a byte from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return the read byte on success, negative value on error. + */ +int onewire_read(gpio_num_t pin); + +/** + * @brief Read multiple bytes from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param[out] buf A pointer to the buffer to contain the read bytes + * @param count Number of bytes to read + * + * @return `true` on success, `false` on error. + */ +bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count); + +/** + * @brief Actively drive the bus high to provide extra power for certain + * operations of parasitically-powered devices. + * + * For parasitically-powered devices which need more power than can be + * provided via the normal pull-up resistor, it may be necessary for some + * operations to drive the bus actively high. This function can be used to + * perform that operation. + * + * The bus can be depowered once it is no longer needed by calling + * ::onewire_depower(), or it will be depowered automatically the next time + * ::onewire_reset() is called to start another command. + * + * @note Make sure the device(s) you are powering will not pull more current + * than the ESP32/ESP8266 is able to supply via its GPIO pins (this is + * especially important when multiple devices are on the same bus and + * they are all performing a power-intensive operation at the same time + * (i.e. multiple DS18B20 sensors, which have all been given a + * "convert T" operation by using ::onewire_skip_rom())). + * + * @note This routine will check to make sure that the bus is already high + * before driving it, to make sure it doesn't attempt to drive it high + * while something else is pulling it low (which could cause a reset or + * damage the ESP32/ESP8266). + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` on success, `false` on error. + */ +bool onewire_power(gpio_num_t pin); + +/** + * @brief Stop forcing power onto the bus. + * + * You only need to do this if you previously called ::onewire_power() to drive + * the bus high and now want to allow it to float instead. Note that + * onewire_reset() will also automatically depower the bus first, so you do + * not need to call this first if you just want to start a new operation. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + */ +void onewire_depower(gpio_num_t pin); + +/** + * @brief Clear the search state so that it will start from the beginning on + * the next call to ::onewire_search_next(). + * + * @param[out] search The onewire_search_t structure to reset. + */ +void onewire_search_start(onewire_search_t *search); + +/** + * @brief Setup the search to search for devices with the specified + * "family code". + * + * @param[out] search The onewire_search_t structure to update. + * @param family_code The "family code" to search for. + */ +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code); + +/** + * @brief Search for the next device on the bus. + * + * The order of returned device addresses is deterministic. You will always + * get the same devices in the same order. + * + * @note It might be a good idea to check the CRC to make sure you didn't get + * garbage. + * + * @return the address of the next device on the bus, or ::ONEWIRE_NONE if + * there is no next address. ::ONEWIRE_NONE might also mean that + * the bus is shorted, there are no devices, or you have already + * retrieved all of them. + */ +onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin); + +/** + * @brief Compute a Dallas Semiconductor 8 bit CRC. + * + * These are used in the ROM address and scratchpad registers to verify the + * transmitted data is correct. + */ +uint8_t onewire_crc8(const uint8_t *data, uint8_t len); + +/** + * @brief Compute the 1-Wire CRC16 and compare it against the received CRC. + * + * Example usage (reading a DS2408): + * @code{.c} + * // Put everything in a buffer so we can compute the CRC easily. + * uint8_t buf[13]; + * buf[0] = 0xF0; // Read PIO Registers + * buf[1] = 0x88; // LSB address + * buf[2] = 0x00; // MSB address + * onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes + * onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + * if (!onewire_check_crc16(buf, 11, &buf[11])) { + * // TODO: Handle error. + * } + * @endcode + * + * @param input Array of bytes to checksum. + * @param len Number of bytes in `input` + * @param inverted_crc The two CRC16 bytes in the received data. + * This should just point into the received data, + * *not* at a 16-bit integer. + * @param crc_iv The crc starting value (optional) + * + * @return `true` if the CRC matches, `false` otherwise. + */ +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv); + +/** + * @brief Compute a Dallas Semiconductor 16 bit CRC. + * + * This is required to check the integrity of data received from many 1-Wire + * devices. Note that the CRC computed here is *not* what you'll get from the + * 1-Wire network, for two reasons: + * + * 1. The CRC is transmitted bitwise inverted. + * 2. Depending on the endian-ness of your processor, the binary + * representation of the two-byte return value may have a different + * byte order than the two bytes you get from 1-Wire. + * + * @param input Array of bytes to checksum. + * @param len How many bytes are in `input`. + * @param crc_iv The crc starting value (optional) + * + * @return the CRC16, as defined by Dallas Semiconductor. + */ +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv); + + +#endif /* ONEWIRE_H_ */ +// === Fim de: components/peripherals/src/onewire.h === diff --git a/projeto_parte9.c b/projeto_parte9.c new file mode 100755 index 0000000..54217fa --- /dev/null +++ b/projeto_parte9.c @@ -0,0 +1,1232 @@ + + +// === Início de: components/peripherals/src/ds18x20.c === +/* + * Copyright (c) 2016 Grzegorz Hetman + * Copyright (c) 2016 Alex Stewart + * Copyright (c) 2018 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include +#include +#include "ds18x20.h" + +#define ds18x20_WRITE_SCRATCHPAD 0x4E +#define ds18x20_READ_SCRATCHPAD 0xBE +#define ds18x20_COPY_SCRATCHPAD 0x48 +#define ds18x20_READ_EEPROM 0xB8 +#define ds18x20_READ_PWRSUPPLY 0xB4 +#define ds18x20_SEARCHROM 0xF0 +#define ds18x20_SKIP_ROM 0xCC +#define ds18x20_READROM 0x33 +#define ds18x20_MATCHROM 0x55 +#define ds18x20_ALARMSEARCH 0xEC +#define ds18x20_CONVERT_T 0x44 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + +static const char* TAG = "ds18x20"; + +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + portENTER_CRITICAL(&mux); + onewire_write(pin, ds18x20_CONVERT_T); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + portEXIT_CRITICAL(&mux); + + if (wait){ + vTaskDelay(pdMS_TO_TICKS(750)); + onewire_depower(pin); + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer) +{ + CHECK_ARG(buffer); + + uint8_t crc; + uint8_t expected_crc; + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_READ_SCRATCHPAD); + + for (int i = 0; i < 8; i++) + buffer[i] = onewire_read(pin); + crc = onewire_read(pin); + + expected_crc = onewire_crc8(buffer, 8); + if (crc != expected_crc) + { + ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1], + buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer) +{ + CHECK_ARG(buffer); + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_WRITE_SCRATCHPAD); + + for (int i = 0; i < 3; i++) + onewire_write(pin, buffer[i]); + + return ESP_OK; +} + +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + portENTER_CRITICAL(&mux); + onewire_write(pin, ds18x20_COPY_SCRATCHPAD); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + portEXIT_CRITICAL(&mux); + + // And then it needs to keep that power up for 10ms. + vTaskDelay(pdMS_TO_TICKS(10)); + onewire_depower(pin); + + return ESP_OK; +} + +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + + *temperature = ((int16_t)temp * 625.0) / 100; + + return ESP_OK; +} + +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4; + + *temperature = (temp * 625) / 100 - 25; + + return ESP_OK; +} + +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + if ((uint8_t)addr == DS18B20_FAMILY_ID) { + return ds18b20_read_temperature(pin, addr, temperature); + } else { + return ds18s20_read_temperature(pin, addr, temperature); + } +} + +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18b20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18s20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18x20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list) +{ + CHECK_ARG(result_list && addr_count); + + CHECK(ds18x20_measure(pin, DS18X20_ANY, true)); + + return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list); +} + +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, size_t* found) +{ + CHECK_ARG(addr_list && addr_count); + + onewire_search_t search; + onewire_addr_t addr; + + *found = 0; + onewire_search_start(&search); + while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE) + { + uint8_t family_id = (uint8_t)addr; + if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID) + { + if (*found < addr_count) + addr_list[*found] = addr; + *found += 1; + } + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list) +{ + CHECK_ARG(result_list); + + esp_err_t res = ESP_OK; + for (size_t i = 0; i < addr_count; i++) + { + esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]); + if (tmp != ESP_OK) + res = tmp; + } + return res; +} + +// === Fim de: components/peripherals/src/ds18x20.c === + + +// === Início de: components/peripherals/src/led.c === +#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_error.h" +#include "evse_api.h" + +#define LED_UPDATE_INTERVAL_MS 100 +#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}; +static TimerHandle_t led_update_timer = NULL; +static evse_state_t led_state = -1; + +// ---------------------------- +// Funções Internas +// ---------------------------- + +static void led_update_timer_callback(TimerHandle_t xTimer); +static void led_update(void); +static void led_apply_by_state(evse_state_t state); + +static inline void led_gpio_write(gpio_num_t gpio, bool level) { + if (gpio != GPIO_NUM_NC) + gpio_set_level(gpio, level); +} + +static void led_timer_callback(TimerHandle_t xTimer) +{ + led_t *led = (led_t *)pvTimerGetTimerID(xTimer); + led->on = !led->on; + led_gpio_write(led->gpio, led->on); + uint32_t next_time = led->on ? led->ontime : led->offtime; + + xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME); +} + +// ---------------------------- +// 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)); + } + + if (!led_update_timer) { + led_update_timer = xTimerCreate("led_update_timer", + pdMS_TO_TICKS(LED_UPDATE_INTERVAL_MS), + pdTRUE, NULL, + led_update_timer_callback); + if (led_update_timer) { + xTimerStart(led_update_timer, BLOCK_TIME); + } else { + ESP_LOGE(TAG, "Failed to create LED update timer"); + } + } +} + +// ---------------------------- +// 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; + + // Evita reconfiguração idêntica + 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; + led_gpio_write(led->gpio, 0); + } else if (offtime == 0) { + led->on = true; + led_gpio_write(led->gpio, 1); + } else { + led->on = true; + led_gpio_write(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; + } +} + +// ---------------------------- +// Controle por Estado +// ---------------------------- + +static void led_apply_by_state(evse_state_t state) +{ + // Reset todos + 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 (state) { + case EVSE_STATE_A: + led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON); + break; + case EVSE_STATE_B1: + case EVSE_STATE_B2: + case EVSE_STATE_C1: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON); + break; + case EVSE_STATE_C2: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT); + break; + case EVSE_STATE_D1: + case EVSE_STATE_D2: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_BLINK_FAST); + break; + case EVSE_STATE_E: + case EVSE_STATE_F: + led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); + break; + default: + break; + } +} + +// ---------------------------- +// Timer Update +// ---------------------------- + +static void led_update(void) +{ + if (evse_error_is_active()) { + led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); + led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF); + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF); + return; + } + + evse_state_t current = evse_get_state(); + + if (current != led_state) { + led_state = current; + led_apply_by_state(current); + } +} + +static void led_update_timer_callback(TimerHandle_t xTimer) +{ + (void)xTimer; + led_update(); +} + +// === Fim de: components/peripherals/src/led.c === + + +// === Início de: components/peripherals/src/rcm.c === +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_log.h" + +#include "rcm.h" +#include "board_config.h" +#include "evse_api.h" + +// static bool do_test = false; + +// static bool triggered = false; + +// static bool test_triggered = false; + +// static void IRAM_ATTR rcm_isr_handler(void* arg) +// { +// if (!do_test) { +// triggered = true; +// } else { +// test_triggered = true; +// } +// } + +void rcm_init(void) +{ + if (board_config.rcm) { + gpio_config_t io_conf = {}; + + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = BIT64(board_config.rcm_test_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + io_conf.mode = GPIO_MODE_INPUT; + // io_conf.intr_type = GPIO_INTR_POSEDGE; + io_conf.pin_bit_mask = BIT64(board_config.rcm_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + //ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.rcm_gpio, rcm_isr_handler, NULL)); + } +} + +bool rcm_test(void) +{ + // do_test = true; + // test_triggered = false; + + // gpio_set_level(board_config.rcm_test_gpio, 1); + // vTaskDelay(pdMS_TO_TICKS(100)); + // gpio_set_level(board_config.rcm_test_gpio, 0); + + // do_test = false; + + // return test_triggered; + + gpio_set_level(board_config.rcm_test_gpio, 1); + vTaskDelay(pdMS_TO_TICKS(100)); + bool success = gpio_get_level(board_config.rcm_gpio) == 1; + gpio_set_level(board_config.rcm_test_gpio, 0); + + return success; +} + +bool rcm_is_triggered(void) +{ + // bool _triggered = triggered; + // if (gpio_get_level(board_config.rcm_gpio) == 0) { + // triggered = false; + // } + // return _triggered; + if (gpio_get_level(board_config.rcm_gpio) == 1) { + vTaskDelay(pdMS_TO_TICKS(1)); + return gpio_get_level(board_config.rcm_gpio) == 1; + } + + return false; +} +// === Fim de: components/peripherals/src/rcm.c === + + +// === Início de: components/peripherals/src/adc.c === +#include "adc.h" +#include "esp_log.h" + +const static char* TAG = "adc"; + +adc_oneshot_unit_handle_t adc_handle; + +adc_cali_handle_t adc_cali_handle; + +void adc_init(void) +{ + adc_oneshot_unit_init_cfg_t conf = { + .unit_id = ADC_UNIT_1 + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&conf, &adc_handle)); + + bool calibrated = false; + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "Calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle) == ESP_OK) { + calibrated = true; + } + } +#endif + +#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "Calibration scheme version is %s", "Line Fitting"); + adc_cali_line_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, +#if CONFIG_IDF_TARGET_ESP32 + .default_vref = 1100 +#endif + }; + if (adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle) == ESP_OK) { + calibrated = true; + } + } +#endif + + if (!calibrated) { + ESP_LOGE(TAG, "No calibration scheme"); + ESP_ERROR_CHECK(ESP_FAIL); + } +} +// === Fim de: components/peripherals/src/adc.c === + + +// === Início de: components/peripherals/src/adc121s021_dma.c === +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "adc121s021_dma.h" + +#define TAG "adc_dma" + +#define PIN_NUM_MOSI 23 +#define PIN_NUM_MISO 19 +#define PIN_NUM_CLK 18 +#define PIN_NUM_CS 5 + +#define SPI_HOST_USED SPI2_HOST +#define SAMPLE_SIZE_BYTES 2 +#define ADC_BITS 12 + +static spi_device_handle_t adc_spi; + +void adc121s021_dma_init(void) +{ + spi_bus_config_t buscfg = { + .mosi_io_num = PIN_NUM_MOSI, + .miso_io_num = PIN_NUM_MISO, + .sclk_io_num = PIN_NUM_CLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = SAMPLE_SIZE_BYTES, + }; + + spi_device_interface_config_t devcfg = { + .clock_speed_hz = 6000000, // 6 MHz + .mode = 0, + .spics_io_num = PIN_NUM_CS, + .queue_size = 2, + .flags = SPI_DEVICE_NO_DUMMY, + }; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST_USED, &buscfg, SPI_DMA_CH_AUTO)); + ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST_USED, &devcfg, &adc_spi)); +} + +bool adc121s021_dma_get_sample(uint16_t *sample) +{ + uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy TX + uint8_t rx_buffer[2] = {0}; + + spi_transaction_t t = { + .length = 16, // 16 bits + .tx_buffer = tx_buffer, + .rx_buffer = rx_buffer, + .flags = 0 + }; + + esp_err_t err = spi_device_transmit(adc_spi, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "SPI transmit error: %s", esp_err_to_name(err)); + return false; + } + + // Extrai os 12 bits significativos da resposta do ADC121S021 + *sample = ((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF; + + return true; +} + +// === Fim de: components/peripherals/src/adc121s021_dma.c === + + +// === Início de: components/peripherals/src/peripherals.c === +#include "peripherals.h" +#include "adc.h" +#include "led.h" +#include "buzzer.h" +#include "proximity.h" +#include "ac_relay.h" +#include "socket_lock.h" +#include "rcm.h" +#include "aux_io.h" +#include "ntc_sensor.h" + +void peripherals_init(void) +{ + ac_relay_init(); + led_init(); + buzzer_init(); + adc_init(); + proximity_init(); + // socket_lock_init(); + // rcm_init(); + //energy_meter_init(); + // aux_init(); + ntc_sensor_init(); +} +// === Fim de: components/peripherals/src/peripherals.c === + + +// === Início de: components/peripherals/include/adc121s021_dma.h === +#ifndef ADC_DMA_H_ +#define ADC_DMA_H_ + + +#include +#include + +void adc121s021_dma_init(void); +bool adc121s021_dma_get_sample(uint16_t *sample); + + +#endif /* ADC_DMA_h_ */ + +// === Fim de: components/peripherals/include/adc121s021_dma.h === + + +// === Início de: components/peripherals/include/peripherals.h === +#ifndef PERIPHERALS_H +#define PERIPHERALS_H + +void peripherals_init(void); + +#endif /* PERIPHERALS_H */ + +// === Fim de: components/peripherals/include/peripherals.h === + + +// === Início de: components/peripherals/include/rcm.h === +#ifndef RCM_H_ +#define RCM_H_ + +#include + +/** + * @brief Initialize residual current monitor + * + */ +void rcm_init(void); + +/** + * @brief Test residual current monitor + * + * @return true + * @return false + */ +bool rcm_test(void); + +/** + * @brief Residual current monitor was detected leakage + * + * @return true + * @return false + */ +bool rcm_is_triggered(void); + +#endif /* RCM_H_ */ + +// === Fim de: components/peripherals/include/rcm.h === + + +// === Início de: components/peripherals/include/aux_io.h === +#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_ */ +// === Fim de: components/peripherals/include/aux_io.h === + + +// === Início de: components/peripherals/include/led.h === +#ifndef LED_H_ +#define LED_H_ + +#include +#include + +/** + * @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_ */ + +// === Fim de: components/peripherals/include/led.h === + + +// === Início de: components/peripherals/include/buzzer.h === +#ifndef BUZZER_H_ +#define BUZZER_H_ + +#include + +/** + * @brief Inicializa o buzzer e inicia monitoramento automático do estado EVSE. + */ +void buzzer_init(void); + +/** + * @brief Liga e desliga o buzzer manualmente (uso interno ou testes). + */ +void buzzer_on(void); +void buzzer_off(void); + +/** + * @brief Ativa o buzzer por um período fixo (em milissegundos). + */ +void buzzer_beep_ms(uint16_t ms); + +#endif /* BUZZER_H_ */ + +// === Fim de: components/peripherals/include/buzzer.h === + + +// === Início de: components/peripherals/include/ac_relay.h === +#ifndef AC_RELAY_H_ +#define AC_RELAY_H_ + +#include + +/** + * @brief Inicializa o relé de corrente alternada. + */ +void ac_relay_init(void); + +/** + * @brief Define o estado do relé de corrente alternada. + * + * @param state true para ligar, false para desligar. + */ +void ac_relay_set_state(bool state); + +/** + * @brief Retorna o estado atual do relé de corrente alternada. + * + * @return true se estiver ligado, false se desligado. + */ +bool ac_relay_get_state(void); + +#endif /* AC_RELAY_H_ */ + +// === Fim de: components/peripherals/include/ac_relay.h === + + +// === Início de: components/peripherals/include/lm75a.h === +#ifndef LM75A_H +#define LM75A_H + +#include "esp_err.h" // Para o uso de tipos de erro do ESP-IDF, caso esteja utilizando. + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Inicializa o sensor LM75A. + * + * Configura o sensor para leitura e define os pinos de comunicação. + */ +esp_err_t lm75a_init(void); + +/** + * @brief Desinicializa o sensor LM75A. + * + * Libera os recursos usados pelo sensor. + */ +esp_err_t lm75a_deinit(void); + +/** + * @brief Lê a temperatura do LM75A. + * + * @param show Se for 1, a temperatura será exibida em algum tipo de log ou interface. + * Se for 0, o valor é apenas retornado sem exibição. + * @return A temperatura lida em graus Celsius. + */ +float lm75a_read_temperature(int show); + +/** + * @brief Define o valor do limite de temperatura (T_OS) para o sensor LM75A. + * + * @param tos O limite de temperatura de sobrecarga (T_OS) em graus Celsius. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_tos(int tos); + +/** + * @brief Define o valor do limite de temperatura de histerese (T_HYS) para o sensor LM75A. + * + * @param thys O limite de histerese de temperatura (T_HYS) em graus Celsius. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_thys(int thys); + +/** + * @brief Obtém o limite de temperatura de sobrecarga (T_OS) do sensor LM75A. + * + * @return O valor atual de T_OS em graus Celsius. + */ +int lm75a_get_tos(void); + +/** + * @brief Obtém o limite de temperatura de histerese (T_HYS) do sensor LM75A. + * + * @return O valor atual de T_HYS em graus Celsius. + */ +int lm75a_get_thys(void); + +/** + * @brief Habilita ou desabilita a interrupção do LM75A. + * + * @param en 1 para habilitar a interrupção, 0 para desabilitar. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_int(int en); + +/** + * @brief Obtém o estado do pino OS (Overtemperature Shutdown) do LM75A. + * + * @return 1 se o pino OS estiver ativo (indica que a temperatura de sobrecarga foi atingida), + * 0 caso contrário. + */ +int lm75a_get_osio(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LM75A_H */ + +// === Fim de: components/peripherals/include/lm75a.h === + + +// === Início de: components/peripherals/include/ntc_sensor.h === +#ifndef NTC_SENSOR_H_ +#define NTC_SENSOR_H_ + +/** + * @brief Initialize ntc senso + * + */ +void ntc_sensor_init(void); + +/** + * @brief Return temperature after temp_sensor_measure + * + * @return float + */ +float ntc_temp_sensor(void); + +#endif /* NTC_SENSOR_H_ */ + +// === Fim de: components/peripherals/include/ntc_sensor.h === + + +// === Início de: components/peripherals/include/proximity.h === +#ifndef PROXIMITY_H_ +#define PROXIMITY_H_ + +#include + +/** + * @brief Initialize proximity check + * + */ +void proximity_init(void); + +/** + * @brief Return measured value of max current on PP + * + * @return current in A + */ +uint8_t proximity_get_max_current(void); + +#endif /* PROXIMITY_H_ */ + +// === Fim de: components/peripherals/include/proximity.h === + + +// === Início de: components/peripherals/include/socket_lock.h === +#ifndef SOCKED_LOCK_H_ +#define SOCKED_LOCK_H_ + +#include "esp_err.h" + +typedef enum +{ + SOCKED_LOCK_STATUS_IDLE, + SOCKED_LOCK_STATUS_OPERATING, + SOCKED_LOCK_STATUS_LOCKING_FAIL, + SOCKED_LOCK_STATUS_UNLOCKING_FAIL +} socket_lock_status_t; + +/** + * @brief Initialize socket lock + * + */ +void socket_lock_init(void); + +/** + * @brief Get socket lock detection on high, stored in NVS + * + * @return true when locked has zero resistance + * @return false when unlocked has zero resistance + */ +bool socket_lock_is_detection_high(void); + +/** + * @brief Set socket lock detection on high, stored in NVS + * + * @param detection_high + */ +void socket_lock_set_detection_high(bool detection_high); + +/** + * @brief Get socket lock operating time + * + * @return time in ms + */ +uint16_t socket_lock_get_operating_time(void); + +/** + * @brief Set socket lock operating time + * + * @param operating_time - time in ms + * @return esp_err_t + */ +esp_err_t socket_lock_set_operating_time(uint16_t operating_time); + +/** + * @brief Get socket lock retry count + * + * @return retry count + */ +uint8_t socket_lock_get_retry_count(void); + +/** + * @brief Set socket lock retry count + * + * @param retry_count + */ +void socket_lock_set_retry_count(uint8_t retry_count); + +/** + * @brief Get socket lock break time + * + * @return time in ms + */ +uint16_t socket_lock_get_break_time(void); + +/** + * @brief Set socket lock break time + * + * @param break_time + * @return esp_err_t + */ +esp_err_t socket_lock_set_break_time(uint16_t break_time); + +/** + * @brief Set socke lock to locked / unlocked state + * + * @param locked + */ +void socket_lock_set_locked(bool locked); + +/** + * @brief Get socket lock status + * + * @return socket_lock_status_t + */ +socket_lock_status_t socket_lock_get_status(void); + +/** + * @brief Read the current physical lock state using the detection pin. + */ +bool socket_lock_is_locked_state(void); + + +#endif /* SOCKED_LOCK_H_ */ + +// === Fim de: components/peripherals/include/socket_lock.h === + + +// === Início de: components/peripherals/include/adc.h === +#ifndef ADC_H_ +#define ADC_H_ + +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" + +extern adc_oneshot_unit_handle_t adc_handle; + +extern adc_cali_handle_t adc_cali_handle; + +void adc_init(void); + +#endif /* ADC_H_ */ +// === Fim de: components/peripherals/include/adc.h === diff --git a/projeto_unificado.c b/projeto_unificado.c new file mode 100755 index 0000000..edca027 --- /dev/null +++ b/projeto_unificado.c @@ -0,0 +1,8613 @@ + + +// === Início de: main/main.c === +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_spiffs.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "driver/gpio.h" + +#include "wifi.h" +#include "board_config.h" +#include "logger.h" +#include "rest_main.h" + +#include "peripherals.h" +#include "protocols.h" +#include "evse_manager.h" +#include "evse_api.h" +#include "auth.h" +#include "loadbalancer.h" +#include "meter_manager.h" + + +#define EVSE_MANAGER_TICK_PERIOD_MS 1000 +#define AP_CONNECTION_TIMEOUT 120000 +#define RESET_HOLD_TIME 10000 +#define DEBOUNCE_TIME_MS 50 + +#define PRESS_BIT BIT0 +#define RELEASED_BIT BIT1 + +static const char *TAG = "app_main"; + +static TaskHandle_t user_input_task; +static TickType_t press_tick = 0; +static TickType_t last_interrupt_tick = 0; +static bool pressed = false; + + +// +// File system (SPIFFS) init and info +// +static void fs_info(esp_vfs_spiffs_conf_t *conf) { + size_t total = 0, used = 0; + esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used); + if (ret == ESP_OK) + ESP_LOGI(TAG, "Partition %s: total: %d, used: %d", conf->partition_label, total, used); + else + ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", esp_err_to_name(ret)); +} + +static void fs_init(void) { + esp_vfs_spiffs_conf_t cfg_conf = { + .base_path = "/cfg", + .partition_label = "cfg", + .max_files = 1, + .format_if_mount_failed = false + }; + + esp_vfs_spiffs_conf_t data_conf = { + .base_path = "/data", + .partition_label = "data", + .max_files = 5, + .format_if_mount_failed = true + }; + + ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf)); + ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf)); + + fs_info(&cfg_conf); + fs_info(&data_conf); +} + +// +// Wi-Fi event monitoring task +// +static void wifi_event_task_func(void *param) { + EventBits_t mode_bits; + while (1) { + mode_bits = xEventGroupWaitBits(wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + + if (mode_bits & WIFI_AP_MODE_BIT) { + if (xEventGroupWaitBits(wifi_event_group, WIFI_AP_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & WIFI_AP_CONNECTED_BIT) { + xEventGroupWaitBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + } else { + if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { + //wifi_ap_stop(); + } + } + } else if (mode_bits & WIFI_STA_MODE_BIT) { + xEventGroupWaitBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); + } + } +} + +// +// Botão e tratamento +// +static void handle_button_press(void) { + ESP_LOGI(TAG, "Ativando modo AP"); + if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { + wifi_ap_start(); + } +} + +static void user_input_task_func(void *param) { + uint32_t notification; + while (1) { + if (xTaskNotifyWait(0x00, 0xFF, ¬ification, portMAX_DELAY)) { + if (notification & PRESS_BIT) { + press_tick = xTaskGetTickCount(); + pressed = true; + ESP_LOGI(TAG, "Botão pressionado"); + } + + if (notification & RELEASED_BIT && pressed) { + pressed = false; + ESP_LOGI(TAG, "Botão liberado"); + handle_button_press(); + } + } + } +} + +static void IRAM_ATTR button_isr_handler(void *arg) { + BaseType_t higher_task_woken = pdFALSE; + TickType_t now = xTaskGetTickCountFromISR(); + + if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) return; + last_interrupt_tick = now; + + if (!gpio_get_level(board_config.button_wifi_gpio)) { + xTaskNotifyFromISR(user_input_task, RELEASED_BIT, eSetBits, &higher_task_woken); + } else { + xTaskNotifyFromISR(user_input_task, PRESS_BIT, eSetBits, &higher_task_woken); + } + + if (higher_task_woken) { + portYIELD_FROM_ISR(); + } +} + +static void button_init(void) { + gpio_config_t conf = { + .pin_bit_mask = BIT64(board_config.button_wifi_gpio), + .mode = GPIO_MODE_INPUT, + .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 +// +static void init_modules(void) { + peripherals_init(); + //api_init(); + ESP_ERROR_CHECK(rest_server_init("/data")); + protocols_init(); + evse_manager_init(); + evse_init(); // Cria a task para FSM + button_init(); + auth_init(); + loadbalancer_init(); + meter_manager_grid_init(); + meter_manager_grid_start(); + //meter_manager_evse_init(); + + // 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(); +} + +// +// Função principal do firmware +// +void app_main(void) { + logger_init(); + esp_log_set_vprintf(logger_vprintf); + + esp_reset_reason_t reason = esp_reset_reason(); + ESP_LOGI(TAG, "Reset reason: %d", reason); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "Erasing NVS flash"); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + fs_init(); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + + board_config_load(); + wifi_ini(); + //wifi_ap_start(); + init_modules(); + + 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 === + + +// === Início de: components/evse/evse_pilot.c === +#include +#include +#include +#include +#include + +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_rom_sys.h" + +#include "evse_pilot.h" +#include "adc.h" +#include "board_config.h" + +#define PILOT_PWM_TIMER LEDC_TIMER_0 +#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0 +#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE +#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT +#define PILOT_PWM_MAX_DUTY 1023 + +#define NUM_PILOT_SAMPLES 100 +#define MAX_SAMPLE_ATTEMPTS 1000 +#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior + +static const char *TAG = "evse_pilot"; + +void pilot_init(void) +{ + ledc_timer_config_t ledc_timer = { + .speed_mode = PILOT_PWM_SPEED_MODE, + .timer_num = PILOT_PWM_TIMER, + .duty_resolution = PILOT_PWM_DUTY_RES, + .freq_hz = 1000, + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + ledc_channel_config_t ledc_channel = { + .speed_mode = PILOT_PWM_SPEED_MODE, + .channel = PILOT_PWM_CHANNEL, + .timer_sel = PILOT_PWM_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = board_config.pilot_pwm_gpio, + .duty = 0, + .hpoint = 0 + }; + 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)); + + adc_oneshot_chan_cfg_t config = { + .bitwidth = ADC_BITWIDTH_DEFAULT, + .atten = ADC_ATTEN_DB_12, + }; + + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.pilot_adc_channel, &config)); +} + +void pilot_set_level(bool level) +{ + ESP_LOGI(TAG, "Set level %d", level); + ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); +} + +void pilot_set_amps(uint16_t amps) +{ + ESP_LOGI(TAG, "Set amps %d", amps); + + if (amps < 60 || amps > 800) { + ESP_LOGE(TAG, "Invalid ampere value: %d A*10", amps); + return; + } + + uint32_t duty; + if (amps <= 510) { + duty = (PILOT_PWM_MAX_DUTY * amps) / 600; + } else { + duty = ((PILOT_PWM_MAX_DUTY * amps) / 2500) + (64 * (PILOT_PWM_MAX_DUTY / 100)); + } + + if (duty > PILOT_PWM_MAX_DUTY) + duty = PILOT_PWM_MAX_DUTY; + + ESP_LOGI(TAG, "Set amp %dA*10 -> duty %lu/%d", amps, duty, PILOT_PWM_MAX_DUTY); + ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty); + ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); +} +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)]; +} + +void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) +{ + ESP_LOGD(TAG, "pilot_measure"); + + int samples[NUM_PILOT_SAMPLES]; + int collected = 0, attempts = 0; + int sample; + + while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { + if (adc_oneshot_read(adc_handle, board_config.pilot_adc_channel, &sample) == ESP_OK) { + samples[collected++] = sample; + esp_rom_delay_us(10); + } else { + esp_rom_delay_us(100); + attempts++; + } + } + + if (collected < NUM_PILOT_SAMPLES) { + ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES); + *up_voltage = PILOT_VOLTAGE_1; + *down_voltage_n12 = false; + return; + } + + int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); + int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); + + + ESP_LOGD(TAG, "Final: high_raw=%d, low_raw=%d", high_raw, low_raw); + + int high_mv = 0; + int low_mv = 0; + + if (adc_cali_raw_to_voltage(adc_cali_handle, high_raw, &high_mv) != ESP_OK || + adc_cali_raw_to_voltage(adc_cali_handle, low_raw, &low_mv) != ESP_OK) { + ESP_LOGW(TAG, "ADC calibration failed"); + *up_voltage = PILOT_VOLTAGE_1; + *down_voltage_n12 = false; + return; + } + + if (high_mv >= board_config.pilot_down_threshold_12) + *up_voltage = PILOT_VOLTAGE_12; + else if (high_mv >= board_config.pilot_down_threshold_9) + *up_voltage = PILOT_VOLTAGE_9; + else if (high_mv >= board_config.pilot_down_threshold_6) + *up_voltage = PILOT_VOLTAGE_6; + else if (high_mv >= board_config.pilot_down_threshold_3) + *up_voltage = PILOT_VOLTAGE_3; + else + *up_voltage = PILOT_VOLTAGE_1; + + *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12); + + ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12); +} + +// === Fim de: components/evse/evse_pilot.c === + + +// === Início de: components/evse/evse_hardware.c === +#include "evse_hardware.h" +#include "evse_pilot.h" +#include "ac_relay.h" +#include "socket_lock.h" +#include "proximity.h" + +static const char *TAG = "evse_hardware"; + +void evse_hardware_init(void) { + pilot_init(); + pilot_set_level(true); // Sinal piloto em 12V (inicial) + ac_relay_set_state(false); // Relé desligado + //socket_lock_set_locked(false); // Destrava o conector +} + +void evse_hardware_tick(void) { + // Tick para atualizações de hardware com polling, se necessário +} + +bool evse_hardware_is_pilot_high(void) { + return pilot_get_state(); // true se sinal piloto estiver em nível alto +} + +bool evse_hardware_is_vehicle_connected(void) { + // Verifica se o veículo está conectado pelo resistor do pino PP + return proximity_get_max_current() > 0; +} + +bool evse_hardware_is_energy_detected(void) { + return false; +} + +void evse_hardware_relay_on(void) { + ac_relay_set_state(true); +} + +void evse_hardware_relay_off(void) { + ac_relay_set_state(false); +} + +bool evse_hardware_relay_status(void) { + return ac_relay_get_state(); +} + +void evse_hardware_lock(void) { + socket_lock_set_locked(true); +} + +void evse_hardware_unlock(void) { + socket_lock_set_locked(false); +} + +bool evse_hardware_is_locked(void) { + return socket_lock_is_locked_state(); +} + +// === Fim de: components/evse/evse_hardware.c === + + +// === Início de: components/evse/evse_state.c === +#include "evse_state.h" +#include "evse_events.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "esp_log.h" + +static evse_state_t current_state = EVSE_STATE_A; +static bool is_authorized = false; + +// Proteção básica para variáveis globais em sistemas concorrentes +static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; + +static evse_state_event_t map_state_to_event(evse_state_t s) { + switch (s) { + case EVSE_STATE_A: + return EVSE_STATE_EVENT_IDLE; + case EVSE_STATE_B1: + return EVSE_STATE_EVENT_WAITING; + case EVSE_STATE_B2: + case EVSE_STATE_C1: + case EVSE_STATE_C2: + return EVSE_STATE_EVENT_CHARGING; + case EVSE_STATE_E: + case EVSE_STATE_F: + return EVSE_STATE_EVENT_FAULT; + default: + return EVSE_STATE_EVENT_IDLE; + } +} +void evse_set_state(evse_state_t state) { + bool changed = false; + evse_state_t previous_state; + + portENTER_CRITICAL(&state_mux); + previous_state = current_state; + if (state != current_state) { + current_state = state; + changed = true; + } + portEXIT_CRITICAL(&state_mux); + + if (changed) { + ESP_LOGI("EVSE_STATE", "Estado alterado de %s para %s", + evse_state_to_str(previous_state), + evse_state_to_str(state)); + + evse_state_event_data_t evt = { + .state = map_state_to_event(state) + }; + esp_event_post(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); + } +} + +evse_state_t evse_get_state(void) { + portENTER_CRITICAL(&state_mux); + evse_state_t s = current_state; + portEXIT_CRITICAL(&state_mux); + return s; +} + +const char* evse_state_to_str(evse_state_t state) { + switch (state) { + case EVSE_STATE_A: return "A - EV Not Connected (12V)"; + case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)"; + case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)"; + case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)"; + case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)"; + case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)"; + case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)"; + case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)"; + case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal"; + default: return "Unknown State"; + } +} + +void evse_state_init(void) { + portENTER_CRITICAL(&state_mux); + current_state = EVSE_STATE_A; + is_authorized = true; + portEXIT_CRITICAL(&state_mux); + + ESP_LOGI("EVSE_STATE", "Inicializado em estado: %s", evse_state_to_str(current_state)); + + evse_state_event_data_t evt = { + .state = map_state_to_event(current_state) + }; + esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); +} + +void evse_state_tick(void) { + // Tick do estado (placeholder) +} + +bool evse_state_is_charging(evse_state_t state) { + return state == EVSE_STATE_C1 || state == EVSE_STATE_C2; +} + +bool evse_state_is_plugged(evse_state_t state) { + return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 || + state == EVSE_STATE_C1 || state == EVSE_STATE_C2 || + state == EVSE_STATE_D1 || state == EVSE_STATE_D2; +} + +bool evse_state_is_session(evse_state_t state) { + return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2; +} + +void evse_state_set_authorized(bool authorized) { + portENTER_CRITICAL(&state_mux); + is_authorized = authorized; + portEXIT_CRITICAL(&state_mux); +} + +bool evse_state_get_authorized(void) { + portENTER_CRITICAL(&state_mux); + bool result = is_authorized; + portEXIT_CRITICAL(&state_mux); + return result; +} + +// === Fim de: components/evse/evse_state.c === + + +// === Início de: components/evse/evse_fsm.c === +// evse_fsm.c - Máquina de Estados EVSE com controle centralizado + +#include "evse_fsm.h" +#include "evse_api.h" +#include "evse_pilot.h" +#include "evse_config.h" +#include "esp_log.h" +#include "ac_relay.h" +#include "board_config.h" +#include "socket_lock.h" +#include "proximity.h" +#include "rcm.h" +#include "evse_state.h" + +static const char *TAG = "evse_fsm"; + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static bool c1_d1_waiting = false; +static TickType_t c1_d1_relay_to = 0; + +void evse_fsm_reset(void) { + evse_set_state(EVSE_STATE_A); + c1_d1_waiting = false; + c1_d1_relay_to = 0; +} + +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) { + cable_max_current = proximity_get_max_current(); + } + + 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 * 10, cable_max_current * 10)); + ac_relay_set_state(false); + break; + + case EVSE_STATE_C1: + case EVSE_STATE_D1: + pilot_set_level(true); + 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 * 10, cable_max_current * 10)); + ac_relay_set_state(true); + break; + } +} + +void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled) { + TickType_t now = xTaskGetTickCount(); + 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; + + 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; + } + } + __attribute__((fallthrough)); // Evita warning de fallthrough implícito + + case EVSE_STATE_C2: + case EVSE_STATE_D2: + if (!enabled || !available) { + evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1) ? EVSE_STATE_D1 : EVSE_STATE_C1); + break; + } + + switch (pilot_voltage) { + case PILOT_VOLTAGE_6: + evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); + break; + case PILOT_VOLTAGE_3: + evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1); + break; + case PILOT_VOLTAGE_9: + evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); + break; + case PILOT_VOLTAGE_12: + evse_set_state(EVSE_STATE_A); + break; + default: + break; + } + break; + + case EVSE_STATE_E: + break; // Sem transições a partir de E + + case EVSE_STATE_F: + if (available) { + evse_set_state(EVSE_STATE_A); + } + break; + } + + evse_state_t next = evse_get_state(); + if (next != prev) { + ESP_LOGI(TAG, "State changed: %s -> %s", evse_state_to_str(prev), evse_state_to_str(next)); + update_outputs(next); + } +} + +// === Fim de: components/evse/evse_fsm.c === + + +// === Início de: components/evse/evse_error.c === +#include "evse_error.h" +#include "evse_config.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "ntc_sensor.h" + +static const char *TAG = "evse_error"; + +static uint32_t error_bits = 0; +static TickType_t auto_clear_timeout = 0; +static bool error_cleared = false; + +void evse_error_init(void) { + // Inicialização do sistema de erros +} + +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); + ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); + } + } + + // 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"); + } + } +} + +void evse_temperature_check(void) { + float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida) + uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável + + // Log informativo com os valores + ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold); + + // Se a temperatura parecer inválida, aplica erro de sensor + if (temp_c < -40.0f || temp_c > 150.0f) { + if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado + evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT); + ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado"); + } + return; + } + + evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida + + if (temp_c >= threshold) { + if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado + evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); + ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold); + } + } else { + evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); + } +} + +uint32_t evse_get_error(void) { + return error_bits; +} + +bool evse_is_error_cleared(void) { + return error_cleared; +} + +void evse_mark_error_cleared(void) { + error_cleared = false; +} + +// Já existentes +void evse_error_set(uint32_t bitmask) { + error_bits |= bitmask; + + if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) { + auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s + } +} + +void evse_error_clear(uint32_t bitmask) { + bool had_error = error_bits != 0; + error_bits &= ~bitmask; + + if (had_error && error_bits == 0) { + error_cleared = true; + } +} + +void evse_error_tick(void) { + if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) { + evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS); + auto_clear_timeout = 0; + } +} + +bool evse_error_is_active(void) { + return error_bits != 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; +} + +// === Fim de: components/evse/evse_error.c === + + +// === Início de: components/evse/evse_core.c === +// evse_core.c - Função principal de controle do EVSE + +#include "evse_fsm.h" +#include "evse_error.h" +#include "evse_limits.h" +#include "evse_config.h" +#include "evse_api.h" +#include "evse_pilot.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" + +static const char *TAG = "evse_core"; + +static SemaphoreHandle_t mutex; + +static evse_state_t last_state = EVSE_STATE_A; + +static void evse_core_task(void *arg); + +void evse_init(void) { + ESP_LOGI(TAG, "EVSE Init"); + + mutex = xSemaphoreCreateMutex(); + + evse_check_defaults(); + evse_fsm_reset(); + pilot_set_level(true); // Estado inicial do piloto + + xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); +} + +void evse_process(void) { + xSemaphoreTake(mutex, portMAX_DELAY); + + pilot_voltage_t pilot_voltage; + bool is_n12v = false; + + pilot_measure(&pilot_voltage, &is_n12v); + ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no"); + + if (evse_get_error() == 0 && !evse_is_error_cleared()) { + + evse_error_check(pilot_voltage, is_n12v); + + evse_fsm_process( + pilot_voltage, + evse_state_get_authorized(), + evse_config_is_available(), + evse_config_is_enabled() + ); + + evse_limits_check(evse_get_state()); + + 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; + } + + evse_mark_error_cleared(); + } + + xSemaphoreGive(mutex); +} + + +// ================================ +// Interface pública +// ================================ + +bool evse_is_enabled(void) { + return evse_config_is_enabled(); +} + +void evse_set_enabled(bool value) { + ESP_LOGI(TAG, "Set enabled %d", value); + evse_config_set_enabled(value); +} + +bool evse_is_available(void) { + return evse_config_is_available(); +} + +void evse_set_available(bool value) { + ESP_LOGI(TAG, "Set available %d", value); + evse_config_set_available(value); +} + +// ================================ +// Tarefa principal +// ================================ + +static void evse_core_task(void *arg) { + while (true) { + evse_process(); + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +// === Fim de: components/evse/evse_core.c === + + +// === Início de: components/evse/evse_limits.c === +#include "evse_limits.h" +#include +#include + +// ======================== +// Estado interno +// ======================== + +static bool limit_reached = false; +static uint32_t consumption_limit = 0; +static uint32_t charging_time_limit = 0; +static uint16_t under_power_limit = 0; + +static uint32_t default_consumption_limit = 0; +static uint32_t default_charging_time_limit = 0; +static uint16_t default_under_power_limit = 0; + +// ======================== +// Estado de controle +// ======================== + +void evse_set_limit_reached(uint8_t value) { + limit_reached = (value != 0); +} + +bool evse_is_limit_reached(void) { + return limit_reached; +} + +// ======================== +// Limites em tempo de execução +// ======================== + +uint32_t evse_get_consumption_limit(void) { + return consumption_limit; +} + +void evse_set_consumption_limit(uint32_t value) { + consumption_limit = value; +} + +uint32_t evse_get_charging_time_limit(void) { + return charging_time_limit; +} + +void evse_set_charging_time_limit(uint32_t value) { + charging_time_limit = value; +} + +uint16_t evse_get_under_power_limit(void) { + return under_power_limit; +} + +void evse_set_under_power_limit(uint16_t value) { + under_power_limit = value; +} + +// ======================== +// Limites padrão (persistentes) +// ======================== + +uint32_t evse_get_default_consumption_limit(void) { + return default_consumption_limit; +} + +void evse_set_default_consumption_limit(uint32_t value) { + default_consumption_limit = value; +} + +uint32_t evse_get_default_charging_time_limit(void) { + return default_charging_time_limit; +} + +void evse_set_default_charging_time_limit(uint32_t value) { + default_charging_time_limit = value; +} + +uint16_t evse_get_default_under_power_limit(void) { + return default_under_power_limit; +} + +void evse_set_default_under_power_limit(uint16_t value) { + default_under_power_limit = value; +} + +// ======================== +// Lógica de verificação de limites +// ======================== + +void evse_limits_check(evse_state_t state) { + // Se algum limite estiver ativo, verifique o estado + if ((consumption_limit > 0 || charging_time_limit > 0 || under_power_limit > 0) + && evse_state_is_charging(state)) { + // (Lógica real a ser aplicada aqui, ex: medição de consumo, tempo ou potência) + evse_set_limit_reached(1); + } +} + +// === Fim de: components/evse/evse_limits.c === + + +// === Início de: components/evse/evse_config.c === +#include // For PRI macros +#include "evse_config.h" +#include "board_config.h" +#include "evse_limits.h" +#include "esp_log.h" +#include "nvs.h" + +static const char *TAG = "evse_config"; + +static nvs_handle_t nvs; + +// ======================== +// Configurable parameters +// ======================== +static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT; +static uint16_t charging_current; // Persisted (NVS) +static uint16_t charging_current_runtime = 0; // Runtime only +static bool socket_outlet; +static bool rcm; +static uint8_t temp_threshold = 60; +static bool require_auth; + +// ======================== +// Initialization +// ======================== +esp_err_t evse_config_init(void) { + ESP_LOGD(TAG, "Initializing NVS configuration..."); + return nvs_open("evse", NVS_READWRITE, &nvs); +} + +void evse_check_defaults(void) { + esp_err_t err; + uint8_t u8; + uint16_t u16; + uint32_t u32; + bool needs_commit = false; + + ESP_LOGD(TAG, "Checking default parameters..."); + + // Max charging current + err = nvs_get_u8(nvs, "max_chrg_curr", &u8); + if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) { + max_charging_current = MAX_CHARGING_CURRENT_LIMIT; + nvs_set_u8(nvs, "max_chrg_curr", max_charging_current); + needs_commit = true; + ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current); + } else { + max_charging_current = u8; + } + + // Charging current (default, persisted) + err = nvs_get_u16(nvs, "def_chrg_curr", &u16); + if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT * 10) || u16 > (max_charging_current * 10)) { + charging_current = max_charging_current * 10; + nvs_set_u16(nvs, "def_chrg_curr", charging_current); + needs_commit = true; + ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current); + } else { + charging_current = u16; + } + + // Runtime charging current initialized from persisted default + charging_current_runtime = charging_current; + ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime); + + // Auth required + err = nvs_get_u8(nvs, "require_auth", &u8); + require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false; + if (err != ESP_OK) { + nvs_set_u8(nvs, "require_auth", require_auth); + needs_commit = true; + } + + // Socket outlet + err = nvs_get_u8(nvs, "socket_outlet", &u8); + socket_outlet = (err == ESP_OK && u8) && board_config.proximity; + if (err != ESP_OK) { + nvs_set_u8(nvs, "socket_outlet", socket_outlet); + needs_commit = true; + } + + // RCM + err = nvs_get_u8(nvs, "rcm", &u8); + rcm = (err == ESP_OK && u8) && board_config.rcm; + if (err != ESP_OK) { + nvs_set_u8(nvs, "rcm", rcm); + needs_commit = true; + } + + // Temp threshold + err = nvs_get_u8(nvs, "temp_threshold", &u8); + temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60; + if (err != ESP_OK) { + nvs_set_u8(nvs, "temp_threshold", temp_threshold); + needs_commit = true; + } + + // Optional limits + if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK) + evse_set_consumption_limit(u32); + + if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK) + evse_set_charging_time_limit(u32); + + if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) + evse_set_under_power_limit(u16); + + // Save to NVS if needed + if (needs_commit) { + err = nvs_commit(nvs); + if (err == ESP_OK) { + ESP_LOGD(TAG, "Configuration committed to NVS."); + } else { + ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); + } + } +} + +// ======================== +// Charging current getters/setters +// ======================== +uint8_t evse_get_max_charging_current(void) { + return max_charging_current; +} + +esp_err_t evse_set_max_charging_current(uint8_t value) { + if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT) + return ESP_ERR_INVALID_ARG; + max_charging_current = value; + nvs_set_u8(nvs, "max_chrg_curr", value); + return nvs_commit(nvs); +} + +uint16_t evse_get_charging_current(void) { + return charging_current; +} + +esp_err_t evse_set_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) + return ESP_ERR_INVALID_ARG; + charging_current = value; + nvs_set_u16(nvs, "def_chrg_curr", value); + return nvs_commit(nvs); +} + +uint16_t evse_get_default_charging_current(void) { + uint16_t value; + if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK) + return value; + return charging_current; +} + +esp_err_t evse_set_default_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) + return ESP_ERR_INVALID_ARG; + nvs_set_u16(nvs, "def_chrg_curr", value); + return nvs_commit(nvs); +} + +// ======================== +// Runtime current (not saved) +// ======================== +void evse_set_runtime_charging_current(uint16_t value) { + if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) { + ESP_LOGW(TAG, "Rejected runtime charging current (out of bounds): %d", value); + return; + } + charging_current_runtime = value; + ESP_LOGD(TAG, "Runtime charging current updated: %d", charging_current_runtime); +} + +uint16_t evse_get_runtime_charging_current(void) { + return charging_current_runtime; +} + + +// ======================== +// Socket outlet +// ======================== +bool evse_get_socket_outlet(void) { + return socket_outlet; +} + +esp_err_t evse_set_socket_outlet(bool value) { + if (value && !board_config.proximity) + return ESP_ERR_INVALID_ARG; + socket_outlet = value; + nvs_set_u8(nvs, "socket_outlet", value); + return nvs_commit(nvs); +} + +// ======================== +// RCM +// ======================== +bool evse_is_rcm(void) { + return rcm; +} + +esp_err_t evse_set_rcm(bool value) { + if (value && !board_config.rcm) + return ESP_ERR_INVALID_ARG; + rcm = value; + nvs_set_u8(nvs, "rcm", value); + return nvs_commit(nvs); +} + +// ======================== +// Temperature +// ======================== +uint8_t evse_get_temp_threshold(void) { + return temp_threshold; +} + +esp_err_t evse_set_temp_threshold(uint8_t value) { + if (value < 40 || value > 80) + return ESP_ERR_INVALID_ARG; + temp_threshold = value; + nvs_set_u8(nvs, "temp_threshold", value); + return nvs_commit(nvs); +} + +// ======================== +// Authentication +// ======================== +bool evse_is_require_auth(void) { + return require_auth; +} + +void evse_set_require_auth(bool value) { + require_auth = value; + nvs_set_u8(nvs, "require_auth", value); + nvs_commit(nvs); +} + +// ======================== +// Availability +// ======================== +static bool is_available = true; + +bool evse_config_is_available(void) { + return is_available; +} + +void evse_config_set_available(bool available) { + is_available = available; +} + +// ======================== +// Enable/Disable +// ======================== +static bool is_enabled = true; + +bool evse_config_is_enabled(void) { + return is_enabled; +} + +void evse_config_set_enabled(bool enabled) { + is_enabled = enabled; +} + +// === Fim de: components/evse/evse_config.c === + + +// === Início de: components/evse/evse_manager.c === +#include "evse_manager.h" +#include "evse_state.h" +#include "evse_error.h" +#include "evse_hardware.h" +#include "evse_config.h" +#include "evse_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include + +#include "auth_events.h" +#include "loadbalancer_events.h" +#include "esp_event.h" + +static const char *TAG = "EVSE_Manager"; + +static SemaphoreHandle_t evse_mutex; +static bool auth_enabled = false; + +#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo + +// ===== Task de ciclo principal ===== +static void evse_manager_task(void *arg) { + while (true) { + evse_manager_tick(); + vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS)); + } +} + +// ===== Tratador de eventos de autenticação ===== +static void on_auth_event(void* arg, esp_event_base_t base, int32_t id, void* data) { + if (base != AUTH_EVENTS || data == NULL) return; + + switch (id) { + case AUTH_EVENT_TAG_PROCESSED: { + auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data; + ESP_LOGI("EVSE", "Tag: %s | Autorizada: %s", evt->tag, evt->authorized ? "SIM" : "NÃO"); + evse_state_set_authorized(evt->authorized); + break; + } + + case AUTH_EVENT_ENABLED_CHANGED: + case AUTH_EVENT_INIT: { + auth_enabled_event_data_t *evt = (auth_enabled_event_data_t*)data; + auth_enabled = evt->enabled; + + ESP_LOGI("EVSE", "Auth %s (%s)", + id == AUTH_EVENT_ENABLED_CHANGED ? "ficou" : "init", + evt->enabled ? "ATIVO" : "INATIVO"); + + if (!auth_enabled) { + evse_state_set_authorized(true); + ESP_LOGI("EVSE", "Autenticação desativada → autorização forçada."); + } else { + evse_state_set_authorized(false); + ESP_LOGI("EVSE", "Autenticação ativada → aguardando autorização por tag."); + } + break; + } + } +} + +// ===== Tratador de eventos de loadbalancer ===== +static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) { + 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", evt->timestamp_us); + // Ações adicionais podem ser adicionadas aqui conforme necessário + } else if (event_id == LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED) { + const loadbalancer_charging_limit_event_t* evt = (const loadbalancer_charging_limit_event_t*) event_data; + ESP_LOGD(TAG, "Novo limite de corrente: %.1f A (ts: %lld)", evt->limit, evt->timestamp_us); + evse_set_runtime_charging_current((uint16_t)(evt->limit)); + } +} + +// ===== Inicialização ===== +void evse_manager_init(void) { + evse_mutex = xSemaphoreCreateMutex(); + + evse_config_init(); + evse_error_init(); + evse_hardware_init(); + evse_state_init(); + + 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_LOGI(TAG, "EVSE Manager inicializado."); + xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL); +} + +// ===== Main Tick ===== +void evse_manager_tick(void) { + xSemaphoreTake(evse_mutex, portMAX_DELAY); + + evse_hardware_tick(); + evse_error_tick(); + evse_state_tick(); + evse_temperature_check(); + + if (auth_enabled) { + // If the car is disconnected, revoke authorization + if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) { + ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); + evse_state_set_authorized(false); + } + } else { + // If authentication is disabled, ensure authorization is always granted + if (!evse_state_get_authorized()) { + evse_state_set_authorized(true); + ESP_LOGI(TAG, "Authentication disabled → forced authorization."); + } + } + + xSemaphoreGive(evse_mutex); +} + + +// ===== API pública ===== +bool evse_manager_is_available(void) { + return evse_config_is_available(); +} + +void evse_manager_set_available(bool available) { + evse_config_set_available(available); +} + +void evse_manager_set_authorized(bool authorized) { + evse_state_set_authorized(authorized); +} + +bool evse_manager_is_charging(void) { + return evse_state_is_charging(evse_get_state()); +} + +void evse_manager_set_enabled(bool enabled) { + evse_config_set_enabled(enabled); +} + +bool evse_manager_is_enabled(void) { + return evse_config_is_enabled(); +} + +// === Fim de: components/evse/evse_manager.c === + + +// === Início de: components/evse/evse_events.c === +#include "evse_events.h" + +ESP_EVENT_DEFINE_BASE(EVSE_EVENTS); + +// === Fim de: components/evse/evse_events.c === + + +// === Início de: components/evse/include/evse_pilot.h === +#ifndef PILOT_H_ +#define PILOT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Níveis categóricos de tensão no sinal CP (Control Pilot) + */ +typedef enum +{ + PILOT_VOLTAGE_12, ///< Estado A: +12V + PILOT_VOLTAGE_9, ///< Estado B: +9V + PILOT_VOLTAGE_6, ///< Estado C: +6V + PILOT_VOLTAGE_3, ///< Estado D: +3V + PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V +} pilot_voltage_t; + +/** + * @brief Inicializa o driver do sinal Pilot + */ +void pilot_init(void); + +/** + * @brief Define o nível do Pilot: +12V ou -12V + * + * @param level true = +12V, false = -12V + */ +void pilot_set_level(bool level); + +/** + * @brief Ativa o PWM do Pilot com corrente limitada + * + * @param amps Corrente em décimos de ampère (ex: 160 = 16A) + */ +void pilot_set_amps(uint16_t amps); + +/** + * @brief Mede o nível de tensão do Pilot e detecta -12V + * + * @param up_voltage Valor categórico da tensão positiva + * @param down_voltage_n12 true se o nível negativo atingir -12V + */ +void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12); + +/** + * @brief Retorna o estado lógico atual do Pilot (nível alto = +12V) + * + * @return true se nível atual for +12V, false se for -12V + */ +bool pilot_get_state(void); + +/** + * @brief Cache interno opcional dos níveis de tensão reais do Pilot + */ +typedef struct { + uint16_t high_mv; ///< Pico positivo medido (mV) + uint16_t low_mv; ///< Pico negativo medido (mV) +} pilot_voltage_cache_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PILOT_H_ */ + +// === Fim de: components/evse/include/evse_pilot.h === + + +// === Início de: components/evse/include/evse_manager.h === +#ifndef EVSE_MANAGER_H +#define EVSE_MANAGER_H + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/** + * @brief Inicializa os módulos internos do EVSE (hardware, estado, erros, etc.) + * e inicia a tarefa de supervisão periódica (tick). + */ +void evse_manager_init(void); + +/** + * @brief Executa uma iteração do ciclo de controle do EVSE. + * + * Esta função é chamada automaticamente pela task periódica, + * mas pode ser chamada manualmente em testes. + */ +void evse_manager_tick(void); + +/** + * @brief Verifica se o EVSE está disponível para uso. + * + * Isso considera falhas críticas, disponibilidade configurada, etc. + */ +bool evse_manager_is_available(void); + +/** + * @brief Define se o EVSE deve estar disponível (ex: via controle remoto). + */ +void evse_manager_set_available(bool available); + +/** + * @brief Define se o EVSE está autorizado a carregar (ex: após autenticação). + */ +void evse_manager_set_authorized(bool authorized); + +/** + * @brief Verifica se o EVSE está atualmente carregando. + */ +bool evse_manager_is_charging(void); + +/** + * @brief Ativa ou desativa logicamente o EVSE (controla habilitação geral). + */ +void evse_manager_set_enabled(bool enabled); + +/** + * @brief Verifica se o EVSE está ativado logicamente. + */ +bool evse_manager_is_enabled(void); + +#ifdef __cplusplus +} +#endif + + +#endif // EVSE_MANAGER_H + +// === Fim de: components/evse/include/evse_manager.h === + + +// === Início de: components/evse/include/evse_fsm.h === +#ifndef EVSE_FSM_H +#define EVSE_FSM_H + +#include +#include +#include "evse_api.h" +#include "evse_pilot.h" +#include "freertos/FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reinicia a máquina de estados do EVSE para o estado inicial (A). + */ +void evse_fsm_reset(void); + +/** + * @brief Processa uma leitura do sinal de piloto e atualiza a máquina de estados do EVSE. + * + * Esta função deve ser chamada periodicamente pelo núcleo de controle para + * avaliar mudanças no estado do conector, disponibilidade do carregador e + * autorização do usuário. + * + * @param pilot_voltage Leitura atual da tensão do sinal piloto. + * @param authorized Indica se o carregamento foi autorizado. + * @param available Indica se o carregador está disponível (ex: sem falhas). + * @param enabled Indica se o carregador está habilitado via software. + */ +void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_FSM_H + +// === Fim de: components/evse/include/evse_fsm.h === + + +// === Início de: components/evse/include/evse_hardware.h === +#ifndef EVSE_HARDWARE_H +#define EVSE_HARDWARE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Inicializa todos os periféricos de hardware do EVSE (pilot, relé, trava, etc.) + */ +void evse_hardware_init(void); + +/** + * @brief Executa atualizações periódicas no hardware (tick) + */ +void evse_hardware_tick(void); + +/** + * @brief Verifica se o sinal piloto está em nível alto (12V) + */ +bool evse_hardware_is_pilot_high(void); + +/** + * @brief Verifica se o veículo está fisicamente conectado via Proximity + */ +bool evse_hardware_is_vehicle_connected(void); + +/** + * @brief Verifica se há consumo de energia (corrente detectada) + */ +bool evse_hardware_is_energy_detected(void); + +/** + * @brief Liga o relé de fornecimento de energia + */ +void evse_hardware_relay_on(void); + +/** + * @brief Desliga o relé de fornecimento de energia + */ +void evse_hardware_relay_off(void); + +/** + * @brief Consulta o estado atual do relé + * @return true se ligado, false se desligado + */ +bool evse_hardware_relay_status(void); + +/** + * @brief Aciona a trava física do conector + */ +void evse_hardware_lock(void); + +/** + * @brief Libera a trava física do conector + */ +void evse_hardware_unlock(void); + +/** + * @brief Verifica se o conector está travado + */ +bool evse_hardware_is_locked(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_HARDWARE_H + +// === Fim de: components/evse/include/evse_hardware.h === + + +// === Início de: components/evse/include/evse_config.h === +#ifndef EVSE_CONFIG_H +#define EVSE_CONFIG_H + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ======================== +// Limites Globais (Defines) +// ======================== + +// Corrente máxima de carregamento (configurável pelo usuário) +#define MIN_CHARGING_CURRENT_LIMIT 6 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A + +// Corrente via cabo (proximity) — se configurável +#define MIN_CABLE_CURRENT_LIMIT 6 // A +#define MAX_CABLE_CURRENT_LIMIT 63 // A + +// ======================== +// Funções de Configuração +// ======================== + +// Inicialização +esp_err_t evse_config_init(void); +void evse_check_defaults(void); + +// Corrente de carregamento +uint8_t evse_get_max_charging_current(void); +esp_err_t evse_set_max_charging_current(uint8_t value); + +uint16_t evse_get_charging_current(void); +esp_err_t evse_set_charging_current(uint16_t value); + +uint16_t evse_get_default_charging_current(void); +esp_err_t evse_set_default_charging_current(uint16_t value); + +// Configuração de socket outlet +bool evse_get_socket_outlet(void); +esp_err_t evse_set_socket_outlet(bool socket_outlet); + +void evse_set_runtime_charging_current(uint16_t value); +uint16_t evse_get_runtime_charging_current(void); + + +// RCM +bool evse_is_rcm(void); +esp_err_t evse_set_rcm(bool rcm); + +// Temperatura +uint8_t evse_get_temp_threshold(void); +esp_err_t evse_set_temp_threshold(uint8_t threshold); + +// Autenticação +bool evse_is_require_auth(void); +void evse_set_require_auth(bool require); + +// Disponibilidade +bool evse_config_is_available(void); +void evse_config_set_available(bool available); + +// Ativação/desativação do EVSE +bool evse_config_is_enabled(void); +void evse_config_set_enabled(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_CONFIG_H + +// === Fim de: components/evse/include/evse_config.h === + + +// === Início de: components/evse/include/evse_state.h === +#ifndef EVSE_STATE_H +#define EVSE_STATE_H + +#include "evse_events.h" + + +#include + +// Estado do EVSE (pilot signal) +typedef enum { + EVSE_STATE_A, + EVSE_STATE_B1, + EVSE_STATE_B2, + EVSE_STATE_C1, + EVSE_STATE_C2, + EVSE_STATE_D1, + EVSE_STATE_D2, + EVSE_STATE_E, + EVSE_STATE_F +} evse_state_t; + + +// Funções públicas necessárias +void evse_state_init(void); +void evse_state_tick(void); + +void evse_state_set_authorized(bool authorized); +bool evse_state_get_authorized(void); + + +evse_state_t evse_get_state(void); + +void evse_set_state(evse_state_t state); + +// Converte o estado para string +const char* evse_state_to_str(evse_state_t state); + +// Retorna true se o estado representa sessão ativa +bool evse_state_is_session(evse_state_t state); + +// Retorna true se o estado representa carregamento ativo +bool evse_state_is_charging(evse_state_t state); + +// Retorna true se o estado representa veículo conectado +bool evse_state_is_plugged(evse_state_t state); + +//evse_state_event_t map_state_to_event(evse_state_t state); + +#endif // EVSE_STATE_H + +// === Fim de: components/evse/include/evse_state.h === + + +// === Início de: components/evse/include/evse_error.h === +#ifndef EVSE_ERROR_H +#define EVSE_ERROR_H + +#include +#include +#include "evse_pilot.h" + + +#define EVSE_ERR_AUTO_CLEAR_BITS ( \ + EVSE_ERR_DIODE_SHORT_BIT | \ + EVSE_ERR_TEMPERATURE_HIGH_BIT | \ + EVSE_ERR_RCM_TRIGGERED_BIT ) + +// 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) + +// 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); +bool evse_error_cleared_flag(void); + +#endif // EVSE_ERROR_H + +// === Fim de: components/evse/include/evse_error.h === + + +// === Início de: components/evse/include/evse_limits.h === +#ifndef EVSE_LIMITS_H +#define EVSE_LIMITS_H + +#include +#include +#include "evse_state.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Estado dos limites +void evse_set_limit_reached(uint8_t value); +bool evse_is_limit_reached(void); + +/// Verifica e aplica lógica de limites com base no estado atual do EVSE +void evse_limits_check(evse_state_t state); + +/// Limites ativos (runtime) +uint32_t evse_get_consumption_limit(void); +void evse_set_consumption_limit(uint32_t value); + +uint32_t evse_get_charging_time_limit(void); +void evse_set_charging_time_limit(uint32_t value); + +uint16_t evse_get_under_power_limit(void); +void evse_set_under_power_limit(uint16_t value); + +/// Limites padrão (persistentes) +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 === + + +// === Início de: components/evse/include/evse_events.h === +#ifndef EVSE_EVENTS_H +#define EVSE_EVENTS_H + +#pragma once +#include "esp_event.h" + +ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); + +typedef enum { + EVSE_EVENT_INIT, + EVSE_EVENT_STATE_CHANGED, + // Outros eventos possíveis futuramente +} evse_event_id_t; + +typedef enum { + EVSE_STATE_EVENT_IDLE, + EVSE_STATE_EVENT_WAITING, + EVSE_STATE_EVENT_CHARGING, + EVSE_STATE_EVENT_FAULT +} evse_state_event_t; + +typedef struct { + evse_state_event_t state; +} evse_state_event_data_t; + + +#endif // EVSE_EVENTS_H + +// === Fim de: components/evse/include/evse_events.h === + + +// === Início de: components/evse/include/evse_api.h === +#ifndef EVSE_API_H +#define EVSE_API_H + +#include +#include +#include "esp_err.h" +#include "evse_state.h" // Define evse_state_t + +// Inicialização +void evse_init(void); +void evse_process(void); + +// Estado +evse_state_t evse_get_state(void); +const char* evse_state_to_str(evse_state_t state); +bool evse_is_connector_plugged(evse_state_t state); +bool evse_is_limit_reached(void); + +// Autorização e disponibilidade +bool evse_is_enabled(void); +void evse_set_enabled(bool value); +bool evse_is_available(void); +void evse_set_available(bool value); +bool evse_is_require_auth(void); +void evse_set_require_auth(bool value); + +// Corrente +uint16_t evse_get_charging_current(void); +esp_err_t evse_set_charging_current(uint16_t value); +uint16_t evse_get_default_charging_current(void); +esp_err_t evse_set_default_charging_current(uint16_t value); +uint8_t evse_get_max_charging_current(void); +esp_err_t evse_set_max_charging_current(uint8_t value); + +// Temperatura +uint8_t evse_get_temp_threshold(void); +esp_err_t evse_set_temp_threshold(uint8_t value); + +// RCM / Socket +bool evse_get_socket_outlet(void); +esp_err_t evse_set_socket_outlet(bool value); +bool evse_is_rcm(void); +esp_err_t evse_set_rcm(bool value); + +// Limites +uint32_t evse_get_consumption_limit(void); +void evse_set_consumption_limit(uint32_t value); +uint32_t evse_get_charging_time_limit(void); +void evse_set_charging_time_limit(uint32_t value); +uint16_t evse_get_under_power_limit(void); +void evse_set_under_power_limit(uint16_t value); + +void evse_set_limit_reached(uint8_t value); + +// Limites default +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); + +#endif // EVSE_API_H + +// === Fim de: components/evse/include/evse_api.h === + + +// === Início de: components/loadbalancer/src/loadbalancer_events.c === +#include "loadbalancer_events.h" + +// Define a base de eventos para o loadbalancer +ESP_EVENT_DEFINE_BASE(LOADBALANCER_EVENTS); + +// === Fim de: components/loadbalancer/src/loadbalancer_events.c === + + +// === Início de: components/loadbalancer/src/loadbalancer.c === +#include "loadbalancer.h" +#include "loadbalancer_events.h" +#include "esp_event.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "input_filter.h" +#include "nvs_flash.h" +#include "nvs.h" +#include +#include "meter_events.h" +#include "evse_events.h" + + + +static const char *TAG = "loadbalancer"; + +// Limites configuráveis +#define MIN_CHARGING_CURRENT_LIMIT 6 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A +#define MIN_GRID_CURRENT_LIMIT 6 // A +#define MAX_GRID_CURRENT_LIMIT 100 // A + +// Parâmetros +static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; +static bool loadbalancer_enabled = false; + +static float grid_current = 0.0f; +static float evse_current = 0.0f; +static input_filter_t grid_filter; +static input_filter_t evse_filter; + +#define NVS_NAMESPACE "loadbalancing" +#define NVS_MAX_GRID_CURRENT "max_grid_curr" +#define NVS_LOADBALANCER_ENABLED "enabled" + +static void loadbalancer_meter_event_handler(void *handler_arg, + esp_event_base_t base, + int32_t id, + void *event_data) +{ + if (id != METER_EVENT_DATA_READY || event_data == NULL) + return; + + const meter_event_data_t *evt = (const meter_event_data_t *)event_data; + + ESP_LOGI(TAG, "Received meter event from source: %s", evt->source); + ESP_LOGI(TAG, "Raw IRMS: [%.2f, %.2f, %.2f] A", evt->irms[0], evt->irms[1], evt->irms[2]); + ESP_LOGI(TAG, "Raw VRMS: [%.1f, %.1f, %.1f] V", evt->vrms[0], evt->vrms[1], evt->vrms[2]); + ESP_LOGI(TAG, "Raw Power: [W1=%d, W2=%d, W3=%d]", evt->watt[0], evt->watt[1], evt->watt[2]); + ESP_LOGI(TAG, "Freq: %.2f Hz | PF: %.2f | Energy: %.3f kWh", + evt->frequency, evt->power_factor, evt->total_energy); + + // Calcula a corrente máxima entre as 3 fases + float max_irms = evt->irms[0]; + for (int i = 1; i < 3; ++i) + { + if (evt->irms[i] > max_irms) + { + max_irms = evt->irms[i]; + } + } + + ESP_LOGI(TAG, "Max IRMS detected: %.2f A", max_irms); + + // Atualiza com filtro exponencial dependendo da origem + if (strncmp(evt->source, "GRID", 4) == 0) + { + grid_current = input_filter_update(&grid_filter, max_irms); + ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current); + } + else if (strncmp(evt->source, "EVSE", 4) == 0) + { + evse_current = input_filter_update(&evse_filter, max_irms); + ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current); + } + else + { + ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source); + } +} + +static void loadbalancer_evse_event_handler(void *handler_arg, + esp_event_base_t base, + int32_t id, + void *event_data) +{ + const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data; + + ESP_LOGI(TAG, "EVSE state changed: %d", evt->state); + + switch (evt->state) + { + case EVSE_STATE_EVENT_IDLE: + // Vehicle is disconnected - current flow can be reduced or reset + ESP_LOGI(TAG, "EVSE is IDLE - possible to release current"); + break; + + case EVSE_STATE_EVENT_WAITING: + // EV is connected but not charging yet (e.g., waiting for authorization) + ESP_LOGI(TAG, "EVSE is waiting - connected but not charging"); + break; + + case EVSE_STATE_EVENT_CHARGING: + grid_current = 0.0f; + evse_current = 0.0f; + // Charging has started - maintain or monitor current usage + ESP_LOGI(TAG, "EVSE is charging"); + break; + + case EVSE_STATE_EVENT_FAULT: + // A fault has occurred - safety measures may be needed + ESP_LOGW(TAG, "EVSE is in FAULT - temporarily disabling load balancing"); + // Optional: disable load balancing during fault condition + // loadbalancer_set_enabled(false); + break; + + default: + ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state); + break; + } +} + +// Carrega configuração do NVS +static esp_err_t loadbalancer_load_config() +{ + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS for load/init: %s", esp_err_to_name(err)); + return err; + } + + bool needs_commit = false; + uint8_t temp_u8; + + // max_grid_current + err = nvs_get_u8(handle, NVS_MAX_GRID_CURRENT, &temp_u8); + if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT) + { + max_grid_current = temp_u8; + } + else + { + max_grid_current = MAX_GRID_CURRENT_LIMIT; + nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, max_grid_current); + ESP_LOGW(TAG, "max_grid_current missing or invalid, setting default: %d", max_grid_current); + needs_commit = true; + } + + // loadbalancer_enabled + err = nvs_get_u8(handle, NVS_LOADBALANCER_ENABLED, &temp_u8); + if (err == ESP_OK && temp_u8 <= 1) + { + loadbalancer_enabled = (temp_u8 != 0); + } + else + { + loadbalancer_enabled = false; + nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, 0); + ESP_LOGW(TAG, "loadbalancer_enabled missing or invalid, setting default: 0"); + needs_commit = true; + } + + if (needs_commit) + { + nvs_commit(handle); + } + + nvs_close(handle); + return ESP_OK; +} + +// Salva o estado habilitado no NVS +void loadbalancer_set_enabled(bool enabled) +{ + ESP_LOGI(TAG, "Setting load balancing enabled to %d", enabled); + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err)); + return; + } + + err = nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, enabled ? 1 : 0); + if (err == ESP_OK) + { + nvs_commit(handle); + loadbalancer_enabled = enabled; + ESP_LOGI(TAG, "Load balancing enabled state saved"); + + loadbalancer_state_event_t evt = { + .enabled = enabled, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_STATE_CHANGED, + &evt, + sizeof(evt), + portMAX_DELAY); + } + else + { + ESP_LOGE(TAG, "Failed to save loadbalancer_enabled"); + } + + nvs_close(handle); +} + +// Define e salva o limite de corrente da rede +esp_err_t load_balancing_set_max_grid_current(uint8_t value) +{ + if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT) + { + ESP_LOGE(TAG, "Invalid grid current limit: %d", value); + return ESP_ERR_INVALID_ARG; + } + + nvs_handle_t handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err)); + return err; + } + + err = nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, value); + if (err == ESP_OK) + { + nvs_commit(handle); + max_grid_current = value; + ESP_LOGI(TAG, "max_grid_current set to: %d", value); + } + else + { + ESP_LOGE(TAG, "Failed to save max_grid_current to NVS"); + } + + nvs_close(handle); + return err; +} + +uint8_t load_balancing_get_max_grid_current(void) +{ + return max_grid_current; +} + +bool loadbalancer_is_enabled(void) +{ + return loadbalancer_enabled; +} + +// Tarefa principal com eventos +void loadbalancer_task(void *param) +{ + while (true) + { + if (!loadbalancer_enabled) + { + vTaskDelay(pdMS_TO_TICKS(1000)); + continue; + } + + float available = max_grid_current - grid_current + evse_current; + + if (available < MIN_CHARGING_CURRENT_LIMIT) + { + available = MIN_CHARGING_CURRENT_LIMIT; + } + else if (available > max_grid_current) + { + available = max_grid_current; + } + + ESP_LOGD(TAG, "Setting EVSE current limit: %.1f A", available); + + loadbalancer_charging_limit_event_t evt = { + .limit = available, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED, + &evt, + sizeof(evt), + portMAX_DELAY); + + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + +void loadbalancer_init(void) +{ + ESP_LOGI(TAG, "Initializing load balancer"); + + if (loadbalancer_load_config() != ESP_OK) + { + ESP_LOGW(TAG, "Failed to load/init config. Using in-memory defaults."); + } + + input_filter_init(&grid_filter, 0.3f); + input_filter_init(&evse_filter, 0.3f); + + if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS) + { + ESP_LOGE(TAG, "Failed to create loadbalancer task"); + } + + loadbalancer_state_event_t evt = { + .enabled = loadbalancer_enabled, + .timestamp_us = esp_timer_get_time()}; + + esp_event_post(LOADBALANCER_EVENTS, + LOADBALANCER_EVENT_INIT, + &evt, + sizeof(evt), + portMAX_DELAY); + + ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, + &loadbalancer_meter_event_handler, NULL)); + + ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, + EVSE_EVENT_STATE_CHANGED, + &loadbalancer_evse_event_handler, + NULL)); +} + +// === Fim de: components/loadbalancer/src/loadbalancer.c === + + +// === Início de: components/loadbalancer/src/input_filter.c === +#include "input_filter.h" + +void input_filter_init(input_filter_t *filter, float alpha) { + if (filter) { + filter->alpha = alpha; + filter->value = 0.0f; + filter->initialized = 0; + } +} + +float input_filter_update(input_filter_t *filter, float input) { + if (!filter) return input; + + if (!filter->initialized) { + filter->value = input; + filter->initialized = 1; + } else { + filter->value = filter->alpha * input + (1.0f - filter->alpha) * filter->value; + } + + return filter->value; +} + +// === Fim de: components/loadbalancer/src/input_filter.c === + + +// === Início de: components/loadbalancer/include/loadbalancer_events.h === +#pragma once +#include "esp_event.h" +#include +#include +#include "esp_timer.h" + +ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS); + +typedef enum { + LOADBALANCER_EVENT_INIT, + LOADBALANCER_EVENT_STATE_CHANGED, + LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED +} loadbalancer_event_id_t; + +typedef struct { + float limit; + int64_t timestamp_us; +} loadbalancer_charging_limit_event_t; + +typedef struct { + bool enabled; + int64_t timestamp_us; +} loadbalancer_state_event_t; + + +// === Fim de: components/loadbalancer/include/loadbalancer_events.h === + + +// === Início de: components/loadbalancer/include/loadbalancer.h === +#ifndef LOADBALANCER_H_ +#define LOADBALANCER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "esp_err.h" + + +/** + * @brief Initializes the load balancer. + * + * This function configures the load balancer and its resources, including + * any necessary persistence configurations, such as storage in NVS (Non-Volatile Storage). + * This function prepares the system to perform load balancing efficiently. + */ +void loadbalancer_init(void); + +/** + * @brief Continuous task for the load balancer. + * + * This function executes the load balancing logic continuously, typically in a FreeRTOS task. + * It performs balance calculations, checks the grid current and energy conditions, and adjusts + * the outputs as necessary to ensure efficient energy consumption. + * + * @param param Input parameter, usually used to pass additional information or relevant context + * for the task execution. + */ +void loadbalancer_task(void *param); + +/** + * @brief Enables or disables the load balancing system. + * + * This function allows enabling or disabling the load balancing system. When enabled, the load + * balancer starts managing the grid current based on the configured limits. If disabled, the system + * operates without balancing. + * + * The configuration is persisted in NVS, ensuring that the choice is maintained across system restarts. + * + * @param value If true, enables load balancing. If false, disables it. + */ +void loadbalancer_set_enabled(bool value); + +/** + * @brief Checks if load balancing is enabled. + * + * This function returns the current status of the load balancing system. + * + * @return Returns true if load balancing is enabled, otherwise returns false. + */ +bool loadbalancer_is_enabled(void); + +/** + * @brief Sets the maximum grid current. + * + * This function configures the maximum grid current that can be supplied to the load balancing system. + * The value set ensures that the system does not overload the electrical infrastructure and respects + * the safety limits. + * + * @param max_grid_current The maximum allowed current (in amperes) for the load balancing system. + * This value should be appropriate for the grid capacity and the installation. + */ +esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current); + + +/** + * @brief Gets the maximum grid current. + * + * This function retrieves the current maximum grid current limit. + * + * @return The maximum grid current (in amperes). + */ +uint8_t load_balancing_get_max_grid_current(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LOADBALANCER_H_ */ + +// === Fim de: components/loadbalancer/include/loadbalancer.h === + + +// === Início de: components/loadbalancer/include/input_filter.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float alpha; ///< Fator de suavização (0.0 a 1.0) + float value; ///< Último valor filtrado + int initialized; ///< Flag de inicialização +} input_filter_t; + +/** + * @brief Inicializa o filtro com o fator alpha desejado. + * @param filter Ponteiro para a estrutura do filtro + * @param alpha Valor entre 0.0 (mais lento) e 1.0 (sem filtro) + */ +void input_filter_init(input_filter_t *filter, float alpha); + +/** + * @brief Atualiza o valor filtrado com uma nova entrada. + * @param filter Ponteiro para o filtro + * @param input Valor bruto + * @return Valor suavizado + */ +float input_filter_update(input_filter_t *filter, float input); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/loadbalancer/include/input_filter.h === + + +// === Início de: components/auth/src/auth_events.c === +#include "auth_events.h" + +ESP_EVENT_DEFINE_BASE(AUTH_EVENTS); + +// === Fim de: components/auth/src/auth_events.c === + + +// === Início de: components/auth/src/wiegand.c === +/** + * @file wiegand.c + * + * ESP-IDF Wiegand protocol receiver + */ +#include +#include +#include +#include +#include "wiegand.h" + +static const char *TAG = "wiegand"; + +#define TIMER_INTERVAL_US 50000 // 50ms + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0) + +static void isr_disable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_DISABLE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_DISABLE); +} + +static void isr_enable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_NEGEDGE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE); +} + +#if HELPER_TARGET_IS_ESP32 +static void IRAM_ATTR isr_handler(void *arg) +#else +static void isr_handler(void *arg) +#endif +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + if (!reader->enabled) + return; + + int d0 = gpio_get_level(reader->gpio_d0); + int d1 = gpio_get_level(reader->gpio_d1); + + // ignore equal + if (d0 == d1) + return; + // overflow + if (reader->bits >= reader->size * 8) + return; + + esp_timer_stop(reader->timer); + + uint8_t value; + if (reader->bit_order == WIEGAND_MSB_FIRST) + value = (d0 ? 0x80 : 0) >> (reader->bits % 8); + else + value = (d0 ? 1 : 0) << (reader->bits % 8); + + if (reader->byte_order == WIEGAND_MSB_FIRST) + reader->buf[reader->size - reader->bits / 8 - 1] |= value; + else + reader->buf[reader->bits / 8] |= value; + + reader->bits++; + + esp_timer_start_once(reader->timer, TIMER_INTERVAL_US); +} + +static void timer_handler(void *arg) +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + + ESP_LOGI(TAG, "Got %d bits of data", reader->bits); + + wiegand_reader_disable(reader); + + if (reader->callback) + reader->callback(reader); + + wiegand_reader_enable(reader); + + isr_enable(reader); +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order) +{ + CHECK_ARG(reader && buf_size && callback); + + /* + esp_err_t res = gpio_install_isr_service(0); + if (res != ESP_OK && res != ESP_ERR_INVALID_STATE) + return res; + */ + + memset(reader, 0, sizeof(wiegand_reader_t)); + reader->gpio_d0 = gpio_d0; + reader->gpio_d1 = gpio_d1; + reader->size = buf_size; + reader->buf = calloc(buf_size, 1); + reader->bit_order = bit_order; + reader->byte_order = byte_order; + reader->callback = callback; + + esp_timer_create_args_t timer_args = { + .name = TAG, + .arg = reader, + .callback = timer_handler, + .dispatch_method = ESP_TIMER_TASK}; + CHECK(esp_timer_create(&timer_args, &reader->timer)); + + CHECK(gpio_set_direction(gpio_d0, GPIO_MODE_INPUT)); + CHECK(gpio_set_direction(gpio_d1, GPIO_MODE_INPUT)); + CHECK(gpio_set_pull_mode(gpio_d0, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + CHECK(gpio_set_pull_mode(gpio_d1, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + isr_disable(reader); + CHECK(gpio_isr_handler_add(gpio_d0, isr_handler, reader)); + CHECK(gpio_isr_handler_add(gpio_d1, isr_handler, reader)); + isr_enable(reader); + reader->enabled = true; + + ESP_LOGI(TAG, "Reader initialized on D0=%d, D1=%d", gpio_d0, gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + isr_disable(reader); + esp_timer_stop(reader->timer); + reader->enabled = false; + + ESP_LOGI(TAG, "Reader on D0=%d, D1=%d disabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + reader->bits = 0; + memset(reader->buf, 0, reader->size); + + isr_enable(reader); + reader->enabled = true; + + ESP_LOGI(TAG, "Reader on D0=%d, D1=%d enabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_done(wiegand_reader_t *reader) +{ + CHECK_ARG(reader && reader->buf); + + isr_disable(reader); + CHECK(gpio_isr_handler_remove(reader->gpio_d0)); + CHECK(gpio_isr_handler_remove(reader->gpio_d1)); + esp_timer_stop(reader->timer); + CHECK(esp_timer_delete(reader->timer)); + free(reader->buf); + + ESP_LOGI(TAG, "Reader removed"); + + return ESP_OK; +} + +// === Fim de: components/auth/src/wiegand.c === + + +// === Início de: components/auth/src/auth.c === +/* + * auth.c + */ + +#include "auth.h" +#include "auth_events.h" +#include "esp_event.h" +#include +#include +#include +#include +#include +#include "wiegand_reader.h" +#include "nvs_flash.h" +#include "nvs.h" + +#define MAX_TAGS 50 + +static const char *TAG = "Auth"; + +static bool enabled = false; +static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN]; +static int tag_count = 0; + +// =========================== +// Persistência em NVS +// =========================== + +static void load_auth_config(void) { + nvs_handle_t handle; + esp_err_t err = nvs_open("auth", NVS_READONLY, &handle); + if (err == ESP_OK) { + uint8_t val; + if (nvs_get_u8(handle, "enabled", &val) == ESP_OK) { + enabled = val; + ESP_LOGI(TAG, "Loaded auth enabled = %d", enabled); + } + nvs_close(handle); + } else { + ESP_LOGW(TAG, "No stored auth config found. Using default."); + } +} + +static void save_auth_config(void) { + nvs_handle_t handle; + if (nvs_open("auth", NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_u8(handle, "enabled", enabled); + nvs_commit(handle); + nvs_close(handle); + ESP_LOGI(TAG, "Auth config saved: enabled = %d", enabled); + } else { + ESP_LOGE(TAG, "Failed to save auth config."); + } +} + +// =========================== +// Internos +// =========================== + +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 true; + //TODO + //return false; +} + +// =========================== +// API pública +// =========================== + +void auth_set_enabled(bool value) { + enabled = value; + save_auth_config(); + ESP_LOGI(TAG, "Auth %s", enabled ? "ENABLED" : "DISABLED"); + + auth_enabled_event_data_t event = { .enabled = enabled }; + esp_event_post(AUTH_EVENTS, AUTH_EVENT_ENABLED_CHANGED, &event, sizeof(event), portMAX_DELAY); +} + +bool auth_is_enabled(void) { + return enabled; +} + +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; + + strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1); + valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; + tag_count++; + ESP_LOGI(TAG, "Tag added: %s", tag); + return true; +} + +bool auth_remove_tag(const char *tag) { + for (int i = 0; i < tag_count; i++) { + if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { + for (int j = i; j < tag_count - 1; j++) { + strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN); + } + tag_count--; + ESP_LOGI(TAG, "Tag removed: %s", tag); + return true; + } + } + return false; +} + +bool auth_tag_exists(const char *tag) { + return is_tag_valid(tag); +} + +void auth_list_tags(void) { + ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); + for (int i = 0; i < tag_count; i++) { + ESP_LOGI(TAG, "- %s", valid_tags[i]); + } +} + +void auth_init(void) { + load_auth_config(); // carrega estado de ativação + + if (enabled) { + initWiegand(); // só inicia se estiver habilitado + ESP_LOGI(TAG, "Wiegand reader initialized (Auth enabled)"); + } else { + ESP_LOGI(TAG, "Auth disabled, Wiegand reader not started"); + } + + auth_enabled_event_data_t evt = { .enabled = enabled }; + esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); + + ESP_LOGI(TAG, "Estado inicial AUTH enviado (enabled = %d)", enabled); +} + +void auth_process_tag(const char *tag) { + if (!tag || !auth_is_enabled()) { + ESP_LOGW(TAG, "Auth disabled or NULL tag received."); + return; + } + + auth_tag_event_data_t event; + strncpy(event.tag, tag, AUTH_EVENT_TAG_MAX_LEN - 1); + event.tag[AUTH_EVENT_TAG_MAX_LEN - 1] = '\0'; + event.authorized = is_tag_valid(tag); + + ESP_LOGI(TAG, "Tag %s: %s", tag, event.authorized ? "AUTHORIZED" : "DENIED"); + + esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, &event, sizeof(event), portMAX_DELAY); +} + +// === Fim de: components/auth/src/auth.c === + + +// === Início de: components/auth/src/wiegand_reader.c === +#include +#include +#include +#include +#include +#include +#include +#include "auth.h" + +#define CONFIG_EXAMPLE_BUF_SIZE 50 + +static const char *TAG = "WiegandReader"; + +static wiegand_reader_t reader; +static QueueHandle_t queue = NULL; + +typedef struct { + uint8_t data[CONFIG_EXAMPLE_BUF_SIZE]; + size_t bits; +} data_packet_t; + +static void reader_callback(wiegand_reader_t *r) { + data_packet_t p; + p.bits = r->bits; + memcpy(p.data, r->buf, CONFIG_EXAMPLE_BUF_SIZE); + xQueueSendToBack(queue, &p, 0); +} + +static void wiegand_task(void *arg) { + queue = xQueueCreate(5, sizeof(data_packet_t)); + if (!queue) { + ESP_LOGE(TAG, "Failed to create queue"); + vTaskDelete(NULL); + return; + } + + ESP_ERROR_CHECK(wiegand_reader_init(&reader, 19, 18, + true, CONFIG_EXAMPLE_BUF_SIZE, reader_callback, WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST)); + + data_packet_t p; + while (1) { + ESP_LOGI(TAG, "Waiting for Wiegand data..."); + if (xQueueReceive(queue, &p, portMAX_DELAY) == pdPASS) { + ESP_LOGI(TAG, "Bits received: %d", p.bits); + + char tag[20] = {0}; + + if (p.bits == 26) { + snprintf(tag, sizeof(tag), "%03d%03d%03d", p.data[0], p.data[1], p.data[2]); + } else if (p.bits == 34) { + snprintf(tag, sizeof(tag), "%03d%03d%03d%03d", p.data[0], p.data[1], p.data[2], p.data[3]); + } else { + ESP_LOGW(TAG, "Unsupported bit length: %d", (int)p.bits); + continue; + } + + ESP_LOGI(TAG, "Tag read: %s", tag); + auth_process_tag(tag); // agora delega toda a lógica à auth.c + } + } +} + +void initWiegand(void) { + ESP_LOGI(TAG, "Initializing Wiegand reader"); + xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL); +} + +// === Fim de: components/auth/src/wiegand_reader.c === + + +// === Início de: components/auth/include/auth.h === +#ifndef AUTH_H +#define AUTH_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Tamanho máximo de uma tag RFID (incluindo '\0') +#define AUTH_TAG_MAX_LEN 20 + +/// Estrutura de evento emitida após leitura de uma tag +typedef struct { + char tag[AUTH_TAG_MAX_LEN]; ///< Tag lida + bool authorized; ///< true se a tag for reconhecida como válida +} auth_event_t; + +/** + * @brief Inicializa o sistema de autenticação. + * + * - Carrega a configuração (enabled) da NVS + * - Inicia o leitor Wiegand + * - Emite evento AUTH_EVENT_INIT com estado atual + */ +void auth_init(void); + +/** + * @brief Ativa ou desativa o uso de autenticação via RFID. + * + * Esta configuração é persistida em NVS. Se desativado, o sistema + * considerará todas as autorizações como aceitas. + * + * @param value true para ativar, false para desativar + */ +void auth_set_enabled(bool value); + +/** + * @brief Verifica se o sistema de autenticação está habilitado. + */ +bool auth_is_enabled(void); + +/** + * @brief Adiciona uma nova tag RFID à lista de autorizadas. + * + * @param tag String da tag (máx AUTH_TAG_MAX_LEN-1) + * @return true se a tag foi adicionada, false se já existia ou inválida + */ +bool auth_add_tag(const char *tag); + +/** + * @brief Remove uma tag previamente cadastrada. + * + * @param tag String da tag + * @return true se foi removida, false se não encontrada + */ +bool auth_remove_tag(const char *tag); + +/** + * @brief Verifica se uma tag já está registrada como válida. + */ +bool auth_tag_exists(const char *tag); + +/** + * @brief Lista todas as tags válidas atualmente registradas (via logs). + */ +void auth_list_tags(void); + +/** + * @brief Processa uma tag RFID lida (chamada normalmente pelo leitor). + * + * - Verifica validade + * - Emite evento AUTH_EVENT_TAG_PROCESSED + * - Inicia timer de expiração se autorizada + */ +void auth_process_tag(const char *tag); + + +#ifdef __cplusplus +} +#endif + +#endif // AUTH_H + +// === Fim de: components/auth/include/auth.h === + + +// === Início de: components/auth/include/auth_events.h === +#pragma once +#include "esp_event.h" + +#define AUTH_EVENT_TAG_MAX_LEN 32 + +ESP_EVENT_DECLARE_BASE(AUTH_EVENTS); + +typedef enum { + AUTH_EVENT_TAG_PROCESSED, + AUTH_EVENT_ENABLED_CHANGED, + AUTH_EVENT_INIT, +} auth_event_id_t; + +typedef struct { + char tag[AUTH_EVENT_TAG_MAX_LEN]; + bool authorized; +} auth_tag_event_data_t; + +typedef struct { + bool enabled; +} auth_enabled_event_data_t; + +// === Fim de: components/auth/include/auth_events.h === + + +// === Início de: components/auth/include/wiegand.h === +/* + * Copyright (c) 2021 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file wiegand.h + * @defgroup wiegand wiegand + * @{ + * + * ESP-IDF Wiegand protocol receiver + * + * Copyright (c) 2021 Ruslan V. Uss + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __WIEGAND_H__ +#define __WIEGAND_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wiegand_reader wiegand_reader_t; + +typedef void (*wiegand_callback_t)(wiegand_reader_t *reader); + +/** + * Bit and byte order of data + */ +typedef enum { + WIEGAND_MSB_FIRST = 0, + WIEGAND_LSB_FIRST +} wiegand_order_t; + +/** + * Wiegand reader descriptor + */ +struct wiegand_reader +{ + gpio_num_t gpio_d0, gpio_d1; + wiegand_callback_t callback; + wiegand_order_t bit_order; + wiegand_order_t byte_order; + + uint8_t *buf; + size_t size; + size_t bits; + esp_timer_handle_t timer; + bool start_parity; + bool enabled; +}; + +/** + * @brief Create and initialize reader instance. + * + * @param reader Reader descriptor + * @param gpio_d0 GPIO pin for D0 + * @param gpio_d1 GPIO pin for D0 + * @param internal_pullups Enable internal pull-up resistors for D0 and D1 GPIO + * @param buf_size Reader buffer size in bytes, must be large enough to + * contain entire Wiegand key + * @param callback Callback function for processing received codes + * @param bit_order Bit order of data + * @param byte_order Byte order of data + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order); + +/** + * @brief Disable reader + * + * While reader is disabled, it will not receive new data + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader); + +/** + * @brief Enable reader + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader); + +/** + * @brief Delete reader instance. + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_done(wiegand_reader_t *reader); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __WIEGAND_H__ */ + +// === Fim de: components/auth/include/wiegand.h === + + +// === Início de: components/auth/include/wiegand_reader.h === +#ifndef WIEGAND_READER_H +#define WIEGAND_READER_H + +#ifdef __cplusplus +extern "C" { +#endif + +void initWiegand(void); + +#ifdef __cplusplus +} +#endif + +#endif // WIEGAND_READER_H + +// === Fim de: components/auth/include/wiegand_reader.h === + + +// === Início de: components/rest_api/src/ocpp_api.c === +// ========================= +// ocpp_api.c +// ========================= +#include "ocpp_api.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "ocpp_api"; + +static struct { + char url[256]; + char chargeBoxId[128]; + char certificate[256]; + char privateKey[256]; +} ocpp_config = {"", "", "", ""}; + +static esp_err_t ocpp_get_status_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *status = cJSON_CreateObject(); + cJSON_AddStringToObject(status, "status", "connected"); + char *str = cJSON_Print(status); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(status); + return ESP_OK; +} + +static esp_err_t ocpp_get_config_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "url", ocpp_config.url); + cJSON_AddStringToObject(json, "chargeBoxId", ocpp_config.chargeBoxId); + cJSON_AddStringToObject(json, "certificate", ocpp_config.certificate); + cJSON_AddStringToObject(json, "privateKey", ocpp_config.privateKey); + char *str = cJSON_Print(json); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(json); + return ESP_OK; +} + +static esp_err_t ocpp_post_config_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + cJSON *url = cJSON_GetObjectItem(json, "url"); + if (url) strlcpy(ocpp_config.url, url->valuestring, sizeof(ocpp_config.url)); + cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId"); + if (id) strlcpy(ocpp_config.chargeBoxId, id->valuestring, sizeof(ocpp_config.chargeBoxId)); + cJSON *cert = cJSON_GetObjectItem(json, "certificate"); + if (cert) strlcpy(ocpp_config.certificate, cert->valuestring, sizeof(ocpp_config.certificate)); + cJSON *key = cJSON_GetObjectItem(json, "privateKey"); + if (key) strlcpy(ocpp_config.privateKey, key->valuestring, sizeof(ocpp_config.privateKey)); + cJSON_Delete(json); + httpd_resp_sendstr(req, "OCPP config atualizada com sucesso"); + return ESP_OK; +} + +void register_ocpp_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t status_uri = { + .uri = "/api/v1/ocpp", + .method = HTTP_GET, + .handler = ocpp_get_status_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &status_uri); + + httpd_uri_t get_uri = { + .uri = "/api/v1/config/ocpp", + .method = HTTP_GET, + .handler = ocpp_get_config_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + httpd_uri_t post_uri = { + .uri = "/api/v1/config/ocpp", + .method = HTTP_POST, + .handler = ocpp_post_config_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/ocpp_api.c === + + +// === Início de: components/rest_api/src/static_file_api.c === +#include "static_file_api.h" +#include "esp_log.h" +#include +#include +#include "esp_vfs.h" + +static const char *TAG = "static_file_api"; + +#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128) +#define SCRATCH_BUFSIZE (10240) + +typedef struct rest_server_context { + char base_path[ESP_VFS_PATH_MAX + 1]; + char scratch[SCRATCH_BUFSIZE]; +} rest_server_context_t; + +#define CHECK_FILE_EXTENSION(filename, ext) \ + (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0) + +static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) { + const char *type = "text/plain"; + if (CHECK_FILE_EXTENSION(filepath, ".html")) type = "text/html"; + else if (CHECK_FILE_EXTENSION(filepath, ".js")) type = "application/javascript"; + else if (CHECK_FILE_EXTENSION(filepath, ".css")) type = "text/css"; + else if (CHECK_FILE_EXTENSION(filepath, ".png")) type = "image/png"; + else if (CHECK_FILE_EXTENSION(filepath, ".ico")) type = "image/x-icon"; + else if (CHECK_FILE_EXTENSION(filepath, ".svg")) type = "image/svg+xml"; + return httpd_resp_set_type(req, type); +} + +static esp_err_t static_get_handler(httpd_req_t *req) { + char filepath[FILE_PATH_MAX]; + rest_server_context_t *ctx = (rest_server_context_t *) req->user_ctx; + + strlcpy(filepath, ctx->base_path, sizeof(filepath)); + if (req->uri[strlen(req->uri) - 1] == '/') { + strlcat(filepath, "/index.html", sizeof(filepath)); + } else { + strlcat(filepath, req->uri, sizeof(filepath)); + } + + int fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + // fallback para /index.html (SPA) + ESP_LOGW(TAG, "Arquivo não encontrado: %s. Tentando index.html", filepath); + strlcpy(filepath, ctx->base_path, sizeof(filepath)); + strlcat(filepath, "/index.html", sizeof(filepath)); + fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Arquivo não encontrado"); + return ESP_FAIL; + } + } + + set_content_type_from_file(req, filepath); + + char *chunk = ctx->scratch; + ssize_t read_bytes; + do { + read_bytes = read(fd, chunk, SCRATCH_BUFSIZE); + if (read_bytes == -1) { + ESP_LOGE(TAG, "Erro lendo arquivo: %s", filepath); + close(fd); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao ler arquivo"); + return ESP_FAIL; + } else if (read_bytes > 0) { + if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) { + close(fd); + httpd_resp_sendstr_chunk(req, NULL); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao enviar arquivo"); + return ESP_FAIL; + } + } + } while (read_bytes > 0); + + close(fd); + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + +void register_static_file_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t uri = { + .uri = "/*", + .method = HTTP_GET, + .handler = static_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &uri); +} + +// === Fim de: components/rest_api/src/static_file_api.c === + + +// === Início de: components/rest_api/src/meters_settings_api.c === +#include "meters_settings_api.h" +#include "meter_manager.h" // Atualizado para usar o novo manager +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "meters_settings_api"; + +// Função para recuperar as configurações dos contadores +static esp_err_t meters_config_get_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters"); + + httpd_resp_set_type(req, "application/json"); + + cJSON *config = cJSON_CreateObject(); + + // Recuperando as configurações dos contadores + meter_type_t gridmeterType = meter_manager_grid_get_model(); + meter_type_t evsemeterType = meter_manager_evse_get_model(); + + ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType)); + ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType)); + + // Adicionando os tipos de contadores ao objeto JSON + cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType)); + cJSON_AddStringToObject(config, "evsemeter", meter_type_to_str(evsemeterType)); + + // Convertendo o objeto JSON para uma string + const char *json_str = cJSON_Print(config); + ESP_LOGI(TAG, "Returning meters config: %s", json_str); + + httpd_resp_sendstr(req, json_str); + + // Liberação da memória + free((void *)json_str); + cJSON_Delete(config); + + return ESP_OK; +} + +// Função para atualizar as configurações dos contadores +static esp_err_t meters_config_post_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + + if (len <= 0) { + ESP_LOGE(TAG, "Received empty body in POST request"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + + buf[len] = '\0'; // Garantir que a string está terminada + + ESP_LOGI(TAG, "Received POST data: %s", buf); + + cJSON *json = cJSON_Parse(buf); + if (!json) { + ESP_LOGE(TAG, "Failed to parse JSON data"); + // Resposta detalhada de erro + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format"); + return ESP_FAIL; + } + + // Atualizando os contadores + cJSON *gridmeter = cJSON_GetObjectItem(json, "gridmeter"); + if (gridmeter) { + meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type + ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring); + meter_manager_grid_set_model(gridType); + } + + cJSON *evsemeter = cJSON_GetObjectItem(json, "evsemeter"); + if (evsemeter) { + meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type + ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring); + meter_manager_evse_set_model(evseType); + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Meters updated successfully"); + + ESP_LOGI(TAG, "Meters configuration updated successfully"); + + return ESP_OK; +} + +// Registrando os manipuladores de URI para os contadores +void register_meters_settings_handlers(httpd_handle_t server, void *ctx) { + ESP_LOGI(TAG, "Registering URI handlers for meters settings"); + + // URI para o método GET + httpd_uri_t meters_get_uri = { + .uri = "/api/v1/config/meters", + .method = HTTP_GET, + .handler = meters_config_get_handler, + .user_ctx = ctx + }; + ESP_LOGI(TAG, "Registering GET handler for /api/v1/config/meters"); + httpd_register_uri_handler(server, &meters_get_uri); + + // URI para o método POST + httpd_uri_t meters_post_uri = { + .uri = "/api/v1/config/meters", + .method = HTTP_POST, + .handler = meters_config_post_handler, + .user_ctx = ctx + }; + ESP_LOGI(TAG, "Registering POST handler for /api/v1/config/meters"); + httpd_register_uri_handler(server, &meters_post_uri); +} + +// === Fim de: components/rest_api/src/meters_settings_api.c === + + +// === Início de: components/rest_api/src/rest_main.c === +#include "rest_main.h" +#include "evse_settings_api.h" +#include "meters_settings_api.h" +#include "loadbalancing_settings_api.h" +#include "network_api.h" +#include "ocpp_api.h" +#include "auth_api.h" +#include "dashboard_api.h" +#include "static_file_api.h" +#include "esp_log.h" + + +static const char *TAG = "rest_main"; + +esp_err_t rest_server_init(const char *base_path) { + ESP_LOGI(TAG, "Initializing REST API with base path: %s", base_path); + + rest_server_context_t *ctx = calloc(1, sizeof(rest_server_context_t)); + if (!ctx) { + ESP_LOGE(TAG, "Failed to allocate memory for REST context"); + return ESP_ERR_NO_MEM; + } + + strlcpy(ctx->base_path, base_path, sizeof(ctx->base_path)); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.uri_match_fn = httpd_uri_match_wildcard; + config.max_uri_handlers = 32; + + httpd_handle_t server = NULL; + esp_err_t err = httpd_start(&server, &config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err)); + free(ctx); + return err; + } + + ESP_LOGI(TAG, "HTTP server started successfully"); + + // Register endpoint groups + register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_network_handlers(server, ctx); // Apenas chamando a função sem comparação + register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação + register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação + register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação + register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação + register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação + + ESP_LOGI(TAG, "All REST API endpoint groups registered successfully"); + + return ESP_OK; +} + +// === Fim de: components/rest_api/src/rest_main.c === + + +// === Início de: components/rest_api/src/network_api.c === +// ========================= +// network_api.c +// ========================= + +#include "network_api.h" +#include "esp_log.h" +#include "cJSON.h" +#include "wifi.h" +#include "mqtt.h" + +static const char *TAG = "network_api"; + +typedef struct { + bool enabled; + char ssid[33]; + char password[65]; +} wifi_task_data_t; + + +static void wifi_apply_config_task(void *param) { + wifi_task_data_t *data = (wifi_task_data_t *)param; + ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task"); + wifi_set_config(data->enabled, data->ssid, data->password); + free(data); + vTaskDelete(NULL); +} + +static esp_err_t wifi_get_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi"); + + httpd_resp_set_type(req, "application/json"); + + // Obter dados da NVS via wifi.c + bool enabled = wifi_get_enabled(); + char ssid[33] = {0}; + char password[65] = {0}; + + wifi_get_ssid(ssid); + wifi_get_password(password); + + // Criar JSON + cJSON *json = cJSON_CreateObject(); + cJSON_AddBoolToObject(json, "enabled", enabled); + cJSON_AddStringToObject(json, "ssid", ssid); + cJSON_AddStringToObject(json, "password", password); + + // Enviar resposta + char *response = cJSON_Print(json); + httpd_resp_sendstr(req, response); + + // Limpeza + free(response); + cJSON_Delete(json); + + return ESP_OK; +} + +static esp_err_t wifi_post_handler(httpd_req_t *req) { + ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) return ESP_FAIL; + buf[len] = '\0'; + + cJSON *json = cJSON_Parse(buf); + if (!json) return ESP_FAIL; + + // Valores padrão + bool enabled = false; + const char *ssid = NULL; + const char *password = NULL; + + cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); + if (cJSON_IsBool(j_enabled)) enabled = j_enabled->valueint; + + cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid"); + if (cJSON_IsString(j_ssid)) ssid = j_ssid->valuestring; + + cJSON *j_password = cJSON_GetObjectItem(json, "password"); + if (cJSON_IsString(j_password)) password = j_password->valuestring; + + // Enviar resposta antes de alterar Wi-Fi + httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso"); + + // Alocar struct para passar para a task + wifi_task_data_t *task_data = malloc(sizeof(wifi_task_data_t)); + if (!task_data) { + cJSON_Delete(json); + ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task"); + return ESP_ERR_NO_MEM; + } + + task_data->enabled = enabled; + strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid)); + strncpy(task_data->password, password ? password : "", sizeof(task_data->password)); + + // Criar task normal com função C + xTaskCreate( + wifi_apply_config_task, + "wifi_config_task", + 4096, + task_data, + 3, + NULL + ); + + cJSON_Delete(json); + return ESP_OK; +} + + +static esp_err_t config_mqtt_get_handler(httpd_req_t *req) +{ + ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt"); + + httpd_resp_set_type(req, "application/json"); + + bool enabled = mqtt_get_enabled(); + char server[64] = {0}; + char base_topic[32] = {0}; + char username[32] = {0}; + char password[64] = {0}; + uint16_t periodicity = mqtt_get_periodicity(); + + mqtt_get_server(server); + mqtt_get_base_topic(base_topic); + mqtt_get_user(username); + mqtt_get_password(password); + + ESP_LOGI(TAG, "MQTT Config:"); + ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); + ESP_LOGI(TAG, " Server: %s", server); + ESP_LOGI(TAG, " Topic: %s", base_topic); + ESP_LOGI(TAG, " Username: %s", username); + ESP_LOGI(TAG, " Password: %s", password); + ESP_LOGI(TAG, " Periodicity: %d", periodicity); + + cJSON *config = cJSON_CreateObject(); + cJSON_AddBoolToObject(config, "enabled", enabled); + cJSON_AddStringToObject(config, "host", server); + cJSON_AddNumberToObject(config, "port", 1883); + cJSON_AddStringToObject(config, "username", username); + cJSON_AddStringToObject(config, "password", password); + cJSON_AddStringToObject(config, "topic", base_topic); + cJSON_AddNumberToObject(config, "periodicity", periodicity); + + const char *config_str = cJSON_Print(config); + httpd_resp_sendstr(req, config_str); + + free((void *)config_str); + cJSON_Delete(config); + return ESP_OK; +} + + +static esp_err_t config_mqtt_post_handler(httpd_req_t *req) +{ + ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt"); + + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + ESP_LOGE(TAG, "Failed to read request body"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body"); + return ESP_FAIL; + } + buf[len] = '\0'; + ESP_LOGI(TAG, "Received JSON: %s", buf); + + cJSON *json = cJSON_Parse(buf); + if (!json) { + ESP_LOGE(TAG, "Invalid JSON format"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + + bool enabled = false; + const char *host = NULL, *topic = NULL, *username = NULL, *password = NULL; + int periodicity = 30; + + if (cJSON_IsBool(cJSON_GetObjectItem(json, "enabled"))) + enabled = cJSON_GetObjectItem(json, "enabled")->valueint; + + cJSON *j_host = cJSON_GetObjectItem(json, "host"); + if (cJSON_IsString(j_host)) host = j_host->valuestring; + + cJSON *j_topic = cJSON_GetObjectItem(json, "topic"); + if (cJSON_IsString(j_topic)) topic = j_topic->valuestring; + + cJSON *j_user = cJSON_GetObjectItem(json, "username"); + if (cJSON_IsString(j_user)) username = j_user->valuestring; + + cJSON *j_pass = cJSON_GetObjectItem(json, "password"); + if (cJSON_IsString(j_pass)) password = j_pass->valuestring; + + cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity"); + if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint; + + ESP_LOGI(TAG, "Applying MQTT config:"); + ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); + ESP_LOGI(TAG, " Host: %s", host); + ESP_LOGI(TAG, " Topic: %s", topic); + ESP_LOGI(TAG, " Username: %s", username); + ESP_LOGI(TAG, " Password: %s", password); + ESP_LOGI(TAG, " Periodicity: %d", periodicity); + + esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config"); + cJSON_Delete(json); + return ESP_FAIL; + } + + httpd_resp_sendstr(req, "Configuração MQTT atualizada com sucesso"); + cJSON_Delete(json); + return ESP_OK; +} + + + +void register_network_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t wifi_get = { + .uri = "/api/v1/config/wifi", + .method = HTTP_GET, + .handler = wifi_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &wifi_get); + + httpd_uri_t wifi_post = { + .uri = "/api/v1/config/wifi", + .method = HTTP_POST, + .handler = wifi_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &wifi_post); + + // URI handler for getting MQTT config + httpd_uri_t config_mqtt_get_uri = { + .uri = "/api/v1/config/mqtt", + .method = HTTP_GET, + .handler = config_mqtt_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &config_mqtt_get_uri); + + // URI handler for posting MQTT config + httpd_uri_t config_mqtt_post_uri = { + .uri = "/api/v1/config/mqtt", + .method = HTTP_POST, + .handler = config_mqtt_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &config_mqtt_post_uri); +} + +// === Fim de: components/rest_api/src/network_api.c === + + +// === Início de: components/rest_api/src/dashboard_api.c === +#include "dashboard_api.h" +#include "esp_log.h" +#include "cJSON.h" +#include "evse_api.h" +#include "evse_error.h" + +static const char *TAG = "dashboard_api"; + +static esp_err_t dashboard_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + + // Cria o objeto JSON principal do dashboard + cJSON *dashboard = cJSON_CreateObject(); + + // Status do sistema + evse_state_t state = evse_get_state(); + cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state)); + + // Carregador - informação do carregador 1 (adapte conforme necessário) + cJSON *chargers = cJSON_CreateArray(); + cJSON *charger1 = cJSON_CreateObject(); + cJSON_AddNumberToObject(charger1, "id", 1); + cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state)); + cJSON_AddNumberToObject(charger1, "current", evse_get_charging_current() / 10); + cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current()); + + // Calcular a potência com base na corrente (considerando 230V) + int power = (evse_get_charging_current() / 10) * 230; + cJSON_AddNumberToObject(charger1, "power", power); + + cJSON_AddItemToArray(chargers, charger1); + cJSON_AddItemToObject(dashboard, "chargers", chargers); + + // Consumo e tempo de carregamento + cJSON_AddNumberToObject(dashboard, "energyConsumed", evse_get_consumption_limit()); + cJSON_AddNumberToObject(dashboard, "chargingTime", evse_get_charging_time_limit()); + + // Alertas + cJSON *alerts = cJSON_CreateArray(); + if (evse_is_limit_reached()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido.")); + } + if (!evse_is_available()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível.")); + } + if (!evse_is_enabled()) { + cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado.")); + } + cJSON_AddItemToObject(dashboard, "alerts", alerts); + + // Erros + uint32_t error_bits = evse_get_error(); + cJSON *errors = cJSON_CreateArray(); + if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado")); + if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento")); + if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento")); + if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM")); + if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado")); + if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada")); + if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto")); + if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura")); + cJSON_AddItemToObject(dashboard, "errors", errors); + + // Enviar resposta JSON + const char *json_str = cJSON_Print(dashboard); + httpd_resp_sendstr(req, json_str); + + // Liberar memória + free((void *)json_str); + cJSON_Delete(dashboard); + + return ESP_OK; +} + +void register_dashboard_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t uri = { + .uri = "/api/v1/dashboard", + .method = HTTP_GET, + .handler = dashboard_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &uri); +} + +// === Fim de: components/rest_api/src/dashboard_api.c === + + +// === Início de: components/rest_api/src/auth_api.c === +// ========================= +// auth_api.c +// ========================= +#include "auth_api.h" +#include "auth.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "auth_api"; + +static struct { + char username[128]; +} users[10] = { /*{"admin"}, {"user1"}*/ }; +static int num_users = 2; + +static esp_err_t auth_methods_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *json = cJSON_CreateObject(); + cJSON_AddBoolToObject(json, "RFID", auth_is_enabled() ); + char *str = cJSON_PrintUnformatted(json); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(json); + return ESP_OK; +} + +static esp_err_t auth_methods_post_handler(httpd_req_t *req) { + char buf[256]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados"); + return ESP_FAIL; + } + + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido"); + return ESP_FAIL; + } + + cJSON *rfid = cJSON_GetObjectItem(json, "RFID"); + if (rfid && cJSON_IsBool(rfid)) { + auth_set_enabled(cJSON_IsTrue(rfid)); + } else { + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'RFID' inválido ou ausente"); + return ESP_FAIL; + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Métodos de autenticação atualizados"); + return ESP_OK; +} + + +static esp_err_t users_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *root = cJSON_CreateObject(); + cJSON *list = cJSON_CreateArray(); + for (int i = 0; i < num_users; ++i) { + cJSON *u = cJSON_CreateObject(); + cJSON_AddStringToObject(u, "username", users[i].username); + cJSON_AddItemToArray(list, u); + } + cJSON_AddItemToObject(root, "users", list); + char *str = cJSON_Print(root); + httpd_resp_sendstr(req, str); + free(str); + cJSON_Delete(root); + return ESP_OK; +} + +static esp_err_t users_post_handler(httpd_req_t *req) { + char buf[128]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) return ESP_FAIL; + buf[len] = '\0'; + if (num_users < 10) { + strlcpy(users[num_users].username, buf, sizeof(users[num_users].username)); + num_users++; + httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); + } else { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido"); + } + return ESP_OK; +} + +static esp_err_t users_delete_handler(httpd_req_t *req) { + char query[128]; + if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { + char username[128]; + if (httpd_query_key_value(query, "username", username, sizeof(username)) == ESP_OK) { + for (int i = 0; i < num_users; i++) { + if (strcmp(users[i].username, username) == 0) { + for (int j = i; j < num_users - 1; j++) { + users[j] = users[j + 1]; + } + num_users--; + httpd_resp_sendstr(req, "Usuário removido com sucesso"); + return ESP_OK; + } + } + } + } + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário não encontrado"); + return ESP_FAIL; +} + +void register_auth_handlers(httpd_handle_t server, void *ctx) { + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/auth-methods", + .method = HTTP_GET, + .handler = auth_methods_get_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/auth-methods", + .method = HTTP_POST, + .handler = auth_methods_post_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_GET, + .handler = users_get_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_POST, + .handler = users_post_handler, + .user_ctx = ctx + }); + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/config/users", + .method = HTTP_DELETE, + .handler = users_delete_handler, + .user_ctx = ctx + }); +} + +// === Fim de: components/rest_api/src/auth_api.c === + + +// === Início de: components/rest_api/src/loadbalancing_settings_api.c === +#include "loadbalancing_settings_api.h" +#include "loadbalancer.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "loadbalancing_settings_api"; + +// GET Handler: Retorna configurações atuais de load balancing +static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) { + bool enabled = loadbalancer_is_enabled(); + uint8_t currentLimit = load_balancing_get_max_grid_current(); + + ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit); + + httpd_resp_set_type(req, "application/json"); + + cJSON *config = cJSON_CreateObject(); + cJSON_AddBoolToObject(config, "loadBalancingEnabled", enabled); + cJSON_AddNumberToObject(config, "loadBalancingCurrentLimit", currentLimit); + + const char *json_str = cJSON_Print(config); + httpd_resp_sendstr(req, json_str); + + ESP_LOGI(TAG, "Returned config: %s", json_str); + + free((void *)json_str); + cJSON_Delete(config); + return ESP_OK; +} + +// POST Handler: Atualiza configurações de load balancing +static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + + if (len <= 0) { + ESP_LOGE(TAG, "Received empty POST body"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + + buf[len] = '\0'; + ESP_LOGI(TAG, "Received POST data: %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; + } + + // Atualizar estado habilitado + cJSON *enabled_item = cJSON_GetObjectItem(json, "loadBalancingEnabled"); + if (enabled_item && cJSON_IsBool(enabled_item)) { + bool isEnabled = cJSON_IsTrue(enabled_item); + loadbalancer_set_enabled(isEnabled); + ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled); + } + + // Atualizar limite de corrente + cJSON *limit_item = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); + if (limit_item && cJSON_IsNumber(limit_item)) { + uint8_t currentLimit = (uint8_t)limit_item->valuedouble; + + // Validar intervalo + if (currentLimit < 6 || currentLimit > 100) { + ESP_LOGW(TAG, "Rejected invalid currentLimit: %d", currentLimit); + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid currentLimit (must be 6-100)"); + return ESP_FAIL; + } + + esp_err_t err = load_balancing_set_max_grid_current(currentLimit); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to save currentLimit: %s", esp_err_to_name(err)); + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save setting"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit); + } + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Load balancing settings updated successfully"); + return ESP_OK; +} + +// Registro dos handlers na API HTTP +void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx) { + // GET + httpd_uri_t get_uri = { + .uri = "/api/v1/config/loadbalancing", + .method = HTTP_GET, + .handler = loadbalancing_config_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + // POST + httpd_uri_t post_uri = { + .uri = "/api/v1/config/loadbalancing", + .method = HTTP_POST, + .handler = loadbalancing_config_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/loadbalancing_settings_api.c === + + +// === Início de: components/rest_api/src/evse_settings_api.c === +// ========================= +// evse_settings_api.c +// ========================= +#include "evse_settings_api.h" +#include "evse_api.h" +#include "esp_log.h" +#include "cJSON.h" + +static const char *TAG = "evse_settings_api"; + +static esp_err_t config_settings_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "application/json"); + cJSON *config = cJSON_CreateObject(); + cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current()); + cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); + const char *json_str = cJSON_Print(config); + httpd_resp_sendstr(req, json_str); + free((void *)json_str); + cJSON_Delete(config); + return ESP_OK; +} + +static esp_err_t config_settings_post_handler(httpd_req_t *req) { + char buf[512]; + int len = httpd_req_recv(req, buf, sizeof(buf) - 1); + if (len <= 0) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); + return ESP_FAIL; + } + buf[len] = '\0'; + cJSON *json = cJSON_Parse(buf); + if (!json) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + + cJSON *current = cJSON_GetObjectItem(json, "currentLimit"); + if (current) evse_set_max_charging_current(current->valueint); + cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit"); + if (temp) evse_set_temp_threshold(temp->valueint); + + cJSON_Delete(json); + httpd_resp_sendstr(req, "Configurações atualizadas com sucesso"); + return ESP_OK; +} + +void register_evse_settings_handlers(httpd_handle_t server, void *ctx) { + httpd_uri_t get_uri = { + .uri = "/api/v1/config/settings", + .method = HTTP_GET, + .handler = config_settings_get_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &get_uri); + + httpd_uri_t post_uri = { + .uri = "/api/v1/config/settings", + .method = HTTP_POST, + .handler = config_settings_post_handler, + .user_ctx = ctx + }; + httpd_register_uri_handler(server, &post_uri); +} + +// === Fim de: components/rest_api/src/evse_settings_api.c === + + +// === Início de: components/rest_api/include/dashboard_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra o handler da dashboard (status geral do sistema) + */ +void register_dashboard_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/dashboard_api.h === + + +// === Início de: components/rest_api/include/static_file_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra o handler para servir arquivos estáticos da web (SPA) + */ +void register_static_file_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/static_file_api.h === + + +// === Início de: components/rest_api/include/network_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de configuração Wi-Fi e MQTT + */ +void register_network_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/network_api.h === + + +// === Início de: components/rest_api/include/auth_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de autenticação e gerenciamento de usuários + */ +void register_auth_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/auth_api.h === + + +// === Início de: components/rest_api/include/loadbalancing_settings_api.h === +// ========================= +// loadbalancing_settings_api.h +// ========================= + +#ifndef LOADBALANCING_SETTINGS_API_H +#define LOADBALANCING_SETTINGS_API_H + +#include "esp_err.h" +#include "esp_http_server.h" + +// Função para registrar os manipuladores de URI para as configurações de load balancing e solar +void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx); + +#endif // LOADBALANCING_SETTINGS_API_H + +// === Fim de: components/rest_api/include/loadbalancing_settings_api.h === + + +// === Início de: components/rest_api/include/rest_main.h === +#pragma once + +#include +#include + +#define SCRATCH_BUFSIZE (10240) + +typedef struct rest_server_context { + char base_path[ESP_VFS_PATH_MAX + 1]; + char scratch[SCRATCH_BUFSIZE]; +} rest_server_context_t; + +esp_err_t rest_server_init(const char *base_path); + +// === Fim de: components/rest_api/include/rest_main.h === + + +// === Início de: components/rest_api/include/meters_settings_api.h === +// ========================= +// meters_settings_api.h +// ========================= + +#ifndef METERS_SETTINGS_API_H +#define METERS_SETTINGS_API_H + +#include "esp_err.h" +#include "esp_http_server.h" + +// Função para registrar os manipuladores de URI para as configurações dos contadores +void register_meters_settings_handlers(httpd_handle_t server, void *ctx); + +#endif // METERS_SETTINGS_API_H + +// === Fim de: components/rest_api/include/meters_settings_api.h === + + +// === Início de: components/rest_api/include/ocpp_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers da configuração e status do OCPP + */ +void register_ocpp_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/ocpp_api.h === + + +// === Início de: components/rest_api/include/evse_settings_api.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Registra os handlers de configuração elétrica e limites de carregamento + */ +void register_evse_settings_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/rest_api/include/evse_settings_api.h === + + +// === Início de: components/network/src/wifi_2.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_mac.h" +#include "nvs.h" +#include "mdns.h" + +#include "wifi.h" + + +#include "nvs_flash.h" +#include + +#define WIFI_STORAGE_NAMESPACE "wifi_config" + + + +#define TAG "wifi" +#define AP_SSID "plx-%02x%02x%02x" +#define MDNS_HOSTNAME "plx%02x" + +#define NVS_NAMESPACE "wifi" + +static nvs_handle_t nvs; +static esp_netif_t *ap_netif; +EventGroupHandle_t wifi_event_group; + +// +// Event handler para modo AP +// +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT) { + switch (event_id) { + case WIFI_EVENT_AP_STACONNECTED: { + wifi_event_ap_staconnected_t *event = event_data; + ESP_LOGI(TAG, "STA " MACSTR " conectou, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupSetBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + break; + } + case WIFI_EVENT_AP_STADISCONNECTED: { + wifi_event_ap_stadisconnected_t *event = event_data; + ESP_LOGI(TAG, "STA " MACSTR " desconectou, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + break; + } + } + } +} + +// +// Iniciar o AP com SSID baseado no MAC +// +void wifi_ap_start(void) +{ + ESP_LOGI(TAG, "Iniciando AP"); + + ESP_ERROR_CHECK(esp_wifi_stop()); + + wifi_config_t ap_config = { + .ap = { + .ssid = "", + .ssid_len = 0, + .channel = 1, + .password = "", + .max_connection = 4, + .authmode = WIFI_AUTH_OPEN + } + }; + + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP)); + snprintf((char *)ap_config.ap.ssid, sizeof(ap_config.ap.ssid), AP_SSID, mac[3], mac[4], mac[5]); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); +} + +// +// Inicializar Wi-Fi em modo AP +// +void wifi_ini(void) +{ + ESP_LOGI(TAG, "Inicializando Wi-Fi (modo AP)"); + + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + /* + if (!esp_event_loop_is_running()) { + ESP_ERROR_CHECK(esp_event_loop_create_default()); + }*/ + + ap_netif = esp_netif_create_default_wifi_ap(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP)); + char hostname[16]; + snprintf(hostname, sizeof(hostname), MDNS_HOSTNAME, mac[5]); + + ESP_ERROR_CHECK(mdns_init()); + ESP_ERROR_CHECK(mdns_hostname_set(hostname)); + ESP_ERROR_CHECK(mdns_instance_name_set("EVSE Controller")); + + wifi_ap_start(); +} + +esp_netif_t *wifi_get_ap_netif(void) +{ + return ap_netif; +} + +esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password) { + + return ESP_OK; +} + +void wifi_get_ssid(char *value) { + // Your implementation here +} + +void wifi_get_password(char *value) { + // Your implementation here +} + +bool wifi_get_enabled(void) +{ + return true; +} + +// === Fim de: components/network/src/wifi_2.c === + + +// === Início de: components/network/src/wifi.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_mac.h" +#include "nvs.h" +#include "mdns.h" + +#include "wifi.h" + +#define AP_SSID "plx-%02x%02x%02x" + +#define MDNS_SSID "plx%02x" + +#define NVS_NAMESPACE "wifi" +#define NVS_ENABLED "enabled" +#define NVS_SSID "ssid" +#define NVS_PASSWORD "password" + +static const char *TAG = "wifi"; + +static nvs_handle_t nvs; + +static esp_netif_t *sta_netif; + +static esp_netif_t *ap_netif; + +EventGroupHandle_t wifi_event_group; + +static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "event_handler"); + + if (event_base == WIFI_EVENT) + { + if (event_id == WIFI_EVENT_AP_STACONNECTED) + { + ESP_LOGI(TAG, "STA connected"); + wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; + ESP_LOGI(TAG, "WiFi AP " MACSTR " join, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + } + if (event_id == WIFI_EVENT_AP_STADISCONNECTED) + { + ESP_LOGI(TAG, "AP STA disconnected"); + wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; + ESP_LOGI(TAG, "WiFi AP " MACSTR " leave, AID=%d", MAC2STR(event->mac), event->aid); + xEventGroupClearBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); + } + if (event_id == WIFI_EVENT_STA_DISCONNECTED) + { + ESP_LOGI(TAG, "STA disconnected"); + xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); + esp_wifi_connect(); + } + if (event_id == WIFI_EVENT_STA_START) + { + ESP_LOGI(TAG, "STA start"); + esp_wifi_connect(); + } + } + else if (event_base == IP_EVENT) + { + ESP_LOGI(TAG, "event_base == IP_EVENT"); + + if (event_id == IP_EVENT_STA_GOT_IP || event_id == IP_EVENT_GOT_IP6) + { + if (event_id == IP_EVENT_STA_GOT_IP) + { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "WiFi STA got ip: " IPSTR, IP2STR(&event->ip_info.ip)); + } + else + { + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + ESP_LOGI(TAG, "WiFi STA got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip)); + } + xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); + } + } +} + +static void sta_set_config(void) +{ + + ESP_LOGI(TAG, "sta_set_config"); + + if (wifi_get_enabled()) + { + wifi_config_t wifi_config = { + .sta = { + .pmf_cfg = { + .capable = true, + .required = false}}}; + wifi_get_ssid((char *)wifi_config.sta.ssid); + wifi_get_password((char *)wifi_config.sta.password); + + esp_wifi_set_mode(WIFI_MODE_STA); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); + } +} + +static void ap_set_config(void) +{ + + ESP_LOGI(TAG, "ap_set_config"); + + wifi_config_t wifi_ap_config = { + .ap = { + .max_connection = 1, + .authmode = WIFI_AUTH_OPEN}}; + uint8_t mac[6]; + esp_wifi_get_mac(ESP_IF_WIFI_AP, mac); + sprintf((char *)wifi_ap_config.ap.ssid, AP_SSID, mac[3], mac[4], mac[5]); + + wifi_config_t wifi_sta_config = {0}; + + esp_wifi_set_mode(WIFI_MODE_APSTA); + esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_ap_config); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config); +} + +static void sta_try_start(void) +{ + + ESP_LOGI(TAG, "sta_try_start"); + + sta_set_config(); + if (wifi_get_enabled()) + { + ESP_LOGI(TAG, "Starting STA"); + esp_wifi_start(); + xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT); + } +} + +void wifi_ini(void) +{ + + + ESP_LOGI(TAG, "Wifi init"); + + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + wifi_event_group = xEventGroupCreate(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + + ap_netif = esp_netif_create_default_wifi_ap(); + sta_netif = esp_netif_create_default_wifi_sta(); + + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + + char chargeid[6]; + uint8_t mac[6]; + esp_wifi_get_mac(ESP_IF_WIFI_AP, mac); + sprintf((char *)chargeid, MDNS_SSID, mac[5]); + + ESP_ERROR_CHECK(mdns_init()); + ESP_ERROR_CHECK(mdns_hostname_set(chargeid)); + ESP_ERROR_CHECK(mdns_instance_name_set("EVSE controller")); + + sta_try_start(); + +} + +esp_netif_t *wifi_get_sta_netif(void) +{ + return sta_netif; +} + +esp_netif_t *wifi_get_ap_netif(void) +{ + return ap_netif; +} + +esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password) +{ + + ESP_LOGI(TAG, "Wifi set config"); + + if (enabled) + { + if (ssid == NULL || strlen(ssid) == 0) + { + size_t len = 0; + nvs_get_str(nvs, NVS_SSID, NULL, &len); + if (len <= 1) + { + ESP_LOGE(TAG, "Required SSID"); + return ESP_ERR_INVALID_ARG; + } + } + } + + if (ssid != NULL && strlen(ssid) > 32) + { + ESP_LOGE(TAG, "SSID out of range"); + return ESP_ERR_INVALID_ARG; + } + + if (password != NULL && strlen(password) > 32) + { + ESP_LOGE(TAG, "Password out of range"); + return ESP_ERR_INVALID_ARG; + } + + nvs_set_u8(nvs, NVS_ENABLED, enabled); + if (ssid != NULL) + { + nvs_set_str(nvs, NVS_SSID, ssid); + } + if (password != NULL) + { + nvs_set_str(nvs, NVS_PASSWORD, password); + } + nvs_commit(nvs); + + ESP_LOGI(TAG, "Stopping AP/STA"); + xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT); + esp_wifi_stop(); + + sta_try_start(); + + return ESP_OK; +} + +uint16_t wifi_scan(wifi_scan_ap_t *scan_aps) +{ + + ESP_LOGI(TAG, "wifi_scan"); + + uint16_t number = WIFI_SCAN_SCAN_LIST_SIZE; + wifi_ap_record_t ap_info[WIFI_SCAN_SCAN_LIST_SIZE]; + uint16_t ap_count = 0; + memset(ap_info, 0, sizeof(ap_info)); + + esp_wifi_scan_start(NULL, true); + esp_wifi_scan_get_ap_records(&number, ap_info); + esp_wifi_scan_get_ap_num(&ap_count); + + ESP_LOGI(TAG, "wifi_scan --- %d", ap_count); + + for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++) + { + + ESP_LOGI(TAG, "wifi_scan ---"); + + strcpy(scan_aps[i].ssid, (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; + } + + return ap_count; +} + +bool wifi_get_enabled(void) +{ + uint8_t value = false; + nvs_get_u8(nvs, NVS_ENABLED, &value); + return value; +} + +void wifi_get_ssid(char *value) +{ + size_t len = 32; + value[0] = '\0'; + nvs_get_str(nvs, NVS_SSID, value, &len); +} + +void wifi_get_password(char *value) +{ + size_t len = 64; + value[0] = '\0'; + nvs_get_str(nvs, NVS_PASSWORD, value, &len); +} + +void wifi_ap_start(void) +{ + ESP_LOGI(TAG, "Starting AP"); + + xEventGroupClearBits(wifi_event_group, WIFI_STA_MODE_BIT); + esp_wifi_stop(); + + ap_set_config(); + esp_wifi_start(); + + xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); +} + +void wifi_ap_stop(void) +{ + ESP_LOGI(TAG, "Stopping AP"); + xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT); + esp_wifi_stop(); + + sta_try_start(); +} + +bool wifi_is_ap(void) +{ + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + return mode == WIFI_MODE_APSTA; +} + +// === Fim de: components/network/src/wifi.c === + + +// === Início de: components/network/include/wifi.h === +#ifndef WIFI_H_ +#define WIFI_H_ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_err.h" +#include "esp_netif.h" + +#define WIFI_SCAN_SCAN_LIST_SIZE 10 + +#define WIFI_AP_CONNECTED_BIT BIT0 +#define WIFI_AP_DISCONNECTED_BIT BIT1 +#define WIFI_STA_CONNECTED_BIT BIT2 +#define WIFI_STA_DISCONNECTED_BIT BIT3 +#define WIFI_AP_MODE_BIT BIT4 +#define WIFI_STA_MODE_BIT BIT5 + +typedef struct +{ + char ssid[32]; + int rssi; + bool auth; +} wifi_scan_ap_t; + +/** + * @brief WiFi event group WIFI_AP_CONNECTED_BIT | WIFI_AP_DISCONNECTED_BIT | WIFI_STA_CONNECTED_BIT | WIFI_STA_DISCONNECTED_BIT | WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT + * + */ +extern EventGroupHandle_t wifi_event_group; + +/** + * @brief Initialize WiFi + * + */ +void wifi_ini(void); + +/** + * @brief Return WiFi STA network interface + * + * @return esp_netif_t* + */ +esp_netif_t* wifi_get_sta_netif(void); + +/** + * @brief Return WiFi AP network interface + * + * @return esp_netif_t* + */ +esp_netif_t* wifi_get_ap_netif(void); + +/** + * @brief Set WiFi config + * + * @param enabled + * @param ssid NULL value will be skiped + * @param password NULL value will be skiped + * @return esp_err_t + */ +esp_err_t wifi_set_config(bool enabled, const char* ssid, const char* password); + +/** + * @brief Get WiFi STA enabled, stored in NVS + * + * @return true + * @return false + */ +bool wifi_get_enabled(void); + +/** + * @brief Scan for AP + * + * @param scan_aps array with length WIFI_SCAN_SCAN_LIST_SIZE + * @return uint16_t number of available AP + */ +uint16_t wifi_scan(wifi_scan_ap_t *scan_aps); + +/** + * @brief Get WiFi STA ssid, string length 32, stored in NVS + * + * @param value + */ +void wifi_get_ssid(char* value); + +/** + * @brief Get WiFi STA password, string length 32, stored in NVS + * + * @param value + */ +void wifi_get_password(char* value); + +/** + * @brief Start WiFi AP mode + * + */ +void wifi_ap_start(void); + +/** + * @brief Stop WiFi AP mode + * + */ +void wifi_ap_stop(void); + +#endif /* WIFI_H_ */ + +// === Fim de: components/network/include/wifi.h === + + +// === Início de: components/peripherals/src/ac_relay.c === +#include "esp_log.h" +#include "driver/gpio.h" + +#include "ac_relay.h" +#include "board_config.h" + +static const char* TAG = "ac_relay"; + +/** + * @brief Initialize the AC relay GPIO. + * + * Configures the specified GPIO pin as an output and sets its initial state to OFF (low). + */ +void ac_relay_init(void) +{ + gpio_config_t conf = { + .pin_bit_mask = BIT64(board_config.ac_relay_gpio), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_DISABLE, ///< Disabled unless required + .pull_up_en = GPIO_PULLUP_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + + esp_err_t ret = gpio_config(&conf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure GPIO (error: %s)", esp_err_to_name(ret)); + return; + } + + gpio_set_level(board_config.ac_relay_gpio, false); ///< Ensure relay starts OFF + ESP_LOGI(TAG, "AC relay initialized. Pin: %d", board_config.ac_relay_gpio); +} + +/** + * @brief Set the state of the AC relay. + * + * @param state True to turn the relay ON, False to turn it OFF. + */ +void ac_relay_set_state(bool state) +{ + ESP_LOGI(TAG, "Setting AC relay state: Pin: %d, State: %d", board_config.ac_relay_gpio, state); + + esp_err_t ret = gpio_set_level(board_config.ac_relay_gpio, state); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set GPIO level (error: %s)", esp_err_to_name(ret)); + } +} + +/** + * @brief Get the current state of the AC relay. + * + * @return true if the relay is ON, false if OFF. + */ +bool ac_relay_get_state(void) +{ + int level = gpio_get_level(board_config.ac_relay_gpio); + ESP_LOGD(TAG, "Current AC relay state: Pin: %d, State: %d", board_config.ac_relay_gpio, level); + return level; +} + +// === Fim de: components/peripherals/src/ac_relay.c === + + +// === Início de: components/peripherals/src/ntc_sensor.c === +#include +#include +#include "freertos/task.h" +#include "esp_log.h" +#include "ntc_sensor.h" +#include "ntc_driver.h" + +#include "adc.h" + +static const char *TAG = "temp_sensor"; + +#define MEASURE_PERIOD 15000 // 10s + +static float temp = 0.0; + +static ntc_device_handle_t ntc = NULL; + +static portMUX_TYPE temp_mux = portMUX_INITIALIZER_UNLOCKED; + +static void ntc_sensor_task_func(void *param) { + float t; + while (true) { + if (ntc_dev_get_temperature(ntc, &t) == ESP_OK) { + portENTER_CRITICAL(&temp_mux); + temp = t; + portEXIT_CRITICAL(&temp_mux); + } + vTaskDelay(pdMS_TO_TICKS(MEASURE_PERIOD)); + } +} + +float ntc_temp_sensor(void) { + float t; + portENTER_CRITICAL(&temp_mux); + t = temp; + portEXIT_CRITICAL(&temp_mux); + return t; +} + +void ntc_sensor_init(void) +{ + + ESP_LOGI(TAG, "ntc_sensor_init"); + + // Select the NTC sensor and initialize the hardware parameters + ntc_config_t ntc_config = { + .b_value = 3950, + .r25_ohm = 10000, + .fixed_ohm = 4700, + .vdd_mv = 3300, + .circuit_mode = CIRCUIT_MODE_NTC_GND, + .atten = ADC_ATTEN_DB_12, + .channel = ADC_CHANNEL_0, + .unit = ADC_UNIT_1}; + + // Create the NTC Driver and Init ADC + // ntc_device_handle_t ntc = NULL; + // adc_oneshot_unit_handle_t adc_handle = NULL; + ESP_ERROR_CHECK(ntc_dev_create(&ntc_config, &ntc, &adc_handle)); + ESP_ERROR_CHECK(ntc_dev_get_adc_handle(ntc, &adc_handle)); + + xTaskCreate(ntc_sensor_task_func, "ntc_sensor_task", 5 * 1024, NULL, 3, NULL); +} + +// === Fim de: components/peripherals/src/ntc_sensor.c === + + +// === Início de: components/peripherals/src/proximity.c === +#include "esp_log.h" + +#include "proximity.h" +#include "board_config.h" +#include "adc.h" + +static const char *TAG = "proximity"; + +void proximity_init(void) +{ + if (board_config.proximity) + { + adc_oneshot_chan_cfg_t config = { + .bitwidth = ADC_BITWIDTH_DEFAULT, + .atten = ADC_ATTEN_DB_12}; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.proximity_adc_channel, &config)); + } +} + +uint8_t proximity_get_max_current(void) +{ + int voltage; + adc_oneshot_read(adc_handle, board_config.proximity_adc_channel, &voltage); + adc_cali_raw_to_voltage(adc_cali_handle, voltage, &voltage); + + ESP_LOGI(TAG, "Measured: %dmV", voltage); + + uint8_t current; + + if (voltage >= board_config.proximity_down_threshold_8) + { + current = 8; + } + else if (voltage >= board_config.proximity_down_threshold_10) + { + current = 10; + } + + else if (voltage >= board_config.proximity_down_threshold_13) + { + current = 13; + } + else if (voltage >= board_config.proximity_down_threshold_20) + { + current = 20; + } + + else if (voltage >= board_config.proximity_down_threshold_25) + { + current = 25; + } + else if (voltage >= board_config.proximity_down_threshold_32) + { + current = 32; + } + else + { + current = 32; + } + + ESP_LOGI(TAG, "Max current: %dA", current); + + return current; +} +// === Fim de: components/peripherals/src/proximity.c === + + +// === Início de: components/peripherals/src/buzzer.c === +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "board_config.h" +#include "buzzer.h" +#include "evse_api.h" + +static gpio_num_t buzzer_gpio = GPIO_NUM_NC; +static evse_state_t last_buzzer_state = -1; +static QueueHandle_t buzzer_queue = NULL; + +void buzzer_on(void) { + if (buzzer_gpio != GPIO_NUM_NC) + gpio_set_level(buzzer_gpio, 1); +} + +void buzzer_off(void) { + if (buzzer_gpio != GPIO_NUM_NC) + gpio_set_level(buzzer_gpio, 0); +} + +// ---------------------- +// Padrões de Buzzer +// ---------------------- + +typedef struct { + uint16_t on_ms; + uint16_t off_ms; +} buzzer_pattern_step_t; + +typedef enum { + BUZZER_PATTERN_NONE = 0, + BUZZER_PATTERN_PLUGGED, + BUZZER_PATTERN_UNPLUGGED, + BUZZER_PATTERN_CHARGING, +} buzzer_pattern_id_t; + +static const buzzer_pattern_step_t pattern_plugged[] = { + {100, 100}, {200, 0} +}; + +static const buzzer_pattern_step_t pattern_unplugged[] = { + {150, 150}, {150, 150}, {150, 0} +}; + +static const buzzer_pattern_step_t pattern_charging[] = { + {80, 150}, {100, 120}, {120, 100}, {140, 0} +}; + +// ---------------------- +// Executor de padrões +// ---------------------- + +static void buzzer_execute_pattern(buzzer_pattern_id_t pattern_id) { + const buzzer_pattern_step_t *pattern = NULL; + size_t length = 0; + + switch (pattern_id) { + case BUZZER_PATTERN_PLUGGED: + pattern = pattern_plugged; + length = sizeof(pattern_plugged) / sizeof(pattern_plugged[0]); + break; + case BUZZER_PATTERN_UNPLUGGED: + pattern = pattern_unplugged; + length = sizeof(pattern_unplugged) / sizeof(pattern_unplugged[0]); + break; + case BUZZER_PATTERN_CHARGING: + pattern = pattern_charging; + length = sizeof(pattern_charging) / sizeof(pattern_charging[0]); + break; + default: + return; + } + + for (size_t i = 0; i < length; i++) { + buzzer_on(); + vTaskDelay(pdMS_TO_TICKS(pattern[i].on_ms)); + buzzer_off(); + if (pattern[i].off_ms > 0) + vTaskDelay(pdMS_TO_TICKS(pattern[i].off_ms)); + } +} + +// ---------------------- +// Task que toca o buzzer +// ---------------------- + +static void buzzer_worker_task(void *arg) { + buzzer_pattern_id_t pattern_id; + + while (true) { + if (xQueueReceive(buzzer_queue, &pattern_id, portMAX_DELAY)) { + //buzzer_execute_pattern(pattern_id); + } + } +} + +// ---------------------- +// Task de monitoramento +// ---------------------- + +static void buzzer_monitor_task(void *arg) { + while (true) { + evse_state_t current = evse_get_state(); + + if (current != last_buzzer_state) { + buzzer_pattern_id_t pattern_id = BUZZER_PATTERN_NONE; + + switch (current) { + case EVSE_STATE_A: + if (last_buzzer_state != EVSE_STATE_A) + pattern_id = BUZZER_PATTERN_UNPLUGGED; + break; + case EVSE_STATE_B1: + case EVSE_STATE_B2: + if (last_buzzer_state != EVSE_STATE_B1 && last_buzzer_state != EVSE_STATE_B2) + pattern_id = BUZZER_PATTERN_PLUGGED; + break; + case EVSE_STATE_C2: + case EVSE_STATE_D2: + if (last_buzzer_state != EVSE_STATE_C2 && last_buzzer_state != EVSE_STATE_D2) + pattern_id = BUZZER_PATTERN_CHARGING; + break; + default: + break; + } + + if (pattern_id != BUZZER_PATTERN_NONE) { + xQueueSend(buzzer_queue, &pattern_id, 0); // Não bloqueia + } + + last_buzzer_state = current; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +// ---------------------- +// Inicialização +// ---------------------- + +void buzzer_init(void) { + if (board_config.buzzer) { + buzzer_gpio = board_config.buzzer_gpio; + + gpio_config_t io_conf = { + .pin_bit_mask = BIT64(buzzer_gpio), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .pull_up_en = GPIO_PULLUP_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + gpio_set_level(buzzer_gpio, 0); + } + + buzzer_queue = xQueueCreate(4, sizeof(buzzer_pattern_id_t)); + + xTaskCreate(buzzer_monitor_task, "buzzer_monitor", 2048, NULL, 3, NULL); + xTaskCreate(buzzer_worker_task, "buzzer_worker", 2048, NULL, 3, NULL); +} + +// === Fim de: components/peripherals/src/buzzer.c === + + +// === Início de: components/peripherals/src/ds18x20.h === +/* + * Copyright (c) 2016 Grzegorz Hetman + * Copyright (c) 2016 Alex Stewart + * Copyright (c) 2018 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DS18X20_H +#define _DS18X20_H + +#include +#include "onewire.h" + +typedef onewire_addr_t ds18x20_addr_t; + +/** An address value which can be used to indicate "any device on the bus" */ +#define DS18X20_ANY ONEWIRE_NONE + +/** Family ID (lower address byte) of DS18B20 sensors */ +#define DS18B20_FAMILY_ID 0x28 + +/** Family ID (lower address byte) of DS18S20 sensors */ +#define DS18S20_FAMILY_ID 0x10 + +/** + * @brief Find the addresses of all ds18x20 devices on the bus. + * + * Scans the bus for all devices and places their addresses in the supplied + * array. If there are more than `addr_count` devices on the bus, only the + * first `addr_count` are recorded. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A pointer to an array of ::ds18x20_addr_t values. + * This will be populated with the addresses of the found + * devices. + * @param addr_count Number of slots in the `addr_list` array. At most this + * many addresses will be returned. + * @param found The number of devices found. Note that this may be less + * than, equal to, or more than `addr_count`, depending on + * how many ds18x20 devices are attached to the bus. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found); + +/** + * @brief Tell one or more sensors to perform a temperature measurement and + * conversion (CONVERT_T) operation. + * + * This operation can take up to 750ms to complete. + * + * If `wait=true`, this routine will automatically drive the pin high for the + * necessary 750ms after issuing the command to ensure parasitically-powered + * devices have enough power to perform the conversion operation (for + * non-parasitically-powered devices, this is not necessary but does not + * hurt). If `wait=false`, this routine will drive the pin high, but will + * then return immediately. It is up to the caller to wait the requisite time + * and then depower the bus using onewire_depower() or by issuing another + * command once conversion is done. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device on the bus. This can be set + * to ::DS18X20_ANY to send the command to all devices on the bus + * at the same time. + * @param wait Whether to wait for the necessary 750ms for the ds18x20 to + * finish performing the conversion before returning to the + * caller (You will normally want to do this). + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait); + +/** + * @brief Read the value from the last CONVERT_T operation. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18b20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18s20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation for multiple devices. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of int16_ts to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list); + +/** Perform a ds18x20_measure() followed by ds18s20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18s20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** Perform a ds18x20_measure() followed by ds18b20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** Perform a ds18x20_measure() followed by ds18x20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature); + +/** + * @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi() + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of int16_ts to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list); + +/** + * @brief Read the scratchpad data for a particular ds18x20 device. + * + * This is not generally necessary to do directly. It is done automatically + * as part of ds18x20_read_temperature(). + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 8-byte buffer to hold the read data. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Write the scratchpad data for a particular ds18x20 device. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to write. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 3-byte buffer to hold the data to write + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Issue the copy scratchpad command, copying current scratchpad to + * EEPROM. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to command. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr); + + +#endif /* _DS18X20_H */ +// === Fim de: components/peripherals/src/ds18x20.h === + + +// === Início de: components/peripherals/src/socket_lock.c === +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "nvs.h" + +#include "socket_lock.h" +#include "board_config.h" + +#define NVS_NAMESPACE "socket_lock" +#define NVS_OPERATING_TIME "op_time" +#define NVS_BREAK_TIME "break_time" +#define NVS_RETRY_COUNT "retry_count" +#define NVS_DETECTION_HIGH "detect_hi" + +#define OPERATING_TIME_MIN 100 +#define OPERATING_TIME_MAX 1000 +#define LOCK_DELAY 500 + +#define LOCK_BIT BIT0 +#define UNLOCK_BIT BIT1 +#define REPEAT_LOCK_BIT BIT2 +#define REPEAT_UNLOCK_BIT BIT3 + +static const char* TAG = "socket_lock"; + +static nvs_handle_t nvs; + +static uint16_t operating_time = 300; + +static uint16_t break_time = 1000; + +static bool detection_high; + +static uint8_t retry_count = 5; + +static socket_lock_status_t status; + +static TaskHandle_t socket_lock_task; + +static bool is_locked(void) +{ + gpio_set_level(board_config.socket_lock_a_gpio, 1); + gpio_set_level(board_config.socket_lock_b_gpio, 1); + + vTaskDelay(pdMS_TO_TICKS(board_config.socket_lock_detection_delay)); + + return gpio_get_level(board_config.socket_lock_detection_gpio) == detection_high; +} + +bool socket_lock_is_locked_state(void) +{ + return is_locked(); +} + +static void socket_lock_task_func(void* param) +{ + uint32_t notification; + + TickType_t previous_tick = 0; + uint8_t attempt = 0; + + while (true) { + if (xTaskNotifyWait(0x00, 0xff, ¬ification, portMAX_DELAY)) { + if (notification & (LOCK_BIT | UNLOCK_BIT)) { + attempt = retry_count; + } + + if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT)) { + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 1); + vTaskDelay(pdMS_TO_TICKS(operating_time)); + + if (!is_locked()) { + ESP_LOGI(TAG, "Unlock OK"); + status = SOCKED_LOCK_STATUS_IDLE; + } else { + if (attempt > 1) { + ESP_LOGW(TAG, "Not unlocked yet, repeating..."); + attempt--; + xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits); + } else { + ESP_LOGE(TAG, "Not unlocked"); + status = SOCKED_LOCK_STATUS_UNLOCKING_FAIL; + } + } + + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + } else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT)) { + if (notification & LOCK_BIT) { + vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); //delay before first lock attempt + } + gpio_set_level(board_config.socket_lock_a_gpio, 1); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + vTaskDelay(pdMS_TO_TICKS(operating_time)); + + if (is_locked()) { + ESP_LOGI(TAG, "Lock OK"); + status = SOCKED_LOCK_STATUS_IDLE; + } else { + if (attempt > 1) { + ESP_LOGW(TAG, "Not locked yet, repeating..."); + attempt--; + xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits); + } else { + ESP_LOGE(TAG, "Not locked"); + status = SOCKED_LOCK_STATUS_LOCKING_FAIL; + } + } + + gpio_set_level(board_config.socket_lock_a_gpio, 0); + gpio_set_level(board_config.socket_lock_b_gpio, 0); + } + + TickType_t delay_tick = xTaskGetTickCount() - previous_tick; + if (delay_tick < pdMS_TO_TICKS(break_time)) { + vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick); + } + previous_tick = xTaskGetTickCount(); + } + } +} + +void socket_lock_init(void) +{ + if (board_config.socket_lock) { + ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs)); + + nvs_get_u16(nvs, NVS_OPERATING_TIME, &operating_time); + + nvs_get_u16(nvs, NVS_BREAK_TIME, &break_time); + + nvs_get_u8(nvs, NVS_RETRY_COUNT, &retry_count); + + uint8_t u8; + if (nvs_get_u8(nvs, NVS_DETECTION_HIGH, &u8) == ESP_OK) { + detection_high = u8; + } + + gpio_config_t io_conf = {}; + + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task); + } +} + +bool socket_lock_is_detection_high(void) +{ + return detection_high; +} + +void socket_lock_set_detection_high(bool _detection_high) +{ + detection_high = _detection_high; + + nvs_set_u8(nvs, NVS_DETECTION_HIGH, detection_high); + nvs_commit(nvs); +} + +uint16_t socket_lock_get_operating_time(void) +{ + return operating_time; +} + +esp_err_t socket_lock_set_operating_time(uint16_t _operating_time) +{ + if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX) { + ESP_LOGE(TAG, "Operating time out of range"); + return ESP_ERR_INVALID_ARG; + } + + operating_time = _operating_time; + nvs_set_u16(nvs, NVS_OPERATING_TIME, operating_time); + nvs_commit(nvs); + + return ESP_OK; +} + +uint8_t socket_lock_get_retry_count(void) +{ + return retry_count; +} + +void socket_lock_set_retry_count(uint8_t _retry_count) +{ + retry_count = _retry_count; + nvs_set_u8(nvs, NVS_RETRY_COUNT, retry_count); + nvs_commit(nvs); +} + +uint16_t socket_lock_get_break_time(void) +{ + return break_time; +} + +esp_err_t socket_lock_set_break_time(uint16_t _break_time) +{ + if (_break_time < board_config.socket_lock_min_break_time) { + ESP_LOGE(TAG, "Operating time out of range"); + return ESP_ERR_INVALID_ARG; + } + + break_time = _break_time; + nvs_set_u16(nvs, NVS_BREAK_TIME, break_time); + nvs_commit(nvs); + + return ESP_OK; +} + +void socket_lock_set_locked(bool locked) +{ + ESP_LOGI(TAG, "Set locked %d", locked); + + xTaskNotify(socket_lock_task, locked ? LOCK_BIT : UNLOCK_BIT, eSetBits); + status = SOCKED_LOCK_STATUS_OPERATING; +} + +socket_lock_status_t socket_lock_get_status(void) +{ + return status; +} +// === Fim de: components/peripherals/src/socket_lock.c === + + +// === Início de: components/peripherals/src/temp_sensor.c === +#include +#include +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/gpio.h" + +#include "temp_sensor.h" +#include "lm75a.h" + +#define MAX_SENSORS 5 +#define MEASURE_PERIOD 10000 // 10s +#define MEASURE_ERR_THRESHOLD 3 + +static const char *TAG = "temp_sensor"; + +static uint8_t sensor_count = 0; + +static int16_t low_temp = 0; + +static int high_temp = 0; + +static uint8_t measure_err_count = 0; + +static void temp_sensor_task_func(void *param) +{ + while (true) + { + high_temp = lm75a_read_temperature(0); + + vTaskDelay(pdMS_TO_TICKS(MEASURE_PERIOD)); + } +} + +void temp_sensor_init(void) +{ + + ESP_LOGW(TAG, "temp_sensor_init"); + + lm75a_init(); + + xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 5, NULL); +} + +uint8_t temp_sensor_get_count(void) +{ + return sensor_count; +} + +int16_t temp_sensor_get_low(void) +{ + return low_temp; +} + +int temp_sensor_get_high(void) +{ + return high_temp; +} + +bool temp_sensor_is_error(void) +{ + return sensor_count == 0 || measure_err_count > MEASURE_ERR_THRESHOLD; +} +// === Fim de: components/peripherals/src/temp_sensor.c === + + +// === Início de: components/peripherals/src/aux_io.c === +#include +#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; +} +// === Fim de: components/peripherals/src/aux_io.c === + + +// === Início de: components/peripherals/src/lm75a.c === +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define I2C_MASTER_NUM I2C_NUM_1 +#define I2C_MASTER_SCL_IO GPIO_NUM_22 // CONFIG_EXAMPLE_I2C_SCL /*!< gpio number for I2C master clock */ +#define I2C_MASTER_SDA_IO GPIO_NUM_21 // CONFIG_EXAMPLE_I2C_SDA /*!< gpio number for I2C master data */ +#define I2C_MASTER_FREQ_HZ 100000 // CONFIG_I2C_TRANS_SPEED /*!< I2C master clock frequency */ +#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ +#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ +#define LM75A_SLAVE_ADDR 0x48 // CONFIG_LM75A_SLAVE_ADDR /*!< LM75A slave address, you can set any 7bit value */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ + +/* +#define GPIO_INPUT_IO_0 CONFIG_LM75A_OS_PIN +#define GPIO_OUTPUT_IO_0 CONFIG_LM75A_VCC_PIN +#define GPIO_OUTPUT_PIN_SEL (1ULL << GPIO_OUTPUT_IO_0) +#define GPIO_INPUT_PIN_SEL (1ULL << GPIO_INPUT_IO_0) +#define ESP_INTR_FLAG_DEFAULT 0 +*/ + +// static xQueueHandle gpio_evt_queue = NULL; +// static int gpio_int_task_enable = 0; +// static TaskHandle_t gpio_int_task_handle = NULL; + +/** + * @brief test code to read esp-i2c-slave + * We need to fill the buffer of esp slave device, then master can read them out. + * + * _______________________________________________________________________________________ + * | start | slave_addr + rd_bit +ack | read n-1 bytes + ack | read 1 byte + nack | stop | + * --------|--------------------------|----------------------|--------------------|------| + * + */ +static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t *data_rd, size_t size) +{ + if (size == 0) + { + return ESP_OK; + } + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (LM75A_SLAVE_ADDR << 1) | READ_BIT, ACK_CHECK_EN); + if (size > 1) + { + i2c_master_read(cmd, data_rd, size - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief Test code to write esp-i2c-slave + * Master device write data to slave(both esp32), + * the data will be stored in slave buffer. + * We can read them out from slave buffer. + * + * ___________________________________________________________________ + * | start | slave_addr + wr_bit + ack | write n bytes + ack | stop | + * --------|---------------------------|----------------------|------| + * + */ +static esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t *data_wr, size_t size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (LM75A_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief i2c master initialization + */ +static void i2c_master_init() +{ + int i2c_master_port = I2C_MASTER_NUM; + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = I2C_MASTER_SDA_IO; + conf.sda_pullup_en = GPIO_PULLUP_DISABLE; + conf.scl_io_num = I2C_MASTER_SCL_IO; + conf.scl_pullup_en = GPIO_PULLUP_DISABLE; + conf.master.clk_speed = I2C_MASTER_FREQ_HZ; + conf.clk_flags = 0; + + i2c_param_config(i2c_master_port, &conf); + i2c_driver_install(i2c_master_port, conf.mode, + I2C_MASTER_RX_BUF_DISABLE, + I2C_MASTER_TX_BUF_DISABLE, 0); +} + +int lm75a_read_temperature(int show) +{ + uint8_t buf[2]; + float tmp; + buf[0] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + if (show) + printf("lm75a_read_temperature=%.1f\n", tmp); + return tmp; +} + +/* +static void IRAM_ATTR gpio_isr_handler(void *arg) +{ + uint32_t gpio_num = (uint32_t)arg; + xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); +} + +static void gpio_int_task(void *arg) +{ + uint32_t io_num; + gpio_int_task_enable = 1; + while (gpio_int_task_enable) + { + if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) + { + + // read temperature to clean int; + if (io_num == GPIO_INPUT_IO_0) + { + printf("GPIO[%d] intr, val: %d\n\n", io_num, gpio_get_level(io_num)); + lm75a_read_temperature(0); // read to clean interrupt. + } + } + } + printf("quit gpio_int_task\n"); + if (gpio_evt_queue) + { + vQueueDelete(gpio_evt_queue); + gpio_evt_queue = NULL; + } + gpio_int_task_handle = NULL; + vTaskDelete(NULL); +} + +void init_os_gpio() +{ + printf("init_os_gpio!\n"); + + if (gpio_evt_queue == NULL) + gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); + + if (gpio_int_task_handle == NULL) + { + xTaskCreate(gpio_int_task, "gpio_int_task", 2048, NULL, 10, &gpio_int_task_handle); + // install gpio isr service + gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); + // hook isr handler for specific gpio pin again + gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void *)GPIO_INPUT_IO_0); + } +} + +static void deinit_os_gpio() +{ + printf("deinit_os_gpio!\n"); + + if (gpio_int_task_handle) + { + gpio_isr_handler_remove(GPIO_INPUT_IO_0); + gpio_uninstall_isr_service(); + gpio_int_task_enable = 0; + int io = 0; + xQueueSend(gpio_evt_queue, &io, 0); // send a fake signal to quit task. + } +} + +static void lm75a_vcc_enable() +{ + gpio_config_t io_conf; + // enable output for vcc + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + // enable input for interrupt + io_conf.intr_type = GPIO_PIN_INTR_NEGEDGE; // GPIO_PIN_INTR_ANYEDGE; + io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + gpio_set_pull_mode(GPIO_INPUT_IO_0, GPIO_FLOATING); + gpio_config(&io_conf); + gpio_set_level(GPIO_OUTPUT_IO_0, 1); +} + +static void lm75a_vcc_disable() +{ + gpio_set_level(GPIO_OUTPUT_IO_0, 0); +} +*/ + +void lm75a_init() +{ + // lm75a_vcc_enable(); + i2c_master_init(); +} + +void lm75a_deinit() +{ + // deinit_os_gpio(); + i2c_driver_delete(I2C_MASTER_NUM); + // lm75a_vcc_disable(); +} + +void lm75a_set_tos(int tos) +{ + uint8_t buf[4]; + printf("lm75a_set_tos: %d\n", tos); + // set Tos: + buf[0] = 0x3; + buf[1] = (tos & 0xff); + buf[2] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 3); +} + +void lm75a_set_thys(int thys) +{ + uint8_t buf[4]; + printf("lm75a_set_thys: %d\n", thys); + // set Thyst: + buf[0] = 0x2; + buf[1] = (thys & 0xff); + buf[2] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 3); +} + +void lm75a_get_tos() +{ + uint8_t buf[4]; + float tmp; + buf[0] = 0x3; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + + printf("lm75a_get_tos: %.1f\n", tmp); +} + +void lm75a_get_thys() +{ + uint8_t buf[4]; + float tmp; + buf[0] = 0x2; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 1); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); + tmp = buf[0]; + if (buf[1] & 128) + tmp += 0.5; + + printf("lm75a_get_thys: %.1f\n", tmp); +} + +void lm75a_set_int(int en) +{ + uint8_t buf[2]; + + en = !!en; + if (en) + { + printf("lm75a_set_int: %d\n", en); + buf[0] = 0x1; + buf[1] = (1 << 1); // D1 set to 1; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 2); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); // do one time read to clean interrupt before enter interrupt mode. + // gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_NEGEDGE); + // init_os_gpio(); + } + else + { + printf("lm75a_set_int: %d\n", en); + // deinit_os_gpio(); + buf[0] = 0x1; + buf[1] = 0; + i2c_master_write_slave(I2C_MASTER_NUM, buf, 2); + i2c_master_read_slave(I2C_MASTER_NUM, buf, 2); // do one time read to clean interrupt before enter interrupt mode. + } +} + +void lm75a_get_osio() +{ + // printf("os_io: %d\n", gpio_get_level(GPIO_INPUT_IO_0)); +} + +// === Fim de: components/peripherals/src/lm75a.c === + + +// === Início de: components/peripherals/src/onewire.c === +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +#include +#include +#include +#include "rom/ets_sys.h" + +#include "onewire.h" + +#define ONEWIRE_SELECT_ROM 0x55 +#define ONEWIRE_SKIP_ROM 0xcc +#define ONEWIRE_SEARCH 0xf0 +#define ONEWIRE_CRC8_TABLE + +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + +// Waits up to `max_wait` microseconds for the specified pin to go high. +// Returns true if successful, false if the bus never comes high (likely +// shorted). +static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait) +{ + bool state; + for (int i = 0; i < ((max_wait + 4) / 5); i++) { + if (gpio_get_level(pin)) + break; + ets_delay_us(5); + } + state = gpio_get_level(pin); + // Wait an extra 1us to make sure the devices have an adequate recovery + // time before we drive things low again. + ets_delay_us(1); + return state; +} + +static void setup_pin(gpio_num_t pin, bool open_drain) +{ + gpio_set_direction(pin, open_drain ? GPIO_MODE_INPUT_OUTPUT_OD : GPIO_MODE_OUTPUT); + // gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY); +} + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return false; +// +// Returns true if a device asserted a presence pulse, false otherwise. +// +bool onewire_reset(gpio_num_t pin) +{ + setup_pin(pin, true); + + gpio_set_level(pin, 1); + // wait until the wire is high... just in case + if (!_onewire_wait_for_bus(pin, 250)) + return false; + + gpio_set_level(pin, 0); + ets_delay_us(480); + + portENTER_CRITICAL(&mux); + gpio_set_level(pin, 1); // allow it to float + ets_delay_us(70); + bool r = !gpio_get_level(pin); + portEXIT_CRITICAL(&mux); + + // Wait for all devices to finish pulling the bus low before returning + if (!_onewire_wait_for_bus(pin, 410)) + return false; + + return r; +} + +static bool _onewire_write_bit(gpio_num_t pin, bool v) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return false; + + portENTER_CRITICAL(&mux); + if (v) { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(10); + gpio_set_level(pin, 1); // allow output high + ets_delay_us(55); + } else { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(65); + gpio_set_level(pin, 1); // allow output high + } + ets_delay_us(1); + portEXIT_CRITICAL(&mux); + + return true; +} + +static int _onewire_read_bit(gpio_num_t pin) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return -1; + + portENTER_CRITICAL(&mux); + gpio_set_level(pin, 0); + ets_delay_us(2); + gpio_set_level(pin, 1); // let pin float, pull up will raise + ets_delay_us(11); + int r = gpio_get_level(pin); // Must sample within 15us of start + ets_delay_us(48); + portEXIT_CRITICAL(&mux); + + return r; +} + +// Write a byte. The writing code uses open-drain mode and expects the pullup +// resistor to pull the line high when not driven low. If you need strong +// power after the write (e.g. DS18B20 in parasite power mode) then call +// onewire_power() after this is complete to actively drive the line high. +// +bool onewire_write(gpio_num_t pin, uint8_t v) +{ + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) + if (!_onewire_write_bit(pin, (bitMask & v))) + return false; + + return true; +} + +bool onewire_write_bytes(gpio_num_t pin, const uint8_t* buf, size_t count) +{ + for (size_t i = 0; i < count; i++) + if (!onewire_write(pin, buf[i])) + return false; + + return true; +} + +// Read a byte +// +int onewire_read(gpio_num_t pin) +{ + int r = 0; + + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + int bit = _onewire_read_bit(pin); + if (bit < 0) + return -1; + else if (bit) + r |= bitMask; + } + return r; +} + +bool onewire_read_bytes(gpio_num_t pin, uint8_t* buf, size_t count) +{ + size_t i; + int b; + + for (i = 0; i < count; i++) { + b = onewire_read(pin); + if (b < 0) + return false; + buf[i] = b; + } + return true; +} + +bool onewire_select(gpio_num_t pin, onewire_addr_t addr) +{ + uint8_t i; + + if (!onewire_write(pin, ONEWIRE_SELECT_ROM)) + return false; + + for (i = 0; i < 8; i++) { + if (!onewire_write(pin, addr & 0xff)) + return false; + addr >>= 8; + } + + return true; +} + +bool onewire_skip_rom(gpio_num_t pin) +{ + return onewire_write(pin, ONEWIRE_SKIP_ROM); +} + +bool onewire_power(gpio_num_t pin) +{ + // Make sure the bus is not being held low before driving it high, or we + // may end up shorting ourselves out. + if (!_onewire_wait_for_bus(pin, 10)) + return false; + + setup_pin(pin, false); + gpio_set_level(pin, 1); + + return true; +} + +void onewire_depower(gpio_num_t pin) +{ + setup_pin(pin, true); +} + +void onewire_search_start(onewire_search_t* search) +{ + // reset the search state + memset(search, 0, sizeof(*search)); +} + +void onewire_search_prefix(onewire_search_t* search, uint8_t family_code) +{ + uint8_t i; + + search->rom_no[0] = family_code; + for (i = 1; i < 8; i++) { + search->rom_no[i] = 0; + } + search->last_discrepancy = 64; + search->last_device_found = false; +} + +// Perform a search. If the next device has been successfully enumerated, its +// ROM address will be returned. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return 1 : device found, ROM number in ROM_NO buffer +// 0 : device not found, end of search +// +onewire_addr_t onewire_search_next(onewire_search_t* search, gpio_num_t pin) +{ + //TODO: add more checking for read/write errors + uint8_t id_bit_number; + uint8_t last_zero, search_result; + int rom_byte_number; + int8_t id_bit, cmp_id_bit; + onewire_addr_t addr; + unsigned char rom_byte_mask; + bool search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!search->last_device_found) { + // 1-Wire reset + if (!onewire_reset(pin)) { + // reset the search + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } + + // issue the search command + onewire_write(pin, ONEWIRE_SEARCH); + + // loop to do the search + do { + // read a bit and its complement + id_bit = _onewire_read_bit(pin); + cmp_id_bit = _onewire_read_bit(pin); + + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < search->last_discrepancy) + search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == search->last_discrepancy); + + // if 0 was picked then record its position in LastZero + if (!search_direction) + last_zero = id_bit_number; + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction) + search->rom_no[rom_byte_number] |= rom_byte_mask; + else + search->rom_no[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + _onewire_write_bit(pin, search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set last_discrepancy,last_device_found,search_result + search->last_discrepancy = last_zero; + + // check for last device + if (search->last_discrepancy == 0) + search->last_device_found = true; + + search_result = 1; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !search->rom_no[0]) { + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } else { + addr = 0; + for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) { + addr = (addr << 8) | search->rom_no[rom_byte_number]; + } + //printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr); + } + return addr; +} + +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#ifdef ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (c) 2000 Dallas Semiconductor Corporation +static const uint8_t dscrc_table[] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, + 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, + 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, + 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, + 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, + 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, + 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, + 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, + 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, + 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, + 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, + 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, + 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, + 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 +}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t onewire_crc8(const uint8_t* data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + crc = dscrc_table[crc ^ *data++]; + + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t onewire_crc8(const uint8_t* data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + { + uint8_t inbyte = *data++; + for (int i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) + crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif /* ONEWIRE_CRC8_TABLE */ + +// Compute the 1-Wire CRC16 and compare it against the received CRC. +// Example usage (reading a DS2408): +// // Put everything in a buffer so we can compute the CRC easily. +// uint8_t buf[13]; +// buf[0] = 0xF0; // Read PIO Registers +// buf[1] = 0x88; // LSB address +// buf[2] = 0x00; // MSB address +// WriteBytes(net, buf, 3); // Write 3 cmd bytes +// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 +// if (!CheckCRC16(buf, 11, &buf[11])) { +// // Handle error. +// } +// +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param inverted_crc - The two CRC16 bytes in the received data. +// This should just point into the received data, +// *not* at a 16-bit integer. +// @param crc - The crc starting value (optional) +// @return 1, iff the CRC matches. +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv) +{ + uint16_t crc = ~onewire_crc16(input, len, crc_iv); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +// Compute a Dallas Semiconductor 16 bit CRC. This is required to check +// the integrity of data received from many 1-Wire devices. Note that the +// CRC computed here is *not* what you'll get from the 1-Wire network, +// for two reasons: +// 1) The CRC is transmitted bitwise inverted. +// 2) Depending on the endian-ness of your processor, the binary +// representation of the two-byte return value may have a different +// byte order than the two bytes you get from 1-Wire. +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param crc - The crc starting value (optional) +// @return The CRC16, as defined by Dallas Semiconductor. +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv) +{ + uint16_t crc = crc_iv; + static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + uint16_t i; + for (i = 0; i < len; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +} +// === Fim de: components/peripherals/src/onewire.c === + + +// === Início de: components/peripherals/src/onewire.h === +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +#ifndef ONEWIRE_H_ +#define ONEWIRE_H_ + +#include +#include +#include "driver/gpio.h" + +/** + * Type used to hold all 1-Wire device ROM addresses (64-bit) + */ +typedef uint64_t onewire_addr_t; + +/** + * Structure to contain the current state for onewire_search_next(), etc + */ +typedef struct +{ + uint8_t rom_no[8]; + uint8_t last_discrepancy; + bool last_device_found; +} onewire_search_t; + +/** + * ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device + * (CRC mismatch), and so can be useful as an indicator for "no-such-device", + * etc. + */ +#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL)) + +/** + * @brief Perform a 1-Wire reset cycle. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if at least one device responds with a presence pulse, + * `false` if no devices were detected (or the bus is shorted, etc) + */ +bool onewire_reset(gpio_num_t pin); + +/** + * @brief Issue a 1-Wire "ROM select" command to select a particular device. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param addr The ROM address of the device to select + * + * @return `true` if the "ROM select" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_select(gpio_num_t pin, const onewire_addr_t addr); + +/** + * @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if the "skip ROM" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_skip_rom(gpio_num_t pin); + +/** + * @brief Write a byte on the onewire bus. + * + * The writing code uses open-drain mode and expects the pullup resistor to + * pull the line high when not driven low. If you need strong power after the + * write (e.g. DS18B20 in parasite power mode) then call ::onewire_power() + * after this is complete to actively drive the line high. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param v The byte value to write + * + * @return `true` if successful, `false` on error. + */ +bool onewire_write(gpio_num_t pin, uint8_t v); + +/** + * @brief Write multiple bytes on the 1-Wire bus. + * + * See ::onewire_write() for more info. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param buf A pointer to the buffer of bytes to be written + * @param count Number of bytes to write + * + * @return `true` if all bytes written successfully, `false` on error. + */ +bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count); + +/** + * @brief Read a byte from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return the read byte on success, negative value on error. + */ +int onewire_read(gpio_num_t pin); + +/** + * @brief Read multiple bytes from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param[out] buf A pointer to the buffer to contain the read bytes + * @param count Number of bytes to read + * + * @return `true` on success, `false` on error. + */ +bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count); + +/** + * @brief Actively drive the bus high to provide extra power for certain + * operations of parasitically-powered devices. + * + * For parasitically-powered devices which need more power than can be + * provided via the normal pull-up resistor, it may be necessary for some + * operations to drive the bus actively high. This function can be used to + * perform that operation. + * + * The bus can be depowered once it is no longer needed by calling + * ::onewire_depower(), or it will be depowered automatically the next time + * ::onewire_reset() is called to start another command. + * + * @note Make sure the device(s) you are powering will not pull more current + * than the ESP32/ESP8266 is able to supply via its GPIO pins (this is + * especially important when multiple devices are on the same bus and + * they are all performing a power-intensive operation at the same time + * (i.e. multiple DS18B20 sensors, which have all been given a + * "convert T" operation by using ::onewire_skip_rom())). + * + * @note This routine will check to make sure that the bus is already high + * before driving it, to make sure it doesn't attempt to drive it high + * while something else is pulling it low (which could cause a reset or + * damage the ESP32/ESP8266). + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` on success, `false` on error. + */ +bool onewire_power(gpio_num_t pin); + +/** + * @brief Stop forcing power onto the bus. + * + * You only need to do this if you previously called ::onewire_power() to drive + * the bus high and now want to allow it to float instead. Note that + * onewire_reset() will also automatically depower the bus first, so you do + * not need to call this first if you just want to start a new operation. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + */ +void onewire_depower(gpio_num_t pin); + +/** + * @brief Clear the search state so that it will start from the beginning on + * the next call to ::onewire_search_next(). + * + * @param[out] search The onewire_search_t structure to reset. + */ +void onewire_search_start(onewire_search_t *search); + +/** + * @brief Setup the search to search for devices with the specified + * "family code". + * + * @param[out] search The onewire_search_t structure to update. + * @param family_code The "family code" to search for. + */ +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code); + +/** + * @brief Search for the next device on the bus. + * + * The order of returned device addresses is deterministic. You will always + * get the same devices in the same order. + * + * @note It might be a good idea to check the CRC to make sure you didn't get + * garbage. + * + * @return the address of the next device on the bus, or ::ONEWIRE_NONE if + * there is no next address. ::ONEWIRE_NONE might also mean that + * the bus is shorted, there are no devices, or you have already + * retrieved all of them. + */ +onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin); + +/** + * @brief Compute a Dallas Semiconductor 8 bit CRC. + * + * These are used in the ROM address and scratchpad registers to verify the + * transmitted data is correct. + */ +uint8_t onewire_crc8(const uint8_t *data, uint8_t len); + +/** + * @brief Compute the 1-Wire CRC16 and compare it against the received CRC. + * + * Example usage (reading a DS2408): + * @code{.c} + * // Put everything in a buffer so we can compute the CRC easily. + * uint8_t buf[13]; + * buf[0] = 0xF0; // Read PIO Registers + * buf[1] = 0x88; // LSB address + * buf[2] = 0x00; // MSB address + * onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes + * onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + * if (!onewire_check_crc16(buf, 11, &buf[11])) { + * // TODO: Handle error. + * } + * @endcode + * + * @param input Array of bytes to checksum. + * @param len Number of bytes in `input` + * @param inverted_crc The two CRC16 bytes in the received data. + * This should just point into the received data, + * *not* at a 16-bit integer. + * @param crc_iv The crc starting value (optional) + * + * @return `true` if the CRC matches, `false` otherwise. + */ +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv); + +/** + * @brief Compute a Dallas Semiconductor 16 bit CRC. + * + * This is required to check the integrity of data received from many 1-Wire + * devices. Note that the CRC computed here is *not* what you'll get from the + * 1-Wire network, for two reasons: + * + * 1. The CRC is transmitted bitwise inverted. + * 2. Depending on the endian-ness of your processor, the binary + * representation of the two-byte return value may have a different + * byte order than the two bytes you get from 1-Wire. + * + * @param input Array of bytes to checksum. + * @param len How many bytes are in `input`. + * @param crc_iv The crc starting value (optional) + * + * @return the CRC16, as defined by Dallas Semiconductor. + */ +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv); + + +#endif /* ONEWIRE_H_ */ +// === Fim de: components/peripherals/src/onewire.h === + + +// === Início de: components/peripherals/src/ds18x20.c === +/* + * Copyright (c) 2016 Grzegorz Hetman + * Copyright (c) 2016 Alex Stewart + * Copyright (c) 2018 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include +#include +#include "ds18x20.h" + +#define ds18x20_WRITE_SCRATCHPAD 0x4E +#define ds18x20_READ_SCRATCHPAD 0xBE +#define ds18x20_COPY_SCRATCHPAD 0x48 +#define ds18x20_READ_EEPROM 0xB8 +#define ds18x20_READ_PWRSUPPLY 0xB4 +#define ds18x20_SEARCHROM 0xF0 +#define ds18x20_SKIP_ROM 0xCC +#define ds18x20_READROM 0x33 +#define ds18x20_MATCHROM 0x55 +#define ds18x20_ALARMSEARCH 0xEC +#define ds18x20_CONVERT_T 0x44 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + +static const char* TAG = "ds18x20"; + +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + portENTER_CRITICAL(&mux); + onewire_write(pin, ds18x20_CONVERT_T); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + portEXIT_CRITICAL(&mux); + + if (wait){ + vTaskDelay(pdMS_TO_TICKS(750)); + onewire_depower(pin); + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer) +{ + CHECK_ARG(buffer); + + uint8_t crc; + uint8_t expected_crc; + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_READ_SCRATCHPAD); + + for (int i = 0; i < 8; i++) + buffer[i] = onewire_read(pin); + crc = onewire_read(pin); + + expected_crc = onewire_crc8(buffer, 8); + if (crc != expected_crc) + { + ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1], + buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer) +{ + CHECK_ARG(buffer); + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_WRITE_SCRATCHPAD); + + for (int i = 0; i < 3; i++) + onewire_write(pin, buffer[i]); + + return ESP_OK; +} + +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + portENTER_CRITICAL(&mux); + onewire_write(pin, ds18x20_COPY_SCRATCHPAD); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + portEXIT_CRITICAL(&mux); + + // And then it needs to keep that power up for 10ms. + vTaskDelay(pdMS_TO_TICKS(10)); + onewire_depower(pin); + + return ESP_OK; +} + +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + + *temperature = ((int16_t)temp * 625.0) / 100; + + return ESP_OK; +} + +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4; + + *temperature = (temp * 625) / 100 - 25; + + return ESP_OK; +} + +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + if ((uint8_t)addr == DS18B20_FAMILY_ID) { + return ds18b20_read_temperature(pin, addr, temperature); + } else { + return ds18s20_read_temperature(pin, addr, temperature); + } +} + +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18b20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18s20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18x20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list) +{ + CHECK_ARG(result_list && addr_count); + + CHECK(ds18x20_measure(pin, DS18X20_ANY, true)); + + return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list); +} + +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, size_t* found) +{ + CHECK_ARG(addr_list && addr_count); + + onewire_search_t search; + onewire_addr_t addr; + + *found = 0; + onewire_search_start(&search); + while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE) + { + uint8_t family_id = (uint8_t)addr; + if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID) + { + if (*found < addr_count) + addr_list[*found] = addr; + *found += 1; + } + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list) +{ + CHECK_ARG(result_list); + + esp_err_t res = ESP_OK; + for (size_t i = 0; i < addr_count; i++) + { + esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]); + if (tmp != ESP_OK) + res = tmp; + } + return res; +} + +// === Fim de: components/peripherals/src/ds18x20.c === + + +// === Início de: components/peripherals/src/led.c === +#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_error.h" +#include "evse_api.h" + +#define LED_UPDATE_INTERVAL_MS 100 +#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}; +static TimerHandle_t led_update_timer = NULL; +static evse_state_t led_state = -1; + +// ---------------------------- +// Funções Internas +// ---------------------------- + +static void led_update_timer_callback(TimerHandle_t xTimer); +static void led_update(void); +static void led_apply_by_state(evse_state_t state); + +static inline void led_gpio_write(gpio_num_t gpio, bool level) { + if (gpio != GPIO_NUM_NC) + gpio_set_level(gpio, level); +} + +static void led_timer_callback(TimerHandle_t xTimer) +{ + led_t *led = (led_t *)pvTimerGetTimerID(xTimer); + led->on = !led->on; + led_gpio_write(led->gpio, led->on); + uint32_t next_time = led->on ? led->ontime : led->offtime; + + xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME); +} + +// ---------------------------- +// 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)); + } + + if (!led_update_timer) { + led_update_timer = xTimerCreate("led_update_timer", + pdMS_TO_TICKS(LED_UPDATE_INTERVAL_MS), + pdTRUE, NULL, + led_update_timer_callback); + if (led_update_timer) { + xTimerStart(led_update_timer, BLOCK_TIME); + } else { + ESP_LOGE(TAG, "Failed to create LED update timer"); + } + } +} + +// ---------------------------- +// 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; + + // Evita reconfiguração idêntica + 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; + led_gpio_write(led->gpio, 0); + } else if (offtime == 0) { + led->on = true; + led_gpio_write(led->gpio, 1); + } else { + led->on = true; + led_gpio_write(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; + } +} + +// ---------------------------- +// Controle por Estado +// ---------------------------- + +static void led_apply_by_state(evse_state_t state) +{ + // Reset todos + 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 (state) { + case EVSE_STATE_A: + led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON); + break; + case EVSE_STATE_B1: + case EVSE_STATE_B2: + case EVSE_STATE_C1: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON); + break; + case EVSE_STATE_C2: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT); + break; + case EVSE_STATE_D1: + case EVSE_STATE_D2: + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_BLINK_FAST); + break; + case EVSE_STATE_E: + case EVSE_STATE_F: + led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); + break; + default: + break; + } +} + +// ---------------------------- +// Timer Update +// ---------------------------- + +static void led_update(void) +{ + if (evse_error_is_active()) { + led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); + led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF); + led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF); + return; + } + + evse_state_t current = evse_get_state(); + + if (current != led_state) { + led_state = current; + led_apply_by_state(current); + } +} + +static void led_update_timer_callback(TimerHandle_t xTimer) +{ + (void)xTimer; + led_update(); +} + +// === Fim de: components/peripherals/src/led.c === + + +// === Início de: components/peripherals/src/rcm.c === +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_log.h" + +#include "rcm.h" +#include "board_config.h" +#include "evse_api.h" + +// static bool do_test = false; + +// static bool triggered = false; + +// static bool test_triggered = false; + +// static void IRAM_ATTR rcm_isr_handler(void* arg) +// { +// if (!do_test) { +// triggered = true; +// } else { +// test_triggered = true; +// } +// } + +void rcm_init(void) +{ + if (board_config.rcm) { + gpio_config_t io_conf = {}; + + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = BIT64(board_config.rcm_test_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + io_conf.mode = GPIO_MODE_INPUT; + // io_conf.intr_type = GPIO_INTR_POSEDGE; + io_conf.pin_bit_mask = BIT64(board_config.rcm_gpio); + ESP_ERROR_CHECK(gpio_config(&io_conf)); + //ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.rcm_gpio, rcm_isr_handler, NULL)); + } +} + +bool rcm_test(void) +{ + // do_test = true; + // test_triggered = false; + + // gpio_set_level(board_config.rcm_test_gpio, 1); + // vTaskDelay(pdMS_TO_TICKS(100)); + // gpio_set_level(board_config.rcm_test_gpio, 0); + + // do_test = false; + + // return test_triggered; + + gpio_set_level(board_config.rcm_test_gpio, 1); + vTaskDelay(pdMS_TO_TICKS(100)); + bool success = gpio_get_level(board_config.rcm_gpio) == 1; + gpio_set_level(board_config.rcm_test_gpio, 0); + + return success; +} + +bool rcm_is_triggered(void) +{ + // bool _triggered = triggered; + // if (gpio_get_level(board_config.rcm_gpio) == 0) { + // triggered = false; + // } + // return _triggered; + if (gpio_get_level(board_config.rcm_gpio) == 1) { + vTaskDelay(pdMS_TO_TICKS(1)); + return gpio_get_level(board_config.rcm_gpio) == 1; + } + + return false; +} +// === Fim de: components/peripherals/src/rcm.c === + + +// === Início de: components/peripherals/src/adc.c === +#include "adc.h" +#include "esp_log.h" + +const static char* TAG = "adc"; + +adc_oneshot_unit_handle_t adc_handle; + +adc_cali_handle_t adc_cali_handle; + +void adc_init(void) +{ + adc_oneshot_unit_init_cfg_t conf = { + .unit_id = ADC_UNIT_1 + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&conf, &adc_handle)); + + bool calibrated = false; + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "Calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle) == ESP_OK) { + calibrated = true; + } + } +#endif + +#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "Calibration scheme version is %s", "Line Fitting"); + adc_cali_line_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, +#if CONFIG_IDF_TARGET_ESP32 + .default_vref = 1100 +#endif + }; + if (adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle) == ESP_OK) { + calibrated = true; + } + } +#endif + + if (!calibrated) { + ESP_LOGE(TAG, "No calibration scheme"); + ESP_ERROR_CHECK(ESP_FAIL); + } +} +// === Fim de: components/peripherals/src/adc.c === + + +// === Início de: components/peripherals/src/adc121s021_dma.c === +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "adc121s021_dma.h" + +#define TAG "adc_dma" + +#define PIN_NUM_MOSI 23 +#define PIN_NUM_MISO 19 +#define PIN_NUM_CLK 18 +#define PIN_NUM_CS 5 + +#define SPI_HOST_USED SPI2_HOST +#define SAMPLE_SIZE_BYTES 2 +#define ADC_BITS 12 + +static spi_device_handle_t adc_spi; + +void adc121s021_dma_init(void) +{ + spi_bus_config_t buscfg = { + .mosi_io_num = PIN_NUM_MOSI, + .miso_io_num = PIN_NUM_MISO, + .sclk_io_num = PIN_NUM_CLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = SAMPLE_SIZE_BYTES, + }; + + spi_device_interface_config_t devcfg = { + .clock_speed_hz = 6000000, // 6 MHz + .mode = 0, + .spics_io_num = PIN_NUM_CS, + .queue_size = 2, + .flags = SPI_DEVICE_NO_DUMMY, + }; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST_USED, &buscfg, SPI_DMA_CH_AUTO)); + ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST_USED, &devcfg, &adc_spi)); +} + +bool adc121s021_dma_get_sample(uint16_t *sample) +{ + uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy TX + uint8_t rx_buffer[2] = {0}; + + spi_transaction_t t = { + .length = 16, // 16 bits + .tx_buffer = tx_buffer, + .rx_buffer = rx_buffer, + .flags = 0 + }; + + esp_err_t err = spi_device_transmit(adc_spi, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "SPI transmit error: %s", esp_err_to_name(err)); + return false; + } + + // Extrai os 12 bits significativos da resposta do ADC121S021 + *sample = ((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF; + + return true; +} + +// === Fim de: components/peripherals/src/adc121s021_dma.c === + + +// === Início de: components/peripherals/src/peripherals.c === +#include "peripherals.h" +#include "adc.h" +#include "led.h" +#include "buzzer.h" +#include "proximity.h" +#include "ac_relay.h" +#include "socket_lock.h" +#include "rcm.h" +#include "aux_io.h" +#include "ntc_sensor.h" + +void peripherals_init(void) +{ + ac_relay_init(); + led_init(); + buzzer_init(); + adc_init(); + proximity_init(); + // socket_lock_init(); + // rcm_init(); + //energy_meter_init(); + // aux_init(); + ntc_sensor_init(); +} +// === Fim de: components/peripherals/src/peripherals.c === + + +// === Início de: components/peripherals/include/adc121s021_dma.h === +#ifndef ADC_DMA_H_ +#define ADC_DMA_H_ + + +#include +#include + +void adc121s021_dma_init(void); +bool adc121s021_dma_get_sample(uint16_t *sample); + + +#endif /* ADC_DMA_h_ */ + +// === Fim de: components/peripherals/include/adc121s021_dma.h === + + +// === Início de: components/peripherals/include/peripherals.h === +#ifndef PERIPHERALS_H +#define PERIPHERALS_H + +void peripherals_init(void); + +#endif /* PERIPHERALS_H */ + +// === Fim de: components/peripherals/include/peripherals.h === + + +// === Início de: components/peripherals/include/rcm.h === +#ifndef RCM_H_ +#define RCM_H_ + +#include + +/** + * @brief Initialize residual current monitor + * + */ +void rcm_init(void); + +/** + * @brief Test residual current monitor + * + * @return true + * @return false + */ +bool rcm_test(void); + +/** + * @brief Residual current monitor was detected leakage + * + * @return true + * @return false + */ +bool rcm_is_triggered(void); + +#endif /* RCM_H_ */ + +// === Fim de: components/peripherals/include/rcm.h === + + +// === Início de: components/peripherals/include/aux_io.h === +#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_ */ +// === Fim de: components/peripherals/include/aux_io.h === + + +// === Início de: components/peripherals/include/led.h === +#ifndef LED_H_ +#define LED_H_ + +#include +#include + +/** + * @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_ */ + +// === Fim de: components/peripherals/include/led.h === + + +// === Início de: components/peripherals/include/buzzer.h === +#ifndef BUZZER_H_ +#define BUZZER_H_ + +#include + +/** + * @brief Inicializa o buzzer e inicia monitoramento automático do estado EVSE. + */ +void buzzer_init(void); + +/** + * @brief Liga e desliga o buzzer manualmente (uso interno ou testes). + */ +void buzzer_on(void); +void buzzer_off(void); + +/** + * @brief Ativa o buzzer por um período fixo (em milissegundos). + */ +void buzzer_beep_ms(uint16_t ms); + +#endif /* BUZZER_H_ */ + +// === Fim de: components/peripherals/include/buzzer.h === + + +// === Início de: components/peripherals/include/ac_relay.h === +#ifndef AC_RELAY_H_ +#define AC_RELAY_H_ + +#include + +/** + * @brief Inicializa o relé de corrente alternada. + */ +void ac_relay_init(void); + +/** + * @brief Define o estado do relé de corrente alternada. + * + * @param state true para ligar, false para desligar. + */ +void ac_relay_set_state(bool state); + +/** + * @brief Retorna o estado atual do relé de corrente alternada. + * + * @return true se estiver ligado, false se desligado. + */ +bool ac_relay_get_state(void); + +#endif /* AC_RELAY_H_ */ + +// === Fim de: components/peripherals/include/ac_relay.h === + + +// === Início de: components/peripherals/include/lm75a.h === +#ifndef LM75A_H +#define LM75A_H + +#include "esp_err.h" // Para o uso de tipos de erro do ESP-IDF, caso esteja utilizando. + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Inicializa o sensor LM75A. + * + * Configura o sensor para leitura e define os pinos de comunicação. + */ +esp_err_t lm75a_init(void); + +/** + * @brief Desinicializa o sensor LM75A. + * + * Libera os recursos usados pelo sensor. + */ +esp_err_t lm75a_deinit(void); + +/** + * @brief Lê a temperatura do LM75A. + * + * @param show Se for 1, a temperatura será exibida em algum tipo de log ou interface. + * Se for 0, o valor é apenas retornado sem exibição. + * @return A temperatura lida em graus Celsius. + */ +float lm75a_read_temperature(int show); + +/** + * @brief Define o valor do limite de temperatura (T_OS) para o sensor LM75A. + * + * @param tos O limite de temperatura de sobrecarga (T_OS) em graus Celsius. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_tos(int tos); + +/** + * @brief Define o valor do limite de temperatura de histerese (T_HYS) para o sensor LM75A. + * + * @param thys O limite de histerese de temperatura (T_HYS) em graus Celsius. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_thys(int thys); + +/** + * @brief Obtém o limite de temperatura de sobrecarga (T_OS) do sensor LM75A. + * + * @return O valor atual de T_OS em graus Celsius. + */ +int lm75a_get_tos(void); + +/** + * @brief Obtém o limite de temperatura de histerese (T_HYS) do sensor LM75A. + * + * @return O valor atual de T_HYS em graus Celsius. + */ +int lm75a_get_thys(void); + +/** + * @brief Habilita ou desabilita a interrupção do LM75A. + * + * @param en 1 para habilitar a interrupção, 0 para desabilitar. + * @return ESP_OK em caso de sucesso ou código de erro se falhar. + */ +esp_err_t lm75a_set_int(int en); + +/** + * @brief Obtém o estado do pino OS (Overtemperature Shutdown) do LM75A. + * + * @return 1 se o pino OS estiver ativo (indica que a temperatura de sobrecarga foi atingida), + * 0 caso contrário. + */ +int lm75a_get_osio(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LM75A_H */ + +// === Fim de: components/peripherals/include/lm75a.h === + + +// === Início de: components/peripherals/include/ntc_sensor.h === +#ifndef NTC_SENSOR_H_ +#define NTC_SENSOR_H_ + +/** + * @brief Initialize ntc senso + * + */ +void ntc_sensor_init(void); + +/** + * @brief Return temperature after temp_sensor_measure + * + * @return float + */ +float ntc_temp_sensor(void); + +#endif /* NTC_SENSOR_H_ */ + +// === Fim de: components/peripherals/include/ntc_sensor.h === + + +// === Início de: components/peripherals/include/proximity.h === +#ifndef PROXIMITY_H_ +#define PROXIMITY_H_ + +#include + +/** + * @brief Initialize proximity check + * + */ +void proximity_init(void); + +/** + * @brief Return measured value of max current on PP + * + * @return current in A + */ +uint8_t proximity_get_max_current(void); + +#endif /* PROXIMITY_H_ */ + +// === Fim de: components/peripherals/include/proximity.h === + + +// === Início de: components/peripherals/include/socket_lock.h === +#ifndef SOCKED_LOCK_H_ +#define SOCKED_LOCK_H_ + +#include "esp_err.h" + +typedef enum +{ + SOCKED_LOCK_STATUS_IDLE, + SOCKED_LOCK_STATUS_OPERATING, + SOCKED_LOCK_STATUS_LOCKING_FAIL, + SOCKED_LOCK_STATUS_UNLOCKING_FAIL +} socket_lock_status_t; + +/** + * @brief Initialize socket lock + * + */ +void socket_lock_init(void); + +/** + * @brief Get socket lock detection on high, stored in NVS + * + * @return true when locked has zero resistance + * @return false when unlocked has zero resistance + */ +bool socket_lock_is_detection_high(void); + +/** + * @brief Set socket lock detection on high, stored in NVS + * + * @param detection_high + */ +void socket_lock_set_detection_high(bool detection_high); + +/** + * @brief Get socket lock operating time + * + * @return time in ms + */ +uint16_t socket_lock_get_operating_time(void); + +/** + * @brief Set socket lock operating time + * + * @param operating_time - time in ms + * @return esp_err_t + */ +esp_err_t socket_lock_set_operating_time(uint16_t operating_time); + +/** + * @brief Get socket lock retry count + * + * @return retry count + */ +uint8_t socket_lock_get_retry_count(void); + +/** + * @brief Set socket lock retry count + * + * @param retry_count + */ +void socket_lock_set_retry_count(uint8_t retry_count); + +/** + * @brief Get socket lock break time + * + * @return time in ms + */ +uint16_t socket_lock_get_break_time(void); + +/** + * @brief Set socket lock break time + * + * @param break_time + * @return esp_err_t + */ +esp_err_t socket_lock_set_break_time(uint16_t break_time); + +/** + * @brief Set socke lock to locked / unlocked state + * + * @param locked + */ +void socket_lock_set_locked(bool locked); + +/** + * @brief Get socket lock status + * + * @return socket_lock_status_t + */ +socket_lock_status_t socket_lock_get_status(void); + +/** + * @brief Read the current physical lock state using the detection pin. + */ +bool socket_lock_is_locked_state(void); + + +#endif /* SOCKED_LOCK_H_ */ + +// === Fim de: components/peripherals/include/socket_lock.h === + + +// === Início de: components/peripherals/include/adc.h === +#ifndef ADC_H_ +#define ADC_H_ + +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" + +extern adc_oneshot_unit_handle_t adc_handle; + +extern adc_cali_handle_t adc_cali_handle; + +void adc_init(void); + +#endif /* ADC_H_ */ +// === Fim de: components/peripherals/include/adc.h === + + +// === Início de: components/peripherals/include/temp_sensor.h === +#ifndef TEMP_SENSOR_H_ +#define TEMP_SENSOR_H_ + +#include +#include "esp_err.h" + +/** + * @brief Initialize DS18S20 temperature sensor bus + * + */ +void temp_sensor_init(void); + +/** + * @brief Get found sensor count + * + * @return uint8_t + */ +uint8_t temp_sensor_get_count(void); + +/** + * @brief Return lowest temperature after temp_sensor_measure + * + * @return int16_t + */ +int16_t temp_sensor_get_low(void); + +/** + * @brief Return highest temperature after temp_sensor_measure + * + * @return int + */ +int temp_sensor_get_high(void); + +/** + * @brief Return temperature sensor error + * + * @return bool + */ +bool temp_sensor_is_error(void); + +#endif /* TEMP_SENSOR_H_ */ + +// === Fim de: components/peripherals/include/temp_sensor.h === diff --git a/projeto_unificado.zip b/projeto_unificado.zip new file mode 100644 index 0000000000000000000000000000000000000000..3e6a8a57709495b380f4095e2b5fbb9849930c0d GIT binary patch literal 53746 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�ykP5c)@?Dn0xy<2m9~9JdJ&^8 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 DKO53^ literal 0 HcmV?d00001 diff --git a/readproject.py b/readproject.py new file mode 100644 index 0000000..a047a1e --- /dev/null +++ b/readproject.py @@ -0,0 +1,67 @@ +import os + +TAMANHO_MAX = 31000 # Limite por arquivo + +def coletar_arquivos(diretorios, extensoes=(".c", ".h")): + arquivos = [] + for diretorio in diretorios: + for raiz, pastas, nomes_arquivos in os.walk(diretorio): + pastas[:] = [p for p in pastas if p != "build"] # Ignorar "build" + for nome in nomes_arquivos: + if nome.endswith(extensoes): + caminho_completo = os.path.join(raiz, nome) + arquivos.append(caminho_completo) + return arquivos + +def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX): + parte = 1 + conteudo_atual = "" + total_arquivos = 0 + + for arquivo in arquivos: + try: + with open(arquivo, "r", encoding="utf-8") as f_origem: + conteudo = f_origem.read() + except Exception as e: + print(f"⚠️ Erro ao ler {arquivo}: {e}") + continue + + bloco = f"\n\n// === Início de: {arquivo} ===\n{conteudo}\n// === Fim de: {arquivo} ===\n" + + # Se ultrapassar o limite, salva em um novo arquivo + if len(conteudo_atual) + len(bloco) > limite: + nome_saida = f"{prefixo}{parte}.c" + with open(nome_saida, "w", encoding="utf-8") as f_saida: + f_saida.write(conteudo_atual) + print(f"✅ Criado: {nome_saida}") + parte += 1 + conteudo_atual = "" # reinicia buffer + + conteudo_atual += bloco + total_arquivos += 1 + + # Salvar o que sobrou + if conteudo_atual: + nome_saida = f"{prefixo}{parte}.c" + with open(nome_saida, "w", encoding="utf-8") as f_saida: + f_saida.write(conteudo_atual) + print(f"✅ Criado: {nome_saida}") + + print(f"\n🔹 {total_arquivos} arquivos de código processados.") + print(f"🔹 Arquivos gerados: {parte}") + +def main(): + diretorio_main = "main" + componentes_escolhidos = [ + "evse", "loadbalancer", "auth", "manager_meter", + "rest_api", "network", "peripherals" + ] + + diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos] + diretorios_para_incluir = [diretorio_main] + diretorios_componentes + + arquivos = coletar_arquivos(diretorios_para_incluir) + unir_em_partes(arquivos) + +if __name__ == "__main__": + main()