From d41580cbdfcd11929cfcc5600d9d637699dfb966 Mon Sep 17 00:00:00 2001 From: Pieter Robyns Date: Thu, 10 Aug 2017 13:49:00 +0200 Subject: [PATCH] New tool for real-time debugging and analysis of signals --- apps/grlora_analyze.py | 129 ++++++++++++++++++++++++++++++++++++ include/lora/CMakeLists.txt | 1 + include/lora/debugger.h | 55 +++++++++++++++ lib/CMakeLists.txt | 1 + lib/debugger.cc | 83 +++++++++++++++++++++++ lib/decoder_impl.cc | 4 ++ lib/decoder_impl.h | 2 + 7 files changed, 275 insertions(+) create mode 100755 apps/grlora_analyze.py create mode 100644 include/lora/debugger.h create mode 100644 lib/debugger.cc diff --git a/apps/grlora_analyze.py b/apps/grlora_analyze.py new file mode 100755 index 0000000..fb287bd --- /dev/null +++ b/apps/grlora_analyze.py @@ -0,0 +1,129 @@ +#!/usr/bin/python3 + +# ----------------------------------------------------------------------------- +# grlora_analyze.py +# A Python tool capable of performing a real-time analysis of signals passed +# by GNU Radio. +# +# Author: Pieter Robyns +# ----------------------------------------------------------------------------- + +import os +import socket +import numpy as np +import matplotlib.pyplot as plt +import struct +import argparse +from threading import Thread + +# Pop num bytes from l +def fetch(l, num): + fet = l[0:num] + del l[0:num] + return fet + +class State: + READ_HEADER = 0 + READ_DATA = 1 + +class Plotter(Thread): + def __init__(self): + Thread.__init__(self) + self.setDaemon(True) + self.socket_address = "/tmp/gr_lora.sock" + self.socket = None + self.init_socket() + plt.ion() + + def init_socket(self): + if self.socket is None: + try: + os.unlink(self.socket_address) + except OSError: + if os.path.exists(self.socket_address): + raise FileExistsError + + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.socket.bind(self.socket_address) + self.socket.listen(1) + self.socket.setblocking(0) + print("[gr-lora analyzer]: listening at " + self.socket_address) + else: + return # Socket is already initialized + + def get_data(self, connection): + buffer = connection.recv(1024) + if buffer: + return buffer + else: + raise ConnectionResetError + + def run(self): + while True: + plt.pause(0.0001) # Give some time to Qt GUI to render + try: + client_socket, client_address = self.socket.accept() + try: + self.state = State.READ_HEADER + data = bytearray() + data_len = 0 + draw_over = False + + while True: + plt.pause(0.0001) + try: + # Parse buffer data + if self.state == State.READ_HEADER: + while len(data) < 5: + data += self.get_data(client_socket) + + data_len, draw_over = struct.unpack(">I?", fetch(data, 5)) + self.state = State.READ_DATA + elif self.state == State.READ_DATA: + while len(data) < data_len: + data += self.get_data(client_socket) + + plot_data = np.frombuffer(fetch(data, data_len), dtype=np.complex64) + if not draw_over: + plt.gcf().clear() + plt.plot(np.arange(len(plot_data)), np.real(plot_data), "b", np.arange(len(plot_data)), np.imag(plot_data), "g") + self.state = State.READ_HEADER + except ConnectionResetError: + print("[gr-lora analyzer]: connection reset") + break + finally: + # Clean up the connection + client_socket.close() + except BlockingIOError: + pass + +# Test operation of the Plotter class +def test(): + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect("/tmp/gr_lora.sock") + try: + print("[fake client]: sending!") + with open(os.path.expanduser("~/usrpsf7.cfile"),"rb") as f: + data = f.read() + chunk_size = 524288 + for i in range(0, len(data), chunk_size): + chunk = data[i:i+chunk_size] + data_len = len(chunk) + draw_over = False + client.sendall(struct.pack(">I?", data_len, draw_over) + chunk) + finally: + print("[fake client]: done sending!") + client.close() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='gr-lora debugging / analysis tool for interfacing with GNU Radio over Unix sockets') + parser.add_argument('--test', dest='test', help='Peform a test', action='store_true') + args, unknown = parser.parse_known_args() + plotter = Plotter() + plotter.start() + + if args.test: + test() + + plotter.join() + exit(0) diff --git a/include/lora/CMakeLists.txt b/include/lora/CMakeLists.txt index 9e60420..e26b85e 100644 --- a/include/lora/CMakeLists.txt +++ b/include/lora/CMakeLists.txt @@ -26,5 +26,6 @@ install(FILES message_file_sink.h message_socket_sink.h channelizer.h + debugger.h controller.h DESTINATION include/lora ) diff --git a/include/lora/debugger.h b/include/lora/debugger.h new file mode 100644 index 0000000..430f5e5 --- /dev/null +++ b/include/lora/debugger.h @@ -0,0 +1,55 @@ +/* -*- c++ -*- */ +/* + * Copyright 2017 Pieter Robyns. + * + * This 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 3, or (at your option) + * any later version. + * + * This software 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 software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_DEBUGGER_H +#define INCLUDED_DEBUGGER_H + +#include +#include +#include + +#define PACKED __attribute__((packed, aligned(1))) + +namespace gr { + namespace lora { + class debugger { + public: + debugger(); + virtual ~debugger(); + + void attach(std::string path = "/tmp/gr_lora.sock"); + void detach(void); + void analyze_samples(bool clear, bool draw_over); + void store_samples(const gr_complex* samples, uint32_t length); + private: + typedef struct header { + uint32_t length; + bool draw_over; + } PACKED header; + + void send_samples(); + std::vector d_samples; + int d_socket; + bool d_attached; + }; + } +} + +#endif /* INCLUDED_DEBUGGER_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b2bf736..8659c0f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -31,6 +31,7 @@ list(APPEND lora_sources message_socket_sink_impl.cc channelizer_impl.cc controller_impl.cc + debugger.cc ) set(lora_sources "${lora_sources}" PARENT_SCOPE) diff --git a/lib/debugger.cc b/lib/debugger.cc new file mode 100644 index 0000000..d3ed005 --- /dev/null +++ b/lib/debugger.cc @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace gr { + namespace lora { + debugger::debugger() : d_attached(false) { + } + + debugger::~debugger() { + + } + + /* + * Attach to a UNIX domain socket. + */ + void debugger::attach(std::string path) { + if((d_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + std::cerr << "Failed to create UNIX domain socket." << std::endl; + return; + } + + struct sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strncpy(address.sun_path, path.c_str(), sizeof(address.sun_path)-1); + + if(connect(d_socket, (struct sockaddr*)&address, sizeof(address)) == -1) { + std::cerr << "Failed to connect to analyzer." << std::endl; + } else { + d_attached = true; + } + } + + void debugger::detach(void) { + if(d_attached) { + close(d_socket); + d_attached = false; + } + } + + /* + TODO: For some vague reason, making a template member function breaks swig. This only happens if we separate declaration and definition of the function (i.e. in utilities.h, the functions don't have this issue). Therefore, it seems like a bug. To resolve, we can port LoRa Receiver to C++, removing the SWIG dependency. + + See: https://github.com/gnuradio/gnuradio/search?utf8=%E2%9C%93&q=quicksort_index.h&type= + */ + void debugger::analyze_samples(bool clear, bool draw_over) { + if(d_attached) { + uint32_t num_payload_bytes = d_samples.size() * sizeof(gr_complex); + uint32_t num_bytes_sent; + + debugger::header hdr; + hdr.length = htonl(num_payload_bytes); + hdr.draw_over = draw_over; + + // Send header + if((num_bytes_sent = send(d_socket, &hdr, sizeof(hdr), 0)) == -1) { + std::cerr << "Failed to send header." << std::endl; + return; + } + + // Send payload + if((num_bytes_sent = send(d_socket, &d_samples[0], num_payload_bytes, 0)) == -1) { + std::cerr << "Failed to send payload." << std::endl; + return; + } + + if(clear) + d_samples.clear(); + } + } + + void debugger::store_samples(const gr_complex* samples, uint32_t length) { + if(d_attached) { + d_samples.insert(d_samples.end(), samples, samples + length); + } + } + } +} diff --git a/lib/decoder_impl.cc b/lib/decoder_impl.cc index cf06920..1d54a8d 100644 --- a/lib/decoder_impl.cc +++ b/lib/decoder_impl.cc @@ -31,6 +31,7 @@ #include "tables.h" #include "utilities.h" + //#define NO_TMP_WRITES 1 /// Debug output file write //#define CFO_CORRECT 1 /// Correct shift fft estimation @@ -142,6 +143,9 @@ namespace gr { // Whitening empty file // DBGR_QUICK_TO_FILE("/tmp/whitening_out", false, g, -1, ""); + #ifndef NDEBUG + d_dbg.attach(); + #endif } /** diff --git a/lib/decoder_impl.h b/lib/decoder_impl.h index 8e70a3d..2cb6d13 100644 --- a/lib/decoder_impl.h +++ b/lib/decoder_impl.h @@ -26,6 +26,7 @@ #include #include #include +#include #define DECIMATOR_FILTER_SIZE (2*8*1 + 1) // 2*decim_factor*delay+1 @@ -64,6 +65,7 @@ namespace gr { */ class decoder_impl : public decoder { private: + debugger d_dbg; ///< Debugger for plotting samples, printing output, etc. DecoderState d_state; ///< Holds the current state of the decoder (state machine). std::vector d_downchirp; ///< The complex ideal downchirp.