| New file |
| | |
| | | # 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-* |
| New file |
| | |
| | | |
| | | 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 |
| New file |
| | |
| | | #!/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 |
| New file |
| | |
| | | all: |
| | | cd cjson && ./build.sh |
| | | |
| | | clean: |
| | | cd cjson && rm -rf c* |
| | | rm -rf install |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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_ ----- */ |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
| | | |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
| New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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_ ----- */ |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | } |
| | | |
| | | |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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 |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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); |
| | | } |
| | | |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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_ ----- */ |
| | | |
| New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
| | | |
| New file |
| | |
| | | |
| | | 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 |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | |
| | | all: |
| | | ./build.sh |
| | | |
| | | clean: |
| | | rm -rf install |
| | | ./build.sh -c |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ); |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
| New file |
| | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 */ |
| New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 |
| New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 */ |
| New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
| New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | } |
| | | |
| | | |
| New file |
| | |
| | | /******************************************************************************** |
| | | * 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 |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
| New file |
| | |
| | | [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 |
| | | |
| New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
| | | |
| New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | |
| | | 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 |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | #!/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 |
| | | |
| New file |
| | |
| | | |
| | | all: |
| | | ./build.sh |
| | | |
| | | clean: |
| | | rm -rf install |
| | | ./build.sh -c |
| New file |
| | |
| | | #!/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"}]}' |
| | | |
| New file |
| | |
| | | #!/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 |
| | | |