From 3f800cefc728b7a33e9b8016265a7f68065f9045 Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" <zany@triq.net> Date: Sun, 30 Jan 2022 12:13:43 +0100 Subject: [PATCH] Add rtl_tcp pass-through output (#1915) --- include/output_rtltcp.h | 31 ++ include/r_api.h | 2 + include/raw_output.h | 28 ++ include/rtl_433.h | 1 + src/CMakeLists.txt | 2 + src/output_rtltcp.c | 557 +++++++++++++++++++++++++++++++++++ src/r_api.c | 13 + src/raw_output.c | 30 ++ src/rtl_433.c | 10 + vs15/rtl_433.vcxproj | 4 + vs15/rtl_433.vcxproj.filters | 12 + 11 files changed, 690 insertions(+) create mode 100644 include/output_rtltcp.h create mode 100644 include/raw_output.h create mode 100644 src/output_rtltcp.c create mode 100644 src/raw_output.c diff --git a/include/output_rtltcp.h b/include/output_rtltcp.h new file mode 100644 index 00000000..59ac76e2 --- /dev/null +++ b/include/output_rtltcp.h @@ -0,0 +1,31 @@ +/** @file + rtl_tcp output for rtl_433 raw data. + + Copyright (C) 2022 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_RTLTCP_H_ +#define INCLUDE_OUTPUT_RTLTCP_H_ + +#include "raw_output.h" + +#include <stdint.h> + +struct r_cfg; + +/** Construct rtl_tcp data output. + + @param host the server host to bind + @param port the server port to bind + @param cfg the r_api config to use + @return The initialized rtltcp output instance. + You must release this object with raw_output_free once you're done with it. +*/ +struct raw_output *raw_output_rtltcp_create(char const *host, char const *port, struct r_cfg *cfg); + +#endif /* INCLUDE_OUTPUT_RTLTCP_H_ */ diff --git a/include/r_api.h b/include/r_api.h index 299ac9d5..2f7a1ee0 100644 --- a/include/r_api.h +++ b/include/r_api.h @@ -85,6 +85,8 @@ void add_trigger_output(struct r_cfg *cfg, char *param); void add_null_output(struct r_cfg *cfg, char *param); +void add_rtltcp_output(struct r_cfg *cfg, char *param); + void start_outputs(struct r_cfg *cfg, char const *const *well_known); void add_sr_dumper(struct r_cfg *cfg, char const *spec, int overwrite); diff --git a/include/raw_output.h b/include/raw_output.h new file mode 100644 index 00000000..c8d9157b --- /dev/null +++ b/include/raw_output.h @@ -0,0 +1,28 @@ +/** @file + Raw I/Q data output handler. + + Copyright (C) 2022 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_RAW_OUTPUT_H_ +#define INCLUDE_RAW_OUTPUT_H_ + +#include <stdint.h> + +struct raw_output; + +typedef struct raw_output { + void (*output_frame)(struct raw_output *output, uint8_t const *data, uint32_t len); + void (*output_free)(struct raw_output *output); +} raw_output_t; + +void raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len); + +void raw_output_free(struct raw_output *output); + +#endif /* INCLUDE_RAW_OUTPUT_H_ */ diff --git a/include/rtl_433.h b/include/rtl_433.h index 7fe352ee..30b785c0 100644 --- a/include/rtl_433.h +++ b/include/rtl_433.h @@ -94,6 +94,7 @@ typedef struct r_cfg { uint16_t num_r_devices; list_t data_tags; list_t output_handler; + list_t raw_handler; struct dm_state *demod; char const *sr_filename; int sr_execopen; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b12bb25f..a30688fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(r_433 STATIC output_file.c output_influx.c output_mqtt.c + output_rtltcp.c output_trigger.c output_udp.c pulse_analyzer.c @@ -33,6 +34,7 @@ add_library(r_433 STATIC pulse_detect_fsk.c r_api.c r_util.c + raw_output.c rfraw.c samp_grab.c sdr.c diff --git a/src/output_rtltcp.c b/src/output_rtltcp.c new file mode 100644 index 00000000..03040624 --- /dev/null +++ b/src/output_rtltcp.c @@ -0,0 +1,557 @@ +/** @file + rtl_tcp output for rtl_433 raw data. + + Copyright (C) 2022 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_rtltcp.h" + +#include "rtl_433.h" +#include "r_api.h" +#include "r_util.h" +#include "fatal.h" +#include "compat_pthread.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <signal.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 <sys/select.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 + +#ifdef _MSC_VER +#include <BaseTsd.h> +typedef SSIZE_T ssize_t; +#endif + +// MSG_NOSIGNAL is Linux and most BSDs only, not macOS or Windows +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +/* rtl_tcp server */ + +// Only available if Threads are enabled. +// Currently serves a maximum of 1 client connection. +// A data backing of max_clients+1 slots is needed to write a data slot +// when each client is blocking a different data slot. +// Should use a global ring buffer or shared memory for sendfile() someday. + +#ifdef THREADS + +#define DATA_SLOTS 2 +typedef struct rtltcp_server { + struct sockaddr_storage addr; + socklen_t addr_len; + SOCKET sock; + int client_count; ///< number of connected clients + + int data_recent; ///< the data slot with most recent data, -1 otherwise + int data_inuse[DATA_SLOTS]; ///< data slot is in use, 0 otherwise + void *data_buf[DATA_SLOTS]; ///< data slot memory of data_size bytes, NULL otherwise + int data_size[DATA_SLOTS]; ///< data slot data_buf size, 0 otherwise + int data_len[DATA_SLOTS]; ///< data slot valid bytes in data_buf, 0 otherwise + + pthread_t thread; + pthread_mutex_t lock; ///< lock for data slots + pthread_cond_t cond; ///< wait for data slots + r_cfg_t *cfg; + struct raw_output *output; +} rtltcp_server_t; + +static ssize_t send_all(int sockfd, void const *buf, size_t len, int flags) +{ + size_t sent = 0; + while (sent < len) { + ssize_t ret = send(sockfd, (uint8_t *)buf + sent, len - sent, flags); + if (ret < 0) + return ret; + sent += (size_t)ret; + } + return sent; +} + +static void send_header(SOCKET sock) +{ + uint8_t msg[] = {'R', 'T', 'L', '0', 0, 0, 0, 0, 0, 0, 0, 0}; + send_all(sock, msg, sizeof(msg), MSG_NOSIGNAL); // ignore SIGPIPE +} + +#define RTLTCP_SET_FREQ 0x01 +#define RTLTCP_SET_SAMPLE_RATE 0x02 +#define RTLTCP_SET_GAIN_MODE 0x03 +#define RTLTCP_SET_GAIN 0x04 +#define RTLTCP_SET_FREQ_CORRECTION 0x05 +#define RTLTCP_SET_IF_TUNER_GAIN 0x06 +#define RTLTCP_SET_TEST_MODE 0x07 +#define RTLTCP_SET_AGC_MODE 0x08 +#define RTLTCP_SET_DIRECT_SAMPLING 0x09 +#define RTLTCP_SET_OFFSET_TUNING 0x0a +#define RTLTCP_SET_RTL_XTAL 0x0b +#define RTLTCP_SET_TUNER_XTAL 0x0c +#define RTLTCP_SET_TUNER_GAIN_BY_ID 0x0d +#define RTLTCP_SET_BIAS_TEE 0x0e + +/* +E.g. initialization from Gqrx: +- RTLTCP_SET_GAIN_MODE with 1 +- RTLTCP_SET_AGC_MODE with 0 +- RTLTCP_SET_DIRECT_SAMPLING with 0 +- RTLTCP_SET_OFFSET_TUNING with 0 +- RTLTCP_SET_BIAS_TEE with 0 +- RTLTCP_SET_SAMPLE_RATE with 250000 +- RTLTCP_SET_FREQ with 52000000 +- RTLTCP_SET_GAIN with 0 +- RTLTCP_SET_GAIN with 0 +- RTLTCP_SET_FREQ with 433968000 +*/ + +static int parse_command(r_cfg_t *cfg, uint8_t const *buf, int len) +{ + UNUSED(cfg); + + if (len < 5) + return 0; + int cmd = buf[0]; + unsigned arg = (unsigned)buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | buf[4]; + // fprintf(stderr, "CMD: %d with %u (%d) %02x %02x %02x %02x\n", cmd, arg, (int)arg, buf[1], buf[2], buf[3], buf[4]); + len -= 5; + + switch (cmd) { + case RTLTCP_SET_FREQ: + fprintf(stderr, "rtl_tcp received command SET_FREQ with %u\n", arg); + // set_center_freq(cfg, arg); + break; + case RTLTCP_SET_SAMPLE_RATE: + fprintf(stderr, "rtl_tcp received command SET_SAMPLE_RATE with %u\n", arg); + // set_sample_rate(cfg, arg); + break; + case RTLTCP_SET_GAIN_MODE: + fprintf(stderr, "rtl_tcp received command SET_GAIN_MODE with %u\n", arg); + break; + case RTLTCP_SET_GAIN: + fprintf(stderr, "rtl_tcp received command SET_GAIN with %u\n", arg); + break; + case RTLTCP_SET_FREQ_CORRECTION: + fprintf(stderr, "rtl_tcp received command SET_FREQ_CORRECTION with %u\n", arg); + // set_freq_correction(cfg, (int)arg); + break; + case RTLTCP_SET_IF_TUNER_GAIN: + fprintf(stderr, "rtl_tcp received command SET_IF_TUNER_GAIN with %u\n", arg); + break; + case RTLTCP_SET_TEST_MODE: + fprintf(stderr, "rtl_tcp received command SET_TEST_MODE with %u\n", arg); + break; + case RTLTCP_SET_AGC_MODE: + fprintf(stderr, "rtl_tcp received command SET_AGC_MODE with %u\n", arg); + break; + case RTLTCP_SET_DIRECT_SAMPLING: + fprintf(stderr, "rtl_tcp received command SET_DIRECT_SAMPLING with %u\n", arg); + break; + case RTLTCP_SET_OFFSET_TUNING: + fprintf(stderr, "rtl_tcp received command SET_OFFSET_TUNING with %u\n", arg); + break; + case RTLTCP_SET_RTL_XTAL: + fprintf(stderr, "rtl_tcp received command SET_RTL_XTAL with %u\n", arg); + break; + case RTLTCP_SET_TUNER_XTAL: + fprintf(stderr, "rtl_tcp received command SET_TUNER_XTAL with %u\n", arg); + break; + case RTLTCP_SET_TUNER_GAIN_BY_ID: + fprintf(stderr, "rtl_tcp received command SET_TUNER_GAIN_BY_ID with %u\n", arg); + break; + case RTLTCP_SET_BIAS_TEE: + fprintf(stderr, "rtl_tcp received command SET_BIAS_TEE with %u\n", arg); + break; + default: + fprintf(stderr, "rtl_tcp received unknown command %d with %u\n", cmd, arg); + break; + } + + return 5; +} + +// event handler to broadcast to all our sockets +static void rtltcp_broadcast_send(rtltcp_server_t *srv, uint8_t const *data, int len) +{ + // fprintf(stderr, "%s: %d byte frame\n", __func__, len); + pthread_mutex_lock(&srv->lock); + if (srv->client_count <= 0) { + pthread_mutex_unlock(&srv->lock); + return; // no clients, do nothing + } + + // find a free slot + int slot = 0; + for (; slot < DATA_SLOTS; ++slot) { + if (srv->data_inuse[slot] == 0) { + break; + } + } + if (slot >= DATA_SLOTS) { + fprintf(stderr, "%s: all data slots in use!\n", __func__); + return; // this should never happen + } + + // (re-)allocate slot buffer if needed + if (srv->data_buf[slot] == NULL || srv->data_size[slot] < len) { + //fprintf(stderr, "%s: allocating buffer of %d bytes for rtl_tcp\n", __func__, len); + free(srv->data_buf[slot]); + srv->data_buf[slot] = malloc(len); + if (!srv->data_buf[slot]) { + FATAL_MALLOC("rtltcp_broadcast_send()"); + } + srv->data_size[slot] = len; + } + + // transfer data to the buffer slot + memcpy(srv->data_buf[slot], data, len); + srv->data_len[slot] = len; + srv->data_recent = slot; + pthread_mutex_unlock(&srv->lock); + pthread_cond_signal(&srv->cond); + // perhaps broadcast if we want to support multiple clients + //int pthread_cond_broadcast(&srv->cond); +} + +static THREAD_RETURN THREAD_CALL accept_thread(void *arg) +{ + rtltcp_server_t *srv = arg; + + // Start listening for clients, waits for an incoming connection + listen(srv->sock, 1); + //fprintf(stderr, "rtl_tcp listening...\n"); + + for (;;) { + // Accept actual connection from the client + struct sockaddr_storage addr = {0}; + unsigned addr_len = sizeof(addr); + int sock = accept(srv->sock, (struct sockaddr *)&addr, &addr_len); + + if (sock < 0) { + perror("ERROR on accept"); + continue; + } + + // Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs +#ifdef SO_NOSIGPIPE + int opt = 1; + if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)) == -1) { + perror("setsockopt"); + continue; + } +#endif + + char host[INET6_ADDRSTRLEN] = {0}; + char port[NI_MAXSERV] = {0}; + + int err = getnameinfo((struct sockaddr *)&srv->addr, srv->addr_len, + host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + if (err != 0) { + fprintf(stderr, "failed to convert address to string (code=%d)\n", err); + continue; + } + fprintf(stderr, "rtl_tcp client connected from %s port %s\n", host, port); + + pthread_mutex_lock(&srv->lock); + srv->client_count += 1; + pthread_mutex_unlock(&srv->lock); + int slot = -1; // data sent in previous loop + + send_header(sock); + + // Client loop + for (;;) { + // Read available commands + int abort = 0; + for (;;) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + struct timeval timeout = {0}; + + int ready = select(sock + 1, &fds, NULL, NULL, &timeout); + if (ready <= 0) + break; + + uint8_t buf[128] = {0}; + ssize_t len = recv(sock, buf, sizeof(buf), 0); + //fprintf(stderr, "rtl_tcp recv %zd bytes (%d)\n", len, ready); + if (len <= 0) { + abort = 1; + break; + } + int pos = 0; + while (pos + 5 <= len) { + pos += parse_command(srv->cfg, &buf[pos], (int)len - pos); + } + } + if (abort) { + break; + } + + // Send frames + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + struct timeval timeout = {0}; + + int ready = select(sock + 1, NULL, &fds, NULL, &timeout); + if (ready <= 0) { + fprintf(stderr, "rtl_tcp send not ready for write?\n"); + break; + } + + pthread_mutex_lock(&srv->lock); + if (srv->data_recent < 0 || srv->data_recent == slot) + pthread_cond_wait(&srv->cond, &srv->lock); + // Maybe timeout to check recv() + // pthread_cond_timedwait(&srv->cond, &srv->lock, const struct timespec *abstime); + + // Get data and mark as in use + slot = srv->data_recent; + srv->data_inuse[slot] += 1; + void const *data = srv->data_buf[slot]; + int data_len = srv->data_len[slot]; + pthread_mutex_unlock(&srv->lock); + + send_all(sock, data, data_len, MSG_NOSIGNAL); // ignore SIGPIPE + + // Mark data as done + pthread_mutex_lock(&srv->lock); + srv->data_inuse[slot] -= 1; + pthread_mutex_unlock(&srv->lock); + } + + pthread_mutex_lock(&srv->lock); + srv->client_count -= 1; + pthread_mutex_unlock(&srv->lock); + + fprintf(stderr, "rtl_tcp client disconnected from %s port %s\n", host, port); + close(sock); + } + return NULL; +} + +static int rtltcp_server_start(rtltcp_server_t *srv, char const *host, char const *port, r_cfg_t *cfg, struct raw_output *output) +{ + 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_STREAM; + 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) { + srv->sock = sock; + memset(&srv->addr, 0, sizeof(srv->addr)); + memcpy(&srv->addr, res->ai_addr, res->ai_addrlen); + srv->addr_len = res->ai_addrlen; + break; // success + } + } + freeaddrinfo(res0); + if (sock == INVALID_SOCKET) { + perror("socket"); + return -1; + } + + if (bind(sock, (struct sockaddr *)&srv->addr, srv->addr_len) < 0) { + perror("error on binding"); + return -1; + } + + srv->cfg = cfg; + srv->output = output; + + srv->data_recent = -1; + + char address[INET6_ADDRSTRLEN] = {0}; + char portstr[NI_MAXSERV] = {0}; + + int err = getnameinfo((struct sockaddr *)&srv->addr, srv->addr_len, + address, sizeof(address), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + if (err != 0) { + fprintf(stderr, "failed to convert address to string (code=%d)\n", err); + return -1; + } + fprintf(stderr, "Starting rtl_tcp server on address %s %s\n", address, portstr); + + pthread_mutex_init(&srv->lock, NULL); + pthread_cond_init(&srv->cond, NULL); + +#ifndef _WIN32 + // Block all signals from the worker thread + sigset_t sigset; + sigset_t oldset; + sigfillset(&sigset); + pthread_sigmask(SIG_SETMASK, &sigset, &oldset); +#endif + int r = pthread_create(&srv->thread, NULL, accept_thread, srv); +#ifndef _WIN32 + pthread_sigmask(SIG_SETMASK, &oldset, NULL); +#endif + if (r) { + fprintf(stderr, "%s: error in pthread_create, rc: %d\n", __func__, r); + } + + return r; +} + +static int rtltcp_server_stop(rtltcp_server_t *srv) +{ + if (!srv) + return 0; + + fprintf(stderr, "Stopping rtl_tcp server...\n"); + // thread is likely blocking in accept, recv, or send + int r = pthread_cancel(srv->thread); + if (r) { + fprintf(stderr, "%s: error in pthread_cancel, rc: %d\n", __func__, r); + } + pthread_mutex_destroy(&srv->lock); + pthread_cond_destroy(&srv->cond); + + srv->client_count = 0; + for (int slot = 0; slot < DATA_SLOTS; ++slot) { + free(srv->data_buf[slot]); + srv->data_buf[slot] = NULL; + srv->data_inuse[slot] = 0; + } + + // close server socket + int ret = 0; + if (srv->sock != INVALID_SOCKET) { + ret = closesocket(srv->sock); + srv->sock = INVALID_SOCKET; + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + return ret; +} + +/* rtl_tcp raw output */ + +typedef struct raw_output_rtltcp { + struct raw_output output; + rtltcp_server_t server; +} raw_output_rtltcp_t; + +static void raw_output_rtltcp_frame(raw_output_t *output, uint8_t const *data, uint32_t len) +{ + raw_output_rtltcp_t *rtltcp = (raw_output_rtltcp_t *)output; + + rtltcp_broadcast_send(&rtltcp->server, data, len); +} + +static void raw_output_rtltcp_free(raw_output_t *output) +{ + raw_output_rtltcp_t *rtltcp = (raw_output_rtltcp_t *)output; + + if (!rtltcp) + return; + + rtltcp_server_stop(&rtltcp->server); + + free(rtltcp); +} + +struct raw_output *raw_output_rtltcp_create(const char *host, const char *port, r_cfg_t *cfg) +{ + raw_output_rtltcp_t *rtltcp = calloc(1, sizeof(raw_output_rtltcp_t)); + if (!rtltcp) { + WARN_CALLOC("raw_output_rtltcp_create()"); + return NULL; // NOTE: returns NULL on alloc failure. + } + + rtltcp->output.output_frame = raw_output_rtltcp_frame; + rtltcp->output.output_free = raw_output_rtltcp_free; + + int ret = rtltcp_server_start(&rtltcp->server, host, port, cfg, &rtltcp->output); + if (ret != 0) { + exit(1); + } + + return &rtltcp->output; +} + +#else + +struct raw_output *raw_output_rtltcp_create(const char *host, const char *port, r_cfg_t *cfg) +{ + UNUSED(host); + UNUSED(port); + UNUSED(cfg); + fprintf(stderr, "\nWARNING: rtl_tcp not available in this build!\n\n"); + return NULL; +} + +#endif diff --git a/src/r_api.c b/src/r_api.c index b2c4a24f..0e04c603 100644 --- a/src/r_api.c +++ b/src/r_api.c @@ -34,6 +34,7 @@ #include "output_mqtt.h" #include "output_influx.h" #include "output_trigger.h" +#include "output_rtltcp.h" #include "write_sigrok.h" #include "mongoose.h" #include "compat_time.h" @@ -223,6 +224,8 @@ void r_free_cfg(r_cfg_t *cfg) free(cfg->devices); + list_free_elems(&cfg->raw_handler, (list_elem_free_fn)raw_output_free); + list_free_elems(&cfg->output_handler, (list_elem_free_fn)data_output_free); list_free_elems(&cfg->data_tags, (list_elem_free_fn)data_tag_free); @@ -999,6 +1002,16 @@ void add_null_output(r_cfg_t *cfg, char *param) list_push(&cfg->output_handler, NULL); } +void add_rtltcp_output(r_cfg_t *cfg, char *param) +{ + char *host = "localhost"; + char *port = "1234"; + hostport_param(param, &host, &port); + fprintf(stderr, "rtl_tcp server at %s port %s\n", host, port); + + list_push(&cfg->raw_handler, raw_output_rtltcp_create(host, port, cfg)); +} + void add_sr_dumper(r_cfg_t *cfg, char const *spec, int overwrite) { // create channels diff --git a/src/raw_output.c b/src/raw_output.c new file mode 100644 index 00000000..4c191a88 --- /dev/null +++ b/src/raw_output.c @@ -0,0 +1,30 @@ +/** @file + Raw I/Q data output handler. + + Copyright (C) 2022 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 "raw_output.h" + +#include <stdint.h> + +/* generic raw_output */ + +void raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len) +{ + if (!output) + return; + output->output_frame(output, data, len); +} + +void raw_output_free(struct raw_output *output) +{ + if (!output) + return; + output->output_free(output); +} diff --git a/src/rtl_433.c b/src/rtl_433.c index 138eaacf..1f784556 100644 --- a/src/rtl_433.c +++ b/src/rtl_433.c @@ -40,6 +40,7 @@ #include "pulse_demod.h" #include "rfraw.h" #include "data.h" +#include "raw_output.h" #include "r_util.h" #include "optparse.h" #include "abuf.h" @@ -365,6 +366,12 @@ static void sdr_callback(unsigned char *iq_buf, uint32_t len, void *ctx) char time_str[LOCAL_TIME_BUFLEN]; unsigned long n_samples; + // do this here and not in sdr_handler so realtime replay can use rtl_tcp output + for (void **iter = cfg->raw_handler.elems; iter && *iter; ++iter) { + raw_output_t *output = *iter; + raw_output_frame(output, iq_buf, len); + } + if ((cfg->bytes_to_read > 0) && (cfg->bytes_to_read <= len)) { len = cfg->bytes_to_read; cfg->exit_async = 1; @@ -1144,6 +1151,9 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg) else if (strncmp(arg, "null", 4) == 0) { add_null_output(cfg, arg_param(arg)); } + else if (strncmp(arg, "rtl_tcp", 7) == 0) { + add_rtltcp_output(cfg, arg_param(arg)); + } else { fprintf(stderr, "Invalid output format %s\n", arg); usage(1); diff --git a/vs15/rtl_433.vcxproj b/vs15/rtl_433.vcxproj index 40bb4200..b921ee2b 100644 --- a/vs15/rtl_433.vcxproj +++ b/vs15/rtl_433.vcxproj @@ -116,6 +116,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command> <ClInclude Include="..\include\output_file.h" /> <ClInclude Include="..\include\output_influx.h" /> <ClInclude Include="..\include\output_mqtt.h" /> + <ClInclude Include="..\include\output_rtltcp.h" /> <ClInclude Include="..\include\output_trigger.h" /> <ClInclude Include="..\include\output_udp.h" /> <ClInclude Include="..\include\pulse_analyzer.h" /> @@ -126,6 +127,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command> <ClInclude Include="..\include\r_device.h" /> <ClInclude Include="..\include\r_private.h" /> <ClInclude Include="..\include\r_util.h" /> + <ClInclude Include="..\include\raw_output.h" /> <ClInclude Include="..\include\rfraw.h" /> <ClInclude Include="..\include\rtl_433.h" /> <ClInclude Include="..\include\rtl_433_devices.h" /> @@ -157,6 +159,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command> <ClCompile Include="..\src\output_file.c" /> <ClCompile Include="..\src\output_influx.c" /> <ClCompile Include="..\src\output_mqtt.c" /> + <ClCompile Include="..\src\output_rtltcp.c" /> <ClCompile Include="..\src\output_trigger.c" /> <ClCompile Include="..\src\output_udp.c" /> <ClCompile Include="..\src\pulse_analyzer.c" /> @@ -165,6 +168,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command> <ClCompile Include="..\src\pulse_detect_fsk.c" /> <ClCompile Include="..\src\r_api.c" /> <ClCompile Include="..\src\r_util.c" /> + <ClCompile Include="..\src\raw_output.c" /> <ClCompile Include="..\src\rfraw.c" /> <ClCompile Include="..\src\rtl_433.c" /> <ClCompile Include="..\src\samp_grab.c" /> diff --git a/vs15/rtl_433.vcxproj.filters b/vs15/rtl_433.vcxproj.filters index 856cef4c..9fa44ca8 100644 --- a/vs15/rtl_433.vcxproj.filters +++ b/vs15/rtl_433.vcxproj.filters @@ -86,6 +86,9 @@ <ClInclude Include="..\include\output_mqtt.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\include\output_rtltcp.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\include\output_trigger.h"> <Filter>Header Files</Filter> </ClInclude> @@ -116,6 +119,9 @@ <ClInclude Include="..\include\r_util.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\include\raw_output.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\include\rfraw.h"> <Filter>Header Files</Filter> </ClInclude> @@ -205,6 +211,9 @@ <ClCompile Include="..\src\output_mqtt.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\src\output_rtltcp.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\src\output_trigger.c"> <Filter>Source Files</Filter> </ClCompile> @@ -229,6 +238,9 @@ <ClCompile Include="..\src\r_util.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\src\raw_output.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\src\rfraw.c"> <Filter>Source Files</Filter> </ClCompile>