From d857f181635059fd6c227f758cffd0707f26ffe5 Mon Sep 17 00:00:00 2001 From: Mikael Nousiainen Date: Sun, 28 Nov 2021 20:52:29 +0200 Subject: [PATCH] Initial implementation of: reader thread for async handling of rig data, UDP multicast publisher routine and rig state poll thread routine. The reader thread can correctly handle asynchronous data, such as transceive or spectrum data. Work in progress: multi-platform code for I/O routines still missing and the poll routine is not yet in use. Tested briefly on an IC-7300 so far. --- README.multicast | 218 +-- extra/kylix/hamlib_rigapi.pas | 2 +- include/hamlib/rig.h | 40 +- lib/Makefile.am | 2 +- lib/cJSON.c | 3110 +++++++++++++++++++++++++++++++++ lib/cJSON.h | 293 ++++ rigs/elad/elad.c | 6 +- rigs/icom/frame.c | 44 +- rigs/icom/icom.c | 47 +- rigs/icom/icom.h | 2 +- rigs/jrc/jrc.c | 8 +- rigs/kenwood/kenwood.c | 6 +- rigs/pcr/pcr.c | 6 +- rigs/tentec/tt550.c | 10 +- rigs/uniden/uniden.c | 4 +- rigs/uniden/uniden_digital.c | 4 +- src/Makefile.am | 2 +- src/cache.c | 444 +++++ src/cache.h | 32 + src/conf.c | 8 +- src/event.c | 1053 +++++------ src/event.h | 11 +- src/iofunc.c | 169 +- src/iofunc.h | 4 + src/misc.c | 36 + src/misc.h | 9 +- src/network.c | 566 ++++-- src/network.h | 6 +- src/rig.c | 678 ++----- src/snapshot_data.c | 347 ++++ src/snapshot_data.h | 6 + src/token.h | 4 +- tests/dumpcaps.c | 4 +- tests/rigctl_parse.c | 209 +-- tests/rigctld.c | 28 +- 35 files changed, 5648 insertions(+), 1770 deletions(-) create mode 100644 lib/cJSON.c create mode 100644 lib/cJSON.h create mode 100644 src/cache.c create mode 100644 src/cache.h create mode 100644 src/snapshot_data.c create mode 100644 src/snapshot_data.h diff --git a/README.multicast b/README.multicast index 5b85d9aeb..5a1c3a6f9 100644 --- a/README.multicast +++ b/README.multicast @@ -1,10 +1,9 @@ -Planned for version 4.3 -- comments/suggestions about this are more than welcome -Multicast UDP broadcast on port 4531 (one below rigctld 4532) +Planned for version 5.0 -- comments/suggestions about this are more than welcome +Multicast UDP broadcast on port 4532 Bidirectional rig control and status Choice of token pairs or JSON All packets will be tagged with ID=[unique name] -- so multiple rigs can broadcast/rx on the same port - Broadcast packet contents to be based on get_rig_info output This will be the text format of name=value pairs Can be multiple VFO lines @@ -25,163 +24,94 @@ CRC=0xf49f4708 (this is just an example CRC and not accurate for this example) Example JSON { - "__comment1__": "customizable rig identification -- will allow multiple rigs to be on the multicast", - "ID": "Rig#1", - "VFOs": [ + "app": "Hamlib", + "__comment_version__": "protocol version YYYYMMDD x.x.x, 1.0.0 will be used when this is implemented", + "version": "20210521 0.0.0", + "__comment_seq__": "Seq is 1-up sequence number 32-bit -- wraps around to 1 from 2^32-1", + "seq": 1, + "__comment_crc__": "32-bit CRC of entire JSON record replacing the CRC value with 0x00000000", + "crc": "0x00000000", + "rig": { + "__comment1__": "customizable rig identification -- will allow multiple rigs to be on the multicast", + "id": "Rig#1", + "name": "Dummy", + "ptt" : false, + "split": true, + "splitVfo": "VFOB", + "satMode": false, + "status": "OK"; + "errorMsg": "OK", + }, + "vfos": [ { - "Name": "VFOA", - "Freq": 14074000, - "Mode": "USB", - "Width": 5000, - "RX": true, - "TX": false + "name": "VFOA", + "freq": 14074000, + "mode": "USB", + "width": 5000, + "rx": true, + "tx": false }, { - "Name": "VFOB", - "Freq": 14076000, - "Mode": "USB", - "Width": 5000, - "RX": false, - "TX": false + "name": "VFOB", + "freq": 14076000, + "mode": "USB", + "width": 5000, + "rx": false, + "tx": true }], "__comment_spectra__": "Rigs that have spectrum output may include this array data", - "Spectra": [ + "spectra": [ { - "Name": "Main", - "Length": 475, - "__comment_spectrum_data__": "2-char hex bytes so data len=2*Length", - "Data": "00AAFF75BD2AAA...", - "Type": "FIXED|CENTER", - "MinLevel": 0, - "MaxLevel": 140, - "MinStrength": -100, - "MaxStrength": 0, + "__comment_id__": "A numeric ID for the spectrum data stream. These IDs are exposed in rig caps.", + "id": 0, + "__comment_name__": "Name identifying the spectrum data stream and matching the ID. The name corresponds to VFOs.", + "name": "Main", + "__comment_spectrum_length__": "Length of spectrum FFT data in bytes", + "length": 475, + "__comment_spectrum_data__": "Spectrum FFT data in 2-char hexadecimal byte format, so that length of the string is 2 * length", + "data": "00AAFF75BD2AAA...", - "__comment_spectrum_center__": "If Type=CENTER, the following fields will be present:", - "CenterFreq": 14267000, - "Span": 25000, + "type": "FIXED|CENTER", + "minLevel": 0, + "maxLevel": 140, + "minStrength": -100, + "maxStrength": 0, - "__comment_spectrum_fixed__": "If SpectrumType=FIXED, the following fields will be present:", - "LowFreq": 14000000, - "HighFreq": 14250000 + "__comment_spectrum_frequencies__": "The following fields will be calculated automatically by Hamlib based on the spectrum information exposed by the rig", + "centerFreq": 14267000, + "span": 25000, + "lowFreq": 14000000, + "highFreq": 14250000 }, { - "Name": "Sub", - "Length": 475, - "__comment_spectrum_data__": "2-char hex bytes so data len=2*Length", - "Data": "00AAFF75BD2AAA...", - "Type": "FIXED|CENTER", - "MinLevel": 0, - "MaxLevel": 140, - "MinStrength": -100, - "MaxStrength": 0, + "__comment_id__": "A numeric ID for the spectrum data stream. These IDs are exposed in rig caps.", + "id": 1, + "__comment_name__": "Name identifying the spectrum data stream and matching the ID. The name corresponds to VFOs.", + "name": "Sub", + "__comment_spectrum_length__": "Length of spectrum FFT data in bytes", + "length": 475, + "__comment_spectrum_data__": "Spectrum FFT data in 2-char hexadecimal byte format, so that length of the string is 2 * length", + "data": "00AAFF75BD2AAA...", - "__comment_spectrum_center__": "If Type=CENTER, the following fields will be present:", - "CenterFreq": 14267000, - "Span": 25000, + "type": "FIXED|CENTER", + "minLevel": 0, + "maxLevel": 140, + "minStrength": -100, + "maxStrength": 0, - "__comment_spectrum_fixed__": "If SpectrumType=FIXED, the following fields will be present:", - "LowFreq": 14000000, - "HighFreq": 14250000 + "__comment_spectrum_frequencies__": "The following fields will be calculated automatically by Hamlib based on the spectrum information exposed by the rig", + "centerFreq": 14267000, + "span": 25000, + "lowFreq": 14000000, + "highFreq": 14250000 }], - LastCommand { - "ID": "MyApp 123", - "Command": "set_freq VFOA 14074000". - "Status": "OK" + "lastCommand": { + "id": "MyApp 123", + "command": "set_freq VFOA 14074000", + "status": "OK" }, - "PTT" : false, - "Split": true, - "SatMode": false, - "Rig": "Dummy", - "App": "Hamlib", - "__comment_version__": "protocol version YYYYMMDD x.x.x, 1.0.0 will be used when this is implemented", - "Version": "20210521 0.0.0", - "Status": "OK"; - "__comment_seq__": "Seq is 1-up sequence number 32-bit -- wraps around to 1 from 2^32-1", - "Seq": 1, - "__comment_crc__": "32-bit CRC of entire JSON record replacing the CRC value with 0x00000000", - "ErrorMsg": "OK", - "CRC": "0x00000000" } Will be able to set freq, mode, width, ptt, satmode, and split to start since those are common to many apps. More functions will be added as time goes on. - -C# Json Deserialize Example -- beginning of a HamlibMultiCast client -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.IO; - -namespace HamlibMultiCast -{ - public class HamlibMulticastClient - { - public class VFO - { - public string Name; - public ulong Freq; - public string Mode; - public int Width; - public bool RX; - public bool TX; - } - public class SpectrumClass - { - public int Length; - public string Data; - public string Type; - public int MinLevel; - public int MaxLevel; - public int MinStrength; - public int MaxStrength; - public double CenterFreq; - public int Span; - public double LowFreq; - public double HighFreq; - } - LastCommand { - public string ID; // an application name plus sequence number recommended - public string Command; - public string Status; - } - public string ID; - public List VFOs { get; set; } - public bool PTT; - public bool Split; - public bool SatMode; - public string Rig; - public string App; - public string Version; - public string Status; - public UInt32 Seq; - public string CRC; - public SpectrumClass Spectrum { get; set; } - public string ErrorMsg; - } - class Program - { - static int Main(string[] args) - { - Console.WriteLine("HamlibMultiCast Test Json Deserialize"); - ITraceWriter traceWriter = new MemoryTraceWriter(); - try - { - - string json = File.ReadAllText("test.json"); - var client = JsonConvert.DeserializeObject(json, new JsonSerializerSettings { TraceWriter = traceWriter, Converters = { new JavaScriptDateTimeConverter() } }); - Console.WriteLine(traceWriter); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - return 1; - } - return 0; - } - } -} - diff --git a/extra/kylix/hamlib_rigapi.pas b/extra/kylix/hamlib_rigapi.pas index 2437c2add..5d32d7dce 100644 --- a/extra/kylix/hamlib_rigapi.pas +++ b/extra/kylix/hamlib_rigapi.pas @@ -967,7 +967,7 @@ type {* * non overridable fields, internal use *} - hold_decode : integer; + transaction_active : integer; current_vfo : vfo_t; transceive : integer; vfo_list : integer; diff --git a/include/hamlib/rig.h b/include/hamlib/rig.h index 3815fe91e..62575eed7 100644 --- a/include/hamlib/rig.h +++ b/include/hamlib/rig.h @@ -147,7 +147,8 @@ enum rig_errcode_e { RIG_BUSBUSY, /*!< 14 Collision on the bus */ RIG_EARG, /*!< 15 NULL RIG handle or any invalid pointer parameter in get arg */ RIG_EVFO, /*!< 16 Invalid VFO */ - RIG_EDOM /*!< 17 Argument out of domain of func */ + RIG_EDOM, /*!< 17 Argument out of domain of func */ + RIG_EDEPRECATED /*!< 18 Function deprecated */ }; /** @@ -1045,12 +1046,15 @@ typedef uint64_t setting_t; * \brief Transceive mode * The rig notifies the host of any event, like freq changed, mode changed, etc. * \def RIG_TRN_OFF + * \deprecated * Turn it off * \brief Transceive mode * \def RIG_TRN_RIG + * \deprecated * RIG_TRN_RIG means the rig acts asynchronously * \brief Transceive mode * \def RIG_TRN_POLL + * \deprecated * RIG_TRN_POLL means we have to poll the rig * */ @@ -1681,7 +1685,7 @@ struct rig_spectrum_line freq_t low_edge_freq; /*!< Low edge frequency of the spectrum scope in Hz in RIG_SPECTRUM_FIXED mode. */ freq_t high_edge_freq; /*!< High edge frequency of the spectrum scope in Hz in RIG_SPECTRUM_FIXED mode. */ - int spectrum_data_length; /*!< Number of bytes of 8-bit spectrum data in the data buffer. The amount of data may vary if the rig has multiple spectrum scopes, depending on the scope. */ + size_t spectrum_data_length; /*!< Number of bytes of 8-bit spectrum data in the data buffer. The amount of data may vary if the rig has multiple spectrum scopes, depending on the scope. */ unsigned char *spectrum_data; /*!< 8-bit spectrum data covering bandwidth of either the span_freq in center mode or from low edge to high edge in fixed mode. A higher value represents higher signal strength. */ }; @@ -1763,7 +1767,7 @@ struct rig_caps { vfo_op_t vfo_ops; /*!< VFO op bit field list */ scan_t scan_ops; /*!< Scan bit field list */ int targetable_vfo; /*!< Bit field list of direct VFO access commands */ - int transceive; /*!< Supported transceive mode */ + int transceive; /*!< \deprecated Use async_data_supported instead */ int bank_qty; /*!< Number of banks */ int chan_desc_sz; /*!< Max length of memory channel name */ @@ -1985,7 +1989,7 @@ struct rig_caps { const char *clone_combo_get; /*!< String describing key combination to enter save cloning mode */ const char *macro_name; /*!< Rig model macro name */ - int async_data_supported; + int async_data_supported; /*!< Indicates that rig is capable of outputting asynchronous data updates, such as transceive state updates or spectrum data. 1 if true, 0 otherwise. */ int (*read_frame_direct)(RIG *rig, size_t buffer_length, const unsigned char *buffer); @@ -2088,6 +2092,9 @@ enum rig_function_e { RIG_FUNCTION_SET_MEM_ALL_CB, RIG_FUNCTION_GET_MEM_ALL_CB, RIG_FUNCTION_SET_VFO_OPT, + RIG_FUNCTION_READ_FRAME_DIRECT, + RIG_FUNCTION_IS_ASYNC_FRAME, + RIG_FUNCTION_PROCESS_ASYNC_FRAME, }; /** @@ -2198,9 +2205,11 @@ typedef struct hamlib_port { int client_port; /*!< client socket port for tcp connection */ RIG *rig; /*!< our parent RIG device */ - int async; /*!< enable asynchronous data handling if true */ - int fd_sync_write; /*!< file descriptor for writing synchronous data */ - int fd_sync_read; /*!< file descriptor for reading synchronous data */ + int async; /*!< enable asynchronous data handling if true */ + int fd_sync_write; /*!< file descriptor for writing synchronous data */ + int fd_sync_read; /*!< file descriptor for reading synchronous data */ + int fd_sync_error_write; /*!< file descriptor for writing synchronous data error codes */ + int fd_sync_error_read; /*!< file descriptor for reading synchronous data error codes */ } hamlib_port_t; //! @endcond @@ -2363,15 +2372,15 @@ struct rig_state { * non overridable fields, internal use */ - int hold_decode; /*!< set to 1 to hold the event decoder (async) otherwise 0 */ + int transaction_active; /*!< set to 1 to inform the async reader thread that a synchronous command transaction is waiting for a response, otherwise 0 */ vfo_t current_vfo; /*!< VFO currently set */ int vfo_list; /*!< Complete list of VFO for this rig */ int comm_state; /*!< Comm port state, opened/closed. */ rig_ptr_t priv; /*!< Pointer to private rig state data. */ rig_ptr_t obj; /*!< Internal use by hamlib++ for event handling. */ - int transceive; /*!< Whether the transceive mode is on */ - int poll_interval; /*!< Event notification polling period in milliseconds */ + int async_data; /*!< Whether async data mode is on */ + int poll_interval; /*!< Rig state polling period in milliseconds */ freq_t current_freq; /*!< Frequency currently set */ rmode_t current_mode; /*!< Mode currently set */ //rmode_t current_modeB; /*!< Mode currently set VFOB */ @@ -2401,10 +2410,19 @@ struct rig_state { int power_now; /*!< Current RF power level in rig units */ int power_min; /*!< Minimum RF power level in rig units */ int power_max; /*!< Maximum RF power level in rig units */ - unsigned char disable_yaesu_bandselect; /*!< Disables Yaeus band select logic */ + unsigned char disable_yaesu_bandselect; /*!< Disables Yaesu band select logic */ int twiddle_rit; /*!< Suppresses VFOB reading (cached value used) so RIT control can be used */ int twiddle_state; /*!< keeps track of twiddle status */ vfo_t rx_vfo; /*!< Rx VFO currently set */ + + volatile unsigned int snapshot_packet_sequence_number; + + volatile int multicast_publisher_run; + void *multicast_publisher_priv_data; + volatile int async_data_handler_thread_run; + void *async_data_handler_priv_data; + volatile int poll_routine_thread_run; + void *poll_routine_priv_data; }; //! @cond Doxygen_Suppress diff --git a/lib/Makefile.am b/lib/Makefile.am index 36fde6aac..6c45280bc 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -3,5 +3,5 @@ EXTRA_DIST = getopt.c getopt.h getopt_long.c usleep.c \ noinst_LTLIBRARIES = libmisc.la -libmisc_la_SOURCES = +libmisc_la_SOURCES = cJSON.c cJSON.h libmisc_la_LIBADD = $(LTLIBOBJS) $(NET_LIBS) diff --git a/lib/cJSON.c b/lib/cJSON.c new file mode 100644 index 000000000..ae746bd04 --- /dev/null +++ b/lib/cJSON.c @@ -0,0 +1,3110 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + 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. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) +#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ +if (hooks == NULL) +{ +/* Reset hooks */ +global_hooks.allocate = malloc; +global_hooks.deallocate = free; +global_hooks.reallocate = realloc; +return; +} + +global_hooks.allocate = malloc; +if (hooks->malloc_fn != NULL) +{ +global_hooks.allocate = hooks->malloc_fn; +} + +global_hooks.deallocate = free; +if (hooks->free_fn != NULL) +{ +global_hooks.deallocate = hooks->free_fn; +} + +/* use realloc only if both free and malloc are used */ +global_hooks.reallocate = NULL; +if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) +{ +global_hooks.reallocate = realloc; +} +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ +cJSON *next = NULL; +while (item != NULL) +{ +next = item->next; +if (!(item->type & cJSON_IsReference) && (item->child != NULL)) +{ +cJSON_Delete(item->child); +} +if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) +{ +global_hooks.deallocate(item->valuestring); +} +if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) +{ +global_hooks.deallocate(item->string); +} +global_hooks.deallocate(item); +item = next; +} +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } + loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ +if (number >= INT_MAX) +{ +object->valueint = INT_MAX; +} +else if (number <= (double)INT_MIN) +{ +object->valueint = INT_MIN; +} +else +{ +object->valueint = (int)number; +} + +return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ +char *copy = NULL; +/* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ +if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) +{ +return NULL; +} +if (strlen(valuestring) <= strlen(object->valuestring)) +{ +strcpy(object->valuestring, valuestring); +return object->valuestring; +} +copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); +if (copy == NULL) +{ +return NULL; +} +if (object->valuestring != NULL) +{ +cJSON_free(object->valuestring); +} +object->valuestring = copy; + +return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + + fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + + fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + + fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + + fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ +printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + +if ((length < 0) || (buffer == NULL)) +{ +return false; +} + +p.buffer = (unsigned char*)buffer; +p.length = (size_t)length; +p.offset = 0; +p.noalloc = true; +p.format = format; +p.hooks = global_hooks; + +return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + + success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + + fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + + success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + + fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ +return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ +return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ +return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ +if (array == NULL) +{ +return false; +} + +return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ +if ((object == NULL) || (string == NULL)) +{ +return false; +} + +return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ +cJSON *null = cJSON_CreateNull(); +if (add_item_to_object(object, name, null, &global_hooks, false)) +{ +return null; +} + +cJSON_Delete(null); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ +cJSON *true_item = cJSON_CreateTrue(); +if (add_item_to_object(object, name, true_item, &global_hooks, false)) +{ +return true_item; +} + +cJSON_Delete(true_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ +cJSON *false_item = cJSON_CreateFalse(); +if (add_item_to_object(object, name, false_item, &global_hooks, false)) +{ +return false_item; +} + +cJSON_Delete(false_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ +cJSON *bool_item = cJSON_CreateBool(boolean); +if (add_item_to_object(object, name, bool_item, &global_hooks, false)) +{ +return bool_item; +} + +cJSON_Delete(bool_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ +cJSON *number_item = cJSON_CreateNumber(number); +if (add_item_to_object(object, name, number_item, &global_hooks, false)) +{ +return number_item; +} + +cJSON_Delete(number_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ +cJSON *string_item = cJSON_CreateString(string); +if (add_item_to_object(object, name, string_item, &global_hooks, false)) +{ +return string_item; +} + +cJSON_Delete(string_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ +cJSON *raw_item = cJSON_CreateRaw(raw); +if (add_item_to_object(object, name, raw_item, &global_hooks, false)) +{ +return raw_item; +} + +cJSON_Delete(raw_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ +cJSON *object_item = cJSON_CreateObject(); +if (add_item_to_object(object, name, object_item, &global_hooks, false)) +{ +return object_item; +} + +cJSON_Delete(object_item); +return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ +cJSON *array = cJSON_CreateArray(); +if (add_item_to_object(object, name, array, &global_hooks, false)) +{ +return array; +} + +cJSON_Delete(array); +return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ +if ((parent == NULL) || (item == NULL)) +{ +return NULL; +} + +if (item != parent->child) +{ +/* not the first element */ +item->prev->next = item->next; +} +if (item->next != NULL) +{ +/* not the last element */ +item->next->prev = item->prev; +} + +if (item == parent->child) +{ +/* first element */ +parent->child = item->next; +} +else if (item->next == NULL) +{ +/* last element */ +parent->child->prev = item->prev; +} + +/* make sure the detached item doesn't point anywhere anymore */ +item->prev = NULL; +item->next = NULL; + +return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ +if (which < 0) +{ +return NULL; +} + +return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ +cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ +cJSON *to_detach = cJSON_GetObjectItem(object, string); + +return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ +cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + +return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ +cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ +cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ +cJSON *after_inserted = NULL; + +if (which < 0) +{ +return false; +} + +after_inserted = get_array_item(array, (size_t)which); +if (after_inserted == NULL) +{ +return add_item_to_array(array, newitem); +} + +newitem->next = after_inserted; +newitem->prev = after_inserted->prev; +after_inserted->prev = newitem; +if (after_inserted == array->child) +{ +array->child = newitem; +} +else +{ +newitem->prev->next = newitem; +} +return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ +if ((parent == NULL) || (replacement == NULL) || (item == NULL)) +{ +return false; +} + +if (replacement == item) +{ +return true; +} + +replacement->next = item->next; +replacement->prev = item->prev; + +if (replacement->next != NULL) +{ +replacement->next->prev = replacement; +} +if (parent->child == item) +{ +if (parent->child->prev == parent->child) +{ +replacement->prev = replacement; +} +parent->child = replacement; +} +else +{ /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ +if (replacement->prev != NULL) +{ +replacement->prev->next = replacement; +} +if (replacement->next == NULL) +{ +parent->child->prev = replacement; +} +} + +item->next = NULL; +item->prev = NULL; +cJSON_Delete(item); + +return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ +if (which < 0) +{ +return false; +} + +return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ +return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ +return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ +cJSON *item = cJSON_New_Item(&global_hooks); +if(item) +{ +item->type = boolean ? cJSON_True : cJSON_False; +} + +return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + + fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/lib/cJSON.h b/lib/cJSON.h new file mode 100644 index 000000000..90c6edb0e --- /dev/null +++ b/lib/cJSON.h @@ -0,0 +1,293 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + 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 cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/rigs/elad/elad.c b/rigs/elad/elad.c index 346c96909..427fe6500 100644 --- a/rigs/elad/elad.c +++ b/rigs/elad/elad.c @@ -189,7 +189,7 @@ int elad_transaction(RIG *rig, const char *cmdstr, char *data, size_t datasize) rs = &rig->state; - rs->hold_decode = 1; + rs->transaction_active = 1; /* Emulators don't need any post_write_delay */ if (priv->is_emulation) { rs->rigport.post_write_delay = 0; } @@ -237,7 +237,7 @@ transaction_write: if (!datasize) { - rig->state.hold_decode = 0; + rig->state.transaction_active = 0; /* no reply expected so we need to write a command that always gives a reply so we can read any error replies from the actual @@ -414,7 +414,7 @@ transaction_read: transaction_quit: - rs->hold_decode = 0; + rs->transaction_active = 0; return retval; } diff --git a/rigs/icom/frame.c b/rigs/icom/frame.c index 2137df20e..c6351262f 100644 --- a/rigs/icom/frame.c +++ b/rigs/icom/frame.c @@ -153,7 +153,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, /* * should check return code and that write wrote cmd_len chars! */ - Hold_Decode(rig); + set_transaction_active(rig); rig_flush(&rs->rigport); @@ -163,7 +163,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, if (retval != RIG_OK) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(retval); } @@ -185,14 +185,14 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, if (retval == -RIG_ETIMEOUT || retval == 0) { /* Nothing received, CI-V interface is not echoing */ - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_BUSERROR); } if (retval < 0) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); /* Other error, return it */ RETURNFUNC(retval); @@ -200,7 +200,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, if (retval < 1) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_EPROTO); } @@ -209,7 +209,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, { case COL: /* Collision */ - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_BUSBUSY); @@ -220,7 +220,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, default: /* Timeout after reading at least one character */ /* Problem on ci-v bus? */ - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_BUSERROR); } @@ -230,7 +230,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, /* Not the same length??? */ /* Problem on ci-v bus? */ /* Someone else got a packet in? */ - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_EPROTO); } @@ -240,7 +240,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, /* Frames are different? */ /* Problem on ci-v bus? */ /* Someone else got a packet in? */ - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_EPROTO); } @@ -251,7 +251,7 @@ int icom_one_transaction(RIG *rig, unsigned char cmd, int subcmd, */ if (data_len == NULL) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(RIG_OK); } @@ -284,7 +284,7 @@ read_another_frame: if (frm_len < 0) { rig_unlock(); - Unhold_Decode(rig); + set_transaction_inactive(rig); /* RIG_TIMEOUT: timeout getting response, return timeout */ /* other error: return it */ RETURNFUNC(frm_len); @@ -293,7 +293,7 @@ read_another_frame: if (frm_len < 1) { rig_unlock(); - Unhold_Decode(rig); + set_transaction_inactive(rig); RETURNFUNC(-RIG_EPROTO); } @@ -301,7 +301,7 @@ read_another_frame: if (retval < 0) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(retval); } @@ -318,7 +318,7 @@ read_another_frame: switch (buf[frm_len - 1]) { case COL: - Unhold_Decode(rig); + set_transaction_inactive(rig); /* Collision */ rig_unlock(); RETURNFUNC(-RIG_BUSBUSY); @@ -328,12 +328,12 @@ read_another_frame: break; case NAK: - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_ERJCTED); default: - Unhold_Decode(rig); + set_transaction_inactive(rig); /* Timeout after reading at least one character */ /* Problem on ci-v bus? */ rig_unlock(); @@ -342,7 +342,7 @@ read_another_frame: if (frm_len < ACKFRMLEN) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_EPROTO); } @@ -351,7 +351,7 @@ read_another_frame: // e.g. fe fe e0 50 fa fd if (frm_len == 6 && NAK == buf[frm_len - 2]) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_ERJCTED); } @@ -362,7 +362,7 @@ read_another_frame: // has to be one of these two now or frame is corrupt if (FI != buf[frm_len - 1] && ACK != buf[frm_len - 1]) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_BUSBUSY); } @@ -371,7 +371,7 @@ read_another_frame: if (frm_data_len <= 0) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_EPROTO); } @@ -389,7 +389,7 @@ read_another_frame: if (elapsed_ms > rs->rigport.timeout) { - Unhold_Decode(rig); + set_transaction_inactive(rig); rig_unlock(); RETURNFUNC(-RIG_ETIMEOUT); } @@ -397,7 +397,7 @@ read_another_frame: goto read_another_frame; } - Unhold_Decode(rig); + set_transaction_inactive(rig); *data_len = frm_data_len; memcpy(data, buf + 4, *data_len); diff --git a/rigs/icom/icom.c b/rigs/icom/icom.c index 6ad781007..0e8cb8b48 100644 --- a/rigs/icom/icom.c +++ b/rigs/icom/icom.c @@ -44,6 +44,7 @@ #include "icom_defs.h" #include "frame.h" #include "misc.h" +#include "event.h" // we automatically determine availability of the 1A 03 command enum { ENUM_1A_03_UNK, ENUM_1A_03_YES, ENUM_1A_03_NO }; @@ -8455,7 +8456,7 @@ int icom_mW2power(RIG *rig, float *power, unsigned int mwpower, freq_t freq, RETURNFUNC(RIG_OK); } -static int icom_parse_spectrum_frame(RIG *rig, int length, +static int icom_parse_spectrum_frame(RIG *rig, size_t length, const unsigned char *frame_data) { struct rig_caps *caps = rig->caps; @@ -8466,7 +8467,7 @@ static int icom_parse_spectrum_frame(RIG *rig, int length, int division = (int) from_bcd(frame_data + 1, 1 * 2); int max_division = (int) from_bcd(frame_data + 2, 1 * 2); - int spectrum_data_length_in_frame; + size_t spectrum_data_length_in_frame; const unsigned char *spectrum_data_start_in_frame; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); @@ -8541,7 +8542,7 @@ static int icom_parse_spectrum_frame(RIG *rig, int length, cache->spectrum_metadata_valid = 1; rig_debug(RIG_DEBUG_TRACE, - "%s: Spectrum line start: id=%d division=%d max_division=%d mode=%d center=%.0f span=%.0f low_edge=%.0f high_edge=%.0f oor=%d data_length=%d\n", + "%s: Spectrum line start: id=%d division=%d max_division=%d mode=%d center=%.0f span=%.0f low_edge=%.0f high_edge=%.0f oor=%d data_length=%ld\n", __func__, spectrum_id, division, max_division, spectrum_scope_mode, cache->spectrum_center_freq, cache->spectrum_span_freq, cache->spectrum_low_edge_freq, cache->spectrum_high_edge_freq, out_of_range, @@ -8563,7 +8564,7 @@ static int icom_parse_spectrum_frame(RIG *rig, int length, priv_caps->spectrum_scope_caps.spectrum_line_length) { rig_debug(RIG_DEBUG_ERR, - "%s: too much spectrum scope data received: %d bytes > %d bytes expected\n", + "%s: too much spectrum scope data received: %ld bytes > %d bytes expected\n", __func__, offset + spectrum_data_length_in_frame, priv_caps->spectrum_scope_caps.spectrum_line_length); RETURNFUNC(-RIG_EPROTO); @@ -8578,6 +8579,7 @@ static int icom_parse_spectrum_frame(RIG *rig, int length, { struct rig_spectrum_line spectrum_line = { + .id = spectrum_id, .data_level_min = priv_caps->spectrum_scope_caps.data_level_min, .data_level_max = priv_caps->spectrum_scope_caps.data_level_max, .signal_strength_min = priv_caps->spectrum_scope_caps.signal_strength_min, @@ -8591,10 +8593,7 @@ static int icom_parse_spectrum_frame(RIG *rig, int length, .spectrum_data = cache->spectrum_data, }; - if (rig->callbacks.spectrum_event) - { - rig->callbacks.spectrum_event(rig, &spectrum_line, rig->callbacks.spectrum_arg); - } + rig_fire_spectrum_event(rig, &spectrum_line); cache->spectrum_metadata_valid = 0; } @@ -8634,38 +8633,19 @@ int icom_process_async_frame(RIG *rig, size_t frame_length, */ switch (frame[4]) { - case C_SND_FREQ: - + case C_SND_FREQ: { /* * TODO: the freq length might be less than 4 or 5 bytes * on older rigs! */ - if (rig->callbacks.freq_event) - { - freq_t freq; - freq = from_bcd(frame + 5, (priv->civ_731_mode ? 4 : 5) * 2); - RETURNFUNC(rig->callbacks.freq_event(rig, RIG_VFO_CURR, freq, - rig->callbacks.freq_arg)); - } - else - { - RETURNFUNC(-RIG_ENAVAIL); - } - + freq_t freq = (freq_t) from_bcd(frame + 5, (priv->civ_731_mode ? 4 : 5) * 2); + rig_fire_freq_event(rig, RIG_VFO_CURR, freq); break; + } case C_SND_MODE: - if (rig->callbacks.mode_event) - { - icom2rig_mode(rig, frame[5], frame[6], &mode, &width); - RETURNFUNC(rig->callbacks.mode_event(rig, RIG_VFO_CURR, - mode, width, rig->callbacks.mode_arg)); - } - else - { - RETURNFUNC(-RIG_ENAVAIL); - } - + icom2rig_mode(rig, frame[5], frame[6], &mode, &width); + rig_fire_mode_event(rig, RIG_VFO_CURR, mode, width); break; case C_CTL_SCP: @@ -8673,7 +8653,6 @@ int icom_process_async_frame(RIG *rig, size_t frame_length, { icom_parse_spectrum_frame(rig, frame_length - (6 + 1), frame + 6); } - break; default: diff --git a/rigs/icom/icom.h b/rigs/icom/icom.h index 10d23fdd9..56a896c99 100644 --- a/rigs/icom/icom.h +++ b/rigs/icom/icom.h @@ -181,7 +181,7 @@ struct icom_spectrum_scope_cache freq_t spectrum_span_freq; /*!< The frequency span of the current spectrum scope line being received */ freq_t spectrum_low_edge_freq; /*!< The low edge frequency of the current spectrum scope line being received */ freq_t spectrum_high_edge_freq; /*!< The high edge frequency of the current spectrum scope line being received */ - int spectrum_data_length; /*!< Number of bytes of 8-bit spectrum data in the data buffer. The amount of data may vary if the rig has multiple spectrum scopes, depending on the scope. */ + size_t spectrum_data_length; /*!< Number of bytes of 8-bit spectrum data in the data buffer. The amount of data may vary if the rig has multiple spectrum scopes, depending on the scope. */ unsigned char *spectrum_data; /*!< Dynamically allocated buffer for raw spectrum data */ }; diff --git a/rigs/jrc/jrc.c b/rigs/jrc/jrc.c index 61e429f71..7a5583b9f 100644 --- a/rigs/jrc/jrc.c +++ b/rigs/jrc/jrc.c @@ -77,25 +77,25 @@ static int jrc_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, rig_flush(&rs->rigport); - Hold_Decode(rig); + set_transaction_active(rig); retval = write_block(&rs->rigport, cmd, cmd_len); if (retval != RIG_OK) { - Unhold_Decode(rig); + set_transaction_inactive(rig); return retval; } if (!data || !data_len) { - Unhold_Decode(rig); + set_transaction_inactive(rig); return 0; } retval = read_string(&rs->rigport, data, BUFSZ, EOM, strlen(EOM), 0); - Unhold_Decode(rig); + set_transaction_inactive(rig); if (retval < 0) { diff --git a/rigs/kenwood/kenwood.c b/rigs/kenwood/kenwood.c index 40572df83..6489945c0 100644 --- a/rigs/kenwood/kenwood.c +++ b/rigs/kenwood/kenwood.c @@ -254,7 +254,7 @@ int kenwood_transaction(RIG *rig, const char *cmdstr, char *data, rs = &rig->state; - rs->hold_decode = 1; + rs->transaction_active = 1; /* Emulators don't need any post_write_delay */ if (priv->is_emulation) { rs->rigport.post_write_delay = 0; } @@ -350,7 +350,7 @@ transaction_write: if (!datasize) { - rig->state.hold_decode = 0; + rig->state.transaction_active = 0; // there are some commands that have problems with immediate follow-up // so we'll just ignore them @@ -578,7 +578,7 @@ transaction_quit: strncpy(priv->last_if_response, buffer, caps->if_len); } - rs->hold_decode = 0; + rs->transaction_active = 0; RETURNFUNC(retval); } diff --git a/rigs/pcr/pcr.c b/rigs/pcr/pcr.c index 8add36dab..b0c9e0eb2 100644 --- a/rigs/pcr/pcr.c +++ b/rigs/pcr/pcr.c @@ -361,11 +361,11 @@ pcr_send(RIG *rig, const char *cmd) /* XXX not required in auto update mode? (should not harm) */ priv->cmd_buf[len + 0] = 0x0a; - rs->hold_decode = 1; + rs->transaction_active = 1; err = write_block(&rs->rigport, priv->cmd_buf, len + 1); - rs->hold_decode = 0; + rs->transaction_active = 0; return err; } @@ -520,8 +520,6 @@ pcr_init(RIG *rig) priv->sub_rcvr = priv->main_rcvr; priv->current_vfo = RIG_VFO_MAIN; - rig->state.transceive = RIG_TRN_OFF; - return RIG_OK; } diff --git a/rigs/tentec/tt550.c b/rigs/tentec/tt550.c index 6fdea17ff..edfc18d2f 100644 --- a/rigs/tentec/tt550.c +++ b/rigs/tentec/tt550.c @@ -79,10 +79,10 @@ tt550_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, rs = &rig->state; /* - * Hold_Decode keeps the asynchronous decode routine from being called + * set_transaction_active keeps the asynchronous decode routine from being called * when we get data back from a normal command. */ - Hold_Decode(rig); + set_transaction_active(rig); rig_flush(&rs->rigport); @@ -90,7 +90,7 @@ tt550_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, if (retval != RIG_OK) { - Unhold_Decode(rig); + set_transaction_inactive(rig); return retval; } @@ -99,7 +99,7 @@ tt550_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, */ if (!data || !data_len) { - Unhold_Decode(rig); + set_transaction_inactive(rig); return 0; } @@ -117,7 +117,7 @@ tt550_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, *data_len = retval; - Unhold_Decode(rig); + set_transaction_inactive(rig); return RIG_OK; } diff --git a/rigs/uniden/uniden.c b/rigs/uniden/uniden.c index c0399802d..a583dfa90 100644 --- a/rigs/uniden/uniden.c +++ b/rigs/uniden/uniden.c @@ -128,7 +128,7 @@ uniden_transaction(RIG *rig, const char *cmdstr, int cmd_len, size_t reply_len = BUFSZ; rs = &rig->state; - rs->hold_decode = 1; + rs->transaction_active = 1; transaction_write: @@ -267,7 +267,7 @@ transaction_write: retval = RIG_OK; transaction_quit: - rs->hold_decode = 0; + rs->transaction_active = 0; return retval; } diff --git a/rigs/uniden/uniden_digital.c b/rigs/uniden/uniden_digital.c index 8e2fe3761..f2f130ab4 100644 --- a/rigs/uniden/uniden_digital.c +++ b/rigs/uniden/uniden_digital.c @@ -110,7 +110,7 @@ uniden_digital_transaction(RIG *rig, const char *cmdstr, int cmd_len, size_t reply_len = BUFSZ; rs = &rig->state; - rs->hold_decode = 1; + rs->transaction_active = 1; transaction_write: @@ -262,7 +262,7 @@ transaction_write: retval = RIG_OK; transaction_quit: - rs->hold_decode = 0; + rs->transaction_active = 0; return retval; } diff --git a/src/Makefile.am b/src/Makefile.am index 89fe5a067..b3b4b585a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,7 +11,7 @@ RIGSRC = hamlibdatetime.h rig.c serial.c serial.h misc.c misc.h register.c regis network.c network.h cm108.c cm108.h gpio.c gpio.h idx_builtin.h token.h \ par_nt.h microham.c microham.h amplifier.c amp_reg.c amp_conf.c \ amp_conf.h amp_settings.c extamp.c sleep.c sleep.h sprintflst.c \ - sprintflst.h + sprintflst.h cache.c cache.h snapshot_data.c snapshot_data.h lib_LTLIBRARIES = libhamlib.la libhamlib_la_SOURCES = $(RIGSRC) diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 000000000..c359b4734 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,444 @@ +/* + * Hamlib Interface - rig state cache routines + * Copyright (c) 2000-2012 by Stephane Fillod + * Copyright (c) 2000-2003 by Frank Singleton + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "cache.h" +#include "misc.h" + +#define CHECK_RIG_ARG(r) (!(r) || !(r)->caps || !(r)->state.comm_state) + +/** + * \addtogroup rig + * @{ + */ + +int rig_set_cache_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) +{ + ENTERFUNC; + + rig_cache_show(rig, __func__, __LINE__); + + if (vfo == RIG_VFO_CURR) + { + // if CURR then update this before we figure out the real VFO + vfo = rig->state.current_vfo; + } + + // pick a sane default + if (vfo == RIG_VFO_NONE || vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } + + if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; + + switch (vfo) + { + case RIG_VFO_ALL: // we'll use NONE to reset all VFO caches + elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_INVALIDATE); + break; + + case RIG_VFO_A: + case RIG_VFO_MAIN: + case RIG_VFO_MAIN_A: + rig->state.cache.modeMainA = mode; + + if (width > 0) { rig->state.cache.widthMainA = width; } + + elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_SET); + elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_SET); + break; + + case RIG_VFO_B: + case RIG_VFO_SUB: + case RIG_VFO_MAIN_B: + rig->state.cache.modeMainB = mode; + + if (width > 0) { rig->state.cache.widthMainB = width; } + + elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_SET); + elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_SET); + break; + + case RIG_VFO_C: + case RIG_VFO_MAIN_C: + rig->state.cache.modeMainC = mode; + + if (width > 0) { rig->state.cache.widthMainC = width; } + + elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_SET); + elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_SET); + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo=%s\n", __func__, rig_strvfo(vfo)); + RETURNFUNC(-RIG_EINTERNAL); + } + + rig_cache_show(rig, __func__, __LINE__); + RETURNFUNC(RIG_OK); +} + +int rig_set_cache_freq(RIG *rig, vfo_t vfo, freq_t freq) +{ + int flag = HAMLIB_ELAPSED_SET; + + if (rig_need_debug(RIG_DEBUG_CACHE)) + { + ENTERFUNC; + rig_cache_show(rig, __func__, __LINE__); + } + + rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, current_vfo=%s\n", __func__, + rig_strvfo(vfo), rig_strvfo(rig->state.current_vfo)); + + if (vfo == RIG_VFO_CURR) + { + // if CURR then update this before we figure out the real VFO + vfo = rig->state.current_vfo; + } + + // if freq == 0 then we are asking to invalidate the cache + if (freq == 0) { flag = HAMLIB_ELAPSED_INVALIDATE; } + + // pick a sane default + if (vfo == RIG_VFO_NONE || vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } + + if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; + + if (rig_need_debug(RIG_DEBUG_CACHE)) + { + rig_debug(RIG_DEBUG_CACHE, "%s: set vfo=%s to freq=%.0f\n", __func__, + rig_strvfo(vfo), freq); + } + + switch (vfo) + { + case RIG_VFO_ALL: // we'll use NONE to reset all VFO caches + elapsed_ms(&rig->state.cache.time_freqMainA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqMainB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqMainC, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqSubA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqSubB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqSubC, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_freqMem, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_vfo, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_ptt, HAMLIB_ELAPSED_INVALIDATE); + elapsed_ms(&rig->state.cache.time_split, HAMLIB_ELAPSED_INVALIDATE); + break; + + case RIG_VFO_A: + case RIG_VFO_MAIN: + case RIG_VFO_MAIN_A: + rig->state.cache.freqMainA = freq; + elapsed_ms(&rig->state.cache.time_freqMainA, flag); + break; + + case RIG_VFO_B: + case RIG_VFO_MAIN_B: + case RIG_VFO_SUB: + rig->state.cache.freqMainB = freq; + elapsed_ms(&rig->state.cache.time_freqMainB, flag); + break; + + case RIG_VFO_C: + case RIG_VFO_MAIN_C: + rig->state.cache.freqMainC = freq; + elapsed_ms(&rig->state.cache.time_freqMainC, flag); + break; + + case RIG_VFO_SUB_A: + rig->state.cache.freqSubA = freq; + elapsed_ms(&rig->state.cache.time_freqSubA, flag); + break; + + case RIG_VFO_SUB_B: + rig->state.cache.freqSubB = freq; + elapsed_ms(&rig->state.cache.time_freqSubB, flag); + break; + + case RIG_VFO_SUB_C: + rig->state.cache.freqSubC = freq; + elapsed_ms(&rig->state.cache.time_freqSubC, flag); + break; + + case RIG_VFO_MEM: + rig->state.cache.freqMem = freq; + elapsed_ms(&rig->state.cache.time_freqMem, flag); + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo?, vfo=%s\n", __func__, + rig_strvfo(vfo)); + RETURNFUNC(-RIG_EINVAL); + } + + if (rig_need_debug(RIG_DEBUG_CACHE)) + { + rig_cache_show(rig, __func__, __LINE__); + RETURNFUNC(RIG_OK); + } + + return RIG_OK; +} + +/** + * \brief get cached values for a VFO + * \param rig The rig handle + * \param vfo The VFO to get information from + * \param freq The frequency is stored here + * \param cache_ms_freq The age of the last frequency update in ms + * \param mode The mode is stored here + * \param cache_ms_mode The age of the last mode update in ms + * \param width The width is stored here + * \param cache_ms_width The age of the last width update in ms + * + * Use this to query the cache and then determine to actually fetch data from + * the rig. + * + * \note All pointers must be given. No pointer can be left at NULL + * + * \return RIG_OK if the operation has been successful, otherwise + * a negative value if an error occurred (in which case, cause is + * set appropriately). + * + */ +int rig_get_cache(RIG *rig, vfo_t vfo, freq_t *freq, int *cache_ms_freq, + rmode_t *mode, int *cache_ms_mode, pbwidth_t *width, int *cache_ms_width) +{ + if (CHECK_RIG_ARG(rig) || !freq || !cache_ms_freq || + !mode || !cache_ms_mode || !width || !cache_ms_width) + { + RETURNFUNC(-RIG_EINVAL); + } + + if (rig_need_debug(RIG_DEBUG_CACHE)) + { + ENTERFUNC; + } + + rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, current_vfo=%s\n", __func__, + rig_strvfo(vfo), rig_strvfo(rig->state.current_vfo)); + + if (vfo == RIG_VFO_CURR) + { + vfo = rig->state.current_vfo; + } + else if (vfo == RIG_VFO_OTHER) + { + switch (vfo) + { + case RIG_VFO_OTHER: + vfo = RIG_VFO_OTHER; + break; + case RIG_VFO_A: + vfo = RIG_VFO_B; + break; + case RIG_VFO_MAIN_A: + vfo = RIG_VFO_MAIN_B; + break; + case RIG_VFO_MAIN: + vfo = RIG_VFO_SUB; + break; + case RIG_VFO_B: + vfo = RIG_VFO_A; + break; + case RIG_VFO_MAIN_B: + vfo = RIG_VFO_MAIN_A; + break; + case RIG_VFO_SUB_A: + vfo = RIG_VFO_SUB_B; + break; + case RIG_VFO_SUB_B: + vfo = RIG_VFO_SUB_A; + break; + default: + rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo=%s\n", __func__, rig_strvfo(vfo)); + } + } + + // pick a sane default + if (vfo == RIG_VFO_CURR || vfo == RIG_VFO_NONE) { vfo = RIG_VFO_A; } + + // If we're in satmode we map SUB to SUB_A + if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; + + switch (vfo) + { + case RIG_VFO_CURR: + *freq = rig->state.cache.freqCurr; + *mode = rig->state.cache.modeCurr; + *width = rig->state.cache.widthCurr; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqCurr, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeCurr, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthCurr, + HAMLIB_ELAPSED_GET); + break; + case RIG_VFO_OTHER: + *freq = rig->state.cache.freqOther; + *mode = rig->state.cache.modeOther; + *width = rig->state.cache.widthOther; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqOther, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeOther, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthOther, + HAMLIB_ELAPSED_GET); + break; + case RIG_VFO_A: + case RIG_VFO_MAIN: + case RIG_VFO_MAIN_A: + *freq = rig->state.cache.freqMainA; + *mode = rig->state.cache.modeMainA; + *width = rig->state.cache.widthMainA; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainA, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainA, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainA, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_B: + case RIG_VFO_SUB: + case RIG_VFO_MAIN_B: + *freq = rig->state.cache.freqMainB; + *mode = rig->state.cache.modeMainB; + *width = rig->state.cache.widthMainB; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainB, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainB, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainB, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_SUB_A: + *freq = rig->state.cache.freqSubA; + *mode = rig->state.cache.modeSubA; + *width = rig->state.cache.widthSubA; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubA, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubA, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubA, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_SUB_B: + *freq = rig->state.cache.freqSubB; + *mode = rig->state.cache.modeSubB; + *width = rig->state.cache.widthSubB; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubB, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubB, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubB, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_C: + //case RIG_VFO_MAINC: // not used by any rig yet + *freq = rig->state.cache.freqMainC; + *mode = rig->state.cache.modeMainC; + *width = rig->state.cache.widthMainC; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainC, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainC, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainC, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_SUB_C: + *freq = rig->state.cache.freqSubC; + *mode = rig->state.cache.modeSubC; + *width = rig->state.cache.widthSubC; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubC, + HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubC, + HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubC, + HAMLIB_ELAPSED_GET); + break; + + case RIG_VFO_MEM: + *freq = rig->state.cache.freqMem; + *mode = rig->state.cache.modeMem; + *width = rig->state.cache.widthMem; + *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMem, HAMLIB_ELAPSED_GET); + *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMem, HAMLIB_ELAPSED_GET); + *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMem, + HAMLIB_ELAPSED_GET); + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo?, vfo=%s\n", __func__, + rig_strvfo(vfo)); + RETURNFUNC(-RIG_EINVAL); + } + + rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, freq=%.0f, mode=%s, width=%d\n", __func__, rig_strvfo(vfo), + (double)*freq, rig_strrmode(*mode), (int)*width); + + if (rig_need_debug(RIG_DEBUG_CACHE)) + { + RETURNFUNC(RIG_OK); + } + + return RIG_OK; +} + +void rig_cache_show(RIG *rig, const char *func, int line) +{ + rig_debug(RIG_DEBUG_CACHE, + "%s(%d): freqMainA=%.0f, modeMainA=%s, widthMainA=%d\n", func, line, + rig->state.cache.freqMainA, rig_strrmode(rig->state.cache.modeMainA), + (int)rig->state.cache.widthMainA); + rig_debug(RIG_DEBUG_CACHE, + "%s(%d): freqMainB=%.0f, modeMainB=%s, widthMainB=%d\n", func, line, + rig->state.cache.freqMainB, rig_strrmode(rig->state.cache.modeMainB), + (int)rig->state.cache.widthMainB); + + if (rig->state.vfo_list & RIG_VFO_SUB_A) + { + rig_debug(RIG_DEBUG_CACHE, + "%s(%d): freqSubA=%.0f, modeSubA=%s, widthSubA=%d\n", func, line, + rig->state.cache.freqSubA, rig_strrmode(rig->state.cache.modeSubA), + (int)rig->state.cache.widthSubA); + rig_debug(RIG_DEBUG_CACHE, + "%s(%d): freqSubB=%.0f, modeSubB=%s, widthSubB=%d\n", func, line, + rig->state.cache.freqSubB, rig_strrmode(rig->state.cache.modeSubB), + (int)rig->state.cache.widthSubB); + } +} + +/*! @} */ diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 000000000..08d98ea5f --- /dev/null +++ b/src/cache.h @@ -0,0 +1,32 @@ +/* + * Hamlib Interface - rig state cache routines + * Copyright (c) 2000-2012 by Stephane Fillod + * Copyright (c) 2000-2003 by Frank Singleton + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _CACHE_H +#define _CACHE_H + +#include + +int rig_set_cache_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); +int rig_set_cache_freq(RIG *rig, vfo_t vfo, freq_t freq); +void rig_cache_show(RIG *rig, const char *func, int line); + +#endif diff --git a/src/conf.c b/src/conf.c index 2f31c369a..1f7cf2a5d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -89,9 +89,9 @@ static const struct confparams frontend_cfg_params[] = "0", RIG_CONF_NUMERIC, { .n = { 0.0, 1000.0, .001 } } }, { - TOK_POLL_INTERVAL, "poll_interval", "Polling interval", - "Polling interval in millisecond for transceive emulation", - "500", RIG_CONF_NUMERIC, { .n = { 0, 1000000, 1 } } + TOK_POLL_INTERVAL, "poll_interval", "Rig state poll interval in milliseconds", + "Polling interval in milliseconds for transceive emulation, value of 0 disables polling", + "0", RIG_CONF_NUMERIC, { .n = { 0, 1000000, 1 } } }, { TOK_PTT_TYPE, "ptt_type", "PTT type", @@ -582,6 +582,8 @@ static int frontend_set_conf(RIG *rig, token_t token, const char *val) case TOK_POLL_INTERVAL: rs->poll_interval = atof(val); + // Make sure cache times out before next poll cycle + rig_set_cache_timeout_ms(rig, HAMLIB_CACHE_ALL, atol(val)); break; case TOK_LO_FREQ: diff --git a/src/event.c b/src/event.c index 188b1aaf2..e3183acef 100644 --- a/src/event.c +++ b/src/event.c @@ -37,526 +37,323 @@ #include #include -#include -#include #include #include - -#ifdef HAVE_SYS_TIME_H -# include -#endif - -#include - -#ifdef HAVE_SYS_IOCTL_H -# include -#endif - -#include -#include #include +#ifdef HAVE_PTHREAD +# include +#endif + #include #include "event.h" #include "misc.h" - -#if defined(WIN32) && !defined(HAVE_TERMIOS_H) -# include "win32termios.h" -# define select win32_serial_select -#endif - -#ifndef DOC_HIDDEN +#include "cache.h" +#include "network.h" #define CHECK_RIG_ARG(r) (!(r) || !(r)->caps || !(r)->state.comm_state) - -#ifdef HAVE_SIGACTION -static struct sigaction hamlib_trn_oldact, hamlib_trn_poll_oldact; - -#ifdef HAVE_SIGINFO_T -static void sa_sigioaction(int signum, siginfo_t *si, void *data); -static void sa_sigalrmaction(int signum, siginfo_t *si, void *data); -#else -static void sa_sigiohandler(int signum); -static void sa_sigalrmhandler(int signum); -#endif -#endif - - -/* This one should be in an include file */ -extern int foreach_opened_rig(int (*cfunc)(RIG *, rig_ptr_t), rig_ptr_t data); - - -/* - * add_trn_rig - * not exported in Hamlib API. - * Assumes rig->caps->transceive == RIG_TRN_RIG - */ -int add_trn_rig(RIG *rig) +#ifdef HAVE_PTHREAD +typedef struct rig_poll_routine_args_s { -#ifdef HAVE_SIGACTION - struct sigaction act; - int status; + RIG *rig; +} rig_poll_routine_args; - /* - * FIXME: multiple open will register several time SIGIO hndlr - */ - memset(&act, 0, sizeof(act)); +typedef struct rig_poll_routine_priv_data_s +{ + pthread_t thread_id; + rig_poll_routine_args args; +} rig_poll_routine_priv_data; -#ifdef HAVE_SIGINFO_T - act.sa_sigaction = sa_sigioaction; -#else - act.sa_handler = sa_sigiohandler; -#endif +// TODO: Where to start/stop rig poll routine? - sigemptyset(&act.sa_mask); +void *rig_poll_routine(void *arg) +{ + rig_poll_routine_args *args = (rig_poll_routine_args *)arg; + RIG *rig = args->rig; + struct rig_state *rs = &rig->state; + int result; + int update_occurred; -#if defined(HAVE_SIGINFO_T) && defined(SA_SIGINFO) - act.sa_flags = SA_SIGINFO | SA_RESTART; -#else - act.sa_flags = SA_RESTART; -#endif + vfo_t vfo = RIG_VFO_NONE, vfo_prev = RIG_VFO_NONE; + freq_t freq_main = 0, freq_sub = 0, freq_main_prev = 0, freq_sub_prev = 0; + rmode_t mode_main = RIG_MODE_NONE, mode_sub = RIG_MODE_NONE, + mode_main_prev = RIG_MODE_NONE, mode_sub_prev = RIG_MODE_NONE; + pbwidth_t width_main = 0, width_sub = 0, width_main_prev = 0, width_sub_prev = 0; + split_t split, split_prev = -1; - status = sigaction(SIGIO, &act, &hamlib_trn_oldact); + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting rig poll routine thread\n", __FILE__, __LINE__); - if (status < 0) + // Rig cache time should be equal to rig poll interval (should be set automatically by rigctld at least) + rig_set_cache_timeout_ms(rig, HAMLIB_CACHE_ALL, rs->poll_interval); + + update_occurred = 0; + + while (rs->poll_routine_thread_run) { - rig_debug(RIG_DEBUG_ERR, - "%s: sigaction failed: %s\n", - __func__, - strerror(errno)); + if (rig->caps->get_vfo) + { + result = rig_get_vfo(rig, &vfo); + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_vfo error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + if (vfo != vfo_prev) + { + rig_fire_vfo_event(rig, vfo); + } + + if (vfo != vfo_prev) + { + rig_debug(RIG_DEBUG_CACHE, + "%s(%d) vfo=%s was %s\n", __FILE__, __LINE__, + rig_strvfo(vfo), rig_strvfo(vfo_prev)); + update_occurred = 1; + vfo_prev = vfo; + } + } + + if (rig->caps->get_freq) + { + result = rig_get_freq(rig, RIG_VFO_A, &freq_main); + + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqA error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + result = rig_get_freq(rig, RIG_VFO_B, &freq_sub); + + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqB error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + if (freq_main != freq_main_prev) + { + rig_fire_freq_event(rig, RIG_VFO_A, freq_main); + } + if (freq_sub != freq_sub_prev) + { + rig_fire_freq_event(rig, RIG_VFO_B, freq_sub); + } + + if (freq_main != freq_main_prev || freq_sub != freq_sub_prev) + { + rig_debug(RIG_DEBUG_CACHE, + "%s(%d) freq_main=%.0f was %.0f, freq_sub=%.0f was %.0f\n", __FILE__, __LINE__, + freq_main, freq_main_prev, freq_sub, freq_sub_prev); + update_occurred = 1; + freq_main_prev = freq_main; + freq_sub_prev = freq_sub; + } + } + + if (rig->caps->get_mode) + { + result = rig_get_mode(rig, RIG_VFO_A, &mode_main, &width_main); + + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + result = rig_get_mode(rig, RIG_VFO_B, &mode_sub, &width_sub); + + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeB error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + if (mode_main != mode_main_prev || width_main != width_main_prev) + { + rig_fire_mode_event(rig, RIG_VFO_A, mode_main, width_main); + } + if (mode_sub != mode_sub_prev || width_sub != width_sub_prev) + { + rig_fire_mode_event(rig, RIG_VFO_B, mode_sub, width_sub); + } + + if (mode_main != mode_main_prev || mode_sub != mode_sub_prev) + { + rig_debug(RIG_DEBUG_CACHE, "%s(%d) mode_main=%s was %s, mode_sub=%s was %s\n", + __FILE__, __LINE__, rig_strrmode(mode_main), rig_strrmode(mode_main_prev), + rig_strrmode(mode_sub), rig_strrmode(mode_sub_prev)); + update_occurred = 1; + mode_main_prev = mode_main; + mode_sub_prev = mode_sub; + } + + if (width_main != width_main_prev || width_sub != width_sub_prev) + { + rig_debug(RIG_DEBUG_CACHE, + "%s(%d) width_main=%ld was %ld, width_sub=%ld was %ld\n", __FILE__, __LINE__, + width_main, width_main_prev, width_sub, width_sub_prev); + update_occurred = 1; + width_main_prev = width_main; + width_sub_prev = width_sub; + } + } + + if (rig->caps->get_split_vfo) + { + vfo_t tx_vfo; + result = rig_get_split_vfo(rig, RIG_VFO_A, &split, &tx_vfo); + + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(result)); + } + + if (split != split_prev) + { + rig_debug(RIG_DEBUG_CACHE, "%s(%d) split=%d was %d\n", __FILE__, __LINE__, split, + split_prev); + update_occurred = 1; + split_prev = split; + } + } + + if (update_occurred) + { + network_publish_rig_poll_data(rig); + } + + hl_usleep(rs->poll_interval * 1000); } - status = fcntl(rig->state.rigport.fd, F_SETOWN, getpid()); + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopping rig poll routine thread\n", __FILE__, + __LINE__); - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s: fcntl SETOWN failed: %s\n", - __func__, - strerror(errno)); - } - -#if defined(HAVE_SIGINFO_T) && defined(O_ASYNC) -#ifdef F_SETSIG - status = fcntl(rig->state.rigport.fd, F_SETSIG, SIGIO); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s: fcntl SETSIG failed: %s\n", - __func__, - strerror(errno)); - } - -#endif - - status = fcntl(rig->state.rigport.fd, F_SETFL, O_ASYNC); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s: fcntl SETASYNC failed: %s\n", - __func__, - strerror(errno)); - } - -#else - return -RIG_ENIMPL; -#endif - - return RIG_OK; - -#else - return -RIG_ENIMPL; -#endif /* !HAVE_SIGACTION */ + return NULL; } - -/* - * remove_trn_rig - * not exported in Hamlib API. - * Assumes rig->caps->transceive == RIG_TRN_RIG - */ -int remove_trn_rig(RIG *rig) -{ -#ifdef HAVE_SIGACTION - int status; - - /* assert(rig->caps->transceive == RIG_TRN_RIG); */ - -#if defined(HAVE_SIGINFO_T) && defined(O_ASYNC) - status = fcntl(rig->state.rigport.fd, F_SETFL, 0); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s: fcntl SETASYNC failed: %s\n", - __func__, - strerror(errno)); - } - -#endif - - status = sigaction(SIGIO, &hamlib_trn_oldact, NULL); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s: sigaction failed: %s\n", - __func__, - strerror(errno)); - } - - return RIG_OK; -#else - return -RIG_ENIMPL; -#endif /* !HAVE_SIGACTION */ -} - - -#ifdef HAVE_SIGACTION - - -/* - * add_trn_poll_rig - * not exported in Hamlib API. - */ -static int add_trn_poll_rig(RIG *rig) -{ -#ifdef HAVE_SIGACTION - struct sigaction act; - int status; - - /* - * FIXME: multiple open will register several time SIGALRM hndlr - */ - memset(&act, 0, sizeof(act)); - -#ifdef HAVE_SIGINFO_T - act.sa_sigaction = sa_sigalrmaction; -#else - act.sa_handler = sa_sigalrmhandler; -#endif - act.sa_flags = SA_RESTART; - - sigemptyset(&act.sa_mask); - - status = sigaction(SIGALRM, &act, &hamlib_trn_poll_oldact); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s sigaction failed: %s\n", - __func__, - strerror(errno)); - } - - return RIG_OK; - -#else - return -RIG_ENIMPL; -#endif /* !HAVE_SIGINFO */ -} - - -/* - * remove_trn_poll_rig - * not exported in Hamlib API. - */ -static int remove_trn_poll_rig(RIG *rig) -{ -#ifdef HAVE_SIGINFO - int status; - - status = sigaction(SIGALRM, &hamlib_trn_poll_oldact, NULL); - - if (status < 0) - { - rig_debug(RIG_DEBUG_ERR, - "%s sigaction failed: %s\n", - __func__, - strerror(errno)); - } - - return RIG_OK; - -#else - return -RIG_ENIMPL; -#endif /* !HAVE_SIGINFO */ -} - - -/* - * This is used by sa_sigio, the SIGIO handler - * to find out which rig generated this event, - * and decode/process it. +/** + * \brief Start rig poll routine * - * assumes rig!=NULL + * Start rig poll routine + * + * \return RIG_OK or < 0 if error */ -static int search_rig_and_decode(RIG *rig, rig_ptr_t data) +int rig_poll_routine_start(RIG *rig) { - fd_set rfds; - struct timeval tv; - int retval; + struct rig_state *rs = &rig->state; + rig_poll_routine_priv_data *poll_routine_priv; ENTERFUNC; - /* - * so far, only file oriented ports have event reporting support - */ - if (rig->state.rigport.type.rig != RIG_PORT_SERIAL - || rig->state.rigport.fd == -1) + if (rs->poll_interval < 1) { - return -1; + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig poll routine disabled, poll interval set to zero\n", __FILE__, + __LINE__); + RETURNFUNC(RIG_OK); } - /* - * TODO: FIXME: We may end up calling decode_event right before or after the hold_decode lock is released - * by backend transaction routine. With the Icom backend this will end up waiting for the next CI-V frame - * to be read and this will interfere with reading of the next response to any command. - * => It is difficult to find a way to avoid this routine picking up regular responses. - */ - - /* - * Do not disturb, the backend is currently receiving data - */ - if (rig->state.hold_decode) + if (rs->poll_routine_priv_data != NULL) { - rig_debug(RIG_DEBUG_TRACE, "%s: hold decode, backend is receiving data\n", - __func__); - RETURNFUNC(-1); + rig_debug(RIG_DEBUG_ERR, "%s(%d): rig poll routine already running\n", __FILE__, + __LINE__); + RETURNFUNC(-RIG_EINVAL); } - /* FIXME: siginfo is not portable, however use it where available */ -#if 0&&defined(HAVE_SIGINFO_T) - siginfo_t *si = (siginfo_t *)data; - - if (rig->state.rigport.fd != si->si_fd) + rs->poll_routine_thread_run = 1; + rs->poll_routine_priv_data = calloc(1, sizeof(rig_poll_routine_priv_data)); + if (rs->poll_routine_priv_data == NULL) { - return -1; + RETURNFUNC(-RIG_ENOMEM); } -#else - FD_ZERO(&rfds); - FD_SET(rig->state.rigport.fd, &rfds); - /* Read status immediately. */ - tv.tv_sec = 0; - tv.tv_usec = 0; + poll_routine_priv = (rig_poll_routine_priv_data *) rs->poll_routine_priv_data; + poll_routine_priv->args.rig = rig; + int err = pthread_create(&poll_routine_priv->thread_id, NULL, + rig_poll_routine, &poll_routine_priv->args); - /* don't use FIONREAD to detect activity - * since it is less portable than select - * REM: EINTR possible with 0sec timeout? retval==0? - */ - retval = select(rig->state.rigport.fd + 1, &rfds, NULL, NULL, &tv); - - if (retval < 0) + if (err) { - rig_debug(RIG_DEBUG_ERR, - "%s: select() failed: %s\n", - __func__, - strerror(errno)); - RETURNFUNC(-1); + rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error: %s\n", __FILE__, __LINE__, + strerror(errno)); + RETURNFUNC(-RIG_EINTERNAL); } -#endif - - /* - * Do not disturb, the backend is currently receiving data - */ - if (rig->state.hold_decode) - { - rig_debug(RIG_DEBUG_TRACE, "%s: hold decode, backend is receiving data\n", - __func__); - RETURNFUNC(-1); - } - - if (rig->caps->decode_event) - { - rig->caps->decode_event(rig); - } - - RETURNFUNC(1); /* process each opened rig */ + RETURNFUNC(RIG_OK); } - -/* - * This is used by sa_sigio, the SIGALRM handler - * to poll each RIG in RIG_TRN_POLL mode. +/** + * \brief Stop rig poll routine * - * assumes rig!=NULL + * Stop rig poll routine + * + * \return RIG_OK or < 0 if error */ -static int search_rig_and_poll(RIG *rig, rig_ptr_t data) +int rig_poll_routine_stop(RIG *rig) { struct rig_state *rs = &rig->state; - int retval; + rig_poll_routine_priv_data *poll_routine_priv; - if (rig->state.transceive != RIG_TRN_POLL) + ENTERFUNC; + + if (rs->poll_interval < 1) { - return -1; + RETURNFUNC(RIG_OK); } - /* - * Do not disturb, the backend is currently receiving data - */ - if (rig->state.hold_decode) + if (rs->poll_routine_priv_data == NULL) { - return -1; + RETURNFUNC(-RIG_EINVAL); } - rig->state.hold_decode = 2; + rs->poll_routine_thread_run = 0; - if (rig->caps->get_vfo && rig->callbacks.vfo_event) + poll_routine_priv = (rig_poll_routine_priv_data *) rs->poll_routine_priv_data; + + if (poll_routine_priv->thread_id != 0) { - vfo_t vfo = RIG_VFO_CURR; + int err = pthread_join(poll_routine_priv->thread_id, NULL); - retval = rig->caps->get_vfo(rig, &vfo); - - if (retval == RIG_OK) + if (err) { - if (vfo != rs->current_vfo) - { - rig->callbacks.vfo_event(rig, vfo, rig->callbacks.vfo_arg); - } - - rs->current_vfo = vfo; + rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__, + strerror(errno)); + // just ignore it } + + poll_routine_priv->thread_id = 0; } - if (rig->caps->get_freq && rig->callbacks.freq_event) - { - freq_t freq; + free(rs->poll_routine_priv_data); + rs->poll_routine_priv_data = NULL; - retval = rig->caps->get_freq(rig, RIG_VFO_CURR, &freq); - - if (retval == RIG_OK) - { - if (freq != rs->current_freq) - { - rig->callbacks.freq_event(rig, - RIG_VFO_CURR, - freq, - rig->callbacks.freq_arg); - } - - rs->current_freq = freq; - } - } - - if (rig->caps->get_mode && rig->callbacks.mode_event) - { - rmode_t rmode; - pbwidth_t width; - - retval = rig->caps->get_mode(rig, RIG_VFO_CURR, &rmode, &width); - - if (retval == RIG_OK) - { - if (rmode != rs->current_mode || width != rs->current_width) - { - rig->callbacks.mode_event(rig, - RIG_VFO_CURR, - rmode, - width, - rig->callbacks.mode_arg); - } - - rs->current_mode = rmode; - rs->current_width = width; - } - } - - rig->state.hold_decode = 0; - - return 1; /* process each opened rig */ -} - - -/* - * This is the SIGIO handler - * - * lookup in the list of open rigs, - * check the rig is not holding SIGIO, - * then call rig->caps->decode_event() (this is done by search_rig) - */ -#ifdef HAVE_SIGINFO_T -static void sa_sigioaction(int signum, siginfo_t *si, rig_ptr_t data) -{ - rig_debug(RIG_DEBUG_VERBOSE, "%s: activity detected\n", __func__); - - foreach_opened_rig(search_rig_and_decode, si); -} - -#else - -static void sa_sigiohandler(int signum) -{ - rig_debug(RIG_DEBUG_VERBOSE, "%s: activity detected\n", __func__); - - foreach_opened_rig(search_rig_and_decode, NULL); + RETURNFUNC(RIG_OK); } #endif - -/* - * This is the SIGALRM handler - * - * lookup in the list of open rigs, - * check the rig is not holding SIGALRM, - * then call get_freq and check for changes (this is done by search_rig) - */ -#ifdef HAVE_SIGINFO_T -static void sa_sigalrmaction(int signum, siginfo_t *si, rig_ptr_t data) -{ - rig_debug(RIG_DEBUG_TRACE, "%s entered\n", __func__); - - foreach_opened_rig(search_rig_and_poll, si); -} - -#else - -static void sa_sigalrmhandler(int signum) -{ - rig_debug(RIG_DEBUG_TRACE, "%s entered\n", __func__); - - foreach_opened_rig(search_rig_and_poll, NULL); -} - -#endif /* !HAVE_SIGINFO_T */ - -#endif /* HAVE_SIGINFO */ - -#endif /* !DOC_HIDDEN */ - - /** * \brief set the callback for freq events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for freq events, to be called when in transceive mode. + * Install a callback for freq events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_freq_callback(RIG *rig, freq_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.freq_event = cb; rig->callbacks.freq_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -566,27 +363,25 @@ int HAMLIB_API rig_set_freq_callback(RIG *rig, freq_cb_t cb, rig_ptr_t arg) * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for mode events, to be called when in transceive mode. + * Install a callback for mode events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_mode_callback(RIG *rig, mode_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.mode_event = cb; rig->callbacks.mode_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -596,27 +391,25 @@ int HAMLIB_API rig_set_mode_callback(RIG *rig, mode_cb_t cb, rig_ptr_t arg) * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for vfo events, to be called when in transceive mode. + * Install a callback for vfo events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_vfo_callback(RIG *rig, vfo_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.vfo_event = cb; rig->callbacks.vfo_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -626,27 +419,25 @@ int HAMLIB_API rig_set_vfo_callback(RIG *rig, vfo_cb_t cb, rig_ptr_t arg) * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for ptt events, to be called when in transceive mode. + * Install a callback for ptt events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_ptt_callback(RIG *rig, ptt_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.ptt_event = cb; rig->callbacks.ptt_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -656,27 +447,25 @@ int HAMLIB_API rig_set_ptt_callback(RIG *rig, ptt_cb_t cb, rig_ptr_t arg) * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for dcd events, to be called when in transceive mode. + * Install a callback for dcd events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_dcd_callback(RIG *rig, dcd_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.dcd_event = cb; rig->callbacks.dcd_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -693,22 +482,20 @@ int HAMLIB_API rig_set_dcd_callback(RIG *rig, dcd_cb_t cb, rig_ptr_t arg) * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_pltune_callback(RIG *rig, pltune_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.pltune = cb; rig->callbacks.pltune_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -718,28 +505,26 @@ int HAMLIB_API rig_set_pltune_callback(RIG *rig, pltune_cb_t cb, rig_ptr_t arg) * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * - * Install a callback for spectrum line reception events, to be called when in transceive mode. + * Install a callback for spectrum line reception events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). - * - * \sa rig_set_trn() */ int HAMLIB_API rig_set_spectrum_callback(RIG *rig, spectrum_cb_t cb, rig_ptr_t arg) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; if (CHECK_RIG_ARG(rig)) { - return -RIG_EINVAL; + RETURNFUNC(-RIG_EINVAL); } rig->callbacks.spectrum_event = cb; rig->callbacks.spectrum_arg = arg; - return RIG_OK; + RETURNFUNC(RIG_OK); } @@ -755,138 +540,14 @@ int HAMLIB_API rig_set_spectrum_callback(RIG *rig, spectrum_cb_t cb, * set appropriately). * * \sa rig_get_trn() + * + * \deprecated This functionality has never worked correctly and it is now disabled in favor of new async data handling capabilities. + * The command will always return RIG_EDEPRECATED until the command will be removed eventually. */ int HAMLIB_API rig_set_trn(RIG *rig, int trn) { - const struct rig_caps *caps; - int retcode = RIG_OK; -#ifdef HAVE_SETITIMER - struct itimerval value; -#endif - - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); - - if (CHECK_RIG_ARG(rig)) - { - return -RIG_EINVAL; - } - - caps = rig->caps; - - /* detect whether tranceive is active already */ - if (trn != RIG_TRN_OFF && rig->state.transceive != RIG_TRN_OFF) - { - if (trn == rig->state.transceive) - { - return RIG_OK; - } - else - { - /* when going POLL<->RIG, transition to OFF */ - retcode = rig_set_trn(rig, RIG_TRN_OFF); - - if (retcode != RIG_OK) - { - return retcode; - } - } - } - - switch (trn) - { - case RIG_TRN_RIG: - if (caps->transceive != RIG_TRN_RIG) - { - return -RIG_ENAVAIL; - } - - retcode = add_trn_rig(rig); - - /* some protocols (e.g. CI-V's) offer no way - * to turn on/off the transceive mode */ - if (retcode == RIG_OK && caps->set_trn) - { - retcode = caps->set_trn(rig, RIG_TRN_RIG); - } - - break; - - case RIG_TRN_POLL: -#ifdef HAVE_SETITIMER - - add_trn_poll_rig(rig); - - /* install handler here */ - value.it_value.tv_sec = 0; - value.it_value.tv_usec = rig->state.poll_interval * 1000; - value.it_interval.tv_sec = 0; - value.it_interval.tv_usec = rig->state.poll_interval * 1000; - retcode = setitimer(ITIMER_REAL, &value, NULL); - - if (retcode == -1) - { - rig_debug(RIG_DEBUG_ERR, - "%s: setitimer: %s\n", - __func__, - strerror(errno)); - remove_trn_poll_rig(rig); - return -RIG_EINTERNAL; - } - -#else - return -RIG_ENAVAIL; -#endif - break; - - case RIG_TRN_OFF: - if (rig->state.transceive == RIG_TRN_POLL) - { -#ifdef HAVE_SETITIMER - - // don't care about the return code here - remove_trn_poll_rig(rig); - - value.it_value.tv_sec = 0; - value.it_value.tv_usec = 0; - value.it_interval.tv_sec = 0; - value.it_interval.tv_usec = 0; - - retcode = setitimer(ITIMER_REAL, &value, NULL); - - if (retcode == -1) - { - rig_debug(RIG_DEBUG_ERR, "%s: setitimer: %s\n", - __func__, - strerror(errno)); - return -RIG_EINTERNAL; - } - -#else - return -RIG_ENAVAIL; -#endif - } - else if (rig->state.transceive == RIG_TRN_RIG) - { - retcode = remove_trn_rig(rig); - - if (caps->set_trn && caps->transceive == RIG_TRN_RIG) - { - retcode = caps->set_trn(rig, RIG_TRN_OFF); - } - } - - break; - - default: - return -RIG_EINVAL; - } - - if (retcode == RIG_OK) - { - rig->state.transceive = trn; - } - - return retcode; + ENTERFUNC; + RETURNFUNC(-RIG_EDEPRECATED); } @@ -903,23 +564,209 @@ int HAMLIB_API rig_set_trn(RIG *rig, int trn) * set appropriately). * * \sa rig_set_trn() + * + * \deprecated This functionality has never worked correctly and it is now disabled in favor of new async data handling capabilities. + * The command will always return RIG_EDEPRECATED until the command will be removed eventually. */ int HAMLIB_API rig_get_trn(RIG *rig, int *trn) { - rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + ENTERFUNC; + RETURNFUNC(-RIG_EDEPRECATED); +} - if (CHECK_RIG_ARG(rig) || !trn) +int rig_fire_freq_event(RIG *rig, vfo_t vfo, freq_t freq) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: freq changed to %"PRIll"Hz on %s\n", + (int64_t)freq, rig_strvfo(vfo)); + + rig_set_cache_freq(rig, vfo, freq); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.freq_event) { - return -RIG_EINVAL; + rig->callbacks.freq_event(rig, vfo, freq, rig->callbacks.freq_arg); } - if (rig->caps->get_trn != NULL) + RETURNFUNC(0); +} + + +int rig_fire_mode_event(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: mode changed to %s, width %liHz on %s\n", + rig_strrmode(mode), width, rig_strvfo(vfo)); + + rig_set_cache_mode(rig, vfo, mode, width); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.mode_event) { - return rig->caps->get_trn(rig, trn); + rig->callbacks.mode_event(rig, vfo, mode, width, rig->callbacks.mode_arg); } - *trn = rig->state.transceive; - return RIG_OK; + RETURNFUNC(0); +} + + +int rig_fire_vfo_event(RIG *rig, vfo_t vfo) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: vfo changed to %s\n", rig_strvfo(vfo)); + + rig->state.cache.vfo = vfo; + elapsed_ms(&rig->state.cache.time_vfo, HAMLIB_ELAPSED_SET); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.vfo_event) + { + rig->callbacks.vfo_event(rig, vfo, rig->callbacks.vfo_arg); + } + + RETURNFUNC(0); +} + + +int rig_fire_ptt_event(RIG *rig, vfo_t vfo, ptt_t ptt) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: PTT changed to %i on %s\n", ptt, rig_strvfo(vfo)); + + rig->state.cache.ptt = ptt; + elapsed_ms(&rig->state.cache.time_ptt, HAMLIB_ELAPSED_SET); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.ptt_event) + { + rig->callbacks.ptt_event(rig, vfo, ptt, rig->callbacks.ptt_arg); + } + + RETURNFUNC(0); +} + + +int rig_fire_dcd_event(RIG *rig, vfo_t vfo, dcd_t dcd) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: DCD changed to %i on %s\n", dcd, rig_strvfo(vfo)); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.dcd_event) + { + rig->callbacks.dcd_event(rig, vfo, dcd, rig->callbacks.dcd_arg); + } + + RETURNFUNC(0); +} + + +int rig_fire_pltune_event(RIG *rig, vfo_t vfo, freq_t *freq, rmode_t *mode, pbwidth_t *width) +{ + ENTERFUNC; + + rig_debug(RIG_DEBUG_ERR, "Event: Pipelined tuning event, vfo=%s\n", rig_strvfo(vfo)); + + network_publish_rig_transceive_data(rig); + + if (rig->callbacks.pltune) + { + rig->callbacks.pltune(rig, vfo, freq, mode, width, rig->callbacks.pltune_arg); + } + + RETURNFUNC(RIG_OK); +} + + +static int print_spectrum_line(char *str, size_t length, + struct rig_spectrum_line *line) +{ + int data_level_max = line->data_level_max / 2; + int aggregate_count = line->spectrum_data_length / 120; + int aggregate_value = 0; + int i, c; + int charlen = strlen("█"); + + str[0] = '\0'; + + for (i = 0, c = 0; i < line->spectrum_data_length; i++) + { + int current = line->spectrum_data[i]; + aggregate_value = current > aggregate_value ? current : aggregate_value; + + if (i > 0 && i % aggregate_count == 0) + { + if (c + charlen >= length) + { + break; + } + + int level = aggregate_value * 10 / data_level_max; + + if (level >= 8) + { + strcpy(str + c, "█"); + c += charlen; + } + else if (level >= 6) + { + strcpy(str + c, "▓"); + c += charlen; + } + else if (level >= 4) + { + strcpy(str + c, "▒"); + c += charlen; + } + else if (level >= 2) + { + strcpy(str + c, "░"); + c += charlen; + } + else if (level >= 0) + { + strcpy(str + c, " "); + c += 1; + } + + aggregate_value = 0; + } + } + + return c; +} + + +int rig_fire_spectrum_event(RIG *rig, struct rig_spectrum_line *line) +{ + ENTERFUNC; + + if (rig_need_debug(RIG_DEBUG_ERR)) + { + char spectrum_debug[line->spectrum_data_length * 4]; + print_spectrum_line(spectrum_debug, sizeof(spectrum_debug), line); + rig_debug(RIG_DEBUG_ERR, "%s: ASCII Spectrum Scope: %s\n", __func__, + spectrum_debug); + } + + network_publish_rig_spectrum_data(rig, line); + + if (rig->callbacks.spectrum_event) + { + rig->callbacks.spectrum_event(rig, line, rig->callbacks.spectrum_arg); + } + + RETURNFUNC(RIG_OK); } /** @} */ diff --git a/src/event.h b/src/event.h index e64f749ee..3563fb35d 100644 --- a/src/event.h +++ b/src/event.h @@ -24,9 +24,16 @@ #include +int rig_poll_routine_start(RIG *rig); +int rig_poll_routine_stop(RIG *rig); -int add_trn_rig(RIG *rig); -int remove_trn_rig(RIG *rig); +int rig_fire_freq_event(RIG *rig, vfo_t vfo, freq_t freq); +int rig_fire_mode_event(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); +int rig_fire_vfo_event(RIG *rig, vfo_t vfo); +int rig_fire_ptt_event(RIG *rig, vfo_t vfo, ptt_t ptt); +int rig_fire_dcd_event(RIG *rig, vfo_t vfo, dcd_t dcd); +int rig_fire_pltune_event(RIG *rig, vfo_t vfo, freq_t *freq, rmode_t *mode, pbwidth_t *width); +int rig_fire_spectrum_event(RIG *rig, struct rig_spectrum_line *line); #endif /* _EVENT_H */ diff --git a/src/iofunc.c b/src/iofunc.c index 6483d2e18..c6f14e137 100644 --- a/src/iofunc.c +++ b/src/iofunc.c @@ -65,6 +65,16 @@ static void close_sync_data_pipe(hamlib_port_t *p) close(p->fd_sync_write); p->fd_sync_write = -1; } + + + if (p->fd_sync_error_read != -1) { + close(p->fd_sync_error_read); + p->fd_sync_error_read = -1; + } + if (p->fd_sync_error_write != -1) { + close(p->fd_sync_error_write); + p->fd_sync_error_write = -1; + } } /** @@ -83,6 +93,8 @@ int HAMLIB_API port_open(hamlib_port_t *p) p->fd = -1; p->fd_sync_write = -1; p->fd_sync_read = -1; + p->fd_sync_error_write = -1; + p->fd_sync_error_read = -1; if (p->async) { @@ -98,6 +110,18 @@ int HAMLIB_API port_open(hamlib_port_t *p) p->fd_sync_read = sync_pipe_fds[0]; p->fd_sync_write = sync_pipe_fds[1]; + status = pipe2(sync_pipe_fds, O_NONBLOCK); + if (status != 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: synchronous data error code pipe open status=%d, err=%s\n", __func__, + status, strerror(errno)); + close_sync_data_pipe(p); + RETURNFUNC(-RIG_EINTERNAL); + } + + p->fd_sync_error_read = sync_pipe_fds[0]; + p->fd_sync_error_write = sync_pipe_fds[1]; + rig_debug(RIG_DEBUG_VERBOSE, "%s: created synchronous data pipe\n", __func__); } @@ -440,7 +464,7 @@ static ssize_t port_read_generic(hamlib_port_t *p, void *buf, size_t count, int } //! @cond Doxygen_Suppress -#define port_write_generic(p,b,c,d) write((d) ? (p)->fd : (p)->fd_sync_write,(b),(c)) +#define port_write(p,b,c) write((p)->fd,(b),(c)) #define port_select(p,n,r,w,e,t) select((n),(r),(w),(e),(t)) //! @endcond @@ -475,7 +499,7 @@ static ssize_t port_read_generic(hamlib_port_t *p, void *buf, size_t count, int * it could work very well also with any file handle, like a socket. */ -static int write_block_generic(hamlib_port_t *p, const unsigned char *txbuffer, size_t count, int direct) +int HAMLIB_API write_block(hamlib_port_t *p, const unsigned char *txbuffer, size_t count) { int ret; @@ -513,7 +537,7 @@ static int write_block_generic(hamlib_port_t *p, const unsigned char *txbuffer, for (i = 0; i < count; i++) { - ret = port_write_generic(p, txbuffer + i, 1, direct); + ret = port_write(p, txbuffer + i, 1); if (ret != 1) { @@ -532,7 +556,7 @@ static int write_block_generic(hamlib_port_t *p, const unsigned char *txbuffer, } else { - ret = port_write_generic(p, txbuffer, count, direct); + ret = port_write(p, txbuffer, count); if (ret != count) { @@ -573,44 +597,16 @@ static int write_block_generic(hamlib_port_t *p, const unsigned char *txbuffer, return RIG_OK; } - -/** - * \brief Write a block of characters to an fd. - * \param p rig port descriptor - * \param txbuffer command sequence to be sent - * \param count number of bytes to send - * \return 0 = OK, <0 = NOK - * - * Write a block of count characters to port file descriptor, - * with a pause between each character if write_delay is > 0 - * - * The write_delay is for Yaesu type rigs..require 5 character - * sequence to be sent with 50-200msec between each char. - * - * Also, post_write_delay is for some Yaesu rigs (eg: FT747) that - * get confused with sequential fast writes between cmd sequences. - * - * input: - * - * fd - file descriptor to write to - * txbuffer - pointer to a command sequence array - * count - count of byte to send from the txbuffer - * write_delay - write delay in ms between 2 chars - * post_write_delay - minimum delay between two writes - * post_write_date - timeval of last write - * - * Actually, this function has nothing specific to serial comm, - * it could work very well also with any file handle, like a socket. - */ - -int HAMLIB_API write_block(hamlib_port_t *p, const unsigned char *txbuffer, size_t count) -{ - return write_block_generic(p, txbuffer, count, 1); -} - int HAMLIB_API write_block_sync(hamlib_port_t *p, const unsigned char *txbuffer, size_t count) { - return write_block_generic(p, txbuffer, count, 0); + // TODO: Macro for write() + return (int) write((p)->fd_sync_write, txbuffer, count); +} + +int HAMLIB_API write_block_sync_error(hamlib_port_t *p, const unsigned char *txbuffer, size_t count) +{ + // TODO: Macro for write() + return (int) write((p)->fd_sync_error_write, txbuffer, count); } static int read_block_generic(hamlib_port_t *p, unsigned char *rxbuffer, size_t count, int direct) @@ -757,6 +753,43 @@ int HAMLIB_API read_block_direct(hamlib_port_t *p, unsigned char *rxbuffer, size return read_block_generic(p, rxbuffer, count, 1); } +static int flush_and_read_last_byte(int fd) +{ + fd_set rfds, efds; + ssize_t bytes_read; + struct timeval tv_timeout; + int retval; + char data; + + do { + tv_timeout.tv_sec = 0; + tv_timeout.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + efds = rfds; + + retval = port_select(p, fd + 1, &rfds, NULL, &efds, &tv_timeout); + if (retval < 0) + { + return -RIG_ETIMEOUT; + } + if (retval == 0) + { + return -RIG_EIO; + } + + if (FD_ISSET(fd, &efds)) + { + return -RIG_EIO; + } + + bytes_read = read(fd, &data, 1); + } while (bytes_read > 0); + + return data; +} + static int read_string_generic(hamlib_port_t *p, unsigned char *rxbuffer, size_t rxmax, @@ -766,10 +799,10 @@ static int read_string_generic(hamlib_port_t *p, int direct) { fd_set rfds, efds; - int fd; + int fd, errorfd, maxfd; struct timeval tv, tv_timeout, start_time, end_time, elapsed_time; int total_count = 0; - int i=0; + int i = 0; rig_debug(RIG_DEBUG_TRACE, "%s called, rxmax=%d\n", __func__, (int)rxmax); @@ -787,6 +820,8 @@ static int read_string_generic(hamlib_port_t *p, } fd = direct ? p->fd : p->fd_sync_read; + errorfd = direct ? -1 : p->fd_sync_error_read; + maxfd = (fd > errorfd) ? fd : errorfd; /* * Wait up to timeout ms. @@ -808,9 +843,13 @@ static int read_string_generic(hamlib_port_t *p, FD_ZERO(&rfds); FD_SET(fd, &rfds); + if (!direct) + { + FD_SET(errorfd, &rfds); + } efds = rfds; - retval = port_select(p, fd + 1, &rfds, NULL, &efds, &tv); + retval = port_select(p, maxfd + 1, &rfds, NULL, &efds, &tv); if (retval == 0) { @@ -820,7 +859,10 @@ static int read_string_generic(hamlib_port_t *p, gettimeofday(&end_time, NULL); timersub(&end_time, &start_time, &elapsed_time); - dump_hex((unsigned char *) rxbuffer, total_count); + if (direct) + { + dump_hex((unsigned char *) rxbuffer, total_count); + } if (!flush_flag) { rig_debug(RIG_DEBUG_WARN, "%s(): Timed out %d.%03d seconds after %d chars\n", @@ -838,7 +880,10 @@ static int read_string_generic(hamlib_port_t *p, if (retval < 0) { - dump_hex(rxbuffer, total_count); + if (direct) + { + dump_hex(rxbuffer, total_count); + } rig_debug(RIG_DEBUG_ERR, "%s(): select() error after %d chars: %s\n", __func__, @@ -857,9 +902,25 @@ static int read_string_generic(hamlib_port_t *p, return -RIG_EIO; } + if (!direct) + { + if (FD_ISSET(errorfd, &efds)) + { + rig_debug(RIG_DEBUG_ERR, + "%s(): fd error from sync error pipe after %d chars\n", + __func__, + total_count); + return -RIG_EIO; + } + + if (FD_ISSET(errorfd, &rfds)) + { + return flush_and_read_last_byte(errorfd); + } + } /* - * read 1 character from the rig, (check if in stop set) + * read 1 character from the rig, (check if in stop set) * The file descriptor must have been set up non blocking. */ do @@ -875,7 +936,10 @@ static int read_string_generic(hamlib_port_t *p, /* if we get 0 bytes or an error something is wrong */ if (rd_count <= 0) { - dump_hex((unsigned char *) rxbuffer, total_count); + if (direct) + { + dump_hex((unsigned char *) rxbuffer, total_count); + } rig_debug(RIG_DEBUG_ERR, "%s(): read() failed - %s\n", __func__, @@ -901,10 +965,13 @@ static int read_string_generic(hamlib_port_t *p, */ rxbuffer[total_count] = '\000'; - rig_debug(RIG_DEBUG_TRACE, - "%s(): RX %d characters\n", - __func__, - total_count); + if (direct) + { + rig_debug(RIG_DEBUG_TRACE, + "%s(): RX %d characters\n", + __func__, + total_count); + } dump_hex((unsigned char *) rxbuffer, total_count); diff --git a/src/iofunc.h b/src/iofunc.h index 5def45bf4..1be0fccee 100644 --- a/src/iofunc.h +++ b/src/iofunc.h @@ -45,6 +45,10 @@ extern HAMLIB_EXPORT(int) write_block_sync(hamlib_port_t *p, const unsigned char *txbuffer, size_t count); +extern HAMLIB_EXPORT(int) write_block_sync_error(hamlib_port_t *p, + const unsigned char *txbuffer, + size_t count); + extern HAMLIB_EXPORT(int) read_string(hamlib_port_t *p, unsigned char *rxbuffer, size_t rxmax, diff --git a/src/misc.c b/src/misc.c index 13305050b..c3447dc26 100644 --- a/src/misc.c +++ b/src/misc.c @@ -288,6 +288,33 @@ unsigned long long HAMLIB_API from_bcd_be(const unsigned char bcd_data[], return f; } +size_t HAMLIB_API to_hex(size_t source_length, const unsigned char *source_data, size_t dest_length, char *dest_data) +{ + size_t i; + size_t length = source_length; + const unsigned char *source = source_data; + char *dest = dest_data; + + if (source_length == 0 || dest_length == 0) + { + return 0; + } + + if (source_length * 2 > dest_length) + { + length = dest_length / 2 - 1; + } + + for (i = 0; i < length; i++) + { + sprintf(dest, "%02X", source[0]); + source++; + dest += 2; + } + + return length; +} + /** * \brief Convert duration of one morse code dot (element) to milliseconds at the given speed. * \param wpm morse code speed in words per minute @@ -2288,6 +2315,15 @@ void *HAMLIB_API rig_get_function_ptr(rig_model_t rig_model, case RIG_FUNCTION_SET_VFO_OPT: return caps->set_vfo_opt; + case RIG_FUNCTION_READ_FRAME_DIRECT: + return caps->read_frame_direct; + + case RIG_FUNCTION_IS_ASYNC_FRAME: + return caps->is_async_frame; + + case RIG_FUNCTION_PROCESS_ASYNC_FRAME: + return caps->process_async_frame; + default: rig_debug(RIG_DEBUG_ERR, "Unknown function?? function=%d\n", rig_function); } diff --git a/src/misc.h b/src/misc.h index 74b76e5f9..24cfd1cd0 100644 --- a/src/misc.h +++ b/src/misc.h @@ -31,8 +31,8 @@ * thus not ensure mutual exclusion. * Fix it when making Hamlib reentrant! --SF */ -#define Hold_Decode(rig) {(rig)->state.hold_decode = 1;} -#define Unhold_Decode(rig) {(rig)->state.hold_decode = 0;} +#define set_transaction_active(rig) {(rig)->state.transaction_active = 1;} +#define set_transaction_inactive(rig) {(rig)->state.transaction_active = 0;} __BEGIN_DECLS @@ -72,6 +72,11 @@ extern HAMLIB_EXPORT(unsigned long long) from_bcd_be(const unsigned char bcd_data[], unsigned bcd_len); +extern HAMLIB_EXPORT(size_t) to_hex(size_t source_length, + const unsigned char *source_data, + size_t dest_length, + char *dest_data); + extern HAMLIB_EXPORT(double) morse_code_dot_to_millis(int wpm); extern HAMLIB_EXPORT(int) dot10ths_to_millis(int dot10ths, int wpm); extern HAMLIB_EXPORT(int) millis_to_dot10ths(int millis, int wpm); diff --git a/src/network.c b/src/network.c index 2e26e8851..9b5c4a5cc 100644 --- a/src/network.c +++ b/src/network.c @@ -78,6 +78,7 @@ #include #include "network.h" #include "misc.h" +#include "snapshot_data.h" #ifdef __MINGW32__ @@ -88,6 +89,36 @@ static int wsstarted; #define NET_BUFFER_SIZE 8192 //! @endcond +#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL 0x01 +#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE 0x02 +#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM 0x03 + +#pragma pack(push,1) +typedef struct multicast_publisher_data_packet_s +{ + uint8_t type; + uint8_t padding; + uint16_t data_length; +} __attribute__((packed)) multicast_publisher_data_packet; +#pragma pack(pop) + +typedef struct multicast_publisher_args_s +{ + RIG *rig; + int socket_fd; + const char *multicast_addr; + int multicast_port; + + int data_write_fd; + int data_read_fd; +} multicast_publisher_args; + +typedef struct multicast_publisher_priv_data_s +{ + pthread_t thread_id; + multicast_publisher_args args; +} multicast_publisher_priv_data; + static void handle_error(enum rig_debug_level_e lvl, const char *msg) { int e; @@ -405,148 +436,328 @@ int network_close(hamlib_port_t *rp) } //! @endcond -volatile int multicast_server_run = 1; -pthread_t multicast_server_threadId; extern void sync_callback(int lock); -struct multicast_server_args_s -{ - RIG *rig; -} multicast_server_args; - +#ifdef HAVE_PTHREAD //! @cond Doxygen_Suppress -// our multicast server loop -void *multicast_server(void *arg) + +static int multicast_publisher_write_data(int fd, size_t length, const unsigned char *data) { - struct multicast_server_args_s *args = (struct multicast_server_args_s *)arg; - RIG *rig = args->rig; - rig_debug(RIG_DEBUG_TRACE, "%s(%d): Starting multicast server\n", __FILE__, - __LINE__); - // we can and should use a really small cache time while we are polling - // rig_set_cache_timeout_ms(rig, HAMLIB_CACHE_ALL, 100); + ssize_t result; - // this is really verbose since it runs very quickly - // so we only spit out WARN unless CACHE has been selected - //if (!rig_need_debug(RIG_DEBUG_CACHE)) { rig_set_debug(RIG_DEBUG_WARN); } - - - freq_t freqMain = 0, freqSub = 0, freqMainLast = 0, freqSubLast = 0; - rmode_t modeMain = RIG_MODE_NONE, modeSub = RIG_MODE_NONE, - modeMainLast = RIG_MODE_NONE, modeSubLast = RIG_MODE_NONE; - pbwidth_t widthMain = 0, widthSub = 0, widthMainLast = 0, widthSubLast = 0; - split_t split, splitLast = -1; - - do + result = write(fd, data, length); + if (result < 0) { - int retval; - int updateOccurred; - - updateOccurred = 0; - - if (rig->caps->get_freq) - { - retval = rig_get_freq(rig, RIG_VFO_A, &freqMain); - - if (retval != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqA error %s\n", __FILE__, __LINE__, rigerror(retval)); } - - retval = rig_get_freq(rig, RIG_VFO_B, &freqSub); - - if (retval != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqB error %s\n", __FILE__, __LINE__, rigerror(retval)); } - - if (freqMain != freqMainLast || freqSub != freqSubLast) - { - rig_debug(RIG_DEBUG_WARN, - "%s(%d) freqMain=%.0f was %.0f, freqSub=%.0f was %.0f\n", __FILE__, __LINE__, - freqMain, freqMainLast, freqSub, freqSubLast); - updateOccurred = 1; - freqMainLast = freqMain; - freqSubLast = freqSub; - } - } - - if (rig->caps->get_mode) - { - retval = rig_get_mode(rig, RIG_VFO_A, &modeMain, &widthMain); - - if (retval != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(retval)); } - - retval = rig_get_mode(rig, RIG_VFO_B, &modeSub, &widthSub); - - if (retval != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeB error %s\n", __FILE__, __LINE__, rigerror(retval)); } - - if (modeMain != modeMainLast || modeSub != modeSubLast) - { - rig_debug(RIG_DEBUG_TRACE, "%s(%d) modeMain=%s was %s, modeSub=%s was %s\n", - __FILE__, __LINE__, rig_strrmode(modeMain), rig_strrmode(modeMainLast), - rig_strrmode(modeSub), rig_strrmode(modeSubLast)); - updateOccurred = 1; - modeMainLast = modeMain; - modeSubLast = modeSub; - } - - if (widthMain != widthMainLast || widthSub != widthSubLast) - { - rig_debug(RIG_DEBUG_WARN, - "%s(%d) widthMain=%ld was %ld, widthSub=%ld was %ld\n", __FILE__, __LINE__, - widthMain, widthMainLast, widthSub, widthSubLast); - updateOccurred = 1; - widthMainLast = widthMain; - widthSubLast = widthSub; - } - } - - if (rig->caps->get_split_vfo) - { - vfo_t tx_vfo; - retval = rig_get_split_vfo(rig, RIG_VFO_A, &split, &tx_vfo); - - if (retval != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(retval)); } - - if (split != splitLast) - { - rig_debug(RIG_DEBUG_WARN, "%s(%d) split=%d was %d\n", __FILE__, __LINE__, split, - splitLast); - updateOccurred = 1; - splitLast = split; - } - } - - if (updateOccurred) - { - rig_debug(RIG_DEBUG_WARN, "%s(%d): update occurred...time to send multicast\n", - __FILE__, __LINE__); - } - - hl_usleep(100 * 1000); + rig_debug(RIG_DEBUG_ERR, "%s: error writing to multicast publisher data pipe, status=%ld, err=%s\n", __func__, + result, strerror(errno)); + RETURNFUNC(-RIG_EIO); } - while (multicast_server_run); - rig_debug(RIG_DEBUG_TRACE, "%s(%d): Stopping multicast server\n", __FILE__, + if (result != length) + { + rig_debug(RIG_DEBUG_ERR, "%s: could not write to multicast publisher data pipe, expected %ld bytes, wrote %ld bytes\n", + __func__, length, result); + RETURNFUNC(-RIG_EIO); + } + + RETURNFUNC(RIG_OK); +} + +static int multicast_publisher_read_data(int fd, size_t length, unsigned char *data) +{ + fd_set rfds, efds; + struct timeval timeout; + ssize_t result; + int retval; + + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + efds = rfds; + + retval = select(fd + 1, &rfds, NULL, &efds, &timeout); + if (retval == 0) + { + RETURNFUNC(-RIG_ETIMEOUT); + } + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, + "%s(): select() failed when reading multicast publisher data: %s\n", + __func__, + strerror(errno)); + + return -RIG_EIO; + } + + if (FD_ISSET(fd, &efds)) + { + rig_debug(RIG_DEBUG_ERR, "%s(): fd error when reading multicast publisher data\n", __func__); + return -RIG_EIO; + } + + result = read(fd, data, length); + if (result < 0) + { + if (errno == EAGAIN) + { + RETURNFUNC(-RIG_ETIMEOUT); + } + + rig_debug(RIG_DEBUG_ERR, "%s: error reading multicast publisher data: %s\n", __func__, strerror(errno)); + RETURNFUNC(-RIG_EIO); + } + + if (result != length) + { + rig_debug(RIG_DEBUG_ERR, "%s: could not read from multicast publisher data pipe, expected %ld bytes, read %ld bytes\n", + __func__, length, result); + RETURNFUNC(-RIG_EIO); + } + + RETURNFUNC(RIG_OK); +} + +static int multicast_publisher_write_packet_header(RIG *rig, multicast_publisher_data_packet *packet) +{ + struct rig_state *rs = &rig->state; + multicast_publisher_priv_data *mcast_publisher_priv; + ssize_t result; + + if (rs->multicast_publisher_priv_data == NULL) + { + // Silently ignore if multicast publisher is not enabled + RETURNFUNC(RIG_OK); + } + + mcast_publisher_priv = (multicast_publisher_priv_data *) rs->multicast_publisher_priv_data; + + result = multicast_publisher_write_data( + mcast_publisher_priv->args.data_write_fd, sizeof(multicast_publisher_data_packet), (unsigned char *) packet); + if (result != RIG_OK) + { + RETURNFUNC(result); + } + + RETURNFUNC(RIG_OK); +} + +int network_publish_rig_poll_data(RIG *rig) +{ + multicast_publisher_data_packet packet = { + .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL, + .padding = 0, + .data_length = 0, + }; + return multicast_publisher_write_packet_header(rig, &packet); +} + +int network_publish_rig_transceive_data(RIG *rig) +{ + multicast_publisher_data_packet packet = { + .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE, + .padding = 0, + .data_length = 0, + }; + return multicast_publisher_write_packet_header(rig, &packet); +} + +int network_publish_rig_spectrum_data(RIG *rig, struct rig_spectrum_line *line) +{ + int result; + struct rig_state *rs = &rig->state; + multicast_publisher_priv_data *mcast_publisher_priv; + multicast_publisher_data_packet packet = { + .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM, + .padding = 0, + .data_length = sizeof(struct rig_spectrum_line) + line->spectrum_data_length, + }; + + if (rs->multicast_publisher_priv_data == NULL) + { + // Silently ignore if multicast publisher is not enabled + RETURNFUNC(RIG_OK); + } + + result = multicast_publisher_write_packet_header(rig, &packet); + if (result != RIG_OK) + { + RETURNFUNC(result); + } + + mcast_publisher_priv = (multicast_publisher_priv_data *) rs->multicast_publisher_priv_data; + + result = multicast_publisher_write_data( + mcast_publisher_priv->args.data_write_fd, sizeof(struct rig_spectrum_line), (unsigned char *) line); + if (result != RIG_OK) + { + RETURNFUNC(result); + } + + result = multicast_publisher_write_data( + mcast_publisher_priv->args.data_write_fd, line->spectrum_data_length, line->spectrum_data); + if (result != RIG_OK) + { + RETURNFUNC(result); + } + + RETURNFUNC(RIG_OK); +} + +static int multicast_publisher_read_packet(int fd, uint8_t *type, struct rig_spectrum_line *spectrum_line, unsigned char *spectrum_data) +{ + int result; + multicast_publisher_data_packet packet; + + result = multicast_publisher_read_data(fd, sizeof(packet), (unsigned char *) &packet); + if (result < 0) + { + RETURNFUNC(result); + } + + switch (packet.type) + { + case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL: + case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE: + break; + case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM: + result = multicast_publisher_read_data( + fd, sizeof(struct rig_spectrum_line), (unsigned char *) spectrum_line); + if (result < 0) + { + RETURNFUNC(result); + } + + if (packet.data_length - sizeof(struct rig_spectrum_line) != spectrum_line->spectrum_data_length) + { + rig_debug(RIG_DEBUG_ERR, "%s: multicast publisher data error, expected %ld bytes of spectrum data, got %ld bytes\n", + __func__, spectrum_line->spectrum_data_length, packet.data_length - sizeof(struct rig_spectrum_line)); + RETURNFUNC(-RIG_EPROTO); + } + + spectrum_line->spectrum_data = spectrum_data; + + result = multicast_publisher_read_data(fd, spectrum_line->spectrum_data_length, spectrum_data); + if (result < 0) + { + RETURNFUNC(result); + } + break; + default: + rig_debug(RIG_DEBUG_ERR, "%s: unexpected multicast publisher data packet type: %d\n", __func__, packet.type); + RETURNFUNC(-RIG_EPROTO); + } + + *type = packet.type; + + RETURNFUNC(RIG_OK); +} + +void *multicast_publisher(void *arg) +{ + struct multicast_publisher_args_s *args = (struct multicast_publisher_args_s *)arg; + RIG *rig = args->rig; + struct rig_state *rs = &rig->state; + struct rig_spectrum_line spectrum_line; + unsigned char spectrum_data[2048]; + char snapshot_buffer[16 * 1024]; + uint8_t packet_type; + + struct sockaddr_in dest_addr; + int socket_fd = args->socket_fd; + int result; + ssize_t send_result; + + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting multicast publisher\n", __FILE__, __LINE__); + + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = inet_addr(args->multicast_addr); + dest_addr.sin_port = htons(args->multicast_port); + + while (rs->multicast_publisher_run) + { + result = multicast_publisher_read_packet(args->data_read_fd, &packet_type, &spectrum_line, spectrum_data); + if (result != RIG_OK) + { + if (result == -RIG_ETIMEOUT) + { + continue; + } + + // TODO: how to detect closing of pipe, indicate with error code + // TODO: error handling, flush pipe in case of error? + hl_usleep(500 * 1000); + continue; + } + + result = snapshot_serialize(sizeof(snapshot_buffer), snapshot_buffer, rig, + packet_type == MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM ? &spectrum_line : NULL); + if (result != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: error serializing rig snapshot data, result=%d\n", __func__, result); + continue; + } + + rig_debug(RIG_DEBUG_TRACE, "%s: sending rig snapshot data: %s\n", __func__, snapshot_buffer); + + send_result = sendto( + socket_fd, + snapshot_buffer, + strlen(snapshot_buffer), + 0, + (struct sockaddr *) &dest_addr, + sizeof(dest_addr) + ); + if (send_result < 0) { + rig_debug(RIG_DEBUG_ERR, "%s: error sending UDP packet: %s\n", __func__, strerror(errno)); + } + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopping multicast publisher\n", __FILE__, __LINE__); return NULL; } + +static void multicast_publisher_close_data_pipe(multicast_publisher_priv_data *mcast_publisher_priv) +{ + if (mcast_publisher_priv->args.data_read_fd != -1) { + close(mcast_publisher_priv->args.data_read_fd); + mcast_publisher_priv->args.data_read_fd = -1; + } + if (mcast_publisher_priv->args.data_write_fd != -1) { + close(mcast_publisher_priv->args.data_write_fd); + mcast_publisher_priv->args.data_write_fd = -1; + } +} + //! @endcond /** - * \brief Open multicast server using rig.state data + * \brief Start multicast publisher * - * Open Open multicast server using rig.state data. - * NB: The signal PIPE will be ignored for the whole application. + * Start multicast publisher. * - * \param rp Port data structure (must spec port id eg hostname:port -- hostname defaults to 224.0.1.1) - * \param default_port Default network socket port + * \param multicast_addr UDP address + * \param multicast_port UDP socket port * \return RIG_OK or < 0 if error */ -int network_multicast_server(RIG *rig, const char *multicast_addr, - int default_port, enum multicast_item_e items) +int network_multicast_publisher_start(RIG *rig, const char *multicast_addr, + int multicast_port, enum multicast_item_e items) { + struct rig_state *rs = &rig->state; + multicast_publisher_priv_data *mcast_publisher_priv; + int socket_fd; + int data_pipe_fds[2]; int status; - //ENTERFUNC; - rig_debug(RIG_DEBUG_VERBOSE, - "%s(%d):network_multicast_server under development\n", __FILE__, __LINE__); - rig_debug(RIG_DEBUG_VERBOSE, "%s(%d):ADDR=%s, port=%d\n", __FILE__, __LINE__, - multicast_addr, default_port); + ENTERFUNC; + + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d):address=%s, port=%d\n", __FILE__, __LINE__, + multicast_addr, multicast_port); if (strcmp(multicast_addr, "0.0.0.0") == 0) { @@ -555,45 +766,134 @@ int network_multicast_server(RIG *rig, const char *multicast_addr, return RIG_OK; // don't start it } - if (multicast_server_threadId != 0) + if (rs->multicast_publisher_priv_data != NULL) { - rig_debug(RIG_DEBUG_ERR, "%s(%d): multicast_server already running\n", __FILE__, + rig_debug(RIG_DEBUG_ERR, "%s(%d): multicast publisher already running\n", __FILE__, __LINE__); + RETURNFUNC(-RIG_EINVAL); } status = network_init(); - if (status != RIG_OK) { RETURNFUNC(status); } + if (status != RIG_OK) + { + RETURNFUNC(status); + } + + socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd < 0) { + rig_debug(RIG_DEBUG_ERR, "%s: error opening new UDP socket: %s", __func__, strerror(errno)); + RETURNFUNC(-RIG_EIO); + } if (items & RIG_MULTICAST_TRANSCEIVE) { - rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_TRANSCEIVE enabled\n", __FILE__, - __LINE__); + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_TRANSCEIVE enabled\n", __FILE__, __LINE__); } if (items & RIG_MULTICAST_SPECTRUM) { - rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_SPECTRUM enabled\n", __FILE__, - __LINE__); + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_SPECTRUM enabled\n", __FILE__, __LINE__); } else { rig_debug(RIG_DEBUG_ERR, "%s(%d) unknown MULTICAST item requested=0x%x\n", - __FILE__, __LINE__, items); + __FILE__, __LINE__, items); } - multicast_server_args.rig = rig; - int err = pthread_create(&multicast_server_threadId, NULL, multicast_server, - &multicast_server_args); + rs->snapshot_packet_sequence_number = 0; + rs->multicast_publisher_run = 1; + rs->multicast_publisher_priv_data = calloc(1, sizeof(multicast_publisher_priv_data)); + if (rs->multicast_publisher_priv_data == NULL) + { + close(socket_fd); + RETURNFUNC(-RIG_ENOMEM); + } + + status = pipe2(data_pipe_fds, O_NONBLOCK); + if (status != 0) + { + free(rs->multicast_publisher_priv_data); + rs->multicast_publisher_priv_data = NULL; + close(socket_fd); + rig_debug(RIG_DEBUG_ERR, "%s: multicast publisher data pipe open status=%d, err=%s\n", __func__, + status, strerror(errno)); + RETURNFUNC(-RIG_EINTERNAL); + } + + mcast_publisher_priv = (multicast_publisher_priv_data *) rs->multicast_publisher_priv_data; + mcast_publisher_priv->args.socket_fd = socket_fd; + mcast_publisher_priv->args.multicast_addr = multicast_addr; + mcast_publisher_priv->args.multicast_port = multicast_port; + mcast_publisher_priv->args.rig = rig; + mcast_publisher_priv->args.data_read_fd = data_pipe_fds[0]; + mcast_publisher_priv->args.data_write_fd = data_pipe_fds[1]; + + int err = pthread_create(&mcast_publisher_priv->thread_id, NULL, multicast_publisher, + &mcast_publisher_priv->args); if (err) { + multicast_publisher_close_data_pipe(mcast_publisher_priv); + free(mcast_publisher_priv); + rs->multicast_publisher_priv_data = NULL; + close(socket_fd); rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error %s\n", __FILE__, __LINE__, strerror(errno)); - return -RIG_EINTERNAL; + RETURNFUNC(-RIG_EINTERNAL); } RETURNFUNC(RIG_OK); } +/** + * \brief Stop multicast publisher + * + * Stop multicast publisher + * + * \return RIG_OK or < 0 if error + */ +int network_multicast_publisher_stop(RIG *rig) +{ + struct rig_state *rs = &rig->state; + multicast_publisher_priv_data *mcast_publisher_priv; + + ENTERFUNC; + + rs->multicast_publisher_run = 0; + + mcast_publisher_priv = (multicast_publisher_priv_data *) rs->multicast_publisher_priv_data; + if (mcast_publisher_priv == NULL) + { + RETURNFUNC(RIG_OK); + } + + if (mcast_publisher_priv->thread_id != 0) + { + int err = pthread_join(mcast_publisher_priv->thread_id, NULL); + + if (err) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__, + strerror(errno)); + // just ignore it + } + + mcast_publisher_priv->thread_id = 0; + } + + multicast_publisher_close_data_pipe(mcast_publisher_priv); + + if (mcast_publisher_priv->args.socket_fd >= 0) + { + close(mcast_publisher_priv->args.socket_fd); + mcast_publisher_priv->args.socket_fd = -1; + } + + free(rs->multicast_publisher_priv_data); + rs->multicast_publisher_priv_data = NULL; + + RETURNFUNC(RIG_OK); +} +#endif /** @} */ diff --git a/src/network.h b/src/network.h index 901e8f857..c46c7b22c 100644 --- a/src/network.h +++ b/src/network.h @@ -29,9 +29,13 @@ __BEGIN_DECLS /* Hamlib internal use, see rig.c */ int network_open(hamlib_port_t *p, int default_port); -HAMLIB_EXPORT(int) network_multicast_server(RIG *rig, const char *multicast_addr, int default_port, enum multicast_item_e items); int network_close(hamlib_port_t *rp); void network_flush(hamlib_port_t *rp); +int network_publish_rig_poll_data(RIG *rig); +int network_publish_rig_transceive_data(RIG *rig); +int network_publish_rig_spectrum_data(RIG *rig, struct rig_spectrum_line *line); +HAMLIB_EXPORT(int) network_multicast_publisher_start(RIG *rig, const char *multicast_addr, int multicast_port, enum multicast_item_e items); +HAMLIB_EXPORT(int) network_multicast_publisher_stop(RIG *rig); __END_DECLS diff --git a/src/rig.c b/src/rig.c index 0e957c05e..1bb6885bb 100644 --- a/src/rig.c +++ b/src/rig.c @@ -77,6 +77,7 @@ #include "misc.h" #include "sprintflst.h" #include "hamlibdatetime.h" +#include "cache.h" /** * \brief Hamlib release number @@ -196,7 +197,8 @@ static const char *const rigerror_table[] = "Communication bus collision", "NULL RIG handle or invalid pointer parameter", "Invalid VFO", - "Argument out of domain of func" + "Argument out of domain of func", + "Function deprecated" }; @@ -228,14 +230,19 @@ void rig_unlock() {}; #endif #ifdef HAVE_PTHREAD -volatile int async_data_handler_thread_run = 0; -pthread_t async_data_handler_thread_id; - -struct async_data_handler_args_s +typedef struct async_data_handler_args_s { RIG *rig; } async_data_handler_args; +typedef struct async_data_handler_priv_data_s +{ + pthread_t thread_id; + async_data_handler_args args; +} async_data_handler_priv_data; + +static int async_data_handler_start(RIG *rig); +static int async_data_handler_stop(RIG *rig); void *async_data_handler(void *arg); #endif @@ -396,30 +403,6 @@ static int rig_check_rig_caps() return rc; } -static void cache_show(RIG *rig, const char *func, int line) -{ - rig_debug(RIG_DEBUG_CACHE, - "%s(%d): freqMainA=%.0f, modeMainA=%s, widthMainA=%d\n", func, line, - rig->state.cache.freqMainA, rig_strrmode(rig->state.cache.modeMainA), - (int)rig->state.cache.widthMainA); - rig_debug(RIG_DEBUG_CACHE, - "%s(%d): freqMainB=%.0f, modeMainB=%s, widthMainB=%d\n", func, line, - rig->state.cache.freqMainB, rig_strrmode(rig->state.cache.modeMainB), - (int)rig->state.cache.widthMainB); - - if (rig->state.vfo_list & RIG_VFO_SUB_A) - { - rig_debug(RIG_DEBUG_CACHE, - "%s(%d): freqSubA=%.0f, modeSubA=%s, widthSubA=%d\n", func, line, - rig->state.cache.freqSubA, rig_strrmode(rig->state.cache.modeSubA), - (int)rig->state.cache.widthSubA); - rig_debug(RIG_DEBUG_CACHE, - "%s(%d): freqSubB=%.0f, modeSubB=%s, widthSubB=%d\n", func, line, - rig->state.cache.freqSubB, rig_strrmode(rig->state.cache.modeSubB), - (int)rig->state.cache.widthSubB); - } -} - /** * \brief allocate a new RIG handle * \param rig_model The rig model for this new handle @@ -479,9 +462,15 @@ RIG *HAMLIB_API rig_init(rig_model_t rig_model) rs->pttport.fd = -1; rs->comm_state = 0; rs->rigport.type.rig = caps->port_type; /* default from caps */ +#ifdef HAVE_PTHREAD rs->rigport.async = caps->async_data_supported; +#else + rs->rigport.async = 0; +#endif rs->rigport.fd_sync_write = -1; rs->rigport.fd_sync_read = -1; + rs->rigport.fd_sync_error_write = -1; + rs->rigport.fd_sync_error_read = -1; switch (caps->port_type) { @@ -530,8 +519,12 @@ RIG *HAMLIB_API rig_init(rig_model_t rig_model) rs->vfo_comp = 0.0; /* override it with preferences */ rs->current_vfo = RIG_VFO_CURR; /* we don't know yet! */ rs->tx_vfo = RIG_VFO_CURR; /* we don't know yet! */ - rs->transceive = RIG_TRN_OFF; - rs->poll_interval = 500; +#ifdef HAVE_PTHREAD + rs->async_data = caps->async_data_supported; +#else + rs->async_data = 0; +#endif + rs->poll_interval = 0; // disable polling by default rs->lo_freq = 0; rs->cache.timeout_ms = 500; // 500ms cache timeout by default @@ -1054,20 +1047,11 @@ int HAMLIB_API rig_open(RIG *rig) RETURNFUNC(status); } - if (caps->async_data_supported) + status = async_data_handler_start(rig); + if (status < 0) { - async_data_handler_thread_run = 1; - async_data_handler_args.rig = rig; - int err = pthread_create(&async_data_handler_thread_id, NULL, - async_data_handler, &async_data_handler_args); - - if (err) - { - rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error: %s\n", __FILE__, __LINE__, - strerror(errno)); - port_close(&rs->rigport, rs->rigport.type.rig); - return -RIG_EINTERNAL; - } + port_close(&rs->rigport, rs->rigport.type.rig); + RETURNFUNC(status); } add_opened_rig(rig); @@ -1084,7 +1068,7 @@ int HAMLIB_API rig_open(RIG *rig) if (status != RIG_OK) { - // TODO: stop async reader + async_data_handler_stop(rig); port_close(&rs->rigport, rs->rigport.type.rig); RETURNFUNC(status); } @@ -1142,23 +1126,6 @@ int HAMLIB_API rig_open(RIG *rig) rig_set_parm(rig, RIG_PARM_SCREENSAVER, parm_value); } -#if 0 - - /* - * Check the current tranceive state of the rig - */ - if (rs->transceive == RIG_TRN_RIG) - { - int retval, trn; - retval = rig_get_trn(rig, &trn); - - if (retval == RIG_OK && trn == RIG_TRN_RIG) - { - add_trn_rig(rig); - } - } - -#endif // read frequency to update internal status // freq_t freq; // if (caps->get_freq) rig_get_freq(rig, RIG_VFO_A, &freq); @@ -1188,44 +1155,6 @@ int HAMLIB_API rig_close(RIG *rig) ENTERFUNC; - // terminate the multicast server - extern int multicast_server_run; - multicast_server_run = 0; -#ifdef HAVE_PTHREAD - extern pthread_t multicast_server_threadId; - - if (multicast_server_threadId != 0) - { - int err = pthread_join(multicast_server_threadId, NULL); - - if (err) - { - rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__, - strerror(errno)); - // just ignore it - } - - multicast_server_threadId = 0; - } - -#endif - - async_data_handler_thread_run = 0; - - if (async_data_handler_thread_id != 0) - { - int err = pthread_join(async_data_handler_thread_id, NULL); - - if (err) - { - rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error: %s\n", __FILE__, __LINE__, - strerror(errno)); - // just ignore the error - } - - async_data_handler_thread_id = 0; - } - if (!rig || !rig->caps) { RETURNFUNC(-RIG_EINVAL); @@ -1239,12 +1168,6 @@ int HAMLIB_API rig_close(RIG *rig) RETURNFUNC(-RIG_EINVAL); } - if (rs->transceive != RIG_TRN_OFF) - { - TRACE; - rig_set_trn(rig, RIG_TRN_OFF); - } - /* * Let the backend say 73s to the rig. * and ignore the return code. @@ -1254,6 +1177,8 @@ int HAMLIB_API rig_close(RIG *rig) caps->rig_close(rig); } + async_data_handler_stop(rig); + /* * FIXME: what happens if PTT and rig ports are the same? * (eg. ptt_type = RIG_PTT_SERIAL) @@ -1483,393 +1408,6 @@ int HAMLIB_API rig_get_twiddle(RIG *rig, int *seconds) RETURNFUNC(RIG_OK); } -static int set_cache_mode(RIG *rig, vfo_t vfo, mode_t mode, pbwidth_t width) -{ - ENTERFUNC; - - cache_show(rig, __func__, __LINE__); - - if (vfo == RIG_VFO_CURR) - { - // if CURR then update this before we figure out the real VFO - vfo = rig->state.current_vfo; - } - - // pick a sane default - if (vfo == RIG_VFO_NONE || vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } - - if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; - - switch (vfo) - { - case RIG_VFO_ALL: // we'll use NONE to reset all VFO caches - elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_INVALIDATE); - break; - - case RIG_VFO_A: - case RIG_VFO_MAIN: - case RIG_VFO_MAIN_A: - rig->state.cache.modeMainA = mode; - - if (width > 0) { rig->state.cache.widthMainA = width; } - - elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_SET); - elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_SET); - break; - - case RIG_VFO_B: - case RIG_VFO_SUB: - case RIG_VFO_MAIN_B: - rig->state.cache.modeMainB = mode; - - if (width > 0) { rig->state.cache.widthMainB = width; } - - elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_SET); - elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_SET); - break; - - case RIG_VFO_C: - case RIG_VFO_MAIN_C: - rig->state.cache.modeMainC = mode; - - if (width > 0) { rig->state.cache.widthMainC = width; } - - elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_SET); - elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_SET); - break; - - default: - rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo=%s\n", __func__, rig_strvfo(vfo)); - RETURNFUNC(-RIG_EINTERNAL); - } - - cache_show(rig, __func__, __LINE__); - RETURNFUNC(RIG_OK); -} - -static int set_cache_freq(RIG *rig, vfo_t vfo, freq_t freq) -{ - int flag = HAMLIB_ELAPSED_SET; - - if (rig_need_debug(RIG_DEBUG_CACHE)) - { - ENTERFUNC; - cache_show(rig, __func__, __LINE__); - } - - rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, current_vfo=%s\n", __func__, - rig_strvfo(vfo), rig_strvfo(rig->state.current_vfo)); - - if (vfo == RIG_VFO_CURR) - { - // if CURR then update this before we figure out the real VFO - vfo = rig->state.current_vfo; - } - - // if freq == 0 then we are asking to invalidate the cache - if (freq == 0) { flag = HAMLIB_ELAPSED_INVALIDATE; } - - // pick a sane default - if (vfo == RIG_VFO_NONE || vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } - - if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; - - if (rig_need_debug(RIG_DEBUG_CACHE)) - { - rig_debug(RIG_DEBUG_CACHE, "%s: set vfo=%s to freq=%.0f\n", __func__, - rig_strvfo(vfo), freq); - } - - switch (vfo) - { - case RIG_VFO_ALL: // we'll use NONE to reset all VFO caches - elapsed_ms(&rig->state.cache.time_freqMainA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqMainB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqMainC, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqSubA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqSubB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqSubC, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_freqMem, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_vfo, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_modeMainA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_modeMainB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_modeMainC, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainA, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainB, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_widthMainC, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_ptt, HAMLIB_ELAPSED_INVALIDATE); - elapsed_ms(&rig->state.cache.time_split, HAMLIB_ELAPSED_INVALIDATE); - break; - - case RIG_VFO_A: - case RIG_VFO_MAIN: - case RIG_VFO_MAIN_A: - rig->state.cache.freqMainA = freq; - elapsed_ms(&rig->state.cache.time_freqMainA, flag); - break; - - case RIG_VFO_B: - case RIG_VFO_MAIN_B: - case RIG_VFO_SUB: - rig->state.cache.freqMainB = freq; - elapsed_ms(&rig->state.cache.time_freqMainB, flag); - break; - - case RIG_VFO_C: - case RIG_VFO_MAIN_C: - rig->state.cache.freqMainC = freq; - elapsed_ms(&rig->state.cache.time_freqMainC, flag); - break; - - case RIG_VFO_SUB_A: - rig->state.cache.freqSubA = freq; - elapsed_ms(&rig->state.cache.time_freqSubA, flag); - break; - - case RIG_VFO_SUB_B: - rig->state.cache.freqSubB = freq; - elapsed_ms(&rig->state.cache.time_freqSubB, flag); - break; - - case RIG_VFO_SUB_C: - rig->state.cache.freqSubC = freq; - elapsed_ms(&rig->state.cache.time_freqSubC, flag); - break; - - case RIG_VFO_MEM: - rig->state.cache.freqMem = freq; - elapsed_ms(&rig->state.cache.time_freqMem, flag); - break; - - default: - rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo?, vfo=%s\n", __func__, - rig_strvfo(vfo)); - RETURNFUNC(-RIG_EINVAL); - } - - if (rig_need_debug(RIG_DEBUG_CACHE)) - { - cache_show(rig, __func__, __LINE__); - RETURNFUNC(RIG_OK); - } - - return RIG_OK; -} - -/** - * \brief get cached values for a VFO - * \param rig The rig handle - * \param vfo The VFO to get information from - * \param freq The frequency is stored here - * \param cache_ms_freq The age of the last frequency update in ms - * \param mode The mode is stored here - * \param cache_ms_mode The age of the last mode update in ms - * \param width The width is stored here - * \param cache_ms_width The age of the last width update in ms - * - * Use this to query the cache and then determine to actually fetch data from - * the rig. - * - * \note All pointers must be given. No pointer can be left at NULL - * - * \return RIG_OK if the operation has been successful, otherwise - * a negative value if an error occurred (in which case, cause is - * set appropriately). - * - */ -int rig_get_cache(RIG *rig, vfo_t vfo, freq_t *freq, int *cache_ms_freq, - rmode_t *mode, int *cache_ms_mode, pbwidth_t *width, int *cache_ms_width) -{ - if (CHECK_RIG_ARG(rig) || !freq || !cache_ms_freq || - !mode || !cache_ms_mode || !width || !cache_ms_width) - { - RETURNFUNC(-RIG_EINVAL); - } - - if (rig_need_debug(RIG_DEBUG_CACHE)) - { - ENTERFUNC; - } - - rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, current_vfo=%s\n", __func__, - rig_strvfo(vfo), rig_strvfo(rig->state.current_vfo)); - - if (vfo == RIG_VFO_CURR) - { - vfo = rig->state.current_vfo; - } - else if (vfo == RIG_VFO_OTHER) - { - switch (vfo) - { - case RIG_VFO_OTHER: - vfo = RIG_VFO_OTHER; - break; - case RIG_VFO_A: - vfo = RIG_VFO_B; - break; - case RIG_VFO_MAIN_A: - vfo = RIG_VFO_MAIN_B; - break; - case RIG_VFO_MAIN: - vfo = RIG_VFO_SUB; - break; - case RIG_VFO_B: - vfo = RIG_VFO_A; - break; - case RIG_VFO_MAIN_B: - vfo = RIG_VFO_MAIN_A; - break; - case RIG_VFO_SUB_A: - vfo = RIG_VFO_SUB_B; - break; - case RIG_VFO_SUB_B: - vfo = RIG_VFO_SUB_A; - break; - default: - rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo=%s\n", __func__, rig_strvfo(vfo)); - } - } - - // pick a sane default - if (vfo == RIG_VFO_CURR || vfo == RIG_VFO_NONE) { vfo = RIG_VFO_A; } - - // If we're in satmode we map SUB to SUB_A - if (vfo == RIG_VFO_SUB && rig->state.cache.satmode) { vfo = RIG_VFO_SUB_A; }; - - switch (vfo) - { - case RIG_VFO_CURR: - *freq = rig->state.cache.freqCurr; - *mode = rig->state.cache.modeCurr; - *width = rig->state.cache.widthCurr; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqCurr, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeCurr, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthCurr, - HAMLIB_ELAPSED_GET); - break; - case RIG_VFO_OTHER: - *freq = rig->state.cache.freqOther; - *mode = rig->state.cache.modeOther; - *width = rig->state.cache.widthOther; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqOther, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeOther, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthOther, - HAMLIB_ELAPSED_GET); - break; - case RIG_VFO_A: - case RIG_VFO_MAIN: - case RIG_VFO_MAIN_A: - *freq = rig->state.cache.freqMainA; - *mode = rig->state.cache.modeMainA; - *width = rig->state.cache.widthMainA; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainA, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainA, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainA, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_B: - case RIG_VFO_SUB: - case RIG_VFO_MAIN_B: - *freq = rig->state.cache.freqMainB; - *mode = rig->state.cache.modeMainB; - *width = rig->state.cache.widthMainB; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainB, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainB, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainB, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_SUB_A: - *freq = rig->state.cache.freqSubA; - *mode = rig->state.cache.modeSubA; - *width = rig->state.cache.widthSubA; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubA, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubA, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubA, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_SUB_B: - *freq = rig->state.cache.freqSubB; - *mode = rig->state.cache.modeSubB; - *width = rig->state.cache.widthSubB; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubB, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubB, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubB, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_C: - //case RIG_VFO_MAINC: // not used by any rig yet - *freq = rig->state.cache.freqMainC; - *mode = rig->state.cache.modeMainC; - *width = rig->state.cache.widthMainC; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMainC, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMainC, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMainC, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_SUB_C: - *freq = rig->state.cache.freqSubC; - *mode = rig->state.cache.modeSubC; - *width = rig->state.cache.widthSubC; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqSubC, - HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeSubC, - HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthSubC, - HAMLIB_ELAPSED_GET); - break; - - case RIG_VFO_MEM: - *freq = rig->state.cache.freqMem; - *mode = rig->state.cache.modeMem; - *width = rig->state.cache.widthMem; - *cache_ms_freq = elapsed_ms(&rig->state.cache.time_freqMem, HAMLIB_ELAPSED_GET); - *cache_ms_mode = elapsed_ms(&rig->state.cache.time_modeMem, HAMLIB_ELAPSED_GET); - *cache_ms_width = elapsed_ms(&rig->state.cache.time_widthMem, - HAMLIB_ELAPSED_GET); - break; - - default: - rig_debug(RIG_DEBUG_ERR, "%s: unknown vfo?, vfo=%s\n", __func__, - rig_strvfo(vfo)); - RETURNFUNC(-RIG_EINVAL); - } - - rig_debug(RIG_DEBUG_CACHE, "%s: vfo=%s, freq=%.0f, mode=%s, width=%d\n", __func__, rig_strvfo(vfo), - (double)*freq, rig_strrmode(*mode), (int)*width); - - if (rig_need_debug(RIG_DEBUG_CACHE)) - { - RETURNFUNC(RIG_OK); - } - - return RIG_OK; -} - // detect if somebody is twiddling the VFO // indicator is last set freq doesn't match current freq // so we have to query freq every time we set freq or vfo to handle this @@ -1904,7 +1442,7 @@ static int twiddling(RIG *rig) rig->state.twiddle_time = time(NULL); // update last twiddle time rig->state.current_freq = curr_freq; // we have a new freq to remember - set_cache_freq(rig, RIG_VFO_CURR, curr_freq); + rig_set_cache_freq(rig, RIG_VFO_CURR, curr_freq); } elapsed = time(NULL) - rig->state.twiddle_time; @@ -2010,7 +1548,7 @@ int HAMLIB_API rig_set_freq(RIG *rig, vfo_t vfo, freq_t freq) if (retcode != RIG_OK) { RETURNFUNC(retcode); } - set_cache_freq(rig, vfo, (freq_t)0); + rig_set_cache_freq(rig, vfo, (freq_t)0); #if 0 // this verification seems to be causing bad behavior on some rigs @@ -2099,7 +1637,7 @@ int HAMLIB_API rig_set_freq(RIG *rig, vfo_t vfo, freq_t freq) #endif ) { - set_cache_freq(rig, RIG_VFO_ALL, (freq_t)0); + rig_set_cache_freq(rig, RIG_VFO_ALL, (freq_t)0); TRACE; retcode = rig_get_freq(rig, vfo, &freq_new); @@ -2118,7 +1656,7 @@ int HAMLIB_API rig_set_freq(RIG *rig, vfo_t vfo, freq_t freq) // update our current freq too if (vfo == RIG_VFO_CURR || vfo == rig->state.current_vfo) { rig->state.current_freq = freq_new; } - set_cache_freq(rig, vfo, freq_new); + rig_set_cache_freq(rig, vfo, freq_new); if (vfo != RIG_VFO_CURR) { @@ -2170,7 +1708,7 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) called vfo=%s\n", __func__, __LINE__, rig_strvfo(vfo)); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); curr_vfo = rig->state.current_vfo; // save vfo for restore later @@ -2199,7 +1737,7 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) RETURNFUNC(RIG_OK); } - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); // there are some rigs that can't get VFOA freq while VFOB is transmitting // so we'll return the cached VFOA freq for them @@ -2233,7 +1771,7 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) &cache_ms_width); //rig_debug(RIG_DEBUG_TRACE, "%s: cache check1 age=%dms\n", __func__, cache_ms_freq); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); if (*freq != 0 && cache_ms_freq < rig->state.cache.timeout_ms) { @@ -2272,7 +1810,7 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) retcode = caps->get_freq(rig, vfo, freq); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); // sometimes a network rig like FLRig will return freq=0 // so we'll just reuse the cache for that condition @@ -2284,8 +1822,8 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) if (retcode == RIG_OK) { - set_cache_freq(rig, vfo, *freq); - cache_show(rig, __func__, __LINE__); + rig_set_cache_freq(rig, vfo, *freq); + rig_cache_show(rig, __func__, __LINE__); } } else @@ -2305,7 +1843,7 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) RETURNFUNC(retcode); } - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); TRACE; retcode = caps->get_freq(rig, vfo, freq); @@ -2319,9 +1857,9 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) if (RIG_OK == retcode) { - cache_show(rig, __func__, __LINE__); - set_cache_freq(rig, vfo, *freq); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); + rig_set_cache_freq(rig, vfo, *freq); + rig_cache_show(rig, __func__, __LINE__); /* return the first error code */ retcode = rc2; } @@ -2344,9 +1882,9 @@ int HAMLIB_API rig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) *freq += rig->state.lo_freq; } - cache_show(rig, __func__, __LINE__); - set_cache_freq(rig, vfo, *freq); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); + rig_set_cache_freq(rig, vfo, *freq); + rig_cache_show(rig, __func__, __LINE__); ELAPSED2; RETURNFUNC(retcode); @@ -2470,7 +2008,7 @@ int HAMLIB_API rig_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) if (retcode != RIG_OK) { RETURNFUNC(retcode); } - set_cache_mode(rig, vfo, mode, width); + rig_set_cache_mode(rig, vfo, mode, width); ELAPSED2; RETURNFUNC(retcode); @@ -2528,14 +2066,14 @@ int HAMLIB_API rig_get_mode(RIG *rig, } *mode = RIG_MODE_NONE; - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); int cache_ms_freq, cache_ms_mode, cache_ms_width; rig_get_cache(rig, vfo, &freq, &cache_ms_freq, mode, &cache_ms_mode, width, &cache_ms_width); rig_debug(RIG_DEBUG_TRACE, "%s: %s cache check age=%dms\n", __func__, rig_strvfo(vfo), cache_ms_mode); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); if ((*mode != RIG_MODE_NONE && cache_ms_mode < rig->state.cache.timeout_ms) && cache_ms_width < rig->state.cache.timeout_ms) @@ -2560,7 +2098,7 @@ int HAMLIB_API rig_get_mode(RIG *rig, retcode = caps->get_mode(rig, vfo, mode, width); rig_debug(RIG_DEBUG_TRACE, "%s: retcode after get_mode=%d\n", __func__, retcode); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); } else { @@ -2578,7 +2116,7 @@ int HAMLIB_API rig_get_mode(RIG *rig, TRACE; retcode = caps->set_vfo(rig, vfo == RIG_VFO_CURR ? RIG_VFO_A : vfo); - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); if (retcode != RIG_OK) { @@ -2603,7 +2141,7 @@ int HAMLIB_API rig_get_mode(RIG *rig, rig_debug(RIG_DEBUG_TRACE, "%s(%d): debug\n", __func__, __LINE__); rig->state.current_mode = *mode; rig->state.current_width = *width; - cache_show(rig, __func__, __LINE__); + rig_cache_show(rig, __func__, __LINE__); } if (*width == RIG_PASSBAND_NORMAL && *mode != RIG_MODE_NONE) @@ -2612,8 +2150,8 @@ int HAMLIB_API rig_get_mode(RIG *rig, *width = rig_passband_normal(rig, *mode); } - set_cache_mode(rig, vfo, *mode, *width); - cache_show(rig, __func__, __LINE__); + rig_set_cache_mode(rig, vfo, *mode, *width); + rig_cache_show(rig, __func__, __LINE__); ELAPSED2; RETURNFUNC(retcode); @@ -2889,12 +2427,12 @@ int HAMLIB_API rig_set_vfo(RIG *rig, vfo_t vfo) rig_debug(RIG_DEBUG_TRACE, "%s: retcode from rig_get_freq = %.10000s\n", __func__, rigerror(retcode)); - set_cache_freq(rig, vfo, curr_freq); + rig_set_cache_freq(rig, vfo, curr_freq); } else { // if no get_freq clear all cache to be sure we refresh whatever we can - set_cache_freq(rig, RIG_VFO_ALL, (freq_t)0); + rig_set_cache_freq(rig, RIG_VFO_ALL, (freq_t)0); } #if 0 // with new cache should not have to expire here anymore @@ -7201,6 +6739,76 @@ HAMLIB_EXPORT(void) sync_callback(int lock) #define MAX_FRAME_LENGTH 1024 +static int async_data_handler_start(RIG *rig) +{ + const struct rig_caps *caps = rig->caps; + struct rig_state *rs = &rig->state; + async_data_handler_priv_data *async_data_handler_priv; + + ENTERFUNC; + +#ifdef HAVE_PTHREAD + if (caps->async_data_supported) + { + rs->async_data_handler_thread_run = 1; + rs->async_data_handler_priv_data = calloc(1, sizeof(async_data_handler_priv_data)); + if (rs->async_data_handler_priv_data == NULL) + { + RETURNFUNC(-RIG_ENOMEM); + } + + async_data_handler_priv = (async_data_handler_priv_data *) rs->async_data_handler_priv_data; + async_data_handler_priv->args.rig = rig; + int err = pthread_create(&async_data_handler_priv->thread_id, NULL, + async_data_handler, &async_data_handler_priv->args); + + if (err) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error: %s\n", __FILE__, __LINE__, + strerror(errno)); + RETURNFUNC(-RIG_EINTERNAL); + } + } +#endif + + RETURNFUNC(RIG_OK); +} + +static int async_data_handler_stop(RIG *rig) +{ + struct rig_state *rs = &rig->state; + async_data_handler_priv_data *async_data_handler_priv; + + ENTERFUNC; + +#ifdef HAVE_PTHREAD + rs->async_data_handler_thread_run = 0; + + async_data_handler_priv = (async_data_handler_priv_data *) rs->async_data_handler_priv_data; + if (async_data_handler_priv != NULL) + { + if (async_data_handler_priv->thread_id != 0) + { + int err = pthread_join(async_data_handler_priv->thread_id, NULL); + + if (err) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error: %s\n", __FILE__, __LINE__, + strerror(errno)); + // just ignore the error + } + + async_data_handler_priv->thread_id = 0; + } + + free(rs->async_data_handler_priv_data); + rs->async_data_handler_priv_data = NULL; + } +#endif + + RETURNFUNC(RIG_OK); +} + void *async_data_handler(void *arg) { struct async_data_handler_args_s *args = (struct async_data_handler_args_s *)arg; @@ -7209,10 +6817,14 @@ void *async_data_handler(void *arg) struct rig_state *rs = &rig->state; int result; - rig_debug(RIG_DEBUG_TRACE, "%s(%d): Starting async data handler thread\n", __FILE__, + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting async data handler thread\n", __FILE__, __LINE__); - while (async_data_handler_thread_run) + // TODO: check how to enable "transceive" on recent Kenwood/Yaesu rigs + // TODO: add initial support for async in Kenwood kenwood_transaction (+one) functions -> add transaction_active flag usage + // TODO: add initial support for async in Yaesu newcat_get_cmd/set_cmd (+validate) functions -> add transaction_active flag usage + + while (rs->async_data_handler_thread_run) { int frame_length; int async_frame; @@ -7220,8 +6832,19 @@ void *async_data_handler(void *arg) result = rig->caps->read_frame_direct(rig, sizeof(frame), frame); if (result < 0) { - // TODO: error handling - rig_debug(RIG_DEBUG_ERR, "%s: read_frame_direct() failed, result=%d\n", __func__, result); + // TODO: it may be necessary to have mutex locking on transaction_active flag + if (rs->transaction_active) + { + unsigned char data = (unsigned char) result; + write_block_sync_error(&rs->rigport, &data, 1); + } + + if (result != -RIG_ETIMEOUT) + { + // TODO: error handling -> store errors in rig state -> to be exposed in async snapshot packets + rig_debug(RIG_DEBUG_ERR, "%s: read_frame_direct() failed, result=%d\n", __func__, result); + hl_usleep(500 * 1000); + } continue; } @@ -7236,7 +6859,8 @@ void *async_data_handler(void *arg) result = rig->caps->process_async_frame(rig, frame_length, frame); if (result < 0) { - // TODO: error handling + // TODO: error handling -> store errors in rig state -> to be exposed in async snapshot packets + rig_debug(RIG_DEBUG_ERR, "%s: process_async_frame() failed, result=%d\n", __func__, result); continue; } } @@ -7245,14 +6869,14 @@ void *async_data_handler(void *arg) result = write_block_sync(&rs->rigport, frame, frame_length); if (result < 0) { - // TODO: error handling + // TODO: error handling? can writing to a pipe really fail in ways we can recover from? rig_debug(RIG_DEBUG_ERR, "%s: write_block_sync() failed, result=%d\n", __func__, result); continue; } } } - rig_debug(RIG_DEBUG_TRACE, "%s(%d): Stopping async data handler thread\n", __FILE__, + rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopping async data handler thread\n", __FILE__, __LINE__); return NULL; diff --git a/src/snapshot_data.c b/src/snapshot_data.c new file mode 100644 index 000000000..71876778a --- /dev/null +++ b/src/snapshot_data.c @@ -0,0 +1,347 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "misc.h" +#include "snapshot_data.h" +#include "hamlibdatetime.h" + +#include "cJSON.h" + +// Maximum number of data bytes in a single spectrum line +#define SPECTRUM_DATA_MAX_LENGTH 2048 + +#define MAX_VFO_COUNT 4 + +#define SPECTRUM_MODE_FIXED "FIXED" +#define SPECTRUM_MODE_CENTER "CENTER" + +static int snapshot_serialize_vfo(cJSON *vfo_node, RIG *rig, vfo_t vfo) +{ + freq_t freq; + int freq_ms, mode_ms, width_ms; + rmode_t mode; + pbwidth_t width; + ptt_t ptt; + split_t split; + vfo_t split_vfo; + int result; + int is_rx, is_tx; + cJSON *node; + + // TODO: This data should match rig_get_info command response + + node = cJSON_AddStringToObject(vfo_node, "name", rig_strvfo(vfo)); + if (node == NULL) + { + goto error; + } + + result = rig_get_cache(rig, vfo, &freq, &freq_ms, &mode, &mode_ms, &width, &width_ms); + if (result == RIG_OK) + { + node = cJSON_AddNumberToObject(vfo_node, "freq", freq); + if (node == NULL) + { + goto error; + } + node = cJSON_AddStringToObject(vfo_node, "mode", rig_strrmode(mode)); + if (node == NULL) + { + goto error; + } + node = cJSON_AddNumberToObject(vfo_node, "width", (double) width); + if (node == NULL) + { + goto error; + } + } + + ptt = rig->state.cache.ptt; + node = cJSON_AddBoolToObject(vfo_node, "ptt", ptt == RIG_PTT_OFF ? 0 : 1); + if (node == NULL) + { + goto error; + } + + split = rig->state.cache.split; + split_vfo = rig->state.cache.split_vfo; + + is_rx = (split == RIG_SPLIT_OFF && vfo == rig->state.current_vfo) || (split == RIG_SPLIT_ON && vfo != split_vfo); + node = cJSON_AddBoolToObject(vfo_node, "rx", is_rx); + if (node == NULL) + { + goto error; + } + + is_tx = (split == RIG_SPLIT_OFF && vfo == rig->state.current_vfo) || (split == RIG_SPLIT_ON && vfo == split_vfo); + node = cJSON_AddBoolToObject(vfo_node, "tx", is_tx); + if (node == NULL) + { + goto error; + } + + RETURNFUNC(RIG_OK); + + error: + RETURNFUNC(-RIG_EINTERNAL); +} + +static int snapshot_serialize_spectrum(cJSON *spectrum_node, RIG *rig, struct rig_spectrum_line *spectrum_line) +{ + // Spectrum data is represented as a hexadecimal ASCII string where each data byte is represented as 2 ASCII letters + char spectrum_data_string[SPECTRUM_DATA_MAX_LENGTH * 2]; + cJSON *node; + int i; + struct rig_spectrum_scope *scopes = rig->caps->spectrum_scopes; + char *name = "?"; + + for (i = 0; scopes[i].name != NULL; i++) + { + if (scopes[i].id == spectrum_line->id) + { + name = scopes[i].name; + } + } + + node = cJSON_AddNumberToObject(spectrum_node, "id", spectrum_line->id); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddStringToObject(spectrum_node, "name", name); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddStringToObject(spectrum_node, "type", + spectrum_line->spectrum_mode == RIG_SPECTRUM_MODE_CENTER ? + SPECTRUM_MODE_CENTER : SPECTRUM_MODE_FIXED); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "minLevel", spectrum_line->data_level_min); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "maxLevel", spectrum_line->data_level_max); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "minStrength", spectrum_line->signal_strength_min); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "maxStrength", spectrum_line->signal_strength_max); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "centerFreq", spectrum_line->center_freq); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "span", spectrum_line->span_freq); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "lowFreq", spectrum_line->low_edge_freq); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "highFreq", spectrum_line->high_edge_freq); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddNumberToObject(spectrum_node, "length", (double) spectrum_line->spectrum_data_length); + if (node == NULL) + { + goto error; + } + + to_hex(spectrum_line->spectrum_data_length, spectrum_line->spectrum_data, + sizeof(spectrum_data_string), spectrum_data_string); + node = cJSON_AddStringToObject(spectrum_node, "data", spectrum_data_string); + if (node == NULL) + { + goto error; + } + + RETURNFUNC(RIG_OK); + + error: + RETURNFUNC(-RIG_EINTERNAL); +} + +int snapshot_serialize(size_t buffer_length, char *buffer, RIG *rig, struct rig_spectrum_line *spectrum_line) +{ + cJSON *root_node; + cJSON *rig_node, *vfos_array, *vfo_node, *spectra_array, *spectrum_node; + cJSON *node; + cJSON_bool bool_result; + + int vfo_count = 2; + vfo_t vfos[MAX_VFO_COUNT]; + int result; + int i; + + vfos[0] = RIG_VFO_A; + vfos[1] = RIG_VFO_B; + + root_node = cJSON_CreateObject(); + if (root_node == NULL) + { + RETURNFUNC(-RIG_EINTERNAL); + } + + node = cJSON_AddStringToObject(root_node, "app", PACKAGE_NAME); + if (node == NULL) + { + goto error; + } + node = cJSON_AddStringToObject(root_node, "version", PACKAGE_VERSION " " HAMLIBDATETIME); + if (node == NULL) + { + goto error; + } + node = cJSON_AddNumberToObject(root_node, "seq", rig->state.snapshot_packet_sequence_number); + if (node == NULL) + { + goto error; + } + + // TODO: What content should CRC be based on? + node = cJSON_AddNumberToObject(root_node, "crc", 0); + if (node == NULL) + { + goto error; + } + + rig_node = cJSON_CreateObject(); + if (rig_node == NULL) + { + goto error; + } + + cJSON_AddItemToObject(root_node, "rig", rig_node); + + node = cJSON_AddStringToObject(rig_node, "id", "rig_id"); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddStringToObject(rig_node, "status", ""); + if (node == NULL) + { + goto error; + } + node = cJSON_AddStringToObject(rig_node, "errorMsg", ""); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddStringToObject(rig_node, "name", rig->caps->model_name); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddBoolToObject(rig_node, "split", rig->state.cache.split == RIG_SPLIT_ON ? 1 : 0); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddStringToObject(rig_node, "splitVfo", rig_strvfo(rig->state.cache.split_vfo)); + if (node == NULL) + { + goto error; + } + + node = cJSON_AddBoolToObject(rig_node, "satMode", 0); + if (node == NULL) + { + goto error; + } + + vfos_array = cJSON_CreateArray(); + if (vfos_array == NULL) + { + goto error; + } + + for (i = 0; i < vfo_count; i++) + { + vfo_node = cJSON_CreateObject(); + result = snapshot_serialize_vfo(vfo_node, rig, vfos[i]); + if (result != RIG_OK) + { + cJSON_Delete(vfo_node); + goto error; + } + + cJSON_AddItemToArray(vfos_array, vfo_node); + } + + cJSON_AddItemToObject(root_node, "vfos", vfos_array); + + if (spectrum_line != NULL) + { + spectra_array = cJSON_CreateArray(); + if (spectra_array == NULL) + { + goto error; + } + + spectrum_node = cJSON_CreateObject(); + result = snapshot_serialize_spectrum(spectrum_node, rig, spectrum_line); + if (result != RIG_OK) + { + cJSON_Delete(spectrum_node); + goto error; + } + + cJSON_AddItemToArray(spectra_array, spectrum_node); + + cJSON_AddItemToObject(root_node, "spectra", spectra_array); + } + + bool_result = cJSON_PrintPreallocated(root_node, buffer, (int) buffer_length, 0); + + cJSON_Delete(root_node); + + if (!bool_result) + { + RETURNFUNC(-RIG_EINVAL); + } + + rig->state.snapshot_packet_sequence_number++; + + RETURNFUNC(RIG_OK); + +error: + cJSON_Delete(root_node); + RETURNFUNC(-RIG_EINTERNAL); +} diff --git a/src/snapshot_data.h b/src/snapshot_data.h new file mode 100644 index 000000000..ab1536429 --- /dev/null +++ b/src/snapshot_data.h @@ -0,0 +1,6 @@ +#ifndef _SNAPSHOT_DATA_H +#define _SNAPSHOT_DATA_H + +int snapshot_serialize(size_t buffer_length, char *buffer, RIG *rig, struct rig_spectrum_line *spectrum_line); + +#endif diff --git a/src/token.h b/src/token.h index 84e3d283c..b73174a69 100644 --- a/src/token.h +++ b/src/token.h @@ -98,7 +98,7 @@ /** \brief rig: ?? */ #define TOK_VFO_COMP TOKEN_FRONTEND(110) -/** \brief rig: polling interval (units?) */ +/** \brief rig: Rig state poll interval in milliseconds */ #define TOK_POLL_INTERVAL TOKEN_FRONTEND(111) /** \brief rig: lo frequency of any transverters */ #define TOK_LO_FREQ TOKEN_FRONTEND(112) @@ -106,7 +106,7 @@ #define TOK_RANGE_SELECTED TOKEN_FRONTEND(121) /** \brief rig: Range Name */ #define TOK_RANGE_NAME TOKEN_FRONTEND(122) -/** \brief rig: Cache timeout */ +/** \brief rig: Cache timeout in milliseconds */ #define TOK_CACHE_TIMEOUT TOKEN_FRONTEND(123) /** \brief rig: Auto power on rig_open when supported */ #define TOK_AUTO_POWER_ON TOKEN_FRONTEND(124) diff --git a/tests/dumpcaps.c b/tests/dumpcaps.c index b0d1f19fd..ee73e0b76 100644 --- a/tests/dumpcaps.c +++ b/tests/dumpcaps.c @@ -247,8 +247,8 @@ int dumpcaps(RIG *rig, FILE *fout) caps->targetable_vfo ? "Y" : "N"); fprintf(fout, - "Has transceive: %s\n", - caps->transceive ? "Y" : "N"); + "Has async data support: %s\n", + caps->async_data_supported ? "Y" : "N"); fprintf(fout, "Announce: 0x%x\n", caps->announces); fprintf(fout, diff --git a/tests/rigctl_parse.c b/tests/rigctl_parse.c index df9f281cd..35c7c1289 100644 --- a/tests/rigctl_parse.c +++ b/tests/rigctl_parse.c @@ -4023,218 +4023,25 @@ declare_proto_rig(get_channel) } -static int myfreq_event(RIG *rig, vfo_t vfo, freq_t freq, rig_ptr_t arg) -{ - ENTERFUNC; - - printf("Event: freq changed to %"PRIll"Hz on %s\n", - (int64_t)freq, - rig_strvfo(vfo)); - - RETURNFUNC(0); -} - - -static int mymode_event(RIG *rig, - vfo_t vfo, - rmode_t mode, - pbwidth_t width, - rig_ptr_t arg) -{ - ENTERFUNC; - - printf("Event: mode changed to %s, width %liHz on %s\n", - rig_strrmode(mode), - width, rig_strvfo(vfo)); - - RETURNFUNC(0); -} - - -static int myvfo_event(RIG *rig, vfo_t vfo, rig_ptr_t arg) -{ - ENTERFUNC; - - printf("Event: vfo changed to %s\n", rig_strvfo(vfo)); - RETURNFUNC(0); -} - - -static int myptt_event(RIG *rig, vfo_t vfo, ptt_t ptt, rig_ptr_t arg) -{ - ENTERFUNC; - - printf("Event: PTT changed to %i on %s\n", ptt, rig_strvfo(vfo)); - - RETURNFUNC(0); -} - - -static int mydcd_event(RIG *rig, vfo_t vfo, dcd_t dcd, rig_ptr_t arg) -{ - ENTERFUNC; - - printf("Event: DCD changed to %i on %s\n", dcd, rig_strvfo(vfo)); - - RETURNFUNC(0); -} - - -static int print_spectrum_line(char *str, size_t length, - struct rig_spectrum_line *line) -{ - int data_level_max = line->data_level_max / 2; - int aggregate_count = line->spectrum_data_length / 120; - int aggregate_value = 0; - int i, c; - int charlen = strlen("█"); - - str[0] = '\0'; - - for (i = 0, c = 0; i < line->spectrum_data_length; i++) - { - int current = line->spectrum_data[i]; - aggregate_value = current > aggregate_value ? current : aggregate_value; - - if (i > 0 && i % aggregate_count == 0) - { - if (c + charlen >= length) - { - break; - } - - int level = aggregate_value * 10 / data_level_max; - - if (level >= 8) - { - strcpy(str + c, "█"); - c += charlen; - } - else if (level >= 6) - { - strcpy(str + c, "▓"); - c += charlen; - } - else if (level >= 4) - { - strcpy(str + c, "▒"); - c += charlen; - } - else if (level >= 2) - { - strcpy(str + c, "░"); - c += charlen; - } - else if (level >= 0) - { - strcpy(str + c, " "); - c += 1; - } - - aggregate_value = 0; - } - } - - return c; -} - - -static int myspectrum_event(RIG *rig, struct rig_spectrum_line *line, - rig_ptr_t arg) -{ - ENTERFUNC; - - if (rig_need_debug(RIG_DEBUG_ERR)) - { - char spectrum_debug[line->spectrum_data_length * 4]; - print_spectrum_line(spectrum_debug, sizeof(spectrum_debug), line); - rig_debug(RIG_DEBUG_ERR, "%s: ASCII Spectrum Scope: %s\n", __func__, - spectrum_debug); - } - - // TODO: Push out spectrum data via multicast server once it is implemented - - RETURNFUNC(0); -} - - /* 'A' */ +/** + * \deprecated Function deprecated. Use the new async data functionality instead. + */ declare_proto_rig(set_trn) { - int trn; - ENTERFUNC; - - if (!strcmp(arg1, "?")) - { - fprintf(fout, "OFF RIG POLL\n"); - RETURNFUNC(RIG_OK); - } - - if (!strcmp(arg1, "OFF")) - { - trn = RIG_TRN_OFF; - } - else if (!strcmp(arg1, "RIG") || !strcmp(arg1, "ON")) - { - trn = RIG_TRN_RIG; - } - else if (!strcmp(arg1, "POLL")) - { - trn = RIG_TRN_POLL; - } - else - { - RETURNFUNC(-RIG_EINVAL); - } - - if (trn != RIG_TRN_OFF) - { - rig_set_freq_callback(rig, myfreq_event, NULL); - rig_set_mode_callback(rig, mymode_event, NULL); - rig_set_vfo_callback(rig, myvfo_event, NULL); - rig_set_ptt_callback(rig, myptt_event, NULL); - rig_set_dcd_callback(rig, mydcd_event, NULL); - rig_set_spectrum_callback(rig, myspectrum_event, NULL); - } - - RETURNFUNC(RIG_OK); - //RETURNFUNC(rig_set_trn(rig, trn)); + RETURNFUNC(-RIG_EDEPRECATED); } /* 'a' */ +/** + * \deprecated Function deprecated. Use the new async data functionality instead. + */ declare_proto_rig(get_trn) { - int status; - int trn; - static const char *trn_txt[] = - { - "OFF", - "RIG", - "POLL" - }; - ENTERFUNC; - - status = rig_get_trn(rig, &trn); - - if (status != RIG_OK) - { - RETURNFUNC(status); - } - - if ((interactive && prompt) || (interactive && !prompt && ext_resp)) - { - fprintf(fout, "%s: ", cmd->arg1); - } - - if (trn >= 0 && trn <= 2) - { - fprintf(fout, "%s%c", trn_txt[trn], resp_sep); - } - - RETURNFUNC(status); + RETURNFUNC(-RIG_EDEPRECATED); } diff --git a/tests/rigctld.c b/tests/rigctld.c index 6c956e75f..8b9fd5f64 100644 --- a/tests/rigctld.c +++ b/tests/rigctld.c @@ -111,6 +111,7 @@ static struct option long_options[] = {"uplink", 1, 0, 'x'}, {"debug-time-stamps", 0, 0, 'Z'}, {"multicast-addr", 1, 0, 'M'}, + {"multicast-port", 1, 0, 'n'}, {0, 0, 0, 0} }; @@ -145,6 +146,7 @@ static int volatile ctrl_c; const char *portno = "4532"; const char *src_addr = NULL; /* INADDR_ANY */ const char *multicast_addr = "0.0.0.0"; +int multicast_port = 4532; #define MAXCONFLEN 1024 @@ -558,6 +560,21 @@ int main(int argc, char *argv[]) multicast_addr = optarg; break; + case 'n': + if (!optarg) + { + usage(); /* wrong arg count */ + exit(1); + } + + multicast_port = atoi(optarg); + if (multicast_port == 0) + { + fprintf(stderr, "Invalid multicast port: %s\n", optarg); + exit(1); + } + break; + default: usage(); /* unknown option? */ exit(1); @@ -756,9 +773,8 @@ int main(int argc, char *argv[]) saved_result = result; - enum multicast_item_e items = RIG_MULTICAST_POLL | RIG_MULTICAST_TRANSCEIVE | - RIG_MULTICAST_SPECTRUM; - retcode = network_multicast_server(my_rig, multicast_addr, 4532, items); + enum multicast_item_e items = RIG_MULTICAST_POLL | RIG_MULTICAST_TRANSCEIVE | RIG_MULTICAST_SPECTRUM; + retcode = network_multicast_publisher_start(my_rig, multicast_addr, multicast_port, items); if (retcode != RIG_OK) { @@ -927,7 +943,6 @@ int main(int argc, char *argv[]) rig_debug(RIG_DEBUG_ERR, "%s: select() failed: %s\n", __func__, strerror(errno_stored)); - // TODO: FIXME: Why does this select() return EINTR after any command when set_trn RIG is enabled? if (errno == EINTR) { rig_debug(RIG_DEBUG_VERBOSE, "%s: ignoring interrupted system call\n", @@ -995,6 +1010,8 @@ int main(int argc, char *argv[]) } while (retcode == 0 && !ctrl_c); + network_multicast_publisher_stop(my_rig); + #ifdef HAVE_PTHREAD /* allow threads to finish current action */ mutex_rigctld(1); @@ -1271,7 +1288,8 @@ void usage(void) " -W, --twiddle_rit suppress VFOB getfreq so RIT can be twiddled\n" " -x, --uplink set uplink get_freq ignore, 1=Sub, 2=Main\n" " -Z, --debug-time-stamps enable time stamps for debug messages\n" - " -M, --multicast-addr=addr set multicast addr, default 0.0.0.0 (off), recommend 224.0.1.1\n" + " -M, --multicast-addr=addr set multicast UDP address, default 0.0.0.0 (off), recommend 224.0.1.1\n" + " -n, --multicast-port=port set multicast UDP port, default 4532\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n\n", portno);