guowenxue
2023-07-10 5b0985618c8a49ea5ff872486672324120e25361
add gpsd program and booster
22 files added
5473 ■■■■■ changed files
gpsd/booster/at-esp32.c 383 ●●●●● patch | view | raw | blame | history
gpsd/booster/at-esp32.h 135 ●●●●● patch | view | raw | blame | history
gpsd/booster/atcmd.c 296 ●●●●● patch | view | raw | blame | history
gpsd/booster/atcmd.h 87 ●●●●● patch | view | raw | blame | history
gpsd/booster/comport.c 515 ●●●●● patch | view | raw | blame | history
gpsd/booster/comport.h 69 ●●●●● patch | view | raw | blame | history
gpsd/booster/dictionary.c 381 ●●●●● patch | view | raw | blame | history
gpsd/booster/dictionary.h 174 ●●●●● patch | view | raw | blame | history
gpsd/booster/esp32.c 161 ●●●●● patch | view | raw | blame | history
gpsd/booster/esp32.h 30 ●●●●● patch | view | raw | blame | history
gpsd/booster/iniparser.c 837 ●●●●● patch | view | raw | blame | history
gpsd/booster/iniparser.h 359 ●●●●● patch | view | raw | blame | history
gpsd/booster/linux_list.h 723 ●●●●● patch | view | raw | blame | history
gpsd/booster/logger.c 276 ●●●●● patch | view | raw | blame | history
gpsd/booster/logger.h 65 ●●●●● patch | view | raw | blame | history
gpsd/booster/makefile 18 ●●●●● patch | view | raw | blame | history
gpsd/booster/ringbuf.c 106 ●●●●● patch | view | raw | blame | history
gpsd/booster/ringbuf.h 57 ●●●●● patch | view | raw | blame | history
gpsd/booster/util_proc.c 432 ●●●●● patch | view | raw | blame | history
gpsd/booster/util_proc.h 67 ●●●●● patch | view | raw | blame | history
gpsd/gpsd.c 245 ●●●●● patch | view | raw | blame | history
gpsd/makefile 57 ●●●●● patch | view | raw | blame | history
gpsd/booster/at-esp32.c
New file
@@ -0,0 +1,383 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#include "logger.h"
#include "at-esp32.h"
/*+------------------------+
 *|    Baisc AT command    |
 *+------------------------+*/
static inline int check_at_ready(comport_t *comport)
{
    int             times = 6;
    int             ready = 0;
    while( times-- )
    {
        if( 0 == send_atcmd_check_ok(comport, "AT", 500) )
        {
            ready = 1;
            break;
        }
    }
    return ready;
}
/* AT command: AT+RST */
int esp32_reset(comport_t *comport)
{
    int             rv;
    rv = send_atcmd(comport, "AT+RST", 5000, "ready", AT_ERRSTR, NULL, 0);
    if( rv < 0)
    {
        log_error("send AT command to reset ESP32 failed, rv=%d\n", rv);
        return -1;
    }
    if( check_at_ready(comport) )
    {
        log_info("send AT to reset ESP32 and AT command ready\n");
        return 0;
    }
    else
    {
        log_info("send AT to reset ESP32 but AT command not ready\n");
        return -3;
    }
}
/* AT command: AT+RESTORE */
int esp32_restore(comport_t *comport)
{
    int             rv;
    //rv = send_atcmd_check_ok(comport, "AT+RESTORE", 5000);
    rv = send_atcmd(comport, "AT+RESTORE", 5000, "ready", AT_ERRSTR, NULL, 0);
    if( rv < 0)
    {
        log_error("send AT command to restore ESP32 failed, rv=%d\n", rv);
        return -1;
    }
    if( check_at_ready(comport) )
    {
        log_info("send AT command to resstore ESP32 ready\n");
        return 0;
    }
    else
    {
        log_error("send AT command to restore ESP32 but AT not ready\n");
        return -3;
    }
}
/* AT command: ATE1 or ATE0 */
int esp32_set_echo(comport_t *comport, int enable)
{
    char           *at = enable? "ATE1" : "ATE0";
    return send_atcmd_check_ok(comport, at, 500);
}
/* AT command: AT+GMR */
int esp32_get_firmware(comport_t *comport, char *version, int size)
{
    if( !version || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+GMR", 2000, version, size);
}
/* AT command: AT+SYSSTORE */
int esp32_set_sysstore(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+SYSSTORE=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1000);
}
/*+------------------------+
 *|    WiFi AT command     |
 *+------------------------+*/
/* AT command: AT+CWMODE */
int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWMODE=%d,%d", mode, autoconn?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTAMAC/CIPAPMAC */
int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac)
{
    if( !mac )
        return -1;
    if( MODE_SOFTAP == mode )
        return send_atcmd_check_request(comport, "AT+CIPAPMAC?", 3000, mac, MAC_LEN);
    else
        return send_atcmd_check_request(comport, "AT+CIPSTAMAC?", 3000, mac, MAC_LEN);
}
/* AT command: AT+CIPSTA/AT+CIPAP  */
int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ip || !gateway )
        return -1;
    if( MODE_SOFTAP == mode )
        snprintf(at, sizeof(at), "AT+CIPAP=\"%s\",\"%s\"", ip, gateway);
    else
        snprintf(at, sizeof(at), "AT+CIPSTA=\"%s\",\"%s\"", ip, gateway);
    return send_atcmd_check_ok(comport, at, 2000);
}
/* AT command: AT+CIPSTA/AT+CIPAP  */
int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
{
    char           *at = MODE_SOFTAP==mode? "AT+CIPAP?" : "AT+CIPSTA?";
    char            buf[ATCMD_REPLY_LEN];
    int             rv;
    if( !ip || !gateway )
        return -1;
    rv = send_atcmd_check_value(comport, at, 3000, buf, sizeof(buf));
    if( rv < 0)
    {
        log_error("AT command query IP address failed, rv=%d\n", rv);
        return rv;
    }
    rv = parser_request_value(buf, "ip", ip, IP_LEN);
    if( rv < 0 )
    {
        log_error("Parser query IP address failed, rv=%d\n", rv);
        return rv;
    }
    rv = parser_request_value(buf, "gateway", gateway, IP_LEN);
    if( rv < 0 )
    {
        log_error("Parser query gateway address failed, rv=%d\n", rv);
        return rv;
    }
    return 0;
}
/* AT command: AT+CWDHCP */
int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWDHCP=%d,%d", enable?1:0, mode);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CWAUTOCONN */
int esp32_set_autoconn(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWAUTOCONN=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CWLAP */
int esp32_list_ap(comport_t *comport, char *buf, int size)
{
    if( !buf || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+CWLAP", 8000, buf, size);
}
/* AT command: AT+CWJAP */
int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ssid || !pwd )
        return -1;
    snprintf(at, sizeof(at), "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
    return send_atcmd_check_ok(comport, at, 15000);
}
/* AT command: AT+CWQAP */
int esp32_disconn_ap(comport_t *comport)
{
    return send_atcmd_check_ok(comport, "AT+CWQAP", 5000);
}
/* AT command: AT+CWSTATE?  status value:
 * - 0: ESP32 station has not started any Wi-Fi connection.
 * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
 * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
 * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
 * - 4: ESP32 station is in Wi-Fi disconnected state
 */
int esp32_join_status(comport_t *comport, int *status, char *ssid)
{
    char            buf[ATCMD_REPLY_LEN];
    int             rv;
    if( !status || !ssid )
        return -1;
    rv = send_atcmd_check_request(comport, "AT+CWSTATE?", 3000, buf, sizeof(buf));
    if( rv < 0)
    {
        log_error("AT command query join status failed, rv=%d\n", rv);
        return rv;
    }
    sscanf(buf, "%d,%s", status, ssid);
    return 0;
}
/* AT command:  AT+PING */
int esp32_ping(comport_t *comport, char *host, int timeout)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !host )
        return -1;
    snprintf(at, sizeof(at), "AT+PING=\"%s\"", host);
    return send_atcmd_check_ok(comport, at, timeout);
}
/* AT command:  AT+CWSAP */
int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ssid || !pwd )
        return -1;
    snprintf(at, sizeof(at), "AT+CWSAP=\"%s\",\"%s\",%d,%d", ssid, pwd, channel, encmode);
    return send_atcmd_check_ok(comport, at, 5000);
}
/* AT command: AT+CWLIF */
int esp32_list_client(comport_t *comport, char *buf, int size)
{
    if( !buf || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+CWLIF", 8000, buf, size);
}
/*+------------------------+
 *|   Socket AT command    |
 *+------------------------+*/
/* AT command: AT+CIPMUX */
int esp32_set_socket_mux(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CIPMUX=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVERMAXCONN */
int esp32_set_socket_clients(comport_t *comport, int max)
{
    char            at[ATCMD_LEN]={'\0'};
    if( max <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVERMAXCONN=%d", max);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTO, timeout unit second */
int esp32_set_socket_timeout(comport_t *comport, int timeout)
{
    char            at[ATCMD_LEN]={'\0'};
    if( timeout <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSTO=%d", timeout);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVER */
int esp32_set_tcp_server(comport_t *comport, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    if( port <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVER=1,%d,\"TCP\"", port);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVER */
int esp32_del_tcp_server(comport_t *comport, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    if( port <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVER=0,%d,\"TCP\"", port);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTART */
int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    char            buf[ATCMD_REPLY_LEN]={'\0'};
    int             keepalive = 60; /* unit second */
    int             rv,linkid = 0;
    if( !host || port <= 0 )
        return -1;
    rv = esp32_set_socket_mux(comport, mux);
    if(rv < 0)
        return rv;
    if( mux )
        snprintf(at, sizeof(at), "AT+CIPSTART=1,\"TCP\",\"%s\",%d,%d", host, port, keepalive);
    else
        snprintf(at, sizeof(at), "AT+CIPSTART=\"TCP\",\"%s\",%d,%d", host, port, keepalive);
    rv = send_atcmd_check_value(comport, at, 1500, buf, sizeof(buf));
    if(rv < 0)
        return rv;
    sscanf(buf, "%d,", &linkid);
    return linkid;
}
gpsd/booster/at-esp32.h
New file
@@ -0,0 +1,135 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#ifndef  _AT_ESP32_H_
#define  _AT_ESP32_H_
#include "atcmd.h"
enum
{
    DISABLE = 0,    /* common disable */
    ENABLE,         /* common enable  */
};
#define MAC_LEN     18 /* 11:22:33:44:55:66 */
#define IP_LEN      16 /* 255.255.255.255 */
/*+------------------------+
 *|    Baisc AT command    |
 *+------------------------+*/
/* AT command: AT+RST */
extern int esp32_reset(comport_t *comport);
/* AT command: AT+RESTORE */
extern int esp32_restore(comport_t *comport);
/* AT command: ATE1 or ATE0 */
extern int esp32_set_echo(comport_t *comport, int enable);
/* AT command: AT+GMR */
extern int esp32_get_firmware(comport_t *comport, char *version, int size);
/* AT command: AT+SYSSTORE */
extern int esp32_set_sysstore(comport_t *comport, int enable);
/*+------------------------+
 *|    WiFi AT command     |
 *+------------------------+*/
typedef enum
{
    MODE_DISABLE=0, /* Wi-Fi RF will be disabled */
    MODE_STATION,   /* Station mode */
    MODE_SOFTAP,    /* SoftAP mode */
    MODE_WDS,       /* Wireless Distribution System: Both Station & SoftAP mode */
} workmode_t;
/* AT command: AT+CWMODE */
extern int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn);
/* AT command: AT+CWDHCP */
extern int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable);
/* AT command: AT+CWAUTOCONN */
extern int esp32_set_autoconn(comport_t *comport, int enable);
/* AT command: AT+CIPSTAMAC/CIPAPMAC */
extern int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac);
/* AT command: AT+CIPSTA/CIPAP */
extern int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
/* AT command: AT+CIPSTA/AT+CIPAP  */
extern int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
/* AT command: AT+CWLAP */
extern int esp32_list_ap(comport_t *comport, char *buf, int size);
/* AT command: AT+CWJAP */
extern int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd);
/* AT command: AT+CWQAP */
extern int esp32_disconn_ap(comport_t *comport);
/* AT command: AT+CWSTATE? , status value:
 * - 0: ESP32 station has not started any Wi-Fi connection.
 * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
 * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
 * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
 * - 4: ESP32 station is in Wi-Fi disconnected state
 */
extern int esp32_join_status(comport_t *comport, int *status, char *ssid);
/* AT command:  AT+PING */
extern int esp32_ping(comport_t *comport, char *host, int timeout);
/* AT command:  AT+CWSAP */
typedef enum
{
    MODE_OPEN,
    /* WEP not support */
    MODE_WPAPSK = 2,
    MODE_WPA2PSK,
    MODE_WPA_WPA2PSK,
} encmode_t;
extern int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode);
/* AT command:  AT+CWLIF */
extern int esp32_list_client(comport_t *comport, char *buf, int size);
/*+------------------------+
 *|   Socket AT command    |
 *+------------------------+*/
/* AT command: CIPMUX */
extern int esp32_set_socket_mux(comport_t *comport, int enable);
/* AT command: AT+CIPSERVERMAXCONN */
extern int esp32_set_socket_clients(comport_t *comport, int max);
/* AT command: AT+CIPSTO, timeout unit second */
extern int esp32_set_socket_timeout(comport_t *comport, int timeout);
/* AT command: AT+CIPSERVER */
extern int esp32_set_tcp_server(comport_t *comport, int port);
/* AT command: AT+CIPSERVER */
extern int esp32_del_tcp_server(comport_t *comport, int port);
/* AT command: AT+CIPSTART */
extern int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port);
/*+------------------------+
 *|     BLE AT command     |
 *+------------------------+*/
// RFU
#endif   /* ----- #ifndef _AT_ESP32_H_  ----- */
gpsd/booster/atcmd.c
New file
@@ -0,0 +1,296 @@
/*
 * Copyright (c) 2023 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#include <ctype.h>
#include "logger.h"
#include "atcmd.h"
/*  Description: this function used to send an AT command from serial port and wait for reply message from
 *               the communication module, and it will return once get expet/error string or timeout.
 *
 *    Arugments:
 *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
 *              $at: the AT command need to be sent, without "\r\n"
 *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
 *          $exepct: the expect string reply from communication module
 *           $error: the error string reply from communication module
 *           $reply: the module reply message output buffer
 *            $size: the output buffer ($reply) size
 *
 * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout
 *
 */
int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size)
{
    int                    i, rv = 0;
    int                    res = ATRES_TIMEOUT;
    int                    bytes = 0;
    char                   buf[ATCMD_REPLY_LEN] = {'\0'};
    char                   atcmd[ATCMD_LEN] = {'\0'};
    if( !comport || !at )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( comport->fd <= 0 )
    {
        log_error("comport[%s] not opened\n");
        return -2;
    }
    /* flushes both data received but not read, and data written but not transmitted in serial port */
    tcflush(comport->fd, TCIOFLUSH);
    snprintf(atcmd, sizeof(atcmd), "%s%s", at, AT_SUFFIX);
    rv=comport_send( comport, atcmd, strlen(atcmd) );
    if(rv < 0)
    {
        log_error("send AT command \"%s\" to \"%s\" failed, rv=%d\n", at, comport->devname, rv);
        return -3;
    }
    res = ATRES_TIMEOUT;
    memset( buf, 0, sizeof(buf) );
    for(i=0; i<timeout/10; i++)
    {
        if( bytes >= sizeof(buf) )
            break;
        rv=comport_recv( comport, buf+bytes, sizeof(buf)-bytes, 10);
        if(rv < 0)
        {
            log_error("send AT command \'%s\' to \'%s\' failed, rv=%d\n", at, comport->devname, rv);
            return -3;
        }
        bytes += rv;
        if( expect && strstr(buf, expect) )
        {
            log_debug("send AT command \"%s\" and got reply \"OK\"\n", at);
            res = ATRES_EXPECT;
            break;
        }
        if( error && strstr(buf, error) )
        {
            log_debug("send AT command \"%s\" and got reply \"ERROR\"\n", at);
            res = ATRES_ERROR;
            break;
        }
    }
    if( bytes > 0 )
        log_trace("AT command reply:%s", buf);
    if( reply && size>0 )
    {
        bytes = strlen(buf)>size ? size : strlen(buf);
        memset(reply, 0, size);
        strncpy(reply, buf, bytes);
        log_debug("copy out AT command \"%s\" reply message: \n%s", at, reply);
    }
    return res;
}
/*
 *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
 *                 Reply:   \r\nOK\r\n
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout)
{
    int                     rv;
    if( !comport || !at )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv=send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, NULL, 0);
    if( ATRES_EXPECT == rv )
    {
        return 0;
    }
    else
    {
        return -2;
    }
}
/*
 *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
 *                  Reply:   \r\nEC20F\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
{
    int                     rv, len;
    char                    buf[ATCMD_REPLY_LEN];
    char                   *ptr_start = buf;
    char                   *ptr_end;
    if( !comport || !at || !reply || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
    if( rv <= 0 )
    {
        return -2;
    }
    /* Skip the echo back command line */
    if( !strncmp(buf, at, strlen(at)) )
    {
        ptr_start=strchr(buf, '\n');
        if( !ptr_start )
        {
            log_error("reply message got wrong\n");
            return -3;
        }
        ptr_start++;   /* skip '\n'  */
    }
    /* find end reply string "\r\nOK\r\n"  */
    ptr_end = strstr(ptr_start, AT_OKSTR);
    if( ptr_end )
    {
        len = ptr_end - ptr_start;
    }
    else
    {
        len = strlen(buf) - (ptr_start-buf);
    }
    memset(reply, 0, size);
    len = len>size ? size : len;
    memcpy(reply, ptr_start, len);
    return 0;
}
/*
 *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int parser_request_value(char *buf, char *key, char *value, int size)
{
    char                   *ptr;
    int                    i = 0;
    if( !buf || !key || !value || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    ptr = strstr(buf, key);
    if( !ptr )
    {
        log_debug("Not found key \"%s\" in %s\n", key, buf);
        return -2;
    }
    ptr=strchr(ptr, ':');  /* found ':' before the value */
    if( !ptr )
    {
        log_debug("Not found ':' before value\n");
        return -3;
    }
    ptr++;   /* skip ':'  */
    if( *ptr == '\"' ) /* skip " */
        ptr++;
    memset(value, 0, size);
    while(*ptr!='\r' && i<size-1)
    {
        if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */
            value[i++] = *ptr;
        ptr ++;
    }
    ptr++; /* skip  */
    return 0;
}
/*
 *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
{
    int                     i = 0;
    int                     rv;
    char                    buf[ATCMD_REPLY_LEN];
    char                   *ptr;
    if( !comport || !at || !reply || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
    if( rv <= 0 )
    {
        return -2;
    }
    ptr=strchr(buf, '+');  /* found '+' before the value */
    if( !ptr )
    {
        log_error("reply message got wrong\n");
        return -3;
    }
    ptr++;   /* skip '+'  */
    ptr=strchr(buf, ':');  /* found ':' before the value */
    if( !ptr )
    {
        log_error("reply message got wrong\n");
        return -3;
    }
    ptr++;   /* skip ':'  */
    if( *ptr == '\"' ) /* skip " */
        ptr++;
    memset(reply, 0, size);
    while(*ptr!='\r' && i<size-1)
    {
        if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */
            reply[i++] = *ptr;
        ptr ++;
    }
    return 0;
}
gpsd/booster/atcmd.h
New file
@@ -0,0 +1,87 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#ifndef  _ATCMD_H_
#define  _ATCMD_H_
#include "comport.h"
/* AT command common reply message max length  */
#define ATCMD_REPLY_LEN          1024 /* Max AT command reply message length */
#define ATCMD_LEN                256 /* Max AT command length */
/* AT command reply message got expect or error string */
#define AT_OKSTR                 "\r\nOK\r\n"     /* expect string always be OK */
#define AT_ERRSTR                "\r\nERROR\r\n"  /* error string always be ERROR */
/* AT command should be end by $AT_SUFFIX */
#define AT_SUFFIX                "\r\n"
/* send_atcmd)( return value status */
enum
{
    ATRES_ERROR,    /* AT command reply got error string, such as "ERROR\r\n" */
    ATRES_EXPECT,   /* AT command reply got expect string, such as "OK\r\n" */
    ATRES_TIMEOUT,  /* AT command not get error/expect string, receive util timeout */
};
/*  Description: this function used to send an AT command from serial port and wait for reply message from
 *               the communication module, and it will return once get expet/error string or timeout.
 *
 *    Arugments:
 *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
 *              $at: the AT command need to be sent, without "\r\n"
 *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
 *          $exepct: the expect string reply from communication module
 *           $error: the error string reply from communication module
 *           $reply: the module reply message output buffer
 *            $size: the output buffer ($reply) size
 *
 * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout
 *
 */
int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size);
/*
 *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
 *                  Reply:   \r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout);
/*
 *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
 *                  Reply:   \r\nEC20F\r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size);
/*
 *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int parser_request_value(char *buf, char *key, char *value, int size);
/*
 *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *repy, int size);
#endif   /* ----- #ifndef _ATCMD_H_  ----- */
gpsd/booster/comport.c
New file
@@ -0,0 +1,515 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#include "comport.h"
#define CONFIG_PRINT_LOGGER
//#define CONFIG_PRINT_STDOUT
#if ( defined CONFIG_PRINT_LOGGER )
#include "logger.h"
#define dbg_print(format,args...) log_error(format, ##args)
#elif ( defined CONFIG_PRINT_STDOUT )
#define dbg_print(format,args...) printf(format, ##args)
#else
#define dbg_print(format,args...) do{} while(0);
#endif
static inline void set_settings(comport_t * comport, const char *settings);
/*
 *  description: Open the serial port
 *
 *   input args: $comport:  corresponding comport point
 *               $dev_name:  The comport device name path, such as '/dev/ttyS3'
 *               $baudrate:  The baudrate, such as 115200
 *               $settings:  The databit,parity,stopbit,flowctrl settings, such as '8N1N'
 *
 * return value: The comport opened file description, <0 means failure
 */
int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings)
{
    int                         rv = -1;
    struct termios              old_cfg, new_cfg;
    int                         old_flags;
    long                        tmp;
    if( !comport || !devname )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    /*+-----------------------+
     *| open the serial port  |
     *+-----------------------+*/
    memset(comport, 0, sizeof(*comport));
    strncpy(comport->devname, devname, sizeof(comport->devname));
    comport->baudrate = baudrate;
    comport->fd = -1;
    comport->fragsize = CONFIG_DEF_FRAGSIZE;
    set_settings(comport, settings);
    if( !strstr(comport->devname, "tty") )
    {
        dbg_print("comport device \"%s\" is not tty device\n", comport->devname);
        return -2;
    }
    comport->fd = open(comport->devname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if( comport->fd<0 )
    {
        dbg_print("comport open \"%s\" failed:%s\n", comport->devname, strerror(errno));
        return -3;
    }
    if(   (-1 != (old_flags = fcntl(comport->fd, F_GETFL, 0)))
       && (-1 != fcntl(comport->fd, F_SETFL, old_flags & ~O_NONBLOCK)) )
    {
        /* Flush input and output */
        tcflush(comport->fd, TCIOFLUSH);
    }
    else
    {
        rv = -4;
        goto CleanUp;
    }
    if (0 != tcgetattr(comport->fd, &old_cfg))
    {
        rv = -5;
        goto CleanUp;
    }
    /*+-----------------------+
     *| configure serial port |
     *+-----------------------+*/
    memset(&new_cfg, 0, sizeof(new_cfg));
    new_cfg.c_cflag &= ~CSIZE;
    new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    new_cfg.c_oflag &= ~(OPOST);
    /* Set the data bit */
    switch (comport->databit)
    {
        case 0x07:
            new_cfg.c_cflag |= CS7;
            break;
        case 0x06:
            new_cfg.c_cflag |= CS6;
            break;
        case 0x05:
            new_cfg.c_cflag |= CS5;
            break;
        default:
            new_cfg.c_cflag |= CS8;
            break;
    }
    /* Set the parity */
    switch (comport->parity)
    {
        case 0x01:    /* Odd */
            new_cfg.c_cflag |= (PARENB | PARODD);
            new_cfg.c_cflag |= (INPCK | ISTRIP);
            break;
        case 0x02:    /* Even */
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;;
            new_cfg.c_cflag |= (INPCK | ISTRIP);
            break;
        case 0x03:
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_cflag &= ~CSTOPB;
            break;
        default:
            new_cfg.c_cflag &= ~PARENB;
    }
    /* Set Stop bit */
    if (0x01 != comport->stopbit)
    {
        new_cfg.c_cflag |= CSTOPB;
    }
    else
    {
        new_cfg.c_cflag &= ~CSTOPB;
    }
    /* Set flow control */
    switch (comport->flowctrl)
    {
        case 1:       /* Software control */
        case 3:
            new_cfg.c_cflag &= ~(CRTSCTS);
            new_cfg.c_iflag |= (IXON | IXOFF);
            break;
        case 2:       /* Hardware control */
            new_cfg.c_cflag |= CRTSCTS;
            new_cfg.c_iflag &= ~(IXON | IXOFF);
            break;
        default:      /* NONE */
            new_cfg.c_cflag &= ~(CRTSCTS);
            new_cfg.c_iflag &= ~(IXON | IXOFF);
            break;
    }
    /* Set baudrate */
    switch (comport->baudrate)
    {
        /* Upper is not POSIX(bits/termios-baud.h) */
        case 4000000:
            tmp = B4000000;
            break;
        case 3500000:
            tmp = B3500000;
            break;
        case 3000000:
            tmp = B3000000;
            break;
        case 2500000:
            tmp = B2500000;
            break;
        case 2000000:
            tmp = B2000000;
            break;
        case 1500000:
            tmp = B1500000;
            break;
        case 1152000:
            tmp = B1152000;
            break;
        case 1000000:
            tmp = B1000000;
            break;
        case 921600:
            tmp = B921600;
            break;
        case 576000:
            tmp = B576000;
            break;
        case 500000:
            tmp = B500000;
            break;
        case 460800:
            tmp = B460800;
            break;
        case 230400:
            tmp = B230400;
            break;
        case 115200:
            tmp = B115200;
            break;
        case 57600:
            tmp = B57600;
            break;
        /* Below is POSIX(bits/termios.h) */
        case 38400:
            tmp = B38400;
            break;
        case 19200:
            tmp = B19200;
            break;
        case 9600:
            tmp = B9600;
            break;
        case 4800:
            tmp = B4800;
            break;
        case 2400:
            tmp = B2400;
            break;
        case 1800:
            tmp = B1800;
            break;
        case 1200:
            tmp = B1200;
            break;
        case 600:
            tmp = B600;
            break;
        case 300:
            tmp = B300;
            break;
        case 200:
            tmp = B200;
            break;
        case 150:
            tmp = B150;
            break;
        case 134:
            tmp = B134;
            break;
        case 110:
            tmp = B110;
            break;
        case 75:
            tmp = B75;
            break;
        case 50:
            tmp = B50;
            break;
        default:
            tmp = B115200;
    }
    cfsetispeed(&new_cfg, tmp);
    cfsetispeed(&new_cfg, tmp);
    /* Set the Com port timeout settings */
    new_cfg.c_cc[VMIN] = 0;
    new_cfg.c_cc[VTIME] = 0;
    tcflush(comport->fd, TCIFLUSH);
    if (0 != tcsetattr(comport->fd, TCSANOW, &new_cfg))
    {
        rv = -6;          // Failed to set device com port settings
        goto CleanUp;
    }
    rv = comport->fd;
CleanUp:
    return rv;
}
/*
 *  description: close comport
 *   input args: $comport:  corresponding comport point
 */
void comport_close(comport_t *comport)
{
    if( !comport )
    {
        dbg_print("invalid input arugments\n");
        return ;
    }
    if ( comport->fd >= 0 )
    {
        close(comport->fd);
    }
    comport->fd = -1;
    return ;
}
/*
 *  description: write $data_bytes $data to $comport
 * return value: 0: write ok  <0: write failure
 */
int comport_send(comport_t *comport, char *data, int data_bytes)
{
    char                       *ptr;
    int                         left, bytes = 0;
    int                         rv = 0;
    if( !comport || !data || data_bytes<=0 )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    if( comport->fd < 0 )
    {
        dbg_print("Serail port not opened\n");
        return -2;
    }
    ptr = data;
    left = data_bytes;
    while( left > 0 )
    {
        /* Large data, then slice them to frag and send */
        bytes = left>comport->fragsize ? comport->fragsize : left;
        rv = write(comport->fd, ptr, bytes);
        if( rv<0 )
        {
            rv = -3;
            break;
        }
        left -= rv;
        ptr += rv;
    }
    return rv;
}
/*
 *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
 * return value: the actual read data bytes, <0: read failure
 */
int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout)
{
    fd_set                      rdfds, exfds;
    struct timeval              to, *to_ptr = NULL;
    int                         ret, rv = 0;
    int                         bytes = 0;
    if ( !comport || !buf || buf_size<=0 )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    if ( comport->fd < 0 )
    {
        dbg_print("Serail port not opened\n");
        return -2;
    }
    memset(buf, 0, buf_size);
    FD_ZERO(&rdfds);
    FD_ZERO(&exfds);
    FD_SET(comport->fd, &rdfds);
    FD_SET(comport->fd, &exfds);
    if( TIMEOUT_NONE != timeout )
    {
        to.tv_sec = (time_t) (timeout / 1000);
        to.tv_usec = (long)(1000 * (timeout % 1000));
        to_ptr = &to;
    }
    while( 1 )
    {
        /* check got data arrive or not */
        ret = select(comport->fd+1, &rdfds, 0, &exfds, to_ptr);
        if( ret<0 )
        {
            /* EINTR means catch interrupt signal */
            dbg_print("comport select() failed: %s\n", strerror(errno));
            rv = EINTR==errno ? 0 : -3;
            break;
        }
        else if( 0 == ret ) /* timeout */
        {
            break;
        }
        /* read data from comport */
        ret = read(comport->fd, buf+bytes, buf_size-bytes);
        if(ret <= 0)
        {
            dbg_print("comport read() failed: %s\n", strerror(errno));
            break;
        }
        bytes += ret;
        if( bytes >= buf_size )
            break;
        /* try to read data in 1ms again, if no data arrive it will break */
        to.tv_sec = 0;
        to.tv_usec = 10000;
        to_ptr = &to;
    }
    if( !rv )
        rv = bytes;
    return rv;
}
/**************************************************************************************
 *  Description: Set the comport databit,parity,stopbit,flowctrl into the comport structure
 *   Input Args: comport: the comport_t pointer
 *               settings: The databit/parity/stopbit/flowctrl settings as like "8N1N"
 *  Output Args: NONE
 * Return Value: NONE
 *************************************************************************************/
static inline void set_settings(comport_t * comport, const char *settings)
{
    if( !settings || !comport )
    {
        dbg_print("invalid input arugments\n");
        return ;
    }
    switch (settings[0])        /* data bit */
    {
        case '7':
            comport->databit = 7;
            break;
        case '8':
        default:
            comport->databit = 8;
            break;
    }
    switch (settings[1])        /* parity */
    {
        case 'O':
        case 'o':
            comport->parity = 1;
            break;
        case 'E':
        case 'e':
            comport->parity = 2;
            break;
        case 'S':
        case 's':
            comport->parity = 3;
            break;
        case 'N':
        case 'n':
        default:
            comport->parity = 0;
            break;
    }
    switch (settings[2])        /* stop bit */
    {
        case '0':
            comport->stopbit = 0;
            break;
        case '1':
        default:
            comport->stopbit = 1;
            break;
    }
    switch (settings[3])        /* flow control */
    {
        case 'S':
        case 's':
            comport->flowctrl = 1;
            break;
        case 'H':
        case 'h':
            comport->flowctrl = 2;
            break;
        case 'B':
        case 'b':
            comport->flowctrl = 3;
            break;
        case 'N':
        case 'n':
        default:
            comport->flowctrl = 0;
            break;
    }
}
gpsd/booster/comport.h
New file
@@ -0,0 +1,69 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#ifndef  _COMPORT_H_
#define  _COMPORT_H_
#include  <stdio.h>
#include  <stdlib.h>
#include  <unistd.h>
#include  <string.h>
#include  <getopt.h>
#include  <fcntl.h>
#include  <errno.h>
#include  <termios.h>
#include  <sys/stat.h>
#include  <sys/wait.h>
#include  <sys/types.h>
#include  <sys/stat.h>
#include  <sys/select.h>
#define CONFIG_DEF_FRAGSIZE    128
typedef struct comport_s
{
    char           devname[32];
    unsigned char  databit, parity, stopbit, flowctrl;
    long           baudrate;
    int            fd;
    int            fragsize; /* frag size when do large data send */
} comport_t;
/*
 *  description: Open the comport and returned by $comport
 *
 *   input args: $comport:  corresponding comport handler
 *               $devname:  The comport device name path, such as '/dev/ttyS3'
 *               $baudrate: The baudrate, such as 115200
 *               $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N'
 *
 * return value: The comport opened file description, <0 means failure
 */
extern int  comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings);
/*
 *  description: close comport
 *   input args: $comport:  corresponding comport handler
 */
extern void comport_close(comport_t *comport);
/*
 *  description: write $bytes $data to $comport
 * return value: 0: write ok  <0: write failure
 */
extern int  comport_send(comport_t *comport, char *data, int data_bytes);
/*
 *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
 * return value: the actual read data bytes, <0: read failure
 */
#define TIMEOUT_NONE           0
extern int  comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout);
#endif
gpsd/booster/dictionary.c
New file
@@ -0,0 +1,381 @@
/*-------------------------------------------------------------------------*/
/**
   @file    dictionary.c
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   @url     https://github.com/ndevilla/iniparser
   This module implements a simple dictionary object, i.e. a list
   of string/string associations. This object is useful to store e.g.
   informations retrieved from a configuration file (ini files).
*/
/*--------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include "dictionary.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/** Maximum value size for integers and doubles. */
#define MAXVALSZ    1024
/** Minimal allocated number of entries in a dictionary */
#define DICTMINSZ   128
/** Invalid key token */
#define DICT_INVALID_KEY    ((char*)-1)
/*---------------------------------------------------------------------------
                            Private functions
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Duplicate a string
  @param    s String to duplicate
  @return   Pointer to a newly allocated string, to be freed with free()
  This is a replacement for strdup(). This implementation is provided
  for systems that do not have it.
 */
/*--------------------------------------------------------------------------*/
static char * xstrdup(const char * s)
{
    char * t ;
    size_t len ;
    if (!s)
        return NULL ;
    len = strlen(s) + 1 ;
    t = (char*) malloc(len) ;
    if (t) {
        memcpy(t, s, len) ;
    }
    return t ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Double the size of the dictionary
  @param    d Dictionary to grow
  @return   This function returns non-zero in case of failure
 */
/*--------------------------------------------------------------------------*/
static int dictionary_grow(dictionary * d)
{
    char        ** new_val ;
    char        ** new_key ;
    unsigned     * new_hash ;
    new_val  = (char**) calloc(d->size * 2, sizeof *d->val);
    new_key  = (char**) calloc(d->size * 2, sizeof *d->key);
    new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash);
    if (!new_val || !new_key || !new_hash) {
        /* An allocation failed, leave the dictionary unchanged */
        if (new_val)
            free(new_val);
        if (new_key)
            free(new_key);
        if (new_hash)
            free(new_hash);
        return -1 ;
    }
    /* Initialize the newly allocated space */
    memcpy(new_val, d->val, d->size * sizeof(char *));
    memcpy(new_key, d->key, d->size * sizeof(char *));
    memcpy(new_hash, d->hash, d->size * sizeof(unsigned));
    /* Delete previous data */
    free(d->val);
    free(d->key);
    free(d->hash);
    /* Actually update the dictionary */
    d->size *= 2 ;
    d->val = new_val;
    d->key = new_key;
    d->hash = new_hash;
    return 0 ;
}
/*---------------------------------------------------------------------------
                            Function codes
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Compute the hash key for a string.
  @param    key     Character string to use for key.
  @return   1 unsigned int on at least 32 bits.
  This hash function has been taken from an Article in Dr Dobbs Journal.
  This is normally a collision-free function, distributing keys evenly.
  The key is stored anyway in the struct so that collision can be avoided
  by comparing the key itself in last resort.
 */
/*--------------------------------------------------------------------------*/
unsigned dictionary_hash(const char * key)
{
    size_t      len ;
    unsigned    hash ;
    size_t      i ;
    if (!key)
        return 0 ;
    len = strlen(key);
    for (hash=0, i=0 ; i<len ; i++) {
        hash += (unsigned)key[i] ;
        hash += (hash<<10);
        hash ^= (hash>>6) ;
    }
    hash += (hash <<3);
    hash ^= (hash >>11);
    hash += (hash <<15);
    return hash ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Create a new dictionary object.
  @param    size    Optional initial size of the dictionary.
  @return   1 newly allocated dictionary object.
  This function allocates a new dictionary object of given size and returns
  it. If you do not know in advance (roughly) the number of entries in the
  dictionary, give size=0.
 */
/*-------------------------------------------------------------------------*/
dictionary * dictionary_new(size_t size)
{
    dictionary  *   d ;
    /* If no size was specified, allocate space for DICTMINSZ */
    if (size<DICTMINSZ) size=DICTMINSZ ;
    d = (dictionary*) calloc(1, sizeof *d) ;
    if (d) {
        d->size = size ;
        d->val  = (char**) calloc(size, sizeof *d->val);
        d->key  = (char**) calloc(size, sizeof *d->key);
        d->hash = (unsigned*) calloc(size, sizeof *d->hash);
    }
    return d ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a dictionary object
  @param    d   dictionary object to deallocate.
  @return   void
  Deallocate a dictionary object and all memory associated to it.
 */
/*--------------------------------------------------------------------------*/
void dictionary_del(dictionary * d)
{
    ssize_t  i ;
    if (d==NULL) return ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]!=NULL)
            free(d->key[i]);
        if (d->val[i]!=NULL)
            free(d->val[i]);
    }
    free(d->val);
    free(d->key);
    free(d->hash);
    free(d);
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get a value from a dictionary.
  @param    d       dictionary object to search.
  @param    key     Key to look for in the dictionary.
  @param    def     Default value to return if key not found.
  @return   1 pointer to internally allocated character string.
  This function locates a key in a dictionary and returns a pointer to its
  value, or the passed 'def' pointer if no such key can be found in
  dictionary. The returned character pointer points to data internal to the
  dictionary object, you should not try to free it or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * dictionary_get(const dictionary * d, const char * key, const char * def)
{
    unsigned    hash ;
    ssize_t      i ;
    hash = dictionary_hash(key);
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        /* Compare hash */
        if (hash==d->hash[i]) {
            /* Compare string, to avoid hash collisions */
            if (!strcmp(key, d->key[i])) {
                return d->val[i] ;
            }
        }
    }
    return def ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Set a value in a dictionary.
  @param    d       dictionary object to modify.
  @param    key     Key to modify or add.
  @param    val     Value to add.
  @return   int     0 if Ok, anything else otherwise
  If the given key is found in the dictionary, the associated value is
  replaced by the provided one. If the key cannot be found in the
  dictionary, it is added to it.
  It is Ok to provide a NULL value for val, but NULL values for the dictionary
  or the key are considered as errors: the function will return immediately
  in such a case.
  Notice that if you dictionary_set a variable to NULL, a call to
  dictionary_get will return a NULL value: the variable will be found, and
  its value (NULL) is returned. In other words, setting the variable
  content to NULL is equivalent to deleting the variable from the
  dictionary. It is not possible (in this implementation) to have a key in
  the dictionary without value.
  This function returns non-zero in case of failure.
 */
/*--------------------------------------------------------------------------*/
int dictionary_set(dictionary * d, const char * key, const char * val)
{
    ssize_t         i ;
    unsigned       hash ;
    if (d==NULL || key==NULL) return -1 ;
    /* Compute hash for this key */
    hash = dictionary_hash(key) ;
    /* Find if value is already in dictionary */
    if (d->n>0) {
        for (i=0 ; i<d->size ; i++) {
            if (d->key[i]==NULL)
                continue ;
            if (hash==d->hash[i]) { /* Same hash value */
                if (!strcmp(key, d->key[i])) {   /* Same key */
                    /* Found a value: modify and return */
                    if (d->val[i]!=NULL)
                        free(d->val[i]);
                    d->val[i] = (val ? xstrdup(val) : NULL);
                    /* Value has been modified: return */
                    return 0 ;
                }
            }
        }
    }
    /* Add a new value */
    /* See if dictionary needs to grow */
    if (d->n==d->size) {
        /* Reached maximum size: reallocate dictionary */
        if (dictionary_grow(d) != 0)
            return -1;
    }
    /* Insert key in the first empty slot. Start at d->n and wrap at
       d->size. Because d->n < d->size this will necessarily
       terminate. */
    for (i=d->n ; d->key[i] ; ) {
        if(++i == d->size) i = 0;
    }
    /* Copy key */
    d->key[i]  = xstrdup(key);
    d->val[i]  = (val ? xstrdup(val) : NULL) ;
    d->hash[i] = hash;
    d->n ++ ;
    return 0 ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a key in a dictionary
  @param    d       dictionary object to modify.
  @param    key     Key to remove.
  @return   void
  This function deletes a key in a dictionary. Nothing is done if the
  key cannot be found.
 */
/*--------------------------------------------------------------------------*/
void dictionary_unset(dictionary * d, const char * key)
{
    unsigned    hash ;
    ssize_t      i ;
    if (key == NULL || d == NULL) {
        return;
    }
    hash = dictionary_hash(key);
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        /* Compare hash */
        if (hash==d->hash[i]) {
            /* Compare string, to avoid hash collisions */
            if (!strcmp(key, d->key[i])) {
                /* Found key */
                break ;
            }
        }
    }
    if (i>=d->size)
        /* Key not found */
        return ;
    free(d->key[i]);
    d->key[i] = NULL ;
    if (d->val[i]!=NULL) {
        free(d->val[i]);
        d->val[i] = NULL ;
    }
    d->hash[i] = 0 ;
    d->n -- ;
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump
  @param    f   Opened file pointer.
  @return   void
  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
  output file pointers.
 */
/*--------------------------------------------------------------------------*/
void dictionary_dump(const dictionary * d, FILE * out)
{
    ssize_t  i ;
    if (d==NULL || out==NULL) return ;
    if (d->n<1) {
        fprintf(out, "empty dictionary\n");
        return ;
    }
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]) {
            fprintf(out, "%20s\t[%s]\n",
                    d->key[i],
                    d->val[i] ? d->val[i] : "UNDEF");
        }
    }
    return ;
}
gpsd/booster/dictionary.h
New file
@@ -0,0 +1,174 @@
/*-------------------------------------------------------------------------*/
/**
   @file    dictionary.h
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   @url     https://github.com/ndevilla/iniparser
   This module implements a simple dictionary object, i.e. a list
   of string/string associations. This object is useful to store e.g.
   informations retrieved from a configuration file (ini files).
*/
/*--------------------------------------------------------------------------*/
#ifndef _DICTIONARY_H_
#define _DICTIONARY_H_
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
/*---------------------------------------------------------------------------
                                New types
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Dictionary object
  This object contains a list of string/string associations. Each
  association is identified by a unique string key. Looking up values
  in the dictionary is speeded up by the use of a (hopefully collision-free)
  hash function.
 */
/*-------------------------------------------------------------------------*/
typedef struct _dictionary_ {
    int             n ;     /** Number of entries in dictionary */
    ssize_t         size ;  /** Storage size */
    char        **  val ;   /** List of string values */
    char        **  key ;   /** List of string keys */
    unsigned     *  hash ;  /** List of hash values for keys */
} dictionary ;
/*---------------------------------------------------------------------------
                            Function prototypes
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Compute the hash key for a string.
  @param    key     Character string to use for key.
  @return   1 unsigned int on at least 32 bits.
  This hash function has been taken from an Article in Dr Dobbs Journal.
  This is normally a collision-free function, distributing keys evenly.
  The key is stored anyway in the struct so that collision can be avoided
  by comparing the key itself in last resort.
 */
/*--------------------------------------------------------------------------*/
unsigned dictionary_hash(const char * key);
/*-------------------------------------------------------------------------*/
/**
  @brief    Create a new dictionary object.
  @param    size    Optional initial size of the dictionary.
  @return   1 newly allocated dictionary object.
  This function allocates a new dictionary object of given size and returns
  it. If you do not know in advance (roughly) the number of entries in the
  dictionary, give size=0.
 */
/*--------------------------------------------------------------------------*/
dictionary * dictionary_new(size_t size);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a dictionary object
  @param    d   dictionary object to deallocate.
  @return   void
  Deallocate a dictionary object and all memory associated to it.
 */
/*--------------------------------------------------------------------------*/
void dictionary_del(dictionary * vd);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get a value from a dictionary.
  @param    d       dictionary object to search.
  @param    key     Key to look for in the dictionary.
  @param    def     Default value to return if key not found.
  @return   1 pointer to internally allocated character string.
  This function locates a key in a dictionary and returns a pointer to its
  value, or the passed 'def' pointer if no such key can be found in
  dictionary. The returned character pointer points to data internal to the
  dictionary object, you should not try to free it or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * dictionary_get(const dictionary * d, const char * key, const char * def);
/*-------------------------------------------------------------------------*/
/**
  @brief    Set a value in a dictionary.
  @param    d       dictionary object to modify.
  @param    key     Key to modify or add.
  @param    val     Value to add.
  @return   int     0 if Ok, anything else otherwise
  If the given key is found in the dictionary, the associated value is
  replaced by the provided one. If the key cannot be found in the
  dictionary, it is added to it.
  It is Ok to provide a NULL value for val, but NULL values for the dictionary
  or the key are considered as errors: the function will return immediately
  in such a case.
  Notice that if you dictionary_set a variable to NULL, a call to
  dictionary_get will return a NULL value: the variable will be found, and
  its value (NULL) is returned. In other words, setting the variable
  content to NULL is equivalent to deleting the variable from the
  dictionary. It is not possible (in this implementation) to have a key in
  the dictionary without value.
  This function returns non-zero in case of failure.
 */
/*--------------------------------------------------------------------------*/
int dictionary_set(dictionary * vd, const char * key, const char * val);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a key in a dictionary
  @param    d       dictionary object to modify.
  @param    key     Key to remove.
  @return   void
  This function deletes a key in a dictionary. Nothing is done if the
  key cannot be found.
 */
/*--------------------------------------------------------------------------*/
void dictionary_unset(dictionary * d, const char * key);
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump
  @param    f   Opened file pointer.
  @return   void
  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
  output file pointers.
 */
/*--------------------------------------------------------------------------*/
void dictionary_dump(const dictionary * d, FILE * out);
#ifdef __cplusplus
}
#endif
#endif
gpsd/booster/esp32.c
New file
@@ -0,0 +1,161 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#include "logger.h"
#include "esp32.h"
int esp32_init_module(comport_t *comport)
{
    int             rv;
    char            version[256] = {0};
    if( !comport )
        return -1;
    rv = esp32_reset(comport);
    if( rv < 0)
    {
        log_error("Reset ESP32 WiFi module failed: %d\n", rv);
        return rv;
    }
    esp32_set_echo(comport, DISABLE);
    esp32_set_sysstore(comport, ENABLE);
    rv = esp32_get_firmware(comport, version, sizeof(version));
    if( rv < 0)
    {
        log_error("Query ESP32 firmware version failed: %d\n", rv);
        return rv;
    }
    log_info("ESP32 firmware version:\n%s\n", version);
    return 0;
}
int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd)
{
    esp32_set_wmode(comport, MODE_SOFTAP, ENABLE);
    esp32_set_ipaddr(comport, MODE_SOFTAP, DEF_SOFTAP_IPADDR, DEF_SOFTAP_IPADDR);
    esp32_set_dhcp(comport, MODE_SOFTAP, ENABLE);
    esp32_set_softap(comport, ssid, pwd, 11, MODE_WPA2PSK);
    return 0;
}
int esp32_join_network(comport_t *comport, char *ssid, char *pwd)
{
    int             rv, status = 0;
    int             times=10;
    char            buf[128] = {0};
    if( !comport )
        return -1;
    esp32_join_status(comport, &status, buf);
    if( 2==status && !strcmp(buf, ssid) )
    {
        log_info("ESP32 connected to \"%s\" already\n", ssid);
        return 0;
    }
    esp32_set_wmode(comport, MODE_STATION, ENABLE);
    esp32_set_dhcp(comport, MODE_STATION, ENABLE);
    rv = esp32_connect_ap(comport, ssid, pwd);
    if( rv < 0 )
    {
        log_error("connect to AP \"%s\" failed, rv=%d", ssid, rv);
        return rv;
    }
    while(times--)
    {
        rv = esp32_join_status(comport, &status, buf);
        if( 2 == status )
        {
            log_info("ESP32 connected to \"%s\" already\n", ssid);
            return 0;
        }
        rv = -3;
    }
    return rv;
}
int esp32_check_network(comport_t *comport)
{
    int             rv, times=5;
    char            ip[IP_LEN] = {0};
    char            gateway[IP_LEN] = {0};
    memset(ip, 0, sizeof(ip));
    memset(gateway, 0, sizeof(gateway));
    rv = esp32_get_ipaddr(comport, MODE_STATION, ip, gateway);
    if( rv<0 )
        return rv;
    if( !strlen(ip) || !strlen(gateway) )
        return -3;
    log_info("IP address: %s, netmask: %s\n", ip, gateway);
    while( times-- )
    {
        if( !(rv=esp32_ping(comport, gateway, 5000)) )
        {
            rv = 0;
            break;
        }
    }
    return 0;
}
int esp32_setup_tcp_server(comport_t *comport, int port)
{
    int             rv;
    rv = esp32_set_socket_mux(comport, ENABLE);
    if(rv<0)
        return rv;
    rv = esp32_set_socket_clients(comport, 2);
    if(rv<0)
        return rv;
    rv = esp32_set_tcp_server(comport, port);
    if(rv<0)
        return rv;
    rv = esp32_set_socket_timeout(comport, 600);
    if(rv<0)
        return rv;
    return 0;
}
int esp32_setup_tcp_client(comport_t *comport, char *host, int port)
{
    int             rv;
    rv = esp32_set_tcp_client(comport, DISABLE, host, port);
    if(rv<0)
        return rv;
    return 0;
}
gpsd/booster/esp32.h
New file
@@ -0,0 +1,30 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#ifndef  _ESP32_H_
#define  _ESP32_H_
#include "at-esp32.h"
#define DEF_SOFTAP_IPADDR    "192.168.8.1"
#define DEF_SOFTAP_SSID      "Router_ESP32"
#define DEF_SOFTAP_PWD       "12345678"
extern int esp32_init_module(comport_t *comport);
extern int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd);
extern int esp32_join_network(comport_t *comport, char *ssid, char *pwd);
extern int esp32_check_network(comport_t *comport);
extern int esp32_setup_tcp_server(comport_t *comport, int port);
extern int esp32_setup_tcp_client(comport_t *comport, char *host, int port);
#endif   /* ----- #ifndef _ESP32_H_  ----- */
gpsd/booster/iniparser.c
New file
@@ -0,0 +1,837 @@
/*-------------------------------------------------------------------------*/
/**
   @file    iniparser.c
   @author  N. Devillard
   @brief   Parser for ini files.
   @url     https://github.com/ndevilla/iniparser
*/
/*--------------------------------------------------------------------------*/
/*---------------------------- Includes ------------------------------------*/
#include <ctype.h>
#include <stdarg.h>
#include "iniparser.h"
/*---------------------------- Defines -------------------------------------*/
#define ASCIILINESZ         (1024)
#define INI_INVALID_KEY     ((char*)-1)
/*---------------------------------------------------------------------------
                        Private to this module
 ---------------------------------------------------------------------------*/
/**
 * This enum stores the status for each parsed line (internal use only).
 */
typedef enum _line_status_ {
    LINE_UNPROCESSED,
    LINE_ERROR,
    LINE_EMPTY,
    LINE_COMMENT,
    LINE_SECTION,
    LINE_VALUE
} line_status ;
/*-------------------------------------------------------------------------*/
/**
  @brief    Convert a string to lowercase.
  @param    in   String to convert.
  @param    out Output buffer.
  @param    len Size of the out buffer.
  @return   ptr to the out buffer or NULL if an error occured.
  This function convert a string into lowercase.
  At most len - 1 elements of the input string will be converted.
 */
/*--------------------------------------------------------------------------*/
static const char * strlwc(const char * in, char *out, unsigned len)
{
    unsigned i ;
    if (in==NULL || out == NULL || len==0) return NULL ;
    i=0 ;
    while (in[i] != '\0' && i < len-1) {
        out[i] = (char)tolower((int)in[i]);
        i++ ;
    }
    out[i] = '\0';
    return out ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Duplicate a string
  @param    s String to duplicate
  @return   Pointer to a newly allocated string, to be freed with free()
  This is a replacement for strdup(). This implementation is provided
  for systems that do not have it.
 */
/*--------------------------------------------------------------------------*/
static char * xstrdup(const char * s)
{
    char * t ;
    size_t len ;
    if (!s)
        return NULL ;
    len = strlen(s) + 1 ;
    t = (char*) malloc(len) ;
    if (t) {
        memcpy(t, s, len) ;
    }
    return t ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Remove blanks at the beginning and the end of a string.
  @param    str  String to parse and alter.
  @return   unsigned New size of the string.
 */
/*--------------------------------------------------------------------------*/
static unsigned strstrip(char * s)
{
    char *last = NULL ;
    char *dest = s;
    if (s==NULL) return 0;
    last = s + strlen(s);
    while (isspace((int)*s) && *s) s++;
    while (last > s) {
        if (!isspace((int)*(last-1)))
            break ;
        last -- ;
    }
    *last = (char)0;
    memmove(dest,s,last - s + 1);
    return last - s;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Default error callback for iniparser: wraps `fprintf(stderr, ...)`.
 */
/*--------------------------------------------------------------------------*/
static int default_error_callback(const char *format, ...)
{
  int ret;
  va_list argptr;
  va_start(argptr, format);
  ret = vfprintf(stderr, format, argptr);
  va_end(argptr);
  return ret;
}
static int (*iniparser_error_callback)(const char*, ...) = default_error_callback;
/*-------------------------------------------------------------------------*/
/**
  @brief    Configure a function to receive the error messages.
  @param    errback  Function to call.
  By default, the error will be printed on stderr. If a null pointer is passed
  as errback the error callback will be switched back to default.
 */
/*--------------------------------------------------------------------------*/
void iniparser_set_error_callback(int (*errback)(const char *, ...))
{
  if (errback) {
    iniparser_error_callback = errback;
  } else {
    iniparser_error_callback = default_error_callback;
  }
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get number of sections in a dictionary
  @param    d   Dictionary to examine
  @return   int Number of sections found in dictionary
  This function returns the number of sections found in a dictionary.
  The test to recognize sections is done on the string stored in the
  dictionary: a section name is given as "section" whereas a key is
  stored as "section:key", thus the test looks for entries that do not
  contain a colon.
  This clearly fails in the case a section name contains a colon, but
  this should simply be avoided.
  This function returns -1 in case of error.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getnsec(const dictionary * d)
{
    int i ;
    int nsec ;
    if (d==NULL) return -1 ;
    nsec=0 ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (strchr(d->key[i], ':')==NULL) {
            nsec ++ ;
        }
    }
    return nsec ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get name for section n in a dictionary.
  @param    d   Dictionary to examine
  @param    n   Section number (from 0 to nsec-1).
  @return   Pointer to char string
  This function locates the n-th section in a dictionary and returns
  its name as a pointer to a string statically allocated inside the
  dictionary. Do not free or modify the returned string!
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getsecname(const dictionary * d, int n)
{
    int i ;
    int foundsec ;
    if (d==NULL || n<0) return NULL ;
    foundsec=0 ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (strchr(d->key[i], ':')==NULL) {
            foundsec++ ;
            if (foundsec>n)
                break ;
        }
    }
    if (foundsec<=n) {
        return NULL ;
    }
    return d->key[i] ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump.
  @param    f   Opened file pointer to dump to.
  @return   void
  This function prints out the contents of a dictionary, one element by
  line, onto the provided file pointer. It is OK to specify @c stderr
  or @c stdout as output files. This function is meant for debugging
  purposes mostly.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump(const dictionary * d, FILE * f)
{
    int     i ;
    if (d==NULL || f==NULL) return ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (d->val[i]!=NULL) {
            fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
        } else {
            fprintf(f, "[%s]=UNDEF\n", d->key[i]);
        }
    }
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary to a loadable ini file
  @param    d   Dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given dictionary into a loadable ini file.
  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump_ini(const dictionary * d, FILE * f)
{
    int          i ;
    int          nsec ;
    const char * secname ;
    if (d==NULL || f==NULL) return ;
    nsec = iniparser_getnsec(d);
    if (nsec<1) {
        /* No section in file: dump all keys as they are */
        for (i=0 ; i<d->size ; i++) {
            if (d->key[i]==NULL)
                continue ;
            fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
        }
        return ;
    }
    for (i=0 ; i<nsec ; i++) {
        secname = iniparser_getsecname(d, i) ;
        iniparser_dumpsection_ini(d, secname, f);
    }
    fprintf(f, "\n");
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary section to a loadable ini file
  @param    d   Dictionary to dump
  @param    s   Section name of dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given section of a given dictionary into a loadable ini
  file.  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
{
    int     j ;
    char    keym[ASCIILINESZ+1];
    int     seclen ;
    if (d==NULL || f==NULL) return ;
    if (! iniparser_find_entry(d, s)) return ;
    seclen  = (int)strlen(s);
    fprintf(f, "\n[%s]\n", s);
    sprintf(keym, "%s:", s);
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1)) {
            fprintf(f,
                    "%-30s = %s\n",
                    d->key[j]+seclen+1,
                    d->val[j] ? d->val[j] : "");
        }
    }
    fprintf(f, "\n");
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d   Dictionary to examine
  @param    s   Section name of dictionary to examine
  @return   Number of keys in section
 */
/*--------------------------------------------------------------------------*/
int iniparser_getsecnkeys(const dictionary * d, const char * s)
{
    int     seclen, nkeys ;
    char    keym[ASCIILINESZ+1];
    int j ;
    nkeys = 0;
    if (d==NULL) return nkeys;
    if (! iniparser_find_entry(d, s)) return nkeys;
    seclen  = (int)strlen(s);
    strlwc(s, keym, sizeof(keym));
    keym[seclen] = ':';
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1))
            nkeys++;
    }
    return nkeys;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d    Dictionary to examine
  @param    s    Section name of dictionary to examine
  @param    keys Already allocated array to store the keys in
  @return   The pointer passed as `keys` argument or NULL in case of error
  This function queries a dictionary and finds all keys in a given section.
  The keys argument should be an array of pointers which size has been
  determined by calling `iniparser_getsecnkeys` function prior to this one.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
 */
/*--------------------------------------------------------------------------*/
const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys)
{
    int i, j, seclen ;
    char keym[ASCIILINESZ+1];
    if (d==NULL || keys==NULL) return NULL;
    if (! iniparser_find_entry(d, s)) return NULL;
    seclen  = (int)strlen(s);
    strlwc(s, keym, sizeof(keym));
    keym[seclen] = ':';
    i = 0;
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1)) {
            keys[i] = d->key[j];
            i++;
        }
    }
    return keys;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key
  @param    d       Dictionary to search
  @param    key     Key string to look for
  @param    def     Default value to return if key not found.
  @return   pointer to statically allocated character string
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the pointer passed as 'def' is returned.
  The returned char pointer is pointing to a string allocated in
  the dictionary, do not free or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getstring(const dictionary * d, const char * key, const char * def)
{
    const char * lc_key ;
    const char * sval ;
    char tmp_str[ASCIILINESZ+1];
    if (d==NULL || key==NULL)
        return def ;
    lc_key = strlwc(key, tmp_str, sizeof(tmp_str));
    sval = dictionary_get(d, lc_key, def);
    return sval ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an long int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   long integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  "42"      ->  42
  "042"     ->  34 (octal -> decimal)
  "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound)
{
    const char * str ;
    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return strtol(str, NULL, 0);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  "42"      ->  42
  "042"     ->  34 (octal -> decimal)
  "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
int iniparser_getint(const dictionary * d, const char * key, int notfound)
{
    return (int)iniparser_getlongint(d, key, notfound);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a double
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   double
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
 */
/*--------------------------------------------------------------------------*/
double iniparser_getdouble(const dictionary * d, const char * key, double notfound)
{
    const char * str ;
    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return atof(str);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a boolean
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  A true boolean is found if one of the following is matched:
  - A string starting with 'y'
  - A string starting with 'Y'
  - A string starting with 't'
  - A string starting with 'T'
  - A string starting with '1'
  A false boolean is found if one of the following is matched:
  - A string starting with 'n'
  - A string starting with 'N'
  - A string starting with 'f'
  - A string starting with 'F'
  - A string starting with '0'
  The notfound value returned if no boolean is identified, does not
  necessarily have to be 0 or 1.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getboolean(const dictionary * d, const char * key, int notfound)
{
    int          ret ;
    const char * c ;
    c = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (c==INI_INVALID_KEY) return notfound ;
    if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
        ret = 1 ;
    } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
        ret = 0 ;
    } else {
        ret = notfound ;
    }
    return ret;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Finds out if a given entry exists in a dictionary
  @param    ini     Dictionary to search
  @param    entry   Name of the entry to look for
  @return   integer 1 if entry exists, 0 otherwise
  Finds out if a given entry exists in the dictionary. Since sections
  are stored as keys with NULL associated values, this is the only way
  of querying for the presence of sections in a dictionary.
 */
/*--------------------------------------------------------------------------*/
int iniparser_find_entry(const dictionary * ini, const char * entry)
{
    int found=0 ;
    if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
        found = 1 ;
    }
    return found ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Set an entry in a dictionary.
  @param    ini     Dictionary to modify.
  @param    entry   Entry to modify (entry name)
  @param    val     New value to associate to the entry.
  @return   int 0 if Ok, -1 otherwise.
  If the given entry can be found in the dictionary, it is modified to
  contain the provided value. If it cannot be found, the entry is created.
  It is Ok to set val to NULL.
 */
/*--------------------------------------------------------------------------*/
int iniparser_set(dictionary * ini, const char * entry, const char * val)
{
    char tmp_str[ASCIILINESZ+1];
    return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete an entry in a dictionary
  @param    ini     Dictionary to modify
  @param    entry   Entry to delete (entry name)
  @return   void
  If the given entry can be found, it is deleted from the dictionary.
 */
/*--------------------------------------------------------------------------*/
void iniparser_unset(dictionary * ini, const char * entry)
{
    char tmp_str[ASCIILINESZ+1];
    dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Load a single line from an INI file
  @param    input_line  Input line, may be concatenated multi-line input
  @param    section     Output space to store section
  @param    key         Output space to store key
  @param    value       Output space to store value
  @return   line_status value
 */
/*--------------------------------------------------------------------------*/
static line_status iniparser_line(
    const char * input_line,
    char * section,
    char * key,
    char * value)
{
    line_status sta ;
    char * line = NULL;
    size_t      len ;
    line = xstrdup(input_line);
    len = strstrip(line);
    sta = LINE_UNPROCESSED ;
    if (len<1) {
        /* Empty line */
        sta = LINE_EMPTY ;
    } else if (line[0]=='#' || line[0]==';') {
        /* Comment line */
        sta = LINE_COMMENT ;
    } else if (line[0]=='[' && line[len-1]==']') {
        /* Section name */
        sscanf(line, "[%[^]]", section);
        strstrip(section);
        strlwc(section, section, len);
        sta = LINE_SECTION ;
    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2) {
        /* Usual key=value with quotes, with or without comments */
        strstrip(key);
        strlwc(key, key, len);
        /* Don't strip spaces from values surrounded with quotes */
        sta = LINE_VALUE ;
    } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
        /* Usual key=value without quotes, with or without comments */
        strstrip(key);
        strlwc(key, key, len);
        strstrip(value);
        /*
         * sscanf cannot handle '' or "" as empty values
         * this is done here
         */
        if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
            value[0]=0 ;
        }
        sta = LINE_VALUE ;
    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
        /*
         * Special cases:
         * key=
         * key=;
         * key=#
         */
        strstrip(key);
        strlwc(key, key, len);
        value[0]=0 ;
        sta = LINE_VALUE ;
    } else {
        /* Generate syntax error */
        sta = LINE_ERROR ;
    }
    free(line);
    return sta ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Parse an ini file and return an allocated dictionary object
  @param    ininame Name of the ini file to read.
  @return   Pointer to newly allocated dictionary
  This is the parser for ini files. This function is called, providing
  the name of the file to be read. It returns a dictionary object that
  should not be accessed directly, but through accessor functions
  instead.
  The returned dictionary must be freed using iniparser_freedict().
 */
/*--------------------------------------------------------------------------*/
dictionary * iniparser_load(const char * ininame)
{
    FILE * in ;
    char line    [ASCIILINESZ+1] ;
    char section [ASCIILINESZ+1] ;
    char key     [ASCIILINESZ+1] ;
    char tmp     [(ASCIILINESZ * 2) + 2] ;
    char val     [ASCIILINESZ+1] ;
    int  last=0 ;
    int  len ;
    int  lineno=0 ;
    int  errs=0;
    int  mem_err=0;
    dictionary * dict ;
    if ((in=fopen(ininame, "r"))==NULL) {
        iniparser_error_callback("iniparser: cannot open %s\n", ininame);
        return NULL ;
    }
    dict = dictionary_new(0) ;
    if (!dict) {
        fclose(in);
        return NULL ;
    }
    memset(line,    0, ASCIILINESZ);
    memset(section, 0, ASCIILINESZ);
    memset(key,     0, ASCIILINESZ);
    memset(val,     0, ASCIILINESZ);
    last=0 ;
    while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
        lineno++ ;
        len = (int)strlen(line)-1;
        if (len<=0)
            continue;
        /* Safety check against buffer overflows */
        if (line[len]!='\n' && !feof(in)) {
            iniparser_error_callback(
              "iniparser: input line too long in %s (%d)\n",
              ininame,
              lineno);
            dictionary_del(dict);
            fclose(in);
            return NULL ;
        }
        /* Get rid of \n and spaces at end of line */
        while ((len>=0) &&
                ((line[len]=='\n') || (isspace(line[len])))) {
            line[len]=0 ;
            len-- ;
        }
        if (len < 0) { /* Line was entirely \n and/or spaces */
            len = 0;
        }
        /* Detect multi-line */
        if (line[len]=='\\') {
            /* Multi-line value */
            last=len ;
            continue ;
        } else {
            last=0 ;
        }
        switch (iniparser_line(line, section, key, val)) {
            case LINE_EMPTY:
            case LINE_COMMENT:
            break ;
            case LINE_SECTION:
            mem_err = dictionary_set(dict, section, NULL);
            break ;
            case LINE_VALUE:
            sprintf(tmp, "%s:%s", section, key);
            mem_err = dictionary_set(dict, tmp, val);
            break ;
            case LINE_ERROR:
            iniparser_error_callback(
              "iniparser: syntax error in %s (%d):\n-> %s\n",
              ininame,
              lineno,
              line);
            errs++ ;
            break;
            default:
            break ;
        }
        memset(line, 0, ASCIILINESZ);
        last=0;
        if (mem_err<0) {
            iniparser_error_callback("iniparser: memory allocation failure\n");
            break ;
        }
    }
    if (errs) {
        dictionary_del(dict);
        dict = NULL ;
    }
    fclose(in);
    return dict ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Free all memory associated to an ini dictionary
  @param    d Dictionary to free
  @return   void
  Free all memory associated to an ini dictionary.
  It is mandatory to call this function before the dictionary object
  gets out of the current context.
 */
/*--------------------------------------------------------------------------*/
void iniparser_freedict(dictionary * d)
{
    dictionary_del(d);
}
gpsd/booster/iniparser.h
New file
@@ -0,0 +1,359 @@
/*-------------------------------------------------------------------------*/
/**
   @file    iniparser.h
   @author  N. Devillard
   @brief   Parser for ini files.
   @url     https://github.com/ndevilla/iniparser
*/
/*--------------------------------------------------------------------------*/
#ifndef _INIPARSER_H_
#define _INIPARSER_H_
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
 * The following #include is necessary on many Unixes but not Linux.
 * It is not needed for Windows platforms.
 * Uncomment it if needed.
 */
/* #include <unistd.h> */
#include "dictionary.h"
#ifdef __cplusplus
extern "C" {
#endif
/*-------------------------------------------------------------------------*/
/**
  @brief    Configure a function to receive the error messages.
  @param    errback  Function to call.
  By default, the error will be printed on stderr. If a null pointer is passed
  as errback the error callback will be switched back to default.
 */
/*--------------------------------------------------------------------------*/
void iniparser_set_error_callback(int (*errback)(const char *, ...));
/*-------------------------------------------------------------------------*/
/**
  @brief    Get number of sections in a dictionary
  @param    d   Dictionary to examine
  @return   int Number of sections found in dictionary
  This function returns the number of sections found in a dictionary.
  The test to recognize sections is done on the string stored in the
  dictionary: a section name is given as "section" whereas a key is
  stored as "section:key", thus the test looks for entries that do not
  contain a colon.
  This clearly fails in the case a section name contains a colon, but
  this should simply be avoided.
  This function returns -1 in case of error.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getnsec(const dictionary * d);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get name for section n in a dictionary.
  @param    d   Dictionary to examine
  @param    n   Section number (from 0 to nsec-1).
  @return   Pointer to char string
  This function locates the n-th section in a dictionary and returns
  its name as a pointer to a string statically allocated inside the
  dictionary. Do not free or modify the returned string!
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getsecname(const dictionary * d, int n);
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary to a loadable ini file
  @param    d   Dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given dictionary into a loadable ini file.
  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump_ini(const dictionary * d, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary section to a loadable ini file
  @param    d   Dictionary to dump
  @param    s   Section name of dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given section of a given dictionary into a loadable ini
  file.  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump.
  @param    f   Opened file pointer to dump to.
  @return   void
  This function prints out the contents of a dictionary, one element by
  line, onto the provided file pointer. It is OK to specify @c stderr
  or @c stdout as output files. This function is meant for debugging
  purposes mostly.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump(const dictionary * d, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d   Dictionary to examine
  @param    s   Section name of dictionary to examine
  @return   Number of keys in section
 */
/*--------------------------------------------------------------------------*/
int iniparser_getsecnkeys(const dictionary * d, const char * s);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d    Dictionary to examine
  @param    s    Section name of dictionary to examine
  @param    keys Already allocated array to store the keys in
  @return   The pointer passed as `keys` argument or NULL in case of error
  This function queries a dictionary and finds all keys in a given section.
  The keys argument should be an array of pointers which size has been
  determined by calling `iniparser_getsecnkeys` function prior to this one.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
 */
/*--------------------------------------------------------------------------*/
const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key
  @param    d       Dictionary to search
  @param    key     Key string to look for
  @param    def     Default value to return if key not found.
  @return   pointer to statically allocated character string
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the pointer passed as 'def' is returned.
  The returned char pointer is pointing to a string allocated in
  the dictionary, do not free or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getstring(const dictionary * d, const char * key, const char * def);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  - "42"      ->  42
  - "042"     ->  34 (octal -> decimal)
  - "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
int iniparser_getint(const dictionary * d, const char * key, int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an long int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  - "42"      ->  42
  - "042"     ->  34 (octal -> decimal)
  - "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
 */
/*--------------------------------------------------------------------------*/
long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a double
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   double
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
 */
/*--------------------------------------------------------------------------*/
double iniparser_getdouble(const dictionary * d, const char * key, double notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a boolean
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  A true boolean is found if one of the following is matched:
  - A string starting with 'y'
  - A string starting with 'Y'
  - A string starting with 't'
  - A string starting with 'T'
  - A string starting with '1'
  A false boolean is found if one of the following is matched:
  - A string starting with 'n'
  - A string starting with 'N'
  - A string starting with 'f'
  - A string starting with 'F'
  - A string starting with '0'
  The notfound value returned if no boolean is identified, does not
  necessarily have to be 0 or 1.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getboolean(const dictionary * d, const char * key, int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Set an entry in a dictionary.
  @param    ini     Dictionary to modify.
  @param    entry   Entry to modify (entry name)
  @param    val     New value to associate to the entry.
  @return   int     0 if Ok, -1 otherwise.
  If the given entry can be found in the dictionary, it is modified to
  contain the provided value. If it cannot be found, the entry is created.
  It is Ok to set val to NULL.
 */
/*--------------------------------------------------------------------------*/
int iniparser_set(dictionary * ini, const char * entry, const char * val);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete an entry in a dictionary
  @param    ini     Dictionary to modify
  @param    entry   Entry to delete (entry name)
  @return   void
  If the given entry can be found, it is deleted from the dictionary.
 */
/*--------------------------------------------------------------------------*/
void iniparser_unset(dictionary * ini, const char * entry);
/*-------------------------------------------------------------------------*/
/**
  @brief    Finds out if a given entry exists in a dictionary
  @param    ini     Dictionary to search
  @param    entry   Name of the entry to look for
  @return   integer 1 if entry exists, 0 otherwise
  Finds out if a given entry exists in the dictionary. Since sections
  are stored as keys with NULL associated values, this is the only way
  of querying for the presence of sections in a dictionary.
 */
/*--------------------------------------------------------------------------*/
int iniparser_find_entry(const dictionary * ini, const char * entry) ;
/*-------------------------------------------------------------------------*/
/**
  @brief    Parse an ini file and return an allocated dictionary object
  @param    ininame Name of the ini file to read.
  @return   Pointer to newly allocated dictionary
  This is the parser for ini files. This function is called, providing
  the name of the file to be read. It returns a dictionary object that
  should not be accessed directly, but through accessor functions
  instead.
  The returned dictionary must be freed using iniparser_freedict().
 */
/*--------------------------------------------------------------------------*/
dictionary * iniparser_load(const char * ininame);
/*-------------------------------------------------------------------------*/
/**
  @brief    Free all memory associated to an ini dictionary
  @param    d Dictionary to free
  @return   void
  Free all memory associated to an ini dictionary.
  It is mandatory to call this function before the dictionary object
  gets out of the current context.
 */
/*--------------------------------------------------------------------------*/
void iniparser_freedict(dictionary * d);
#ifdef __cplusplus
}
#endif
#endif
gpsd/booster/linux_list.h
New file
@@ -0,0 +1,723 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  linux_list.h
 *    Description:  This file is copied from Linux kernel, which provide link list API.
 *
 *        Version:  1.0.0(08/09/2020)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "08/09/2020 02:24:34 AM"
 *
 ********************************************************************************/
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <linux/stddef.h>
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
/*
 * Architectures might want to move the poison pointer offset
 * into some well-recognized area such as 0xdead000000000000,
 * that is also not mappable by user-space exploits:
 */
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
#ifndef ARCH_HAS_PREFETCH
#define ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif
/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */
struct list_head {
    struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}
/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}
/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}
/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}
/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
                struct list_head *new)
{
    new->next = old->next;
    new->next->prev = new;
    new->prev = old->prev;
    new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
                    struct list_head *new)
{
    list_replace(old, new);
    INIT_LIST_HEAD(old);
}
/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static inline void list_del_init(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    INIT_LIST_HEAD(entry);
}
/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del(list->prev, list->next);
    list_add(list, head);
}
/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
{
    __list_del(list->prev, list->next);
    list_add_tail(list, head);
}
/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                const struct list_head *head)
{
    return list->next == head;
}
/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
    return head->next == head;
}
/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}
/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
    return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    struct list_head *new_first = entry->next;
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry;
    entry->next = list;
    head->next = new_first;
    new_first->prev = head;
}
/**
 * list_cut_position - cut a list into two
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *    and if so we won't cut the list
 *
 * This helper moves the initial part of @head, up to and
 * including @entry, from @head to @list. You should
 * pass on @entry an element you know is on @head. @list
 * should be an empty list or a list you do not care about
 * losing its data.
 *
 */
static inline void list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    if (list_empty(head))
        return;
    if (list_is_singular(head) &&
        (head->next != entry && head != entry))
        return;
    if (entry == head)
        INIT_LIST_HEAD(list);
    else
        __list_cut_position(list, head, entry);
}
static inline void __list_splice(const struct list_head *list,
                 struct list_head *prev,
                 struct list_head *next)
{
    struct list_head *first = list->next;
    struct list_head *last = list->prev;
    first->prev = prev;
    prev->next = first;
    last->next = next;
    next->prev = last;
}
/**
 * list_splice - join two lists, this is designed for stacks
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(const struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head, head->next);
}
/**
 * list_splice_tail - join two lists, each list being a queue
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice_tail(struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head->prev, head);
}
/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}
/**
 * list_splice_tail_init - join two lists and reinitialise the emptied list
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * Each of the lists is a queue.
 * The list at @list is reinitialised
 */
static inline void list_splice_tail_init(struct list_head *list,
                     struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}
/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:    the type of the struct this is embedded in.
 * @member:    the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
/**
 * list_first_entry - get the first element from a list
 * @ptr:    the list head to take the element from.
 * @type:    the type of the struct this is embedded in.
 * @member:    the name of the list_struct within the struct.
 *
 * Note, that list is expected to be not empty.
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)
/**
 * list_for_each    -    iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        pos = pos->next)
/**
 * __list_for_each    -    iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 *
 * This variant differs from list_for_each() in that it's the
 * simplest possible list iteration code, no prefetching is done.
 * Use this for code that knows the list to be very short (empty
 * or 1 entry) most of the time.
 */
#define __list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)
/**
 * list_for_each_prev    -    iterate over a list backwards
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 */
#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
        pos = pos->prev)
/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:        another &struct list_head to use as temporary storage
 * @head:    the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)
/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:        another &struct list_head to use as temporary storage
 * @head:    the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; \
         prefetch(pos->prev), pos != (head); \
         pos = n, n = pos->prev)
/**
 * list_for_each_entry    -    iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)                \
    for (pos = list_entry((head)->next, typeof(*pos), member);    \
         prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_reverse - iterate backwards over list of given type.
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry_reverse(pos, head, member)            \
    for (pos = list_entry((head)->prev, typeof(*pos), member);    \
         prefetch(pos->member.prev), &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
 * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
 * @pos:    the type * to use as a start point
 * @head:    the head of the list
 * @member:    the name of the list_struct within the struct.
 *
 * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
 */
#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))
/**
 * list_for_each_entry_continue - continue iteration over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Continue to iterate over list of given type, continuing after
 * the current position.
 */
#define list_for_each_entry_continue(pos, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member);    \
         prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_continue_reverse - iterate backwards from the given point
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Start to iterate over list of given type backwards, continuing after
 * the current position.
 */
#define list_for_each_entry_continue_reverse(pos, head, member)        \
    for (pos = list_entry(pos->member.prev, typeof(*pos), member);    \
         prefetch(pos->member.prev), &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
 * list_for_each_entry_from - iterate over list of given type from the current point
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing from current position.
 */
#define list_for_each_entry_from(pos, head, member)            \
    for (; prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry_safe(pos, n, head, member)            \
    for (pos = list_entry((head)->next, typeof(*pos), member),    \
        n = list_entry(pos->member.next, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_continue
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing after current point,
 * safe against removal of list entry.
 */
#define list_for_each_entry_safe_continue(pos, n, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member),        \
        n = list_entry(pos->member.next, typeof(*pos), member);        \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_from
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type from current point, safe against
 * removal of list entry.
 */
#define list_for_each_entry_safe_from(pos, n, head, member)            \
    for (n = list_entry(pos->member.next, typeof(*pos), member);        \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_reverse
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate backwards over list of given type, safe against removal
 * of list entry.
 */
#define list_for_each_entry_safe_reverse(pos, n, head, member)        \
    for (pos = list_entry((head)->prev, typeof(*pos), member),    \
        n = list_entry(pos->member.prev, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/*
 * Double linked lists with a single pointer list head.
 * Mostly useful for hash tables where the two pointer list head is
 * too wasteful.
 * You lose the ability to access the tail in O(1).
 */
struct hlist_head {
    struct hlist_node *first;
};
struct hlist_node {
    struct hlist_node *next, **pprev;
};
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
    h->next = NULL;
    h->pprev = NULL;
}
static inline int hlist_unhashed(const struct hlist_node *h)
{
    return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h)
{
    return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
    struct hlist_node *next = n->next;
    struct hlist_node **pprev = n->pprev;
    *pprev = next;
    if (next)
        next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
    __hlist_del(n);
    n->next = LIST_POISON1;
    n->pprev = LIST_POISON2;
}
static inline void hlist_del_init(struct hlist_node *n)
{
    if (!hlist_unhashed(n)) {
        __hlist_del(n);
        INIT_HLIST_NODE(n);
    }
}
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
    struct hlist_node *first = h->first;
    n->next = first;
    if (first)
        first->pprev = &n->next;
    h->first = n;
    n->pprev = &h->first;
}
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node *n,
                    struct hlist_node *next)
{
    n->pprev = next->pprev;
    n->next = next;
    next->pprev = &n->next;
    *(n->pprev) = n;
}
static inline void hlist_add_after(struct hlist_node *n,
                    struct hlist_node *next)
{
    next->next = n->next;
    n->next = next;
    next->pprev = &n->next;
    if(next->next)
        next->next->pprev  = &next->next;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
    for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
         pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
    for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
         pos = n)
/**
 * hlist_for_each_entry    - iterate over list of given type
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry(tpos, pos, head, member)             \
    for (pos = (head)->first;                     \
         pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_continue(tpos, pos, member)         \
    for (pos = (pos)->next;                         \
         pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_from - iterate over a hlist continuing from current point
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_from(tpos, pos, member)             \
    for (; pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @n:        another &struct hlist_node to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_safe(tpos, pos, n, head, member)         \
    for (pos = (head)->first;                     \
         pos && ({ n = pos->next; 1; }) &&                 \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = n)
#endif
gpsd/booster/logger.c
New file
@@ -0,0 +1,276 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>
#include "logger.h"
typedef void (*log_LockFn)(void *udata, int lock);
static struct {
    char        file[32]; /* logger file name */
    FILE       *fp;       /* logger file pointer */
    long        size;     /* logger file max size */
    int         level;    /* logger level */
    log_LockFn  lockfn;   /* lock function */
    void       *udata;    /* lock data */
} L;
static const char *level_names[] = {
    "FATAL",
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
};
static const char *level_colors[] = {
    "\x1b[35m",
    "\x1b[31m",
    "\x1b[33m",
    "\x1b[32m",
    "\x1b[36m",
    "\x1b[94m"
};
static inline void time_to_str(char *buf)
{
    struct timeval   tv;
    struct tm       *tm;
    int              len;
    gettimeofday(&tv, NULL);
    tm = localtime(&tv.tv_sec);
    len = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%06d ",
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec);
    buf[len] = '\0';
}
static void mutex_lock(void *udata, int lock)
{
    int              err;
    pthread_mutex_t *l = (pthread_mutex_t *) udata;
    if (lock)
    {
        if ( (err = pthread_mutex_lock(l)) != 0 )
            log_error("Unable to lock log lock: %s", strerror(err));
    }
    else
    {
        if ( (err = pthread_mutex_unlock(l) != 0) )
            log_error("Unable to unlock log lock: %s", strerror(err));
    }
}
int log_open(char *fname, int level, int size, int lock)
{
    FILE            *fp;
    L.level = level;
    L.size = size*1024;
    if( !fname || !strcmp(fname, "console") || !strcmp(fname, "stderr") )
    {
        strcpy(L.file, "console");
        L.fp = stderr;
        L.size = 0; /* console don't need rollback */
    }
    else
    {
        if ( !(fp = fopen(fname, "a+")) )
        {
            fprintf(stderr, "%s() failed: %s\n", __func__, strerror(errno));
            return -2;
        }
        L.fp = fp;
        strncpy(L.file, fname, sizeof(L.file));
    }
    if( lock )
    {
        static pthread_mutex_t     log_lock;
        pthread_mutex_init(&log_lock, NULL);
        L.udata = (void *)&log_lock;
        L.lockfn = mutex_lock;
    }
    fprintf(L.fp, "\n");
    log_info("logger system(%s) start: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
            LOG_VERSION, L.file, level_names[level], size);
    return 0;
}
void log_close(void)
{
    if( L.fp && L.fp!=stderr )
        fclose(L.fp);
    if (L.udata )
        pthread_mutex_destroy( L.udata);
}
static void log_rollback(void)
{
    char       cmd[128]={0};
    long       fsize;
    /* don't need rollback */
    if(L.size <= 0 )
        return ;
    fsize = ftell(L.fp);
    if( fsize < L.size )
        return ;
    /* backup current log file  */
    snprintf(cmd, sizeof(cmd), "cp %s %s.bak", L.file, L.file);
    system(cmd);
    /* rollback file */
    fseek(L.fp, 0, SEEK_SET);
    truncate(L.file, 0);
    fprintf(L.fp, "\n");
    log_info("logger system(%s) rollback: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
            LOG_VERSION, L.file, level_names[L.level], L.size/1024);
    return ;
}
void _log_write(int level, const char *file, int line, const char *fmt, ...)
{
    va_list    args;
    char       time_string[100];
    if ( !L.fp || level>L.level )
        return;
    /* Acquire lock */
    if ( L.lockfn )
        L.lockfn(L.udata, 1);
    log_rollback();
    /* check and rollback file */
    time_to_str(time_string);
    /* Log to stderr */
    if ( L.fp == stderr )
    {
        fprintf(L.fp, "%s %s %-5s\x1b[0m \x1b[90m%s:%03d:\x1b[0m ",
                time_string, level_colors[level], level_names[level], file, line);
    }
    else /* Log to file */
    {
        fprintf(L.fp, "%s %-5s %s:%03d: ", time_string, level_names[level], file, line);
    }
    va_start(args, fmt);
    vfprintf(L.fp, fmt, args);
    va_end(args);
    fflush(L.fp);
    /* Release lock */
    if ( L.lockfn )
        L.lockfn(L.udata, 0);
}
#define LINELEN 81
#define CHARS_PER_LINE 16
static char *print_char =
"                "
"                "
" !\"#$%&'()*+,-./"
"0123456789:;<=>?"
"@ABCDEFGHIJKLMNO"
"PQRSTUVWXYZ[\\]^_"
"`abcdefghijklmno"
"pqrstuvwxyz{|}~ "
"                "
"                "
" ???????????????"
"????????????????"
"????????????????"
"????????????????"
"????????????????"
"????????????????";
void log_dump(int level, const char *prompt, char *buf, size_t len)
{
    int rc;
    int idx;
    char prn[LINELEN];
    char lit[CHARS_PER_LINE + 2];
    char hc[4];
    short line_done = 1;
    if (!L.fp || level>L.level)
        return;
    if( prompt )
        _log_write(level, __FILE__, __LINE__, "%s", prompt);
    rc = len;
    idx = 0;
    lit[CHARS_PER_LINE] = '\0';
    while (rc > 0)
    {
        if (line_done)
            snprintf(prn, LINELEN, "%08X: ", idx);
        do
        {
            unsigned char c = buf[idx];
            snprintf(hc, 4, "%02X ", c);
            strncat(prn, hc, LINELEN);
            lit[idx % CHARS_PER_LINE] = print_char[c];
        }
        while (--rc > 0 && (++idx % CHARS_PER_LINE != 0));
        line_done = (idx % CHARS_PER_LINE) == 0;
        if (line_done)
        {
            if (L.fp)
                fprintf(L.fp, "%s  %s\n", prn, lit);
        }
    }
    if (!line_done)
    {
        int ldx = idx % CHARS_PER_LINE;
        lit[ldx++] = print_char[(int)buf[idx]];
        lit[ldx] = '\0';
        while ((++idx % CHARS_PER_LINE) != 0)
            strncat(prn, "   ", sizeof(prn)-strlen(prn));
        if (L.fp)
            fprintf(L.fp, "%s  %s\n", prn, lit);
    }
}
gpsd/booster/logger.h
New file
@@ -0,0 +1,65 @@
/*
 * Copyright (c) 2022 Guo Wenxue
 * Author: Guo Wenxue <guowenxue@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GPL license.
 */
#ifndef  _LOGGER_H_
#define  _LOGGER_H_
#include <stdio.h>
#include <stdarg.h>
#define LOG_VERSION "v0.1"
/* log level */
enum {
    LOG_LEVEL_FATAL,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_WARN,
    LOG_LEVEL_INFO,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_TRACE,
    LOG_LEVEL_MAX
};
enum {
    LOG_LOCK_DISABLE, /* disable lock */
    LOG_LOCK_ENABLE,  /* enable lock */
};
#define ROLLBACK_NONE   0
/* description: Initial the logger system
 * arguments  :
 *             $fname: logger file name, NULL/"console"/"stderr" will log to console
 *             $level: logger level above;
 *             $size : logger file max size in KiB
 *             $lock : thread lock enable or not
 * return     : <0: Failed  ==0: Sucessfully
 */
int  log_open(char *fname, int level, int size, int lock);
/* description: Terminate the logger system */
void log_close(void);
/* description: log message into log file. Don't call this function directly. */
void _log_write(int level, const char *file, int line, const char *fmt, ...);
/* description: dump a buffer in hex to logger file */
void log_dump(int level, const char *prompt, char *buf, size_t len);
/* function: log message into logger file with different log level */
#define log_trace(...) _log_write(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) _log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...)  _log_write(LOG_LEVEL_INFO,  __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...)  _log_write(LOG_LEVEL_WARN,  __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) _log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) _log_write(LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#endif
gpsd/booster/makefile
New file
@@ -0,0 +1,18 @@
PWD=$(shell pwd )
LIBNAME=$(shell basename ${PWD} )
TOPDIR=$(shell dirname ${PWD} )
CFLAGS+=-D_GNU_SOURCE
all: clean
    @rm -f *.o
    @${CROSSTOOL}gcc ${CFLAGS} -I${TOPDIR} -c *.c
    ${CROSSTOOL}ar -rcs  lib${LIBNAME}.a *.o
clean:
    @rm -f *.o
    @rm -f *.a
distclean:
    @make clean
gpsd/booster/ringbuf.c
New file
@@ -0,0 +1,106 @@
/********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ringbuf.h
 *    Description:  This head file
 *
 *        Version:  1.0.0(2021å¹´04月29æ—¥)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2021å¹´04月29æ—¥ 12æ—¶18分32ç§’"
 *
 ********************************************************************************/
#include <string.h>
#include <assert.h>
#include "ringbuf.h"
void rb_init (struct ring_buffer *ring, unsigned char* buff, int size)
{
    memset (ring, 0, sizeof (struct ring_buffer));
    ring->rd_pointer = 0;
    ring->wr_pointer = 0;
    ring->buffer= buff;
    ring->size = size;
}
int rb_write (struct ring_buffer *rb, unsigned char * buf, int len)
{
    int                  total;
    int                  i;
    /* total = len = min(space, len) */
    total = rb_free_size(rb);
    if(len > total)
        len = total;
    else
        total = len;
    i = rb->wr_pointer;
    if(i + len > rb->size)
    {
        memcpy(rb->buffer + i, buf, rb->size - i);
        buf += rb->size - i;
        len -= rb->size - i;
        i = 0;
    }
    memcpy(rb->buffer + i, buf, len);
    rb->wr_pointer = i + len;
    return total;
}
int rb_free_size (struct ring_buffer *rb)
{
    return (rb->size - 1 - rb_data_size(rb));
}
int rb_read (struct ring_buffer *rb, unsigned char * buf, int max)
{
    int                  total;
    int                  i;
    /* total = len = min(used, len) */
    total = rb_data_size(rb);
    if(max > total)
        max = total;
    else
        total = max;
    i = rb->rd_pointer;
    if(i + max > rb->size)
    {
        memcpy(buf, rb->buffer + i, rb->size - i);
        buf += rb->size - i;
        max -= rb->size - i;
        i = 0;
    }
    memcpy(buf, rb->buffer + i, max);
    rb->rd_pointer = i + max;
    return total;
}
int rb_data_size (struct ring_buffer *rb)
{
    return ((rb->wr_pointer - rb->rd_pointer) & (rb->size-1));
}
void rb_clear (struct ring_buffer *rb)
{
    memset(rb->buffer,0,rb->size);
    rb->rd_pointer=0;
    rb->wr_pointer=0;
}
unsigned char rb_peek(struct ring_buffer* rb, int index)
{
    assert(index < rb_data_size(rb));
    return rb->buffer[((rb->rd_pointer + index) % rb->size)];
}
gpsd/booster/ringbuf.h
New file
@@ -0,0 +1,57 @@
/********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ringbuf.h
 *    Description:  This head file
 *
 *        Version:  1.0.0(2021å¹´04月29æ—¥)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2021å¹´04月29æ—¥ 12æ—¶18分32ç§’"
 *
 ********************************************************************************/
#ifndef  _RINGBUF_H_
#define  _RINGBUF_H_
struct ring_buffer
{
    unsigned char      *buffer;
    int                 wr_pointer;
    int                 rd_pointer;
    int                 size;
};
/* Initial the ring buffer */
void rb_init (struct ring_buffer *ring, unsigned char *buff, int size) ;
/*  Description: Write $len bytes data in $buf into ring buffer $rb
 * Return Value: The actual written into ring buffer data size, if ring buffer
 * left space size small than $len, then only part of the data be written into.
 */
int rb_write (struct ring_buffer *rb, unsigned char *buf, int len) ;
/* Get ring buffer left free size  */
int rb_free_size (struct ring_buffer *rb);
/* Read $max bytes data from ring buffer $rb to $buf */
int rb_read (struct ring_buffer *rb, unsigned char *buf, int max);
/* Read a specify $index byte data in ring buffer $rb  */
unsigned char rb_peek(struct ring_buffer *rb, int index);
/* Get data size in the ring buffer  */
int rb_data_size (struct ring_buffer *rb);
/* Clear the ring buffer data  */
void rb_clear (struct ring_buffer *rb) ;
#endif   /* ----- #ifndef _RINGBUF_H_  ----- */
gpsd/booster/util_proc.c
New file
@@ -0,0 +1,432 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  util_proc.c
 *    Description:  This file is the process API
 *
 *        Version:  1.0.0(7/06/2020)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "7/06/2020 09:19:02 PM"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "util_proc.h"
#include "logger.h"
proc_signal_t     g_signal={0};
void proc_default_sighandler(int sig)
{
    switch(sig)
    {
        case SIGINT:
            log_warn("SIGINT - stopping\n");
            g_signal.stop = 1;
            break;
        case SIGTERM:
            log_warn("SIGTERM - stopping\n");
            g_signal.stop = 1;
            break;
        case SIGSEGV:
            log_warn("SIGSEGV - stopping\n");
#if 0
            if(g_signal.stop)
                exit(0);
            g_signal.stop = 1;
#endif
            break;
        case SIGPIPE:
            log_warn("SIGPIPE - warnning\n");
            break;
        default:
            break;
    }
}
/* install default signal process functions  */
void install_default_signal(void)
{
    struct sigaction sigact, sigign;
    log_info("Install default signal handler.\n");
    /*  Initialize the catch signal structure. */
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigact.sa_handler = proc_default_sighandler;
    /*  Setup the ignore signal. */
    sigemptyset(&sigign.sa_mask);
    sigign.sa_flags = 0;
    sigign.sa_handler = SIG_IGN;
    sigaction(SIGTERM, &sigact, 0); /*  catch terminate signal "kill" command */
    sigaction(SIGINT,  &sigact, 0); /*  catch interrupt signal CTRL+C */
    //sigaction(SIGSEGV, &sigact, 0); /*  catch segmentation faults  */
    sigaction(SIGPIPE, &sigact, 0); /*  catch broken pipe */
#if 0
    sigaction(SIGCHLD, &sigact, 0); /*  catch child process return */
    sigaction(SIGUSR2, &sigact, 0); /*  catch USER signal */
#endif
}
/* ****************************************************************************
 * FunctionName: daemonize
 * Description : Set the programe runs as daemon in background
 * Inputs      : nodir: DON'T change the work directory to / :  1:NoChange 0:Change
 *               noclose: close the opened file descrtipion or not 1:Noclose 0:Close
 * Output      : NONE
 * Return      : NONE
 * *****************************************************************************/
void daemonize(int nochdir, int noclose)
{
    int rv, fd;
    int i;
    /*  already a daemon */
    if (1 == getppid())
        return;
    /*  fork error */
    rv = fork();
    if (rv < 0) exit(1);
    /*  parent process exit */
    if (rv > 0)
        exit(0);
    /*  obtain a new process session group */
    setsid();
    if (!noclose)
    {
        /*  close all descriptors */
        for (i = getdtablesize(); i >= 0; --i)
        {
            //if (i != g_logPtr->fd)
                close(i);
        }
        /*  Redirect Standard input [0] to /dev/null */
        fd = open("/dev/null", O_RDWR);
        /* Redirect Standard output [1] to /dev/null */
        dup(fd);
        /* Redirect Standard error [2] to /dev/null */
        dup(fd);
    }
    umask(0);
    if (!nochdir)
        chdir("/");
    return;
}
/* ****************************************************************************
 * FunctionName: check_set_program_running
 * Description : check program already running or not, if not then run it and
 *               record pid into $pidfile
 * Inputs      : daemon:  set program running in daemon or not
 *               pid_file:The record PID file path
 * Output      : NONE
 * Return      : 0: Record successfully  Else: Failure
 * *****************************************************************************/
int check_set_program_running(int daemon, char *pidfile)
{
    if( !pidfile )
        return 0;
    if( check_daemon_running(pidfile) )
    {
        log_error("Program already running, process exit now");
        return -1;
    }
    if( daemon )
    {
        if( set_daemon_running(pidfile) < 0 )
        {
            log_error("set program running as daemon failure\n");
            return -2;
        }
    }
    else
    {
        if( record_daemon_pid(pidfile) < 0 )
        {
            log_error("record program running PID failure\n");
            return -3;
        }
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: record_daemon_pid
 * Description : Record the running daemon program PID to the file "pid_file"
 * Inputs      : pid_file:The record PID file path
 * Output      : NONE
 * Return      : 0: Record successfully  Else: Failure
 * *****************************************************************************/
int record_daemon_pid(const char *pid_file)
{
    struct stat fStatBuf;
    int fd = -1;
    int mode = S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU;
    char ipc_dir[64] = { 0 };
    strncpy(ipc_dir, pid_file, 64);
    /* dirname() will modify ipc_dir and save the result */
    dirname(ipc_dir);
    /* If folder pid_file PATH doesnot exist, then we will create it" */
    if (stat(ipc_dir, &fStatBuf) < 0)
    {
        if (mkdir(ipc_dir, mode) < 0)
        {
            log_error("cannot create %s: %s\n", ipc_dir, strerror(errno));
            return -1;
        }
        (void)chmod(ipc_dir, mode);
    }
    /*  Create the process running PID file */
    mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    if ((fd = open(pid_file, O_RDWR | O_CREAT | O_TRUNC, mode)) >= 0)
    {
        char pid[PID_ASCII_SIZE];
        snprintf(pid, sizeof(pid), "%u\n", (unsigned)getpid());
        write(fd, pid, strlen(pid));
        close(fd);
        log_debug("Record PID<%u> to file %s.\n", getpid(), pid_file);
    }
    else
    {
        log_error("cannot create %s: %s\n", pid_file, strerror(errno));
        return -1;
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: get_daemon_pid
 * Description : Get the daemon process PID from the PID record file "pid_file"
 * Inputs      : pid_file: the PID record file
 * Output      : NONE
 * Return      : pid_t: The daemon process PID number
 * *****************************************************************************/
pid_t get_daemon_pid(const char *pid_file)
{
    FILE *f;
    pid_t pid;
    if ((f = fopen(pid_file, "rb")) != NULL)
    {
        char pid_ascii[PID_ASCII_SIZE];
        (void)fgets(pid_ascii, PID_ASCII_SIZE, f);
        (void)fclose(f);
        pid = atoi(pid_ascii);
    }
    else
    {
        log_error("Can't open PID record file %s: %s\n", pid_file, strerror(errno));
        return -1;
    }
    return pid;
}
/* ****************************************************************************
 * FunctionName: check_daemon_running
 * Description : Check the daemon program already running or not
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 1: The daemon program alread running   0: Not running
 * *****************************************************************************/
int check_daemon_running(const char *pid_file)
{
    int rv = -1;
    struct stat fStatBuf;
    rv = stat(pid_file, &fStatBuf);
    if (0 == rv)
    {
        pid_t pid = -1;
        printf("PID record file \"%s\" exist.\n", pid_file);
        pid = get_daemon_pid(pid_file);
        if (pid > 0)  /*  Process pid exist */
        {
            if ((rv = kill(pid, 0)) == 0)
            {
                printf("Program with PID[%d] seems running.\n", pid);
                return 1;
            }
            else   /* Send signal to the old process get no reply. */
            {
                printf("Program with PID[%d] seems exit.\n", pid);
                remove(pid_file);
                return 0;
            }
        }
        else if (0 == pid)
        {
            printf("Can not read program PID form record file.\n");
            remove(pid_file);
            return 0;
        }
        else  /* Read pid from file "pid_file" failure */
        {
            printf("Read record file \"%s\" failure, maybe program still running.\n", pid_file);
            return 1;
        }
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: stop_daemon_running
 * Description : Stop the daemon program running
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 1: The daemon program alread running   0: Not running
 * *****************************************************************************/
int stop_daemon_running(const char *pid_file)
{
    pid_t            pid = -1;
    struct stat      fStatBuf;
    if ( stat(pid_file, &fStatBuf) < 0)
        return 0;
    printf("PID record file \"%s\" exist.\n", pid_file);
    pid = get_daemon_pid(pid_file);
    if (pid > 0)  /*  Process pid exist */
    {
        while ( (kill(pid, 0) ) == 0)
        {
            kill(pid, SIGTERM);
            sleep(1);
        }
        remove(pid_file);
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: set_daemon_running
 * Description : Set the programe running as daemon if it's not running and record
 *               its PID to the pid_file.
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 0: Successfully. 1: Failure
 * *****************************************************************************/
int set_daemon_running(const char *pid_file)
{
    daemonize(0, 1);
    log_info("Program running as daemon [PID:%d].\n", getpid());
    if (record_daemon_pid(pid_file) < 0)
    {
        log_error("Record PID to file \"%s\" failure.\n", pid_file);
        return -2;
    }
    return 0;
}
/* start a new thread to run $thread_workbody point function  */
int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg)
{
    int                rv = 0;
    pthread_t          tid;
    pthread_attr_t     thread_attr;
    /* Initialize the thread  attribute */
    rv = pthread_attr_init(&thread_attr);
    if(rv)
        return -1;
    /* Set the stack size of the thread */
    rv = pthread_attr_setstacksize(&thread_attr, 120 * 1024);
    if(rv)
        goto CleanUp;
    /* Set thread to detached state:Don`t need pthread_join */
    rv = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
    if(rv)
        goto CleanUp;
    /* Create the thread */
    rv = pthread_create(&tid, &thread_attr, thread_workbody, thread_arg);
    if(rv)
        goto CleanUp;
CleanUp:
    if( thread_id )
    {
        if( rv )
            *thread_id = 0;
        else
            *thread_id = tid;
    }
    /* Destroy the  attributes  of  thread */
    pthread_attr_destroy(&thread_attr);
    return rv;
}
/* excute a linux command by system() */
void exec_system_cmd(const char *format, ...)
{
    char                cmd[256];
    va_list             args;
    memset(cmd, 0, sizeof(cmd));
    va_start(args, format);
    vsnprintf(cmd, sizeof(cmd), format, args);
    va_end(args);
    system(cmd);
}
gpsd/booster/util_proc.h
New file
@@ -0,0 +1,67 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  util_proc.h
 *    Description:  This head file is for Linux process/thread API
 *
 *        Version:  1.0.0(7/06/2012~)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "7/06/2012 09:21:33 PM"
 *
 ********************************************************************************/
#ifndef __UTIL_PROC_H_
#define __UTIL_PROC_H_
#include <signal.h>
#define PID_ASCII_SIZE  11
typedef struct proc_signal_s
{
    int       signal;
    unsigned  stop;     /* 0: Not term  1: Stop  */
}  proc_signal_t;
typedef void *(* thread_body_t) (void *thread_arg);
extern proc_signal_t    g_signal;
/* install default signal process functions  */
extern void install_default_signal(void);
/* excute a linux command by system() */
extern void exec_system_cmd(const char *format, ...);
/* check program already running or not, if not then run it and record pid into $pidfile */
extern int check_set_program_running(int daemon, char *pidfile);
/* check program already running or not from $pid_file  */
extern int check_daemon_running(const char *pid_file);
/* set program daemon running and record pid in $pid_file  */
extern int set_daemon_running(const char *pid_file);
/* record proces ID into $pid_file  */
extern int record_daemon_pid(const char *pid_file);
/* stop program running from $pid_file  */
extern int stop_daemon_running(const char *pid_file);
/* my implementation for set program running in daemon   */
extern void daemonize(int nochdir, int noclose);
/* start a new thread to run $thread_workbody point function  */
extern int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg);
/* +---------------------+
 * |   Low level API     |
 * +---------------------+*/
/* get daemon process ID from $pid_file   */
extern pid_t get_daemon_pid(const char *pid_file);
#endif
gpsd/gpsd.c
New file
@@ -0,0 +1,245 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  gpsd.c
 *    Description:  This file is GPS location parser program
 *
 *        Version:  1.0.0(07/10/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "07/10/23 09:49:55"
 *
 ********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "logger.h"
#include "comport.h"
#include "util_proc.h"
typedef struct gps_fix_s
{
    char       time[32];  /* GPS UTC time, formt: hhmmss */
    char       status;    /* A: Valid  V:Invalid */
    float      latitude;  /* Latitude */
    char       lat;       /* N: North  S: South */
    float      longitude; /* Longitude */
    char       lon;       /* E: East   W: West */
    float      speed;     /* Speed over ground, meters/sec */
    float      track;     /* Course made good (relative to true north) */
} gps_fix_t;
int proc_gprmc(char *buf, int size, gps_fix_t *info);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0 Build on (%s)\n", progname, __DATE__);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    banner(progname);
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is a MQTT subscribe daemon program to control relay. \n", progname);
    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf(" -D[device  ]  Set GPS connect serial port device\n");
    printf(" -d[debug   ]  Running in debug mode\n");
    printf(" -l[level   ]  Set the log level as [0..%d]\n", LOG_LEVEL_MAX-1);
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    return;
}
int main (int argc, char **argv)
{
    const char             *progname=NULL;
    int                     opt = 0;
    int                     rv = 0;
    int                     debug = 0;
    char                    pid_file[64] = { 0 }; /*   The file used to record the PID */
    char                   *log_file = "/tmp/gpsd.log";
    int                     log_level = LOG_LEVEL_INFO;
    int                     log_size = 10;
    char                    buf[4096];
    char                   *dev=NULL;
    comport_t               comport;
    gps_fix_t               info;
    struct option long_options[] = {
        {"device", required_argument, NULL, 'D'},
        {"debug", no_argument, NULL, 'd'},
        {"level", required_argument, NULL, 'l'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /*  Parser the command line parameters */
    while ((opt = getopt_long(argc, argv, "D:dl:vh", long_options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'D':  /*  Set serial port device */
                dev = optarg;
                break;
            case 'd': /*  Set debug running */
                debug = 1;
                log_file = "console";
                log_level = LOG_LEVEL_DEBUG;
                log_size = ROLLBACK_NONE;
                break;
            case 'l': /*  Set the log level */
                rv = atoi(optarg);
                log_level = rv>LOG_LEVEL_MAX ? LOG_LEVEL_MAX-1 : rv;
                break;
            case 'v':  /*  Get software version */
                banner(progname); /*  Defined in version.h */
                return EXIT_SUCCESS;
            case 'h':  /*  Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        } /*   end of "switch(opt)" */
    }
    if( !dev )
    {
        program_usage(progname);
        return 0;
    }
    if( (rv=log_open(log_file, log_level, log_size, LOG_LOCK_DISABLE)) < 0 )
    {
        fprintf(stderr, "open logger failed, rv=%d\n", rv);
        return 1;
    }
    if( !debug )
    {
        snprintf(pid_file, sizeof(pid_file), "/var/run/%s.pid", progname);
        log_info("check program running in daemon or not by pidfile [%s]\n", pid_file);
        if( check_daemon_running(pid_file) )
        {
            printf("Programe already running, exit now.\n");
            return 3;
        }
        if( set_daemon_running(pid_file) < 0 )
        {
            printf("Programe already running, exit now.\n");
            return 3;
        }
    }
    if( (rv=comport_open(&comport, dev, 4800, "8N1N")) < 0 )
    {
        log_error("Open serial port \"%s\" failed, rv=%d\n", dev, rv);
        return 2;
    }
    log_info("Open serial port \"%s\" successfully\n", dev);
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        rv = comport_recv(&comport, buf, sizeof(buf), 3000);
        if( rv > 0 )
        {
            proc_gprmc(buf, strlen(buf), &info);
        }
    }
    comport_close(&comport);
    return 0;
}
/*
 * $GPRMC,024216.000,A,3029.6651,N,11423.6251,E,0.08, 156.95,100723,,,A*65
 * 1     024216.000    UTC time format: HHMMSS, 10:42:16(BeiJing)
 * 2     A             Status of Fix:
 *                      A = Autonomous, valid;
 *                      D = Differential, valid;
 *                      V = invalid
 *
 * 3,4   3029.6651,N   Latitude format: ddmm.mmmm, Latitude 30 deg. 29.6651 min North
 * 5,6   11423.6251,E  Longitude: dddmm.mmmm, Longitude 114 deg. 23.6251 min East
 * 7     0.08          Speed over ground, Knots
 * 8     156.95        Course Made Good, True north
 * 9     100723        Date of fix ddmmyy. 2023-07-10
 * 10,11 ,,            Magnetic variation
 * 12    A             FAA mode indicator (NMEA 2.3 and later)
 *                      A=autonomous,
 *                      D=differential,
 *                      E=Estimated,
 *                      M=Manual input mode,
 *                      N=not valid,
 *                      S=Simulator,
 *                      V=Valid
 * 13   *68            mandatory nmea_checksum
 */
#define DD(s)   ((int)((s)[0]-'0')*10+(int)((s)[1]-'0'))
int proc_gprmc(char *buf, int size, gps_fix_t *info)
{
    char           *ptr_start=NULL;
    char           *ptr_end=NULL;
    char            hhmmss[12]={0x0};
    char            ddmmyy[12]={0x0};
    if( !buf || size<=0 || !info )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    log_trace("GPS receive raw data\n:%s\n", buf);
    if( !(ptr_start=strstr(buf, "$GPRMC")) )
    {
        log_warn("$GPRMC keywords not matched\n");
        return -2;
    }
    if( !(ptr_end=strchr(ptr_start, 0x0D)) )
    {
        log_warn("$GPRMC data not integrated\n", ptr_start);
    }
    *ptr_end = '\0';
    memset(info, 0, sizeof(*info));
    sscanf(ptr_start, "$GPRMC,%[^,],%c,%f,%c,%f,%c,%f,%f,%[^,]",
            hhmmss, &info->status, &info->latitude, &info->lat,
            &info->longitude, &info->lon, &info->speed, &info->track, ddmmyy);
    snprintf(info->time, sizeof(info->time), "%04d-%02d-%02d %02d:%02d:%02d",
            DD(ddmmyy+4)+2000, DD(ddmmyy+2), DD(ddmmyy),
            DD(hhmmss)+8, DD(hhmmss+2), DD(hhmmss+4));
    if( info->status == 'A' )
    {
        log_info("NMEA0183: %s lat=%.2f(%c) lon=%.2f(%c) speed=%.2f, track=%.2f\n",
                info->time, info->latitude, info->lat,
                info->longitude, info->lon, info->speed, info->track);
    }
    else if( info->status == 'V' )
    {
        log_info("NMEA0183: Invalid data\n");
    }
    return 0;
}
gpsd/makefile
New file
@@ -0,0 +1,57 @@
#*********************************************************************************
#      Copyright:  (C) 2022 Avnet. All rights reserved.
#         Author:  Guo Wenxue<wenxue.guo@avnet.com>
#
#       Filename:  Makefile
#    Description:  This Makefile used to compile all the C source code file in
#                  current folder to a excutable binary file.
#
#********************************************************************************/
PRJ_PATH=$(shell pwd)
APP_NAME = gpsd
INST_PATH= /tftp
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSSTOOL=arm-linux-gnueabi-
endif
# C source files in top-level directory
SRCFILES = $(wildcard *.c)
# common CFLAGS for our source code
CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized -D_GNU_SOURCE
# C source file in sub-directory
DIRS= booster
DIRS_PATH=$(patsubst %,${PRJ_PATH}/%,$(DIRS))
CFLAGS+=$(patsubst %,-I%,$(DIRS_PATH))
LDFLAGS+=$(patsubst %,-L%,$(DIRS_PATH))
LIBS=$(patsubst %,-l%,$(DIRS))
LDFLAGS+=-lpthread
.PHONY:libs
all: entry modules binary
entry:
    @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
modules:
    @set -e; for d in ${DIRS}; do $(MAKE) CROSSTOOL=${CROSSTOOL} CFLAGS="${CFLAGS}" -C $${d}; done
binary:  ${SRCFILES}
    $(CROSSTOOL)gcc $(CFLAGS) -o ${APP_NAME} $^ ${LDFLAGS} ${LIBS}
    @echo " Compile over"
clean:
    set -e; for d in ${DIRS}; do $(MAKE) clean -C $${d}; done
    @rm -f *.o $(APP_NAME)
distclean: clean
    @rm -rf cscope* tags
install:
    cp ${APP_NAME} ${INST_PATH}