APUE course source code
guowenxue
2 days ago 9c22371ef5059a2e46226ee90a0667ffad65b574
Add projects source code
56 files added
9890 ■■■■■ changed files
.gitignore 26 ●●●●● patch | view | raw | blame | history
project/1.packet/makefile 25 ●●●●● patch | view | raw | blame | history
project/1.packet/openlibs/cjson/build.sh 46 ●●●●● patch | view | raw | blame | history
project/1.packet/openlibs/makefile 6 ●●●●● patch | view | raw | blame | history
project/1.packet/pack_gps.c 124 ●●●●● patch | view | raw | blame | history
project/1.packet/pack_json.c 169 ●●●●● patch | view | raw | blame | history
project/1.packet/pack_seg.c 176 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/database.c 265 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/database.h 60 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/ds18b20.c 118 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/ds18b20.h 31 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/list.h 723 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/logger.c 279 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/logger.h 70 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/makefile 34 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/packet.c 357 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/packet.h 141 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/proc.c 432 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/proc.h 89 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/socket.c 658 ●●●●● patch | view | raw | blame | history
project/2.socketd/booster/socket.h 146 ●●●●● patch | view | raw | blame | history
project/2.socketd/makefile 67 ●●●●● patch | view | raw | blame | history
project/2.socketd/openlibs/makefile 13 ●●●●● patch | view | raw | blame | history
project/2.socketd/openlibs/sqlite/build.sh 181 ●●●●● patch | view | raw | blame | history
project/2.socketd/openlibs/sqlite/makefile 7 ●●●●● patch | view | raw | blame | history
project/2.socketd/socket_client.c 263 ●●●●● patch | view | raw | blame | history
project/2.socketd/socket_server.c 351 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/config.c 200 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/config.h 71 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/ds18b20.c 118 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/ds18b20.h 31 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/ini_dictionary.c 398 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/ini_dictionary.h 165 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/iniparser.c 807 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/iniparser.h 308 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/leds.c 162 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/leds.h 58 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/logger.c 279 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/logger.h 70 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/makefile 36 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/proc.c 432 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/proc.h 89 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/sht20.c 245 ●●●●● patch | view | raw | blame | history
project/3.mqttd/booster/sht20.h 34 ●●●●● patch | view | raw | blame | history
project/3.mqttd/etc/mqttd.conf 59 ●●●●● patch | view | raw | blame | history
project/3.mqttd/makefile 66 ●●●●● patch | view | raw | blame | history
project/3.mqttd/mqttd.c 434 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/cjson/build.sh 182 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/libgpiod/build.sh 187 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/makefile 16 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/mosquitto/build.sh 188 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/openssl/build.sh 182 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/sqlite/build.sh 181 ●●●●● patch | view | raw | blame | history
project/3.mqttd/openlibs/sqlite/makefile 7 ●●●●● patch | view | raw | blame | history
project/3.mqttd/tools/publisher.sh 14 ●●●●● patch | view | raw | blame | history
project/3.mqttd/tools/subsciber.sh 14 ●●●●● patch | view | raw | blame | history
.gitignore
New file
@@ -0,0 +1,26 @@
# git ignore files/folders in the list
# ignore folders
install/
# ignore files
*.tar*
*.o
lib*.so*
lib*.a
mqttd
# ctags files
cscope.*
tags
# running files
*.log
*.db
# openlibs
cJSON-*
mosquitto-*
libgpiod-*
openssl-*
sqlite-*
project/1.packet/makefile
New file
@@ -0,0 +1,25 @@
PRJ_PATH=`pwd`
CFLAGS=-I ${PRJ_PATH}/openlibs/install/include
LDFLAGS=-L ${PRJ_PATH}/openlibs/install/lib -lcjson
SRCS = $(wildcard ${VPATH}/*.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
SRCFILES = $(wildcard *.c)
BINARIES=$(SRCFILES:%.c=%)
all: binaries
binaries: library  ${BINARIES}
    @echo " Compile over"
library:
    make -C openlibs
%: %.c
    $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
    rm -f $(BINARIES)
    make clean -C openlibs
project/1.packet/openlibs/cjson/build.sh
New file
@@ -0,0 +1,46 @@
#!/bin/bash
# libraries install path
INST_PATH=`pwd`/../install
# LingYun studio FTP server address for all the open source code
LYFTP_SRC=http://weike-iot.com:2211/src/
# set shell script exit when any command failure
set -e
# funciton used to build cjson source code
function build_cjson()
{
   SRC_NAME=cJSON-1.7.15
   if [ -L $INST_PATH/lib/libcjson.so ] ; then
      echo "$SRC_NAME already compile and installed"
      return ;
   fi
   # If source code tarball file not exist, it will download the packet.
   if [ ! -f ${SRC_NAME}.tar.gz ] ; then
      wget ${LYFTP_SRC}/${SRC_NAME}.tar.gz
   fi
   # If source code folder not exist, decompress the tarball packet
   if [ ! -d ${SRC_NAME} ] ; then
      tar -xzf ${SRC_NAME}.tar.gz
   fi
   cd ${SRC_NAME}
   make && make DESTDIR=${INST_PATH} PREFIX=/ install
   # copy static library but not use shared library
   rm -f ${INST_PATH}/lib/libcjson*.so*
   cp libcjson.a ${INST_PATH}/lib/
   cd -
}
# start build cjson
build_cjson
project/1.packet/openlibs/makefile
New file
@@ -0,0 +1,6 @@
all:
    cd cjson && ./build.sh
clean:
    cd cjson && rm -rf c*
    rm -rf install
project/1.packet/pack_gps.c
New file
@@ -0,0 +1,124 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  pack_gps.c
 *    Description:  This file is GPS NMEA format string parser example
 *
 *        Version:  1.0.0(2023年07月10日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023年07月10日 17时21分35秒"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define GPS_RAWDATA  "\
$GPGSA,A,3,18,05,15,24,23,13,29,,,,,,2.2,1.1,1.9*3C\r\n\
$GPRMC,091859.000,A,3029.6909,N,11423.6327,E,0.23,200.08,100723,,,A*65\r\n\
$GPGGA,091900.000,3029.6909,N,11423.6326,E,1,07,1.1,28.2,M,-13.7,M,,0000*40\r\n"
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);
int main (int argc, char **argv)
{
    char       buf[] = GPS_RAWDATA;
    gps_fix_t   info;
    proc_gprmc(buf, strlen(buf), &info);
    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 )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    printf("GPS receive raw data:\n%s\n", buf);
    if( !(ptr_start=strstr(buf, "$GPRMC")) )
    {
        printf("$GPRMC keywords not matched\n");
        return -2;
    }
    if( !(ptr_end=strchr(ptr_start, 0x0D)) )
    {
        printf("$GPRMC data not integrated\n");
        return -3;
    }
    *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' )
    {
        printf("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' )
    {
        printf("NMEA0183: Invalid data\n");
    }
    return 0;
}
project/1.packet/pack_json.c
New file
@@ -0,0 +1,169 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  pack_json.c
 *    Description:  This file is JSON format string package and parser example
 *
 *        Version:  1.0.0(2023年07月10日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023年07月10日 17时57分00秒"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cjson/cJSON.h>
#define DEVSN_LEN     11
typedef struct data_s
{
        char        devsn[DEVSN_LEN+1];
        char        time[32];
        float       temp;
        float       rh;
} pack_t;
int pack_json(char *buf, int size, char *devsn, char *time, float temp, float rh);
int unpack_json(char *buf, int len, pack_t *pack);
int main (int argc, char **argv)
{
    char         buf[512];
    char        *devsn="ISK1023001";
    char        *time="2023-07-10 20:00:00";
    float        temp = 20.50;
    float        rh = 30.50;
    pack_t       data;
    int          rv;
    if( (rv=pack_json(buf, sizeof(buf), devsn, time, temp, rh)) < 0 )
    {
        printf("Package sample data message failed, rv=%d\n", rv);
        return rv;
    }
    if( (rv=unpack_json(buf, rv, &data)) < 0 )
    {
        printf("Parser sample data message failed, rv=%d\n", rv);
        return rv;
    }
    return 0;
}
int pack_json(char *buf, int size, char *devsn, char *time, float temp, float rh)
{
    if( !buf || size<=0 || !devsn || !time )
    {
        printf("\033[1;31m%s:%d %s() Invalid input arguments\033[0m\n", __FILE__, __LINE__, __func__);
        return -1;
    }
    memset(buf, 0, size);
    snprintf(buf, size, "{\"devsn\":\"%s\", \"time\":\"%s\", \"temperature\":%.2f, \"humidity\":%.2f}", devsn, time, temp, rh);
    printf("\033[1;32mPacket Message: %s\033[0m\n", buf);
    return strlen(buf);
}
int unpack_json(char *buf, int len, pack_t *pack)
{
    cJSON     *root = NULL;
    cJSON     *item;
    char      *value;
    int        bytes;
    int        rv = 0;
    if( !buf || len<=0 || !pack )
    {
        printf("\033[1;31m%s:%d %s() Invalid input arguments\033[0m\n", __FILE__, __LINE__, __func__);
        return -1;
    }
    printf("\033[1;33mParser Message: %s\033[0m\n", buf);
    root = cJSON_Parse(buf);
    if( !root )
    {
        printf("cJSON_Parse root failure: %s\n", cJSON_GetErrorPtr());
        return -2;
    }
    memset(pack, 0, sizeof(*pack));
    /*+--------------------------+
     *| Start parser SN segement |
     *+--------------------------+*/
    item = cJSON_GetObjectItem(root, "devsn");
    if( !item )
    {
        printf("cJSON_GetObjectItem() item failure: %s\n", cJSON_GetErrorPtr());
        rv = -3;
        goto cleanup;
    }
    bytes = strlen(item->valuestring);
    bytes = bytes>sizeof(pack->devsn) ? sizeof(pack->devsn) : bytes;
    strncpy(pack->devsn, item->valuestring, bytes);
    printf("Found devsn      : %s\n", pack->devsn);
    /*+----------------------------+
     *| Start parser Time segement |
     *+----------------------------+*/
    item = cJSON_GetObjectItem(root, "time");
    if( !item )
    {
        printf("cJSON_GetObjectItem() item failure: %s\n", cJSON_GetErrorPtr());
        rv = -4;
        goto cleanup;
    }
    bytes = strlen(item->valuestring);
    bytes = bytes>sizeof(pack->time) ? sizeof(pack->time) : bytes;
    strncpy(pack->time, item->valuestring, bytes);
    printf("Found time       : %s\n", pack->time);
    /*+--------------------------+
     *| Start parser temperature |
     *+--------------------------+*/
    item = cJSON_GetObjectItem(root, "temperature");
    if( !item )
    {
        printf("cJSON_GetObjectItem() item failure: %s\n", cJSON_GetErrorPtr());
        rv = -5;
        goto cleanup;
    }
    pack->temp = item->valuedouble;
    printf("Found temperature: %.2f\n", pack->temp);
    /*+-----------------------+
     *| Start parser humidity |
     *+-----------------------+*/
    item = cJSON_GetObjectItem(root, "humidity");
    if( !item )
    {
        printf("cJSON_GetObjectItem() item failure: %s\n", cJSON_GetErrorPtr());
        rv = -6;
        goto cleanup;
    }
    pack->rh = item->valuedouble;
    printf("Found humidity   : %.2f\n", pack->rh);
cleanup:
    cJSON_Delete(root); /* must delete it, or it will result memory leak */
    return rv;
}
project/1.packet/pack_seg.c
New file
@@ -0,0 +1,176 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  pack_seg.c
 *    Description:  This file is segement format string package and parser example
 *
 *        Version:  1.0.0(2023年07月10日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023年07月10日 16时34分49秒"
 *
 ********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CONFIG_DEBUG
#ifdef CONFIG_DEBUG
#define dbg_print(format, args...)  printf(format, ##args)
#else
#define dbg_print(format, args...)  do {} while(0)
#endif
#define DEVSN_LEN     11
typedef struct data_s
{
    char        devsn[DEVSN_LEN+1];
    char        time[32];
    float       temp;
    float       rh;
} pack_t;
int pack_seg(char *buf, int size, char *devsn, char *time, float temp, float rh);
int unpack_seg(char *buf, int len, pack_t *pack);
int main (int argc, char **argv)
{
    char         buf[512];
    char        *devsn="ISK1023001";
    char        *time="2023-07-10 20:00:00";
    float        temp = 20.50;
    float        rh = 30.50;
    pack_t       data;
    int          rv;
    if( (rv=pack_seg(buf, sizeof(buf), devsn, time, temp, rh)) < 0 )
    {
        printf("Package sample data message failed, rv=%d\n", rv);
        return rv;
    }
    if( (rv=unpack_seg(buf, rv, &data)) < 0 )
    {
        printf("Parser sample data message failed, rv=%d\n", rv);
        return rv;
    }
    return 0;
}
int pack_seg(char *buf, int size, char *devsn, char *time, float temp, float rh)
{
    if( !buf || size<=0 || !devsn || !time )
    {
        printf("\033[1;31m%s:%d %s() Invalid input arguments\033[0m\n", __FILE__, __LINE__, __func__);
        return -1;
    }
    memset(buf, 0, size);
    snprintf(buf, size, "%s,%s,%.2f,%.2f", devsn, time, temp, rh);
    dbg_print("\033[1;32mPacket Message: %s\033[0m\n", buf);
    return strlen(buf);
}
int unpack_seg(char *buf, int len, pack_t *pack)
{
    char      *ptr_start = NULL;
    char      *ptr_end = NULL;
    int        bytes;
    if( !buf || len<=0 || !pack )
    {
        printf("\033[1;31m%s:%d %s() Invalid input arguments\033[0m\n", __FILE__, __LINE__, __func__);
        return -1;
    }
    dbg_print("\033[1;33mParser Message: %s\033[0m\n", buf);
    if( !(ptr_start=strstr(buf, "ISK")) )
    {
        printf("ERROR: Device SN not found\n");
        return -2;
    }
    memset(pack, 0, sizeof(*pack));
    /*+--------------------------+
     *| Start parser SN segement |
     *+--------------------------+*/
    if( !(ptr_end=strchr(ptr_start, ',')) )
    {
        printf("ERROR: Package not integrated \n");
        return -2;
    }
    bytes = ptr_end - ptr_start;
    bytes = bytes>DEVSN_LEN ? DEVSN_LEN : bytes;
    memcpy(pack->devsn, ptr_start, bytes);
    printf("Found devsn      : %s\n", pack->devsn);
    /*+----------------------------+
     *| Start parser Time segement |
     *+----------------------------+*/
    ptr_start = ptr_end+1;
    if( !(ptr_end=strchr(ptr_start, ',')) )
    {
        printf("ERROR: Package not integrated \n");
        return -2;
    }
    bytes = ptr_end - ptr_start;
    bytes = bytes>sizeof(pack->time) ? sizeof(pack->time) : bytes;
    memcpy(pack->time, ptr_start, bytes);
    printf("Found time       : %s\n", pack->time);
    /*+--------------------------+
     *| Start parser temperature |
     *+--------------------------+*/
    ptr_start = ptr_end+1;
    if( !(ptr_end=strchr(ptr_start, ',')) )
    {
        printf("ERROR: Package not integrated \n");
        return -2;
    }
    *ptr_end='\0';
    pack->temp = atof(ptr_start);
    *ptr_end=',';
    printf("Found temperature: %.2f\n", pack->temp);
    /*+-----------------------+
     *| Start parser humidity |
     *+-----------------------+*/
    ptr_start = ptr_end+1;
    if( !(ptr_end=strchr(ptr_start, '\0')) )
    {
        printf("ERROR: Package not integrated \n");
        return -2;
    }
    pack->temp = atof(ptr_start);
    printf("Found humidity   : %.2f\n", pack->temp);
    return 0;
}
project/2.socketd/booster/database.c
New file
@@ -0,0 +1,265 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  database.c
 *    Description:  This library used to operate blob packet in sqlite database.
 *
 *        Version:  1.0.0(2020年05月13日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2020年05月13日 12时14分23秒"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "database.h"
#include "logger.h"
/* Blob packet table name */
#define TABLE_NAME     "PackTable"
/* Use static global handler here in order to simplify the API,
 * But it will make this library not thread safe
 */
static sqlite3         *s_clidb = NULL;
/* description: open or create sqlite database if not exist
 * input args:
 * $db_file: sqlite database file name
 * return value: <0: failure   0:ok
 * */
int database_init(const char *db_file)
{
    char               sql[SQL_COMMAND_LEN]={0};
    char              *errmsg = NULL;
    if( !db_file )
    {
        log_error("%s() Invalid input arguments\n", __func__);
        return -1;
    }
    /*+------------------------------------------+
     *|   database already exist, just open it   |
     *+------------------------------------------+*/
    if( 0==access(db_file, F_OK) )
    {
        if( SQLITE_OK != sqlite3_open(db_file, &s_clidb) )
        {
            log_error("open database file '%s' failure\n", db_file);
            return -2;
        }
        log_info("open database file '%s' ok\n", db_file);
        return 0;
    }
    /*+-----------------------------------------+
     *|  database not exist, create and init it |
     *+-----------------------------------------+*/
    if( SQLITE_OK != sqlite3_open(db_file, &s_clidb) )
    {
        log_error("create database file '%s' failure\n", db_file);
        return -2;
    }
    /* SQLite continues without syncing as soon as it has handed data off to the operating system */
    sqlite3_exec(s_clidb, "pragma synchronous = OFF; ", NULL, NULL, NULL);
    /* enable full auto vacuum, Auto increase/decrease  */
    sqlite3_exec(s_clidb, "pragma auto_vacuum = 2 ; ", NULL, NULL, NULL);
    /* Create firehost table in the database */
    snprintf(sql, sizeof(sql), "CREATE TABLE %s(packet BLOB);", TABLE_NAME);
    if( SQLITE_OK != sqlite3_exec(s_clidb, sql, NULL, NULL, &errmsg) )
    {
        log_error("create data_table in database file '%s' failure: %s\n", db_file, errmsg);
        sqlite3_free(errmsg); /* free errmsg  */
        sqlite3_close(s_clidb);   /* close databse */
        unlink(db_file);      /* remove database file */
        return -3;
    }
    log_info("create and init database file '%s' ok\n", db_file);
    return 0;
}
/* description: close sqlite database handler
 * return value: none
 */
void database_term(void)
{
    log_warn("close sqlite database now\n");
    sqlite3_close(s_clidb);
    return ;
}
/* description: push a blob packet into database
 * input args:
 *      $pack:  blob packet data address
 *      $size:  blob packet data bytes
 * return value: <0: failure   0:ok
 */
int database_push_packet(void *pack, int size)
{
    char               sql[SQL_COMMAND_LEN]={0};
    int                rv = 0;
    sqlite3_stmt      *stat = NULL;
    if( !pack || size<=0 )
    {
        log_error("%s() Invalid input arguments\n", __func__);
        return -1;
    }
    if( ! s_clidb )
    {
        log_error("sqlite database not opened\n");
        return -2;
    }
    snprintf(sql, sizeof(sql), "INSERT INTO %s(packet) VALUES(?)", TABLE_NAME);
    rv = sqlite3_prepare_v2(s_clidb, sql, -1, &stat, NULL);
    if(SQLITE_OK!=rv || !stat)
    {
        log_error("blob add sqlite3_prepare_v2 failure\n");
        rv = -2;
        goto OUT;
    }
    if( SQLITE_OK != sqlite3_bind_blob(stat, 1, pack, size, NULL) )
    {
        log_error("blob add sqlite3_bind_blob failure\n");
        rv = -3;
        goto OUT;
    }
    rv = sqlite3_step(stat);
    if( SQLITE_DONE!=rv && SQLITE_ROW!=rv )
    {
        log_error("blob add sqlite3_step failure\n");
        rv = -4;
        goto OUT;
    }
OUT:
    sqlite3_finalize(stat);
    if( rv < 0 )
        log_error("add new blob packet into database failure, rv=%d\n", rv);
    else
        log_info("add new blob packet into database ok\n");
    return rv;
}
/* description: pop the first blob packet from database
 * input args:
 *      $pack:  blob packet output buffer address
 *      $size:  blob packet output buffer size
 *      $byte:  blob packet bytes
 * return value: <0: failure   0:ok
 */
int database_pop_packet(void *pack, int size, int *bytes)
{
    char               sql[SQL_COMMAND_LEN]={0};
    int                rv = 0;
    sqlite3_stmt      *stat = NULL;
    const void        *blob_ptr;
    if( !pack || size<=0 )
    {
        log_error("%s() Invalid input arguments\n", __func__);
        return -1;
    }
    if( ! s_clidb )
    {
        log_error("sqlite database not opened\n");
        return -2;
    }
    /* Only query the first packet record */
    snprintf(sql, sizeof(sql), "SELECT packet FROM %s WHERE rowid = (SELECT rowid FROM %s LIMIT 1);", TABLE_NAME, TABLE_NAME);
    rv = sqlite3_prepare_v2(s_clidb, sql, -1, &stat, NULL);
    if(SQLITE_OK!=rv || !stat)
    {
        log_error("firehost sqlite3_prepare_v2 failure\n");
        rv = -3;
        goto out;
    }
    rv = sqlite3_step(stat);
    if( SQLITE_DONE!=rv && SQLITE_ROW!=rv )
    {
        log_error("firehost sqlite3_step failure\n");
        rv = -5;
        goto out;
    }
    /* 1rd argument<0> means first segement is packet  */
    blob_ptr = sqlite3_column_blob(stat, 0);
    if( !blob_ptr )
    {
        rv = -6;
        goto out;
    }
    *bytes = sqlite3_column_bytes(stat, 0);
    if( *bytes > size )
    {
        log_error("blob packet bytes[%d] larger than bufsize[%d]\n", *bytes, size);
        *bytes = 0;
        rv = -1;
    }
    memcpy(pack, blob_ptr, *bytes);
    rv = 0;
out:
    sqlite3_finalize(stat);
    return rv;
}
/* description: remove the first blob packet from database
 * input args: none
 * return value: <0: failure   0:ok
 */
int database_del_packet(void)
{
    char               sql[SQL_COMMAND_LEN]={0};
    char              *errmsg = NULL;
    if( ! s_clidb )
    {
        log_error("sqlite database not opened\n");
        return -2;
    }
    /*  remove packet from db */
    memset(sql, 0, sizeof(sql));
    snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE rowid = (SELECT rowid FROM %s LIMIT 1);", TABLE_NAME, TABLE_NAME);
    if( SQLITE_OK != sqlite3_exec(s_clidb, sql, NULL, 0, &errmsg) )
    {
        log_error("delete first blob packet from database failure: %s\n", errmsg);
        sqlite3_free(errmsg);
        return -2;
    }
    log_warn("delete first blob packet from database ok\n");
    /*  Vacuum the database */
    sqlite3_exec(s_clidb, "VACUUM;", NULL, 0, NULL);
    return 0;
}
project/2.socketd/booster/database.h
New file
@@ -0,0 +1,60 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  database.h
 *    Description:  This library used to operate blob packet in sqlite database.
 *
 *        Version:  1.0.0(2020年05月13日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2020年05月13日 12时14分23秒"
 *
 ********************************************************************************/
#ifndef  _DATABASE_H_
#define  _DATABASE_H_
#include "sqlite3.h"
#define SQL_COMMAND_LEN        256
/*  description: open or create sqlite database if not exist
 *   input args:
 *              $db_file: sqlite database file name
 * return value: <0: failure   0:ok
 * */
extern int database_init(const char *db_file);
/*  description: close sqlite database handler
 * return value: none
 */
extern void database_term(void);
/*  description: push a blob packet into database
 *   input args:
 *               $pack:  blob packet data address
 *               $size:  blob packet data bytes
 * return value: <0: failure   0:ok
 */
extern int database_push_packet(void *pack, int size);
/*  description: pop the first blob packet from database
 *   input args:
 *               $pack:  blob packet output buffer address
 *               $size:  blob packet output buffer size
 *               $byte:  blob packet bytes
 * return value: <0: failure   0:ok
 */
extern int database_pop_packet(void *pack, int size, int *bytes);
/*  description: remove the first blob packet from database
 *   input args: none
 * return value: <0: failure   0:ok
 */
extern int database_del_packet(void);
#endif   /* ----- #ifndef _DATABASE_H_  ----- */
project/2.socketd/booster/ds18b20.c
New file
@@ -0,0 +1,118 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.c
 *    Description:  This file is temperature sensor DS18B20 code
 *
 *        Version:  1.0.0(2023/8/10)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
 *
 * Pin connection:
 *
 *               DS18B20 Module          Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   DQ       <----->      #Pin7(BCM GPIO4)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=w1-gpio-pullup,gpiopin=4
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "logger.h"
/* File Content:
   pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
   3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
   3a 01 4b 46 7f ff 0c 10 a5 t=19625
   */
int ds18b20_get_temperature(float *temp)
{
    char            w1_path[50] = "/sys/bus/w1/devices/";
    char            chip[20];
    char            buf[128];
    DIR            *dirp;
    struct dirent  *direntp;
    int             fd =-1;
    char           *ptr;
    int             found = 0;
    if( !temp )
    {
        return -1;
    }
    /*+-------------------------------------------------------------------+
     *|  open dierectory /sys/bus/w1/devices to get chipset Serial Number |
     *+-------------------------------------------------------------------+*/
    if((dirp = opendir(w1_path)) == NULL)
    {
        log_error("opendir error: %s\n", strerror(errno));
        return -2;
    }
    while((direntp = readdir(dirp)) != NULL)
    {
        if(strstr(direntp->d_name,"28-"))
        {
            /* find and get the chipset SN filename */
            strcpy(chip,direntp->d_name);
            found = 1;
            break;
        }
    }
    closedir(dirp);
    if( !found )
    {
        log_error("Can not find ds18b20 in %s\n", w1_path);
        return -3;
    }
    /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
    strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
    strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
    /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
    if( (fd=open(w1_path, O_RDONLY)) < 0 )
    {
        log_error("open %s error: %s\n", w1_path, strerror(errno));
        return -4;
    }
    if(read(fd, buf, sizeof(buf)) < 0)
    {
        log_error("read %s error: %s\n", w1_path, strerror(errno));
        return -5;
    }
    ptr = strstr(buf, "t=");
    if( !ptr )
    {
        log_error("ERROR: Can not get temperature\n");
        return -6;
    }
    ptr+=2;
    /* convert string value to float value */
    *temp = atof(ptr)/1000;
    close(fd);
    return 0;
}
project/2.socketd/booster/ds18b20.h
New file
@@ -0,0 +1,31 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.h
 *    Description:  This file is temperature sensor DS18B20 code
 *
 *        Version:  1.0.0(2023/8/10)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
 *
 * Pin connection:
 *
 *               DS18B20 Module          Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   DQ       <----->      #Pin7(BCM GPIO4)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=w1-gpio-pullup,gpiopin=4
 *
 ********************************************************************************/
#ifndef  _DS18B20_H_
#define  _DS18B20_H_
extern int ds18b20_get_temperature(float *temp);
#endif   /* ----- #ifndef _DS18B20_H_  ----- */
project/2.socketd/booster/list.h
New file
@@ -0,0 +1,723 @@
/*********************************************************************************
 *      Copyright:  (C) 2012 Guo Wenxue <guowenxue@gmail.com>
 *                  All rights reserved.
 *
 *       Filename:  list.h
 *    Description:  This file is copied from Linux kernel, which provide link list API.
 *
 *        Version:  1.0.0(08/09/2012~)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "08/09/2012 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
project/2.socketd/booster/logger.c
New file
@@ -0,0 +1,279 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.c
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#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[] = {
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
};
static const char *level_colors[] = {
    "\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);
    }
}
project/2.socketd/booster/logger.h
New file
@@ -0,0 +1,70 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.h
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _LOGGER_H_
#define  _LOGGER_H_
#include <stdio.h>
#include <stdarg.h>
#define LOG_VERSION "v0.1"
/* log level */
enum {
    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
 */
#define THREAD_LOCK_NONE       0
#define THREAD_LOCK_EN         1
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__)
#endif
project/2.socketd/booster/makefile
New file
@@ -0,0 +1,34 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file used compile all the source code to static library
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PWD=$(shell pwd )
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE?=arm-linux-gnueabihf-
endif
LIBNAME=$(shell basename ${PWD} )
TOPDIR=$(shell dirname ${PWD} )
all: clean
    @rm -f *.o
    @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
    ${CROSS_COMPILE}ar -rcs  lib${LIBNAME}.a *.o
clean:
    @rm -f *.o
    @rm -f *.a
distclean:
    @make clean
project/2.socketd/booster/packet.c
New file
@@ -0,0 +1,357 @@
/*********************************************************************************
 *      Copyright:  (C) 2022 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  packet.c
 *    Description:  This file is packet API functions
 *
 *        Version:  1.0.0(18/04/22)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "18/04/22 16:30:25"
 *
 ********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "packet.h"
#include "logger.h"
#include "ds18b20.h"
int get_devid(char *devid, int size, int sn)
{
    if( !devid || size<DEVID_LEN )
    {
        log_error("Invalid input arugments\n");
        return -1;
    }
    memset(devid, 0, size);
    snprintf(devid, size, "RPI#%04d", sn);
    return 0;
}
int get_time(struct tm *ptm)
{
    if( !ptm )
    {
        log_error("Invalid input arugments\n");
        return -1;
    }
    time_t now = time(NULL);
    localtime_r(&now, ptm);
    return 0;
}
int packet_segmented_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size)
{
    char              strtime[TIME_LEN] = {'\0'};
    struct tm        *ptm;
    char             *buf = (char *)pack_buf;
    if( !pack_info || !buf || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    ptm = &pack_info->sample_time;
    snprintf(strtime, sizeof(strtime), "%04d-%02d-%02d %02d:%02d:%02d",
            ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
            ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    memset(buf, 0, size);
    snprintf(buf, size, "%s|%s|%.3f", pack_info->devid, strtime, pack_info->temper);
    return strlen(buf);
}
int packet_json_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size)
{
    char              strtime[TIME_LEN] = {'\0'};
    struct tm        *ptm;
    char             *buf = (char *)pack_buf;
    if( !pack_info || !buf || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    ptm = &pack_info->sample_time;
    snprintf(strtime, sizeof(strtime), "%04d-%02d-%2d %02d:%02d:%02d",
            ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
            ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    memset(buf, 0, size);
    snprintf(buf, size, "{\"devid\":\"%s\", \"time\":\"%s\",\"temperature\":\"%.3f\"}",
            pack_info->devid, strtime, pack_info->temper);
    return strlen(buf);
}
/*
 * description: compute the CRC-ITU-T for the data buffer
 *   input args:
 *               $data  :  data pointer
 *               $length:  number of bytes in the buffer
 * return value: the 16-bits CRC value
 */
/*
 * The CRC-ITU-T, also known as CRC-16-ITU-T or CRC-16-V.41, uses the polynomial:
 *     0x1021:  x^16 + x^12 + x^5 + 1
 */
#define CRC16_ITU_T_POLY 0x1021   /* Define the CRC-ITU-T polynomial */
uint16_t crc_itu_t(const uint8_t *data, size_t length)
{
    uint16_t          crc = 0xFFFF;
    size_t            i, j;
    for (i=0; i<length; i++)
    {
        crc ^= ((uint16_t)data[i] << 8);
        for(j=0; j<8; j++)
        {
            if (crc & 0x8000)
            {
                crc = (crc << 1) ^ CRC16_ITU_T_POLY;
            }
            else
            {
                crc <<= 1;
            }
        }
    }
    return crc;
}
int ushort_to_bytes(uint16_t val, uint8_t *bytes)
{
    int              size  = sizeof (uint16_t);
    int              i = 0;
    if( !bytes )
        return 0;
    while (size)
    {
        *(bytes + --size) = (val >> ((8 * i++) & 0xFF));
    }
    return sizeof(uint16_t);
}
uint16_t bytes_to_ushort(uint8_t *bytes, int len)
{
    int            i = 0;
    uint16_t       val = 0;
    if( !bytes || len > sizeof(uint16_t) )
        return 0;
    for(i=0; i<len; i++)
    {
        val += bytes[i];
        if (i < len - 1)
            val = val << 8;
    }
    return val;
}
/* TLV(Tag Length Value) PDU(Protocal Data Unit) format:
 *
 * +-----------+-----------+------------+-------------+-------------+
 * | Header(2B)|  Tag(1B)  | Length(2B) |  Value(nB)  |  CRC16(2B)  |
 * +-----------+-----------+------------+-------------+-------------+
 *
 * Header(2B): 0xFE 0xFD
 *    Tag(1B): 0x01->temperature 0x02->Humidity 0x03->Noisy ...
 * Length(2B): Data value length, include Header+Tag+Length+Value+CRC16
 *  Value(nB): Data value
 *  CRC16(2B): CRC from Header to Value
 */
/*  description: package a TLV packet
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure   >0: packet bytes
 */
int packet_tlv_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size)
{
    struct tm          *ptm;
    int                 ofset = 0;
    uint16_t            len;
    uint16_t            crc;
    if( !pack_info || !pack_buf || size<TLV_FIXSIZE )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    /*+----------------------+
     *|    TLV Header(2B)    |
     *+----------------------+*/
    ofset += ushort_to_bytes(TLV_HEADER, pack_buf);
    /*+----------------------+
     *|      TLV Tag(1B)     |
     *+----------------------+*/
    pack_buf[ofset++] = TAG_TEMPERATURE;
    /* Skip data length here, we will calculate it later */
    ofset += 2;
    /*+----------------------+
     *|  payload data value  |
     *+----------------------+*/
    /* 6 bytes sample time */
    ptm = &pack_info->sample_time;
    pack_buf[ofset++] = (uint8_t)(ptm->tm_year+1900-2000);
    pack_buf[ofset++] = (uint8_t)(ptm->tm_mon+1);
    pack_buf[ofset++] = (uint8_t)(ptm->tm_mday);
    pack_buf[ofset++] = (uint8_t)(ptm->tm_hour);
    pack_buf[ofset++] = (uint8_t)(ptm->tm_min);
    pack_buf[ofset++] = (uint8_t)(ptm->tm_sec);
    /* 8 bytes device SN */
    strncpy((char *)(pack_buf+ofset), pack_info->devid, DEVID_LEN);
    ofset += DEVID_LEN;
    /* 2 bytes temperature value */
    pack_buf[ofset++] = (int)pack_info->temper;  /* integer part */
    pack_buf[ofset++] = (((int)(pack_info->temper*100))%100); /* fractional part */
    /*+----------------------+
     *|    TLV Length(2B)    |
     *+----------------------+*/
    len = ofset + 2; /* include CRC */
    ushort_to_bytes(len, pack_buf+3);
    /*+----------------------+
     *|      TLV CRC(2B)     |
     *+----------------------+*/
    crc = crc_itu_t(pack_buf, ofset); /* from Header to payload data */
    ofset += ushort_to_bytes(crc, pack_buf+ofset);
    return ofset;
}
/*  description: package a TLV packet.
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure  0: not integrated  >0: processed packet len
 */
int parser_tlv_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size)
{
    int                 ofset = 0;
    uint16_t            crc;
    uint16_t            payload_len;
    uint16_t            pack_head;
    uint8_t             pack_tag;
    uint16_t            pack_len;
    uint16_t            pack_crc;
    if( !pack_info || !pack_buf )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( size < TLV_FIXSIZE )
    {
        log_warn("TLV packet bytes less than min. size\n");
        return 0;
    }
    /*+----------------------+
     *|    TLV Header(2B)    |
     *+----------------------+*/
    pack_head = bytes_to_ushort(pack_buf, 2);
    ofset += 2;
    if( TLV_HEADER != pack_head)
        return -2;
    /*+----------------------+
     *|      TLV Tag(1B)     |
     *+----------------------+*/
    pack_tag = pack_buf[ofset];
    ofset ++;
    /*+----------------------+
     *|    TLV Length(2B)    |
     *+----------------------+*/
    pack_len = bytes_to_ushort(pack_buf+ofset, 2);
    ofset += 2;
    log_debug("header: 0x%02x tag: 0x%02x len: 0x%02x\n", pack_head, pack_tag, pack_len);
    /* Packet length is from Header to CRC16(include) */
    if( pack_len > size )
    {
        log_warn("TLV packet bytes less than length\n");
        return 0;
    }
    /*+----------------------+
     *|      TLV CRC(2B)     |
     *+----------------------+*/
    /* packet CRC */
    pack_crc = bytes_to_ushort(pack_buf+pack_len-2, 2);
    /* calculate CRC  */
    payload_len = pack_len - TLV_FIXSIZE;
    crc = crc_itu_t(pack_buf, ofset+payload_len); /* from Header to payload data */
    if( crc != pack_crc)
    {
        log_error("calculate CRC[0x%04x] != packet CRC[0x%04x]\n", crc, pack_crc);
        return -3;
    }
    /*+----------------------+
     *|  payload data value  |
     *+----------------------+*/
    pack_info->tag = pack_tag;
    if( pack_tag == TAG_TEMPERATURE )
    {
        /* 6 bytes sample time */
        pack_info->sample_time.tm_year = pack_buf[ofset++] + 2000 - 1900;
        pack_info->sample_time.tm_mon = pack_buf[ofset++] - 1;
        pack_info->sample_time.tm_mday = pack_buf[ofset++] ;
        pack_info->sample_time.tm_hour = pack_buf[ofset++];
        pack_info->sample_time.tm_min = pack_buf[ofset++];
        pack_info->sample_time.tm_sec = pack_buf[ofset++];
        /* 8 bytes device SN */
        strncpy(pack_info->devid, (char *)(pack_buf+ofset), DEVID_LEN);
        ofset += DEVID_LEN;
        /* 2 bytes temperature value */
        pack_info->temper = (float)pack_buf[ofset] + (float)pack_buf[ofset+1]/100.0f;
    }
    return pack_len;
}
project/2.socketd/booster/packet.h
New file
@@ -0,0 +1,141 @@
/********************************************************************************
 *      Copyright:  (C) 2022 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  packet.h
 *    Description:  This head file is packet API functions.
 *
 *        Version:  1.0.0(18/04/22)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "18/04/22 16:24:40"
 *
 ********************************************************************************/
#ifndef  _PACKET_H_
#define  _PACKET_H_
#include <stdint.h>
#include <time.h>
#define DEVID_LEN          8
#define TIME_LEN           32
typedef struct pack_info_s
{
    uint8_t       tag;                 /* Tag for TLV */
    char          devid[DEVID_LEN+1];  /* device ID  */
    struct tm     sample_time;         /* sample time  */
    float         temper;              /* sample temperature */
} pack_info_t;
/* packet function pointer type */
typedef int (* pack_proc_t)(pack_info_t *pack_info, uint8_t *pack_buf, int size);
/*  description: get device ID
 *   input args:
 *               $devid :  device ID string
 *               $size  :  device ID output buffer size
 *               $sn    :  serial number
 * return value: <0: failure   0:ok
 */
extern int get_devid(char *devid, int size, int sn);
/*  description: get current system in struct tm
 *   input args:
 *               $sample_time:  sample time in struct tm
 * return value: <0: failure   0:ok
 */
extern int get_time(struct tm *sample_time);
/*  description: package a string packet in format "devid|time|temper"
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure   >0: packet bytes
 */
extern int packet_segmented_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size);
/*  description: package a json string packet: {"devid":"xxx", "time":"xxx", "temperature":"xxx"}
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure   >0: packet bytes
 */
extern int packet_json_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size);
/* TLV(Tag Length Value) PDU(Protocal Data Unit) format:
 *
 * +-----------+-----------+------------+-------------+-------------+
 * | Header(2B)|  Tag(1B)  | Length(2B) |  Value(nB)  |  CRC16(2B)  |
 * +-----------+-----------+------------+-------------+-------------+
 *
 * Header(2B): 0xFE 0xED
 *    Tag(1B): 0x01->temperature 0x02->Humidity 0x03->Noisy ...
 * Length(2B): Data length
 *  Value(nB): Data value
 *  CRC16(2B): CRC from Header to Value
 */
/* TLV Header */
#define TLV_HEADER          0xFEED
/* TLV bytes without payload: Header(2B)+Tag(1B)+Length(2B)+CRC16(2B) */
#define TLV_FIXSIZE         7
/* TLV packet length are no more than 32 bytes */
#define TLV_MAXSIZE         32
/* TVL Tag definition */
enum
{
    TAG_TEMPERATURE = 1,
    TAG_HUMIDITY,
    TAG_NOISY,
};
typedef struct tlv_pack_s
{
    uint16_t            header;
    uint8_t             tag;
    uint16_t            len;
    uint8_t             data[0]; /* Zero length array */
    /* followed: $len bytes payload data */
    /* followed: 2 bytes CRC checksum    */
} tlv_pack_t;
extern int ushort_to_bytes(uint16_t val, uint8_t *bytes);
extern uint16_t bytes_to_ushort(uint8_t *bytes, int len);
/*
 * description: compute the CRC-ITU-T for the data buffer
 *   input args:
 *               $data  :  data pointer
 *               $length:  number of bytes in the buffer
 * return value: the 16-bits CRC value
 */
extern uint16_t crc_itu_t(const uint8_t *data, size_t length);
/*  description: package a TLV packet
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure   >0: packet bytes
 */
int packet_tlv_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size);
/*  description: package a TLV packet
 *   input args:
 *               $pack_info:  packet data contains devid, time and temperature
 *               $pack_buf :  packet output buffer
 *               $size     :  packet output buffer size
 * return value: <0: failure  0: not integrated  >0: processed packet len
 */
int parser_tlv_pack(pack_info_t *pack_info, uint8_t *pack_buf, int size);
#endif   /* ----- #ifndef _PACKET_H_  ----- */
project/2.socketd/booster/proc.c
New file
@@ -0,0 +1,432 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  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 "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\n");
        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)
{
    daemon(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);
}
project/2.socketd/booster/proc.h
New file
@@ -0,0 +1,89 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  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 __PROC_H_
#define __PROC_H_
#include <signal.h>
#include <time.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);
/* +------------------------+
 * |  inline functions API  |
 * +------------------------+*/
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
#endif
project/2.socketd/booster/socket.c
New file
@@ -0,0 +1,658 @@
/********************************************************************************
 *      Copyright:  (C) 2022 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  socket.c
 *    Description:  This file is for socket API functions
 *
 *        Version:  1.0.0(18/04/22)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "18/04/22 17:09:59"
 *
 ********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "socket.h"
#include "logger.h"
/*  description: initial socket context
 *   input args:
 *               $sock:  socket context pointer
 *               $host:  connect server hostname for client mode, unused for server mode
 *               $port:  connect server port for client mode or listen port for server mode
 * return value: <0: failure   0:ok
 */
int socket_init(socket_t *sock, char *host, int port)
{
    if( !sock || port<=0 )
        return -1;
    memset( sock, 0, sizeof(*sock) );
    sock->fd = -1;
    sock->port = port;
    if( host ) /* server no need it */
    {
        strncpy(sock->host, host, HOSTNAME_LEN);
    }
    return 0;
}
/*  description: close socket
 *   input args:
 *               $sock:  socket context pointer
 * return value: <0: failure   0:ok
 */
int socket_term(socket_t *sock)
{
    if( !sock )
        return -1;
    if( sock->fd > 0)
    {
        close(sock->fd);
        sock->fd = -1;
        sock->connected = 0;
    }
    return 0;
}
/*  description: socket server start listen
 *   input args:
 *               $sock:  socket context pointer
 *               $port:  socket listen port
 * return value: <0: failure   0:ok
 */
int socket_listen(socket_t *sock, int port)
{
    int                     listen_fd;
    int                     reuse = 1;
    struct sockaddr_in      serv_addr;
    if( !sock )
        return -1;
    /* set open file description number to max */
    set_socket_rlimit();
    /* create TCP socket */
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd < 0 )
    {
        log_error("create socket failure: %s\n", strerror(errno));
        return -1;
    }
    /* resolve the "Address already in use" error when using bind() */
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    /* bind socket server information */
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if( bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0 )
    {
        log_error("bind socket failure: %s\n", strerror(errno));
        return -2;
    }
    log_info("socket[%d] bind on port[%d] for all IP address ok\n", listen_fd, port);
    /* start listen socket */
    listen(listen_fd, 13);
    sock->port = port;
    sock->fd = listen_fd;
    return listen_fd;
}
/*  description: create epoll for socket server and add listenfd into it
 *   input args: $max_evts:  max events for epoll_create()
 *               $listenfd:  listen socket fd
 * return value: <0: failure  >=0: epollfd
 */
int epoll_init(int max_evts, int listenfd)
{
    int                       epollfd;
    if( max_evts<=0 || listenfd<0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( (epollfd=epoll_create(max_evts)) < 0 )
    {
        log_error("epoll_create() failure: %s\n", strerror(errno));
        return -2;
    }
    if( epoll_add(epollfd, listenfd) < 0 )
    {
        log_error("epoll add listen socket[%d] failure: %s\n", listenfd, strerror(errno));
        close(epollfd);
        return -2;
    }
    log_info("epoll add listen socket[%d] ok\n", listenfd);
    return epollfd;
}
/*  description: close epoll
 *   input args: $epollfd:  epoll fd
 * return value: void
 */
void epoll_term(int epollfd)
{
    if(epollfd > 0 )
        close(epollfd);
}
/*  description: add new fd into epoll to monitor
 *   input args: $epollfd:  epoll fd
 *               $fd:       socket fd need added into epoll
 * return value: <0: failure  0: successfully
 */
int epoll_add(int epollfd, int fd)
{
    struct epoll_event        event;
    if( epollfd<0 || fd<0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    //event.events = EPOLLIN | EPOLLET;  /* Edge Triggered  */
    event.events = EPOLLIN; /* default Level Triggered */
    event.data.fd = fd;
    if( epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) < 0)
    {
        log_error("epoll add fd[%d] failure: %s\n", fd, strerror(errno));
        return -2;
    }
    return 0;
}
/*  description: delete fd from epoll to monitor
 *   input args: $epollfd:  epoll fd
 *               $fd:       socket fd need added into epoll
 * return value: <0: failure  0: successfully
 */
int epoll_del(int epollfd, int fd)
{
    if( epollfd<0 || fd<0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL) < 0)
    {
        log_error("epoll del fd[%d] failure: %s\n", fd, strerror(errno));
        return -2;
    }
    return 0;
}
/*  description: check socket connect status
 *   input args:
 *               $sock:  socket context pointer
 * return value: 1: connected   0:disconnected
 */
int socket_connected(socket_t *sock)
{
    struct tcp_info   info;
    int               len=sizeof(info);
    int               changed = 0;
    if( !sock )
    {
        return 0;
    }
    if( sock->fd < 0 )
    {
        /* socket is connected before but got disconnected now */
        changed = sock->connected ? 1 : 0;
        sock->connected = 0;
        goto out;
    }
    getsockopt(sock->fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
    if( TCP_ESTABLISHED==info.tcpi_state )
    {
        /* socket is disconnected before but got connected now */
        changed = !sock->connected ? 1 : 0;
        sock->connected = 1;
    }
    else
    {
        /* socket is connected before but got disconnected now */
        changed = sock->connected ? 1 : 0;
        sock->connected = 0;
    }
out:
    if( changed )
    {
        log_info("socket status got %s\n", sock->connected?"connected":"disconnected");
    }
    return sock->connected;
}
/*  description: socket connect to server in block mode
 *   input args:
 *               $sock:  socket context pointer
 * return value: <0: failure   0:ok
 */
int socket_connect(socket_t *sock)
{
    int                 rv = 0;
    int                 sockfd = 0;
    char                service[20];
    struct addrinfo     hints, *rp;
    struct addrinfo    *res = NULL;
    struct in_addr      inaddr;
    struct sockaddr_in  addr;
    int                 len = sizeof(addr);
    if( !sock )
        return -1;
    socket_term(sock);
    /*+--------------------------------------------------+
     *| use getaddrinfo() to do domain name translation  |
     *+--------------------------------------------------+*/
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Only support IPv4 */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP; /* TCP protocol */
    /* If $host is a valid IP address, then don't use name resolution */
    if( inet_aton(sock->host, &inaddr) )
    {
        //log_info("%s is a valid IP address, don't use domain name resolution.\n", sock->host);
        hints.ai_flags |= AI_NUMERICHOST;
    }
    /* Obtain address(es) matching host/port */
    snprintf(service, sizeof(service), "%d", sock->port);
    if( (rv=getaddrinfo(sock->host, service, &hints, &res)) )
    {
        log_error("getaddrinfo() parser [%s:%s] failed: %s\n", sock->host, service, gai_strerror(rv));
        return -3;
    }
    /* getaddrinfo() returns a list of address structures. Try each
       address until we successfully connect or bind */
    for (rp=res; rp!=NULL; rp=rp->ai_next)
    {
#if 0
        char                  ipaddr[INET_ADDRSTRLEN];
        struct sockaddr_in   *sp = (struct sockaddr_in *) rp->ai_addr;
        /* print domain name translation result */
        memset( ipaddr, 0, sizeof(ipaddr) );
        if( inet_ntop(AF_INET, &sp->sin_addr, ipaddr, sizeof(ipaddr)) )
        {
            log_info("domain name resolution [%s->%s]\n", sock->host, ipaddr);
        }
#endif
        /*  Create the socket */
        sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if( sockfd < 0)
        {
            log_error("socket() create failed: %s\n", strerror(errno));
            rv = -3;
            continue;
        }
        /* connect to server */
        rv = connect(sockfd, rp->ai_addr, len);
        if( 0 == rv )
        {
            sock->fd = sockfd;
            log_info("Connect to server[%s:%d] on fd[%d] successfully!\n", sock->host, sock->port, sockfd);
            break;
        }
        else
        {
            /* socket connect get error, try another IP address */
            close(sockfd);
            continue;
        }
    }
    freeaddrinfo(res);
    return rv;
}
/*  description: send data from the socket
 *   input args:
 *               $sock :  socket context pointer
 *               $data :  socket send data
 *               $bytes:  socket send data bytes
 * return value: <0: failure   0:ok
 */
int socket_send(socket_t *sock, char *data, int bytes)
{
    int            rv = 0;
    int            i = 0;
    int            left_bytes = bytes;
    if( !sock || !data || bytes<= 0 )
        return -1;
    while( left_bytes > 0 )
    {
        rv=write(sock->fd, &data[i], left_bytes);
        if( rv < 0 )
        {
            log_info("socket[%d] write() failure: %s, close socket now\n", sock->fd, strerror(errno));
            socket_term(sock);
            return -2;
        }
        else if( rv == left_bytes )
        {
            log_info("socket send %d bytes data over\n", bytes);
            return 0;
        }
        else
        {
            /* not send over this time, continue to send left data  */
            i += rv;
            left_bytes -= rv;
            continue;
        }
    }
    return 0;
}
/*  description: receive data from the socket
 *   input args:
 *               $sock :  socket context pointer
 *               $buf  :  socket receive data buffer
 *               $size :  socket receive data buffer size
 *               $timeout: receive data time, <=0 will don't timeout
 * return value: <0: failure   0:ok
 */
int socket_recv(socket_t *sock, char *buf, int size, int timeout)
{
    int               rv = 0;
    fd_set            rdset;
    int               maxfd;
    if( !sock || !buf || size<= 0 )
        return -1;
    memset(buf, 0, size);
    maxfd = sock->fd;
    FD_ZERO(&rdset);
    FD_SET(sock->fd, &rdset);
    if( timeout <= 0 ) /* no timeout  */
    {
        rv=select(maxfd+1, &rdset, NULL, NULL, NULL);
    }
    else
    {
        struct timeval    tv;
        tv.tv_sec = timeout;
        tv.tv_usec = 0;
        rv=select(maxfd+1, &rdset, NULL, NULL, &tv);
    }
    if( rv < 0 )
    {
        log_error("select() on socket[%d] got error: %s\n", sock->fd, strerror(errno));
        return -2;
    }
    else if( rv == 0 )
    {
        log_error("select() on socket[%d] get timeout\n", sock->fd);
        return 0;
    }
    else
    {
        rv = read(sock->fd, buf, size);
        if( rv <= 0 )
        {
            log_error("socket[%d] read() failure or got disconnected: %s, close socket now\n", sock->fd, strerror(errno));
            socket_term(sock);
            return -2;
        }
        else
        {
            log_debug("socket[%d] receive %d bytes data\n", sock->fd, rv);
            return rv;
        }
    }
}
/*+-------------------------------------------------------------------+
 *|                socket utils function                              |
 *+-------------------------------------------------------------------+*/
/* description: set socket listen port as reusable, fix port already used bug  */
int socket_set_reuseaddr(int sockfd)
{
    int opt = 1;
    int len = sizeof (int);
    if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, len))
    {
        log_error("Set socket[%d] option SO_REUSEADDR failed:%s\n", sockfd, strerror(errno));
        return -1;
    }
    log_debug("Set socket[%d] option SO_REUSEADDR ok\n", sockfd);
    return 0;
}
/* set socket as non-block mode, common socket default work as block mode */
int socket_set_nonblock(int sockfd)
{
    int opts;
    /*
     * fcntl may set:
     *
     * EACCES, EAGAIN: Operation is prohibited by locks held by other
     *          processes. Or, operation is prohibited because the file has
     *          been memory-mapped by another process.
     * EBADF:   fd is not an open file descriptor, or the command was F_SETLK
     *          or F_SETLKW and the file descriptor open mode doesn't match
     *          with the type of lock requested.
     * EDEADLK: It was detected that the specified F_SETLKW command would
     *          cause a deadlock.
     * EFAULT:  lock is outside your accessible address space.
     * EINTR:   For F_SETLKW, the command was interrupted by a signal. For
     *          F_GETLK and F_SETLK, the command was interrupted by a signal
     *          before the lock was checked or acquired. Most likely when
     *          locking a remote file (e.g. locking over NFS), but can
     *          sometimes happen locally.
     * EINVAL:  For F_DUPFD, arg is negative or is greater than the maximum
     *          allowable value. For F_SETSIG, arg is not an allowable signal
     *          number.
     * EMFILE:  For F_DUPFD, the process already has the maximum number of
     *          file descriptors open.
     * ENOLCK:  Too many segment locks open, lock table is full, or a remote
     *          locking protocol failed (e.g. locking over NFS).
     * EPERM:   Attempted to clear the O_APPEND flag on a file that has the
     *          append-only attribute set.
     */
    opts = fcntl(sockfd, F_GETFL);
    if (opts < 0)
    {
        log_warn("fcntl() get socket options failure: %s\n", strerror(errno));
        return -1;
    }
    opts |= O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, opts) < 0)
    {
        log_warn("fcntl() set socket options failure: %s\n", strerror(errno));
        return -1;
    }
    log_debug("Set socket[%d] none blocking\n", sockfd);
    return opts;
}
/* set socket receive and send buffer size in linux kernel space */
int socket_set_buffer(int sockfd, int rsize, int ssize)
{
    int        opt;
    socklen_t  optlen = sizeof(opt);
    if(sockfd < 0)
        return -1;
    /* Get system default receive buffer size, Linux X86: 85K */
    if (getsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, &optlen))
    {
        log_warn("getsockopt() get receive buffer failure: %s\n", strerror(errno));
        return -2;
    }
    /* Only when current receive buffer size larger than the default one will change it  */
    if(rsize > opt)
    {
        opt = (int) rsize;
        if (setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, optlen))
        {
            log_warn("setsockopt() set receive buffer to %d failure: %s\n", opt, strerror(errno));
            return -2;
        }
    }
    /* Get system default send buffer size, Linux X86: 16K */
    if (getsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, &optlen))
    {
        log_warn("getsockopt() get send buffer failure: %s\n", strerror(errno));
        return -3;
    }
    /* Only when current receive buffer size larger than the default one will change it  */
    if(ssize > opt)
    {
        opt = (int) ssize;
        if (setsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, optlen))
        {
            log_warn("setsockopt() set send buffer to %d failure: %s\n", opt, strerror(errno));
            return -3;
        }
    }
    log_info("Set socket[%d] RCVBUF size:%d  SNDBUF size:%d\n", sockfd, rsize, ssize);
    return 0;
}
/*
 * Enable socket SO_KEEPALIVE, if the connection disconnected, any system call on socket
 * will return immediately and errno will be set to "WSAENOTCONN"
 *
 * keepalive is not program related, but socket related, * so if you have multiple sockets,
 * you can handle keepalive for each of them separately.
 *
 * Reference: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
 */
int socket_set_keepalive(int sockfd, int keepintvl, int keepcnt)
{
    int  opt;
    if(sockfd < 0)
        return -1;
    /* Enable the KEEPALIVE flag */
    opt = 1;
    if (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof (opt)))
    {
        log_warn("setsockopt() enable SO_KEEPALIVE failure: %s\n", strerror(errno));
        return -2;
    }
    if(keepintvl || keepcnt)
    {
        /*
         *  The tcp_keepidle parameter specifies the interval between the last data packet sent
         *  (simple ACKs are not considered data) and the first keepalive probe; after the
         *  connection is marked to need keepalive, this counter is not used any further.
         *  ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_time
         *  7200
         */
        opt = 3; /* 3 seconds  */
        if (setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof (opt)))
        {
            log_error("setsockopt() set TCP_KEEPIDLE to %d seconds failure: %s\n", opt, strerror(errno));
            return -3;
        }
        if((opt=keepintvl) > 0)
        {
            /*
             * The tcp_keepintvl parameter specifies the interval between subsequential keepalive
             * probes, regardless of what the connection has exchanged in the meantime.
             * ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_intvl
             * 75
             */
            if (setsockopt (sockfd, SOL_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof (opt)))
            {
                log_error("setsockopt() set TCP_KEEPINTVL to %d failure: %s\n", opt, strerror(errno));
                return -4;
            }
        }
        if((opt=keepcnt) > 0)
        {
            /*
             * The TCP_KEEPCNT option specifies the maximum number of unacknowledged probes to
             * send before considering the connection dead and notifying the application layer
             * probes to be sent. The value of TCP_KEEPCNT is an integer value between 1 and n,
             * where n is the value of the systemwide tcp_keepcnt parameter.
             * ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_probes
             * 9
             */
            if (setsockopt (sockfd, SOL_TCP, TCP_KEEPCNT, (char *) &opt, sizeof (opt)))
            {
                log_error("setsockopt() set TCP_KEEPCNT to %d failure: %s\n", opt, strerror(errno));
                return -5;
            }
        }
    }
    log_debug("Set socket[%d] KEEPINTVL:%d  KEEPCNT:%d\n", sockfd, keepintvl, keepcnt);
    return 0;
}
/* Set open file description count to max */
void set_socket_rlimit(void)
{
    struct rlimit limit = {0};
    getrlimit(RLIMIT_NOFILE, &limit );
    limit.rlim_cur  = limit.rlim_max;
    setrlimit(RLIMIT_NOFILE, &limit );
    log_info("set socket open fd max count to %d\n", limit.rlim_max);
}
project/2.socketd/booster/socket.h
New file
@@ -0,0 +1,146 @@
/********************************************************************************
 *      Copyright:  (C) 2022 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  socket.h
 *    Description:  This head file is for socket API functions
 *
 *        Version:  1.0.0(18/04/22)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "18/04/22 17:09:59"
 *
 ********************************************************************************/
#ifndef  _SOCKET_H_
#define  _SOCKET_H_
#include <poll.h>
#include <netdb.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#define HOSTNAME_LEN          64
typedef struct socket_s
{
    char        host[HOSTNAME_LEN]; /* CLIENT: Connect server hostname; SERVER: Unused */
    int         port;               /* CLIENT: Connect server port;     SERVER: listen port */
    int         fd;                 /* socket descriptor  */
    int         connected;          /* socket connect status: 1->connected 0->disconnected  */
} socket_t;
/*  description: initial socket context
 *   input args:
 *               $sock:  socket context pointer
 *               $host:  connect server hostname for client mode, unused for server mode
 *               $port:  connect server port for client mode or listen port for server mode
 * return value: <0: failure   0:ok
 */
extern int socket_init(socket_t *sock, char *host, int port);
/*  description: close socket
 *   input args:
 *               $sock:  socket context pointer
 * return value: <0: failure   0:ok
 */
extern int socket_term(socket_t *sock);
/*  description: socket server start listen
 *   input args:
 *               $sock:  socket context pointer
 *               $port:  socket listen port
 * return value: <0: failure   0:ok
 */
extern int socket_listen(socket_t *sock, int port);
/*  description: create epoll for socket server and add listenfd into it
 *   input args: $max_evts:  max events for epoll_create()
 *               $listenfd:  listen socket fd
 * return value: <0: failure  >=0: epollfd
 */
extern int epoll_init(int max_evts, int listenfd);
/*  description: close epoll
 *   input args: $epollfd:  epoll fd
 * return value: void
 */
extern void epoll_term(int epollfd);
/*  description: add new fd into epoll to monitor
 *   input args: $epollfd:  epoll fd
 *               $fd:       socket fd need added into epoll
 * return value: <0: failure  0: successfully
 */
extern int epoll_add(int epollfd, int fd);
/*  description: delete fd from epoll to monitor
 *   input args: $epollfd:  epoll fd
 *               $fd:       socket fd need added into epoll
 * return value: <0: failure  0: successfully
 */
extern int epoll_del(int epollfd, int fd);
/*  description: check socket connect status
 *   input args:
 *               $sock:  socket context pointer
 * return value: 1: connected   0:disconnected
 */
extern int socket_connected(socket_t *sock);
/*  description: socket client connect to server
 *   input args:
 *               $sock:  socket context pointer
 * return value: <0: failure   0:ok
 */
extern int socket_connect(socket_t *sock);
/*  description: send data from the socket
 *   input args:
 *               $sock :  socket context pointer
 *               $data :  socket send data
 *               $bytes:  socket send data bytes
 * return value: <0: failure   0:ok
 */
extern int socket_send(socket_t *sock, char *data, int bytes);
/*  description: receive data from the socket
 *   input args:
 *               $sock :  socket context pointer
 *               $buf  :  socket receive data buffer
 *               $size :  socket receive data buffer size
 *               $timeout: receive data time, <=0 will don't timeout
 * return value: <0: failure   0:ok
 */
#define TIMEOUT_NONE       0
extern int socket_recv(socket_t *sock, char *buf, int size, int timeout);
/*+-------------------------------------------------------------------+
 *|                socket utils function                              |
 *+-------------------------------------------------------------------+*/
/* description: set socket listen port as reusable, fix port already used bug  */
extern int socket_set_reuseaddr(int sockfd);
/* set socket as non-block mode, common socket default work as block mode */
extern int socket_set_nonblock(int sockfd);
/* set socket receive and send buffer size in linux kernel space */
extern int socket_set_buffer(int sockfd, int rsize, int ssize);
/* set heartbeat keepalive  */
extern int socket_set_keepalive(int sockfd, int keepintvl, int keepcnt);
/*  Set open file description count to max */
extern void set_socket_rlimit(void);
#endif   /* ----- #ifndef _SOCKET_H_  ----- */
project/2.socketd/makefile
New file
@@ -0,0 +1,67 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file is the project top Makefie
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PRJ_PATH=$(shell pwd)
APP_NAME = socket_client socket_server
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE=arm-linux-gnueabihf-
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
SRCS=booster
SRCS_PATH=$(patsubst %,${PRJ_PATH}/%,$(SRCS))
CFLAGS+=$(patsubst %,-I%,$(SRCS_PATH))
LDFLAGS+=$(patsubst %,-L%,$(SRCS_PATH))
LIBS=$(patsubst %,-l%,$(SRCS))
LDFLAGS+=${LIBS}
# Open source libraries
CFLAGS+=-I ${PRJ_PATH}/openlibs/install/include
LDFLAGS+=-L ${PRJ_PATH}/openlibs/install/lib
# libraries
libs=openlibs ${SRCS}
LDFLAGS+=-lsqlite3
LDFLAGS+=-lpthread
all: entry subdir
    ${CROSS_COMPILE}gcc ${CFLAGS} socket_client.c -o socket_client ${LDFLAGS}
    ${CROSS_COMPILE}gcc ${CFLAGS} socket_server.c -o socket_server ${LDFLAGS}
entry:
    @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
subdir:
    @for dir in ${libs} ;  do CFLAGS="${CFLAGS}" make -C $${dir}; done
install:
    cp ${APP_NAME} /tftp
clean:
    @for dir in ${SRCS} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
    @rm -f ${APP_NAME}
distclean:
    @for dir in ${libs} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
    @rm -f ${APP_NAME}
    @rm -f cscope.* tags
project/2.socketd/openlibs/makefile
New file
@@ -0,0 +1,13 @@
PRJ_PATH=$(shell pwd)
libs=$(shell ls -d */ 2>/dev/null | grep -v '^install/' | sed 's|/||g')
# clear CFLAGS
CLFAGS=
all:
    for dir in ${libs} ;  do cd ${PRJ_PATH}/$${dir} && ./build.sh ; done
clean:
    rm -rf install
    for dir in ${libs} ;  do cd ${PRJ_PATH}/$${dir} && ./build.sh -c ; done
project/2.socketd/openlibs/sqlite/build.sh
New file
@@ -0,0 +1,181 @@
#!/bin/bash
# library name and version
# Official: https://www.sqlite.org/index.html
LIB_NAME=sqlite-autoconf-3430000
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$PRJ_PATH/../install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/bin/sqlite3
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    do_export
    ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static --enable-static-shell
    check_result "ERROR: configure ${LIB_NAME} failure"
    make && make install
    check_result "ERROR: compile ${LIB_NAME} failure"
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/2.socketd/openlibs/sqlite/makefile
New file
@@ -0,0 +1,7 @@
all:
    ./build.sh
clean:
    rm -rf install
    ./build.sh -c
project/2.socketd/socket_client.c
New file
@@ -0,0 +1,263 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  socket_client.c
 *    Description:  This is a socket client program to report RPi's temperature
 *
 *        Version:  1.0.0(29/01/19)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "29/01/19 15:34:41"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>
#include "logger.h"
#include "ds18b20.h"
#include "proc.h"
#include "packet.h"
#include "socket.h"
#include "database.h"
#define PROG_VERSION               "v1.0.0"
#define DAEMON_PIDFILE             "/tmp/.socketc.pid"
static void print_usage(char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is LingYun studio temperature socket client program running on RaspberryPi\n", progname);
    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf("-i(ipaddr)    : sepcify server IP address\n");
    printf("-p(--port)    : sepcify server port.\n");
    printf("-I(--interval): sepcify report time interval, default 60 seconds\n");
    printf("-d(--debug)   : running in debug mode\n");
    printf("-h(--help)    : display this help information\n");
    printf("-v(--version) : display the program version\n");
    printf("\n%s version %s build on %s %s\n", progname, PROG_VERSION, __DATE__, __TIME__);
    return;
}
int check_sample_time(time_t *last_time, int interval);
int main (int argc, char **argv)
{
    char               *progname=NULL;
    int                 daemon = 1;
    int                 rv;
    char               *logfile="sock_client.log";
    int                 loglevel=LOG_LEVEL_INFO;
    int                 logsize=10; /* logfile size max to 10K */
    socket_t            sock;
    char               *serverip = NULL;
    int                 port = 0;
    int                 interval = 60; /* default report termperature every 60 seconds */
    time_t              last_time = 0;
    int                 sample_flag = 0;
    char                pack_buf[1024];
    int                 pack_bytes = 0;
    pack_info_t         pack_info;
    //pack_proc_t         pack_proc = packet_segmented_pack; /* use segmented string packet */
    //pack_proc_t         pack_proc = packet_json_pack;      /* use JSON string packet */
    pack_proc_t         pack_proc = packet_tlv_pack;       /* use TLV(Tag Length Value) packet */
    struct option opts[] = {
        {"ipaddr", required_argument, NULL, 'i'},
        {"port", required_argument, NULL, 'p'},
        {"interval", required_argument, NULL, 'I'},
        {"debug", no_argument, NULL, 'd'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = (char *)basename(argv[0]);
    /* Parser the command line parameters */
    while( (rv=getopt_long(argc, argv, "i:p:I:dvh", opts, NULL)) != -1 )
    {
        switch (rv)
        {
            case 'i': /* set socket server hostname or IP address */
                serverip=optarg;
                break;
            case 'p': /* set socket server listen port */
                port=atoi(optarg);
                break;
            case 'I': /* set report time interval */
                interval=atoi(optarg);
                break;
            case 'd': /* set debug running */
                daemon = 0;
                logfile="console";
                loglevel=LOG_LEVEL_DEBUG;
                break;
            case 'v':  /* get software version */
                printf("%s version %s\n", progname, PROG_VERSION);
                return 0;
            case 'h':  /* get help information */
                print_usage(progname);
                return 0;
            default:
                break;
        }
    }
    if( !serverip || !port )
    {
        print_usage(argv[0]);
        return 0;
    }
    /* open logger system */
    if( log_open(logfile, loglevel, logsize, THREAD_LOCK_NONE) < 0 )
    {
        fprintf(stderr, "Initial log system failed\n");
        return 1;
    }
    /* install signal proc handler */
    install_default_signal();
    /* check program already running or not, if already running then exit, or set running as daemon */
    if( check_set_program_running(daemon, DAEMON_PIDFILE) < 0 )
        goto cleanup;
    log_info("socket client start running.\n");
    if( database_init("sock_client.db") < 0 )
    {
        return 2;
    }
    socket_init(&sock, serverip, port);
    while( ! g_signal.stop )
    {
        /* +----------------------------------+
         * |  check and sample temperature    |
         * +----------------------------------+*/
        sample_flag = 0; /* clear sample flag */
        if( check_sample_time(&last_time, interval) )
        {
            log_debug("start DS18B20 sample termperature\n");
            if( (rv=ds18b20_get_temperature(&pack_info.temper)) < 0 )
            {
                log_error("DS18B20 sample temperature failure, rv=%d\n", rv);
                continue;
            }
            log_info("DS18B20 sample termperature %.2f oC\n", pack_info.temper);
            get_devid(pack_info.devid, sizeof(pack_info.devid), 88);
            get_time(&pack_info.sample_time);
            pack_bytes = pack_proc(&pack_info, (uint8_t *)pack_buf, sizeof(pack_buf));
            log_dump(LOG_LEVEL_DEBUG, NULL, pack_buf, pack_bytes);
            sample_flag = 1; /* set sample flag */
        }
        /* +---------------------------------+
         * |  check and do socket connect    |
         * +---------------------------------+*/
        /* start connect to server if not connected */
        if( !socket_connected(&sock) )
        {
            socket_connect(&sock);
        }
        /* +-------------------------------+
         * |      socket disconnect        |
         * +-------------------------------+*/
        if( !socket_connected(&sock) )
        {
            if( sample_flag )
            {
                database_push_packet(pack_buf, pack_bytes);
            }
            continue;
        }
        /* +-------------------------------+
         * |      socket connected         |
         * +-------------------------------+*/
        /*  socket send sample packet */
        if( sample_flag )
        {
            log_debug("socket send sample packet bytes[%d]: %s\n", pack_bytes, pack_buf);
            if( socket_send(&sock, pack_buf, pack_bytes) < 0 )
            {
                log_warn("socket send sample packet failure, save it in database now.\n");
                database_push_packet(pack_buf, pack_bytes);
                continue;
            }
        }
        /*  socket send packet in database  */
        if( !database_pop_packet(pack_buf, sizeof(pack_buf), &pack_bytes) )
        {
            log_debug("socket send database packet bytes[%d]: %s\n", pack_bytes, pack_buf);
            if( socket_send(&sock, pack_buf, pack_bytes) < 0 )
            {
                log_error("socket send database packet failure");
                continue;
            }
            else
            {
                log_warn("socket send database packet okay, remove it from database now.\n");
                database_del_packet();
            }
        }
        msleep(5);
    }
cleanup:
    socket_term(&sock);
    database_term();
    unlink(DAEMON_PIDFILE);
    log_close();
    return 0;
}
int check_sample_time(time_t *last_time, int interval)
{
    int                  need = 0; /* no need sample now */
    time_t               now;
    time(&now);
    if( difftime(now, *last_time)>interval )
    {
        need = 1; /* need sample now  */
        *last_time = now;
    }
    return need;
}
project/2.socketd/socket_server.c
New file
@@ -0,0 +1,351 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  socket_server.c
 *    Description:  This is a socket server program to receive RPi's temperature
 *
 *        Version:  1.0.0(2020年04月14日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2020年04月14日 00时52分56秒"
 *
 ********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <libgen.h>
#include <errno.h>
#include "logger.h"
#include "proc.h"
#include "list.h"
#include "socket.h"
#include "packet.h"
#define PROG_VERSION               "1.0.0"
#define DAEMON_PIDFILE             "/tmp/.sockets.pid"
#define MAX_EVENTS                 512
/* Every client need get a stand alone buffer to save TLV packet data */
typedef struct cli_buf_s
{
    int                  fd;         /* client fd  */
    char                 buf[512];   /* client receive buffer */
    int                  bytes;      /* left bytes in buffer  */
    struct list_head     list;       /* all client buffer saved in a list */
} cli_buf_t;
static int  parser_tlv_data(cli_buf_t *cli_buf);
static void term_socket_client(int epollfd, int fd, struct list_head *buf_list);
static void print_usage(char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is LingYun studio temperature socket server program running on RaspberryPi\n", progname);
    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf("-p(--port)    : sepcify server port.\n");
    printf("-d(--debug)   : running in debug mode\n");
    printf("-v(--version) : display the program version\n");
    printf("-h(--help)    : display this help information\n");
    printf("\n%s version %s build on %s %s\n", progname, PROG_VERSION, __DATE__, __TIME__);
    return;
}
int main (int argc, char *argv[])
{
    char                 *progname=NULL;
    int                   daemon = 1;
    int                   opt;
    int                   i = 0;
    int                   rv = 0;
    char                 *logfile="sock_server.log";
    int                   loglevel=LOG_LEVEL_INFO;
    int                   logsize=10; /* logfile size max to 10K */
    socket_t              sock;
    int                   port = 10086;
    int                   epollfd;
    int                   connfd;
    int                   fd;
    int                   nfds;
    struct epoll_event    event_array[MAX_EVENTS];
    cli_buf_t            *cli_buf;
    cli_buf_t            *node;
    struct list_head      buf_list; /* every client get a stand alone buffer saved in the list */
    struct option long_options[] = {
        {"port", required_argument, NULL, 'p'},
        {"debug", no_argument, NULL, 'd'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    memset(&sock, 0, sizeof(sock));
    INIT_LIST_HEAD(&buf_list);
    progname = basename(argv[0]); /* get program name */
    /* parser the command line parameters */
    while ((opt = getopt_long(argc, argv, "p:dvh", long_options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'p': /* listen port  */
                port = atoi(optarg);
                break;
            case 'd': /* set debug running */
                daemon = 0;
                logfile="console";
                loglevel=LOG_LEVEL_DEBUG;
                break;
            case 'v':  /* print software version */
                printf("%s version %s\n", progname, PROG_VERSION);
                return 0;
            case 'h':  /* print help information */
                print_usage(progname);
                return 0;
            default:
                break;
        }
    }
    /* open logger system */
    if( log_open(logfile, loglevel, logsize, THREAD_LOCK_NONE) < 0 )
    {
        fprintf(stderr, "Initial log system failed\n");
        return 1;
    }
    /* install signal proc handler */
    install_default_signal();
    /* check program already running or not, if already running then exit, or set running as daemon */
    if( check_set_program_running(daemon, DAEMON_PIDFILE) < 0 )
        goto cleanup;
    log_info("socket server(TLV) running and listen on port[%d].\n", port);
    /* initial listen socket  */
    if( socket_listen(&sock, port) < 0 )
    {
        log_error("create listen socket failure\n");
        rv = 2;
        goto cleanup;
    }
    /* create epoll and put listen socket into it  */
    if( (epollfd=epoll_init(MAX_EVENTS, sock.fd)) < 0 )
    {
        log_error("initial epoll for listen socket failure\n");
        rv = 3;
        goto cleanup;
    }
    /* g_signal.stop defined in proc.c, and will be set when catch stop signal */
    while( !g_signal.stop )
    {
        /* epoll wailt and program will blocked here */
        nfds = epoll_wait(epollfd, event_array, MAX_EVENTS, -1 /* never timeout */);
        if( nfds <= 0 )
        {
            log_error("epoll_wait failure or timeout: %s\n", strerror(errno));
            continue;
        }
        /* nfds>0 is the active events count */
        for(i=0; i<nfds; i++)
        {
            fd = event_array[i].data.fd;
            /* epoll get error  */
            if ( (event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP) )
            {
                log_error("epoll_wait get error on fd[%d]: %s\n", fd, strerror(errno));
                if( fd != sock.fd )
                    term_socket_client(epollfd, event_array[i].data.fd, &buf_list);
                continue;
            }
            /* listen socket get event means new client start connect now */
            if( fd == sock.fd )
            {
                /* accept new client socket  */
                if( (connfd=accept(sock.fd, (struct sockaddr *)NULL, NULL)) < 0)
                {
                    log_info("accept new client failure: %s\n", strerror(errno));
                    continue;
                }
                /* add new client socket into epoll  */
                if( epoll_add(epollfd, connfd) < 0 )
                {
                    close(connfd);
                    log_error("epoll add client socket failure, rv=%d\n", rv);
                }
                /* malloc client buffer and add it into list */
                if( NULL != (cli_buf=(cli_buf_t *)malloc(sizeof(*cli_buf))) )
                {
                    memset(cli_buf, 0, sizeof(*cli_buf));
                    cli_buf->fd = connfd;
                    list_add_tail(&cli_buf->list, &buf_list);
                    log_info("alloc and add socket[%d] buffer[%p] into list\n", cli_buf->fd, cli_buf);
                }
                log_info("accept and add new client socket[%d] ok.\n", connfd);
            }
            /* already connected client socket get data arrive  */
            else
            {
                cli_buf = NULL;
                /* Use list_for_each_entry to find the client buf in  buf list */
                list_for_each_entry(node, &buf_list, list)
                {
                    if( node->fd == fd )
                    {
                        log_info("found socket[%d] buffer[%p] in list\n", fd, node);
                        cli_buf = node;
                    }
                }
                if( !cli_buf )
                {
                    log_error("can not find socket[%d] buffer and close it now\n", fd);
                    term_socket_client(epollfd, fd, NULL);
                }
                rv=read(fd, &cli_buf->buf[cli_buf->bytes], sizeof(cli_buf->buf)-cli_buf->bytes);
                if( rv <= 0 )
                {
                    log_warn("socket[%d] read failure [%s] or disconncet, terminate it now.\n",
                            fd, strerror(errno));
                    term_socket_client(epollfd, fd, &buf_list);
                    continue;
                }
                else
                {
                    cli_buf->bytes += rv;
                    log_debug("socket[%d] receive %d bytes data\n", fd, rv);
                }
                parser_tlv_data(cli_buf);
            }
        }
        sleep(1);
    }
cleanup:
    socket_term(&sock);
    log_close();
    return 0;
}
static int parser_tlv_data(cli_buf_t *cli_buf)
{
    int                     i, rv;
    int                     left_bytes;
    pack_info_t             pack_info;
    if( !cli_buf )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    log_info("start to parser client buffer %d bytes data:\n", cli_buf->bytes);
    log_dump(LOG_LEVEL_INFO, NULL, cli_buf->buf, cli_buf->bytes);
    if( cli_buf->bytes < TLV_FIXSIZE )
    {
        log_warn("TLV packet bytes less than min. size\n");
        return -2;
    }
    for(i=0; i<cli_buf->bytes; i++)
    {
        memset(&pack_info, 0, sizeof(pack_info));
        rv = parser_tlv_pack(&pack_info, (uint8_t *)&cli_buf->buf[i], cli_buf->bytes);
        if( rv <= 0 )
        {
            /* parser failed, continue to parser next byte */
            continue;
        }
        else if( rv == 0)
        {
            log_debug("found TLV packet header on [%d]\n", i);
            /* remove the parsed garbage data  */
            left_bytes = cli_buf->bytes - i;
            memmove(cli_buf->buf, &cli_buf->buf[i], left_bytes);
            cli_buf->bytes = left_bytes;
            log_warn("TLV packet not integrated, need receive left data");
            break;
        }
        /* parser and get a correct TLV packet */
        left_bytes = cli_buf->bytes - rv;
        memmove(cli_buf->buf, &cli_buf->buf[i+rv], left_bytes);
        cli_buf->bytes = left_bytes;
        if( TAG_TEMPERATURE == pack_info.tag )
        {
            log_info("devsn: %s temperature:%.2f \n", pack_info.devid, pack_info.temper);
        }
    }
    /* all the data is garbage */
    if( rv < 0)
    {
        log_error("parser TLV data failed, remove the unused garbage data\n");
        /* maybe last byte is the header's first byte */
        cli_buf->buf[0] = cli_buf->buf[cli_buf->bytes-1];
        cli_buf->bytes = 1;
    }
    return 0;
}
static void term_socket_client(int epollfd, int fd, struct list_head *buf_list)
{
    cli_buf_t          *buf, *tmp;
    log_warn("start terminatl client socket[%d]\n", fd);
    /* if get buf_list, then remove buf from list */
    if( buf_list )
    {
        /* Use list_for_each_entry_safe to travel the socket list and destroy the buffer */
        list_for_each_entry_safe(buf, tmp, buf_list, list)
        {
            if( buf->fd == fd )
            {
                list_del(&buf->list);
                free(buf);
                log_info("free socket[%d] buffer[%p]\n", fd, buf);
                break;
            }
        }
    }
    epoll_del(epollfd, fd);
    close( fd );
}
project/3.mqttd/booster/config.c
New file
@@ -0,0 +1,200 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  config.c
 *    Description:  This file is mqttd configure file parser function
 *
 *        Version:  1.0.0(2019年06月25日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2019年06月25日 22时23分55秒"
 *
 ********************************************************************************/
#include "config.h"
#include "logger.h"
#include "iniparser.h"
int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug)
{
    dictionary          *ini;
    const char          *str;
    int                  val;
    int                  rv = 0;
    if( !conf_file || !ctx )
    {
        fprintf(stderr, "%s() Invalid input arguments\n", __func__);
        return -1;
    }
    memset(ctx, 0, sizeof(*ctx));
    ini = iniparser_load(conf_file);
    if( !ini )
    {
        fprintf(stderr, "ERROR: Configure file '%s' load failed\n", conf_file);
        return -2;
    }
    /*+------------------------------------------------------+
     *|    parser logger settings and start logger system    |
     *+------------------------------------------------------+*/
    if( !debug )
    {
        str = iniparser_getstring(ini, "logger:file", "/tmp/mqttd.log");
        strncpy(ctx->logfile, str, sizeof(ctx->logfile));
        ctx->logsize = iniparser_getint(ini, "logger:size", 1024);
        ctx->loglevel = iniparser_getint(ini, "logger:level", LOG_LEVEL_INFO);
    }
    else
    {
        strncpy(ctx->logfile, "console", sizeof(ctx->logfile));
        ctx->loglevel = LOG_LEVEL_DEBUG;
        ctx->logsize = 0;
    }
    if( log_open(ctx->logfile, ctx->loglevel, ctx->logsize, LOG_LOCK_DISABLE) < 0 )
    {
        fprintf(stderr, "Logger system initialise failure\n");
        return -2;
    }
    log_info("Logger system initialise ok\n");
    /*+------------------------------------------------------+
     *|               parser production ID                   |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "common:devid", NULL)) )
    {
        log_error("ERROR: Parser device ID failure\n");
        rv = -3;
        goto cleanup;
    }
    /* cJSON parser ID will get ""  */
    snprintf(ctx->devid, sizeof(ctx->devid), "\"%s\"", str);
    log_info("Parser device ID [%s]\n", ctx->devid);
    /*+------------------------------------------------------+
     *|       parser hardware module configuration           |
     *+------------------------------------------------------+*/
    /* relay */
    ctx->hwconf.relay=iniparser_getint(ini, "hardware:relay", 0);
    if( !ctx->hwconf.relay )
        log_warn("Parser relay module disabled\n");
    else
        log_info("Parser relay module enabled\n");
    /* RGB 3-colors LED */
    ctx->hwconf.led=iniparser_getint(ini, "hardware:rgbled", 0);
    if( !ctx->hwconf.led )
        log_warn("Parser RGB 3-colors Led module disabled\n");
    else
        log_info("Parser RGB 3-colors Led module enabled\n");
    /* beeper */
    ctx->hwconf.beeper=iniparser_getint(ini, "hardware:beep", 0);
    if( !ctx->hwconf.beeper )
        log_warn("Parser beeper module disabled\n");
    else
        log_info("Parser beeper module enabled\n");
    /* DS18B20 temperature module */
    ctx->hwconf.ds18b20=iniparser_getint(ini, "hardware:ds18b20", 0);
    if( !ctx->hwconf.ds18b20 )
        log_warn("Parser DS18B20 temperature module disabled\n");
    else
        log_info("Parser DS18B20 temperature module enabled\n");
    /* SHT20 temperature and hummidity module */
    ctx->hwconf.sht2x=iniparser_getint(ini, "hardware:sht2x", 0);
    if( !ctx->hwconf.sht2x )
        log_warn("Parser SHT2X temperature and hummidity module disabled\n");
    else
        log_info("Parser SHT2X temperature and hummidity module enabled\n");
    /* TSL2561 light intensity sensor module */
    ctx->hwconf.tsl2561=iniparser_getint(ini, "hardware:tsl2561", 0);
    if( !ctx->hwconf.tsl2561 )
        log_warn("Parser TSL2561 light intensity sensor module disabled\n");
    else
        log_info("Parser TSL2561 light intensity sensor module enabled\n");
    /*+------------------------------------------------------+
     *|              parser broker settings                  |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "broker:hostname", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker server hostname failure\n");
        rv = -4;
        goto cleanup;
    }
    strncpy(ctx->host, str, sizeof(ctx->host) );
    if( (val=iniparser_getint(ini, "broker:port", -1)) < 0 )
    {
        log_error("ERROR: Parser MQTT broker server port failure\n");
        rv = -5;
        goto cleanup;
    }
    ctx->port = val;
    log_info("Parser MQTT broker server [%s:%d]\n", ctx->host, ctx->port);
    str=iniparser_getstring(ini, "broker:username", NULL);
    strncpy(ctx->uid, str, sizeof(ctx->uid) );
    str=iniparser_getstring(ini, "broker:password", NULL);
    strncpy(ctx->pwd, str, sizeof(ctx->pwd) );
    if( ctx->uid && ctx->pwd )
        log_info("Parser broker author by [%s:%s]\n", ctx->uid, ctx->pwd);
    ctx->keepalive = iniparser_getint(ini, "broker:keepalive", DEF_KEEPALIVE);
    log_info("Parser broker keepalive timeout [%d] seconds\n", ctx->keepalive);
    /*+------------------------------------------------------+
     *|             parser publisher settings                |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "publisher:pubTopic", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker publisher topic failure\n");
        rv = -6;
        goto cleanup;
    }
    strncpy(ctx->pubTopic, str, sizeof(ctx->pubTopic) );
    ctx->pubQos = iniparser_getint(ini, "publisher:pubQos", DEF_QOS);
    ctx->interval = iniparser_getint(ini, "publisher:interval", DEF_PUBINTERVAL);
    log_info("Parser publisher topic \"%s\" with Qos[%d] interval[%d]\n", ctx->pubTopic, ctx->pubQos, ctx->interval);
    /*+------------------------------------------------------+
     *|             parser subscriber settings               |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "subsciber:subTopic", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker publisher topic failure\n");
        rv = -7;
        goto cleanup;
    }
    strncpy(ctx->subTopic, str, sizeof(ctx->subTopic) );
    ctx->subQos = iniparser_getint(ini, "subsciber:subQos", DEF_QOS);
    log_info("Parser subscriber topic \"%s\" with Qos[%d]\n", ctx->subTopic, ctx->subQos);
cleanup:
    if( ini )
        iniparser_freedict(ini);
    if( rv )
        log_close();
    return rv;
}
project/3.mqttd/booster/config.h
New file
@@ -0,0 +1,71 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  config.h
 *    Description:  This file is mqttd configure file parser function
 *
 *        Version:  1.0.0(2019年06月25日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2019年06月25日 22时23分55秒"
 *
 ********************************************************************************/
#ifndef  __CONF_H_
#define  __CONF_H_
enum
{
    Qos0, /* 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息 */
    Qos1, /* 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息 */
    Qos2, /* Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次  */
};
#define DEF_KEEPALIVE       30
#define DEF_PUBINTERVAL     120
#define DEF_QOS             Qos2
typedef struct hwconf_s
{
    int            relay;   /* relay aviable or not.   0:Disable  1: Enable */
    int            led;     /* RGB led aviable or not. 0:Disable  1: Enable */
    int            beeper;  /* beeper aviable or not.  0:Disable  1: Enable */
    int            ds18b20; /* DS1B820 aviable or not. 0:Disable  1: Enable */
    int            sht2x;   /* SHT20  aviable or not.  0:Disable  1: Enable */
    int            tsl2561; /* TSL2561 aviable or not.  0:Disable  1: Enable */
} hwconf_t;
typedef struct mqtt_ctx_s
{
    char          devid[32];    /*  device ID */
    /* hardware configuration */
    hwconf_t      hwconf;
    /* logger settings */
    char          logfile[128]; /* logger record file */
    int           loglevel;     /* logger level  */
    int           logsize;      /* logger file maxsize, oversize will rollback */
    /* Broker settings  */
    char          host[128];  /* MQTT broker server name  */
    int           port;       /* MQTT broker listen port  */
    char          uid[64];    /* username */
    char          pwd[64];    /* password */
    int           keepalive;  /* MQTT broker send PING message to subsciber/publisher keepalive timeout<seconds> */
    /* Publisher settings */
    char          pubTopic[256]; /* Publisher topic  */
    int           pubQos;        /* Publisher Qos */
    int           interval ;     /* Publish sensor data interval time, unit seconds */
    /* Subscriber settings */
    char          subTopic[256]; /* Subscriber topic */
    int           subQos;        /* Subscriber Qos  */
} mqtt_ctx_t;
extern int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug);
#endif   /* ----- #ifndef _CONF_H_  ----- */
project/3.mqttd/booster/ds18b20.c
New file
@@ -0,0 +1,118 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.c
 *    Description:  This file is temperature sensor DS18B20 code
 *
 *        Version:  1.0.0(2023/8/10)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
 *
 * Pin connection:
 *
 *               DS18B20 Module          Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   DQ       <----->      #Pin7(BCM GPIO4)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=w1-gpio-pullup,gpiopin=4
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "logger.h"
/* File Content:
   pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
   3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
   3a 01 4b 46 7f ff 0c 10 a5 t=19625
   */
int ds18b20_get_temperature(float *temp)
{
    char            w1_path[50] = "/sys/bus/w1/devices/";
    char            chip[20];
    char            buf[128];
    DIR            *dirp;
    struct dirent  *direntp;
    int             fd =-1;
    char           *ptr;
    int             found = 0;
    if( !temp )
    {
        return -1;
    }
    /*+-------------------------------------------------------------------+
     *|  open dierectory /sys/bus/w1/devices to get chipset Serial Number |
     *+-------------------------------------------------------------------+*/
    if((dirp = opendir(w1_path)) == NULL)
    {
        log_error("opendir error: %s\n", strerror(errno));
        return -2;
    }
    while((direntp = readdir(dirp)) != NULL)
    {
        if(strstr(direntp->d_name,"28-"))
        {
            /* find and get the chipset SN filename */
            strcpy(chip,direntp->d_name);
            found = 1;
            break;
        }
    }
    closedir(dirp);
    if( !found )
    {
        log_error("Can not find ds18b20 in %s\n", w1_path);
        return -3;
    }
    /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
    strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
    strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
    /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
    if( (fd=open(w1_path, O_RDONLY)) < 0 )
    {
        log_error("open %s error: %s\n", w1_path, strerror(errno));
        return -4;
    }
    if(read(fd, buf, sizeof(buf)) < 0)
    {
        log_error("read %s error: %s\n", w1_path, strerror(errno));
        return -5;
    }
    ptr = strstr(buf, "t=");
    if( !ptr )
    {
        log_error("ERROR: Can not get temperature\n");
        return -6;
    }
    ptr+=2;
    /* convert string value to float value */
    *temp = atof(ptr)/1000;
    close(fd);
    return 0;
}
project/3.mqttd/booster/ds18b20.h
New file
@@ -0,0 +1,31 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.h
 *    Description:  This file is temperature sensor DS18B20 code
 *
 *        Version:  1.0.0(2023/8/10)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
 *
 * Pin connection:
 *
 *               DS18B20 Module          Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   DQ       <----->      #Pin7(BCM GPIO4)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=w1-gpio-pullup,gpiopin=4
 *
 ********************************************************************************/
#ifndef  _DS18B20_H_
#define  _DS18B20_H_
extern int ds18b20_get_temperature(float *temp);
#endif   /* ----- #ifndef _DS18B20_H_  ----- */
project/3.mqttd/booster/ini_dictionary.c
New file
@@ -0,0 +1,398 @@
/*-------------------------------------------------------------------------*/
/**
   @file    ini_dictionary.c
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   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 "ini_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
 ---------------------------------------------------------------------------*/
/* Doubles the allocated size associated to a pointer */
/* 'size' is the current allocated size. */
static void * mem_double(void * ptr, int size)
{
    void * newptr ;
    newptr = calloc(2*size, 1);
    if (newptr==NULL) {
        return NULL ;
    }
    memcpy(newptr, ptr, size);
    free(ptr);
    return newptr ;
}
/*-------------------------------------------------------------------------*/
/**
  @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 ;
    if (!s)
        return NULL ;
    t = (char*)malloc(strlen(s)+1) ;
    if (t) {
        strcpy(t,s);
    }
    return t ;
}
/*---------------------------------------------------------------------------
                            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)
{
    int         len ;
    unsigned    hash ;
    int         i ;
    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 objet.
  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(int size)
{
    dictionary  *   d ;
    /* If no size was specified, allocate space for DICTMINSZ */
    if (size<DICTMINSZ) size=DICTMINSZ ;
    if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) {
        return NULL;
    }
    d->size = size ;
    d->val  = (char **)calloc(size, sizeof(char*));
    d->key  = (char **)calloc(size, sizeof(char*));
    d->hash = (unsigned int *)calloc(size, sizeof(unsigned));
    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)
{
    int     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.
 */
/*--------------------------------------------------------------------------*/
char * dictionary_get(dictionary * d, const char * key, char * def)
{
    unsigned    hash ;
    int         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)
{
    int         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 */
        d->val  = (char **)mem_double(d->val,  d->size * sizeof(char*)) ;
        d->key  = (char **)mem_double(d->key,  d->size * sizeof(char*)) ;
        d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ;
        if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) {
            /* Cannot grow dictionary */
            return -1 ;
        }
        /* Double size */
        d->size *= 2 ;
    }
    /* 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 ;
    int         i ;
    if (key == 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(dictionary * d, FILE * out)
{
    int     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 ;
}
/* Test code */
#ifdef TESTDIC
#define NVALS 20000
int main(int argc, char *argv[])
{
    dictionary  *   d ;
    char    *   val ;
    int         i ;
    char        cval[90] ;
    /* Allocate dictionary */
    printf("allocating...\n");
    d = dictionary_new(0);
    /* Set values in dictionary */
    printf("setting %d values...\n", NVALS);
    for (i=0 ; i<NVALS ; i++) {
        sprintf(cval, "%04d", i);
        dictionary_set(d, cval, "salut");
    }
    printf("getting %d values...\n", NVALS);
    for (i=0 ; i<NVALS ; i++) {
        sprintf(cval, "%04d", i);
        val = dictionary_get(d, cval, DICT_INVALID_KEY);
        if (val==DICT_INVALID_KEY) {
            printf("cannot get value for key [%s]\n", cval);
        }
    }
    printf("unsetting %d values...\n", NVALS);
    for (i=0 ; i<NVALS ; i++) {
        sprintf(cval, "%04d", i);
        dictionary_unset(d, cval);
    }
    if (d->n != 0) {
        printf("error deleting values\n");
    }
    printf("deallocating...\n");
    dictionary_del(d);
    return 0 ;
}
#endif
/* vim: set ts=4 et sw=4 tw=75 */
project/3.mqttd/booster/ini_dictionary.h
New file
@@ -0,0 +1,165 @@
/*-------------------------------------------------------------------------*/
/**
   @file    ini_dictionary.h
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   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 _INI_DICTIONARY_H_
#define _INI_DICTIONARY_H_
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*---------------------------------------------------------------------------
                                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 */
    int             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 objet.
  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(int 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.
 */
/*--------------------------------------------------------------------------*/
char * dictionary_get(dictionary * d, const char * key, 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(dictionary * d, FILE * out);
#endif
project/3.mqttd/booster/iniparser.c
New file
@@ -0,0 +1,807 @@
/*-------------------------------------------------------------------------*/
/**
   @file    iniparser.c
   @author  N. Devillard
   @brief   Parser for ini files.
*/
/*--------------------------------------------------------------------------*/
/*---------------------------- Includes ------------------------------------*/
#include <ctype.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    s   String to convert.
  @return   ptr to statically allocated string.
  This function returns a pointer to a statically allocated string
  containing a lowercased version of the input string. Do not free
  or modify the returned string! Since the returned string is statically
  allocated, it will be modified at each function call (not re-entrant).
 */
/*--------------------------------------------------------------------------*/
static char * strlwc(const char * s)
{
    static char l[ASCIILINESZ+1];
    int i ;
    if (s==NULL) return NULL ;
    memset(l, 0, ASCIILINESZ+1);
    i=0 ;
    while (s[i] && i<ASCIILINESZ) {
        l[i] = (char)tolower((int)s[i]);
        i++ ;
    }
    l[ASCIILINESZ]=(char)0;
    return l ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Remove blanks at the beginning and the end of a string.
  @param    s   String to parse.
  @return   ptr to statically allocated string.
  This function returns a pointer to a statically allocated string,
  which is identical to the input string, except that all blank
  characters at the end and the beg. of the string have been removed.
  Do not free or modify the returned string! Since the returned string
  is statically allocated, it will be modified at each function call
  (not re-entrant).
 */
/*--------------------------------------------------------------------------*/
static char * strstrip(const char * s)
{
    static char l[ASCIILINESZ+1];
    char * last ;
    if (s==NULL) return NULL ;
    while (isspace((int)*s) && *s) s++;
    memset(l, 0, ASCIILINESZ+1);
    strcpy(l, s);
    last = l + strlen(l);
    while (last > l) {
        if (!isspace((int)*(last-1)))
            break ;
        last -- ;
    }
    *last = (char)0;
    return (char*)l ;
}
/*-------------------------------------------------------------------------*/
/**
  @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(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.
 */
/*--------------------------------------------------------------------------*/
char * iniparser_getsecname(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(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(dictionary * d, FILE * f)
{
    int     i ;
    int     nsec ;
    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(dictionary * d, 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(dictionary * d, 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);
    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))
            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
  @return   pointer to statically allocated character strings
  This function queries a dictionary and finds all keys in a given section.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
char ** iniparser_getseckeys(dictionary * d, char * s)
{
    char **keys;
    int i, j ;
    char    keym[ASCIILINESZ+1];
    int     seclen, nkeys ;
    keys = NULL;
    if (d==NULL) return keys;
    if (! iniparser_find_entry(d, s)) return keys;
    nkeys = iniparser_getsecnkeys(d, s);
    keys = (char**) malloc(nkeys*sizeof(char*));
    seclen  = (int)strlen(s);
    sprintf(keym, "%s:", s);
    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.
 */
/*--------------------------------------------------------------------------*/
char * iniparser_getstring(dictionary * d, const char * key, char * def)
{
    char * lc_key ;
    char * sval ;
    if (d==NULL || key==NULL)
        return def ;
    lc_key = strlwc(key);
    sval = dictionary_get(d, lc_key, def);
    return sval ;
}
/*-------------------------------------------------------------------------*/
/**
  @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(dictionary * d, const char * key, int notfound)
{
    char    *   str ;
    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return (int)strtol(str, NULL, 0);
}
int iniparser_getlong(dictionary * d, const char * key, int notfound)
{
    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 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(dictionary * d, const char * key, double notfound)
{
    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(dictionary * d, const char * key, int notfound)
{
    char    *   c ;
    int         ret ;
    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(
    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, -1 is returned.
  It is Ok to set val to NULL.
 */
/*--------------------------------------------------------------------------*/
int iniparser_set(dictionary * ini, const char * entry, const char * val)
{
    return dictionary_set(ini, strlwc(entry), 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)
{
    dictionary_unset(ini, strlwc(entry));
}
/*-------------------------------------------------------------------------*/
/**
  @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(
    char * input_line,
    char * section,
    char * key,
    char * value)
{
    line_status sta ;
    char        line[ASCIILINESZ+1];
    static char left_line[ASCIILINESZ+1];
    int         len, offset ;
    strcpy(line, strstrip(input_line));
    len = (int)strlen(line);
    sta = LINE_UNPROCESSED ;
    if (len<1) {
        /* Empty line */
        sta = LINE_EMPTY ;
        memset(input_line, 0, len);
    } else if (line[0]=='#' || line[0]==';') {
        /* Comment line */
        sta = LINE_COMMENT ;
        memset(input_line, 0, len);
    } else if (line[0]=='[') {
        /* Section name */
        sscanf(line, "[%[^]]", section);
        strcpy(section, strstrip(section));
        strcpy(section, strlwc(section));
        /* Left configure will go to next time to parser */
        offset = strlen(section) + 2;
        strcpy( left_line, strstrip(&(line[offset])) );
        strcpy( left_line, strstrip(left_line));
        if( strlen(left_line) > 0)
        {
            strcpy(input_line, left_line);
            strcat(input_line, "\n");
        }
        else
        {
            memset(input_line, 0, len);
        }
        sta = LINE_SECTION ;
    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
           ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
        char      *ptr = NULL;
        /* Usual key=value, with or without comments */
        strcpy(key, strstrip(key));
        strcpy(key, strlwc(key));
        strcpy(value, strstrip(value));
        /*
         * sscanf cannot handle '' or "" as empty values
         * this is done here
         */
        if (!strncmp(value, "\"\"", 2) || (!strncmp(value, "''", 2)) ) {
            value[0]=0 ;
        }
        ptr = strchr(line, '=');
        if('\''==*(ptr+1) || '\"'==*(ptr+1))
        {
            offset = strlen(key)+strlen(value) + 1 + 2; /* Skip $key='$val' */
        }
        else
        {
            offset = strlen(key)+strlen(value) + 1; /* Skip $key=$val */
        }
        strcpy( left_line, strstrip(&(line[offset])) );
        if( strlen(left_line) > 0)
        {
            strcpy(input_line, left_line);
            strcat(input_line, "\n");
        }
        else
        {
            memset(input_line, 0, len);
        }
        sta = LINE_VALUE ;
    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
        /*
         * Special cases:
         * key=
         * key=;
         * key=#
         */
        strcpy(key, strstrip(key));
        strcpy(key, strlwc(key));
        value[0]=0 ;
        sta = LINE_VALUE ;
    } else {
        /* Generate syntax error */
        sta = LINE_ERROR ;
        memset(input_line, 0, len);
    }
    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+1] ;
    char val     [ASCIILINESZ+1] ;
    int  last=0 ;
    int  len ;
    int  lineno=0 ;
    int  errs=0;
    dictionary * dict ;
    if ((in=fopen(ininame, "r"))==NULL) {
        fprintf(stderr, "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++ ;
CONTINUE_PARSER:
        len = (int)strlen(line)-1;
        if (len==0)
            continue;
        /* Safety check against buffer overflows */
        if (line[len]!='\n') {
            fprintf(stderr,
                    "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-- ;
        }
        /* 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:
            errs = dictionary_set(dict, section, NULL);
            break ;
            case LINE_VALUE:
            sprintf(tmp, "%s:%s", section, key);
            errs = dictionary_set(dict, tmp, val) ;
            break ;
            case LINE_ERROR:
            fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
                    ininame,
                    lineno);
            fprintf(stderr, "-> %s\n", line);
            errs++ ;
            break;
            default:
            break ;
        }
        if( strlen(line) > 0)
        {
            goto CONTINUE_PARSER;
        }
        memset(line, 0, ASCIILINESZ);
        last=0;
        if (errs<0) {
            fprintf(stderr, "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);
}
/* vim: set ts=4 et sw=4 tw=75 */
project/3.mqttd/booster/iniparser.h
New file
@@ -0,0 +1,308 @@
/*-------------------------------------------------------------------------*/
/**
   @file    cp_iniparser.h
   @author  N. Devillard
   @brief   Parser for ini files.
*/
/*--------------------------------------------------------------------------*/
#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 "ini_dictionary.h"
/*-------------------------------------------------------------------------*/
/**
  @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(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.
 */
/*--------------------------------------------------------------------------*/
char * iniparser_getsecname(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(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(dictionary * d, 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(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(dictionary * d, 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
  @return   pointer to statically allocated character strings
  This function queries a dictionary and finds all keys in a given section.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
char ** iniparser_getseckeys(dictionary * d, char * s);
/*-------------------------------------------------------------------------*/
/**
  @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.
 */
/*--------------------------------------------------------------------------*/
char * iniparser_getstring(dictionary * d, const char * key, 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(dictionary * d, const char * key, int notfound);
int iniparser_getlong(dictionary * d, const char * key, 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(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(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, -1 is returned.
  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(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);
#endif
project/3.mqttd/booster/leds.c
New file
@@ -0,0 +1,162 @@
/*********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  led.c
 *    Description:  This file is used to control RGB 3-colors LED
 *                  compatible with libgpiod-1.x.x, not compatible with 2.x.x
 *
 *
 * Pin connection:
 *               RGB Led Module           Raspberry Pi Board
 *                   R        <----->      #Pin33(BCM GPIO13)
 *                   G        <----->      #Pin35(BCM GPIO19)
 *                   B        <----->      #Pin37(BCM GPIO26)
 *                  GND       <----->      GND
 *
 * System install:
 *                  sudo apt install -y libgpiod-dev gpiod
 *
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <gpiod.h>
#include "logger.h"
#include "leds.h"
#define DELAY     500
static led_info_t    leds_info[LED_CNT] =
{
    {"red",   13, 1, NULL },
    {"green", 19, 1, NULL },
    {"blue",  26, 1, NULL },
};
int init_led(led_ctx_t *ctx, int which)
{
    int            i, rv;
    led_info_t    *led;
    if( !ctx )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    ctx->leds = leds_info;
    ctx->count = LED_CNT;
    ctx->chip = gpiod_chip_open_by_name("gpiochip0");
    if( !ctx->chip )
    {
        log_error("open gpiochip failure, maybe you need running as root\n");
        return -2;
    }
    for(i=0; i<ctx->count; i++)
    {
        if( which == i )
        {
            led = &ctx->leds[i];
            led->line = gpiod_chip_get_line(ctx->chip, led->gpio);
            if( !led->line )
            {
                log_error("open gpioline for %s[%d] failed\n", led->name, led->gpio);
                rv = -3;
                goto failed;
            }
            rv  = gpiod_line_request_output(led->line, led->name, !led->active);
            if( rv )
            {
                log_error("request gpio output for %5s[%d] failed: %s\n", led->name, led->gpio, strerror(errno));
                rv = -4;
                goto failed;
            }
            log_debug("request %5s led[%d] for gpio output okay\n", led->name, led->gpio);
        }
    }
    return 0;
failed:
    term_led(ctx, which);
    return rv;
}
int term_led(led_ctx_t *ctx, int which)
{
    int            i;
    led_info_t    *led;
    log_warn("terminate RGB Led gpios\n");
    if( !ctx )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( !ctx->chip )
        return 0;
    for(i=0; i<ctx->count; i++)
    {
        if( which == i )
        {
            led = &ctx->leds[i];
            if( led->line )
                gpiod_line_release(led->line);
        }
    }
    gpiod_chip_close(ctx->chip);
    return 0;
}
int turn_led(int which, int cmd)
{
    int            rv = 0;
    led_ctx_t      ctx;
    led_info_t    *led;
    if( which<0 || which>=LED_CNT )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( (rv=init_led(&ctx, which)) < 0 )
    {
        log_error("Initial RGB leds failure, rv=%d\n", rv);
        return -2;
    }
    led = &ctx.leds[which];
    if( OFF == cmd )
    {
        gpiod_line_set_value(led->line, !led->active);
    }
    else
    {
        gpiod_line_set_value(led->line, led->active);
    }
    term_led(&ctx, which);
    return 0;
}
project/3.mqttd/booster/leds.h
New file
@@ -0,0 +1,58 @@
/*********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  led.h
 *    Description:  This file is used to control RGB 3-colors LED
 *
 *
 * Pin connection:
 *               RGB Led Module           Raspberry Pi Board
 *                   R        <----->      #Pin33(BCM GPIO13)
 *                   G        <----->      #Pin35(BCM GPIO19)
 *                   B        <----->      #Pin37(BCM GPIO26)
 *                  GND       <----->      GND
 *
 * System install:
 *                  sudo apt install -y libgpiod-dev gpiod
 *
 *
 ********************************************************************************/
#ifndef  _LEDS_H_
#define  _LEDS_H_
#define ON        1
#define OFF       0
/* Three LEDs code */
enum
{
    LED_R = 0,
    LED_G,
    LED_B,
    LED_CNT,
};
/* Three LEDs hardware information */
typedef struct led_info_s
{
    const char         *name;  /* RGB 3-color LED name  */
    int                 gpio;  /* RGB 3-color LED BCM pin number */
    int                 active;/* RGB 3-color LED active GPIO level: 0->low 1->high */
    struct gpiod_line  *line;  /* libgpiod line */
} led_info_t;
/* Three LEDs API context */
typedef struct led_ctx_s
{
    struct gpiod_chip   *chip;
    led_info_t          *leds;
    int                  count;
} led_ctx_t;
extern int init_led(led_ctx_t *ctx, int which);
extern int term_led(led_ctx_t *ctx, int which);
extern int turn_led(int which, int cmd);
#endif   /* ----- #ifndef _LEDS_H_  ----- */
project/3.mqttd/booster/logger.c
New file
@@ -0,0 +1,279 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.c
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#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[] = {
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
};
static const char *level_colors[] = {
    "\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);
    }
}
project/3.mqttd/booster/logger.h
New file
@@ -0,0 +1,70 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.h
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _LOGGER_H_
#define  _LOGGER_H_
#include <stdio.h>
#include <stdarg.h>
#define LOG_VERSION "v0.1"
/* log level */
enum {
    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
 */
#define THREAD_LOCK_NONE       0
#define THREAD_LOCK_EN         1
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__)
#endif
project/3.mqttd/booster/makefile
New file
@@ -0,0 +1,36 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file used compile all the source code to static library
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PWD=$(shell pwd )
CFLAGS += -Wno-format-overflow
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE?=arm-linux-gnueabihf-
endif
LIBNAME=$(shell basename ${PWD} )
TOPDIR=$(shell dirname ${PWD} )
all: clean
    @rm -f *.o
    @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
    ${CROSS_COMPILE}ar -rcs  lib${LIBNAME}.a *.o
clean:
    @rm -f *.o
    @rm -f *.a
distclean:
    @make clean
project/3.mqttd/booster/proc.c
New file
@@ -0,0 +1,432 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  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 "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\n");
        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)
{
    daemon(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);
}
project/3.mqttd/booster/proc.h
New file
@@ -0,0 +1,89 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  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 __PROC_H_
#define __PROC_H_
#include <signal.h>
#include <time.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);
/* +------------------------+
 * |  inline functions API  |
 * +------------------------+*/
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
#endif
project/3.mqttd/booster/sht20.c
New file
@@ -0,0 +1,245 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  sht20.c
 *    Description:  This file is temperature and relative humidity sensor SHT20 code
 *
 *        Version:  1.0.0(10/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
 *
 * Pin connection:
 *               STH20 Module            Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   SDA1     <----->      #Pin3(SDA, BCM GPIO2)
 *                   SCL1     <----->      #Pin5(SCL, BCM GPIO3)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *                  dtoverlay=i2c1,pins_2_3
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <getopt.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include "logger.h"
#include "sht20.h"
#define msleep(x) usleep(1000*(x));
int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len);
int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size);
int sht20_checksum(uint8_t *data, int len, int8_t checksum);
int sht2x_get_temp_humidity(float *temp, float *rh)
{
    int             fd;
    int             rv = 0;
    char           *dev = SHT20_I2CDEV;
    uint8_t         buf[4];
    if( !temp || !rh )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    /*+--------------------------------+
     *|     open /dev/i2c-x device     |
     *+--------------------------------+*/
    if( (fd=open(dev, O_RDWR)) < 0)
    {
        log_error("i2c device '%s' open failed: %s\n", dev, strerror(errno));
        return -2;
    }
    /*+--------------------------------+
     *|   software reset SHT20 sensor  |
     *+--------------------------------+*/
    buf[0] = 0xFE;
    i2c_write(fd, SHT20_I2CADDR, buf, 1);
    msleep(50);
    /*+--------------------------------+
     *|   trigger temperature measure  |
     *+--------------------------------+*/
    buf[0] = 0xF3;
    i2c_write(fd, SHT20_I2CADDR, buf, 1);
    msleep(85); /* datasheet: typ=66, max=85 */
    memset(buf, 0, sizeof(buf));
    i2c_read(fd, SHT20_I2CADDR, buf, 3);
    log_dump(LOG_LEVEL_DEBUG, "Temperature sample data: ", (char *)buf, 3);
    if( !sht20_checksum(buf, 2, buf[2]) )
    {
        rv = -3;
        log_error("Temperature sample data CRC checksum failure.\n");
        goto cleanup;
    }
    *temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
    /*+--------------------------------+
     *|    trigger humidity measure    |
     *+--------------------------------+*/
    buf[0] = 0xF5;
    i2c_write(fd, SHT20_I2CADDR, buf, 1);
    msleep(29); /* datasheet: typ=22, max=29 */
    memset(buf, 0, sizeof(buf));
    i2c_read(fd, SHT20_I2CADDR, buf, 3);
    log_dump(LOG_LEVEL_DEBUG, "Relative humidity sample data: ", (char *)buf, 3);
    if( !sht20_checksum(buf, 2, buf[2]) )
    {
        rv = -4;
        log_error("Relative humidity sample data CRC checksum failure.\n");
        goto cleanup;
    }
    *rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
    /*+--------------------------------+
     *|    print the measure result    |
     *+--------------------------------+*/
    log_debug("Temperature=%lf 'C relative humidity=%lf%%\n", *temp, *rh);
cleanup:
    close(fd);
    return rv;
}
int sht20_checksum(uint8_t *data, int len, int8_t checksum)
{
    int8_t crc = 0;
    int8_t bit;
    int8_t byteCtr;
    //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
    for (byteCtr = 0; byteCtr < len; ++byteCtr)
    {
        crc ^= (data[byteCtr]);
        for ( bit = 8; bit > 0; --bit)
        {
            /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
            if (crc & 0x80) crc = (crc << 1) ^ 0x131;
            else crc = (crc << 1);
        }
    }
    if (crc != checksum)
        return 0;
    else
        return 1;
}
int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len)
{
    struct i2c_rdwr_ioctl_data i2cdata;
    int rv = 0;
    if( !data || len<= 0)
    {
        log_error("%s() invalid input arguments!\n", __func__);
        return -1;
    }
    i2cdata.nmsgs = 1;
    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
    if ( !i2cdata.msgs )
    {
        log_error("%s() msgs malloc failed!\n", __func__);
        return -2;
    }
    i2cdata.msgs[0].addr = slave_addr;
    i2cdata.msgs[0].flags = 0; //write
    i2cdata.msgs[0].len = len;
    i2cdata.msgs[0].buf = malloc(len);
    if( !i2cdata.msgs[0].buf )
    {
        log_error("%s() msgs malloc failed!\n", __func__);
        rv = -3;
        goto cleanup;
    }
    memcpy(i2cdata.msgs[0].buf, data, len);
    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
    {
        log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
        rv = -4;
        goto cleanup;
    }
cleanup:
    if( i2cdata.msgs[0].buf )
        free(i2cdata.msgs[0].buf);
    if( i2cdata.msgs )
        free(i2cdata.msgs);
    return rv;
}
int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size)
{
    struct i2c_rdwr_ioctl_data i2cdata;
    int rv = 0;
    if( !buf || size<= 0)
    {
        log_error("%s() invalid input arguments!\n", __func__);
        return -1;
    }
    i2cdata.nmsgs = 1;
    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
    if ( !i2cdata.msgs )
    {
        log_error("%s() msgs malloc failed!\n", __func__);
        return -2;
    }
    i2cdata.msgs[0].addr = slave_addr;
    i2cdata.msgs[0].flags = I2C_M_RD; //read
    i2cdata.msgs[0].len = size;
    i2cdata.msgs[0].buf = buf;
    memset(buf, 0, size);
    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
    {
        log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
        rv = -4;
    }
    free( i2cdata.msgs );
    return rv;
}
project/3.mqttd/booster/sht20.h
New file
@@ -0,0 +1,34 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  sht20.c
 *    Description:  This file is temperature and relative humidity sensor SHT20 code
 *
 *        Version:  1.0.0(10/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
 *
 * Pin connection:
 *               STH20 Module            Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   SDA      <----->      #Pin3(SDA, BCM GPIO2)
 *                   SCL      <----->      #Pin5(SCL, BCM GPIO3)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *                  dtoverlay=i2c1,pins_2_3
 *
 ********************************************************************************/
#ifndef  _SHT20_H_
#define  _SHT20_H_
#define SHT20_I2CDEV           "/dev/i2c-1"
#define SHT20_I2CADDR          0x40
extern int sht2x_get_temp_humidity(float *temp, float *rh);
#endif   /* ----- #ifndef _SHT20_H_  ----- */
project/3.mqttd/etc/mqttd.conf
New file
@@ -0,0 +1,59 @@
[common]
devid="RPi3B#01"
# 树莓派连接的外设信息,0:禁用或未连接  其他: 使能或相关硬件连接的Pin管脚(wPI模式)
[hardware]
# 是否使能 RGB 三色灯模块,0:禁用  1:使能
rgbled=1
# 是否使能 DS18b20 温度传感器模块,0:禁用  1:使能
ds18b20=1
# 是否使能 SHT2X 温湿度传感器模块,0:禁用  1:使能
sht2x=1
[logger]
# 日志记录文件
file=/tmp/mqttd.log
# 日志级别: 0:ERROR 1:WARN 2:INFO 3:DEBUG 4:TRACE
level=2
# 日志回滚大小
size=1024
[broker]
# broker 服务器地址和端口号
hostname="weike-iot.com"
port=8013
# broker 认证连接的用户名和密码
username="lingyun"
password="lingyun"
# broker给subsciber和publisher发送PING报文保持 keepalive 的时间周期,单位是秒
keepalive=30
# Qos0: 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息
# Qos1: 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息
# Qos2: Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次
[publisher]
# mosquitto_sub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Uplink
pubTopic="$Sys/Studio/Uplink"
pubQos=0
# Publisher上报传感器数据的周期,单位是秒
interval=60
[subsciber]
# mosquitto_pub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Downlink -m '{"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]}'
subTopic="$Sys/Studio/Downlink"
subQos=0
project/3.mqttd/makefile
New file
@@ -0,0 +1,66 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file is the project top Makefie
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PRJ_PATH=$(shell pwd)
APP_NAME = mqttd
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE=arm-linux-gnueabihf-
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
SRCS=booster
SRCS_PATH=$(patsubst %,${PRJ_PATH}/%,$(SRCS))
CFLAGS+=$(patsubst %,-I%,$(SRCS_PATH))
LDFLAGS+=$(patsubst %,-L%,$(SRCS_PATH))
LIBS=$(patsubst %,-l%,$(SRCS))
LDFLAGS+=${LIBS}
# Open source libraries
CFLAGS+=-I ${PRJ_PATH}/openlibs/install/include
LDFLAGS+=-L ${PRJ_PATH}/openlibs/install/lib
# libraries
libs=openlibs ${SRCS}
LDFLAGS+=-lmosquitto -lcjson -lssl -lcrypto -lgpiod
LDFLAGS+=-lpthread
all: entry subdir
    ${CROSS_COMPILE}gcc ${CFLAGS} mqttd.c -o ${APP_NAME} ${LDFLAGS}
entry:
    @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
subdir:
    @for dir in ${libs} ;  do CFLAGS="${CFLAGS}" make -C $${dir}; done
install:
    cp ${APP_NAME} /tftp
clean:
    @for dir in ${SRCS} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
    @rm -f ${APP_NAME}
distclean:
    @for dir in ${libs} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
    @rm -f ${APP_NAME}
    @rm -f cscope.* tags
project/3.mqttd/mqttd.c
New file
@@ -0,0 +1,434 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  main.c
 *    Description:  This file
 *
 *        Version:  1.0.0(29/01/19)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "29/01/19 15:34:41"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>
#include <mosquitto.h>
#include <cjson/cJSON.h>
#include "logger.h"
#include "proc.h"
#include "config.h"
#include "ds18b20.h"
#include "sht20.h"
#include "leds.h"
#define PROG_VERSION               "v1.0.0"
#define DAEMON_PIDFILE             "/tmp/.mqtt.pid"
void *mqtt_sub_worker(void *args);
void *mqtt_pub_worker(void *args);
static void program_usage(char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is LingYun studio MQTT daemon program running on RaspberryPi\n", progname);
    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf(" -d[debug   ]  Running in debug mode\n");
    printf(" -c[conf    ]  Specify configure file\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n%s version %s\n", progname, PROG_VERSION);
    return;
}
int main (int argc, char **argv)
{
    int                daemon = 1;
    pthread_t          tid;
    mqtt_ctx_t         ctx;
    char               *conf_file="/etc/mqttd.conf";
    int                debug = 0;
    int                opt;
    char              *progname=NULL;
    struct option long_options[] = {
        {"conf", required_argument, NULL, 'c'},
        {"debug", no_argument, NULL, 'd'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = (char *)basename(argv[0]);
    /* parser the command line parameters */
    while ((opt = getopt_long(argc, argv, "c:dvh", long_options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'c': /* Set configure file */
                conf_file = optarg;
                break;
            case 'd': /* Set debug running */
                daemon = 0;
                debug = 1;
                break;
            case 'v':  /* Get software version */
                printf("%s version %s\n", progname, PROG_VERSION);
                return 0;
            case 'h':  /* Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    if( !conf_file )
        debug = 1;
    /* parser configure file */
    if( mqttd_parser_conf(conf_file, &ctx, debug)<0 )
    {
        fprintf(stderr, "Parser mqtted configure file failure\n");
        return -2;
    }
    /* install signal proc handler */
    install_default_signal();
    /* check program already running or not, if already running then exit, or set running as daemon */
    if( check_set_program_running(daemon, DAEMON_PIDFILE) < 0 )
        goto cleanup;
    /* initial mosquitto library */
    mosquitto_lib_init();
    /* create MQTT subsciber thread  */
    if( thread_start(&tid, mqtt_sub_worker, &ctx ) < 0 )
    {
        log_error("Start MQTT subsciber worker thread failure\n");
        goto cleanup;
    }
    log_info("Start MQTT subsciber worker thread ok\n");
    /* create MQTT publisher thread  */
    if( thread_start(&tid, mqtt_pub_worker, &ctx) < 0 )
    {
        log_error("Start MQTT publisher worker thread failure\n");
        goto cleanup;
    }
    log_info("Start MQTT publisher worker thread ok\n");
    while( ! g_signal.stop )
    {
        msleep(1000);
    }
cleanup:
    mosquitto_lib_cleanup();
    log_close();
    return 0;
} /* ----- End of main() ----- */
void pub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)userdata;
    int                     rv = 0;
    char                    msg[128];
    float                   temp = 0.0; /* temperature */
    float                   rh = 0.0;   /* relative humidity */
    int                     retain = 0;
    if( result )
    {
        log_error("Publisher connect to broker server[%s:%d] failed, rv=%d\n", ctx->host, ctx->port, result);
        return ;
    }
    log_info("Publisher connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
    log_debug("Publish topic '%s'\n", ctx->pubTopic);
    if( ctx->hwconf.ds18b20 )
    {
        memset(msg, 0, sizeof(msg));
        log_debug("DS18B20 temperature sensor enabled, start broadcast it\n");
        if( ds18b20_get_temperature(&temp) < 0 )
            snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\" }", ctx->devid);
        else
            snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\" }", ctx->devid, temp);
        rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
        if( rv )
        {
            log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
        }
        else
        {
            log_info("Publisher broadcast message '%s' ok\n", msg);
        }
    }
    if( ctx->hwconf.sht2x )
    {
        memset(msg, 0, sizeof(msg));
        log_debug("SHT2X temperature and humidity sensor enabled, start broadcast it\n");
        if( sht2x_get_temp_humidity(&temp, &rh) < 0 )
            snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\", \"RH\":\"error\" }", ctx->devid);
        else
            snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\", \"RH\":\"%.2f\" }", ctx->devid, temp, rh);
        rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
        if( rv )
        {
            log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
        }
        else
        {
            log_info("Publisher broadcast message '%s' ok\n", msg);
        }
    }
    log_info("Publisher broadcast over and disconnect broker now\n", ctx->host, ctx->port);
    mosquitto_disconnect(mosq);
    return ;
}
void *mqtt_pub_worker(void *args)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)args;
    struct mosquitto       *mosq;
    bool                    session = true;
    mosq = mosquitto_new(NULL, session, ctx);
    if( !mosq )
    {
        log_error("mosquitto_new failure\n");
        return NULL;
    }
    /* set connnect to broker username and password  */
    if( strlen(ctx->uid)> 0  && strlen(ctx->pwd)> 0 )
        mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
    /* set callback functions */
    mosquitto_connect_callback_set(mosq, pub_connect_callback);
    while( !g_signal.stop )
    {
        /* connect to MQTT broker  */
        if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
        {
            log_error("Publisher connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
            msleep(1000);
            continue;
        }
        /* -1: use default timeout 1000ms  1: unused */
        mosquitto_loop_forever(mosq, -1, 1);
        /* Publisher broadcast sensors data message interval time, unit seconds */
        sleep( ctx->interval );
    }
    mosquitto_destroy(mosq);
    return NULL;
}
void sub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)userdata;
    if( result )
    {
        log_error("Subscriber connect to broker server failed, rv=%d\n", result);
        return ;
    }
    log_info("Subscriber connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
    mosquitto_subscribe(mosq, NULL, ctx->subTopic, ctx->subQos);
}
void sub_disconnect_callback(struct mosquitto *mosq, void *userdata, int result)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)userdata;
    log_warn("Subscriber disconnect to broker server[%s:%d], reason=%d\n", ctx->host, ctx->port, result);
}
static inline void mqtt_turn_led(int which, char *cmd)
{
    if( strcasestr(cmd, "on") )
        turn_led(which, ON);
    else if( strcasestr(cmd, "off") )
        turn_led(which, OFF);
}
void proc_json_items(cJSON *root, mqtt_ctx_t *ctx)
{
    int                    i;
    cJSON                 *item;
    cJSON                 *array;
    hwconf_t              *hwconf = &ctx->hwconf;
    if( !root )
    {
        log_error("Invalid input arguments $root\n");
        return ;
    }
    for( i=0; i<cJSON_GetArraySize(root); i++ )
    {
        item = cJSON_GetArrayItem(root, i);
        if( !item )
            break;
        /* if item is cJSON_Object, then recursive call proc_json */
        if( cJSON_Object == item->type )
        {
            proc_json_items(item, ctx);
        }
        else if( cJSON_Array == item->type )
        {
            /* RGB colors led control: {"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]} */
            if( hwconf->led && !strcasecmp(item->string, "leds") )
            {
                array = cJSON_GetArrayItem(item, 0);
                if( NULL != array )
                {
                    cJSON        *led_item;
                    if( NULL != (led_item=cJSON_GetObjectItem(array , "red")) )
                    {
                        log_info("turn red led '%s'\n", led_item->valuestring);
                        mqtt_turn_led(LED_R, led_item->valuestring);
                    }
                    if( NULL != (led_item=cJSON_GetObjectItem(array , "green")) )
                    {
                        log_info("turn green led '%s'\n", led_item->valuestring);
                        mqtt_turn_led(LED_G, led_item->valuestring);
                    }
                    if( NULL != (led_item=cJSON_GetObjectItem(array , "blue")) )
                    {
                        log_info("turn blue led '%s'\n", led_item->valuestring);
                        mqtt_turn_led(LED_B, led_item->valuestring);
                    }
                }
            }
        }
    }
}
void sub_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)userdata;
    cJSON                  *root = NULL;
    cJSON                  *item;
    char                   *value;
    if ( !message->payloadlen )
    {
        log_error("%s (null)\n", message->topic);
        return ;
    }
    log_debug("Subscriber receive message: '%s'\n", message->payload);
    root = cJSON_Parse(message->payload);
    if( !root )
    {
        log_error("cJSON_Parse parser failure: %s\n", cJSON_GetErrorPtr());
        return ;
    }
    item = cJSON_GetObjectItem(root, "id");
    if( !item )
    {
        log_error("cJSON_Parse get ID failure: %s\n", cJSON_GetErrorPtr());
        goto cleanup;
    }
    value = cJSON_PrintUnformatted(item);
    if( strcasecmp(value, ctx->devid) )
    {
        free(value);
        goto cleanup;
    }
    free(value);
    log_info("Subscriber receive message: '%s'\n", message->payload);
    proc_json_items(root, ctx);
cleanup:
    cJSON_Delete(root); /* must delete it, or it will result memory leak */
    return ;
}
void *mqtt_sub_worker(void *args)
{
    mqtt_ctx_t             *ctx = (mqtt_ctx_t *)args;
    struct mosquitto       *mosq;
    bool                    session = true;
    mosq = mosquitto_new(NULL, session, ctx);
    if( !mosq )
    {
        log_error("mosquitto_new failure\n");
        return NULL;
    }
    /* set connnect to broker username and password  */
    if( strlen(ctx->uid)> 0  && strlen(ctx->pwd)> 0 )
        mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
    /* set callback functions */
    mosquitto_connect_callback_set(mosq, sub_connect_callback);
    mosquitto_disconnect_callback_set(mosq, sub_disconnect_callback);
    mosquitto_message_callback_set(mosq, sub_message_callback);
    while( !g_signal.stop )
    {
        /* connect to MQTT broker  */
        if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
        {
            log_error("Subscriber connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
            msleep(1000);
            continue;
        }
        /* -1: use default timeout 1000ms  1: unused */
        mosquitto_loop_forever(mosq, -1, 1);
    }
    mosquitto_destroy(mosq);
    return NULL;
}
project/3.mqttd/openlibs/cjson/build.sh
New file
@@ -0,0 +1,182 @@
#!/bin/bash
# library name and version
# Official: https://github.com/DaveGamble/cJSON
LIB_NAME=cJSON-1.7.15
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$TOP_PATH/install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/lib/libcjson.so
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    do_export
    sed -i "s|^CC =.*|CC = ${CROSS_COMPILE}gcc -std=c89|" Makefile
    make && make PREFIX=$PREFIX_PATH install
    check_result "ERROR: compile ${LIB_NAME} failure"
    cp libcjson.a $PREFIX_PATH/lib
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/3.mqttd/openlibs/libgpiod/build.sh
New file
@@ -0,0 +1,187 @@
#!/bin/bash
# Build depends system tools:
#    sudo apt install -y automake texinfo libtool
# library name and version
# Official: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
LIB_NAME=libgpiod-1.6.2
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$TOP_PATH/install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/lib/libgpiod.so
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    ./autogen.sh
    do_export
    echo "ac_cv_func_malloc_0_nonnull=yes" > arm-linux.cache
    ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static \
        --cache-file=arm-linux.cache --enable-tools
    check_result "ERROR: configure ${LIB_NAME} failure"
    make -j ${JOBS} && make install
    check_result "ERROR: compile ${LIB_NAME} failure"
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/3.mqttd/openlibs/makefile
New file
@@ -0,0 +1,16 @@
PRJ_PATH=$(shell pwd)
openssl_dir := $(shell ls -d openssl/ 2>/dev/null | sed 's|/||g')
other_dirs := $(shell ls -d */ 2>/dev/null | grep -v '^install/' | grep -v '^openssl/' | sed 's|/||g')
libs := $(openssl_dir) $(other_dirs)
# clear CFLAGS
CLFAGS=
all:
    for dir in ${libs} ;  do cd ${PRJ_PATH}/$${dir} && ./build.sh ; done
clean:
    rm -rf install
    for dir in ${libs} ;  do cd ${PRJ_PATH}/$${dir} && ./build.sh -c ; done
project/3.mqttd/openlibs/mosquitto/build.sh
New file
@@ -0,0 +1,188 @@
#!/bin/bash
# library name and version
# Official: https://mosquitto.org/
LIB_NAME=mosquitto-2.0.15
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$TOP_PATH/install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/bin/mosquitto
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    do_export
    export CFLAGS=-I${PREFIX_PATH}/include
    export LDFLAGS="-L${PREFIX_PATH}/lib -lcrypto -lssl -ldl -lpthread"
    export DESTDIR=${PREFIX_PATH}
    make WITH_UUID=no WITH_STATIC_LIBRARIES=yes
    check_result "ERROR: compile ${LIB_NAME} failure"
    make DESTDIR=${PREFIX_PATH} prefix=/ install
    check_result "ERROR: compile ${LIB_NAME} failure"
    install -m 755 src/mosquitto $BIN_PATH
    install -m 644 lib/libmosquitto.a $LIB_PATH
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/3.mqttd/openlibs/openssl/build.sh
New file
@@ -0,0 +1,182 @@
#!/bin/bash
# library name and version
# Official: https://www.openssl.org/
LIB_NAME=openssl-1.1.1v
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$TOP_PATH/install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/bin/openssl
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    do_export
    CROSS_COMPILE=${CROSSTOOL} ./Configure linux-armv4 \
        threads -shared -no-zlib --prefix=$PREFIX_PATH --openssldir=$PREFIX_PATH
    check_result "ERROR: configure ${LIB_NAME} failure"
    make -j ${JOBS} && make install
    check_result "ERROR: compile ${LIB_NAME} failure"
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/3.mqttd/openlibs/sqlite/build.sh
New file
@@ -0,0 +1,181 @@
#!/bin/bash
# library name and version
# Official: https://www.sqlite.org/index.html
LIB_NAME=sqlite-autoconf-3430000
PACK_SUFIX=tar.gz
# LingYun source code FTP server
LY_FTP=http://weike-iot.com:2211/src/
# library download URL address
LIB_URL=$LY_FTP
# Cross compiler for cross compile on Linux server
CROSS_COMPILE=arm-linux-gnueabihf-
# compile jobs
JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
# this project absolute path
PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
# top project absolute path
TOP_PATH=$(realpath $PRJ_PATH/..)
# binaries install path
PREFIX_PATH=$PRJ_PATH/../install
BIN_PATH=$PREFIX_PATH/bin
LIB_PATH=$PREFIX_PATH/lib
INC_PATH=$PREFIX_PATH/include
# check installed or not file
INST_FILE=$PREFIX_PATH/bin/sqlite3
# shell script will exit once get command error
set -e
#+-------------------------+
#| Shell script functions  |
#+-------------------------+
function pr_error() {
    echo -e "\033[40;31m $1 \033[0m"
}
function pr_warn() {
    echo -e "\033[40;33m $1 \033[0m"
}
function pr_info() {
    echo -e "\033[40;32m $1 \033[0m"
}
function check_result()
{
    if [ $? != 0 ] ; then
        pr_error $1
    fi
}
# decompress a packet to destination path
function do_unpack()
{
    tarball=$1
    dstpath=`pwd`
    if [[ $# == 2 ]] ; then
        dstpath=$2
    fi
    pr_info "decompress $tarball => $dstpath"
    mkdir -p $dstpath
    case $tarball in
        *.tar.gz)
            tar -xzf $tarball -C $dstpath
            ;;
        *.tar.bz2)
            tar -xjf $tarball -C $dstpath
            ;;
        *.tar.xz)
            tar -xJf $tarball -C $dstpath
            ;;
        *.tar.zst)
            tar -I zstd -xf $tarball -C $dstpath
            ;;
        *.tar)
            tar -xf $tarball -C $dstpath
            ;;
        *.zip)
            unzip -qo $tarball -d $dstpath
            ;;
        *)
            pr_error "decompress Unsupport packet: $tarball"
            return 1;
            ;;
    esac
}
function do_export()
{
    BUILD_ARCH=$(uname -m)
    if [[ $BUILD_ARCH =~ "arm" ]] ; then
        pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
        return ;
    fi
    pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
    # export cross toolchain
    export CC=${CROSS_COMPILE}gcc
    export CXX=${CROSS_COMPILE}g++
    export AS=${CROSS_COMPILE}as
    export AR=${CROSS_COMPILE}ar
    export LD=${CROSS_COMPILE}ld
    export NM=${CROSS_COMPILE}nm
    export RANLIB=${CROSS_COMPILE}ranlib
    export OBJDUMP=${CROSS_COMPILE}objdump
    export STRIP=${CROSS_COMPILE}strip
    # export cross configure
    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
    # Clear LDFLAGS and CFLAGS
    export LDFLAGS=
    export CFLAGS=
}
function do_fetch()
{
    if [ -e ${INST_FILE} ] ; then
        pr_warn "$LIB_NAME compile and installed alredy"
        exit ;
    fi
    if [ -d $LIB_NAME ] ; then
        pr_warn "$LIB_NAME fetch already"
        return ;
    fi
    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
        wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
        check_result "ERROR: download ${LIB_NAME} failure"
    fi
    do_unpack ${LIB_NAME}.${PACK_SUFIX}
}
function do_build()
{
    cd $LIB_NAME
    do_export
    ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static --enable-static-shell
    check_result "ERROR: configure ${LIB_NAME} failure"
    make && make install
    check_result "ERROR: compile ${LIB_NAME} failure"
}
function do_clean()
{
    rm -rf *${LIB_NAME}*
}
if [[ $# == 1 && $1 == -c ]] ;then
    pr_warn "start clean ${LIB_NAME}"
    do_clean
    exit;
fi
do_fetch
do_build
project/3.mqttd/openlibs/sqlite/makefile
New file
@@ -0,0 +1,7 @@
all:
    ./build.sh
clean:
    rm -rf install
    ./build.sh -c
project/3.mqttd/tools/publisher.sh
New file
@@ -0,0 +1,14 @@
#!/bin/bash
hostname="weike-iot.com"
port=8013
uid="lingyun"
pwd="lingyun"
options="-h $hostname -p $port -u $uid -P $pwd"
topic="\$Sys/Studio/Downlink"
set -x
mosquitto_pub $options -t $topic -m '{"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]}'
project/3.mqttd/tools/subsciber.sh
New file
@@ -0,0 +1,14 @@
#!/bin/bash
hostname="weike-iot.com"
port=8013
uid="lingyun"
pwd="lingyun"
options="-h $hostname -p $port -u $uid -P $pwd"
topic="\$Sys/Studio/Uplink"
set -x
mosquitto_sub $options -t $topic