Skip to content

Instantly share code, notes, and snippets.

@tgxn
Created June 8, 2025 06:38
Show Gist options
  • Save tgxn/1022cf9910f8cf5eb59702f334448129 to your computer and use it in GitHub Desktop.
Save tgxn/1022cf9910f8cf5eb59702f334448129 to your computer and use it in GitHub Desktop.
Walter Features Test
#include <esp_mac.h>
#include <WalterModem.h>
#include <HardwareSerial.h>
#define MODEM_HTTP_PROFILE 1
#define TLS_PROFILE 1
/**
* @brief The size in bytes of a minimal sensor + GNSS + cell info packet.
*/
#define PACKET_SIZE 29
/**
* @brief All fixes with a confidence below this number are considered ok.
*/
#define MAX_GNSS_CONFIDENCE 50.0
/**
* @brief The serial interface to talk to the modem.
*/
#define ModemSerial Serial2
/**
* @brief The radio access technology to use - LTEM or NBIOT.
*/
#define RADIO_TECHNOLOGY WALTER_MODEM_RAT_LTEM
// modem stuff
/**
* @brief The modem instance.
*/
WalterModem modem;
/**
* @brief Flag used to signal when a fix is received.
*/
volatile bool fixRcvd = false;
/**
* @brief The last received GNSS fix.
*/
WalterModemGNSSFix posFix = {};
/**
* @brief The buffer to transmit to the UDP server. The first 6 bytes will be
* the MAC address of the Walter this code is running on.
*/
uint8_t dataBuf[PACKET_SIZE] = { 0 };
/**
* @brief Connect to the LTE network.
*
* This function will connect the modem to the LTE network. This function will
* block until the modem is attached.
*
* @return True on success, false on error.
*/
bool lteConnect()
{
/* Set the operational state to full */
if (!modem.setOpState(WALTER_MODEM_OPSTATE_FULL))
{
Serial.print("Could not set operational state to FULL\r\n");
return false;
}
/* Set the network operator selection to automatic */
if (!modem.setNetworkSelectionMode(WALTER_MODEM_NETWORK_SEL_MODE_AUTOMATIC))
{
Serial.print("Could not set the network selection mode to automatic\r\n");
return false;
}
/* Wait for the network to become available */
WalterModemNetworkRegState regState = modem.getNetworkRegState();
while (!(regState == WALTER_MODEM_NETWORK_REG_REGISTERED_HOME ||
regState == WALTER_MODEM_NETWORK_REG_REGISTERED_ROAMING))
{
delay(100);
regState = modem.getNetworkRegState();
}
/* Stabilization time */
Serial.print("Connected to the network\r\n");
return true;
}
/**
* @brief Disconnect from the LTE network.
*
* This function will disconnect the modem from the LTE network and block until
* the network is actually disconnected. After the network is disconnected the
* GNSS subsystem can be used.
*
* @return True on success, false on error.
*/
bool lteDisconnect()
{
/* Set the operational state to minimum */
if (!modem.setOpState(WALTER_MODEM_OPSTATE_MINIMUM))
{
Serial.print("Could not set operational state to MINIMUM\r\n");
return false;
}
/* Wait for the network to become available */
WalterModemNetworkRegState regState = modem.getNetworkRegState();
while (regState != WALTER_MODEM_NETWORK_REG_NOT_SEARCHING)
{
delay(100);
regState = modem.getNetworkRegState();
}
Serial.print("Disconnected from the network\r\n");
return true;
}
/**
* @brief Check the assistance data in the modem response.
*
* This function checks the availability of assistance data in the modem's
* response. This function also sets a flag if any of the assistance databases
* should be updated.
*
* @param rsp The modem response to check.
* @param updateAlmanac Pointer to the flag to set when the almanac should be
* updated.
* @param updateEphemeris Pointer to the flag to set when ephemeris should be
* updated.
*
* @return None.
*/
void checkAssistanceData(
WalterModemRsp *rsp,
bool *updateAlmanac = NULL,
bool *updateEphemeris = NULL
) {
if (updateAlmanac != NULL) {
*updateAlmanac = false;
}
if (updateEphemeris != NULL) {
*updateEphemeris = false;
}
Serial.print("Almanac data is ");
if (rsp->data.gnssAssistance.almanac.available) {
Serial.printf("available and should be updated within %ds\r\n",
rsp->data.gnssAssistance.almanac.timeToUpdate);
if (updateAlmanac != NULL) {
*updateAlmanac = rsp->data.gnssAssistance.almanac.timeToUpdate <= 0;
}
} else {
Serial.print("not available.\r\n");
if (updateAlmanac != NULL) {
*updateAlmanac = true;
}
}
Serial.print("Real-time ephemeris data is ");
if (rsp->data.gnssAssistance.realtimeEphemeris.available) {
Serial.printf("available and should be updated within %ds\r\n", rsp->data.gnssAssistance.realtimeEphemeris.timeToUpdate);
if (updateEphemeris != NULL) {
*updateEphemeris = rsp->data.gnssAssistance.realtimeEphemeris.timeToUpdate <= 0;
}
} else {
Serial.print("not available.\r\n");
if (updateEphemeris != NULL) {
*updateEphemeris = true;
}
}
}
/**
* @brief This function will update GNSS assistance data when needed.
*
* This funtion will check if the current real-time ephemeris data is good
* enough to get a fast GNSS fix. If not the function will attach to the LTE
* network to download newer assistance data.
*
* @return True on success, false on error.
*/
bool updateGNSSAssistance() {
bool lteConnected = false;
WalterModemRsp rsp = {};
lteDisconnect();
/* Even with valid assistance data the system clock could be invalid */
if (!modem.getClock(&rsp))
{
Serial.print("Could not check the modem time\r\n");
return false;
}
if (rsp.data.clock.epochTime <= 0)
{
/* The system clock is invalid, connect to LTE network to sync time */
if (!lteConnect())
{
Serial.print("Could not connect to LTE network\r\n");
return false;
}
lteConnected = true;
/*
* Wait for the modem to synchronize time with the LTE network, try 5 times
* with a delay of 500ms.
*/
for (int i = 0; i < 5; ++i) {
if (!modem.getClock(&rsp)) {
Serial.print("Could not check the modem time\r\n");
return false;
}
if (rsp.data.clock.epochTime > 0) {
Serial.printf("Synchronized clock with network: %" PRIi64 "\r\n", rsp.data.clock.epochTime);
break;
} else if (i == 4) {
Serial.print("Could not sync time with network\r\n");
return false;
}
delay(500);
}
}
/* Check the availability of assistance data */
if (!modem.gnssGetAssistanceStatus(&rsp) ||
rsp.type != WALTER_MODEM_RSP_DATA_TYPE_GNSS_ASSISTANCE_DATA)
{
Serial.print("Could not request GNSS assistance status\r\n");
return false;
}
bool updateAlmanac = false;
bool updateEphemeris = false;
checkAssistanceData(&rsp, &updateAlmanac, &updateEphemeris);
if (!(updateAlmanac || updateEphemeris))
{
if (lteConnected)
{
if (!lteDisconnect())
{
Serial.print("Could not disconnect from the LTE network\r\n");
return false;
}
}
return true;
}
if (!lteConnected)
{
if (!lteConnect())
{
Serial.print("Could not connect to LTE network\r\n");
return false;
}
}
if (updateAlmanac)
{
if (!modem.gnssUpdateAssistance(WALTER_MODEM_GNSS_ASSISTANCE_TYPE_ALMANAC))
{
Serial.print("Could not update almanac data\r\n");
return false;
}
}
if (updateEphemeris)
{
if (!modem.gnssUpdateAssistance(
WALTER_MODEM_GNSS_ASSISTANCE_TYPE_REALTIME_EPHEMERIS))
{
Serial.print("Could not update real-time ephemeris data\r\n");
return false;
}
}
if (!modem.gnssGetAssistanceStatus(&rsp) ||
rsp.type != WALTER_MODEM_RSP_DATA_TYPE_GNSS_ASSISTANCE_DATA)
{
Serial.print("Could not request GNSS assistance status\r\n");
return false;
}
checkAssistanceData(&rsp);
if (!lteDisconnect())
{
Serial.print("Could not disconnect from the LTE network\r\n");
return false;
}
return true;
}
/**
* @brief This function is called when a fix attempt finished.
*
* This function is called by Walter's modem library as soon as a fix attempt
* has finished. This function should be handled as an interrupt and should be
* as short as possible as it is called within the modem data thread.
*
* @param fix The fix data.
* @param args Optional arguments, a NULL pointer in this case.
*
* @return None.
*/
void fixHandler(const WalterModemGNSSFix *fix, void *args) {
memcpy(&posFix, fix, sizeof(WalterModemGNSSFix));
fixRcvd = true;
}
void printMac() {
/* Get the MAC address for board validation */
esp_read_mac(dataBuf, ESP_MAC_WIFI_STA);
Serial.printf("Walter's MAC is: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
dataBuf[0],
dataBuf[1],
dataBuf[2],
dataBuf[3],
dataBuf[4],
dataBuf[5]);
}
const char* ratToString(int rat) {
switch (rat) {
case 0: return "LTEM";
case 1: return "NBIOT";
case 2: return "AUTO";
case 99: return "UNKNOWN";
default: return "default";
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("Walter Modem Turn On :: v0.0.1");
printMac();
/* Modem initialization */
if(modem.begin(&ModemSerial)) {
Serial.println("Modem initialization OK");
} else {
Serial.println("Error: Modem initialization ERROR");
delay(1000);
ESP.restart();
return;
}
WalterModemRsp rsp = {};
if(modem.getIdentity(&rsp)) {
Serial.print("Modem identity:\r\n");
Serial.printf(" IMEI: %s\r\n", rsp.data.identity.imei);
Serial.printf(" IMEISV: %s\r\n", rsp.data.identity.imeisv);
Serial.printf(" SVN: %s\r\n", rsp.data.identity.svn);
}
if(modem.getRAT(&rsp)) {
Serial.printf("Modem radio technology: %s\r\n", ratToString(rsp.data.rat));
if(rsp.data.rat != RADIO_TECHNOLOGY) {
modem.setRAT(RADIO_TECHNOLOGY);
Serial.println("Switched modem radio technology");
}
} else {
Serial.println("Could not retrieve radio access technology");
}
if(!modem.definePDPContext()) {
Serial.println("Could not create PDP context");
delay(1000);
ESP.restart();
return;
}
if(!modem.setOpState(WALTER_MODEM_OPSTATE_NO_RF)) {
Serial.println("Could not set operational state to NO RF");
delay(1000);
ESP.restart();
return;
}
delay(500);
if(modem.getSIMCardID(&rsp)) {
Serial.print("SIM card identity:\r\n");
Serial.printf(" ICCID: %s\r\n", rsp.data.simCardID.iccid);
Serial.printf(" eUICCID: %s\r\n", rsp.data.simCardID.euiccid);
}
if(modem.getSIMCardIMSI(&rsp)) {
Serial.printf("Active IMSI: %s\r\n", rsp.data.imsi);
}
if(!modem.gnssConfig()) {
Serial.print("Could not configure the GNSS subsystem\r\n");
delay(1000);
ESP.restart();
return;
}
modem.gnssSetEventHandler(fixHandler);
}
void loop() {
Serial.println("");
Serial.println("-----");
Serial.println("1. Connect + Test Modem");
Serial.println("-----");
static WalterModemRsp rsp = {};
if (!lteConnect()) {
Serial.println("Could not connect to the LTE network");
delay(1000);
ESP.restart();
return;
}
/* Read the temperature of Walter */
float temp = temperatureRead();
Serial.printf("Temp: %.02f°c\r\n", temp);
if (!modem.getCellInformation(WALTER_MODEM_SQNMONI_REPORTS_SERVING_CELL, &rsp)) {
Serial.println("Could not request cell information");
} else {
Serial.printf("Connected on band %u using operator %s (%u%02u)",
rsp.data.cellInformation.band, rsp.data.cellInformation.netName,
rsp.data.cellInformation.cc, rsp.data.cellInformation.nc);
Serial.printf(" and cell ID %u.\r\n",
rsp.data.cellInformation.cid);
Serial.printf("Signal strength: RSRP: %.2f, RSRQ: %.2f.\r\n",
rsp.data.cellInformation.rsrp, rsp.data.cellInformation.rsrq);
}
// Get signal quality
if (modem.getSignalQuality(&rsp)) {
Serial.print("rsrq: ");
Serial.print(rsp.data.signalQuality.rsrq);
Serial.print(", rsrp: ");
Serial.println(rsp.data.signalQuality.rsrp);
} else {
Serial.println("Error: Could not get signal quality");
}
if (modem.getRSSI(&rsp)) {
Serial.print("rssi: ");
Serial.print(rsp.data.rssi);
Serial.println(" dB");
} else {
Serial.println("Error: Could not get rssi");
}
// Get clock
if (modem.getClock(&rsp)) {
Serial.print("epochTime: ");
Serial.print(rsp.data.clock.epochTime);
Serial.print(", timeZoneOffset: ");
Serial.println(rsp.data.clock.timeZoneOffset);
} else {
Serial.println("Error: Could not get clock");
}
if (!lteDisconnect())
{
Serial.print("Could not disconnect from the LTE network\r\n");
return;
}
Serial.println("-----");
Serial.println("2. Get GPS Fix");
Serial.println("-----");
if(!modem.gnssConfig()) {
Serial.print("Could not configure the GNSS subsystem\r\n");
delay(1000);
ESP.restart();
return;
}
/* Check clock and assistance data, update if required */
if (!updateGNSSAssistance()) {
Serial.println("Could not update GNSS assistance data");
delay(1000);
ESP.restart();
return;
}
/* Try up to 5 times to get a good fix */
for (int i = 0; i < 10; ++i) {
fixRcvd = false;
if(!modem.gnssPerformAction()) {
Serial.print("Could not request GNSS fix\r\n");
delay(1000);
ESP.restart();
return;
}
Serial.print("Started GNSS fix\r\n");
int j = 0;
while (!fixRcvd) {
Serial.print(".");
if (j >= 600) {
Serial.println("");
Serial.println("Timed out while waiting for GNSS fix");
delay(1000);
ESP.restart();
break;
}
j++;
delay(500);
}
Serial.println("");
if (posFix.estimatedConfidence <= MAX_GNSS_CONFIDENCE) {
break;
}
}
uint8_t abovedBTreshold = 0;
for (int i = 0; i < posFix.satCount; ++i) {
if (posFix.sats[i].signalStrength >= 30) {
abovedBTreshold += 1;
}
}
Serial.printf("GNSS fix attempt finished:\r\n"
" Confidence: %.02f\r\n"
" Latitude: %.06f\r\n"
" Longitude: %.06f\r\n"
" Satcount: %d\r\n"
" Good sats: %d\r\n",
posFix.estimatedConfidence,
posFix.latitude,
posFix.longitude,
posFix.satCount,
abovedBTreshold);
Serial.print("----- waiting 60s ");
for (int i = 1; i <= 60; ++i) {
Serial.print(".");
delay(1000);
}
Serial.println();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment