mirror of
https://github.com/merbanan/rtl_433.git
synced 2026-04-23 11:07:09 -04:00
General structural data support for generating (ie.) JSON
Adds a new interface for constructing, printing and releasing data_t* structures. The data structure describes typed hierarchical data (much like json) and then provides a function for dumping it to a file descriptor. It comes with a small test.
This commit is contained in:
@@ -71,6 +71,7 @@ add_custom_target(uninstall
|
||||
########################################################################
|
||||
add_subdirectory(include)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(tests)
|
||||
|
||||
# use space-separation format for the pc file
|
||||
STRING(REPLACE ";" " " RTL433_PC_CFLAGS "${RTL433_PC_CFLAGS}")
|
||||
|
||||
98
include/data.h
Normal file
98
include/data.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* A general structure for extracting hierarchical data from the devices;
|
||||
* typically key-value pairs, but allows for more rich data as well.
|
||||
*
|
||||
* Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_DATA_H_
|
||||
#define INCLUDE_DATA_H_
|
||||
|
||||
typedef enum {
|
||||
DATA_DATA, /* pointer to data is stored */
|
||||
DATA_INT, /* pointer to integer is stored */
|
||||
DATA_DOUBLE, /* pointer to a double is stored */
|
||||
DATA_STRING, /* pointer to a string is stored */
|
||||
DATA_ARRAY, /* pointer to an array of values is stored */
|
||||
DATA_COUNT /* invalid */
|
||||
} data_type_t;
|
||||
|
||||
typedef struct data_array {
|
||||
int num_values;
|
||||
data_type_t type;
|
||||
void *values;
|
||||
} data_array_t;
|
||||
|
||||
typedef struct data {
|
||||
char *key;
|
||||
char *pretty_key; /* the name used for displaying data to user in with a nicer name */
|
||||
data_type_t type;
|
||||
void *value;
|
||||
struct data* next; /* chaining to the next element in the linked list; NULL indicates end-of-list */
|
||||
} data_t;
|
||||
|
||||
/** Constructs a structured data object.
|
||||
|
||||
Example:
|
||||
data_make("key", "Pretty key", DATA_INT, 42,
|
||||
"others", "More data", DATA_DATA, data_make("foo", DATA_DOUBLE, 42.0, NULL),
|
||||
"zoom", NULL, data_array(2, DATA_STRING, (char*[]){"hello", "World"}),
|
||||
"double", "Double", DATA_DOUBLE, 10.0/3,
|
||||
NULL);
|
||||
|
||||
Most of the time the function copies perhaps what you expect it to. Things
|
||||
it copies:
|
||||
- string contents for keys and values
|
||||
- numerical arrays
|
||||
- string arrays (copied deeply)
|
||||
|
||||
Things it moves:
|
||||
- recursive data_t* and data_array_t* values
|
||||
|
||||
The rule is: if an object is boxed (look at the dmt structure in the data.c)
|
||||
and it has a array_elementwise_import in the same structure, then it is
|
||||
copied deeply. Otherwise, it is copied shallowly.
|
||||
|
||||
@param key Name of the first value to put in.
|
||||
@param pretty_key Pretty name for the key. Use "" if to omit pretty label for this field completely,
|
||||
or NULL if to use key name for it.
|
||||
@param type Type of the first value to put in.
|
||||
@param ... The value of the first value to put in, follwed by the rest of the
|
||||
key-type-values. The list is terminated with a NULL.
|
||||
|
||||
@return A constructed data_t* object or NULL if there was a memory allocation error.
|
||||
*/
|
||||
data_t *data_make(const char *key, const char *pretty_key, data_type_t type, ...);
|
||||
|
||||
/** Constructs an array from given data of the given uniform type.
|
||||
|
||||
@param ptr The contents pointed by the argument are copied in.
|
||||
|
||||
@return The constructed data array object, typically placed inside a data_t or NULL
|
||||
if there was a memory allocation error.
|
||||
*/
|
||||
data_array_t *data_array(int num_values, data_type_t type, void *ptr);
|
||||
|
||||
/** Releases a data array */
|
||||
void data_array_free(data_array_t *array);
|
||||
|
||||
/** Prints a structured data object as JSON to the given stream */
|
||||
void data_print_json(data_t *data, FILE* file);
|
||||
|
||||
/** Releases a structure object */
|
||||
void data_free(data_t *data);
|
||||
|
||||
#endif // INCLUDE_DATA_H_
|
||||
@@ -23,6 +23,7 @@ add_executable(rtl_433
|
||||
pulse_demod.c
|
||||
pulse_detect.c
|
||||
util.c
|
||||
data.c
|
||||
devices/silvercrest.c
|
||||
devices/rubicson.c
|
||||
devices/prologue.c
|
||||
@@ -60,6 +61,8 @@ add_executable(rtl_433
|
||||
devices/generic_temperature_sensor.c
|
||||
)
|
||||
|
||||
add_library(data data.c)
|
||||
|
||||
target_link_libraries(rtl_433
|
||||
${LIBRTLSDR_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
|
||||
360
src/data.c
Normal file
360
src/data.c
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* A general structure for extracting hierarchical data from the devices;
|
||||
* typically key-value pairs, but allows for more rich data as well.
|
||||
*
|
||||
* Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "data.h"
|
||||
|
||||
typedef void* (*array_elementwise_import_fn)(void*);
|
||||
typedef void* (*array_element_release_fn)(void*);
|
||||
typedef void* (*value_release_fn)(void*);
|
||||
|
||||
typedef struct {
|
||||
/* what is the element size when put inside an array? */
|
||||
int array_element_size;
|
||||
|
||||
/* is the element boxed (ie. behind a pointer) when inside an array?
|
||||
if it's not boxed ("unboxed"), json dumping function needs to make
|
||||
a copy of the value beforehand, because the dumping function only
|
||||
deals with boxed values.
|
||||
*/
|
||||
_Bool array_is_boxed;
|
||||
|
||||
/* function for importing arrays. strings are specially handled (as they
|
||||
are copied deeply), whereas other arrays are just copied shallowly
|
||||
(but copied nevertheles) */
|
||||
array_elementwise_import_fn array_elementwise_import;
|
||||
|
||||
/* a function for releasing an element when put in an array; integers
|
||||
* don't need to be released, while ie. strings and arrays do. */
|
||||
array_element_release_fn array_element_release;
|
||||
|
||||
/* a function for releasing a value. everything needs to be released. */
|
||||
value_release_fn value_release;
|
||||
} data_meta_type_t;
|
||||
|
||||
struct data_printer_context;
|
||||
|
||||
typedef struct data_printer {
|
||||
void (*print_data)(struct data_printer_context *printer_ctx, data_t *data, FILE *file);
|
||||
void (*print_array)(struct data_printer_context *printer_ctx, data_array_t *data, FILE *file);
|
||||
void (*print_string)(struct data_printer_context *printer_ctx, const char *data, FILE *file);
|
||||
void (*print_double)(struct data_printer_context *printer_ctx, double data, FILE *file);
|
||||
void (*print_int)(struct data_printer_context *printer_ctx, int data, FILE *file);
|
||||
} data_printer_t;
|
||||
|
||||
typedef struct data_printer_context {
|
||||
data_printer_t *printer;
|
||||
} data_printer_context_t;
|
||||
|
||||
static data_meta_type_t dmt[DATA_COUNT] = {
|
||||
// DATA_DATA
|
||||
{ array_element_size : sizeof(data_t*),
|
||||
array_is_boxed : true,
|
||||
array_elementwise_import : NULL,
|
||||
array_element_release : (array_element_release_fn) data_free,
|
||||
value_release : (value_release_fn) data_free },
|
||||
|
||||
// DATA_INT
|
||||
{ array_element_size : sizeof(int),
|
||||
array_is_boxed : false,
|
||||
array_elementwise_import : NULL,
|
||||
array_element_release : NULL,
|
||||
value_release : (value_release_fn) free },
|
||||
|
||||
// DATA_DOUBLE
|
||||
{ array_element_size : sizeof(double),
|
||||
array_is_boxed : false,
|
||||
array_elementwise_import : NULL,
|
||||
array_element_release : NULL,
|
||||
value_release : (value_release_fn) free },
|
||||
|
||||
// DATA_STRING
|
||||
{ array_element_size : sizeof(char*),
|
||||
array_is_boxed : true,
|
||||
array_elementwise_import : (array_elementwise_import_fn) strdup,
|
||||
array_element_release : (array_element_release_fn) free,
|
||||
value_release : (value_release_fn) free },
|
||||
|
||||
// DATA_ARRAY
|
||||
{ array_element_size : sizeof(data_array_t*),
|
||||
array_is_boxed : true,
|
||||
array_elementwise_import : NULL,
|
||||
array_element_release : (array_element_release_fn) data_array_free ,
|
||||
value_release : (value_release_fn) data_array_free },
|
||||
};
|
||||
|
||||
static void print_json_data(data_printer_context_t *printer_ctx, data_t *data, FILE *file);
|
||||
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *data, FILE *file);
|
||||
static void print_json_string(data_printer_context_t *printer_ctx, const char *data, FILE *file);
|
||||
static void print_json_double(data_printer_context_t *printer_ctx, double data, FILE *file);
|
||||
static void print_json_int(data_printer_context_t *printer_ctx, int data, FILE *file);
|
||||
|
||||
static data_printer_t json_printer = {
|
||||
print_data : print_json_data,
|
||||
print_array : print_json_array,
|
||||
print_string : print_json_string,
|
||||
print_double : print_json_double,
|
||||
print_int : print_json_int
|
||||
};
|
||||
|
||||
static _Bool import_values(void* dst, void* src, int num_values, data_type_t type) {
|
||||
int element_size = dmt[type].array_element_size;
|
||||
array_elementwise_import_fn import = dmt[type].array_elementwise_import;
|
||||
if (import) {
|
||||
for (int i = 0; i < num_values; ++i) {
|
||||
void *copy = import(*(void**)(src + element_size * i));
|
||||
if (!copy) {
|
||||
--i;
|
||||
while (i >= 0) {
|
||||
free(*(void**)(dst + element_size * i));
|
||||
--i;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
*((char**) dst + i) = copy;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
memcpy(dst, src, element_size * num_values);
|
||||
}
|
||||
return true; // error is returned early
|
||||
}
|
||||
|
||||
data_array_t *data_array(int num_values, data_type_t type, void *values) {
|
||||
data_array_t *array = calloc(1, sizeof(data_array_t));
|
||||
if (array) {
|
||||
int element_size = dmt[type].array_element_size;
|
||||
array->values = calloc(num_values, element_size);
|
||||
if (!array->values)
|
||||
goto alloc_error;
|
||||
if (!import_values(array->values, values, num_values, type))
|
||||
goto alloc_error;
|
||||
|
||||
array->num_values = num_values;
|
||||
array->type = type;
|
||||
}
|
||||
return array;
|
||||
|
||||
alloc_error:
|
||||
if (array)
|
||||
free(array->values);
|
||||
free(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data_t *data_make(const char *key, const char *pretty_key, data_type_t type, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, type);
|
||||
|
||||
data_t *first = NULL;
|
||||
data_t *prev = NULL;
|
||||
do {
|
||||
data_t *current;
|
||||
void *value = NULL;
|
||||
|
||||
switch (type) {
|
||||
case DATA_COUNT : {
|
||||
assert(0);
|
||||
} break;
|
||||
case DATA_DATA : {
|
||||
value = va_arg(ap, data_t*);
|
||||
} break;
|
||||
case DATA_INT : {
|
||||
value = malloc(sizeof(int));
|
||||
if (value)
|
||||
*(int*) value = va_arg(ap, int);
|
||||
} break;
|
||||
case DATA_DOUBLE : {
|
||||
value = malloc(sizeof(double));
|
||||
if (value)
|
||||
*(double*) value = va_arg(ap, double);
|
||||
} break;
|
||||
case DATA_STRING : {
|
||||
value = strdup(va_arg(ap, char*));
|
||||
} break;
|
||||
case DATA_ARRAY : {
|
||||
value = va_arg(ap, data_t*);
|
||||
} break;
|
||||
}
|
||||
|
||||
// also some null arguments are mapped to an alloc error;
|
||||
// that's ok, because they originate (typically..) from
|
||||
// an alloc error anyway
|
||||
if (!value)
|
||||
goto alloc_error;
|
||||
|
||||
current = calloc(1, sizeof(*current));
|
||||
if (!current)
|
||||
goto alloc_error;
|
||||
if (prev)
|
||||
prev->next = current;
|
||||
|
||||
current->key = strdup(key);
|
||||
if (!current->key)
|
||||
goto alloc_error;
|
||||
current->pretty_key = strdup(pretty_key ? pretty_key : key);
|
||||
if (!current->pretty_key)
|
||||
goto alloc_error;
|
||||
current->type = type;
|
||||
current->value = value;
|
||||
current->next = NULL;
|
||||
|
||||
prev = current;
|
||||
if (!first)
|
||||
first = current;
|
||||
|
||||
key = va_arg(ap, const char*);
|
||||
if (key) {
|
||||
pretty_key = va_arg(ap, const char*);
|
||||
type = va_arg(ap, data_type_t);
|
||||
}
|
||||
} while (key);
|
||||
va_end(ap);
|
||||
|
||||
return first;
|
||||
|
||||
alloc_error:
|
||||
data_free(first);
|
||||
va_end(ap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void data_array_free(data_array_t *array) {
|
||||
array_element_release_fn release = dmt[array->type].array_element_release;
|
||||
if (release) {
|
||||
int element_size = dmt[array->type].array_element_size;
|
||||
for (int i = 0; i < array->num_values; ++i)
|
||||
release(*(void**)(array->values + element_size * i));
|
||||
}
|
||||
free(array->values);
|
||||
free(array);
|
||||
}
|
||||
|
||||
void data_free(data_t *data) {
|
||||
while (data) {
|
||||
data_t *prev_data = data;
|
||||
if (dmt[data->type].value_release)
|
||||
dmt[data->type].value_release(data->value);
|
||||
free(data->pretty_key);
|
||||
free(data->key);
|
||||
data = data->next;
|
||||
free(prev_data);
|
||||
}
|
||||
}
|
||||
|
||||
void data_print(data_t* data, FILE *file, data_printer_context_t *printer_ctx)
|
||||
{
|
||||
printer_ctx->printer->print_data(printer_ctx, data, file);
|
||||
}
|
||||
|
||||
void data_print_json(data_t* data, FILE* file) {
|
||||
data_printer_context_t ctx = {
|
||||
printer : &json_printer
|
||||
};
|
||||
ctx.printer->print_data(&ctx, data, file);
|
||||
fputc('\n', file);
|
||||
}
|
||||
|
||||
/* JSON printer */
|
||||
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *array, FILE *file);
|
||||
|
||||
static void print_value(data_printer_context_t *printer_ctx, FILE *file, data_type_t type, void *value) {
|
||||
switch (type) {
|
||||
case DATA_COUNT : {
|
||||
assert(0);
|
||||
} break;
|
||||
case DATA_DATA : {
|
||||
printer_ctx->printer->print_data(printer_ctx, value, file);
|
||||
} break;
|
||||
case DATA_INT : {
|
||||
printer_ctx->printer->print_int(printer_ctx, *(int*) value, file);
|
||||
} break;
|
||||
case DATA_DOUBLE : {
|
||||
printer_ctx->printer->print_double(printer_ctx, *(double*) value, file);
|
||||
} break;
|
||||
case DATA_STRING : {
|
||||
printer_ctx->printer->print_string(printer_ctx, value, file);
|
||||
} break;
|
||||
case DATA_ARRAY : {
|
||||
printer_ctx->printer->print_array(printer_ctx, value, file);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *array, FILE *file) {
|
||||
int element_size = dmt[array->type].array_element_size;
|
||||
char buffer[element_size];
|
||||
fprintf(file, "[");
|
||||
for (int c = 0; c < array->num_values; ++c) {
|
||||
if (c)
|
||||
fprintf(file, ", ");
|
||||
if (!dmt[array->type].array_is_boxed) {
|
||||
memcpy(buffer, (void**)(array->values + element_size * c), element_size);
|
||||
print_value(printer_ctx, file, array->type, buffer);
|
||||
} else {
|
||||
print_value(printer_ctx, file, array->type, *(void**)(array->values + element_size * c));
|
||||
}
|
||||
}
|
||||
fprintf(file, "]");
|
||||
}
|
||||
|
||||
static void print_json_data(data_printer_context_t *printer_ctx, data_t *data, FILE *file)
|
||||
{
|
||||
_Bool prev = false;
|
||||
fputc('{', file);
|
||||
while (data) {
|
||||
if (prev)
|
||||
fprintf(file, ", ");
|
||||
print_json_string(printer_ctx, data->key, file);
|
||||
fprintf(file, " : ");
|
||||
print_value(printer_ctx, file, data->type, data->value);
|
||||
prev = true;
|
||||
data = data->next;
|
||||
}
|
||||
fputc('}', file);
|
||||
}
|
||||
|
||||
static void print_json_string(data_printer_context_t *printer_ctx, const char *str, FILE *file) {
|
||||
fprintf(file, "\"");
|
||||
while (*str) {
|
||||
if (*str == '"')
|
||||
fputc('\\', file);
|
||||
fputc(*str, file);
|
||||
++str;
|
||||
}
|
||||
fprintf(file, "\"");
|
||||
}
|
||||
|
||||
static void print_json_double(data_printer_context_t *printer_ctx, double data, FILE *file)
|
||||
{
|
||||
fprintf(file, "%f", data);
|
||||
}
|
||||
|
||||
static void print_json_int(data_printer_context_t *printer_ctx, int data, FILE *file)
|
||||
{
|
||||
fprintf(file, "%d", data);
|
||||
}
|
||||
3
tests/CMakeLists.txt
Normal file
3
tests/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_executable(data-test data-test.c)
|
||||
|
||||
target_link_libraries(data-test data)
|
||||
40
tests/data-test.c
Normal file
40
tests/data-test.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* A general structure for extracting hierarchical data from the devices;
|
||||
* typically key-value pairs, but allows for more rich data as well
|
||||
*
|
||||
* Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "data.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
data_t *data = data_make("label" , "", DATA_STRING, "1.2.3",
|
||||
"house_code" , "House Code", DATA_INT, 42,
|
||||
"temp" , "Temperature", DATA_DOUBLE, 99.9,
|
||||
"array" , "Array", DATA_ARRAY, data_array(2, DATA_STRING, (char*[2]){"hello", "world"}),
|
||||
"array2" , "Array 2", DATA_ARRAY, data_array(2, DATA_INT, (int[2]){4, 2}),
|
||||
"array3" , "Array 3", DATA_ARRAY, data_array(2, DATA_ARRAY, (data_array_t*[2]){
|
||||
data_array(2, DATA_INT, (int[2]){4, 2}),
|
||||
data_array(2, DATA_INT, (int[2]){5, 5}) }),
|
||||
"data" , "Data", DATA_DATA, data_make("Hello", "hello", DATA_STRING, "world", NULL),
|
||||
NULL);
|
||||
data_print_json(data, stdout);
|
||||
fprintf(stdout, "\n");
|
||||
data_free(data);
|
||||
}
|
||||
Reference in New Issue
Block a user