Refactor file outputs from data
This commit is contained in:
parent
a26cffa81d
commit
305edc44f9
13 changed files with 838 additions and 784 deletions
|
@ -12,8 +12,8 @@
|
||||||
#ifndef INCLUDE_ABUF_H_
|
#ifndef INCLUDE_ABUF_H_
|
||||||
#define INCLUDE_ABUF_H_
|
#define INCLUDE_ABUF_H_
|
||||||
|
|
||||||
#if defined _MSC_VER // Microsoft Visual Studio
|
#if defined _MSC_VER || defined ESP32 // Microsoft Visual Studio or ESP32
|
||||||
// MSC has something like C99 restrict as __restrict
|
// MSC and ESP32 have something like C99 restrict as __restrict
|
||||||
#ifndef restrict
|
#ifndef restrict
|
||||||
#define restrict __restrict
|
#define restrict __restrict
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,14 +21,7 @@
|
||||||
#ifndef INCLUDE_DATA_H_
|
#ifndef INCLUDE_DATA_H_
|
||||||
#define INCLUDE_DATA_H_
|
#define INCLUDE_DATA_H_
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__)
|
|
||||||
// MSVC has something like C99 restrict as __restrict
|
|
||||||
#ifndef restrict
|
|
||||||
#define restrict __restrict
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DATA_DATA, /**< pointer to data is stored */
|
DATA_DATA, /**< pointer to data is stored */
|
||||||
|
@ -141,20 +134,6 @@ typedef struct data_output {
|
||||||
void (*output_free)(struct data_output *output);
|
void (*output_free)(struct data_output *output);
|
||||||
} data_output_t;
|
} data_output_t;
|
||||||
|
|
||||||
/** Construct data output for CSV printer.
|
|
||||||
|
|
||||||
@param file the output stream
|
|
||||||
@return The auxiliary data to pass along with data_csv_printer to data_print.
|
|
||||||
You must release this object with data_output_free once you're done with it.
|
|
||||||
*/
|
|
||||||
struct data_output *data_output_csv_create(FILE *file);
|
|
||||||
|
|
||||||
struct data_output *data_output_json_create(FILE *file);
|
|
||||||
|
|
||||||
struct data_output *data_output_kv_create(FILE *file);
|
|
||||||
|
|
||||||
struct data_output *data_output_syslog_create(const char *host, const char *port);
|
|
||||||
|
|
||||||
/** Setup known field keys and start output, used by CSV only.
|
/** Setup known field keys and start output, used by CSV only.
|
||||||
|
|
||||||
@param output the data_output handle from data_output_x_create
|
@param output the data_output handle from data_output_x_create
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#define INCLUDE_DECODER_H_
|
#define INCLUDE_DECODER_H_
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "r_device.h"
|
#include "r_device.h"
|
||||||
#include "bitbuffer.h"
|
#include "bitbuffer.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
|
|
|
@ -17,6 +17,12 @@
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "r_device.h"
|
#include "r_device.h"
|
||||||
|
|
||||||
|
#if defined _MSC_VER || defined ESP32 // Microsoft Visual Studio or ESP32
|
||||||
|
// MSC and ESP32 have something like C99 restrict as __restrict
|
||||||
|
#ifndef restrict
|
||||||
|
#define restrict __restrict
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
// Defined in newer <sal.h> for MSVC.
|
// Defined in newer <sal.h> for MSVC.
|
||||||
#ifndef _Printf_format_string_
|
#ifndef _Printf_format_string_
|
||||||
#define _Printf_format_string_
|
#define _Printf_format_string_
|
||||||
|
|
32
include/output_file.h
Normal file
32
include/output_file.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/** @file
|
||||||
|
File outputs for rtl_433 events.
|
||||||
|
|
||||||
|
Copyright (C) 2021 Christian Zuckschwerdt
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INCLUDE_OUTPUT_FILE_H_
|
||||||
|
#define INCLUDE_OUTPUT_FILE_H_
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/** Construct data output for CSV printer.
|
||||||
|
|
||||||
|
@param file the output stream
|
||||||
|
@return The auxiliary data to pass along with data_csv_printer to data_print.
|
||||||
|
You must release this object with data_output_free once you're done with it.
|
||||||
|
*/
|
||||||
|
struct data_output *data_output_csv_create(FILE *file);
|
||||||
|
|
||||||
|
struct data_output *data_output_json_create(FILE *file);
|
||||||
|
|
||||||
|
struct data_output *data_output_kv_create(FILE *file);
|
||||||
|
|
||||||
|
struct data_output *data_output_syslog_create(const char *host, const char *port);
|
||||||
|
|
||||||
|
#endif /* INCLUDE_OUTPUT_FILE_H_ */
|
|
@ -22,6 +22,7 @@ add_library(r_433 STATIC
|
||||||
list.c
|
list.c
|
||||||
mongoose.c
|
mongoose.c
|
||||||
optparse.c
|
optparse.c
|
||||||
|
output_file.c
|
||||||
output_influx.c
|
output_influx.c
|
||||||
output_mqtt.c
|
output_mqtt.c
|
||||||
pulse_analyzer.c
|
pulse_analyzer.c
|
||||||
|
|
767
src/data.c
767
src/data.c
|
@ -10,66 +10,21 @@
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
#include "fatal.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <limits.h>
|
|
||||||
// gethostname() needs _XOPEN_SOURCE 500 on unistd.h
|
|
||||||
#ifndef _XOPEN_SOURCE
|
|
||||||
#define _XOPEN_SOURCE 500
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
// Macro to prevent unused variables (passed into a function)
|
||||||
#include <unistd.h>
|
// from generating a warning.
|
||||||
#endif
|
#define UNUSED(x) (void)(x)
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
|
|
||||||
#undef _WIN32_WINNT
|
|
||||||
#define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#else
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
|
|
||||||
#define SOCKET int
|
|
||||||
#define INVALID_SOCKET (-1)
|
|
||||||
#define closesocket(x) close(x)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "term_ctl.h"
|
|
||||||
#include "abuf.h"
|
|
||||||
#include "fatal.h"
|
|
||||||
#include "r_util.h"
|
|
||||||
|
|
||||||
#include "data.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define _POSIX_HOST_NAME_MAX 128
|
|
||||||
#define perror(str) ws2_perror(str)
|
|
||||||
|
|
||||||
static void ws2_perror (const char *str)
|
|
||||||
{
|
|
||||||
if (str && *str)
|
|
||||||
fprintf(stderr, "%s: ", str);
|
|
||||||
fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <tcpip_adapter.h>
|
|
||||||
#define _POSIX_HOST_NAME_MAX 128
|
|
||||||
#define gai_strerror strerror
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef void* (*array_elementwise_import_fn)(void*);
|
typedef void* (*array_elementwise_import_fn)(void*);
|
||||||
typedef void (*array_element_release_fn)(void*);
|
typedef void (*array_element_release_fn)(void*);
|
||||||
|
@ -456,548 +411,6 @@ void print_array_value(data_output_t *output, data_array_t *array, char const *f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* JSON printer */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
struct data_output output;
|
|
||||||
FILE *file;
|
|
||||||
} data_output_json_t;
|
|
||||||
|
|
||||||
static void print_json_array(data_output_t *output, data_array_t *array, char const *format)
|
|
||||||
{
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
fprintf(json->file, "[");
|
|
||||||
for (int c = 0; c < array->num_values; ++c) {
|
|
||||||
if (c)
|
|
||||||
fprintf(json->file, ", ");
|
|
||||||
print_array_value(output, array, format, c);
|
|
||||||
}
|
|
||||||
fprintf(json->file, "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_json_data(data_output_t *output, data_t *data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
bool separator = false;
|
|
||||||
fputc('{', json->file);
|
|
||||||
while (data) {
|
|
||||||
if (separator)
|
|
||||||
fprintf(json->file, ", ");
|
|
||||||
output->print_string(output, data->key, NULL);
|
|
||||||
fprintf(json->file, " : ");
|
|
||||||
print_value(output, data->type, data->value, data->format);
|
|
||||||
separator = true;
|
|
||||||
data = data->next;
|
|
||||||
}
|
|
||||||
fputc('}', json->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_json_string(data_output_t *output, const char *str, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
size_t str_len = strlen(str);
|
|
||||||
if (str[0] == '{' && str[str_len - 1] == '}') {
|
|
||||||
// Print embedded JSON object verbatim
|
|
||||||
fprintf(json->file, "%s", str);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(json->file, "\"");
|
|
||||||
while (*str) {
|
|
||||||
if (*str == '\r') {
|
|
||||||
fprintf(json->file, "\\r");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*str == '\n') {
|
|
||||||
fprintf(json->file, "\\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*str == '\t') {
|
|
||||||
fprintf(json->file, "\\t");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*str == '"' || *str == '\\')
|
|
||||||
fputc('\\', json->file);
|
|
||||||
fputc(*str, json->file);
|
|
||||||
++str;
|
|
||||||
}
|
|
||||||
fprintf(json->file, "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_json_double(data_output_t *output, double data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
fprintf(json->file, "%.3f", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_json_int(data_output_t *output, int data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
fprintf(json->file, "%d", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_json_flush(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_json_t *json = (data_output_json_t *)output;
|
|
||||||
|
|
||||||
if (json && json->file) {
|
|
||||||
fputc('\n', json->file);
|
|
||||||
fflush(json->file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void data_output_json_free(data_output_t *output)
|
|
||||||
{
|
|
||||||
if (!output)
|
|
||||||
return;
|
|
||||||
|
|
||||||
free(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct data_output *data_output_json_create(FILE *file)
|
|
||||||
{
|
|
||||||
data_output_json_t *json = calloc(1, sizeof(data_output_json_t));
|
|
||||||
if (!json) {
|
|
||||||
WARN_CALLOC("data_output_json_create()");
|
|
||||||
return NULL; // NOTE: returns NULL on alloc failure.
|
|
||||||
}
|
|
||||||
|
|
||||||
json->output.print_data = print_json_data;
|
|
||||||
json->output.print_array = print_json_array;
|
|
||||||
json->output.print_string = print_json_string;
|
|
||||||
json->output.print_double = print_json_double;
|
|
||||||
json->output.print_int = print_json_int;
|
|
||||||
json->output.output_flush = print_json_flush;
|
|
||||||
json->output.output_free = data_output_json_free;
|
|
||||||
json->file = file;
|
|
||||||
|
|
||||||
return &json->output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pretty Key-Value printer */
|
|
||||||
|
|
||||||
static int kv_color_for_key(char const *key)
|
|
||||||
{
|
|
||||||
if (!key || !*key)
|
|
||||||
return TERM_COLOR_RESET;
|
|
||||||
if (!strcmp(key, "tag") || !strcmp(key, "time"))
|
|
||||||
return TERM_COLOR_BLUE;
|
|
||||||
if (!strcmp(key, "model") || !strcmp(key, "type") || !strcmp(key, "id"))
|
|
||||||
return TERM_COLOR_RED;
|
|
||||||
if (!strcmp(key, "mic"))
|
|
||||||
return TERM_COLOR_CYAN;
|
|
||||||
if (!strcmp(key, "mod") || !strcmp(key, "freq") || !strcmp(key, "freq1") || !strcmp(key, "freq2"))
|
|
||||||
return TERM_COLOR_MAGENTA;
|
|
||||||
if (!strcmp(key, "rssi") || !strcmp(key, "snr") || !strcmp(key, "noise"))
|
|
||||||
return TERM_COLOR_YELLOW;
|
|
||||||
return TERM_COLOR_GREEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int kv_break_before_key(char const *key)
|
|
||||||
{
|
|
||||||
if (!key || !*key)
|
|
||||||
return 0;
|
|
||||||
if (!strcmp(key, "model") || !strcmp(key, "mod") || !strcmp(key, "rssi") || !strcmp(key, "codes"))
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int kv_break_after_key(char const *key)
|
|
||||||
{
|
|
||||||
if (!key || !*key)
|
|
||||||
return 0;
|
|
||||||
if (!strcmp(key, "id") || !strcmp(key, "mic"))
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
struct data_output output;
|
|
||||||
FILE *file;
|
|
||||||
void *term;
|
|
||||||
int color;
|
|
||||||
int ring_bell;
|
|
||||||
int term_width;
|
|
||||||
int data_recursion;
|
|
||||||
int column;
|
|
||||||
} data_output_kv_t;
|
|
||||||
|
|
||||||
#define KV_SEP "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ "
|
|
||||||
|
|
||||||
static void print_kv_data(data_output_t *output, data_t *data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
int color = kv->color;
|
|
||||||
int ring_bell = kv->ring_bell;
|
|
||||||
|
|
||||||
// top-level: update width and print separator
|
|
||||||
if (!kv->data_recursion) {
|
|
||||||
kv->term_width = term_get_columns(kv->term); // update current term width
|
|
||||||
if (color)
|
|
||||||
term_set_fg(kv->term, TERM_COLOR_BLACK);
|
|
||||||
if (ring_bell)
|
|
||||||
term_ring_bell(kv->term);
|
|
||||||
char sep[] = KV_SEP KV_SEP KV_SEP KV_SEP;
|
|
||||||
if (kv->term_width < (int)sizeof(sep))
|
|
||||||
sep[kv->term_width > 0 ? kv->term_width - 1 : 40] = '\0';
|
|
||||||
fprintf(kv->file, "%s\n", sep);
|
|
||||||
if (color)
|
|
||||||
term_set_fg(kv->term, TERM_COLOR_RESET);
|
|
||||||
}
|
|
||||||
// nested data object: break before
|
|
||||||
else {
|
|
||||||
if (color)
|
|
||||||
term_set_fg(kv->term, TERM_COLOR_RESET);
|
|
||||||
fprintf(kv->file, "\n");
|
|
||||||
kv->column = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
++kv->data_recursion;
|
|
||||||
while (data) {
|
|
||||||
// break before some known keys
|
|
||||||
if (kv->column > 0 && kv_break_before_key(data->key)) {
|
|
||||||
fprintf(kv->file, "\n");
|
|
||||||
kv->column = 0;
|
|
||||||
}
|
|
||||||
// break if not enough width left
|
|
||||||
else if (kv->column >= kv->term_width - 26) {
|
|
||||||
fprintf(kv->file, "\n");
|
|
||||||
kv->column = 0;
|
|
||||||
}
|
|
||||||
// pad to next alignment if there is enough width left
|
|
||||||
else if (kv->column > 0 && kv->column < kv->term_width - 26) {
|
|
||||||
kv->column += fprintf(kv->file, "%*s", 25 - kv->column % 26, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// print key
|
|
||||||
char *key = *data->pretty_key ? data->pretty_key : data->key;
|
|
||||||
kv->column += fprintf(kv->file, "%-10s: ", key);
|
|
||||||
// print value
|
|
||||||
if (color)
|
|
||||||
term_set_fg(kv->term, kv_color_for_key(data->key));
|
|
||||||
print_value(output, data->type, data->value, data->format);
|
|
||||||
if (color)
|
|
||||||
term_set_fg(kv->term, TERM_COLOR_RESET);
|
|
||||||
|
|
||||||
// force break after some known keys
|
|
||||||
if (kv->column > 0 && kv_break_after_key(data->key)) {
|
|
||||||
kv->column = kv->term_width; // force break;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = data->next;
|
|
||||||
}
|
|
||||||
--kv->data_recursion;
|
|
||||||
|
|
||||||
// top-level: always end with newline
|
|
||||||
if (!kv->data_recursion && kv->column > 0) {
|
|
||||||
//fprintf(kv->file, "\n"); // data_output_print() already adds a newline
|
|
||||||
kv->column = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_kv_array(data_output_t *output, data_array_t *array, char const *format)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
//fprintf(kv->file, "[ ");
|
|
||||||
for (int c = 0; c < array->num_values; ++c) {
|
|
||||||
if (c)
|
|
||||||
fprintf(kv->file, ", ");
|
|
||||||
print_array_value(output, array, format, c);
|
|
||||||
}
|
|
||||||
//fprintf(kv->file, " ]");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_kv_double(data_output_t *output, double data, char const *format)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
kv->column += fprintf(kv->file, format ? format : "%.3f", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_kv_int(data_output_t *output, int data, char const *format)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
kv->column += fprintf(kv->file, format ? format : "%d", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_kv_string(data_output_t *output, const char *data, char const *format)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
kv->column += fprintf(kv->file, format ? format : "%s", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_kv_flush(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
if (kv && kv->file) {
|
|
||||||
fputc('\n', kv->file);
|
|
||||||
fflush(kv->file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void data_output_kv_free(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = (data_output_kv_t *)output;
|
|
||||||
|
|
||||||
if (!output)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (kv->color)
|
|
||||||
term_free(kv->term);
|
|
||||||
|
|
||||||
free(output);
|
|
||||||
}
|
|
||||||
struct data_output *data_output_kv_create(FILE *file)
|
|
||||||
{
|
|
||||||
data_output_kv_t *kv = calloc(1, sizeof(data_output_kv_t));
|
|
||||||
if (!kv) {
|
|
||||||
WARN_CALLOC("data_output_kv_create()");
|
|
||||||
return NULL; // NOTE: returns NULL on alloc failure.
|
|
||||||
}
|
|
||||||
|
|
||||||
kv->output.print_data = print_kv_data;
|
|
||||||
kv->output.print_array = print_kv_array;
|
|
||||||
kv->output.print_string = print_kv_string;
|
|
||||||
kv->output.print_double = print_kv_double;
|
|
||||||
kv->output.print_int = print_kv_int;
|
|
||||||
kv->output.output_flush = print_kv_flush;
|
|
||||||
kv->output.output_free = data_output_kv_free;
|
|
||||||
kv->file = file;
|
|
||||||
|
|
||||||
kv->term = term_init(file);
|
|
||||||
kv->color = term_has_color(kv->term);
|
|
||||||
|
|
||||||
kv->ring_bell = 0; // TODO: enable if requested...
|
|
||||||
|
|
||||||
return &kv->output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSV printer; doesn't really support recursive data objects yet */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
struct data_output output;
|
|
||||||
FILE *file;
|
|
||||||
const char **fields;
|
|
||||||
int data_recursion;
|
|
||||||
const char *separator;
|
|
||||||
} data_output_csv_t;
|
|
||||||
|
|
||||||
static void print_csv_data(data_output_t *output, data_t *data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
const char **fields = csv->fields;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (csv->data_recursion)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int regular = 0; // skip "states" output
|
|
||||||
for (data_t *d = data; d; d = d->next) {
|
|
||||||
if (!strcmp(d->key, "msg") || !strcmp(d->key, "codes") || !strcmp(d->key, "model")) {
|
|
||||||
regular = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!regular)
|
|
||||||
return;
|
|
||||||
|
|
||||||
++csv->data_recursion;
|
|
||||||
for (i = 0; fields[i]; ++i) {
|
|
||||||
const char *key = fields[i];
|
|
||||||
data_t *found = NULL;
|
|
||||||
if (i)
|
|
||||||
fprintf(csv->file, "%s", csv->separator);
|
|
||||||
for (data_t *iter = data; !found && iter; iter = iter->next)
|
|
||||||
if (strcmp(iter->key, key) == 0)
|
|
||||||
found = iter;
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
print_value(output, found->type, found->value, found->format);
|
|
||||||
}
|
|
||||||
--csv->data_recursion;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_csv_array(data_output_t *output, data_array_t *array, char const *format)
|
|
||||||
{
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
for (int c = 0; c < array->num_values; ++c) {
|
|
||||||
if (c)
|
|
||||||
fprintf(csv->file, ";");
|
|
||||||
print_array_value(output, array, format, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_csv_string(data_output_t *output, const char *str, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
while (*str) {
|
|
||||||
if (strncmp(str, csv->separator, strlen(csv->separator)) == 0)
|
|
||||||
fputc('\\', csv->file);
|
|
||||||
fputc(*str, csv->file);
|
|
||||||
++str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compare_strings(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
return strcmp(*(char **)a, *(char **)b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void data_output_csv_start(struct data_output *output, char const *const *fields, int num_fields)
|
|
||||||
{
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
int csv_fields = 0;
|
|
||||||
int i, j;
|
|
||||||
const char **allowed = NULL;
|
|
||||||
int *use_count = NULL;
|
|
||||||
int num_unique_fields;
|
|
||||||
if (!csv)
|
|
||||||
goto alloc_error;
|
|
||||||
|
|
||||||
csv->separator = ",";
|
|
||||||
|
|
||||||
allowed = calloc(num_fields, sizeof(const char *));
|
|
||||||
if (!allowed) {
|
|
||||||
WARN_CALLOC("data_output_csv_start()");
|
|
||||||
goto alloc_error;
|
|
||||||
}
|
|
||||||
memcpy((void *)allowed, fields, sizeof(const char *) * num_fields);
|
|
||||||
|
|
||||||
qsort((void *)allowed, num_fields, sizeof(char *), compare_strings);
|
|
||||||
|
|
||||||
// overwrite duplicates
|
|
||||||
i = 0;
|
|
||||||
j = 0;
|
|
||||||
while (j < num_fields) {
|
|
||||||
while (j > 0 && j < num_fields &&
|
|
||||||
strcmp(allowed[j - 1], allowed[j]) == 0)
|
|
||||||
++j;
|
|
||||||
|
|
||||||
if (j < num_fields) {
|
|
||||||
allowed[i] = allowed[j];
|
|
||||||
++i;
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
num_unique_fields = i;
|
|
||||||
|
|
||||||
csv->fields = calloc(num_unique_fields + 1, sizeof(const char *));
|
|
||||||
if (!csv->fields) {
|
|
||||||
WARN_CALLOC("data_output_csv_start()");
|
|
||||||
goto alloc_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
use_count = calloc(num_unique_fields + 1, sizeof(*use_count)); // '+ 1' so we never alloc size 0
|
|
||||||
if (!use_count) {
|
|
||||||
WARN_CALLOC("data_output_csv_start()");
|
|
||||||
goto alloc_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < num_fields; ++i) {
|
|
||||||
const char **field = bsearch(&fields[i], allowed, num_unique_fields, sizeof(const char *),
|
|
||||||
compare_strings);
|
|
||||||
int *field_use_count = use_count + (field - allowed);
|
|
||||||
if (field && !*field_use_count) {
|
|
||||||
csv->fields[csv_fields] = fields[i];
|
|
||||||
++csv_fields;
|
|
||||||
++*field_use_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
csv->fields[csv_fields] = NULL;
|
|
||||||
free((void *)allowed);
|
|
||||||
free(use_count);
|
|
||||||
|
|
||||||
// Output the CSV header
|
|
||||||
for (i = 0; csv->fields[i]; ++i) {
|
|
||||||
fprintf(csv->file, "%s%s", i > 0 ? csv->separator : "", csv->fields[i]);
|
|
||||||
}
|
|
||||||
fprintf(csv->file, "\n");
|
|
||||||
return;
|
|
||||||
|
|
||||||
alloc_error:
|
|
||||||
free(use_count);
|
|
||||||
free((void *)allowed);
|
|
||||||
if (csv)
|
|
||||||
free((void *)csv->fields);
|
|
||||||
free(csv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_csv_double(data_output_t *output, double data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
fprintf(csv->file, "%.3f", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_csv_int(data_output_t *output, int data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
fprintf(csv->file, "%d", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_csv_flush(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
if (csv && csv->file) {
|
|
||||||
fputc('\n', csv->file);
|
|
||||||
fflush(csv->file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void data_output_csv_free(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
||||||
|
|
||||||
free((void *)csv->fields);
|
|
||||||
free(csv);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct data_output *data_output_csv_create(FILE *file)
|
|
||||||
{
|
|
||||||
data_output_csv_t *csv = calloc(1, sizeof(data_output_csv_t));
|
|
||||||
if (!csv) {
|
|
||||||
WARN_CALLOC("data_output_csv_create()");
|
|
||||||
return NULL; // NOTE: returns NULL on alloc failure.
|
|
||||||
}
|
|
||||||
|
|
||||||
csv->output.print_data = print_csv_data;
|
|
||||||
csv->output.print_array = print_csv_array;
|
|
||||||
csv->output.print_string = print_csv_string;
|
|
||||||
csv->output.print_double = print_csv_double;
|
|
||||||
csv->output.print_int = print_csv_int;
|
|
||||||
csv->output.output_start = data_output_csv_start;
|
|
||||||
csv->output.output_flush = print_csv_flush;
|
|
||||||
csv->output.output_free = data_output_csv_free;
|
|
||||||
csv->file = file;
|
|
||||||
|
|
||||||
return &csv->output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JSON string printer */
|
/* JSON string printer */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -1141,167 +554,3 @@ size_t data_print_jsons(data_t *data, char *dst, size_t len)
|
||||||
|
|
||||||
return len - jsons.msg.left;
|
return len - jsons.msg.left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Datagram (UDP) client */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
struct sockaddr_storage addr;
|
|
||||||
socklen_t addr_len;
|
|
||||||
SOCKET sock;
|
|
||||||
} datagram_client_t;
|
|
||||||
|
|
||||||
static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
|
|
||||||
{
|
|
||||||
if (!host || !port)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
struct addrinfo hints, *res, *res0;
|
|
||||||
int error;
|
|
||||||
SOCKET sock;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
|
||||||
hints.ai_family = PF_UNSPEC;
|
|
||||||
hints.ai_socktype = SOCK_DGRAM;
|
|
||||||
hints.ai_flags = AI_ADDRCONFIG;
|
|
||||||
error = getaddrinfo(host, port, &hints, &res0);
|
|
||||||
if (error) {
|
|
||||||
fprintf(stderr, "%s\n", gai_strerror(error));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
sock = INVALID_SOCKET;
|
|
||||||
for (res = res0; res; res = res->ai_next) {
|
|
||||||
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
||||||
if (sock >= 0) {
|
|
||||||
client->sock = sock;
|
|
||||||
memset(&client->addr, 0, sizeof(client->addr));
|
|
||||||
memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
|
|
||||||
client->addr_len = res->ai_addrlen;
|
|
||||||
break; // success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
freeaddrinfo(res0);
|
|
||||||
if (sock == INVALID_SOCKET) {
|
|
||||||
perror("socket");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//int broadcast = 1;
|
|
||||||
//int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void datagram_client_close(datagram_client_t *client)
|
|
||||||
{
|
|
||||||
if (!client)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (client->sock != INVALID_SOCKET) {
|
|
||||||
closesocket(client->sock);
|
|
||||||
client->sock = INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
|
|
||||||
{
|
|
||||||
int r = sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
|
|
||||||
if (r == -1) {
|
|
||||||
perror("sendto");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
struct data_output output;
|
|
||||||
datagram_client_t client;
|
|
||||||
int pri;
|
|
||||||
char hostname[_POSIX_HOST_NAME_MAX + 1];
|
|
||||||
} data_output_syslog_t;
|
|
||||||
|
|
||||||
static void print_syslog_data(data_output_t *output, data_t *data, char const *format)
|
|
||||||
{
|
|
||||||
UNUSED(format);
|
|
||||||
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
||||||
|
|
||||||
// we expect a normal message around 500 bytes
|
|
||||||
// full stats report would be 12k and we want a max of MTU anyway
|
|
||||||
char message[1024];
|
|
||||||
abuf_t msg = {0};
|
|
||||||
abuf_init(&msg, message, sizeof(message));
|
|
||||||
|
|
||||||
time_t now;
|
|
||||||
struct tm tm_info;
|
|
||||||
time(&now);
|
|
||||||
#ifdef _WIN32
|
|
||||||
gmtime_s(&tm_info, &now);
|
|
||||||
#else
|
|
||||||
gmtime_r(&now, &tm_info);
|
|
||||||
#endif
|
|
||||||
char timestamp[21];
|
|
||||||
strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
|
|
||||||
|
|
||||||
abuf_printf(&msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
|
|
||||||
|
|
||||||
msg.tail += data_print_jsons(data, msg.tail, msg.left);
|
|
||||||
if (msg.tail >= msg.head + sizeof(message))
|
|
||||||
return; // abort on overflow, we don't actually want to send more than fits the MTU
|
|
||||||
|
|
||||||
size_t abuf_len = msg.tail - msg.head;
|
|
||||||
datagram_client_send(&syslog->client, message, abuf_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void data_output_syslog_free(data_output_t *output)
|
|
||||||
{
|
|
||||||
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
||||||
|
|
||||||
if (!syslog)
|
|
||||||
return;
|
|
||||||
|
|
||||||
datagram_client_close(&syslog->client);
|
|
||||||
|
|
||||||
free(syslog);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct data_output *data_output_syslog_create(const char *host, const char *port)
|
|
||||||
{
|
|
||||||
data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
|
|
||||||
if (!syslog) {
|
|
||||||
WARN_CALLOC("data_output_syslog_create()");
|
|
||||||
return NULL; // NOTE: returns NULL on alloc failure.
|
|
||||||
}
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wsa;
|
|
||||||
|
|
||||||
if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
|
|
||||||
perror("WSAStartup()");
|
|
||||||
free(syslog);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
syslog->output.print_data = print_syslog_data;
|
|
||||||
syslog->output.output_free = data_output_syslog_free;
|
|
||||||
// Severity 5 "Notice", Facility 20 "local use 4"
|
|
||||||
syslog->pri = 20 * 8 + 5;
|
|
||||||
#ifdef ESP32
|
|
||||||
const char* adapter_hostname = NULL;
|
|
||||||
tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);
|
|
||||||
if (adapter_hostname) {
|
|
||||||
memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
syslog->hostname[0] = '\0';
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
|
|
||||||
#endif
|
|
||||||
syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
|
|
||||||
datagram_client_open(&syslog->client, host, port);
|
|
||||||
|
|
||||||
return &syslog->output;
|
|
||||||
}
|
|
||||||
|
|
776
src/output_file.c
Normal file
776
src/output_file.c
Normal file
|
@ -0,0 +1,776 @@
|
||||||
|
/** @file
|
||||||
|
File outputs for rtl_433 events.
|
||||||
|
|
||||||
|
Copyright (C) 2021 Christian Zuckschwerdt
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "output_file.h"
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
#include "term_ctl.h"
|
||||||
|
#include "abuf.h"
|
||||||
|
#include "r_util.h"
|
||||||
|
#include "fatal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <limits.h>
|
||||||
|
// gethostname() needs _XOPEN_SOURCE 500 on unistd.h
|
||||||
|
#ifndef _XOPEN_SOURCE
|
||||||
|
#define _XOPEN_SOURCE 500
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#define SOCKET int
|
||||||
|
#define INVALID_SOCKET (-1)
|
||||||
|
#define closesocket(x) close(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define _POSIX_HOST_NAME_MAX 128
|
||||||
|
#define perror(str) ws2_perror(str)
|
||||||
|
|
||||||
|
static void ws2_perror (const char *str)
|
||||||
|
{
|
||||||
|
if (str && *str)
|
||||||
|
fprintf(stderr, "%s: ", str);
|
||||||
|
fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <tcpip_adapter.h>
|
||||||
|
#define _POSIX_HOST_NAME_MAX 128
|
||||||
|
#define gai_strerror strerror
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* JSON printer */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct data_output output;
|
||||||
|
FILE *file;
|
||||||
|
} data_output_json_t;
|
||||||
|
|
||||||
|
static void print_json_array(data_output_t *output, data_array_t *array, char const *format)
|
||||||
|
{
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
fprintf(json->file, "[");
|
||||||
|
for (int c = 0; c < array->num_values; ++c) {
|
||||||
|
if (c)
|
||||||
|
fprintf(json->file, ", ");
|
||||||
|
print_array_value(output, array, format, c);
|
||||||
|
}
|
||||||
|
fprintf(json->file, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_json_data(data_output_t *output, data_t *data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
bool separator = false;
|
||||||
|
fputc('{', json->file);
|
||||||
|
while (data) {
|
||||||
|
if (separator)
|
||||||
|
fprintf(json->file, ", ");
|
||||||
|
output->print_string(output, data->key, NULL);
|
||||||
|
fprintf(json->file, " : ");
|
||||||
|
print_value(output, data->type, data->value, data->format);
|
||||||
|
separator = true;
|
||||||
|
data = data->next;
|
||||||
|
}
|
||||||
|
fputc('}', json->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_json_string(data_output_t *output, const char *str, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
size_t str_len = strlen(str);
|
||||||
|
if (str[0] == '{' && str[str_len - 1] == '}') {
|
||||||
|
// Print embedded JSON object verbatim
|
||||||
|
fprintf(json->file, "%s", str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(json->file, "\"");
|
||||||
|
while (*str) {
|
||||||
|
if (*str == '\r') {
|
||||||
|
fprintf(json->file, "\\r");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*str == '\n') {
|
||||||
|
fprintf(json->file, "\\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*str == '\t') {
|
||||||
|
fprintf(json->file, "\\t");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*str == '"' || *str == '\\')
|
||||||
|
fputc('\\', json->file);
|
||||||
|
fputc(*str, json->file);
|
||||||
|
++str;
|
||||||
|
}
|
||||||
|
fprintf(json->file, "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_json_double(data_output_t *output, double data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
fprintf(json->file, "%.3f", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_json_int(data_output_t *output, int data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
fprintf(json->file, "%d", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_json_flush(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_json_t *json = (data_output_json_t *)output;
|
||||||
|
|
||||||
|
if (json && json->file) {
|
||||||
|
fputc('\n', json->file);
|
||||||
|
fflush(json->file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_output_json_free(data_output_t *output)
|
||||||
|
{
|
||||||
|
if (!output)
|
||||||
|
return;
|
||||||
|
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct data_output *data_output_json_create(FILE *file)
|
||||||
|
{
|
||||||
|
data_output_json_t *json = calloc(1, sizeof(data_output_json_t));
|
||||||
|
if (!json) {
|
||||||
|
WARN_CALLOC("data_output_json_create()");
|
||||||
|
return NULL; // NOTE: returns NULL on alloc failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
json->output.print_data = print_json_data;
|
||||||
|
json->output.print_array = print_json_array;
|
||||||
|
json->output.print_string = print_json_string;
|
||||||
|
json->output.print_double = print_json_double;
|
||||||
|
json->output.print_int = print_json_int;
|
||||||
|
json->output.output_flush = print_json_flush;
|
||||||
|
json->output.output_free = data_output_json_free;
|
||||||
|
json->file = file;
|
||||||
|
|
||||||
|
return &json->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pretty Key-Value printer */
|
||||||
|
|
||||||
|
static int kv_color_for_key(char const *key)
|
||||||
|
{
|
||||||
|
if (!key || !*key)
|
||||||
|
return TERM_COLOR_RESET;
|
||||||
|
if (!strcmp(key, "tag") || !strcmp(key, "time"))
|
||||||
|
return TERM_COLOR_BLUE;
|
||||||
|
if (!strcmp(key, "model") || !strcmp(key, "type") || !strcmp(key, "id"))
|
||||||
|
return TERM_COLOR_RED;
|
||||||
|
if (!strcmp(key, "mic"))
|
||||||
|
return TERM_COLOR_CYAN;
|
||||||
|
if (!strcmp(key, "mod") || !strcmp(key, "freq") || !strcmp(key, "freq1") || !strcmp(key, "freq2"))
|
||||||
|
return TERM_COLOR_MAGENTA;
|
||||||
|
if (!strcmp(key, "rssi") || !strcmp(key, "snr") || !strcmp(key, "noise"))
|
||||||
|
return TERM_COLOR_YELLOW;
|
||||||
|
return TERM_COLOR_GREEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kv_break_before_key(char const *key)
|
||||||
|
{
|
||||||
|
if (!key || !*key)
|
||||||
|
return 0;
|
||||||
|
if (!strcmp(key, "model") || !strcmp(key, "mod") || !strcmp(key, "rssi") || !strcmp(key, "codes"))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kv_break_after_key(char const *key)
|
||||||
|
{
|
||||||
|
if (!key || !*key)
|
||||||
|
return 0;
|
||||||
|
if (!strcmp(key, "id") || !strcmp(key, "mic"))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct data_output output;
|
||||||
|
FILE *file;
|
||||||
|
void *term;
|
||||||
|
int color;
|
||||||
|
int ring_bell;
|
||||||
|
int term_width;
|
||||||
|
int data_recursion;
|
||||||
|
int column;
|
||||||
|
} data_output_kv_t;
|
||||||
|
|
||||||
|
#define KV_SEP "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ "
|
||||||
|
|
||||||
|
static void print_kv_data(data_output_t *output, data_t *data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
int color = kv->color;
|
||||||
|
int ring_bell = kv->ring_bell;
|
||||||
|
|
||||||
|
// top-level: update width and print separator
|
||||||
|
if (!kv->data_recursion) {
|
||||||
|
kv->term_width = term_get_columns(kv->term); // update current term width
|
||||||
|
if (color)
|
||||||
|
term_set_fg(kv->term, TERM_COLOR_BLACK);
|
||||||
|
if (ring_bell)
|
||||||
|
term_ring_bell(kv->term);
|
||||||
|
char sep[] = KV_SEP KV_SEP KV_SEP KV_SEP;
|
||||||
|
if (kv->term_width < (int)sizeof(sep))
|
||||||
|
sep[kv->term_width > 0 ? kv->term_width - 1 : 40] = '\0';
|
||||||
|
fprintf(kv->file, "%s\n", sep);
|
||||||
|
if (color)
|
||||||
|
term_set_fg(kv->term, TERM_COLOR_RESET);
|
||||||
|
}
|
||||||
|
// nested data object: break before
|
||||||
|
else {
|
||||||
|
if (color)
|
||||||
|
term_set_fg(kv->term, TERM_COLOR_RESET);
|
||||||
|
fprintf(kv->file, "\n");
|
||||||
|
kv->column = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
++kv->data_recursion;
|
||||||
|
while (data) {
|
||||||
|
// break before some known keys
|
||||||
|
if (kv->column > 0 && kv_break_before_key(data->key)) {
|
||||||
|
fprintf(kv->file, "\n");
|
||||||
|
kv->column = 0;
|
||||||
|
}
|
||||||
|
// break if not enough width left
|
||||||
|
else if (kv->column >= kv->term_width - 26) {
|
||||||
|
fprintf(kv->file, "\n");
|
||||||
|
kv->column = 0;
|
||||||
|
}
|
||||||
|
// pad to next alignment if there is enough width left
|
||||||
|
else if (kv->column > 0 && kv->column < kv->term_width - 26) {
|
||||||
|
kv->column += fprintf(kv->file, "%*s", 25 - kv->column % 26, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// print key
|
||||||
|
char *key = *data->pretty_key ? data->pretty_key : data->key;
|
||||||
|
kv->column += fprintf(kv->file, "%-10s: ", key);
|
||||||
|
// print value
|
||||||
|
if (color)
|
||||||
|
term_set_fg(kv->term, kv_color_for_key(data->key));
|
||||||
|
print_value(output, data->type, data->value, data->format);
|
||||||
|
if (color)
|
||||||
|
term_set_fg(kv->term, TERM_COLOR_RESET);
|
||||||
|
|
||||||
|
// force break after some known keys
|
||||||
|
if (kv->column > 0 && kv_break_after_key(data->key)) {
|
||||||
|
kv->column = kv->term_width; // force break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = data->next;
|
||||||
|
}
|
||||||
|
--kv->data_recursion;
|
||||||
|
|
||||||
|
// top-level: always end with newline
|
||||||
|
if (!kv->data_recursion && kv->column > 0) {
|
||||||
|
//fprintf(kv->file, "\n"); // data_output_print() already adds a newline
|
||||||
|
kv->column = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_kv_array(data_output_t *output, data_array_t *array, char const *format)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
//fprintf(kv->file, "[ ");
|
||||||
|
for (int c = 0; c < array->num_values; ++c) {
|
||||||
|
if (c)
|
||||||
|
fprintf(kv->file, ", ");
|
||||||
|
print_array_value(output, array, format, c);
|
||||||
|
}
|
||||||
|
//fprintf(kv->file, " ]");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_kv_double(data_output_t *output, double data, char const *format)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
kv->column += fprintf(kv->file, format ? format : "%.3f", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_kv_int(data_output_t *output, int data, char const *format)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
kv->column += fprintf(kv->file, format ? format : "%d", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_kv_string(data_output_t *output, const char *data, char const *format)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
kv->column += fprintf(kv->file, format ? format : "%s", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_kv_flush(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
if (kv && kv->file) {
|
||||||
|
fputc('\n', kv->file);
|
||||||
|
fflush(kv->file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_output_kv_free(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = (data_output_kv_t *)output;
|
||||||
|
|
||||||
|
if (!output)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (kv->color)
|
||||||
|
term_free(kv->term);
|
||||||
|
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
|
struct data_output *data_output_kv_create(FILE *file)
|
||||||
|
{
|
||||||
|
data_output_kv_t *kv = calloc(1, sizeof(data_output_kv_t));
|
||||||
|
if (!kv) {
|
||||||
|
WARN_CALLOC("data_output_kv_create()");
|
||||||
|
return NULL; // NOTE: returns NULL on alloc failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
kv->output.print_data = print_kv_data;
|
||||||
|
kv->output.print_array = print_kv_array;
|
||||||
|
kv->output.print_string = print_kv_string;
|
||||||
|
kv->output.print_double = print_kv_double;
|
||||||
|
kv->output.print_int = print_kv_int;
|
||||||
|
kv->output.output_flush = print_kv_flush;
|
||||||
|
kv->output.output_free = data_output_kv_free;
|
||||||
|
kv->file = file;
|
||||||
|
|
||||||
|
kv->term = term_init(file);
|
||||||
|
kv->color = term_has_color(kv->term);
|
||||||
|
|
||||||
|
kv->ring_bell = 0; // TODO: enable if requested...
|
||||||
|
|
||||||
|
return &kv->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CSV printer; doesn't really support recursive data objects yet */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct data_output output;
|
||||||
|
FILE *file;
|
||||||
|
const char **fields;
|
||||||
|
int data_recursion;
|
||||||
|
const char *separator;
|
||||||
|
} data_output_csv_t;
|
||||||
|
|
||||||
|
static void print_csv_data(data_output_t *output, data_t *data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
const char **fields = csv->fields;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (csv->data_recursion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int regular = 0; // skip "states" output
|
||||||
|
for (data_t *d = data; d; d = d->next) {
|
||||||
|
if (!strcmp(d->key, "msg") || !strcmp(d->key, "codes") || !strcmp(d->key, "model")) {
|
||||||
|
regular = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!regular)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++csv->data_recursion;
|
||||||
|
for (i = 0; fields[i]; ++i) {
|
||||||
|
const char *key = fields[i];
|
||||||
|
data_t *found = NULL;
|
||||||
|
if (i)
|
||||||
|
fprintf(csv->file, "%s", csv->separator);
|
||||||
|
for (data_t *iter = data; !found && iter; iter = iter->next)
|
||||||
|
if (strcmp(iter->key, key) == 0)
|
||||||
|
found = iter;
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
print_value(output, found->type, found->value, found->format);
|
||||||
|
}
|
||||||
|
--csv->data_recursion;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_csv_array(data_output_t *output, data_array_t *array, char const *format)
|
||||||
|
{
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
for (int c = 0; c < array->num_values; ++c) {
|
||||||
|
if (c)
|
||||||
|
fprintf(csv->file, ";");
|
||||||
|
print_array_value(output, array, format, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_csv_string(data_output_t *output, const char *str, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
while (*str) {
|
||||||
|
if (strncmp(str, csv->separator, strlen(csv->separator)) == 0)
|
||||||
|
fputc('\\', csv->file);
|
||||||
|
fputc(*str, csv->file);
|
||||||
|
++str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare_strings(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return strcmp(*(char **)a, *(char **)b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_output_csv_start(struct data_output *output, char const *const *fields, int num_fields)
|
||||||
|
{
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
int csv_fields = 0;
|
||||||
|
int i, j;
|
||||||
|
const char **allowed = NULL;
|
||||||
|
int *use_count = NULL;
|
||||||
|
int num_unique_fields;
|
||||||
|
if (!csv)
|
||||||
|
goto alloc_error;
|
||||||
|
|
||||||
|
csv->separator = ",";
|
||||||
|
|
||||||
|
allowed = calloc(num_fields, sizeof(const char *));
|
||||||
|
if (!allowed) {
|
||||||
|
WARN_CALLOC("data_output_csv_start()");
|
||||||
|
goto alloc_error;
|
||||||
|
}
|
||||||
|
memcpy((void *)allowed, fields, sizeof(const char *) * num_fields);
|
||||||
|
|
||||||
|
qsort((void *)allowed, num_fields, sizeof(char *), compare_strings);
|
||||||
|
|
||||||
|
// overwrite duplicates
|
||||||
|
i = 0;
|
||||||
|
j = 0;
|
||||||
|
while (j < num_fields) {
|
||||||
|
while (j > 0 && j < num_fields &&
|
||||||
|
strcmp(allowed[j - 1], allowed[j]) == 0)
|
||||||
|
++j;
|
||||||
|
|
||||||
|
if (j < num_fields) {
|
||||||
|
allowed[i] = allowed[j];
|
||||||
|
++i;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
num_unique_fields = i;
|
||||||
|
|
||||||
|
csv->fields = calloc(num_unique_fields + 1, sizeof(const char *));
|
||||||
|
if (!csv->fields) {
|
||||||
|
WARN_CALLOC("data_output_csv_start()");
|
||||||
|
goto alloc_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
use_count = calloc(num_unique_fields + 1, sizeof(*use_count)); // '+ 1' so we never alloc size 0
|
||||||
|
if (!use_count) {
|
||||||
|
WARN_CALLOC("data_output_csv_start()");
|
||||||
|
goto alloc_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_fields; ++i) {
|
||||||
|
const char **field = bsearch(&fields[i], allowed, num_unique_fields, sizeof(const char *),
|
||||||
|
compare_strings);
|
||||||
|
int *field_use_count = use_count + (field - allowed);
|
||||||
|
if (field && !*field_use_count) {
|
||||||
|
csv->fields[csv_fields] = fields[i];
|
||||||
|
++csv_fields;
|
||||||
|
++*field_use_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
csv->fields[csv_fields] = NULL;
|
||||||
|
free((void *)allowed);
|
||||||
|
free(use_count);
|
||||||
|
|
||||||
|
// Output the CSV header
|
||||||
|
for (i = 0; csv->fields[i]; ++i) {
|
||||||
|
fprintf(csv->file, "%s%s", i > 0 ? csv->separator : "", csv->fields[i]);
|
||||||
|
}
|
||||||
|
fprintf(csv->file, "\n");
|
||||||
|
return;
|
||||||
|
|
||||||
|
alloc_error:
|
||||||
|
free(use_count);
|
||||||
|
free((void *)allowed);
|
||||||
|
if (csv)
|
||||||
|
free((void *)csv->fields);
|
||||||
|
free(csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_csv_double(data_output_t *output, double data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
fprintf(csv->file, "%.3f", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_csv_int(data_output_t *output, int data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
fprintf(csv->file, "%d", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_csv_flush(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
if (csv && csv->file) {
|
||||||
|
fputc('\n', csv->file);
|
||||||
|
fflush(csv->file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_output_csv_free(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
||||||
|
|
||||||
|
free((void *)csv->fields);
|
||||||
|
free(csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct data_output *data_output_csv_create(FILE *file)
|
||||||
|
{
|
||||||
|
data_output_csv_t *csv = calloc(1, sizeof(data_output_csv_t));
|
||||||
|
if (!csv) {
|
||||||
|
WARN_CALLOC("data_output_csv_create()");
|
||||||
|
return NULL; // NOTE: returns NULL on alloc failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
csv->output.print_data = print_csv_data;
|
||||||
|
csv->output.print_array = print_csv_array;
|
||||||
|
csv->output.print_string = print_csv_string;
|
||||||
|
csv->output.print_double = print_csv_double;
|
||||||
|
csv->output.print_int = print_csv_int;
|
||||||
|
csv->output.output_start = data_output_csv_start;
|
||||||
|
csv->output.output_flush = print_csv_flush;
|
||||||
|
csv->output.output_free = data_output_csv_free;
|
||||||
|
csv->file = file;
|
||||||
|
|
||||||
|
return &csv->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datagram (UDP) client */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t addr_len;
|
||||||
|
SOCKET sock;
|
||||||
|
} datagram_client_t;
|
||||||
|
|
||||||
|
static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
|
||||||
|
{
|
||||||
|
if (!host || !port)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
struct addrinfo hints, *res, *res0;
|
||||||
|
int error;
|
||||||
|
SOCKET sock;
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = PF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_flags = AI_ADDRCONFIG;
|
||||||
|
error = getaddrinfo(host, port, &hints, &res0);
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s\n", gai_strerror(error));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sock = INVALID_SOCKET;
|
||||||
|
for (res = res0; res; res = res->ai_next) {
|
||||||
|
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||||
|
if (sock >= 0) {
|
||||||
|
client->sock = sock;
|
||||||
|
memset(&client->addr, 0, sizeof(client->addr));
|
||||||
|
memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
|
||||||
|
client->addr_len = res->ai_addrlen;
|
||||||
|
break; // success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freeaddrinfo(res0);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
perror("socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//int broadcast = 1;
|
||||||
|
//int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void datagram_client_close(datagram_client_t *client)
|
||||||
|
{
|
||||||
|
if (!client)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (client->sock != INVALID_SOCKET) {
|
||||||
|
closesocket(client->sock);
|
||||||
|
client->sock = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
|
||||||
|
{
|
||||||
|
int r = sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
|
||||||
|
if (r == -1) {
|
||||||
|
perror("sendto");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct data_output output;
|
||||||
|
datagram_client_t client;
|
||||||
|
int pri;
|
||||||
|
char hostname[_POSIX_HOST_NAME_MAX + 1];
|
||||||
|
} data_output_syslog_t;
|
||||||
|
|
||||||
|
static void print_syslog_data(data_output_t *output, data_t *data, char const *format)
|
||||||
|
{
|
||||||
|
UNUSED(format);
|
||||||
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
||||||
|
|
||||||
|
// we expect a normal message around 500 bytes
|
||||||
|
// full stats report would be 12k and we want a max of MTU anyway
|
||||||
|
char message[1024];
|
||||||
|
abuf_t msg = {0};
|
||||||
|
abuf_init(&msg, message, sizeof(message));
|
||||||
|
|
||||||
|
time_t now;
|
||||||
|
struct tm tm_info;
|
||||||
|
time(&now);
|
||||||
|
#ifdef _WIN32
|
||||||
|
gmtime_s(&tm_info, &now);
|
||||||
|
#else
|
||||||
|
gmtime_r(&now, &tm_info);
|
||||||
|
#endif
|
||||||
|
char timestamp[21];
|
||||||
|
strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
|
||||||
|
|
||||||
|
abuf_printf(&msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
|
||||||
|
|
||||||
|
msg.tail += data_print_jsons(data, msg.tail, msg.left);
|
||||||
|
if (msg.tail >= msg.head + sizeof(message))
|
||||||
|
return; // abort on overflow, we don't actually want to send more than fits the MTU
|
||||||
|
|
||||||
|
size_t abuf_len = msg.tail - msg.head;
|
||||||
|
datagram_client_send(&syslog->client, message, abuf_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_output_syslog_free(data_output_t *output)
|
||||||
|
{
|
||||||
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
||||||
|
|
||||||
|
if (!syslog)
|
||||||
|
return;
|
||||||
|
|
||||||
|
datagram_client_close(&syslog->client);
|
||||||
|
|
||||||
|
free(syslog);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct data_output *data_output_syslog_create(const char *host, const char *port)
|
||||||
|
{
|
||||||
|
data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
|
||||||
|
if (!syslog) {
|
||||||
|
WARN_CALLOC("data_output_syslog_create()");
|
||||||
|
return NULL; // NOTE: returns NULL on alloc failure.
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsa;
|
||||||
|
|
||||||
|
if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
|
||||||
|
perror("WSAStartup()");
|
||||||
|
free(syslog);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
syslog->output.print_data = print_syslog_data;
|
||||||
|
syslog->output.output_free = data_output_syslog_free;
|
||||||
|
// Severity 5 "Notice", Facility 20 "local use 4"
|
||||||
|
syslog->pri = 20 * 8 + 5;
|
||||||
|
#ifdef ESP32
|
||||||
|
const char* adapter_hostname = NULL;
|
||||||
|
tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);
|
||||||
|
if (adapter_hostname) {
|
||||||
|
memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
syslog->hostname[0] = '\0';
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
|
||||||
|
#endif
|
||||||
|
syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
|
||||||
|
datagram_client_open(&syslog->client, host, port);
|
||||||
|
|
||||||
|
return &syslog->output;
|
||||||
|
}
|
|
@ -29,6 +29,7 @@
|
||||||
#include "data_tag.h"
|
#include "data_tag.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "optparse.h"
|
#include "optparse.h"
|
||||||
|
#include "output_file.h"
|
||||||
#include "output_mqtt.h"
|
#include "output_mqtt.h"
|
||||||
#include "output_influx.h"
|
#include "output_influx.h"
|
||||||
#include "write_sigrok.h"
|
#include "write_sigrok.h"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
########################################################################
|
########################################################################
|
||||||
# Compile test cases
|
# Compile test cases
|
||||||
########################################################################
|
########################################################################
|
||||||
add_executable(data-test data-test.c)
|
add_executable(data-test data-test.c ../src/output_file.c)
|
||||||
|
|
||||||
target_link_libraries(data-test data)
|
target_link_libraries(data-test data)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
|
#include "output_file.h"
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
|
@ -112,6 +112,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
|
||||||
<ClInclude Include="..\include\list.h" />
|
<ClInclude Include="..\include\list.h" />
|
||||||
<ClInclude Include="..\include\mongoose.h" />
|
<ClInclude Include="..\include\mongoose.h" />
|
||||||
<ClInclude Include="..\include\optparse.h" />
|
<ClInclude Include="..\include\optparse.h" />
|
||||||
|
<ClInclude Include="..\include\output_file.h" />
|
||||||
<ClInclude Include="..\include\output_influx.h" />
|
<ClInclude Include="..\include\output_influx.h" />
|
||||||
<ClInclude Include="..\include\output_mqtt.h" />
|
<ClInclude Include="..\include\output_mqtt.h" />
|
||||||
<ClInclude Include="..\include\pulse_analyzer.h" />
|
<ClInclude Include="..\include\pulse_analyzer.h" />
|
||||||
|
@ -150,6 +151,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
|
||||||
<ClCompile Include="..\src\list.c" />
|
<ClCompile Include="..\src\list.c" />
|
||||||
<ClCompile Include="..\src\mongoose.c" />
|
<ClCompile Include="..\src\mongoose.c" />
|
||||||
<ClCompile Include="..\src\optparse.c" />
|
<ClCompile Include="..\src\optparse.c" />
|
||||||
|
<ClCompile Include="..\src\output_file.c" />
|
||||||
<ClCompile Include="..\src\output_influx.c" />
|
<ClCompile Include="..\src\output_influx.c" />
|
||||||
<ClCompile Include="..\src\output_mqtt.c" />
|
<ClCompile Include="..\src\output_mqtt.c" />
|
||||||
<ClCompile Include="..\src\pulse_analyzer.c" />
|
<ClCompile Include="..\src\pulse_analyzer.c" />
|
||||||
|
|
|
@ -74,6 +74,9 @@
|
||||||
<ClInclude Include="..\include\optparse.h">
|
<ClInclude Include="..\include\optparse.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\include\output_file.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\output_influx.h">
|
<ClInclude Include="..\include\output_influx.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -184,6 +187,9 @@
|
||||||
<ClCompile Include="..\src\optparse.c">
|
<ClCompile Include="..\src\optparse.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\output_file.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\src\output_influx.c">
|
<ClCompile Include="..\src\output_influx.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
Loading…
Add table
Reference in a new issue