blob: 61ab3eff6f22de61f4b711633d8a9c9c2e0bf499 [file] [log] [blame]
/*
* Copyright (C) 2025 Metratec GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_sim7080_gps, CONFIG_MODEM_LOG_LEVEL);
#include "sim7080.h"
static struct sim7080_gnss_data gnss_data;
/**
* Get the next parameter from the gnss phrase.
*
* @param src The source string supported on first call.
* @param delim The delimiter of the parameter list.
* @param saveptr Pointer for subsequent parses.
* @return On success a pointer to the parameter. On failure
* or end of string NULL is returned.
*
* This function is used instead of strtok because strtok would
* skip empty parameters, which is not desired. The modem may
* omit parameters which could lead to a incorrect parse.
*/
static char *gnss_get_next_param(char *src, const char *delim, char **saveptr)
{
char *start, *del;
if (src) {
start = src;
} else {
start = *saveptr;
}
/* Illegal start string. */
if (!start) {
return NULL;
}
/* End of string reached. */
if (*start == '\0' || *start == '\r') {
return NULL;
}
del = strstr(start, delim);
if (!del) {
return NULL;
}
*del = '\0';
*saveptr = del + 1;
if (del == start) {
return NULL;
}
return start;
}
static void gnss_skip_param(char **saveptr)
{
gnss_get_next_param(NULL, ",", saveptr);
}
/**
* Splits float parameters of the CGNSINF response on '.'
*
* @param src Null terminated string containing the float.
* @param f1 Resulting number part of the float.
* @param f2 Resulting fraction part of the float.
* @return 0 if parsing was successful. Otherwise <0 is returned.
*
* If the number part of the float is negative f1 and f2 will be
* negative too.
*/
static int gnss_split_on_dot(const char *src, int32_t *f1, int32_t *f2)
{
char *dot = strchr(src, '.');
if (!dot) {
return -1;
}
*dot = '\0';
*f1 = (int32_t)strtol(src, NULL, 10);
*f2 = (int32_t)strtol(dot + 1, NULL, 10);
if (*f1 < 0) {
*f2 = -*f2;
}
return 0;
}
/**
* Parses cgnsinf response into the gnss_data structure.
*
* @param gps_buf Null terminated buffer containing the response.
* @return 0 on successful parse. Otherwise <0 is returned.
*/
static int parse_cgnsinf(char *gps_buf)
{
char *saveptr;
int ret;
int32_t number, fraction;
char *run_status = gnss_get_next_param(gps_buf, ",", &saveptr);
if (run_status == NULL) {
goto error;
} else if (*run_status != '1') {
goto error;
}
char *fix_status = gnss_get_next_param(NULL, ",", &saveptr);
if (fix_status == NULL) {
goto error;
} else if (*fix_status != '1') {
goto error;
}
char *utc = gnss_get_next_param(NULL, ",", &saveptr);
if (utc == NULL) {
goto error;
}
char *lat = gnss_get_next_param(NULL, ",", &saveptr);
if (lat == NULL) {
goto error;
}
char *lon = gnss_get_next_param(NULL, ",", &saveptr);
if (lon == NULL) {
goto error;
}
char *alt = gnss_get_next_param(NULL, ",", &saveptr);
char *speed = gnss_get_next_param(NULL, ",", &saveptr);
char *course = gnss_get_next_param(NULL, ",", &saveptr);
/* discard fix mode and reserved*/
gnss_skip_param(&saveptr);
gnss_skip_param(&saveptr);
char *hdop = gnss_get_next_param(NULL, ",", &saveptr);
if (hdop == NULL) {
goto error;
}
gnss_data.run_status = 1;
gnss_data.fix_status = 1;
strncpy(gnss_data.utc, utc, sizeof(gnss_data.utc) - 1);
ret = gnss_split_on_dot(lat, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.lat = number * 10000000 + fraction * 10;
ret = gnss_split_on_dot(lon, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.lon = number * 10000000 + fraction * 10;
if (alt) {
ret = gnss_split_on_dot(alt, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.alt = number * 1000 + fraction;
} else {
gnss_data.alt = 0;
}
ret = gnss_split_on_dot(hdop, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.hdop = number * 100 + fraction * 10;
if (course) {
ret = gnss_split_on_dot(course, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.cog = number * 100 + fraction * 10;
} else {
gnss_data.cog = 0;
}
if (speed) {
ret = gnss_split_on_dot(speed, &number, &fraction);
if (ret != 0) {
goto error;
}
gnss_data.kmh = number * 10 + fraction / 10;
} else {
gnss_data.kmh = 0;
}
return 0;
error:
memset(&gnss_data, 0, sizeof(gnss_data));
return -1;
}
/*
* Parses the +CGNSINF Gnss response.
*
* The CGNSINF command has the following parameters but
* not all parameters are set by the module:
*
* +CGNSINF: <GNSS run status>,<Fix status>,<UTC date & Time>,
* <Latitude>,<Longitude>,<MSL Altitude>,<Speed Over Ground>,
* <Course Over Ground>,<Fix Mode>,<Reserved1>,<HDOP>,<PDOP>,
* <VDOP>,<Reserved2>,<GNSS Satellites in View>,<Reserved3>,
* <HPA>,<VPA>
*
*/
MODEM_CMD_DEFINE(on_cmd_cgnsinf)
{
char gps_buf[MDM_GNSS_PARSER_MAX_LEN];
size_t out_len = net_buf_linearize(gps_buf, sizeof(gps_buf) - 1, data->rx_buf, 0, len);
gps_buf[out_len] = '\0';
return parse_cgnsinf(gps_buf);
}
int mdm_sim7080_query_gnss(struct sim7080_gnss_data *data)
{
int ret;
struct modem_cmd cmds[] = { MODEM_CMD("+CGNSINF: ", on_cmd_cgnsinf, 0U, NULL) };
if (sim7080_get_state() != SIM7080_STATE_GNSS) {
LOG_ERR("GNSS functionality is not enabled!!");
return -1;
}
memset(&gnss_data, 0, sizeof(gnss_data));
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmds, ARRAY_SIZE(cmds), "AT+CGNSINF",
&mdata.sem_response, K_SECONDS(2));
if (ret < 0) {
return ret;
}
if (!gnss_data.run_status || !gnss_data.fix_status) {
return -EAGAIN;
}
if (data) {
memcpy(data, &gnss_data, sizeof(gnss_data));
}
return ret;
}
int mdm_sim7080_start_gnss(void)
{
int ret;
sim7080_change_state(SIM7080_STATE_INIT);
k_work_cancel_delayable(&mdata.rssi_query_work);
ret = mdm_sim7080_power_on();
if (ret < 0) {
LOG_ERR("Failed to start modem!!");
return -1;
}
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+CGNSCOLD",
&mdata.sem_response, K_SECONDS(2));
if (ret < 0) {
return -1;
}
sim7080_change_state(SIM7080_STATE_GNSS);
return 0;
}