blob: f32b7e4ce9635d3049e559493d3c13841bc5f42b [file] [log] [blame]
/*
* Copyright (c) 2020 Laird Connectivity
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT swir_hl7800
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
LOG_MODULE_REGISTER(modem_hl7800, CONFIG_MODEM_LOG_LEVEL);
#include <zephyr/types.h>
#include <stddef.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <zephyr/zephyr.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/pm/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/dns_resolve.h>
#if defined(CONFIG_NET_IPV6)
#include "ipv6.h"
#endif
#if defined(CONFIG_NET_IPV4)
#include "ipv4.h"
#endif
#if defined(CONFIG_NET_UDP)
#include "udp_internal.h"
#endif
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
#include <zephyr/fs/fs.h>
#endif
#include "modem_receiver.h"
#include <zephyr/drivers/modem/hl7800.h>
#define PREFIXED_SWITCH_CASE_RETURN_STRING(prefix, val) \
case prefix##_##val: { \
return #val; \
}
/* Uncomment the #define below to enable a hexdump of all incoming
* data from the modem receiver
*/
/* #define HL7800_ENABLE_VERBOSE_MODEM_RECV_HEXDUMP 1 */
#define HL7800_LOG_UNHANDLED_RX_MSGS 1
/* Uncomment the #define(s) below to enable extra debugging */
/* #define HL7800_RX_LOCK_LOG 1 */
/* #define HL7800_TX_LOCK_LOG 1 */
/* #define HL7800_IO_LOG 1 */
#define HL7800_RX_LOCK_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_RX_LOCK_LOG)) { \
LOG_DBG(fmt, ##__VA_ARGS__); \
} \
} while (false)
#define HL7800_TX_LOCK_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_TX_LOCK_LOG)) { \
LOG_DBG(fmt, ##__VA_ARGS__); \
} \
} while (false)
#define HL7800_IO_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_IO_LOG)) { \
LOG_DBG(fmt, ##__VA_ARGS__); \
} \
} while (false)
#if ((LOG_LEVEL == LOG_LEVEL_DBG) && \
defined(CONFIG_MODEM_HL7800_LOW_POWER_MODE))
#define PRINT_AWAKE_MSG LOG_WRN("awake")
#define PRINT_NOT_AWAKE_MSG LOG_WRN("NOT awake")
#else
#define PRINT_AWAKE_MSG
#define PRINT_NOT_AWAKE_MSG
#endif
enum tcp_notif {
HL7800_TCP_NET_ERR,
HL7800_TCP_NO_SOCKS,
HL7800_TCP_MEM,
HL7800_TCP_DNS,
HL7800_TCP_DISCON,
HL7800_TCP_CONN,
HL7800_TCP_ERR,
HL7800_TCP_CLIENT_REQ,
HL7800_TCP_DATA_SND,
HL7800_TCP_ID,
HL7800_TCP_RUNNING,
HL7800_TCP_ALL_USED,
HL7800_TCP_TIMEOUT,
HL7800_TCP_SSL_CONN,
HL7800_TCP_SSL_INIT
};
enum udp_notif {
HL7800_UDP_NET_ERR = 0,
HL7800_UDP_NO_SOCKS = 1,
HL7800_UDP_MEM = 2,
HL7800_UDP_DNS = 3,
HL7800_UDP_CONN = 5,
HL7800_UDP_ERR = 6,
HL7800_UDP_DATA_SND = 8, /* this matches TCP_DATA_SND */
HL7800_UDP_ID = 9,
HL7800_UDP_RUNNING = 10,
HL7800_UDP_ALL_USED = 11
};
enum socket_state {
SOCK_IDLE,
SOCK_RX,
SOCK_TX,
SOCK_CONNECTED,
};
enum hl7800_lpm {
HL7800_LPM_NONE,
HL7800_LPM_EDRX,
HL7800_LPM_PSM,
};
/* pin settings */
enum mdm_control_pins {
MDM_RESET = 0,
MDM_WAKE,
MDM_PWR_ON,
MDM_FAST_SHUTD,
MDM_VGPIO,
MDM_UART_DSR,
MDM_UART_CTS,
MDM_GPIO6,
MAX_MDM_CONTROL_PINS,
};
enum net_operator_status { NO_OPERATOR, REGISTERED };
enum device_service_indications {
WDSI_PKG_DOWNLOADED = 3,
};
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
enum XMODEM_CONTROL_CHARACTERS {
XM_SOH = 0x01,
XM_SOH_1K = 0x02,
XM_EOT = 0x04,
XM_ACK = 0x06, /* 'R' */
XM_NACK = 0x15, /* 'N' */
XM_ETB = 0x17,
XM_CAN = 0x18,
XM_C = 0x43
};
#define XMODEM_DATA_SIZE 1024
#define XMODEM_PACKET_SIZE (XMODEM_DATA_SIZE + 4)
#define XMODEM_PAD_VALUE 26
struct xmodem_packet {
uint8_t preamble;
uint8_t id;
uint8_t id_complement;
uint8_t data[XMODEM_DATA_SIZE];
uint8_t crc;
};
#endif
#define MDM_UART_DEV DEVICE_DT_GET(DT_INST_BUS(0))
#define MDM_SEND_OK_ENABLED 0
#define MDM_SEND_OK_DISABLED 1
#define MDM_CMD_SEND_TIMEOUT K_SECONDS(6)
#define MDM_IP_SEND_RX_TIMEOUT K_SECONDS(62)
#define MDM_SOCK_NOTIF_DELAY K_MSEC(150)
#define MDM_CMD_CONN_TIMEOUT K_SECONDS(31)
#define MDM_MAX_DATA_LENGTH 1500
#define MDM_MTU 1500
#define MDM_MAX_RESP_SIZE 128
#define MDM_IP_INFO_RESP_SIZE 256
#define MDM_HANDLER_MATCH_MAX_LEN 100
#define MDM_MAX_SOCKETS 6
/* Special value used to indicate that a socket is being created
* and that its actual ID hasn't been assigned yet.
*/
#define MDM_CREATE_SOCKET_ID (MDM_MAX_SOCKETS + 1)
#define BUF_ALLOC_TIMEOUT K_SECONDS(1)
#define SIZE_OF_NUL 1
#define SIZE_WITHOUT_NUL(v) (sizeof(v) - SIZE_OF_NUL)
#define CMD_HANDLER(cmd_, cb_) \
{ \
.cmd = cmd_, .cmd_len = (uint16_t)sizeof(cmd_) - 1, \
.func = on_cmd_##cb_ \
}
#define MDM_MANUFACTURER_LENGTH 16
#define MDM_MODEL_LENGTH 7
#define MDM_SN_RESPONSE_LENGTH (MDM_HL7800_SERIAL_NUMBER_SIZE + 7)
#define MDM_NETWORK_STATUS_LENGTH 45
#define MDM_TOP_BAND_SIZE 4
#define MDM_MIDDLE_BAND_SIZE 8
#define MDM_BOTTOM_BAND_SIZE 8
#define MDM_TOP_BAND_START_POSITION 2
#define MDM_MIDDLE_BAND_START_POSITION 6
#define MDM_BOTTOM_BAND_START_POSITION 14
#define MDM_BAND_BITMAP_STR_LENGTH_MAX \
(MDM_TOP_BAND_SIZE + MDM_MIDDLE_BAND_SIZE + MDM_BOTTOM_BAND_SIZE)
#define MDM_BAND_BITMAP_STR_LENGTH_MIN 1
#define MDM_DEFAULT_AT_CMD_RETRIES 3
#define MDM_WAKEUP_TIME K_SECONDS(12)
#define MDM_BOOT_TIME K_SECONDS(12)
#define MDM_WAKE_TO_CHECK_CTS_DELAY_MS K_MSEC(20)
#define MDM_WAIT_FOR_DATA_TIME K_MSEC(50)
#define MDM_RESET_LOW_TIME K_MSEC(50)
#define MDM_RESET_HIGH_TIME K_MSEC(10)
#define MDM_WAIT_FOR_DATA_RETRIES 3
#define RSSI_UNKNOWN -999
#define DNS_WORK_DELAY_SECS 1
#define IFACE_WORK_DELAY K_MSEC(500)
#define SOCKET_CLEANUP_WORK_DELAY K_MSEC(100)
#define WAIT_FOR_KSUP_RETRIES 5
#define CGCONTRDP_RESPONSE_NUM_DELIMS 7
#define COPS_RESPONSE_NUM_DELIMS 2
#define KCELLMEAS_RESPONSE_NUM_DELIMS 4
#define PROFILE_LINE_1 \
"E1 Q0 V1 X4 &C1 &D1 &R1 &S0 +IFC=2,2 &K3 +IPR=115200 +FCLASS0\r\n"
#define PROFILE_LINE_2 \
"S00:255 S01:255 S03:255 S04:255 S05:255 S07:255 S08:255 S10:255\r\n"
#define ADDRESS_FAMILY_IPV4 "IPV4"
#if defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4V6)
#define MODEM_HL7800_ADDRESS_FAMILY "IPV4V6"
#elif defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4)
#define MODEM_HL7800_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4
#else
#define MODEM_HL7800_ADDRESS_FAMILY "IPV6"
#endif
#define MDM_HL7800_SOCKET_AF_IPV4 0
#define MDM_HL7800_SOCKET_AF_IPV6 1
#define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0"
#define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1"
#define SET_RAT_M1_CMD "AT+KSRAT=0,1"
#define SET_RAT_NB1_CMD "AT+KSRAT=1,1"
#define NEW_RAT_CMD_MIN_VERSION "HL7800.4.5.4.0"
#define HL7800_VERSION_FORMAT "HL7800.%zu.%zu.%zu.%zu"
#define MAX_PROFILE_LINE_LENGTH \
MAX(sizeof(PROFILE_LINE_1), sizeof(PROFILE_LINE_2))
#define IPV6_ADDR_FORMAT "####:####:####:####:####:####:####:####"
#define HL7800_IPV6_ADDR_LEN \
sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16")
#define MDM_ADDR_FAM_MAX_LEN sizeof("IPV4V6")
/* The ? can be a + or - */
static const char TIME_STRING_FORMAT[] = "\"yy/MM/dd,hh:mm:ss?zz\"";
#define TIME_STRING_DIGIT_STRLEN 2
#define TIME_STRING_SEPARATOR_STRLEN 1
#define TIME_STRING_PLUS_MINUS_INDEX (6 * 3)
#define TIME_STRING_FIRST_SEPARATOR_INDEX 0
#define TIME_STRING_FIRST_DIGIT_INDEX 1
#define TIME_STRING_TO_TM_STRUCT_YEAR_OFFSET (2000 - 1900)
/* Time structure min, max */
#define TM_YEAR_RANGE 0, 99
#define TM_MONTH_RANGE_PLUS_1 1, 12
#define TM_DAY_RANGE 1, 31
#define TM_HOUR_RANGE 0, 23
#define TM_MIN_RANGE 0, 59
#define TM_SEC_RANGE 0, 60 /* leap second */
#define QUARTER_HOUR_RANGE 0, 96
#define SECONDS_PER_QUARTER_HOUR (15 * 60)
#define SEND_AT_CMD_ONCE_EXPECT_OK(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
#define SEND_AT_CMD_IGNORE_ERROR(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
} \
} while (false)
#define SEND_AT_CMD_EXPECT_OK(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \
MDM_DEFAULT_AT_CMD_RETRIES, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
/* Complex has "no_id_resp" set to true because the sending command
* is the command used to process the response
*/
#define SEND_COMPLEX_AT_CMD(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \
MDM_DEFAULT_AT_CMD_RETRIES, true); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
NET_BUF_POOL_DEFINE(mdm_recv_pool, CONFIG_MODEM_HL7800_RECV_BUF_CNT,
CONFIG_MODEM_HL7800_RECV_BUF_SIZE, 0, NULL);
static uint8_t mdm_recv_buf[MDM_MAX_DATA_LENGTH];
static K_SEM_DEFINE(hl7800_RX_lock_sem, 1, 1);
static K_SEM_DEFINE(hl7800_TX_lock_sem, 1, 1);
static K_MUTEX_DEFINE(cb_lock);
/* RX thread structures */
K_THREAD_STACK_DEFINE(hl7800_rx_stack, CONFIG_MODEM_HL7800_RX_STACK_SIZE);
struct k_thread hl7800_rx_thread;
#define RX_THREAD_PRIORITY K_PRIO_COOP(7)
/* RX thread work queue */
K_THREAD_STACK_DEFINE(hl7800_workq_stack,
CONFIG_MODEM_HL7800_RX_WORKQ_STACK_SIZE);
static struct k_work_q hl7800_workq;
#define WORKQ_PRIORITY K_PRIO_COOP(7)
static const char EOF_PATTERN[] = "--EOF--Pattern--";
static const char CONNECT_STRING[] = "CONNECT";
static const char OK_STRING[] = "OK";
struct hl7800_socket {
struct net_context *context;
sa_family_t family;
enum net_sock_type type;
enum net_ip_protocol ip_proto;
struct sockaddr src;
struct sockaddr dst;
bool created;
bool reconfig;
int socket_id;
int rx_size;
int error;
enum socket_state state;
/** semaphore */
struct k_sem sock_send_sem;
/** socket callbacks */
struct k_work recv_cb_work;
struct k_work rx_data_work;
struct k_work_delayable notif_work;
net_context_recv_cb_t recv_cb;
struct net_pkt *recv_pkt;
void *recv_user_data;
};
struct stale_socket {
int reserved; /* first word of queue data item reserved for the kernel */
enum net_sock_type type;
uint8_t id;
bool allocated;
};
#define NO_ID_RESP_CMD_MAX_LENGTH 32
struct hl7800_config {
struct gpio_dt_spec gpio[MAX_MDM_CONTROL_PINS];
};
struct hl7800_iface_ctx {
struct net_if *iface;
uint8_t mac_addr[6];
struct in_addr ipv4Addr, subnet, gateway, dns_v4;
#ifdef CONFIG_NET_IPV6
struct in6_addr ipv6Addr, dns_v6;
char dns_v6_string[HL7800_IPV6_ADDR_LEN];
#endif
bool restarting;
bool initialized;
bool wait_for_KSUP;
uint32_t wait_for_KSUP_tries;
bool reconfig_IP_connection;
char dns_v4_string[NET_IPV4_ADDR_LEN];
char no_id_resp_cmd[NO_ID_RESP_CMD_MAX_LENGTH];
bool search_no_id_resp;
/* GPIO PORT devices */
struct gpio_callback mdm_vgpio_cb;
struct gpio_callback mdm_uart_dsr_cb;
struct gpio_callback mdm_gpio6_cb;
struct gpio_callback mdm_uart_cts_cb;
int vgpio_state;
int dsr_state;
int gpio6_state;
int cts_state;
/* RX specific attributes */
struct mdm_receiver_context mdm_ctx;
/* socket data */
struct hl7800_socket sockets[MDM_MAX_SOCKETS];
int last_socket_id;
int last_error;
struct stale_socket stale_sockets[MDM_MAX_SOCKETS];
struct k_queue stale_socket_queue;
/* semaphores */
struct k_sem response_sem;
struct k_sem mdm_awake;
/* work */
struct k_work_delayable rssi_query_work;
struct k_work_delayable iface_status_work;
struct k_work_delayable dns_work;
struct k_work mdm_vgpio_work;
struct k_work_delayable mdm_reset_work;
struct k_work_delayable allow_sleep_work;
struct k_work_delayable delete_untracked_socket_work;
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
/* firmware update */
enum mdm_hl7800_fota_state fw_update_state;
struct fs_file_t fw_update_file;
struct xmodem_packet fw_packet;
uint32_t fw_packet_count;
int file_pos;
struct k_work finish_fw_update_work;
bool fw_updated;
#endif
/* modem info */
/* NOTE: make sure length is +1 for null char */
char mdm_manufacturer[MDM_MANUFACTURER_LENGTH];
char mdm_model[MDM_MODEL_LENGTH];
char mdm_revision[MDM_HL7800_REVISION_MAX_SIZE];
char mdm_imei[MDM_HL7800_IMEI_SIZE];
char mdm_sn[MDM_HL7800_SERIAL_NUMBER_SIZE];
char mdm_network_status[MDM_NETWORK_STATUS_LENGTH];
char mdm_iccid[MDM_HL7800_ICCID_SIZE];
enum mdm_hl7800_startup_state mdm_startup_state;
enum mdm_hl7800_radio_mode mdm_rat;
char mdm_active_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE];
char mdm_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE];
char mdm_imsi[MDM_HL7800_IMSI_MAX_STR_SIZE];
int mdm_rssi;
uint16_t mdm_bands_top;
uint32_t mdm_bands_middle;
uint32_t mdm_bands_bottom;
int32_t mdm_sinr;
bool mdm_echo_is_on;
struct mdm_hl7800_apn mdm_apn;
bool mdm_startup_reporting_on;
int device_services_ind;
bool new_rat_cmd_support;
uint8_t operator_index;
enum mdm_hl7800_functionality functionality;
char mdm_pdp_addr_fam[MDM_ADDR_FAM_MAX_LEN];
/* modem state */
bool allow_sleep;
bool uart_on;
enum mdm_hl7800_sleep desired_sleep_level;
enum mdm_hl7800_sleep sleep_state;
enum hl7800_lpm low_power_mode;
enum mdm_hl7800_network_state network_state;
enum net_operator_status operator_status;
struct tm local_time;
int32_t local_time_offset;
bool local_time_valid;
bool configured;
void (*wake_up_callback)(int state);
void (*gpio6_callback)(int state);
void (*cts_callback)(int state);
#ifdef CONFIG_MODEM_HL7800_GPS
struct k_work_delayable gps_work;
uint32_t gps_query_location_rate_seconds;
#endif
};
struct cmd_handler {
const char *cmd;
uint16_t cmd_len;
bool (*func)(struct net_buf **buf, uint16_t len);
};
static sys_slist_t hl7800_event_callback_list =
SYS_SLIST_STATIC_INIT(&hl7800_event_callback_list);
const static struct hl7800_config hl7800_cfg = {
.gpio = {
GPIO_DT_SPEC_INST_GET(0, mdm_reset_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_wake_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_pwr_on_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_fast_shutd_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_vgpio_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_uart_dsr_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_uart_cts_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_gpio6_gpios),
},
};
static struct hl7800_iface_ctx ictx;
static size_t hl7800_read_rx(struct net_buf **buf);
static char *get_network_state_string(enum mdm_hl7800_network_state state);
static char *get_startup_state_string(enum mdm_hl7800_startup_state state);
static char *get_sleep_state_string(enum mdm_hl7800_sleep state);
static void set_network_state(enum mdm_hl7800_network_state state);
static void set_startup_state(enum mdm_hl7800_startup_state state);
static void set_sleep_state(enum mdm_hl7800_sleep state);
static void generate_network_state_event(void);
static void generate_startup_state_event(void);
static void generate_sleep_state_event(void);
static int modem_boot_handler(char *reason);
static void mdm_vgpio_work_cb(struct k_work *item);
static void mdm_reset_work_callback(struct k_work *item);
static int write_apn(char *access_point_name);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void mark_sockets_for_reconfig(void);
#endif
static void hl7800_build_mac(struct hl7800_iface_ctx *ictx);
static void rssi_query(void);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void initialize_sleep_level(void);
static int set_sleep_level(void);
#endif
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
static char *get_fota_state_string(enum mdm_hl7800_fota_state state);
static void set_fota_state(enum mdm_hl7800_fota_state state);
static void generate_fota_state_event(void);
static void generate_fota_count_event(void);
#endif
static struct stale_socket *alloc_stale_socket(void)
{
struct stale_socket *sock = NULL;
for (int i = 0; i < MDM_MAX_SOCKETS; i++) {
if (!ictx.stale_sockets[i].allocated) {
sock = &ictx.stale_sockets[i];
sock->allocated = true;
break;
}
}
return sock;
}
static void free_stale_socket(struct stale_socket *sock)
{
if (sock != NULL) {
sock->allocated = false;
}
}
static int queue_stale_socket(enum net_sock_type type, uint8_t id)
{
int ret = 0;
struct stale_socket *sock = NULL;
sock = alloc_stale_socket();
if (sock != NULL) {
sock->type = type;
sock->id = id;
k_queue_append(&ictx.stale_socket_queue, (void *)sock);
} else {
LOG_ERR("Could not alloc stale socket");
ret = -ENOMEM;
}
return ret;
}
static struct stale_socket *dequeue_stale_socket(void)
{
struct stale_socket *sock = NULL;
sock = (struct stale_socket *)k_queue_get(&ictx.stale_socket_queue, K_NO_WAIT);
return sock;
}
static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset,
char *time_string);
static int modem_reset_and_configure(void);
static int read_pin(int default_state, const struct gpio_dt_spec *spec)
{
int state = gpio_pin_get_dt(spec);
if (state < 0) {
LOG_ERR("Unable to read port: %s pin: %d status: %d",
spec->port->name, spec->pin, state);
state = default_state;
}
return state;
}
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static bool is_cmd_ready(void)
{
ictx.vgpio_state = read_pin(0, &hl7800_cfg.gpio[MDM_VGPIO]);
ictx.gpio6_state = read_pin(0, &hl7800_cfg.gpio[MDM_GPIO6]);
ictx.cts_state = read_pin(1, &hl7800_cfg.gpio[MDM_UART_CTS]);
return ictx.vgpio_state && ictx.gpio6_state && !ictx.cts_state;
}
#endif
/**
* The definition of awake is that the HL7800
* is ready to receive AT commands successfully
*/
static void check_hl7800_awake(void)
{
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
bool is_cmd_rdy = is_cmd_ready();
if (is_cmd_rdy && (ictx.sleep_state != HL7800_SLEEP_AWAKE) &&
!ictx.allow_sleep && !ictx.wait_for_KSUP) {
PRINT_AWAKE_MSG;
set_sleep_state(HL7800_SLEEP_AWAKE);
k_sem_give(&ictx.mdm_awake);
} else if (!is_cmd_rdy && ictx.sleep_state == HL7800_SLEEP_AWAKE &&
ictx.allow_sleep) {
PRINT_NOT_AWAKE_MSG;
if (ictx.desired_sleep_level == HL7800_SLEEP_HIBERNATE ||
ictx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) {
/* If the device is sleeping (not ready to receive commands)
* then the device may send +KSUP when waking up.
* We should wait for it.
*/
ictx.wait_for_KSUP = true;
ictx.wait_for_KSUP_tries = 0;
set_sleep_state(ictx.desired_sleep_level);
} else if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) {
set_sleep_state(HL7800_SLEEP_SLEEP);
}
}
#endif
}
static int hl7800_RX_lock(void)
{
HL7800_RX_LOCK_DBG_LOG("Locking RX [%p]...", k_current_get());
int rc = k_sem_take(&hl7800_RX_lock_sem, K_FOREVER);
if (rc != 0) {
LOG_ERR("Unable to lock hl7800 (%d)", rc);
} else {
HL7800_RX_LOCK_DBG_LOG("Locked RX [%p]", k_current_get());
}
return rc;
}
static void hl7800_RX_unlock(void)
{
HL7800_RX_LOCK_DBG_LOG("UNLocking RX [%p]...", k_current_get());
k_sem_give(&hl7800_RX_lock_sem);
HL7800_RX_LOCK_DBG_LOG("UNLocked RX [%p]", k_current_get());
}
static bool hl7800_RX_locked(void)
{
if (k_sem_count_get(&hl7800_RX_lock_sem) == 0) {
return true;
} else {
return false;
}
}
static int hl7800_TX_lock(void)
{
HL7800_TX_LOCK_DBG_LOG("Locking TX [%p]...", k_current_get());
int rc = k_sem_take(&hl7800_TX_lock_sem, K_FOREVER);
if (rc != 0) {
LOG_ERR("Unable to lock hl7800 (%d)", rc);
} else {
HL7800_TX_LOCK_DBG_LOG("Locked TX [%p]", k_current_get());
}
return rc;
}
static void hl7800_TX_unlock(void)
{
HL7800_TX_LOCK_DBG_LOG("UNLocking TX [%p]...", k_current_get());
k_sem_give(&hl7800_TX_lock_sem);
HL7800_TX_LOCK_DBG_LOG("UNLocked TX [%p]", k_current_get());
}
static bool hl7800_TX_locked(void)
{
if (k_sem_count_get(&hl7800_TX_lock_sem) == 0) {
return true;
} else {
return false;
}
}
static void hl7800_lock(void)
{
hl7800_TX_lock();
hl7800_RX_lock();
}
static void hl7800_unlock(void)
{
hl7800_RX_unlock();
hl7800_TX_unlock();
}
static struct hl7800_socket *socket_get(void)
{
int i;
struct hl7800_socket *sock = NULL;
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (!ictx.sockets[i].context) {
sock = &ictx.sockets[i];
break;
}
}
return sock;
}
static struct hl7800_socket *socket_from_id(int socket_id)
{
int i;
struct hl7800_socket *sock = NULL;
if (socket_id < 1) {
return NULL;
}
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (ictx.sockets[i].socket_id == socket_id) {
sock = &ictx.sockets[i];
break;
}
}
return sock;
}
static void socket_put(struct hl7800_socket *sock)
{
if (!sock) {
return;
}
sock->context = NULL;
sock->socket_id = -1;
sock->created = false;
sock->reconfig = false;
sock->error = 0;
sock->rx_size = 0;
sock->state = SOCK_IDLE;
(void)memset(&sock->src, 0, sizeof(struct sockaddr));
(void)memset(&sock->dst, 0, sizeof(struct sockaddr));
}
char *hl7800_sprint_ip_addr(const struct sockaddr *addr)
{
static char buf[NET_IPV6_ADDR_LEN];
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf,
sizeof(buf));
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf,
sizeof(buf));
} else
#endif
{
LOG_ERR("Unknown IP address family:%d", addr->sa_family);
return NULL;
}
}
void mdm_hl7800_register_wake_test_point_callback(void (*func)(int state))
{
ictx.wake_up_callback = func;
}
void mdm_hl7800_register_gpio6_callback(void (*func)(int state))
{
ictx.gpio6_callback = func;
}
void mdm_hl7800_register_cts_callback(void (*func)(int state))
{
ictx.cts_callback = func;
}
static void modem_assert_wake(bool assert)
{
int state;
if (assert) {
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> ASSERTED");
state = 1;
} else {
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> NOT_ASSERTED");
state = 0;
}
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_WAKE], state);
if (ictx.wake_up_callback != NULL) {
ictx.wake_up_callback(state);
}
}
static void modem_assert_pwr_on(bool assert)
{
if (assert) {
HL7800_IO_DBG_LOG("MDM_PWR_ON -> ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 1);
} else {
HL7800_IO_DBG_LOG("MDM_PWR_ON -> NOT_ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 0);
}
}
static void modem_assert_fast_shutd(bool assert)
{
if (assert) {
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 1);
} else {
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> NOT_ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 0);
}
}
static void allow_sleep_work_callback(struct k_work *item)
{
ARG_UNUSED(item);
LOG_DBG("Allow sleep");
ictx.allow_sleep = true;
set_sleep_state(ictx.desired_sleep_level);
modem_assert_wake(false);
}
static void allow_sleep(bool allow)
{
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
if (allow) {
k_work_reschedule_for_queue(&hl7800_workq,
&ictx.allow_sleep_work,
K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS));
} else {
LOG_DBG("Keep awake");
k_work_cancel_delayable(&ictx.allow_sleep_work);
ictx.allow_sleep = false;
modem_assert_wake(true);
}
#endif
}
static void event_handler(enum mdm_hl7800_event event, void *event_data)
{
sys_snode_t *node;
struct mdm_hl7800_callback_agent *agent;
k_mutex_lock(&cb_lock, K_FOREVER);
SYS_SLIST_FOR_EACH_NODE(&hl7800_event_callback_list, node) {
agent = CONTAINER_OF(node, struct mdm_hl7800_callback_agent, node);
if (agent->event_callback != NULL) {
agent->event_callback(event, event_data);
}
}
k_mutex_unlock(&cb_lock);
}
void mdm_hl7800_get_signal_quality(int *rsrp, int *sinr)
{
if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS == 0) {
rssi_query();
}
*rsrp = ictx.mdm_rssi;
*sinr = ictx.mdm_sinr;
}
void mdm_hl7800_wakeup(bool wakeup)
{
allow_sleep(!wakeup);
}
/* Send an AT command with a series of response handlers */
static int send_at_cmd(struct hl7800_socket *sock, const uint8_t *data,
k_timeout_t timeout, int retries, bool no_id_resp)
{
int ret = 0;
ictx.last_error = 0;
do {
if (!sock) {
k_sem_reset(&ictx.response_sem);
ictx.last_socket_id = 0;
} else {
sock->error = 0;
k_sem_reset(&sock->sock_send_sem);
ictx.last_socket_id = sock->socket_id;
}
if (no_id_resp) {
strncpy(ictx.no_id_resp_cmd, data,
sizeof(ictx.no_id_resp_cmd) - 1);
ictx.search_no_id_resp = true;
}
LOG_DBG("OUT: [%s]", data);
mdm_receiver_send(&ictx.mdm_ctx, data, strlen(data));
mdm_receiver_send(&ictx.mdm_ctx, "\r", 1);
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
goto done;
}
if (!sock) {
ret = k_sem_take(&ictx.response_sem, timeout);
} else {
ret = k_sem_take(&sock->sock_send_sem, timeout);
}
if (ret == 0) {
if (sock) {
ret = sock->error;
} else {
ret = ictx.last_error;
}
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
retries--;
if (retries < 0) {
retries = 0;
}
} while (ret != 0 && retries > 0);
done:
ictx.search_no_id_resp = false;
return ret;
}
static int wakeup_hl7800(void)
{
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
int ret;
allow_sleep(false);
/* If modem is in sleep mode (not hibernate),
* then it can respond in ~10 ms.
*/
if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) {
k_sleep(MDM_WAKE_TO_CHECK_CTS_DELAY_MS);
}
if (!is_cmd_ready()) {
LOG_DBG("Waiting to wakeup");
ret = k_sem_take(&ictx.mdm_awake, MDM_WAKEUP_TIME);
if (ret) {
LOG_DBG("Err waiting for wakeup: %d", ret);
}
}
#endif
return 0;
}
int32_t mdm_hl7800_send_at_cmd(const uint8_t *data)
{
int ret;
if (!data) {
return -EINVAL;
}
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
ret = send_at_cmd(NULL, data, MDM_CMD_SEND_TIMEOUT, 0, false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
/* The access point name (and username and password) are stored in the modem's
* non-volatile memory.
*/
int32_t mdm_hl7800_update_apn(char *access_point_name)
{
int ret = -EINVAL;
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
ret = write_apn(access_point_name);
allow_sleep(true);
hl7800_unlock();
if (ret >= 0) {
/* After a reset the APN will be re-read from the modem
* and an event will be generated.
*/
k_work_reschedule_for_queue(&hl7800_workq, &ictx.mdm_reset_work,
K_NO_WAIT);
}
return ret;
}
bool mdm_hl7800_valid_rat(uint8_t value)
{
if ((value == MDM_RAT_CAT_M1) || (value == MDM_RAT_CAT_NB1)) {
return true;
}
return false;
}
int32_t mdm_hl7800_update_rat(enum mdm_hl7800_radio_mode value)
{
int ret = -EINVAL;
if (value == ictx.mdm_rat) {
/* The set command will fail (in the modem)
* if the RAT isn't different.
*/
return 0;
} else if (!mdm_hl7800_valid_rat(value)) {
return ret;
}
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
if (value == MDM_RAT_CAT_M1) {
if (ictx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD_LEGACY);
}
} else { /* MDM_RAT_CAT_NB1 */
if (ictx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD_LEGACY);
}
}
error:
if (ret >= 0) {
/* Changing the RAT causes the modem to reset. */
ret = modem_boot_handler("RAT changed");
}
allow_sleep(true);
hl7800_unlock();
/* A reset and reconfigure ensures the modem configuration and
* state are valid
*/
if (ret >= 0) {
k_work_reschedule_for_queue(&hl7800_workq, &ictx.mdm_reset_work,
K_NO_WAIT);
}
return ret;
}
int32_t mdm_hl7800_get_local_time(struct tm *tm, int32_t *offset)
{
int ret;
ictx.local_time_valid = false;
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+CCLK?", MDM_CMD_SEND_TIMEOUT, 0, false);
allow_sleep(true);
if (ictx.local_time_valid) {
memcpy(tm, &ictx.local_time, sizeof(struct tm));
memcpy(offset, &ictx.local_time_offset, sizeof(*offset));
} else {
ret = -EIO;
}
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_get_operator_index(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+KCARRIERCFG?", MDM_CMD_SEND_TIMEOUT, 0,
false);
allow_sleep(true);
hl7800_unlock();
if (ret < 0) {
return ret;
} else {
return ictx.operator_index;
}
}
int32_t mdm_hl7800_get_functionality(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
ictx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+CFUN?", MDM_CMD_SEND_TIMEOUT, 0, false);
allow_sleep(true);
hl7800_unlock();
if (ret < 0) {
return ret;
} else {
return ictx.functionality;
}
}
int32_t mdm_hl7800_set_functionality(enum mdm_hl7800_functionality mode)
{
int ret;
char buf[sizeof("AT+CFUN=###,0")] = { 0 };
hl7800_lock();
wakeup_hl7800();
snprintk(buf, sizeof(buf), "AT+CFUN=%u,0", mode);
ictx.last_socket_id = 0;
ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT,
MDM_DEFAULT_AT_CMD_RETRIES, false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#ifdef CONFIG_MODEM_HL7800_GPS
int32_t mdm_hl7800_set_gps_rate(uint32_t rate)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
ictx.gps_query_location_rate_seconds = rate;
/* Stopping first allows changing the rate between two non-zero values.
* Ignore error if GNSS isn't running.
*/
SEND_AT_CMD_IGNORE_ERROR("AT+GNSSSTOP");
if (rate == 0) {
SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0");
} else {
/* Navigation doesn't work when LTE is on. */
SEND_AT_CMD_EXPECT_OK("AT+CFUN=4,0");
SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=1,1");
if (IS_ENABLED(CONFIG_MODEM_HL7800_USE_GLONASS)) {
SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=10,1");
}
/* Enable all NMEA sentences */
SEND_AT_CMD_EXPECT_OK("AT+GNSSNMEA=0,1000,0,1FF");
/* Enable GPS */
SEND_AT_CMD_EXPECT_OK("AT+GNSSSTART=0");
}
error:
if (rate && ret == 0) {
k_work_reschedule_for_queue(&hl7800_workq, &ictx.gps_work,
K_SECONDS(ictx.gps_query_location_rate_seconds));
} else {
k_work_cancel_delayable(&ictx.gps_work);
}
LOG_DBG("GPS status: %d rate: %u", ret, rate);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#endif /* CONFIG_MODEM_HL7800_GPS */
#ifdef CONFIG_MODEM_HL7800_POLTE
int32_t mdm_hl7800_polte_register(void)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
/* register for events */
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1");
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1");
/* register with polte.io */
SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"REGISTER\"");
error:
LOG_DBG("PoLTE register status: %d", ret);
allow_sleep(true);
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_polte_enable(char *user, char *password)
{
int ret = -1;
char buf[sizeof(MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR) +
MDM_HL7800_MAX_POLTE_USER_ID_SIZE + MDM_HL7800_MAX_POLTE_PASSWORD_SIZE] = { 0 };
hl7800_lock();
wakeup_hl7800();
/* register for events */
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1");
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1");
/* restore user and password (not saved in NV by modem) */
snprintk(buf, sizeof(buf), MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR, user, password);
ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES, false);
error:
LOG_DBG("PoLTE register status: %d", ret);
allow_sleep(true);
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_polte_locate(void)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"LOCATE\",2,1");
error:
LOG_DBG("PoLTE locate status: %d", ret);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#endif /* CONFIG_MODEM_HL7800_POLTE */
/**
* @brief Perform a site survey.
*
*/
int32_t mdm_hl7800_perform_site_survey(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
ret = send_at_cmd(NULL, "at%meas=\"97\"", MDM_CMD_SEND_TIMEOUT, 0, false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
void mdm_hl7800_generate_status_events(void)
{
hl7800_lock();
generate_startup_state_event();
generate_network_state_event();
generate_sleep_state_event();
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
generate_fota_state_event();
#endif
event_handler(HL7800_EVENT_RSSI, &ictx.mdm_rssi);
event_handler(HL7800_EVENT_SINR, &ictx.mdm_sinr);
event_handler(HL7800_EVENT_APN_UPDATE, &ictx.mdm_apn);
event_handler(HL7800_EVENT_RAT, &ictx.mdm_rat);
event_handler(HL7800_EVENT_BANDS, ictx.mdm_bands_string);
event_handler(HL7800_EVENT_ACTIVE_BANDS, ictx.mdm_active_bands_string);
event_handler(HL7800_EVENT_REVISION, ictx.mdm_revision);
hl7800_unlock();
}
uint32_t mdm_hl7800_log_filter_set(uint32_t level)
{
uint32_t new_log_level = 0;
#ifdef CONFIG_LOG
new_log_level =
log_filter_set(NULL, CONFIG_LOG_DOMAIN_ID,
log_source_id_get(STRINGIFY(LOG_MODULE_NAME)),
level);
#endif
return new_log_level;
}
static int send_data(struct hl7800_socket *sock, struct net_pkt *pkt)
{
int ret;
struct net_buf *frag;
char dst_addr[NET_IPV6_ADDR_LEN];
char buf[sizeof("AT+KUDPSND=##,\"" IPV6_ADDR_FORMAT "\",#####,####")];
size_t send_len, actual_send_len;
send_len = 0, actual_send_len = 0;
if (!sock) {
return -EINVAL;
}
sock->error = 0;
sock->state = SOCK_TX;
frag = pkt->frags;
send_len = net_buf_frags_len(frag);
/* start sending data */
k_sem_reset(&sock->sock_send_sem);
if (sock->type == SOCK_STREAM) {
snprintk(buf, sizeof(buf), "AT+KTCPSND=%d,%zu", sock->socket_id,
send_len);
} else {
if (!net_addr_ntop(sock->family, &net_sin(&sock->dst)->sin_addr,
dst_addr, sizeof(dst_addr))) {
LOG_ERR("Invalid dst addr");
return -EINVAL;
}
snprintk(buf, sizeof(buf), "AT+KUDPSND=%d,\"%s\",%u,%zu",
sock->socket_id, dst_addr,
net_sin(&sock->dst)->sin_port, send_len);
}
send_at_cmd(sock, buf, K_NO_WAIT, 0, false);
/* wait for CONNECT or error */
ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT);
if (ret) {
LOG_ERR("Err waiting for CONNECT (%d)", ret);
goto done;
}
/* check for error */
if (sock->error != 0) {
ret = sock->error;
LOG_ERR("AT+K**PSND (%d)", ret);
goto done;
}
/* Loop through packet data and send */
while (frag) {
actual_send_len += frag->len;
mdm_receiver_send(&ictx.mdm_ctx, frag->data, frag->len);
frag = frag->frags;
}
if (actual_send_len != send_len) {
LOG_WRN("AT+K**PSND act: %zd exp: %zd", actual_send_len,
send_len);
}
LOG_DBG("Sent %zu bytes", actual_send_len);
/* Send EOF pattern to terminate data */
k_sem_reset(&sock->sock_send_sem);
mdm_receiver_send(&ictx.mdm_ctx, EOF_PATTERN, strlen(EOF_PATTERN));
ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT);
if (ret == 0) {
ret = sock->error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
done:
if (sock->type == SOCK_STREAM) {
if (sock->error == 0) {
sock->state = SOCK_CONNECTED;
}
} else {
sock->state = SOCK_IDLE;
}
return ret;
}
/*** NET_BUF HELPERS ***/
static bool is_crlf(uint8_t c)
{
if (c == '\n' || c == '\r') {
return true;
} else {
return false;
}
}
static void net_buf_skipcrlf(struct net_buf **buf)
{
/* chop off any /n or /r */
while (*buf && is_crlf(*(*buf)->data)) {
net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
}
static uint16_t net_buf_findcrlf(struct net_buf *buf, struct net_buf **frag)
{
uint16_t len = 0U, pos = 0U;
while (buf && !is_crlf(*(buf->data + pos))) {
if (pos + 1 >= buf->len) {
len += buf->len;
buf = buf->frags;
pos = 0U;
} else {
pos++;
}
}
if (buf && is_crlf(*(buf->data + pos))) {
len += pos;
*frag = buf;
return len;
}
return 0;
}
static uint8_t net_buf_get_u8(struct net_buf **buf)
{
uint8_t val = net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
return val;
}
static uint32_t net_buf_remove(struct net_buf **buf, uint32_t len)
{
uint32_t to_remove;
uint32_t removed = 0;
while (*buf && len > 0) {
to_remove = (*buf)->len;
if (to_remove > len) {
to_remove = len;
}
net_buf_pull(*buf, to_remove);
removed += to_remove;
len -= to_remove;
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
return removed;
}
/*** UDP / TCP Helper Function ***/
/* Setup IP header data to be used by some network applications.
* While much is dummy data, some fields such as dst, port and family are
* important.
* Return the IP + protocol header length.
*/
static int pkt_setup_ip_data(struct net_pkt *pkt, struct hl7800_socket *sock)
{
int hdr_len = 0;
uint16_t src_port = 0U, dst_port = 0U;
#if defined(CONFIG_NET_TCP)
struct net_tcp_hdr *tcp;
#endif
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6) {
if (net_ipv6_create(
pkt,
&((struct sockaddr_in6 *)&sock->dst)->sin6_addr,
&((struct sockaddr_in6 *)&sock->src)->sin6_addr)) {
return -1;
}
src_port = ntohs(net_sin6(&sock->src)->sin6_port);
dst_port = ntohs(net_sin6(&sock->dst)->sin6_port);
hdr_len = sizeof(struct net_ipv6_hdr);
}
#endif
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET) {
if (net_ipv4_create(
pkt, &((struct sockaddr_in *)&sock->dst)->sin_addr,
&((struct sockaddr_in *)&sock->src)->sin_addr)) {
return -1;
}
src_port = ntohs(net_sin(&sock->src)->sin_port);
dst_port = ntohs(net_sin(&sock->dst)->sin_port);
hdr_len = sizeof(struct net_ipv4_hdr);
}
#endif
#if defined(CONFIG_NET_UDP)
if (sock->ip_proto == IPPROTO_UDP) {
if (net_udp_create(pkt, dst_port, src_port)) {
return -1;
}
hdr_len += NET_UDPH_LEN;
}
#endif
#if defined(CONFIG_NET_TCP)
if (sock->ip_proto == IPPROTO_TCP) {
NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr);
tcp = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access);
if (!tcp) {
return -1;
}
(void)memset(tcp, 0, NET_TCPH_LEN);
/* Setup TCP header */
tcp->src_port = dst_port;
tcp->dst_port = src_port;
if (net_pkt_set_data(pkt, &tcp_access)) {
return -1;
}
hdr_len += NET_TCPH_LEN;
}
#endif /* CONFIG_NET_TCP */
return hdr_len;
}
/*** MODEM RESPONSE HANDLERS ***/
static uint32_t wait_for_modem_data(struct net_buf **buf, uint32_t current_len,
uint32_t expected_len)
{
uint32_t waitForDataTries = 0;
while ((current_len < expected_len) &&
(waitForDataTries < MDM_WAIT_FOR_DATA_RETRIES)) {
LOG_DBG("cur:%d, exp:%d", current_len, expected_len);
k_sleep(MDM_WAIT_FOR_DATA_TIME);
current_len += hl7800_read_rx(buf);
waitForDataTries++;
}
return current_len;
}
static uint32_t wait_for_modem_data_and_newline(struct net_buf **buf,
uint32_t current_len,
uint32_t expected_len)
{
return wait_for_modem_data(buf, current_len,
(expected_len + strlen("\r\n")));
}
/* Handler: AT+CGMI */
static bool on_cmd_atcmdinfo_manufacturer(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
int len_no_null = MDM_MANUFACTURER_LENGTH - 1;
/* make sure revision data is received
* waiting for: Sierra Wireless\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_MANUFACTURER_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find mfg end");
goto done;
}
if (len < len_no_null) {
LOG_WRN("mfg too short (len:%d)", len);
} else if (len > len_no_null) {
LOG_WRN("mfg too long (len:%d)", len);
len = MDM_MANUFACTURER_LENGTH;
}
out_len = net_buf_linearize(ictx.mdm_manufacturer,
sizeof(ictx.mdm_manufacturer) - 1, *buf, 0,
len);
ictx.mdm_manufacturer[out_len] = 0;
LOG_INF("Manufacturer: %s", ictx.mdm_manufacturer);
done:
return true;
}
/* Handler: AT+CGMM */
static bool on_cmd_atcmdinfo_model(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
int len_no_null = MDM_MODEL_LENGTH - 1;
/* make sure model data is received
* waiting for: HL7800\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_MODEL_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find model end");
goto done;
}
if (len < len_no_null) {
LOG_WRN("model too short (len:%d)", len);
} else if (len > len_no_null) {
LOG_WRN("model too long (len:%d)", len);
len = MDM_MODEL_LENGTH;
}
out_len = net_buf_linearize(ictx.mdm_model, sizeof(ictx.mdm_model) - 1,
*buf, 0, len);
ictx.mdm_model[out_len] = 0;
LOG_INF("Model: %s", ictx.mdm_model);
done:
return true;
}
/* Handler: AT+CGMR */
static bool on_cmd_atcmdinfo_revision(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* make sure revision data is received
* waiting for something like: AHL7800.1.2.3.1.20171211\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_REVISION_MAX_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find rev end");
goto done;
}
if (len == 0) {
LOG_WRN("revision not found");
} else if (len > MDM_HL7800_REVISION_MAX_STRLEN) {
LOG_WRN("revision too long (len:%d)", len);
len = MDM_HL7800_REVISION_MAX_STRLEN;
}
out_len = net_buf_linearize(
ictx.mdm_revision, sizeof(ictx.mdm_revision) - 1, *buf, 0, len);
ictx.mdm_revision[out_len] = 0;
LOG_INF("Revision: %s", ictx.mdm_revision);
event_handler(HL7800_EVENT_REVISION, ictx.mdm_revision);
done:
return true;
}
/* Handler: AT+CGSN */
static bool on_cmd_atcmdinfo_imei(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* make sure IMEI data is received
* waiting for: ###############\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_IMEI_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find IMEI end");
goto done;
}
if (len < MDM_HL7800_IMEI_STRLEN) {
LOG_WRN("IMEI too short (len:%d)", len);
} else if (len > MDM_HL7800_IMEI_STRLEN) {
LOG_WRN("IMEI too long (len:%d)", len);
len = MDM_HL7800_IMEI_STRLEN;
}
out_len = net_buf_linearize(ictx.mdm_imei, sizeof(ictx.mdm_imei) - 1,
*buf, 0, len);
ictx.mdm_imei[out_len] = 0;
LOG_INF("IMEI: %s", ictx.mdm_imei);
done:
return true;
}
/* Handler: +CCID: <ICCID> */
static bool on_cmd_atcmdinfo_iccid(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* make sure ICCID data is received
* waiting for: <ICCID>\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_ICCID_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find ICCID end");
goto done;
}
if (len > MDM_HL7800_ICCID_STRLEN) {
LOG_WRN("ICCID too long (len:%d)", len);
len = MDM_HL7800_ICCID_STRLEN;
}
out_len = net_buf_linearize(ictx.mdm_iccid, MDM_HL7800_ICCID_STRLEN,
*buf, 0, len);
ictx.mdm_iccid[out_len] = 0;
LOG_INF("ICCID: %s", ictx.mdm_iccid);
done:
return true;
}
static bool on_cmd_atcmdinfo_imsi(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* The handler for the IMSI is based on the command.
* waiting for: <IMSI>\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_IMSI_MIN_STR_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find IMSI end");
goto done;
}
if (len > MDM_HL7800_IMSI_MAX_STRLEN) {
LOG_WRN("IMSI too long (len:%d)", len);
len = MDM_HL7800_IMSI_MAX_STRLEN;
}
out_len = net_buf_linearize(ictx.mdm_imsi, MDM_HL7800_IMSI_MAX_STR_SIZE,
*buf, 0, len);
ictx.mdm_imsi[out_len] = 0;
if (strstr(ictx.mdm_imsi, "ERROR") != NULL) {
LOG_ERR("Unable to read IMSI");
memset(ictx.mdm_imsi, 0, sizeof(ictx.mdm_imsi));
}
LOG_INF("IMSI: %s", ictx.mdm_imsi);
done:
return true;
}
static void dns_work_cb(struct k_work *work)
{
#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES)
int ret;
struct dns_resolve_context *dnsCtx;
static const char *const dns_servers_str[] = { ictx.dns_v4_string,
#ifdef CONFIG_NET_IPV6
ictx.dns_v6_string,
#endif
NULL };
if (ictx.iface && net_if_is_up(ictx.iface)) {
/* set new DNS addr in DNS resolver */
LOG_DBG("Refresh DNS resolver");
dnsCtx = dns_resolve_get_default();
ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL);
if (ret < 0) {
LOG_ERR("dns_resolve_init fail (%d)", ret);
return;
}
}
#endif
}
char *mdm_hl7800_get_iccid(void)
{
return ictx.mdm_iccid;
}
char *mdm_hl7800_get_sn(void)
{
return ictx.mdm_sn;
}
char *mdm_hl7800_get_imei(void)
{
return ictx.mdm_imei;
}
char *mdm_hl7800_get_fw_version(void)
{
return ictx.mdm_revision;
}
char *mdm_hl7800_get_imsi(void)
{
return ictx.mdm_imsi;
}
/* Convert HL7800 IPv6 address string in format
* a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16 to
* an IPv6 address.
*/
static int hl7800_net_addr6_pton(const char *src, struct in6_addr *dst)
{
int num_sections = 8;
int i, len;
uint16_t ipv6_section;
len = strlen(src);
for (i = 0; i < len; i++) {
if (!(src[i] >= '0' && src[i] <= '9') && src[i] != '.') {
return -EINVAL;
}
}
for (i = 0; i < num_sections; i++) {
if (!src || *src == '\0') {
return -EINVAL;
}
ipv6_section = (uint16_t)strtol(src, NULL, 10);
src = strchr(src, '.');
src++;
if (!src || *src == '\0') {
return -EINVAL;
}
ipv6_section = (ipv6_section << 8) | (uint16_t)strtol(src, NULL, 10);
UNALIGNED_PUT(htons(ipv6_section), &dst->s6_addr16[i]);
src = strchr(src, '.');
if (src) {
src++;
} else {
if (i < num_sections - 1) {
return -EINVAL;
}
}
}
return 0;
}
/* Handler: +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr and subnet_mask>,
* <gw_addr>,<DNS_prim_addr>,<DNS_sec_addr>
*/
static bool on_cmd_atcmdinfo_ipaddr(struct net_buf **buf, uint16_t len)
{
int ret;
int num_delims = CGCONTRDP_RESPONSE_NUM_DELIMS;
char *delims[CGCONTRDP_RESPONSE_NUM_DELIMS];
size_t out_len;
char value[MDM_IP_INFO_RESP_SIZE];
char *search_start, *addr_start, *sm_start;
struct in_addr new_ipv4_addr;
struct in6_addr new_ipv6_addr;
bool is_ipv4;
int addr_len;
char temp_addr_str[HL7800_IPV6_ADDR_LEN];
k_timeout_t delay;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
search_start = value;
LOG_DBG("IP info: %s", value);
/* find all delimiters (,) */
for (int i = 0; i < num_delims; i++) {
delims[i] = strchr(search_start, ',');
if (!delims[i]) {
LOG_ERR("Could not find delim %d, val: %s", i,
value);
return true;
}
/* Start next search after current delim location */
search_start = delims[i] + 1;
}
/* determine if IPv4 or IPv6 by checking length of ip address plus
* gateway string.
*/
is_ipv4 = false;
addr_len = delims[3] - delims[2];
LOG_DBG("IP string len: %d", addr_len);
if (addr_len <= (NET_IPV4_ADDR_LEN * 2)) {
is_ipv4 = true;
}
/* Find start of subnet mask */
addr_start = delims[2] + 1;
if (is_ipv4) {
num_delims = 4;
} else {
num_delims = 16;
}
search_start = addr_start;
sm_start = addr_start;
for (int i = 0; i < num_delims; i++) {
sm_start = strchr(search_start, '.');
if (!sm_start) {
LOG_ERR("Could not find submask start");
return true;
}
/* Start next search after current delim location */
search_start = sm_start + 1;
}
/* get new IP addr */
addr_len = sm_start - addr_start;
strncpy(temp_addr_str, addr_start, addr_len);
temp_addr_str[addr_len] = 0;
LOG_DBG("IP addr: %s", temp_addr_str);
if (is_ipv4) {
ret = net_addr_pton(AF_INET, temp_addr_str, &new_ipv4_addr);
} else {
ret = hl7800_net_addr6_pton(temp_addr_str, &new_ipv6_addr);
}
if (ret < 0) {
LOG_ERR("Invalid IP addr");
return true;
}
if (is_ipv4) {
/* move past the '.' */
sm_start += 1;
/* store new subnet mask */
addr_len = delims[3] - sm_start;
strncpy(temp_addr_str, sm_start, addr_len);
temp_addr_str[addr_len] = 0;
ret = net_addr_pton(AF_INET, temp_addr_str, &ictx.subnet);
if (ret < 0) {
LOG_ERR("Invalid subnet");
return true;
}
/* store new gateway */
addr_start = delims[3] + 1;
addr_len = delims[4] - addr_start;
strncpy(temp_addr_str, addr_start, addr_len);
temp_addr_str[addr_len] = 0;
ret = net_addr_pton(AF_INET, temp_addr_str, &ictx.gateway);
if (ret < 0) {
LOG_ERR("Invalid gateway");
return true;
}
}
/* store new dns */
addr_start = delims[4] + 1;
addr_len = delims[5] - addr_start;
if (is_ipv4) {
strncpy(ictx.dns_v4_string, addr_start, addr_len);
ictx.dns_v4_string[addr_len] = 0;
ret = net_addr_pton(AF_INET, ictx.dns_v4_string, &ictx.dns_v4);
LOG_DBG("IPv4 DNS addr: %s", ictx.dns_v4_string);
}
#ifdef CONFIG_NET_IPV6
else {
/* store HL7800 formatted IPv6 DNS string temporarily */
strncpy(ictx.dns_v6_string, addr_start, addr_len);
ret = hl7800_net_addr6_pton(ictx.dns_v6_string, &ictx.dns_v6);
net_addr_ntop(AF_INET6, &ictx.dns_v6, ictx.dns_v6_string,
sizeof(ictx.dns_v6_string));
LOG_DBG("IPv6 DNS addr: %s", ictx.dns_v6_string);
}
#endif
if (ret < 0) {
LOG_ERR("Invalid dns");
return true;
}
if (ictx.iface) {
if (is_ipv4) {
#ifdef CONFIG_NET_IPV4
/* remove the current IPv4 addr before adding a new one.
* We dont care if it is successful or not.
*/
net_if_ipv4_addr_rm(ictx.iface, &ictx.ipv4Addr);
if (!net_if_ipv4_addr_add(ictx.iface, &new_ipv4_addr, NET_ADDR_DHCP, 0)) {
LOG_ERR("Cannot set iface IPv4 addr");
}
net_if_ipv4_set_netmask(ictx.iface, &ictx.subnet);
net_if_ipv4_set_gw(ictx.iface, &ictx.gateway);
#endif
/* store the new IP addr */
net_ipaddr_copy(&ictx.ipv4Addr, &new_ipv4_addr);
} else {
#if CONFIG_NET_IPV6
net_if_ipv6_addr_rm(ictx.iface, &ictx.ipv6Addr);
if (!net_if_ipv6_addr_add(ictx.iface, &new_ipv6_addr, NET_ADDR_AUTOCONF,
0)) {
LOG_ERR("Cannot set iface IPv6 addr");
}
#endif
}
/* start DNS update work */
delay = K_NO_WAIT;
if (!ictx.initialized) {
/* Delay this in case the network
* stack is still starting up
*/
delay = K_SECONDS(DNS_WORK_DELAY_SECS);
}
k_work_reschedule_for_queue(&hl7800_workq, &ictx.dns_work,
delay);
} else {
LOG_ERR("iface NULL");
}
return true;
}
/* Handler1: +COPS: <mode>[,<format>,<oper>[,<AcT>]]
*
* Handler2:
* +COPS: [list of supported (<stat>, long alphanumeric <oper>, short
* alphanumeric <oper>, numeric <oper>[,< AcT>])s][,,
* (list of supported <mode>s),(list of supported <format>s)]
*/
static bool on_cmd_atcmdinfo_operator_status(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
int num_delims = COPS_RESPONSE_NUM_DELIMS;
char *delims[COPS_RESPONSE_NUM_DELIMS];
char *search_start;
int i;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* For AT+COPS=?, result is most likely longer than size of log string */
if (strchr(value, '(') != NULL) {
LOG_HEXDUMP_DBG(value, out_len, "Operator: ");
goto done;
} else {
LOG_INF("Operator: %s", value);
}
/* Process AT+COPS? */
if (len == 1) {
/* only mode was returned, there is no operator info */
ictx.operator_status = NO_OPERATOR;
goto done;
}
search_start = value;
/* find all delimiters (,) */
for (i = 0; i < num_delims; i++) {
delims[i] = strchr(search_start, ',');
if (!delims[i]) {
LOG_ERR("Could not find delim %d, val: %s", i, value);
goto done;
}
/* Start next search after current delim location */
search_start = delims[i] + 1;
}
/* we found both delimiters, that means we have an operator */
ictx.operator_status = REGISTERED;
done:
return true;
}
/* Handler: +KGSN: T5640400011101 */
static bool on_cmd_atcmdinfo_serial_number(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
char value[MDM_SN_RESPONSE_LENGTH];
size_t out_len;
int sn_len;
char *val_start;
/* make sure SN# data is received.
* we are waiting for: +KGSN: ##############\r\n
*/
wait_for_modem_data(buf, net_buf_frags_len(*buf),
MDM_SN_RESPONSE_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find sn end");
goto done;
}
/* get msg data */
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* find ':' */
val_start = strchr(value, ':');
if (!val_start) {
LOG_ERR("Unable to find sn ':'");
goto done;
}
/* Remove ": " chars */
val_start += 2;
sn_len = len - (val_start - value);
if (sn_len < MDM_HL7800_SERIAL_NUMBER_STRLEN) {
LOG_WRN("sn too short (len:%d)", sn_len);
} else if (sn_len > MDM_HL7800_SERIAL_NUMBER_STRLEN) {
LOG_WRN("sn too long (len:%d)", sn_len);
sn_len = MDM_HL7800_SERIAL_NUMBER_STRLEN;
}
strncpy(ictx.mdm_sn, val_start, sn_len);
ictx.mdm_sn[sn_len] = 0;
LOG_INF("Serial #: %s", ictx.mdm_sn);
done:
return true;
}
/* Handler: +KSRAT: # */
static bool on_cmd_radio_tech_status(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
ictx.mdm_rat = strtol(value, NULL, 10);
LOG_INF("+KSRAT: %d", ictx.mdm_rat);
event_handler(HL7800_EVENT_RAT, &ictx.mdm_rat);
return true;
}
/* Handler: +KBNDCFG: #,####################### */
static bool on_cmd_radio_band_configuration(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char n_tmp[sizeof("#########")];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
if (value[0] != (ictx.mdm_rat == MDM_RAT_CAT_M1 ? '0' : '1')) {
/* Invalid RAT */
return true;
} else if (strlen(value) < sizeof("#,###################")) {
/* String size too short */
return true;
}
memcpy(ictx.mdm_bands_string, &value[MDM_TOP_BAND_START_POSITION],
MDM_HL7800_LTE_BAND_STRLEN);
memcpy(n_tmp, &value[MDM_TOP_BAND_START_POSITION], MDM_TOP_BAND_SIZE);
n_tmp[MDM_TOP_BAND_SIZE] = 0;
ictx.mdm_bands_top = strtoul(n_tmp, NULL, 16);
memcpy(n_tmp, &value[MDM_MIDDLE_BAND_START_POSITION],
MDM_MIDDLE_BAND_SIZE);
n_tmp[MDM_MIDDLE_BAND_SIZE] = 0;
ictx.mdm_bands_middle = strtoul(n_tmp, NULL, 16);
memcpy(n_tmp, &value[MDM_BOTTOM_BAND_START_POSITION],
MDM_BOTTOM_BAND_SIZE);
n_tmp[MDM_BOTTOM_BAND_SIZE] = 0;
ictx.mdm_bands_bottom = strtoul(n_tmp, NULL, 16);
LOG_INF("Current band configuration: %04x %08x %08x",
ictx.mdm_bands_top, ictx.mdm_bands_middle,
ictx.mdm_bands_bottom);
return true;
}
/* Handler: +KBND: #,####################### */
static bool on_cmd_radio_active_bands(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
if (strlen(value) < sizeof("#,###################")) {
/* String size too short */
return true;
}
memcpy(ictx.mdm_active_bands_string,
&value[MDM_TOP_BAND_START_POSITION], MDM_HL7800_LTE_BAND_STRLEN);
event_handler(HL7800_EVENT_ACTIVE_BANDS, ictx.mdm_active_bands_string);
return true;
}
static char *get_startup_state_string(enum mdm_hl7800_startup_state state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, READY);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, WAITING_FOR_ACCESS_CODE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIM_NOT_PRESENT);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIMLOCK);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNRECOVERABLE_ERROR);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNKNOWN);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, INACTIVE_SIM);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_startup_state(enum mdm_hl7800_startup_state state)
{
ictx.mdm_startup_state = state;
generate_startup_state_event();
}
static void generate_startup_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = ictx.mdm_startup_state;
event.string = get_startup_state_string(ictx.mdm_startup_state);
LOG_INF("Startup State: %s", event.string);
event_handler(HL7800_EVENT_STARTUP_STATE_CHANGE, &event);
}
int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level)
{
int r = -EPERM;
#if CONFIG_MODEM_HL7800_LOW_POWER_MODE
switch (level) {
case HL7800_SLEEP_AWAKE:
case HL7800_SLEEP_HIBERNATE:
case HL7800_SLEEP_LITE_HIBERNATE:
case HL7800_SLEEP_SLEEP:
ictx.desired_sleep_level = level;
r = 0;
break;
default:
r = -EINVAL;
}
if (r == 0) {
hl7800_lock();
wakeup_hl7800();
r = set_sleep_level();
allow_sleep(true);
hl7800_unlock();
}
#endif
return r;
}
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void initialize_sleep_level(void)
{
if (ictx.desired_sleep_level == HL7800_SLEEP_UNINITIALIZED) {
if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_HIBERNATE)) {
ictx.desired_sleep_level = HL7800_SLEEP_HIBERNATE;
} else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE)) {
ictx.desired_sleep_level = HL7800_SLEEP_LITE_HIBERNATE;
} else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_SLEEP)) {
ictx.desired_sleep_level = HL7800_SLEEP_SLEEP;
} else {
ictx.desired_sleep_level = HL7800_SLEEP_AWAKE;
}
}
}
static int set_sleep_level(void)
{
char cmd[sizeof("AT+KSLEEP=#,#,##")];
static const char SLEEP_CMD_FMT[] = "AT+KSLEEP=%d,%d,%d";
int delay = CONFIG_MODEM_HL7800_SLEEP_DELAY_AFTER_REBOOT;
int ret = 0;
/* AT+KSLEEP= <management>[,<level>[,<delay to sleep after reboot>]]
* management 1 means the HL7800 decides when it enters sleep mode
*/
switch (ictx.desired_sleep_level) {
case HL7800_SLEEP_HIBERNATE:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 2, delay);
break;
case HL7800_SLEEP_LITE_HIBERNATE:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 1, delay);
break;
case HL7800_SLEEP_SLEEP:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 0, delay);
break;
default:
/* don't sleep */
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 2, 0, delay);
break;
}
SEND_AT_CMD_EXPECT_OK(cmd);
error:
return ret;
}
#endif /* CONFIG_MODEM_HL7800_LOW_POWER_MODE */
static char *get_sleep_state_string(enum mdm_hl7800_sleep state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, UNINITIALIZED);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, HIBERNATE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, LITE_HIBERNATE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, SLEEP);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, AWAKE);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_sleep_state(enum mdm_hl7800_sleep state)
{
ictx.sleep_state = state;
if (ictx.sleep_state != HL7800_SLEEP_AWAKE) {
k_sem_reset(&ictx.mdm_awake);
}
generate_sleep_state_event();
}
static void generate_sleep_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = ictx.sleep_state;
event.string = get_sleep_state_string(ictx.sleep_state);
LOG_INF("Sleep State: %s", event.string);
event_handler(HL7800_EVENT_SLEEP_STATE_CHANGE, &event);
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
static char *get_fota_state_string(enum mdm_hl7800_fota_state state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, IDLE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, START);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, WIP);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, PAD);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, SEND_EOT);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, FILE_ERROR);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, INSTALL);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, REBOOT_AND_RECONFIGURE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, COMPLETE);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_fota_state(enum mdm_hl7800_fota_state state)
{
LOG_INF("FOTA state: %s->%s",
get_fota_state_string(ictx.fw_update_state),
get_fota_state_string(state));
ictx.fw_update_state = state;
generate_fota_state_event();
}
static void generate_fota_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = ictx.fw_update_state;
event.string = get_fota_state_string(ictx.fw_update_state);
event_handler(HL7800_EVENT_FOTA_STATE, &event);
}
static void generate_fota_count_event(void)
{
uint32_t count = ictx.fw_packet_count * XMODEM_DATA_SIZE;
event_handler(HL7800_EVENT_FOTA_COUNT, &count);
}
#endif
/* Handler: +KSUP: # */
static bool on_cmd_startup_report(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
memset(value, 0, sizeof(value));
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
if (out_len > 0) {
set_startup_state(strtol(value, NULL, 10));
} else {
set_startup_state(HL7800_STARTUP_STATE_UNKNOWN);
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
if (ictx.fw_updated) {
ictx.fw_updated = false;
set_fota_state(HL7800_FOTA_REBOOT_AND_RECONFIGURE);
/* issue reset after a firmware update to reconfigure modem state */
k_work_reschedule_for_queue(&hl7800_workq, &ictx.mdm_reset_work,
K_NO_WAIT);
} else
#endif
{
PRINT_AWAKE_MSG;
ictx.wait_for_KSUP = false;
ictx.mdm_startup_reporting_on = true;
ictx.reconfig_IP_connection = true;
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
mark_sockets_for_reconfig();
#endif
set_sleep_state(HL7800_SLEEP_AWAKE);
k_sem_give(&ictx.mdm_awake);
}
return true;
}
static bool profile_handler(struct net_buf **buf, uint16_t len,
bool active_profile)
{
uint32_t size;
int echo_state = -1;
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MAX_PROFILE_LINE_LENGTH];
size_t output_length;
/* Prepare net buffer for this parser. */
net_buf_remove(buf, len);
net_buf_skipcrlf(buf);
size = wait_for_modem_data(buf, net_buf_frags_len(*buf),
sizeof(PROFILE_LINE_1));
net_buf_skipcrlf(buf); /* remove any \r\n that are in the front */
/* Parse configuration data to determine if echo is on/off. */
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
/* Echo on off is the first thing on the line: E0, E1 */
if (output_length >= SIZE_WITHOUT_NUL("E?")) {
echo_state = (line[1] == '1') ? 1 : 0;
}
}
LOG_DBG("echo: %d", echo_state);
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
if (active_profile) {
ictx.mdm_echo_is_on = (echo_state != 0);
}
/* Discard next line. This waits for the longest possible response even
* though most registers won't have the value 0xFF. */
size = wait_for_modem_data(buf, net_buf_frags_len(*buf),
sizeof(PROFILE_LINE_2));
net_buf_skipcrlf(buf);
len = net_buf_findcrlf(*buf, &frag);
net_buf_remove(buf, len);
net_buf_skipcrlf(buf);
return false;
}
static bool on_cmd_atcmdinfo_active_profile(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, true);
}
static bool on_cmd_atcmdinfo_stored_profile0(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, false);
}
static bool on_cmd_atcmdinfo_stored_profile1(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, false);
}
/* +WPPP: 1,1,"username","password" */
static bool on_cmd_atcmdinfo_pdp_authentication_cfg(struct net_buf **buf,
uint16_t len)
{
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MDM_HL7800_APN_CMD_MAX_SIZE];
size_t output_length;
size_t i;
char *p;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_APN_CMD_MAX_SIZE);
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
if (output_length > 0) {
memset(ictx.mdm_apn.username, 0,
sizeof(ictx.mdm_apn.username));
memset(ictx.mdm_apn.password, 0,
sizeof(ictx.mdm_apn.password));
i = 0;
p = strchr(line, '"');
if (p != NULL) {
p += 1;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i <
MDM_HL7800_APN_USERNAME_MAX_STRLEN)) {
ictx.mdm_apn.username[i++] = *p++;
}
}
LOG_INF("APN Username: %s",
ictx.mdm_apn.username);
p = strchr(p + 1, '"');
if (p != NULL) {
p += 1;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i <
MDM_HL7800_APN_PASSWORD_MAX_STRLEN)) {
ictx.mdm_apn.password[i++] = *p++;
}
}
LOG_INF("APN Password: %s",
ictx.mdm_apn.password);
}
}
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
return false;
}
/* Only context 1 is used. Other contexts are unhandled.
*
* +CGDCONT: 1,"IP","access point name",,0,0,0,0,0,,0,,,,,
*/
static bool on_cmd_atcmdinfo_pdp_context(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MDM_HL7800_APN_CMD_MAX_SIZE];
size_t output_length;
char *p;
size_t i;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_APN_CMD_MAX_SIZE);
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
if (output_length > 0) {
memset(ictx.mdm_apn.value, 0, sizeof(ictx.mdm_apn.value));
memset(ictx.mdm_pdp_addr_fam, 0, MDM_ADDR_FAM_MAX_LEN);
/* Address family after first , */
p = strchr(line, ',');
if (p == NULL) {
LOG_WRN("Issue parsing APN response");
goto done;
}
p += 2;
i = 0;
while ((p != NULL) && (*p != '"') && (i < MDM_ADDR_FAM_MAX_LEN)) {
ictx.mdm_pdp_addr_fam[i++] = *p++;
}
LOG_DBG("PDP address family: %s", ictx.mdm_pdp_addr_fam);
/* APN after second , " */
p = strchr(p, ',');
if (p == NULL) {
LOG_WRN("Issue parsing APN response");
goto done;
}
p++;
if (*p == ',') {
/* APN is blank */
goto done;
}
if (*p == '"') {
p++;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i < MDM_HL7800_APN_MAX_STRLEN)) {
ictx.mdm_apn.value[i++] = *p++;
}
}
LOG_INF("APN: %s", ictx.mdm_apn.value);
}
}
done:
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
return false;
}
static int hl7800_query_rssi(void)
{
int ret;
ret = send_at_cmd(NULL, "AT+KCELLMEAS=0", MDM_CMD_SEND_TIMEOUT, 1,
false);
if (ret < 0) {
LOG_ERR("AT+KCELLMEAS ret:%d", ret);
}
return ret;
}
static void hl7800_start_rssi_work(void)
{
/* Rate is not checked here to allow one reading
* when going from network down->up
*/
k_work_reschedule_for_queue(&hl7800_workq, &ictx.rssi_query_work,
K_NO_WAIT);
}
static void hl7800_stop_rssi_work(void)
{
int rc;
rc = k_work_cancel_delayable(&ictx.rssi_query_work);
if (rc != 0) {
LOG_ERR("Could not cancel RSSI work [%d]", rc);
}
}
static void rssi_query(void)
{
hl7800_lock();
wakeup_hl7800();
hl7800_query_rssi();
allow_sleep(true);
hl7800_unlock();
}
static void hl7800_rssi_query_work(struct k_work *work)
{
rssi_query();
/* re-start RSSI query work */
if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS > 0) {
k_work_reschedule_for_queue(&hl7800_workq, &ictx.rssi_query_work,
K_SECONDS(CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS));
}
}
#ifdef CONFIG_MODEM_HL7800_GPS
/* Unsolicited notification
* Handler: +GNSSEV: <eventType>,<eventStatus>
*/
static bool on_cmd_gps_event(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char *start = NULL;
char *end = NULL;
int8_t event = -1;
int8_t status = -1;
memset(value, 0, sizeof(value));
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
if (out_len > 0) {
start = value;
event = strtol(start, &end, 10);
if (end == strchr(value, ',')) {
start = end + 1;
status = strtol(start, &end, 10);
}
}
LOG_INF("GPS event: %d status: %d", event, status);
if (event == HL7800_GNSS_EVENT_POSITION) {
event_handler(HL7800_EVENT_GPS_POSITION_STATUS, &status);
}
return true;
}
static void gps_work_callback(struct k_work *work)
{
ARG_UNUSED(work);
int r;
hl7800_lock();
wakeup_hl7800();
r = send_at_cmd(NULL, "AT+GNSSLOC?", MDM_CMD_SEND_TIMEOUT, 1, false);
allow_sleep(true);
hl7800_unlock();
LOG_DBG("GPS location request status: %d", r);
if (ictx.gps_query_location_rate_seconds) {
k_work_reschedule_for_queue(&hl7800_workq, &ictx.gps_work,
K_SECONDS(ictx.gps_query_location_rate_seconds));
}
}
/* The AT+GNSSLOC? command returns 1 of 2 things:
*
* +GNSSLOC:
* Latitude: "49 Deg 10 Min 21.49 Sec N"
* Longitude: "123 Deg 4 Min 14.76 Sec W"
* GpsTime: "yyyy mm dd hh:mm:ss"
* FixType: "2D" or "3D"
* HEPE: "8.485 m" (Horizontal Estimated Position Error)
* Altitude: "-1 m"
* AltUnc: "3.0 m"
* Direction: "0.0 deg"
* HorSpeed: "0.0 m/s"
* VerSpeed: "0.0 m/s"
* OK
*
* OR
*
* +GNSSLOC:
* FIX NOT AVAILABLE
* OK
*
* Since each response is on its own line, the command handler is used
* to handle each one as an individual response.
*/
static bool gps_handler(struct net_buf **buf, uint16_t len,
enum mdm_hl7800_gps_string_types str_type)
{
struct mdm_hl7800_compound_event event;
char gps_str[MDM_HL7800_MAX_GPS_STR_SIZE];
size_t gps_len = sizeof(gps_str) - 1;
struct net_buf *frag = NULL;
size_t out_len;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(gps_str));
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find end");
goto done;
}
if (len > gps_len) {
LOG_WRN("GPS string too long (len:%d)", len);
len = gps_len;
}
out_len = net_buf_linearize(gps_str, gps_len, *buf, 0, len);
gps_str[out_len] = 0;
event.code = str_type;
event.string = gps_str;
event_handler(HL7800_EVENT_GPS, &event);
done:
return true;
}
static bool on_cmd_latitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_LATITUDE);
}
static bool on_cmd_longitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_LONGITUDE);
}
static bool on_cmd_gps_time(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_GPS_TIME);
}
static bool on_cmd_fix_type(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_FIX_TYPE);
}
static bool on_cmd_hepe(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_HEPE);
}
static bool on_cmd_altitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_ALTITUDE);
}
static bool on_cmd_alt_unc(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_ALT_UNC);
}
static bool on_cmd_direction(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_DIRECTION);
}
static bool on_cmd_hor_speed(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_HOR_SPEED);
}
static bool on_cmd_ver_speed(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_VER_SPEED);
}
#endif /* CONFIG_MODEM_HL7800_GPS */
#ifdef CONFIG_MODEM_HL7800_POLTE
/* Handler: %POLTEEVU: "REGISTER",0, <mqttAuthUser>, <mqttAuthPassword> */
static bool on_cmd_polte_registration(struct net_buf **buf, uint16_t len)
{
char rsp[MDM_MAX_RESP_SIZE] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
char *rsp_end = rsp + rsp_len;
struct mdm_hl7800_polte_registration_event_data data;
struct net_buf *frag = NULL;
size_t out_len;
char *location;
bool parsed;
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
location = rsp;
parsed = false;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
/* Command handler looks for string up to the user field */
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
location += 1;
if (location >= rsp_end) {
break;
}
data.user = location;
} else {
break;
}
/* Find end of user field and null terminate string */
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
*location = 0;
location += 1;
if (location >= rsp_end) {
break;
}
} else {
break;
}
location = strstr(location, ",\"");
if (location != NULL && location < rsp_end) {
location += 2;
if (location >= rsp_end) {
break;
}
data.password = location;
} else {
break;
}
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
*location = 0;
} else {
break;
}
parsed = true;
} while (false);
if (parsed && data.user && data.password) {
data.status = 0;
} else {
data.status = -1;
LOG_ERR("Unable to parse PoLTE registration");
}
event_handler(HL7800_EVENT_POLTE_REGISTRATION, &data);
return true;
}
/* Handler: %POLTECMD: "LOCATE",<res> */
static bool on_cmd_polte_locate_cmd_rsp(struct net_buf **buf, uint16_t len)
{
char rsp[sizeof("99")] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
size_t out_len;
struct net_buf *frag = NULL;
struct mdm_hl7800_polte_location_data data;
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
data.status = -1;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
data.status = (uint32_t)strtoul(rsp, NULL, 10);
} while (false);
event_handler(HL7800_EVENT_POLTE_LOCATE_STATUS, &data);
return true;
}
/* Handler:
* %POLTEEVU: "LOCATION",<stat>[,<latitude>,<longitude>,<time>,<confidence>]
*/
static bool on_cmd_polte_location(struct net_buf **buf, uint16_t len)
{
char rsp[MDM_MAX_RESP_SIZE] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
char *rsp_end = rsp + rsp_len;
struct net_buf *frag = NULL;
size_t out_len = 0;
char *start;
char *end;
bool parsed;
struct mdm_hl7800_polte_location_data data;
static const char POLTE_LOC_DELIMITER[] = "\",\"";
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
parsed = false;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
data.status = -1;
start = rsp;
end = "";
/* Comma isn't present when there is an error. */
start = strstr(start, ",");
if (start != NULL && start < rsp_end) {
*start = ' ';
start += 1;
}
data.status = (uint32_t)strtoul(rsp, &end, 10);
if (data.status != 0) {
LOG_WRN("Response not received from PoLTE server: %d", data.status);
data.status = MDM_HL7800_POLTE_SERVER_ERROR;
parsed = true;
break;
} else if (start >= rsp_end) {
break;
}
start = strstr(start, "\"") + 1;
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.latitude, start, MIN(end - start, sizeof(data.latitude) - 1));
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.longitude, start, MIN(end - start, sizeof(data.longitude) - 1));
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
data.timestamp = (uint32_t)strtoul(start, NULL, 10);
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, "\"");
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.confidence_in_meters, start,
MIN(end - start, sizeof(data.confidence_in_meters) - 1));
} else {
break;
}
parsed = true;
} while (false);
if (!parsed) {
LOG_HEXDUMP_ERR(rsp, out_len, "Unable to parse PoLTE location");
} else {
LOG_HEXDUMP_DBG(rsp, out_len, "PoLTE Location");
}
event_handler(HL7800_EVENT_POLTE, &data);
return true;
}
#endif /* CONFIG_MODEM_HL7800_POLTE */
static void notify_all_tcp_sockets_closed(void)
{
int i;
struct hl7800_socket *sock = NULL;