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 0000000..3e6a8a5 Binary files /dev/null and b/projeto_unificado.zip differ 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()