From 4977430ef31fb52994fed42b9cb180930ed943d0 Mon Sep 17 00:00:00 2001 From: Sylvain Miermont Date: Tue, 22 Oct 2013 18:23:52 +0200 Subject: Beta 8 - API: lgw_receive now return info on RX frequency and RF path for each packet (no need to keep track of RF/IF settings) - Unified some portion of the code with the 470 MHz variant of the HAL (use SX1255 radios instead of SX1257) - Improved AGC and ARB firmwares - Adding -Wall -Wextra for compilation, fixing all the new warnings for cleaner code - Fixed bugs in handling of FSK datarate - test_loragw_hal now dumps the content of all Lora registers after configuration in reg_dump.log --- loragw_pkt_logger/LICENSE.TXT | 33 ++ loragw_pkt_logger/Makefile | 39 ++ loragw_pkt_logger/README.TXT | 88 ++++ loragw_pkt_logger/global_conf.json | 46 +++ loragw_pkt_logger/inc/parson.h | 100 +++++ loragw_pkt_logger/local_conf.json | 5 + loragw_pkt_logger/obj/.gitkeep | 0 loragw_pkt_logger/src/loragw_pkt_logger.c | 565 ++++++++++++++++++++++++++ loragw_pkt_logger/src/parson.c | 647 ++++++++++++++++++++++++++++++ 9 files changed, 1523 insertions(+) create mode 100644 loragw_pkt_logger/LICENSE.TXT create mode 100644 loragw_pkt_logger/Makefile create mode 100644 loragw_pkt_logger/README.TXT create mode 100644 loragw_pkt_logger/global_conf.json create mode 100644 loragw_pkt_logger/inc/parson.h create mode 100644 loragw_pkt_logger/local_conf.json create mode 100644 loragw_pkt_logger/obj/.gitkeep create mode 100644 loragw_pkt_logger/src/loragw_pkt_logger.c create mode 100644 loragw_pkt_logger/src/parson.c (limited to 'loragw_pkt_logger') diff --git a/loragw_pkt_logger/LICENSE.TXT b/loragw_pkt_logger/LICENSE.TXT new file mode 100644 index 0000000..b46b1f5 --- /dev/null +++ b/loragw_pkt_logger/LICENSE.TXT @@ -0,0 +1,33 @@ + --- For the parson library (parson.c and parson.h) --- + + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + --- For the rest of the code --- + +Copyright (C) 2013 SEMTECH S.A. + + THE FOLLOWING SOFTWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND +(2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER. +CONSEQUENTLY, SEMTECH SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR +CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT +OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION +CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. diff --git a/loragw_pkt_logger/Makefile b/loragw_pkt_logger/Makefile new file mode 100644 index 0000000..6b90495 --- /dev/null +++ b/loragw_pkt_logger/Makefile @@ -0,0 +1,39 @@ +### Application-specific constants + +APP_NAME=loragw_pkt_logger + +### constant symbols + +CC=gcc +CFLAGS=-O2 -Wall -Wextra -Iinc +C99FLAGS=-O2 -Wall -Wextra -std=c99 -Iinc +FLAG_AUX= + +### constants for Lora Gateway HAL library + +LGW_PATH=../loragw_hal +LGW_INC=-I$(LGW_PATH)/inc +#LGW_LNK=-lloragw -lrt +LGW_LNK=-lloragw -lrt -lmpsse +# add libmpsse or not, depending on what option you compiled the libloragw with + +### general build targets + +all: $(APP_NAME) + +clean: + rm -f obj/*.o + rm -f $(APP_NAME) + +### sub-modules compilation + +obj/parson.o: src/parson.c + $(CC) -c $(C99FLAGS) -o obj/parson.o $(LGW_INC) src/parson.c $(FLAG_AUX) + +### main program compilation and assembly + +obj/$(APP_NAME).o: src/$(APP_NAME).c + $(CC) -c $(C99FLAGS) -o obj/$(APP_NAME).o $(LGW_INC) src/$(APP_NAME).c $(FLAG_AUX) + +$(APP_NAME): $(LGW_PATH)/libloragw.a obj/$(APP_NAME).o obj/parson.o + $(CC) -o $(APP_NAME) obj/$(APP_NAME).o obj/parson.o -L$(LGW_PATH) $(LGW_LNK) diff --git a/loragw_pkt_logger/README.TXT b/loragw_pkt_logger/README.TXT new file mode 100644 index 0000000..278d026 --- /dev/null +++ b/loragw_pkt_logger/README.TXT @@ -0,0 +1,88 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + ©2013 Semtech-Cycleo + +Lora Gateway packet logger +=========================== + +1. Introduction +---------------- + +This software is used to set up a Lora concentrator using a JSON configuration +file and then record all the packets received in a log file, indefinitely, until +the user stops the application. +No filtering is done and all packets that are Lora packets with the correct RF +parameters (frequency, datarate, bandwidth) should appear in the log. + +2. Dependencies +---------------- + +This program uses the Parson library (http://kgabis.github.com/parson/) by +Krzysztof Gabis for JSON parsing. +Many thanks to him for that very practical and well written library. + +This program is a typical example of Lora Gateway HAL usage for receiving +packets. + +Only high-level functions are used (the ones contained in loragw_hal) so there +is no hardware dependencies assuming the HAL is matched with the proper version +of the hardware. +Data structures of the received packets are accessed by name (ie. not at a +binary level) so new functionalities can be added to the API without affecting +that program at all. + +It was tested with beta8 of the libloragw library, and should be compatible +with any later version of the library assuming the API is downward-compatible. + +3. Usage +--------- + +To stop the application, press Ctrl+C. + +The only optional parameter when launching the application is the log rotation +time (in seconds). + +The way the program takes configuration files into account is the following: + * if there is a debug_conf.json parse it, others are ignored + * if there is a global_conf.json parse it, look for the next file + * if there is a local_conf.json parse it +If some parameters are defined in both global and local configuration files, the +local definition overwrites the global definition. + +The global configuration file should be exactly the same throughout your +network, contain all global parameters (parameters for "sensor" radio channels) +and preferably default "safe" values for parameters that are specific for each +gateway (eg. specify a default MAC address). + +The local configuration file should contain parameters that are specific to each +gateway (eg. MAC address, frequency for backhaul radio channels). + +In each configuration file, the program looks for a JSON object named +"SX1301_conf" that should contain the parameters for the Lora concentrator board +(RF channels definition, modem parameters, etc) and another JSON object called +"gateway_conf" that should contain the gateway parameters (gateway MAC address, +IP address of the Lora MAC controller, network authentication parameters, etc). + +To learn more about the JSON configuration format, read the provided JSON files +and the API documentation. A dedicated document will be available later on. + +The received packets are put in a CSV file whose name include the MAC address of +the gateway in hexadecimal format and a UTC timestamp of log starting time in +ISO 8601 recommended compact format: +yyyymmddThhmmssZ (eg. 20131009T172345Z for October 9th, 2013 at 5:23:45PM UTC) + +To able continuous monitoring, the current log file is closed is closed and a +new one is opened every hour (by default, rotation interval is settable by the +user using -r command line option). +No packet is lost during that rotation of log file. +Every log file but the current one can then be modified, uploaded and/or deleted +without any consequence for the program execution. + +4. Changelog +------------- + +2013-10-18, beta 1 +Initial version. diff --git a/loragw_pkt_logger/global_conf.json b/loragw_pkt_logger/global_conf.json new file mode 100644 index 0000000..6bc8f06 --- /dev/null +++ b/loragw_pkt_logger/global_conf.json @@ -0,0 +1,46 @@ +{ + "SX1301_conf": { + "radio_0": { + "enable": true, + "freq": 868200000 + }, + "chan_multiSF_0": { + "enable": true, + "radio": 0, + "if": -100000, + "note": "Lora MAC channel, 125kHz, all SF, 868.1 MHz" + }, + "chan_multiSF_1": { + "enable": true, + "radio": 0, + "if": 100000, + "note": "Lora MAC channel, 125kHz, all SF, 868.3 MHz" + }, + "chan_multiSF_2": { + "enable": true, + "radio": 0, + "if": 300000, + "note": "Lora MAC channel, 125kHz, all SF, 868.5 MHz" + }, + "chan_Lora_std": { + "enable": true, + "radio": 0, + "if": 100000, + "bandwidth": 250000, + "spread_factor": 7, + "note": "Lora MAC channel, 250kHz, SF7, 868.3 MHz" + }, + + "chan_FSK": { + "enable": true, + "radio": 0, + "if": 100000, + "bandwidth": 250000, + "datarate": 100000, + "note": "FSK 100kbps channel, 868.3 MHz" + } + }, + "gateway_conf": { + "gateway_ID": "AA555A0000000000" + } +} diff --git a/loragw_pkt_logger/inc/parson.h b/loragw_pkt_logger/inc/parson.h new file mode 100644 index 0000000..00728b1 --- /dev/null +++ b/loragw_pkt_logger/inc/parson.h @@ -0,0 +1,100 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef parson_parson_h +#define parson_parson_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include /* size_t */ + +/* Types and enums */ +typedef struct json_object_t JSON_Object; +typedef struct json_array_t JSON_Array; +typedef struct json_value_t JSON_Value; + +typedef enum json_value_type { + JSONError = 0, + JSONNull = 1, + JSONString = 2, + JSONNumber = 3, + JSONObject = 4, + JSONArray = 5, + JSONBoolean = 6 +} JSON_Value_Type; + +/* Parses first JSON value in a file, returns NULL in case of error */ +JSON_Value * json_parse_file(const char *filename); + +/* Parses first JSON value in a string, returns NULL in case of error */ +JSON_Value * json_parse_string(const char *string); + +/* JSON Object */ +JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); +const char * json_object_get_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); +double json_object_get_number (const JSON_Object *object, const char *name); +int json_object_get_boolean(const JSON_Object *object, const char *name); + +/* dotget functions enable addressing values with dot notation in nested objects, + just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). + Because valid names in JSON can contain dots, some values may be inaccessible + this way. */ +JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); +const char * json_object_dotget_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); +double json_object_dotget_number (const JSON_Object *object, const char *name); +int json_object_dotget_boolean(const JSON_Object *object, const char *name); + +/* Functions to get available names */ +size_t json_object_get_count(const JSON_Object *object); +const char * json_object_get_name (const JSON_Object *object, size_t index); + +/* JSON Array */ +JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); +const char * json_array_get_string (const JSON_Array *array, size_t index); +JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); +JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); +double json_array_get_number (const JSON_Array *array, size_t index); +int json_array_get_boolean(const JSON_Array *array, size_t index); +size_t json_array_get_count (const JSON_Array *array); + +/* JSON Value */ +JSON_Value_Type json_value_get_type (const JSON_Value *value); +JSON_Object * json_value_get_object (const JSON_Value *value); +JSON_Array * json_value_get_array (const JSON_Value *value); +const char * json_value_get_string (const JSON_Value *value); +double json_value_get_number (const JSON_Value *value); +int json_value_get_boolean(const JSON_Value *value); +void json_value_free (JSON_Value *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/loragw_pkt_logger/local_conf.json b/loragw_pkt_logger/local_conf.json new file mode 100644 index 0000000..624893d --- /dev/null +++ b/loragw_pkt_logger/local_conf.json @@ -0,0 +1,5 @@ +{ + "gateway_conf": { + "gateway_ID": "AA555A0000000101" + } +} diff --git a/loragw_pkt_logger/obj/.gitkeep b/loragw_pkt_logger/obj/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/loragw_pkt_logger/src/loragw_pkt_logger.c b/loragw_pkt_logger/src/loragw_pkt_logger.c new file mode 100644 index 0000000..b3200ba --- /dev/null +++ b/loragw_pkt_logger/src/loragw_pkt_logger.c @@ -0,0 +1,565 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + ©2013 Semtech-Cycleo + +Description: + Configure Lora concentrator and record received packets in a log file +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf sprintf fopen fputs */ + +#include /* memset */ +#include /* sigaction */ +#include /* time clock_gettime strftime gmtime clock_nanosleep*/ +#include /* getopt access */ +#include /* atoi */ + +#include "parson.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define MSG(args...) fprintf(stderr,"loragw_pkt_logger: " args) /* message that is destined to the user */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +/* signal handling variables */ +struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* configuration variables needed by the application */ +uint64_t lgwm = 0; /* Lora gateway MAC address */ +char lgwm_str[17]; + +/* clock and log file management */ +time_t now_time; +time_t log_start_time; +FILE * log_file = NULL; +char log_file_name[64]; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); + +int parse_SX1301_configuration(const char * conf_file); + +int parse_gateway_configuration(const char * conf_file); + +void open_log(void); + +void usage (void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +int parse_SX1301_configuration(const char * conf_file) { + int i; + const char conf_obj[] = "SX1301_conf"; + char param_name[32]; /* used to generate variable parameter names */ + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + JSON_Value *root_val; + JSON_Object *root = NULL; + JSON_Object *conf = NULL; + JSON_Value *val; + uint32_t sf, bw; + + /* try to parse JSON */ + root_val = json_parse_file(conf_file); + root = json_value_get_object(root_val); + if (root == NULL) { + MSG("ERROR: %s id not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + conf = json_object_get_object(root, conf_obj); + if (conf == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing SX1301 parameters\n", conf_file, conf_obj); + } + + /* set configuration for RF chains */ + for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { + memset(&rfconf, 0, sizeof(rfconf)); /* initialize configuration structure */ + sprintf(param_name, "radio_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for radio %i\n", i); + continue; + } + /* there is an object to configure that radio, let's parse it */ + sprintf(param_name, "radio_%i.enable", i); + val = json_object_dotget_value(conf, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.enable = (bool)json_value_get_boolean(val); + } else { + rfconf.enable = false; + } + if (rfconf.enable == false) { /* radio disabled, nothing else to parse */ + MSG("INFO: radio %i disabled\n", i); + } else { /* radio enabled, will parse the other parameters */ + sprintf(param_name, "radio_%i.freq", i); + rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf, param_name); + MSG("INFO: radio %i enabled, center frequency %u\n", i, rfconf.freq_hz); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxrf_setconf(i, rfconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for radio %i\n", i); + } + } + + /* set configuration for Lora multi-SF channels (bandwidth cannot be set) */ + for (i = 0; i < LGW_MULTI_NB; ++i) { + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + sprintf(param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora multi-SF channel %i\n", i); + continue; + } + /* there is an object to configure that Lora multi-SF channel, let's parse it */ + sprintf(param_name, "chan_multiSF_%i.enable", i); + val = json_object_dotget_value(conf, param_name); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { /* Lora multi-SF channel disabled, nothing else to parse */ + MSG("INFO: Lora multi-SF channel %i disabled\n", i); + } else { /* Lora multi-SF channel enabled, will parse the other parameters */ + sprintf(param_name, "chan_multiSF_%i.radio", i); + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, param_name); + sprintf(param_name, "chan_multiSF_%i.if", i); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, param_name); + // TODO: handle individual SF enabling and disabling (spread_factor) + MSG("INFO: Lora multi-SF channel %i enabled, radio %i selected, IF %i Hz, 125 kHz bandwidth, SF 7 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxif_setconf(i, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for Lora multi-SF channel %i\n", i); + } + } + + /* set configuration for Lora standard channel */ + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + val = json_object_get_value(conf, "chan_Lora_std"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora standard channel\n"); + } else { + val = json_object_dotget_value(conf, "chan_Lora_std.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: Lora standard channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_Lora_std.if"); + bw = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.bandwidth"); + switch(bw) { + case 500000: ifconf.bandwidth = BW_500KHZ; break; + case 250000: ifconf.bandwidth = BW_250KHZ; break; + case 125000: ifconf.bandwidth = BW_125KHZ; break; + default: ifconf.bandwidth = BW_UNDEFINED; + } + sf = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.spread_factor"); + switch(sf) { + case 7: ifconf.datarate = DR_LORA_SF7; break; + case 8: ifconf.datarate = DR_LORA_SF8; break; + case 9: ifconf.datarate = DR_LORA_SF9; break; + case 10: ifconf.datarate = DR_LORA_SF10; break; + case 11: ifconf.datarate = DR_LORA_SF11; break; + case 12: ifconf.datarate = DR_LORA_SF12; break; + default: ifconf.datarate = DR_UNDEFINED; + } + MSG("INFO: Lora standard channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); + } + if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for Lora standard channel\n"); + } + } + + /* set configuration for FSK channel */ + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + val = json_object_get_value(conf, "chan_FSK"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for FSK channel\n"); + } else { + val = json_object_dotget_value(conf, "chan_FSK.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: FSK channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_FSK.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_FSK.if"); + bw = (uint32_t)json_object_dotget_number(conf, "chan_FSK.bandwidth"); + switch(bw) { + case 500000: ifconf.bandwidth = BW_500KHZ; break; + case 250000: ifconf.bandwidth = BW_250KHZ; break; + case 125000: ifconf.bandwidth = BW_125KHZ; break; + case 62500: ifconf.bandwidth = BW_62K5HZ; break; + case 31200: ifconf.bandwidth = BW_31K2HZ; break; + case 15600: ifconf.bandwidth = BW_15K6HZ; break; + case 7800: ifconf.bandwidth = BW_7K8HZ; break; + default: ifconf.bandwidth = BW_UNDEFINED; + } + ifconf.datarate = (uint32_t)json_object_dotget_number(conf, "chan_FSK.datarate"); + MSG("INFO: FSK channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate); + } + if (lgw_rxif_setconf(9, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for FSK channel\n"); + } + } + json_value_free(root_val); + return 0; +} + +int parse_gateway_configuration(const char * conf_file) { + const char conf_obj[] = "gateway_conf"; + JSON_Value *root_val; + JSON_Object *root = NULL; + JSON_Object *conf = NULL; + unsigned long long ull = 0; + + /* try to parse JSON */ + root_val = json_parse_file(conf_file); + root = json_value_get_object(root_val); + if (root == NULL) { + MSG("ERROR: %s id not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + conf = json_object_get_object(root, conf_obj); + if (conf == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj); + } + + /* getting network parameters (only those necessary for the packet logger) */ + sscanf(json_object_dotget_string(conf, "gateway_ID"), "%llx", &ull); + lgwm = ull; + MSG("INFO: gateway MAC address is configured to %016llX\n", ull); + + json_value_free(root_val); + return 0; +} + +void open_log(void) { + int i; + char iso_date[20]; + + strftime(iso_date,ARRAY_SIZE(iso_date),"%Y%m%dT%H%M%SZ",gmtime(&now_time)); /* format yyyymmddThhmmssZ */ + log_start_time = now_time; /* keep track of when the log was started, for log rotation */ + + sprintf(log_file_name, "pktlog_%s_%s.csv", lgwm_str, iso_date); + log_file = fopen(log_file_name, "a"); /* create log file, append if file already exist */ + if (log_file == NULL) { + MSG("ERROR: impossible to create log file %s\n", log_file_name); + exit(EXIT_FAILURE); + } + + i = fprintf(log_file, "\"gateway ID\",\"node MAC\",\"UTC timestamp\",\"us count\",\"frequency\",\"RF chain\",\"RX chain\",\"status\",\"size\",\"modulation\",\"bandwidth\",\"datarate\",\"coderate\",\"RSSI\",\"SNR\",\"payload\"\n"); + if (i < 0) { + MSG("ERROR: impossible to write to log file %s\n", log_file_name); + exit(EXIT_FAILURE); + } + + MSG("INFO: Now writing to log file %s\n", log_file_name); + return; +} + +/* describe command line options */ +void usage(void) { + MSG( "Available options:\n"); + MSG( " -h print this help\n"); + MSG( " -r rotate log file every N seconds (-1 disable log rotation)\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, j; /* loop and temporary variables */ + struct timespec sleep_time = {0, 3000000}; /* 3 ms */ + + /* clock and log rotation management */ + int log_rotate_interval = 3600; /* by default, rotation every hour */ + int time_check = 0; /* variable used to limit the number of calls to time() function */ + unsigned long pkt_in_log = 0; /* count the number of packet written in each log file */ + + /* configuration file related */ + const char global_conf_fname[] = "global_conf.json"; /* contain global (typ. network-wide) configuration */ + const char local_conf_fname[] = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ + const char debug_conf_fname[] = "debug_conf.json"; /* if present, all other configuration files are ignored */ + + /* allocate memory for packet fetching and processing */ + struct lgw_pkt_rx_s rxpkt[16]; /* array containing up to 16 inbound packets metadata */ + struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ + int nb_pkt; + + /* local timestamp variables until we get accurate GPS time */ + struct timespec fetch_time; + char fetch_timestamp[30]; + struct tm * x; + + /* parse command line options */ + while ((i = getopt (argc, argv, "hr:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_FAILURE; + break; + + case 'r': + log_rotate_interval = atoi(optarg); + if ((log_rotate_interval == 0) || (log_rotate_interval < -1)) { + MSG( "ERROR: Invalid argument for -r option\n"); + return EXIT_FAILURE; + } + break; + + default: + MSG("ERROR: argument parsing use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* configuration files management */ + if (access(debug_conf_fname, R_OK) == 0) { + /* if there is a debug conf, parse only the debug conf */ + MSG("INFO: found debug configuration file %s, other configuration files will be ignored\n", debug_conf_fname); + parse_SX1301_configuration(debug_conf_fname); + parse_gateway_configuration(debug_conf_fname); + } else if (access(global_conf_fname, R_OK) == 0) { + /* if there is a global conf, parse it and then try to parse local conf */ + MSG("INFO: found global configuration file %s, trying to parse it\n", global_conf_fname); + parse_SX1301_configuration(global_conf_fname); + parse_gateway_configuration(global_conf_fname); + if (access(local_conf_fname, R_OK) == 0) { + MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); + parse_SX1301_configuration(local_conf_fname); + parse_gateway_configuration(local_conf_fname); + } + } else if (access(local_conf_fname, R_OK) == 0) { + /* if there is only a local conf, parse it and that's all */ + MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); + parse_SX1301_configuration(local_conf_fname); + parse_gateway_configuration(local_conf_fname); + } else { + MSG("ERROR: failed to find any configuration file named %s, %s or %s\n", global_conf_fname, local_conf_fname, debug_conf_fname); + return EXIT_FAILURE; + } + + /* starting the concentrator */ + i = lgw_start(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator started, packet can now be received\n"); + } else { + MSG("ERROR: failed to start the concentrator\n"); + return EXIT_FAILURE; + } + + /* transform the MAC address into a string */ + sprintf(lgwm_str, "%016llX", lgwm); + + /* opening log file and writing CSV header*/ + time(&now_time); + open_log(); + + /* main loop */ + while ((quit_sig != 1) && (exit_sig != 1)) { + /* fetch packets */ + nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); + if (nb_pkt == LGW_HAL_ERROR) { + MSG("ERROR: failed packet fetch, exiting\n"); + return EXIT_FAILURE; + } else if (nb_pkt == 0) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_time, NULL); /* wait a short time if no packets */ + } else { + /* local timestamp generation until we get accurate GPS time */ + clock_gettime(CLOCK_REALTIME, &fetch_time); + x = gmtime(&(fetch_time.tv_sec)); + sprintf(fetch_timestamp,"%04i-%02i-%02i %02i:%02i:%02i.%03liZ",(x->tm_year)+1900,(x->tm_mon)+1,x->tm_mday,x->tm_hour,x->tm_min,x->tm_sec,(fetch_time.tv_nsec)/1000000); /* ISO 8601 format */ + } + + /* log packets */ + for (i=0; i < nb_pkt; ++i) { + p = &rxpkt[i]; + + /* writing gateway ID */ + fprintf(log_file, "%016llX,", lgwm); + + /* writing node MAC address */ + fputs("\"\",", log_file); // TODO: need to parse payload + + /* writing UTC timestamp*/ + fprintf(log_file, "\"%s\",", fetch_timestamp); + // TODO: replace with GPS time when available + + /* writing internal clock */ + fprintf(log_file, "%010u,", p->count_us); + + /* writing RX frequency */ + fprintf(log_file, "%10u,", p->freq_hz); + + /* writing RF chain */ + fprintf(log_file, "%u,", p->rf_chain); + + /* writing RX modem/IF chain */ + fprintf(log_file, "%2d,", p->if_chain); + + /* writing status */ + switch(p->status) { + case STAT_CRC_OK: fputs("\"CRC_OK\" ,", log_file); break; + case STAT_CRC_BAD: fputs("\"CRC_BAD\",", log_file); break; + case STAT_NO_CRC: fputs("\"NO_CRC\" ,", log_file); break; + case STAT_UNDEFINED:fputs("\"UNDEF\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + + /* writing payload size */ + fprintf(log_file, "%3u,", p->size); + + /* writing modulation */ + switch(p->modulation) { + case MOD_LORA: fputs("\"LORA\",", log_file); break; + case MOD_FSK: fputs("\"FSK\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + + /* writing bandwidth */ + switch(p->bandwidth) { + case BW_500KHZ: fputs("500000,", log_file); break; + case BW_250KHZ: fputs("250000,", log_file); break; + case BW_125KHZ: fputs("125000,", log_file); break; + case BW_62K5HZ: fputs("62500 ,", log_file); break; + case BW_31K2HZ: fputs("31200 ,", log_file); break; + case BW_15K6HZ: fputs("15600 ,", log_file); break; + case BW_7K8HZ: fputs("7800 ,", log_file); break; + case BW_UNDEFINED: fputs("0 ,", log_file); break; + default: fputs("-1 ,", log_file); + } + + /* writing datarate */ + if (p->modulation == MOD_LORA) { + switch (p->datarate) { + case DR_LORA_SF7: fputs("\"SF7\" ,", log_file); break; + case DR_LORA_SF8: fputs("\"SF8\" ,", log_file); break; + case DR_LORA_SF9: fputs("\"SF9\" ,", log_file); break; + case DR_LORA_SF10: fputs("\"SF10\" ,", log_file); break; + case DR_LORA_SF11: fputs("\"SF11\" ,", log_file); break; + case DR_LORA_SF12: fputs("\"SF12\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + } else if (p->modulation == MOD_FSK) { + fprintf(log_file, "\"%6u\",", p->datarate); + } else { + fputs("\"ERR\" ,", log_file); + } + + /* writing coderate */ + switch (p->coderate) { + case CR_LORA_4_5: fputs("\"4/5\",", log_file); break; + case CR_LORA_4_6: fputs("\"2/3\",", log_file); break; + case CR_LORA_4_7: fputs("\"4/7\",", log_file); break; + case CR_LORA_4_8: fputs("\"1/2\",", log_file); break; + case CR_UNDEFINED: fputs("\"\" ,", log_file); break; + default: fputs("\"ERR\",", log_file); + } + + /* writing packet RSSI */ + fprintf(log_file, "%+.0f,", p->rssi); + + /* writing packet average SNR */ + fprintf(log_file, "%+5.1f,", p->snr); + + /* writing hex-encoded payload (bundled in 32-bit words) */ + fputs("\"", log_file); + for (j = 0; j < p->size; ++j) { + if ((j > 0) && (j%4 == 0)) fputs("-", log_file); + fprintf(log_file, "%02X", p->payload[j]); + } + fputs("\"\n", log_file); + ++pkt_in_log; + } + + /* check time and rotate log file if necessary */ + ++time_check; + if (time_check >= 8) { + time_check = 0; + time(&now_time); + if (difftime(now_time, log_start_time) > log_rotate_interval) { + fclose(log_file); + MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); + pkt_in_log = 0; + open_log(); + } + } + } + + if (exit_sig == 1) { + /* clean up before leaving */ + i = lgw_stop(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator stopped successfully\n"); + } else { + MSG("WARNING: failed to stop concentrator successfully\n"); + } + fclose(log_file); + MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); + } + + MSG("INFO: Exiting packet logger program\n"); + return EXIT_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/loragw_pkt_logger/src/parson.c b/loragw_pkt_logger/src/parson.c new file mode 100644 index 0000000..54eae2c --- /dev/null +++ b/loragw_pkt_logger/src/parson.c @@ -0,0 +1,647 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "parson.h" + +#include +#include +#include +#include + +#define ERROR 0 +#define SUCCESS 1 +#define STARTING_CAPACITY 15 +#define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */ +#define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */ +#define MAX_NESTING 19 +#define sizeof_token(a) (sizeof(a) - 1) +#define skip_char(str) ((*str)++) +#define skip_whitespaces(str) while (isspace(**str)) { skip_char(str); } +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define parson_malloc(a) malloc(a) +#define parson_free(a) free((void*)a) +#define parson_realloc(a, b) realloc(a, b) + +/* Type definitions */ +typedef union json_value_value { + const char *string; + double number; + JSON_Object *object; + JSON_Array *array; + int boolean; + int null; +} JSON_Value_Value; + +struct json_value_t { + JSON_Value_Type type; + JSON_Value_Value value; +}; + +struct json_object_t { + const char **names; + JSON_Value **values; + size_t count; + size_t capacity; +}; + +struct json_array_t { + JSON_Value **items; + size_t count; + size_t capacity; +}; + +/* Various */ +static int try_realloc(void **ptr, size_t new_size); +static char * parson_strndup(const char *string, size_t n); +static int is_utf(const unsigned char *string); +static int is_decimal(const char *string, size_t length); + +/* JSON Object */ +static JSON_Object * json_object_init(void); +static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static int json_object_resize(JSON_Object *object, size_t capacity); +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n); +static void json_object_free(JSON_Object *object); + +/* JSON Array */ +static JSON_Array * json_array_init(void); +static int json_array_add(JSON_Array *array, JSON_Value *value); +static int json_array_resize(JSON_Array *array, size_t capacity); +static void json_array_free(JSON_Array *array); + +/* JSON Value */ +static JSON_Value * json_value_init_object(void); +static JSON_Value * json_value_init_array(void); +static JSON_Value * json_value_init_string(const char *string); +static JSON_Value * json_value_init_number(double number); +static JSON_Value * json_value_init_boolean(int boolean); +static JSON_Value * json_value_init_null(void); + +/* Parser */ +static void skip_quotes(const char **string); +static const char * get_processed_string(const char **string); +static JSON_Value * parse_object_value(const char **string, size_t nesting); +static JSON_Value * parse_array_value(const char **string, size_t nesting); +static JSON_Value * parse_string_value(const char **string); +static JSON_Value * parse_boolean_value(const char **string); +static JSON_Value * parse_number_value(const char **string); +static JSON_Value * parse_null_value(const char **string); +static JSON_Value * parse_value(const char **string, size_t nesting); + +/* Various */ +static int try_realloc(void **ptr, size_t new_size) { + void *reallocated_ptr = parson_realloc(*ptr, new_size); + if (!reallocated_ptr) { return ERROR; } + *ptr = reallocated_ptr; + return SUCCESS; +} + +static char * parson_strndup(const char *string, size_t n) { + char *output_string = (char*)parson_malloc(n + 1); + if (!output_string) { return NULL; } + output_string[n] = '\0'; + strncpy(output_string, string, n); + return output_string; +} + +static int is_utf(const unsigned char *s) { + return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); +} + +static int is_decimal(const char *string, size_t length) { + if (length > 1 && string[0] == '0' && string[1] != '.') { return 0; } + if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') { return 0; } + while (length--) { if (strchr("xX", string[length])) { return 0; } } + return 1; +} + +/* JSON Object */ +static JSON_Object * json_object_init(void) { + JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); + if (!new_obj) { return NULL; } + new_obj->names = (const char**)NULL; + new_obj->values = (JSON_Value**)NULL; + new_obj->capacity = 0; + new_obj->count = 0; + return new_obj; +} + +static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { + size_t index; + if (object->count >= object->capacity) { + size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); + if (new_capacity > OBJECT_MAX_CAPACITY) { return ERROR; } + if (json_object_resize(object, new_capacity) == ERROR) { return ERROR; } + } + if (json_object_get_value(object, name) != NULL) { return ERROR; } + index = object->count; + object->names[index] = parson_strndup(name, strlen(name)); + if (!object->names[index]) { return ERROR; } + object->values[index] = value; + object->count++; + return SUCCESS; +} + +static int json_object_resize(JSON_Object *object, size_t capacity) { + if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == ERROR) { return ERROR; } + if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == ERROR) { return ERROR; } + object->capacity = capacity; + return SUCCESS; +} + +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) { + size_t i, name_length; + for (i = 0; i < json_object_get_count(object); i++) { + name_length = strlen(object->names[i]); + if (name_length != n) { continue; } + if (strncmp(object->names[i], name, n) == 0) { return object->values[i]; } + } + return NULL; +} + +static void json_object_free(JSON_Object *object) { + while(object->count--) { + parson_free(object->names[object->count]); + json_value_free(object->values[object->count]); + } + parson_free(object->names); + parson_free(object->values); + parson_free(object); +} + +/* JSON Array */ +static JSON_Array * json_array_init(void) { + JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); + if (!new_array) { return NULL; } + new_array->items = (JSON_Value**)NULL; + new_array->capacity = 0; + new_array->count = 0; + return new_array; +} + +static int json_array_add(JSON_Array *array, JSON_Value *value) { + if (array->count >= array->capacity) { + size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); + if (new_capacity > ARRAY_MAX_CAPACITY) { return ERROR; } + if (!json_array_resize(array, new_capacity)) { return ERROR; } + } + array->items[array->count] = value; + array->count++; + return SUCCESS; +} + +static int json_array_resize(JSON_Array *array, size_t capacity) { + if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == ERROR) { return ERROR; } + array->capacity = capacity; + return SUCCESS; +} + +static void json_array_free(JSON_Array *array) { + while (array->count--) { json_value_free(array->items[array->count]); } + parson_free(array->items); + parson_free(array); +} + +/* JSON Value */ +static JSON_Value * json_value_init_object(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONObject; + new_value->value.object = json_object_init(); + if (!new_value->value.object) { parson_free(new_value); return NULL; } + return new_value; +} + +static JSON_Value * json_value_init_array(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONArray; + new_value->value.array = json_array_init(); + if (!new_value->value.array) { parson_free(new_value); return NULL; } + return new_value; +} + +static JSON_Value * json_value_init_string(const char *string) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONString; + new_value->value.string = string; + return new_value; +} + +static JSON_Value * json_value_init_number(double number) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +static JSON_Value * json_value_init_boolean(int boolean) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONBoolean; + new_value->value.boolean = boolean; + return new_value; +} + +static JSON_Value * json_value_init_null(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { return NULL; } + new_value->type = JSONNull; + return new_value; +} + +/* Parser */ +static void skip_quotes(const char **string) { + skip_char(string); + while (**string != '\"') { + if (**string == '\0') { return; } + if (**string == '\\') { skip_char(string); if (**string == '\0') { return; }} + skip_char(string); + } + skip_char(string); +} + +/* Returns contents of a string inside double quotes and parses escaped + characters inside. + Example: "\u006Corem ipsum" -> lorem ipsum */ +static const char * get_processed_string(const char **string) { + const char *string_start = *string; + char *output, *processed_ptr, *unprocessed_ptr, current_char; + unsigned int utf_val; + skip_quotes(string); + if (**string == '\0') { return NULL; } + output = parson_strndup(string_start + 1, *string - string_start - 2); + if (!output) { return NULL; } + processed_ptr = unprocessed_ptr = output; + while (*unprocessed_ptr) { + current_char = *unprocessed_ptr; + if (current_char == '\\') { + unprocessed_ptr++; + current_char = *unprocessed_ptr; + switch (current_char) { + case '\"': case '\\': case '/': break; + case 'b': current_char = '\b'; break; + case 'f': current_char = '\f'; break; + case 'n': current_char = '\n'; break; + case 'r': current_char = '\r'; break; + case 't': current_char = '\t'; break; + case 'u': + unprocessed_ptr++; + if (!is_utf((const unsigned char*)unprocessed_ptr) || + sscanf(unprocessed_ptr, "%4x", &utf_val) == EOF) { + parson_free(output); return NULL; + } + if (utf_val < 0x80) { + current_char = utf_val; + } else if (utf_val < 0x800) { + *processed_ptr++ = (utf_val >> 6) | 0xC0; + current_char = ((utf_val | 0x80) & 0xBF); + } else { + *processed_ptr++ = (utf_val >> 12) | 0xE0; + *processed_ptr++ = (((utf_val >> 6) | 0x80) & 0xBF); + current_char = ((utf_val | 0x80) & 0xBF); + } + unprocessed_ptr += 3; + break; + default: + parson_free(output); + return NULL; + break; + } + } else if ((unsigned char)current_char < 0x20) { /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ + parson_free(output); + return NULL; + } + *processed_ptr = current_char; + processed_ptr++; + unprocessed_ptr++; + } + *processed_ptr = '\0'; + if (try_realloc((void**)&output, strlen(output) + 1) == ERROR) { return NULL; } + return output; +} + +static JSON_Value * parse_value(const char **string, size_t nesting) { + if (nesting > MAX_NESTING) { return NULL; } + skip_whitespaces(string); + switch (**string) { + case '{': + return parse_object_value(string, nesting + 1); + case '[': + return parse_array_value(string, nesting + 1); + case '\"': + return parse_string_value(string); + case 'f': case 't': + return parse_boolean_value(string); + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parse_number_value(string); + case 'n': + return parse_null_value(string); + default: + return NULL; + } +} + +static JSON_Value * parse_object_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_object(), *new_value = NULL; + JSON_Object *output_object = json_value_get_object(output_value); + const char *new_key = NULL; + if (!output_value) { return NULL; } + skip_char(string); + skip_whitespaces(string); + if (**string == '}') { skip_char(string); return output_value; } /* empty object */ + while (**string != '\0') { + new_key = get_processed_string(string); + skip_whitespaces(string); + if (!new_key || **string != ':') { + json_value_free(output_value); + return NULL; + } + skip_char(string); + new_value = parse_value(string, nesting); + if (!new_value) { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + if(!json_object_add(output_object, new_key, new_value)) { + parson_free(new_key); + parson_free(new_value); + json_value_free(output_value); + return NULL; + } + parson_free(new_key); + skip_whitespaces(string); + if (**string != ',') { break; } + skip_char(string); + skip_whitespaces(string); + } + skip_whitespaces(string); + if (**string != '}' || /* Trim object after parsing is over */ + json_object_resize(output_object, json_object_get_count(output_object)) == ERROR) { + json_value_free(output_value); + return NULL; + } + skip_char(string); + return output_value; +} + +static JSON_Value * parse_array_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_array(), *new_array_value = NULL; + JSON_Array *output_array = json_value_get_array(output_value); + if (!output_value) { return NULL; } + skip_char(string); + skip_whitespaces(string); + if (**string == ']') { /* empty array */ + skip_char(string); + return output_value; + } + while (**string != '\0') { + new_array_value = parse_value(string, nesting); + if (!new_array_value) { + json_value_free(output_value); + return NULL; + } + if(json_array_add(output_array, new_array_value) == ERROR) { + parson_free(new_array_value); + json_value_free(output_value); + return NULL; + } + skip_whitespaces(string); + if (**string != ',') { break; } + skip_char(string); + skip_whitespaces(string); + } + skip_whitespaces(string); + if (**string != ']' || /* Trim array after parsing is over */ + json_array_resize(output_array, json_array_get_count(output_array)) == ERROR) { + json_value_free(output_value); + return NULL; + } + skip_char(string); + return output_value; +} + +static JSON_Value * parse_string_value(const char **string) { + const char *new_string = get_processed_string(string); + if (!new_string) { return NULL; } + return json_value_init_string(new_string); +} + +static JSON_Value * parse_boolean_value(const char **string) { + size_t true_token_size = sizeof_token("true"); + size_t false_token_size = sizeof_token("false"); + if (strncmp("true", *string, true_token_size) == 0) { + *string += true_token_size; + return json_value_init_boolean(1); + } else if (strncmp("false", *string, false_token_size) == 0) { + *string += false_token_size; + return json_value_init_boolean(0); + } + return NULL; +} + +static JSON_Value * parse_number_value(const char **string) { + char *end; + double number = strtod(*string, &end); + JSON_Value *output_value; + if (is_decimal(*string, end - *string)) { + *string = end; + output_value = json_value_init_number(number); + } else { + output_value = NULL; + } + return output_value; +} + +static JSON_Value * parse_null_value(const char **string) { + size_t token_size = sizeof_token("null"); + if (strncmp("null", *string, token_size) == 0) { + *string += token_size; + return json_value_init_null(); + } + return NULL; +} + +/* Parser API */ +JSON_Value * json_parse_file(const char *filename) { + FILE *fp = fopen(filename, "r"); + size_t file_size; + char *file_contents; + JSON_Value *output_value; + if (!fp) { return NULL; } + fseek(fp, 0L, SEEK_END); + file_size = ftell(fp); + rewind(fp); + file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1)); + if (!file_contents) { fclose(fp); return NULL; } + if (fread(file_contents, file_size, 1, fp) < 1) { fclose(fp); return NULL; } + fclose(fp); + file_contents[file_size] = '\0'; + output_value = json_parse_string(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_string(const char *string) { + if (!string || (*string != '{' && *string != '[')) { return NULL; } + return parse_value((const char**)&string, 0); +} + +/* JSON Object API */ +JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { + return json_object_nget_value(object, name, strlen(name)); +} + +const char * json_object_get_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_get_value(object, name)); +} + +double json_object_get_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_get_value(object, name)); +} + +JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_get_value(object, name)); +} + +JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_get_value(object, name)); +} + +int json_object_get_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_get_value(object, name)); +} + +JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { + const char *dot_position = strchr(name, '.'); + if (!dot_position) { return json_object_get_value(object, name); } + object = json_value_get_object(json_object_nget_value(object, name, dot_position - name)); + return json_object_dotget_value(object, dot_position + 1); +} + +const char * json_object_dotget_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_dotget_value(object, name)); +} + +double json_object_dotget_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_dotget_value(object, name)); +} + +JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_dotget_value(object, name)); +} + +JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_dotget_value(object, name)); +} + +int json_object_dotget_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_dotget_value(object, name)); +} + +size_t json_object_get_count(const JSON_Object *object) { + return object ? object->count : 0; +} + +const char * json_object_get_name(const JSON_Object *object, size_t index) { + if (index >= json_object_get_count(object)) { return NULL; } + return object->names[index]; +} + +/* JSON Array API */ +JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { + if (index >= json_array_get_count(array)) { return NULL; } + return array->items[index]; +} + +const char * json_array_get_string(const JSON_Array *array, size_t index) { + return json_value_get_string(json_array_get_value(array, index)); +} + +double json_array_get_number(const JSON_Array *array, size_t index) { + return json_value_get_number(json_array_get_value(array, index)); +} + +JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { + return json_value_get_object(json_array_get_value(array, index)); +} + +JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { + return json_value_get_array(json_array_get_value(array, index)); +} + +int json_array_get_boolean(const JSON_Array *array, size_t index) { + return json_value_get_boolean(json_array_get_value(array, index)); +} + +size_t json_array_get_count(const JSON_Array *array) { + return array ? array->count : 0; +} + +/* JSON Value API */ +JSON_Value_Type json_value_get_type(const JSON_Value *value) { + return value ? value->type : JSONError; +} + +JSON_Object * json_value_get_object(const JSON_Value *value) { + return json_value_get_type(value) == JSONObject ? value->value.object : NULL; +} + +JSON_Array * json_value_get_array(const JSON_Value *value) { + return json_value_get_type(value) == JSONArray ? value->value.array : NULL; +} + +const char * json_value_get_string(const JSON_Value *value) { + return json_value_get_type(value) == JSONString ? value->value.string : NULL; +} + +double json_value_get_number(const JSON_Value *value) { + return json_value_get_type(value) == JSONNumber ? value->value.number : 0; +} + +int json_value_get_boolean(const JSON_Value *value) { + return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; +} + +void json_value_free(JSON_Value *value) { + switch (json_value_get_type(value)) { + case JSONObject: + json_object_free(value->value.object); + break; + case JSONString: + if (value->value.string) { parson_free(value->value.string); } + break; + case JSONArray: + json_array_free(value->value.array); + break; + default: + break; + } + parson_free(value); +} -- cgit v1.2.3