Last active
June 18, 2025 09:26
-
-
Save gamename/eb31742271a3f66484a55e6782fb8bab to your computer and use it in GitHub Desktop.
Cell Modem
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "cellular_modem.h" | |
#include "driver/gpio.h" | |
#include "esp_event.h" | |
#include "esp_log.h" | |
#include "esp_modem_api.h" | |
#include "esp_modem_c_api_types.h" | |
#include "esp_modem_dce_config.h" | |
#include "esp_netif.h" | |
#include "freertos/FreeRTOS.h" | |
#include "freertos/task.h" | |
#include <string.h> | |
#define TAG "CELLULAR_MODEM" | |
// Modem UART configuration (adjust as per your hardware) | |
#define MODEM_UART_PORT UART_NUM_1 | |
#define MODEM_UART_BAUD 115200 | |
#define MODEM_UART_TX_PIN 48 | |
#define MODEM_UART_RX_PIN 14 | |
#define MODEM_UART_RTS_PIN 21 | |
#define MODEM_UART_CTS_PIN 47 | |
// Modem power and reset control (adjust GPIO as per your hardware) | |
#define MODEM_PWR_PIN 25 | |
#define MODEM_PWR_ON_LEVEL 1 | |
#define MODEM_RESET_PIN 45 | |
#define MODEM_RESET_ACTIVE_LEVEL 0 // Active-low reset (0 = reset, 1 = normal) | |
// APN configuration (replace with your provider's APN) | |
#define MODEM_APN "super" | |
static esp_modem_dce_t *dce = NULL; | |
static esp_netif_t *ppp_netif = NULL; | |
static bool is_connected = false; | |
static void modem_power_on(void) { | |
gpio_set_direction(MODEM_RESET_PIN, GPIO_MODE_OUTPUT); | |
gpio_set_pull_mode(MODEM_RESET_PIN, GPIO_FLOATING); | |
gpio_deep_sleep_hold_en(); | |
gpio_hold_dis(MODEM_RESET_PIN); | |
gpio_set_level(MODEM_RESET_PIN, 0); | |
vTaskDelay(pdMS_TO_TICKS(10)); | |
gpio_set_level(MODEM_RESET_PIN, 1); | |
gpio_hold_en(MODEM_RESET_PIN); | |
vTaskDelay(pdMS_TO_TICKS(5000)); | |
} | |
/** | |
* Run AT commands to sync the modem. | |
* @param dce Modem DCE handle. | |
* @param count Number of sync attempts. | |
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure. | |
*/ | |
static esp_err_t run_at(esp_modem_dce_t *dce, uint8_t count) { | |
esp_err_t ret; | |
ESP_LOGI(TAG, "Syncing Sequans GM02SP modem..."); | |
for (int i = 0; i < count; i++) { | |
ret = esp_modem_sync(dce); | |
if (ret != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to sync modem on attempt %d: %s", i + 1, esp_err_to_name(ret)); | |
} else { | |
ESP_LOGI(TAG, "Modem synced successfully on attempt %d", i + 1); | |
break; // Return on first successful sync | |
} | |
vTaskDelay(pdMS_TO_TICKS(3000)); | |
} | |
return ret; | |
} | |
/** | |
* Reset the Sequans GM02SP modem by toggling the reset pin. | |
*/ | |
static void modem_reset(void) { | |
// Configure reset pin | |
gpio_config_t reset_config = {.pin_bit_mask = (1ULL << MODEM_RESET_PIN), | |
.mode = GPIO_MODE_OUTPUT, | |
.pull_up_en = GPIO_PULLUP_DISABLE, | |
.pull_down_en = GPIO_PULLDOWN_DISABLE, | |
.intr_type = GPIO_INTR_DISABLE}; | |
gpio_config(&reset_config); | |
// Disable deep sleep hold to allow pin control | |
gpio_hold_dis((gpio_num_t)MODEM_RESET_PIN); | |
// Perform reset: set low for 10 ms, then high | |
ESP_LOGI(TAG, "Resetting Sequans GM02SP modem..."); | |
gpio_set_level((gpio_num_t)MODEM_RESET_PIN, MODEM_RESET_ACTIVE_LEVEL); | |
vTaskDelay(pdMS_TO_TICKS(10)); // 10 ms low | |
gpio_set_level((gpio_num_t)MODEM_RESET_PIN, !MODEM_RESET_ACTIVE_LEVEL); | |
// Re-enable deep sleep hold to maintain pin state | |
gpio_hold_en((gpio_num_t)MODEM_RESET_PIN); | |
// Wait for modem to stabilize | |
ESP_LOGI(TAG, "Sequans GM02SP modem reset complete, waiting for stabilization..."); | |
vTaskDelay(pdMS_TO_TICKS(5000)); // 5 seconds | |
} | |
/** | |
* Power off the Sequans GM02SP modem. | |
*/ | |
static void modem_power_off(void) { gpio_set_level(MODEM_PWR_PIN, !MODEM_PWR_ON_LEVEL); } | |
/** | |
* IP event handler for PPP connection status. | |
*/ | |
static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { | |
if (event_base == IP_EVENT) { | |
if (event_id == IP_EVENT_PPP_GOT_IP) { | |
ESP_LOGI(TAG, "PPP connected"); | |
is_connected = true; | |
} else if (event_id == IP_EVENT_PPP_LOST_IP) { | |
ESP_LOGW(TAG, "PPP disconnected"); | |
is_connected = false; | |
} | |
} | |
} | |
esp_err_t cellular_modem_init(void) { | |
esp_err_t ret; | |
// Set up PPP netif | |
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP(); | |
ppp_netif = esp_netif_new(&netif_ppp_config); | |
if (!ppp_netif) { | |
ESP_LOGE(TAG, "Failed to create PPP netif"); | |
modem_power_off(); | |
return ESP_FAIL; | |
} | |
// Set up DTE config with hardware flow control | |
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); | |
dte_config.uart_config.port_num = MODEM_UART_PORT; | |
dte_config.uart_config.baud_rate = MODEM_UART_BAUD; | |
dte_config.uart_config.data_bits = UART_DATA_8_BITS; | |
dte_config.uart_config.parity = UART_PARITY_DISABLE; | |
dte_config.uart_config.stop_bits = UART_STOP_BITS_1; | |
dte_config.uart_config.flow_control = ESP_MODEM_FLOW_CONTROL_HW; // Enable CTS/RTS | |
dte_config.uart_config.tx_io_num = MODEM_UART_TX_PIN; | |
dte_config.uart_config.rx_io_num = MODEM_UART_RX_PIN; | |
dte_config.uart_config.rts_io_num = MODEM_UART_RTS_PIN; // Set RTS pin | |
dte_config.uart_config.cts_io_num = MODEM_UART_CTS_PIN; // Set CTS pin | |
dte_config.uart_config.rx_buffer_size = 512; | |
dte_config.uart_config.tx_buffer_size = 512; | |
// Set up DCE config | |
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(MODEM_APN); | |
// Define PDP context for connection | |
esp_modem_PdpContext_t pdp_context = {.apn = MODEM_APN, .context_id = 1, .protocol_type = ""}; | |
// Create DCE for Sequans GM02SP | |
dce = esp_modem_new_dev(ESP_MODEM_DCE_SQNGM02S, &dte_config, &dce_config, ppp_netif); | |
if (!dce) { | |
ESP_LOGE(TAG, "Failed to create DCE for Sequans GM02SP"); | |
esp_netif_destroy(ppp_netif); | |
modem_power_off(); | |
return ESP_FAIL; | |
} | |
// Power on the modem | |
modem_power_on(); | |
// Run AT commands to sync modem | |
ret = run_at(dce, 5); | |
if (ret != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to run AT commands"); | |
modem_power_off(); | |
return ret; | |
} | |
// Connect modem (placeholder, verify function) | |
// TODO: Replace esp_modem_sqn_gm02s_connect with correct function or implement | |
ret = esp_modem_sqn_gm02s_connect(dce, &pdp_context); | |
if (ret != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to connect Sequans GM02SP modem"); | |
esp_modem_destroy(dce); | |
esp_netif_destroy(ppp_netif); | |
modem_power_off(); | |
return ret; | |
} | |
// Register event handler | |
ret = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler, NULL); | |
if (ret != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to register event handler"); | |
esp_modem_destroy(dce); | |
esp_netif_destroy(ppp_netif); | |
modem_power_off(); | |
return ret; | |
} | |
// Set mode to data mode to start PPP | |
ret = esp_modem_set_mode(dce, ESP_MODEM_MODE_DATA); | |
if (ret != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to set data mode"); | |
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler); | |
esp_modem_destroy(dce); | |
esp_netif_destroy(ppp_netif); | |
modem_power_off(); | |
return ret; | |
} | |
ESP_LOGI(TAG, "Sequans GM02SP modem initialized"); | |
return ESP_OK; | |
} | |
esp_err_t cellular_modem_deinit(void) { | |
if (dce) { | |
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler); | |
esp_modem_destroy(dce); | |
dce = NULL; | |
} | |
if (ppp_netif) { | |
esp_netif_destroy(ppp_netif); | |
ppp_netif = NULL; | |
} | |
is_connected = false; | |
modem_power_off(); | |
ESP_LOGI(TAG, "Sequans GM02SP modem deinitialized"); | |
return ESP_OK; | |
} | |
bool cellular_modem_is_connected(void) { return is_connected; } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* snip */ | |
void first_phase(void) { | |
esp_err_t xEspErrRet; | |
// Initialize network interface and event loop for cellular devices | |
if (is_cellular_device) { | |
ESP_LOGI(TAG, "Initializing network for cellular device in first phase"); | |
xEspErrRet = esp_netif_init(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize network stack: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
xEspErrRet = esp_event_loop_create_default(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to create event loop: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
// Initialize cellular modem | |
xEspErrRet = cellular_modem_init(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize cellular modem: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} else { | |
ESP_LOGI(TAG, "Cellular modem initialized successfully"); | |
} | |
// Wait for cellular connection (up to 60 seconds) | |
int retries = 0; | |
while (!cellular_modem_is_connected() && retries < 60) { | |
vTaskDelay(1000 / portTICK_PERIOD_MS); | |
retries++; | |
} | |
if (!cellular_modem_is_connected()) { | |
ESP_LOGE(TAG, "Failed to establish cellular connection after 60 seconds"); | |
cellular_modem_deinit(); | |
return; | |
} else { | |
ESP_LOGI(TAG, "Cellular connection established successfully"); | |
} | |
ESP_LOGI(TAG, "Cellular connection established in first phase"); | |
network_connected = true; | |
} else { | |
// Wi-Fi device setup for scanning | |
xEspErrRet = wifi_init_for_scan(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize WiFi for scanning: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
} | |
// Initialize BLE for provisioning | |
xEspErrRet = nimble_port_init(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize NimBLE stack: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
ESP_LOGI(TAG, "NimBLE stack initialized successfully"); | |
ble_svc_gap_init(); | |
xEspErrRet = gatt_svc_init(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize GATT service: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
ESP_LOGI(TAG, "GATT service initialized successfully"); | |
nimble_host_config_init(); | |
xTaskCreate(nimble_host_task, "NimBLE Host", 8 * 1024, NULL, 5, NULL); | |
} | |
/* snip */ | |
void app_main(void) { | |
// Initialize NVS | |
esp_err_t xEspErrRet = nvs_flash_init(); | |
if (xEspErrRet == ESP_ERR_NVS_NO_FREE_PAGES || xEspErrRet == ESP_ERR_NVS_NEW_VERSION_FOUND) { | |
ESP_LOGW(TAG, "NVS partition invalid, erasing..."); | |
xEspErrRet = nvs_flash_erase(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to erase NVS: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
xEspErrRet = nvs_flash_init(); | |
if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize NVS after erase: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
} else if (xEspErrRet != ESP_OK) { | |
ESP_LOGE(TAG, "Failed to initialize NVS: %s", esp_err_to_name(xEspErrRet)); | |
return; | |
} | |
ESP_LOGI(TAG, "NVS initialized successfully"); | |
// Read thing name from NVS | |
char *temp_thing_name = NULL; | |
if (read_from_nvs("thing_name", &temp_thing_name) == ESP_OK && temp_thing_name != NULL) { | |
if (temp_thing_name[0] != '\0') { | |
strncpy(thing_name, temp_thing_name, MAX_THING_NAME_SIZE - 1); | |
thing_name[MAX_THING_NAME_SIZE - 1] = '\0'; | |
ESP_LOGI(TAG, "Thing name loaded from NVS: %s", thing_name); | |
} else { | |
ESP_LOGW(TAG, "Empty thing name in NVS"); | |
} | |
free(temp_thing_name); | |
} else { | |
ESP_LOGI(TAG, "No thing name found in NVS, device may be unprovisioned"); | |
} | |
// Load firmware data | |
load_firmware_data(); | |
// Check device type | |
is_cellular_device = (get_firmware_device_type() == DEVICE_TYPE_CONTROLLER_CELL); | |
ESP_LOGI(TAG, "Device type: %s", is_cellular_device ? "Cellular Controller" : "Wi-Fi Controller"); | |
if (key_found_in_nvs("provisioned")) { | |
ESP_LOGI(TAG, "Device is provisioned. Starting second phase."); | |
second_phase(); | |
} else { | |
ESP_LOGI(TAG, "Device is not yet provisioned. Starting first phase."); | |
first_phase(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment