#include "SerialService.h" #ifndef CANSERVICE_H #define CANSERVICE_H #include #include #include // Garante o core de aplicação (ESP32-S3) #ifndef APP_CPU_NUM #define APP_CPU_NUM 1 #endif class CanService { public: typedef void (*OnReceiveCallback)(int packetSize, int senderId, CanMessagePosicaoDados posicao, byte* data, int dataLength); // --- IDs especiais do teu protocolo --- uint8_t ID_Num_sMOD = 0; uint8_t ID_Num_sTOD = 250; uint8_t ID_Num_sLRA = 251; CanService(uint8_t nodeId, uint32_t baudRate = 250000) : _nodeId(nodeId), _baudRate(baudRate) {} void begin() { PrintTela("[CAN] Iniciando TWAI..."); // Modo NORMAL (com ACK) twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL); // Aumenta filas internas do driver para evitar enrosco g_config.tx_queue_len = 32; g_config.rx_queue_len = 32; // (alerts_enabled será reconfigurado depois de start) twai_timing_config_t t_config; // 500 kbit/s (ajuste se precisar) if (_baudRate == 250000) t_config = TWAI_TIMING_CONFIG_250KBITS(); else if (_baudRate == 500000) t_config = TWAI_TIMING_CONFIG_500KBITS(); // Começa aceitando tudo para depurar; depois afinamos a máscara twai_filter_config_t f_config = { .acceptance_code = (uint32_t(_nodeId) << 21), .acceptance_mask = ~(0x7FFu << 21), .single_filter = true }; esp_err_t err = twai_driver_install(&g_config, &t_config, &f_config); if (err != ESP_OK) { PrintTela("[CAN] Erro ao instalar driver: ", false); PrintTela(String(err)); return; } err = twai_start(); if (err != ESP_OK) { PrintTela("[CAN] Erro ao iniciar TWAI: ", false); PrintTela(String(err)); return; } // Habilita alerts úteis uint32_t alert_mask = 0; alert_mask |= TWAI_ALERT_BUS_OFF; alert_mask |= TWAI_ALERT_RX_QUEUE_FULL; alert_mask |= TWAI_ALERT_TX_FAILED; #if defined(TWAI_ALERT_PERIPH_ERR) alert_mask |= TWAI_ALERT_PERIPH_ERR; #elif defined(TWAI_ALERT_PERIPH_RESET) alert_mask |= TWAI_ALERT_PERIPH_RESET; #endif #if defined(TWAI_ALERT_RECOVERY_COMPLETE) alert_mask |= TWAI_ALERT_RECOVERY_COMPLETE; #elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS) alert_mask |= TWAI_ALERT_RECOVERY_IN_PROGRESS; #endif twai_reconfigure_alerts(alert_mask, nullptr); // --- Filas da nossa arquitetura --- callbacksQ = xQueueCreate(64, sizeof(twai_message_t)); // jobs de RX -> callback keysQ = xQueueCreate(64, sizeof(uint16_t)); // chaves coalescentes para TX if (!callbacksQ || !keysQ) { PrintTela("[CAN] Falha ao criar filas"); } // --- Tasks --- xTaskCreatePinnedToCore(CanTaskRxWrapper, "CanTaskRx", 4096, this, 6, &CanTaskRxHandle, APP_CPU_NUM); xTaskCreatePinnedToCore(CallbackWorkerWrapper,"CallbackWorker",6144,this,5,&CallbackWorkerHandle,APP_CPU_NUM); xTaskCreatePinnedToCore(CanTaskTxWrapper, "CanTaskTx", 4096, this, 4, &CanTaskTxHandle, APP_CPU_NUM); xTaskCreatePinnedToCore(HealthTaskWrapper, "CanHealth", 4096, this, 3, &HealthTaskHandle, APP_CPU_NUM); // Watchdog por task (vamos resetar dentro dos loops) esp_task_wdt_init(5, true); esp_task_wdt_add(CanTaskRxHandle); esp_task_wdt_add(CallbackWorkerHandle); esp_task_wdt_add(CanTaskTxHandle); esp_task_wdt_add(HealthTaskHandle); PrintTela("[CAN] TWAI iniciado com sucesso"); } // --- Filas e tasks --- QueueHandle_t callbacksQ = nullptr; // fila para o callback worker QueueHandle_t keysQ = nullptr; // fila de chaves para TX coalescente // (Opcional) Se quiser manter uma RX intermediária: QueueHandle_t canQueueRx = nullptr; TaskHandle_t CanTaskRxHandle = NULL; TaskHandle_t CallbackWorkerHandle = NULL; TaskHandle_t CanTaskTxHandle = NULL; TaskHandle_t HealthTaskHandle = NULL; // --- Métricas/health --- volatile uint64_t last_rx_ts = 0; // us volatile uint64_t last_tx_ts = 0; // us volatile uint32_t rx_drops = 0; volatile uint32_t tx_retries = 0; // --- TX coalescing (tabela simples) --- static const int MAX_KEYS = 64; // ajuste conforme nº (pos,addr) struct Slot { uint16_t key = 0; bool used = false; bool pending = false; // 🔧 NOVO: chave já enfileirada para envio twai_message_t msg; uint32_t gen = 0; }; Slot latestByKey[MAX_KEYS]; // Helpers de chave (pos,addr -> 16 bits) static inline uint16_t make_key(const twai_message_t& m) { return (m.data_length_code >= 2) ? ( (uint16_t(m.data[0])<<8) | uint16_t(m.data[1]) ) : 0xFFFF; } bool put_latest(uint16_t key, const twai_message_t& m) { int idx = key % MAX_KEYS; for (int i=0; i(pvParameters)->CanTaskRx(pvParameters); } void CanTaskRx(void* pvParameters) { twai_message_t message; for (;;) { // WDT esp_task_wdt_reset(); if (twai_receive(&message, pdMS_TO_TICKS(50)) == ESP_OK) { // ignore frames que não vamos tratar if (message.data_length_code == 0 || message.extd || message.rtr) { continue; } // marca vida do barramento SEMPRE que recebeu algo válido last_rx_ts = esp_timer_get_time(); if (DebugMode) { PrintTela("[CAN RX] ID: 0x" + String(message.identifier, HEX) + " Len: " + String(message.data_length_code)); } // tenta enfileirar pro worker if (callbacksQ && xQueueSend(callbacksQ, &message, 0) != pdTRUE) { if (DebugMode) { PrintTela("Erro ao adicionar mensagem recebida na fila: " + String(rx_drops)); } rx_drops++; // (opcional) política "drop oldest": remove 1 e tenta de novo twai_message_t dump; if (xQueueReceive(callbacksQ, &dump, 0) == pdTRUE) { xQueueSend(callbacksQ, &message, 0); } vTaskDelay(pdMS_TO_TICKS(1)); // dá uma respirada } } vTaskDelay(1); } } static void CallbackWorkerWrapper(void *pvParameters) { static_cast(pvParameters)->CallbackWorker(pvParameters); } void CallbackWorker(void* pvParameters) { twai_message_t msg; for (;;) { // WDT esp_task_wdt_reset(); if (xQueueReceive(callbacksQ, &msg, portMAX_DELAY) == pdTRUE) { if (_callback && msg.data_length_code > 0) { CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)msg.data[0]; _callback(msg.data_length_code, msg.identifier, posicao, &msg.data[1], msg.data_length_code - 1); } } // yield leve taskYIELD(); } } void PublicarTx(const twai_message_t& msg) { // Sanidade básica if (msg.data_length_code < 2 || msg.data_length_code > 8) return; if (msg.extd || msg.rtr) return; // trabalhamos com standard data frames uint16_t key = make_key(msg); if (key == 0xFFFF) return; // Atualiza a versão mais nova na tabela int idx = key % MAX_KEYS; bool stored = false; for (int i=0; i(pvParameters)->CanTaskTx(pvParameters); } // --- Task de TX com retry/backoff e limpeza do "pending" --- void CanTaskTx(void* pvParameters) { uint16_t key; for (;;) { // WDT esp_task_wdt_reset(); if (xQueueReceive(keysQ, &key, portMAX_DELAY) == pdTRUE) { // busca a msg mais nova dessa chave twai_message_t m{}; int idx = key % MAX_KEYS; int foundIdx = -1; for (int i=0; i(pvParameters)->HealthTask(pvParameters); } bool reinit_twai_unsafe_() { // ⚠️ Use os mesmos pinos/bitrate do begin(); ajuste se necessário twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL); g_config.tx_queue_len = 32; g_config.rx_queue_len = 32; twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) return false; if (twai_start() != ESP_OK) { twai_driver_uninstall(); return false; } uint32_t alert_mask = 0; alert_mask |= TWAI_ALERT_BUS_OFF; alert_mask |= TWAI_ALERT_RX_QUEUE_FULL; alert_mask |= TWAI_ALERT_TX_FAILED; #if defined(TWAI_ALERT_PERIPH_ERR) alert_mask |= TWAI_ALERT_PERIPH_ERR; #elif defined(TWAI_ALERT_PERIPH_RESET) alert_mask |= TWAI_ALERT_PERIPH_RESET; #endif #if defined(TWAI_ALERT_RECOVERY_COMPLETE) alert_mask |= TWAI_ALERT_RECOVERY_COMPLETE; #elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS) alert_mask |= TWAI_ALERT_RECOVERY_IN_PROGRESS; #endif twai_reconfigure_alerts(alert_mask, nullptr); return true; } void HealthTask(void* pvParameters) { const uint32_t RX_TIMEOUT_MS = 3000; // ajuste conforme seu tráfego esperado const uint32_t TX_STALL_MS = 2000; // quanto tempo uma chave pode ficar sem sair const uint8_t MAX_REC_FAILS = 3; // depois disso, reboot static uint8_t consecutive_rec_fails = 0; for (;;) { esp_task_wdt_reset(); uint64_t now = esp_timer_get_time(); // ---- Alerts do driver ---- uint32_t alerts = 0; if (twai_read_alerts(&alerts, 0) == ESP_OK && alerts) { if (alerts & TWAI_ALERT_BUS_OFF) { PrintTela("[CAN ALERT] BUS OFF → iniciando recovery..."); if (twai_initiate_recovery() != ESP_OK) { PrintTela("[CAN ALERT] Falha ao iniciar recovery; reiniciando driver..."); twai_stop(); twai_driver_uninstall(); if (!reinit_twai_unsafe_()) consecutive_rec_fails++; else consecutive_rec_fails = 0; } } #if defined(TWAI_ALERT_RECOVERY_COMPLETE) if (alerts & TWAI_ALERT_RECOVERY_COMPLETE) { PrintTela("[CAN ALERT] Recovery completo."); consecutive_rec_fails = 0; } #elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS) // Algumas versões só têm "em progresso"; trate como sinal de vida do recovery if (alerts & TWAI_ALERT_RECOVERY_IN_PROGRESS) { PrintTela("[CAN ALERT] Recovery em progresso (OK)."); // opcional: zere contador se quiser considerar como sinal saudável consecutive_rec_fails = 0; } #endif if (alerts & TWAI_ALERT_RX_QUEUE_FULL) { PrintTela("[CAN ALERT] RX interno do driver CHEIO."); } if (alerts & TWAI_ALERT_TX_FAILED) { PrintTela("[CAN ALERT] TX FAILED."); } #if defined(TWAI_ALERT_PERIPH_ERR) if (alerts & TWAI_ALERT_PERIPH_ERR) { PrintTela("[CAN ALERT] ERRO no periférico CAN. Reiniciando driver..."); twai_stop(); twai_driver_uninstall(); if (!reinit_twai_unsafe_()) consecutive_rec_fails++; else consecutive_rec_fails = 0; } #elif defined(TWAI_ALERT_PERIPH_RESET) if (alerts & TWAI_ALERT_PERIPH_RESET) { PrintTela("[CAN ALERT] RESET do periférico CAN detectado. Reconfigurando..."); twai_stop(); twai_driver_uninstall(); if (!reinit_twai_unsafe_()) consecutive_rec_fails++; else consecutive_rec_fails = 0; } #endif } // ---- Liveness de RX ---- if (last_rx_ts && ((now - last_rx_ts) / 1000ULL) > RX_TIMEOUT_MS) { PrintTela("[CAN HLTH] Sem RX há " + String((now - last_rx_ts)/1000ULL) + " ms → reiniciando driver..."); twai_stop(); twai_driver_uninstall(); if (!reinit_twai_unsafe_()) consecutive_rec_fails++; else consecutive_rec_fails = 0; last_rx_ts = esp_timer_get_time(); // evita loop de reinicialização } // ---- Stall de TX (chaves pendentes) ---- // Se houver muitas chaves esperando por muito tempo, force drenagem UBaseType_t pendentes = keysQ ? uxQueueMessagesWaiting(keysQ) : 0; if (pendentes > 0 && last_tx_ts && ((now - last_tx_ts) / 1000ULL) > TX_STALL_MS) { PrintTela("[CAN HLTH] TX aparentemente parado (" + String(pendentes) + " chaves pendentes)."); // Estratégia: deixa o CanTaskTx re-enfileirar; se persistir, reinicia driver static uint8_t tx_stall_count = 0; tx_stall_count++; if (tx_stall_count >= 3) { PrintTela("[CAN HLTH] TX stall persistente → reiniciando driver..."); twai_stop(); twai_driver_uninstall(); if (!reinit_twai_unsafe_()) consecutive_rec_fails++; else { consecutive_rec_fails = 0; tx_stall_count = 0; } last_tx_ts = esp_timer_get_time(); } } // ---- Escalonamento final: reboot do chip se não recuperar ---- if (consecutive_rec_fails >= MAX_REC_FAILS) { PrintTela("[CAN HLTH] Falhas consecutivas ao reiniciar TWAI. Reiniciando ESP32..."); vTaskDelay(pdMS_TO_TICKS(100)); esp_restart(); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void setReceiveCallback(OnReceiveCallback cb) { _callback = cb; } bool adicionarMensagemFila(const std::vector& payload) { // 1) Sanidade if (payload.empty() || payload.size() > 8) { PrintTela("[CAN] Payload inválido (0 ou >8 bytes)."); return false; } if (payload.size() < 2) { // precisa de pos (byte0) e addr (byte1) para formar a chave PrintTela("[CAN] Payload precisa ter pelo menos pos e addr (2 bytes)."); return false; } // 2) Monta frame twai_message_t msg{}; // ⚠️ ID: use o ID que o PC espera para respostas desse nó (ajuste se necessário) msg.identifier = _nodeId; // ou outro mapeamento do seu protocolo msg.extd = 0; // standard frame msg.rtr = 0; // data frame msg.ss = 0; msg.data_length_code = payload.size(); for (uint8_t i = 0; i < payload.size(); ++i) { msg.data[i] = payload[i]; } // 3) Publica via coalescência (substitui a antiga fila TX) PublicarTx(msg); return true; } F_Code FuncaoPorPosicao(CanMessagePosicaoDados posicao) { F_Code funcao = F_Code::Nda; int pos = static_cast(posicao); if (pos >= 0 && pos <= 50) { funcao = F_Code::ReqTx; } else if (pos > 50 && pos <= 100) { funcao = F_Code::CfgTx; } else if (pos > 100 && pos <= 150) { funcao = F_Code::CmdTx; } //PrintTela("Funcao parseada para posicao: " + String(pos) + ", funcao: " + String(funcao)); return funcao; } std::vector MontarFrameReqStatusMod(T_Code D_Code, bool Conectado, int Versao) { std::vector data; data.push_back(static_cast(CanMessagePosicaoDados::Status)); data.push_back(ID_Num_sMOD); data.push_back(static_cast(D_Code)); data.push_back(Conectado ? 1 : 0); data.push_back(static_cast(Versao)); return data; } std::vector MontarFrameReqDadosFim(int latencia) { std::vector data; data.push_back(static_cast(CanMessagePosicaoDados::DadosAll)); data.push_back(ID_Num_sTOD); data.push_back(latencia >> 8); data.push_back(latencia & 0xFF); return data; } private: uint32_t _baudRate = 250000; uint8_t _nodeId = 0; OnReceiveCallback _callback = nullptr; bool DebugMode = false; }; #endif // CANSERVICE_H