Good evening @beegee
I’m trying to make a custom AT command to change APN from ATC+APN command, everything seems to be fine but for some reason it’s not saved in flash memory.
Could you help me by verifying my code please
/**
* @file custom_at.cpp
* @author Bernd Giesecke ([email protected])
* @brief
* @version 0.1
* @date 2022-05-12
*
* @copyright Copyright (c) 2022
*
*/
#include "../inc/app.h"
//#define APN_MAX_LENGTH 32
#if defined(_VARIANT_RAK3172_) || defined(_VARIANT_RAK3172_SIP_)
#define AT_PRINTF(...) \
do \
{ \
Serial.printf(__VA_ARGS__); \
Serial.printf("\r\n"); \
} while (0); \
delay(100)
#else // RAK4630 || RAK11720
#define AT_PRINTF(...) \
do \
{ \
Serial.printf(__VA_ARGS__); \
Serial.printf("\r\n"); \
Serial6.printf(__VA_ARGS__); \
Serial6.printf("\r\n"); \
} while (0); \
delay(100)
#endif
/** Custom flash parameters */
custom_param_s g_custom_parameters;
/** APN parameter */
char apn[APN_MAX_LENGTH + 1] = "";
// Forward declarations
int interval_send_handler(SERIAL_PORT port, char *cmd, stParam *param);
int status_handler(SERIAL_PORT port, char *cmd, stParam *param);
int tank_depth_handler(SERIAL_PORT port, char *cmd, stParam *param);
int apn_handler(SERIAL_PORT port, char *cmd, stParam *param);
/**
* @brief Add send interval AT command
*
* @return true if success
* @return false if failed
*/
bool init_interval_at(void)
{
return api.system.atMode.add((char *)"SENDINT",
(char *)"Set/Get the interval sending time values in seconds 0 = off, max 2,147,483 seconds",
(char *)"SENDINT", interval_send_handler,
RAK_ATCMD_PERM_WRITE | RAK_ATCMD_PERM_READ);
}
/**
* @brief Handler for send interval AT commands
*
* @param port Serial port used
* @param cmd char array with the received AT command
* @param param char array with the received AT command parameters
* @return int result of command parsing
* AT_OK AT command & parameters valid
* AT_PARAM_ERROR command or parameters invalid
*/
int interval_send_handler(SERIAL_PORT port, char *cmd, stParam *param)
{
if (param->argc == 1 && !strcmp(param->argv[0], "?"))
{
AT_PRINTF("%s=%ld", cmd, g_custom_parameters.send_interval / 1000);
}
else if (param->argc == 1)
{
MYLOG("AT_CMD", "param->argv[0] >> %s", param->argv[0]);
for (int i = 0; i < strlen(param->argv[0]); i++)
{
if (!isdigit(*(param->argv[0] + i)))
{
MYLOG("AT_CMD", "%d is no digit", i);
return AT_PARAM_ERROR;
}
}
uint32_t new_send_freq = strtoul(param->argv[0], NULL, 10);
MYLOG("AT_CMD", "Requested interval %ld", new_send_freq);
g_custom_parameters.send_interval = new_send_freq * 1000;
MYLOG("AT_CMD", "New interval %ld", g_custom_parameters.send_interval);
// Stop the timer
api.system.timer.stop(RAK_TIMER_0);
if (g_custom_parameters.send_interval != 0)
{
// Restart the timer
api.system.timer.start(RAK_TIMER_0, g_custom_parameters.send_interval, NULL);
}
// Save custom settings
save_at_setting();
}
else
{
return AT_PARAM_ERROR;
}
return AT_OK;
}
/**
* @brief Add custom Status AT commands
*
* @return true AT commands were added
* @return false AT commands couldn't be added
*/
bool init_status_at(void)
{
return api.system.atMode.add((char *)"STATUS",
(char *)"Get device information",
(char *)"STATUS", status_handler,
RAK_ATCMD_PERM_READ);
}
/** Regions as text array */
char *regions_list[] = {"EU433", "CN470", "RU864", "IN865", "EU868", "US915", "AU915", "KR920", "AS923", "AS923-2", "AS923-3", "AS923-4"};
/** Network modes as text array*/
char *nwm_list[] = {"P2P", "LoRaWAN", "FSK"};
/**
* @brief Print device status over Serial
*
* @param port Serial port used
* @param cmd char array with the received AT command
* @param param char array with the received AT command parameters
* @return int result of command parsing
* AT_OK AT command & parameters valid
* AT_PARAM_ERROR command or parameters invalid
*/
int status_handler(SERIAL_PORT port, char *cmd, stParam *param)
{
String value_str = "";
int nw_mode = 0;
int region_set = 0;
uint8_t key_eui[16] = {0}; // efadff29c77b4829acf71e1a6e76f713
if ((param->argc == 1 && !strcmp(param->argv[0], "?")) || (param->argc == 0))
{
AT_PRINTF("Device Status:");
value_str = api.system.hwModel.get();
value_str.toUpperCase();
AT_PRINTF("Module: %s", value_str.c_str());
AT_PRINTF("Version: %s", api.system.firmwareVer.get().c_str());
AT_PRINTF("Send time: %d s", g_custom_parameters.send_interval / 1000);
/// \todo
nw_mode = api.lorawan.nwm.get();
AT_PRINTF("Network mode %s", nwm_list[nw_mode]);
if (nw_mode == 1)
{
AT_PRINTF("Network %s", api.lorawan.njs.get() ? "joined" : "not joined");
region_set = api.lorawan.band.get();
AT_PRINTF("Region: %d", region_set);
AT_PRINTF("Region: %s", regions_list[region_set]);
if (api.lorawan.njm.get())
{
AT_PRINTF("OTAA mode");
api.lorawan.deui.get(key_eui, 8);
AT_PRINTF("DevEUI = %02X%02X%02X%02X%02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3],
key_eui[4], key_eui[5], key_eui[6], key_eui[7]);
api.lorawan.appeui.get(key_eui, 8);
AT_PRINTF("AppEUI = %02X%02X%02X%02X%02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3],
key_eui[4], key_eui[5], key_eui[6], key_eui[7]);
api.lorawan.appkey.get(key_eui, 16);
AT_PRINTF("AppKey = %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3],
key_eui[4], key_eui[5], key_eui[6], key_eui[7],
key_eui[8], key_eui[9], key_eui[10], key_eui[11],
key_eui[12], key_eui[13], key_eui[14], key_eui[15]);
}
else
{
AT_PRINTF("ABP mode");
api.lorawan.appskey.get(key_eui, 16);
AT_PRINTF("AppsKey = %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3],
key_eui[4], key_eui[5], key_eui[6], key_eui[7],
key_eui[8], key_eui[9], key_eui[10], key_eui[11],
key_eui[12], key_eui[13], key_eui[14], key_eui[15]);
api.lorawan.nwkskey.get(key_eui, 16);
AT_PRINTF("NwsKey = %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3],
key_eui[4], key_eui[5], key_eui[6], key_eui[7],
key_eui[8], key_eui[9], key_eui[10], key_eui[11],
key_eui[12], key_eui[13], key_eui[14], key_eui[15]);
api.lorawan.daddr.get(key_eui, 4);
AT_PRINTF("DevAddr = %02X%02X%02X%02X",
key_eui[0], key_eui[1], key_eui[2], key_eui[3]);
}
}
else if (nw_mode == 0)
{
AT_PRINTF("Frequency = %d", api.lora.pfreq.get());
AT_PRINTF("SF = %d", api.lora.psf.get());
AT_PRINTF("BW = %d", api.lora.pbw.get());
AT_PRINTF("CR = %d", api.lora.pcr.get());
AT_PRINTF("Preamble length = %d", api.lora.ppl.get());
AT_PRINTF("TX power = %d", api.lora.ptp.get());
}
else
{
AT_PRINTF("Frequency = %d", api.lora.pfreq.get());
AT_PRINTF("Bitrate = %d", api.lora.pbr.get());
AT_PRINTF("Deviaton = %d", api.lora.pfdev.get());
}
}
else
{
return AT_PARAM_ERROR;
}
return AT_OK;
}
bool init_tank_depth_at(void)
{
return api.system.atMode.add((char *)"DEPTH",
(char *)"Set/Get the tank depth in mm (50mm to 3000mm)",
(char *)"DEPTH", tank_depth_handler,
RAK_ATCMD_PERM_WRITE | RAK_ATCMD_PERM_READ);
}
int tank_depth_handler(SERIAL_PORT port, char *cmd, stParam *param)
{
if (param->argc == 1 && !strcmp(param->argv[0], "?"))
{
AT_PRINTF("%s=%d", cmd, g_custom_parameters.tank_depth_mm);
}
else if (param->argc == 1)
{
MYLOG("AT_CMD", "param->argv[0] >> %s", param->argv[0]);
for (int i = 0; i < strlen(param->argv[0]); i++)
{
if (!isdigit(*(param->argv[0] + i)))
{
MYLOG("AT_CMD", "%d is no digit", i);
return AT_PARAM_ERROR;
}
}
uint32_t new_depth = strtoul(param->argv[0], NULL, 10);
uint8_t old_depth = g_custom_parameters.tank_depth_mm;
MYLOG("AT_CMD", "Requested depth %ld", new_depth);
if ((new_depth < 50) || (new_depth > 3000))
{
return AT_PARAM_ERROR;
}
if (new_depth != old_depth)
{
g_custom_parameters.tank_depth_mm = new_depth;
MYLOG("AT_CMD", "New tank depth %ld", g_custom_parameters.tank_depth_mm);
// Save custom settings
save_at_setting();
}
}
else
{
return AT_PARAM_ERROR;
}
return AT_OK;
}
bool init_apn_at(void)
{
return api.system.atMode.add((char *)"APN",
(char *)"Set/Get the APN (Max 33 chars)",
(char *)"APN", apn_handler,
RAK_ATCMD_PERM_WRITE | RAK_ATCMD_PERM_READ);
}
int apn_handler(SERIAL_PORT port, char *cmd, stParam *param)
{
if (param->argc == 1 && !strcmp(param->argv[0], "?"))
{
// Consultar el APN actual
AT_PRINTF("%s=%s", cmd, apn);
}
else if (param->argc == 1)
{
// Validar longitud del APN
if (strlen(param->argv[0]) > APN_MAX_LENGTH)
{
return AT_PARAM_ERROR;
}
// Verificar si el APN contiene solo caracteres válidos (letras, números, puntos y guiones)
for (int i = 0; i < strlen(param->argv[0]); i++)
{
char c = param->argv[0][i];
if (!isalnum(c) && c != '.' && c != '-')
{
return AT_PARAM_ERROR;
}
}
// Guardar el nuevo APN
strncpy(apn, param->argv[0], sizeof(apn) - 1);
apn[sizeof(apn) - 1] = '\0'; // Asegurar terminación nula
MYLOG("AT_CMD", "New APN set: %s", apn);
// Guardar configuración persistente si es necesario
save_at_setting();
}
else
{
return AT_PARAM_ERROR;
}
return AT_OK;
}
/**
* @brief Get setting from flash
*
* @return false read from flash failed or invalid settings type
*/
bool get_at_setting(void)
{
custom_param_s temp_params;
uint8_t *flash_value = (uint8_t *)&temp_params.valid_flag;
if (!api.system.flash.get(0, flash_value, sizeof(custom_param_s)))
{
MYLOG("AT_CMD", "Failed to read send interval from Flash");
return false;
}
MYLOG("AT_CMD", "Got flag: %02X", temp_params.valid_flag);
MYLOG("AT_CMD", "Got send interval: %08X", temp_params.send_interval);
MYLOG("AT_CMD", "Got send apn: %s", temp_params.apn);
if (flash_value[0] != 0xAA)
{
MYLOG("AT_CMD", "No valid send interval found, set to default, read 0X%08X", temp_params.send_interval);
g_custom_parameters.send_interval = 0;
g_custom_parameters.tank_depth_mm = 1100;
strncpy(g_custom_parameters.apn, "internet.tigo.pa", sizeof(g_custom_parameters.apn) - 1);
g_custom_parameters.apn[sizeof(g_custom_parameters.apn) - 1] = '\0'; // Ensure null termination
save_at_setting();
return false;
}
MYLOG("AT_CMD", "Read send interval %ds", temp_params.send_interval/1000);
g_custom_parameters.send_interval = temp_params.send_interval;
MYLOG("AT_CMD", "Read tank depth %dmm", temp_params.tank_depth_mm);
g_custom_parameters.tank_depth_mm = temp_params.tank_depth_mm;
MYLOG("AT_CMD", "Read APN: %s", g_custom_parameters.apn);
strncpy(g_custom_parameters.apn, temp_params.apn, sizeof(g_custom_parameters.apn) - 1);
g_custom_parameters.apn[sizeof(g_custom_parameters.apn) - 1] = '\0'; // Ensure null termination
return true;
}
/**
* @brief Save setting to flash
*
* @return true write to flash was successful
* @return false write to flash failed or invalid settings type
*/
bool save_at_setting(void)
{
uint8_t *flash_value = (uint8_t *)&g_custom_parameters.valid_flag;
bool wr_result = false;
MYLOG("AT_CMD", "Writing flag: %02X", g_custom_parameters.valid_flag);
MYLOG("AT_CMD", "Writing send interval 0X%08X ", g_custom_parameters.send_interval);
MYLOG("AT_CMD", "Writing tank depth %d ", g_custom_parameters.tank_depth_mm);
MYLOG("AT_CMD", "Writing APN: %s", g_custom_parameters.apn);
wr_result = api.system.flash.set(0, flash_value, sizeof(custom_param_s));
if (!wr_result)
{
// Retry
wr_result = api.system.flash.set(0, flash_value, sizeof(custom_param_s));
}
wr_result = true;
return wr_result;
}
Debug:
23:23:45:941 ---- Sent utf8 encoded message: "atc+apn=internet.tigo.pa\r\n" ----
23:23:45:944 -> [AT_CMD] New APN set: internet.tigo.pa
23:23:46:043 -> [AT_CMD] Writing flag: AA
23:23:46:143 -> [AT_CMD] Writing send interval 0X00007530
23:23:46:244 -> [AT_CMD] Writing tank depth -1
23:23:46:343 -> [AT_CMD] Writing APN:
23:23:46:445 -> OK
23:23:48:843 ---- Sent utf8 encoded message: "atc+apn=?\r\n" ----
23:23:48:845 -> ATC+APN=internet.tigo.pa
23:23:48:945 -> OK
23:24:01:631 -> AT+QSCLK=0
23:24:01:644 -> OK
23:24:06:745 -> [T_H] Reading SHTC3
23:24:06:860 -> [T_H] T: 30.03 H: 65.41
23:24:06:960 -> DevEUI: AC1F09FFFE10225C
23:24:07:965 ->
23:24:08:012 ->
23:24:08:486 ->
23:24:08:488 -> POST enviado con Payload: 0268820367012C01740196
23:24:08:488 -> Cerrando conexión TCP...
23:24:09:011 ->
23:24:09:011 -> AT+QICLOSE
23:24:09:013 -> CLOSE OK
23:24:09:014 -> Sleep mode configurado correctamente.
void setup is shown where I try to use the variable g_custom_parameters.apn
void setup() {
// Inicialización de los pines RGB
pinMode(RGBPIN_1, OUTPUT);
digitalWrite(RGBPIN_1, HIGH);
pinMode(RGBPIN_2, OUTPUT);
digitalWrite(RGBPIN_2, LOW);
Serial.begin(115200);
// Delay for 5 seconds to give the chance for AT+BOOT
delay(5000);
api.system.firmwareVersion.set("RUI3-Water-Level-V1.0.0");
Serial.println("RAKwireless RUI3 Water level sensor");
Serial.println("------------------------------------------------------");
Serial.println("Setup the device with WisToolBox or AT commands before using it");
Serial.printf("Version %s\n", api.system.firmwareVersion.get().c_str());
Serial.println("------------------------------------------------------");
// Initialize module
Wire.begin();
// Register the custom AT command to get device status
if (!init_status_at())
{
MYLOG("SETUP", "Add custom AT command STATUS fail");
}
// Register the custom AT command to set the send interval
if (!init_interval_at())
{
MYLOG("SETUP", "Add custom AT command Send Interval fail");
}
// Register the custom AT command to set the tank depth
if (!init_tank_depth_at())
{
MYLOG("SETUP", "Add custom AT command Tank Depth fail");
}
// Register the custom AT command to set the tank depth
if (!init_apn_at())
{
MYLOG("SETUP", "Add custom AT command Tank Depth fail");
}
// Get saved sending interval from flash
get_at_setting();
gprs.Power_On();
Serial.println("\n\rPower On!");
//delay(60000); // tiempo para configurar el APN, Host y puerto
// Inicialización del GPRS
while (!gprs.init(g_custom_parameters.apn)) {
Serial.println("GPRS init error, reintentando...");
intento++;
delay(2000); // Pausa entre intentos
// Si se supera el número máximo de intentos, reiniciar el dispositivo
if (intento >= maxIntentos) {
Serial.println("Fallo crítico en la inicialización. Reiniciando el dispositivo...");
delay(2000); // Asegura que el mensaje se muestre antes de reiniciar
api.system.reboot();
}
}
Best Regards