Skip to content

Commit 97fcc3c

Browse files
committed
GNSS and APN via AT commands
1 parent 100befc commit 97fcc3c

File tree

3 files changed

+329
-4
lines changed

3 files changed

+329
-4
lines changed

drivers/modem/modem_cellular.c

Lines changed: 294 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ struct modem_cellular_data {
114114
uint8_t iccid[MODEM_CELLULAR_DATA_ICCID_LEN];
115115
uint8_t manufacturer[MODEM_CELLULAR_DATA_MANUFACTURER_LEN];
116116
uint8_t fw_version[MODEM_CELLULAR_DATA_FW_VERSION_LEN];
117+
struct modem_cellular_gnss gnss_data;
117118

118119
/* PPP */
119120
struct modem_ppp *ppp;
@@ -130,6 +131,8 @@ struct modem_cellular_data {
130131
uint8_t event_buf[8];
131132
struct ring_buf event_rb;
132133
struct k_mutex event_rb_lock;
134+
135+
struct modem_cell_profile s_profile;
133136
};
134137

135138
struct modem_cellular_config {
@@ -146,6 +149,68 @@ struct modem_cellular_config {
146149
const struct modem_chat_script *periodic_chat_script;
147150
};
148151

152+
153+
154+
155+
static void modem_cellular_apply_profile(struct modem_cellular_data *data);
156+
157+
158+
159+
160+
/* Hook débil para el backend PPP, si se usa auth */
161+
__attribute__((weak))
162+
void modem_cellular_ppp_set_auth(const char *user, const char *pass)
163+
{
164+
ARG_UNUSED(user);
165+
ARG_UNUSED(pass);
166+
}
167+
168+
int modem_cellular_profile_set(const struct device *dev, struct modem_cell_profile *in)
169+
{
170+
if (!dev || !in) {
171+
return -EINVAL;
172+
}
173+
struct modem_cellular_data *data = (struct modem_cellular_data *)dev->data;
174+
175+
176+
memcpy(data->s_profile.apn, in->apn, sizeof(data->s_profile.apn));
177+
data->s_profile.apn[sizeof(data->s_profile.apn) - 1] = '\0';
178+
179+
memcpy(data->s_profile.user, in->user, sizeof(data->s_profile.user));
180+
data->s_profile.user[sizeof(data->s_profile.user) - 1] = '\0';
181+
182+
memcpy(data->s_profile.pass, in->pass, sizeof(data->s_profile.pass));
183+
data->s_profile.pass[sizeof(data->s_profile.pass) - 1] = '\0';
184+
185+
data->s_profile.use_auth = in->use_auth;
186+
187+
return 0;
188+
}
189+
190+
int modem_cellular_profile_get(const struct device *dev, struct modem_cell_profile *out)
191+
{
192+
if (!dev || !out) {
193+
return -EINVAL;
194+
}
195+
struct modem_cellular_data *data = (struct modem_cellular_data *)dev->data;
196+
197+
memcpy(out, &data->s_profile, sizeof(struct modem_cell_profile));
198+
// *out = data->s_profile; /* copia estructurada directa */
199+
200+
return 0;
201+
}
202+
203+
/* API de conveniencia para leer GNSS desde main */
204+
int modem_cellular_get_gnss(const struct device *dev, struct modem_cellular_gnss *out)
205+
{
206+
if (!dev || !out) {
207+
return -EINVAL;
208+
}
209+
struct modem_cellular_data *data = (struct modem_cellular_data *)dev->data;
210+
*out = data->gnss_data;
211+
return data->gnss_data.valid ? 0 : -ENODATA;
212+
}
213+
149214
static const char *modem_cellular_state_str(enum modem_cellular_state state)
150215
{
151216
switch (state) {
@@ -293,6 +358,114 @@ static void modem_cellular_chat_callback_handler(struct modem_chat *chat,
293358
}
294359
}
295360

361+
static double nmea_degmin_to_decimal(const char *ddmmHemi)
362+
{
363+
size_t n = strlen(ddmmHemi);
364+
if (n < 3) {
365+
return 0.0;
366+
}
367+
368+
char hemi = ddmmHemi[n - 1]; // Last character: N/S/E/W
369+
char buf[16];
370+
if (n - 1 >= sizeof(buf)) {
371+
return 0.0;
372+
}
373+
374+
strncpy(buf, ddmmHemi, n - 1);
375+
buf[n - 1] = '\0';
376+
377+
// Determine number of degree digits (2 for lat, 3 for lon)
378+
int deg_digits = (n >= 10) ? 3 : 2;
379+
char d[4] = {0};
380+
memcpy(d, buf, deg_digits);
381+
382+
double deg = atof(d);
383+
double min = atof(buf + deg_digits);
384+
double dec = deg + (min / 60.0);
385+
386+
if (hemi == 'S' || hemi == 'W') {
387+
dec = -dec;
388+
}
389+
390+
return dec;
391+
}
392+
393+
static void modem_cellular_chat_on_gnss_error(struct modem_chat *chat, void *user_data)
394+
{
395+
struct modem_cellular_data *data = (struct modem_cellular_data *)user_data;
396+
struct modem_cellular_gnss *gnss = &data->gnss_data;
397+
398+
gnss->valid = false;
399+
LOG_WRN("GNSS: error en respuesta");
400+
}
401+
402+
static void modem_cellular_chat_on_gnss(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
403+
{
404+
struct modem_cellular_data *data = (struct modem_cellular_data *)user_data;
405+
struct modem_cellular_gnss *gnss = &data->gnss_data;
406+
407+
// Expected: +QGPSLOC: <UTC>,<lat>,<lon>,<HDOP>,<alt>,<fix>,<COG>,<spkm>,<spkn>,<date>,<nsat>
408+
if (argc < 12) {
409+
LOG_WRN("GNSS: respuesta muy corta (%d campos)", argc);
410+
gnss->valid = false;
411+
return;
412+
}
413+
414+
LOG_INF("GNSS raw message (%d campos):", argc);
415+
for (int i = 0; i < argc; i++) {
416+
LOG_INF(" argv[%d] = '%s'", i, argv[i]);
417+
}
418+
419+
// --- Parse fields ---
420+
memset(gnss, 0, sizeof(*gnss));
421+
422+
// UTC time (hhmmss)
423+
strncpy(gnss->time_utc, argv[1], sizeof(gnss->time_utc) - 1);
424+
gnss->time_utc[sizeof(gnss->time_utc) - 1] = '\0';
425+
426+
// Latitude and Longitude with hemisphere
427+
gnss->lat_deg = nmea_degmin_to_decimal(argv[2]);
428+
gnss->lon_deg = nmea_degmin_to_decimal(argv[3]);
429+
430+
// Precision, altitude, and fix type
431+
gnss->hdop = atof(argv[4]);
432+
gnss->alt_m = atof(argv[5]);
433+
gnss->fix = atoi(argv[6]);
434+
435+
// Course over ground and speed
436+
gnss->cog_deg = atof(argv[7]);
437+
gnss->spd_kmh = atof(argv[8]);
438+
gnss->spd_knt = atof(argv[9]);
439+
440+
// Date (ddmmyy)
441+
strncpy(gnss->date_utc, argv[10], sizeof(gnss->date_utc) - 1);
442+
gnss->date_utc[sizeof(gnss->date_utc) - 1] = '\0';
443+
444+
// Number of satellites
445+
gnss->nsat = atoi(argv[11]);
446+
447+
// Determine validity
448+
gnss->valid = (gnss->fix > 0 && gnss->lat_deg != 0.0 && gnss->lon_deg != 0.0);
449+
450+
//PRINT ALL ARRAY ITEMS FOR DEBUGGING
451+
LOG_INF("GNSS raw message (%d campos):", argc);
452+
for (int i = 0; i < argc; i++) {
453+
LOG_INF(" argv[%d] = '%s'", i, argv[i]);
454+
}
455+
456+
LOG_INF("GNSS fix: UTC=%s, date=%s, lat=%.6f, lon=%.6f, alt=%.2f m, fix=%d, sat=%d, hdop=%.2f, cog=%.2f°, spd=%.2f km/h",
457+
gnss->time_utc,
458+
gnss->date_utc,
459+
gnss->lat_deg,
460+
gnss->lon_deg,
461+
gnss->alt_m,
462+
gnss->fix,
463+
gnss->nsat,
464+
gnss->hdop,
465+
gnss->cog_deg,
466+
gnss->spd_kmh);
467+
}
468+
296469
static void modem_cellular_chat_on_imei(struct modem_chat *chat, char **argv, uint16_t argc,
297470
void *user_data)
298471
{
@@ -434,6 +607,10 @@ MODEM_CHAT_MATCHES_DEFINE(allow_match,
434607
MODEM_CHAT_MATCH("OK", "", NULL),
435608
MODEM_CHAT_MATCH("ERROR", "", NULL));
436609

610+
MODEM_CHAT_MATCHES_DEFINE(qgpsloc_match,
611+
MODEM_CHAT_MATCH("+QGPSLOC:", ",", modem_cellular_chat_on_gnss),
612+
MODEM_CHAT_MATCH("+CME ERROR", "", modem_cellular_chat_on_gnss_error));
613+
437614
MODEM_CHAT_MATCH_DEFINE(imei_match, "", "", modem_cellular_chat_on_imei);
438615
MODEM_CHAT_MATCH_DEFINE(cgmm_match, "", "", modem_cellular_chat_on_cgmm);
439616
MODEM_CHAT_MATCH_DEFINE(csq_match, "+CSQ: ", ",", modem_cellular_chat_on_csq);
@@ -852,6 +1029,10 @@ static void modem_cellular_run_dial_script_event_handler(struct modem_cellular_d
8521029
switch (evt) {
8531030
case MODEM_CELLULAR_EVENT_TIMEOUT:
8541031
modem_chat_attach(&data->chat, data->dlci1_pipe);
1032+
1033+
// NEW: Apply profile settings (APN, auth) before dialing
1034+
modem_cellular_apply_profile(data);
1035+
8551036
modem_chat_run_script_async(&data->chat, config->dial_chat_script);
8561037
break;
8571038

@@ -1462,6 +1643,7 @@ const static struct cellular_driver_api modem_cellular_api = {
14621643
.get_signal = modem_cellular_get_signal,
14631644
.get_modem_info = modem_cellular_get_modem_info,
14641645
.get_registration_status = modem_cellular_get_registration_status,
1646+
//.get_gnss = modem_cellular_get_gnss,
14651647
};
14661648

14671649
#ifdef CONFIG_PM_DEVICE
@@ -1587,6 +1769,48 @@ static int modem_cellular_init(const struct device *dev)
15871769
return 0;
15881770
}
15891771

1772+
/* static void modem_cellular_apply_profile(struct modem_cellular_data *data)
1773+
{
1774+
ARG_UNUSED(data);
1775+
1776+
if (data->s_profile.use_auth && data->s_profile.user[0] && data->s_profile.pass[0]) {
1777+
modem_cellular_ppp_set_auth(data->s_profile.user, data->s_profile.pass);
1778+
}
1779+
1780+
if (!data->s_profile.apn[0]) {
1781+
return;
1782+
}
1783+
1784+
char cmd[64];
1785+
snprintk(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", data->s_profile.apn); */
1786+
1787+
1788+
1789+
/* Enviar y consumir OK, pero ignorar errores para no bloquear el dial */ /* Usa tu infraestructura
1790+
de chat; normalmente
1791+
tienes 'data->chat' y
1792+
'ok_match' */
1793+
// #if defined(MODEM_CHAT_HAS_SEND_SYNC)
1794+
// (void)modem_chat_send_sync(&data->chat, cmd, &ok_match, K_SECONDS(2), data);
1795+
// #else
1796+
// MODEM_CHAT_SCRIPT_CMDS_DEFINE(_cgdcont_script,
1797+
// MODEM_CHAT_SCRIPT_CMD_RESP_DYNAMIC(cmd, ok_match),
1798+
// MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match));
1799+
1800+
/* Copiar la string dinámica al primer comando */
1801+
// ((struct modem_chat_script_chat *)&_cgdcont_script_cmds[0]) = cmd;
1802+
1803+
/* static const struct modem_chat_script _cgdcont_script = {
1804+
.script_cmds = _cgdcont_script_cmds,
1805+
.abort_matches = NULL,
1806+
.callback = NULL,
1807+
.timeout = 3,
1808+
};
1809+
*/
1810+
//(void)modem_chat_script_run(&data->chat, &_cgdcont_script);
1811+
// #endif
1812+
// }
1813+
15901814
/*
15911815
* Every modem uses two custom scripts to initialize the modem and dial out.
15921816
*
@@ -1601,6 +1825,53 @@ static int modem_cellular_init(const struct device *dev)
16011825
* dial out and put the DLCI channel into data mode.
16021826
*/
16031827

1828+
1829+
1830+
1831+
1832+
/* Aplica el perfil PDP/APN de runtime antes del dial.
1833+
* - Si hay auth y backend lo soporta, la pasa via hook débil.
1834+
* - Envía AT+CGDCONT con APN dinámico y consume OK.
1835+
* - Errores se ignoran a propósito para no bloquear el dial.
1836+
*/
1837+
static void modem_cellular_apply_profile(struct modem_cellular_data *data)
1838+
{
1839+
ARG_UNUSED(data);
1840+
1841+
/* 1) PPP auth (opcional) */
1842+
if (data->s_profile.use_auth && data->s_profile.user[0] && data->s_profile.pass[0]) {
1843+
modem_cellular_ppp_set_auth(data->s_profile.user, data->s_profile.pass);
1844+
}
1845+
1846+
/* 2) APN dinámico */
1847+
if (!data->s_profile.apn[0]) {
1848+
/* Sin APN => no tocamos CGDCONT */
1849+
return;
1850+
}
1851+
1852+
/* Construir AT+CGDCONT=1,"IP","<apn>" */
1853+
char cmd[128];
1854+
snprintk(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", data->s_profile.apn);
1855+
1856+
/* 3) Script de un paso: enviar cmd y esperar OK (Zephyr 2.7 API) */
1857+
struct modem_chat_script_chat step;
1858+
1859+
modem_chat_script_chat_init(&step);
1860+
modem_chat_script_chat_set_request(&step, cmd);
1861+
modem_chat_script_chat_set_response_matches(&step, &ok_match, 1);
1862+
1863+
struct modem_chat_script script;
1864+
1865+
modem_chat_script_init(&script);
1866+
modem_chat_script_set_script_chats(&script, &step, 1);
1867+
/* opcional: timeout corto para no demorar el dial si el módem no responde
1868+
// modem_chat_script_set_timeout(&script, 3);
1869+
*/
1870+
1871+
/* 4) Ejecutar e ignorar retorno para no bloquear el flujo */
1872+
(void)modem_chat_run_script(&data->chat, &script);
1873+
}
1874+
16041875
#if DT_HAS_COMPAT_STATUS_OKAY(quectel_bg95)
16051876
MODEM_CHAT_SCRIPT_CMDS_DEFINE(quectel_bg95_init_chat_script_cmds,
16061877
MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", ok_match),
@@ -1652,7 +1923,8 @@ MODEM_CHAT_SCRIPT_DEFINE(quectel_bg95_periodic_chat_script,
16521923

16531924
#if DT_HAS_COMPAT_STATUS_OKAY(quectel_eg25_g)
16541925
MODEM_CHAT_SCRIPT_CMDS_DEFINE(
1655-
quectel_eg25_g_init_chat_script_cmds, MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", ok_match),
1926+
quectel_eg25_g_init_chat_script_cmds,
1927+
MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", ok_match),
16561928
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=4", ok_match),
16571929
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CMEE=1", ok_match),
16581930
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG=1", ok_match),
@@ -1671,17 +1943,25 @@ MODEM_CHAT_SCRIPT_CMDS_DEFINE(
16711943
MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match),
16721944
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CIMI", cimi_match),
16731945
MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match),
1946+
1947+
// GNSS on cmd
1948+
MODEM_CHAT_SCRIPT_CMD_RESP("AT+QGPS=1", ok_match),
1949+
16741950
MODEM_CHAT_SCRIPT_CMD_RESP_NONE("AT+CMUX=0,0,5,127,10,3,30,10,2", 100));
16751951

1952+
16761953
MODEM_CHAT_SCRIPT_DEFINE(quectel_eg25_g_init_chat_script, quectel_eg25_g_init_chat_script_cmds,
16771954
abort_matches, modem_cellular_chat_callback_handler, 10);
16781955

16791956
MODEM_CHAT_SCRIPT_CMDS_DEFINE(quectel_eg25_g_dial_chat_script_cmds,
16801957
MODEM_CHAT_SCRIPT_CMD_RESP_MULT("AT+CGACT=0,1", allow_match),
1681-
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGDCONT=1,\"IP\","
1682-
"\""CONFIG_MODEM_CELLULAR_APN"\"",
1683-
ok_match),
1958+
// MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGDCONT=1,\"IP\","
1959+
// "\""CONFIG_MODEM_CELLULAR_APN"\"",
1960+
// ok_match),
16841961
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=1", ok_match),
1962+
1963+
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGDCONT=1", ok_match),
1964+
16851965
MODEM_CHAT_SCRIPT_CMD_RESP_NONE("ATD*99***1#", 0),);
16861966

16871967
MODEM_CHAT_SCRIPT_DEFINE(quectel_eg25_g_dial_chat_script, quectel_eg25_g_dial_chat_script_cmds,
@@ -1691,11 +1971,21 @@ MODEM_CHAT_SCRIPT_CMDS_DEFINE(quectel_eg25_g_periodic_chat_script_cmds,
16911971
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG?", ok_match),
16921972
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", ok_match),
16931973
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGREG?", ok_match),
1974+
1975+
// GNSS on cmd
1976+
/* GNSS: pide localización y decodifica la línea +QGPSLOC: ... */
1977+
MODEM_CHAT_SCRIPT_CMD_RESP("AT+QGPSLOC?", qgpsloc_match),
1978+
1979+
MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match),
16941980
MODEM_CHAT_SCRIPT_CMD_RESP("AT+CSQ", csq_match));
1981+
16951982

16961983
MODEM_CHAT_SCRIPT_DEFINE(quectel_eg25_g_periodic_chat_script,
16971984
quectel_eg25_g_periodic_chat_script_cmds, abort_matches,
16981985
modem_cellular_chat_callback_handler, 4);
1986+
1987+
1988+
16991989
#endif
17001990

17011991
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_gsm_ppp)

0 commit comments

Comments
 (0)