Skip to content

Instantly share code, notes, and snippets.

@gamename
Last active June 18, 2025 09:26
Show Gist options
  • Save gamename/eb31742271a3f66484a55e6782fb8bab to your computer and use it in GitHub Desktop.
Save gamename/eb31742271a3f66484a55e6782fb8bab to your computer and use it in GitHub Desktop.
Cell Modem
#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; }
/* 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