1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Move in RIOT/applications

This commit is contained in:
chrysn 2022-09-20 11:52:30 +02:00
commit f738c9bb41
11 changed files with 884 additions and 0 deletions

23
examples/sniffer/Makefile Normal file
View File

@ -0,0 +1,23 @@
# Set the name of your application:
APPLICATION = sniffer
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Define modules that are used
USEMODULE += fmt
USEMODULE += gnrc
USEMODULE += netdev_default
USEMODULE += auto_init_gnrc_netif
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
USEMODULE += ztimer64_usec
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,22 @@
About
=====
This application is built to run together with the script `./tools/sniffer.py`
as a sniffer for (wireless) data traffic. This application works with any board
with any network device that supports the gnrc network stack (or precisely the
gnrc parts up to the link-layer). Further the network device (and its driver)
needs to support promiscuous and raw mode for usable output. Finally the board
needs to include auto-initialization code for the targeted network device.
Usage
=====
Compile and flash this application to the board of your choice. You can check
if everything on the RIOT side works by connecting to the board via UART and by
checking with `ifconfig` if a network device is available. Also note the
interface number for the following commands. Then you can check with
`ifconfig <iface> promisc` if promiscuous mode is supported and with
`ifconfig <iface> raw` if raw mode is supported by the driver/network device.
For further information on setting up the host part, see `./tools/README.md`.

137
examples/sniffer/main.c Normal file
View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2015-18 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup app_sniffer
* @brief Sniffer application based on the new network stack
* @{
*
* @file
* @brief Sniffer application for RIOT
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Martine Lenders <m.lenders@fu-berlin.de>
*
* @}
*/
#include <stdio.h>
#include "fmt.h"
#include "thread.h"
#include "shell.h"
#include "net/gnrc.h"
#include "ztimer64.h"
/**
* @brief Buffer size used by the shell
*/
#define SHELL_BUFSIZE (64U)
/**
* @brief Priority of the RAW dump thread
*/
#define RAWDUMP_PRIO (THREAD_PRIORITY_MAIN - 1)
/**
* @brief Message queue size of the RAW dump thread
*/
#define RAWDUMP_MSG_Q_SIZE (32U)
/**
* @brief Stack for the raw dump thread
*/
static char rawdmp_stack[THREAD_STACKSIZE_SMALL];
/**
* @brief Make a raw dump of the given packet contents
*/
void dump_pkt(gnrc_pktsnip_t *pkt)
{
gnrc_pktsnip_t *snip = pkt;
uint8_t lqi = 0;
if (pkt->next) {
if (pkt->next->type == GNRC_NETTYPE_NETIF) {
gnrc_netif_hdr_t *netif_hdr = pkt->next->data;
lqi = netif_hdr->lqi;
pkt = gnrc_pktbuf_remove_snip(pkt, pkt->next);
}
}
uint64_t now_us = ztimer64_now(ZTIMER64_USEC);
print_str("rftest-rx --- len ");
print_u32_hex((uint32_t)gnrc_pkt_len(pkt));
print_str(" lqi ");
print_byte_hex(lqi);
print_str(" rx_time ");
print_u64_hex(now_us);
print_str("\n");
while (snip) {
for (size_t i = 0; i < snip->size; i++) {
print_byte_hex(((uint8_t *)(snip->data))[i]);
print_str(" ");
}
snip = snip->next;
}
print_str("\n\n");
gnrc_pktbuf_release(pkt);
}
/**
* @brief Event loop of the RAW dump thread
*
* @param[in] arg unused parameter
*/
void *rawdump(void *arg)
{
msg_t msg_q[RAWDUMP_MSG_Q_SIZE];
(void)arg;
msg_init_queue(msg_q, RAWDUMP_MSG_Q_SIZE);
while (1) {
msg_t msg;
msg_receive(&msg);
switch (msg.type) {
case GNRC_NETAPI_MSG_TYPE_RCV:
dump_pkt((gnrc_pktsnip_t *)msg.content.ptr);
break;
default:
/* do nothing */
break;
}
}
/* never reached */
return NULL;
}
/**
* @brief Maybe you are a golfer?!
*/
int main(void)
{
gnrc_netreg_entry_t dump;
puts("RIOT sniffer application");
/* start and register rawdump thread */
puts("Run the rawdump thread and register it");
dump.target.pid = thread_create(rawdmp_stack, sizeof(rawdmp_stack), RAWDUMP_PRIO,
THREAD_CREATE_STACKTEST, rawdump, NULL, "rawdump");
dump.demux_ctx = GNRC_NETREG_DEMUX_CTX_ALL;
gnrc_netreg_register(GNRC_NETTYPE_UNDEF, &dump);
/* start the shell */
puts("All ok, starting the shell now");
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}

View File

@ -0,0 +1,114 @@
# RIOT Sniffer Application
## About
This sniffer script can be used to monitor and capture network traffic using
a RIOT based node. It is primarily designed for sniffing wireless data traffic,
but can also well be used for wired network traffic, as long as used network
devices support promiscuous mode and output of raw data.
The python script `sniffer.py` requires a RIOT node running the sniffer app, its
source code is located in this repository (see main folder). This node outputs
received network traffic via a serial port or a network socket in the common
Wireshark/libpcap (pcap) format. This output is then parsed by the `sniffer.py`
script included in this folder run on a host computer.
The `sniffer.py` script is a modified version of [malvira's script](https://github.com/malvira/libmc1322x/blob/master/tools/rftestrx2pcap.py)
for the Redbee Ecotag (https://github.com/malvira/libmc1322x/wiki/wireshark).
## Dependencies
The `sniffer.py` script is written in Python and needs [pyserial](https://pypi.python.org/pypi/pyserial).
Installing the dependencies:
#### Debuntu
apt-get install python-serial
#### PIP
pip install pyserial
## Usage
General usage:
1. Flash an applicable RIOT node with the sniffer application (insert path to
RIOT source and board name), as follows:
```
$ git clone https://github.com/RIOT-OS/applications/
$ cd applications/sniffer
$ BOARD=<name> make clean all flash
```
2. Run the `sniffer.py` script (change to subfolder `tools/`) as follows :
For serial port:
```
$ ./sniffer.py [-b baudrate] <tty> <channel> [outfile]
```
For network socket:
```
$ ./sniffer.py <host>:<port> <channel> [outfile]
```
You should see output like below:
```
ifconfig 3 set chan 26
ifconfig 3 raw
ifconfig 3 promisc
RX: 0
```
For detailed information on the parameters use the scripts on-line help:
```
./sniffer.py -h
```
### Examples
The following examples are made when using the sniffer application together with
an `iotlab-m3` node that is connected to `/dev/ttyUSB1`(or COM1) (`serial` connection type)
and runs per default with a baudrate of 500000. For the `socket` connection type port 20000
is used.
#### Linux (serial)
Dump packets to a file:
```
$ ./sniffer.py -b 500000 /dev/ttyUSB1 17 foo.pcap
```
This .pcap can then be opened in Wireshark.
Alternatively for live captures, you can pipe directly into Wireshark with:
```
$ ./sniffer.py -b 500000 /dev/ttyUSB1 17 | wireshark -k -i -
```
#### Windows (serial)
For windows you can use the optional third argument to output to a
.pcap:
```
$ ./sniffer.py -b 500000 COM1 17 foo.pcap
```
#### IoT-Lab Testbed (socket)
Start an experiment either via the website provided by the IoT-Lab testbed or
by using the RIOT specific iotlab Makefile with 3 neighboring `iotlab-m3` nodes,
where one of them runs the sniffer application and the others run the `gnrc_networking` application.
Now you can bind the sniffer node to localhost:
ssh -L 20000:_node-id_:20000 _user_@_site_.iot-lab.info
Then you can dump or observe the traffic generated by the other nodes running the `gnrc_networking`
application via one of the following commands:
```
$ ./sniffer.py localhost:20000 26 foo.pcap
$ ./sniffer.py localhost:20000 26 | wireshark -k -i -
```

184
examples/sniffer/tools/sniffer.py Executable file
View File

@ -0,0 +1,184 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
(C) 2012, Mariano Alvira <mar@devl.org>
(C) 2014, Oliver Hahm <oliver.hahm@inria.fr>
(C) 2015, Hauke Petersen <hauke.petersen@fu-berlin.de>
(C) 2015, Martine Lenders <mlenders@inf.fu-berlin.de>
(C) 2015, Cenk Gündoğan <cnkgndgn@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the Institute nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
"""
from __future__ import print_function
import argparse
import sys
import re
import socket
from time import sleep, time
from struct import pack
from serial import Serial
# PCAP setup
MAGIC = 0xA1B2C3D4
MAJOR = 2
MINOR = 4
ZONE = 0
SIG = 0
SNAPLEN = 0xFFFF
NETWORK = 230 # 802.15.4 no FCS
DEFAULT_BAUDRATE = 115200
def configure_interface(port, channel):
line = ""
iface = 0
port.write("ifconfig\n".encode())
while True:
line = port.readline()
if line == b"":
print("Application has no network interface defined", file=sys.stderr)
sys.exit(2)
match = re.search(r"^Iface +(\d+)", line.decode(errors="ignore"))
if match is not None:
iface = int(match.group(1))
break
# set channel, raw mode, and promiscuous mode
print("ifconfig %d set chan %d" % (iface, channel), file=sys.stderr)
print("ifconfig %d raw" % iface, file=sys.stderr)
print("ifconfig %d promisc" % iface, file=sys.stderr)
port.write(("ifconfig %d set chan %d\n" % (iface, channel)).encode())
port.write(("ifconfig %d raw\n" % iface).encode())
port.write(("ifconfig %d promisc\n" % iface).encode())
def generate_pcap(port, out):
# count incoming packets
count = 0
# output overall PCAP header
out.write(pack("<LHHLLLL", MAGIC, MAJOR, MINOR, ZONE, SIG, SNAPLEN, NETWORK))
sys.stderr.write("RX: %i\r" % count)
while True:
line = port.readline().rstrip()
pkt_header = re.match(
r">? *rftest-rx --- len (\w+).*", line.decode(errors="ignore")
)
if pkt_header:
now = time()
sec = int(now)
usec = int((now - sec) * 1000000)
length = int(pkt_header.group(1), 16)
out.write(pack("<LLLL", sec, usec, length, length))
out.flush()
count += 1
sys.stderr.write("RX: %i\r" % count)
continue
pkt_data = re.match(r"(\w\w )+", line.decode(errors="ignore"))
if pkt_data:
for part in line.decode(errors="ignore").split(" "):
byte = re.match(r"(\w\w)", part)
if byte:
out.write(pack("<B", int(byte.group(1), 16)))
out.flush()
def connect(args):
conn = None
if args.conn.startswith("/dev/tty") or args.conn.startswith("COM"):
# open serial port
try:
conn = Serial(args.conn, args.baudrate, dsrdtr=0, rtscts=0, timeout=1)
except IOError:
print("error opening serial port %s" % args.conn, file=sys.stderr)
sys.exit(2)
else:
try:
port = args.conn.split(":")[-1]
host = args.conn[: -(len(port) + 1)]
port = int(port)
except (IndexError, ValueError):
print("Can't parse host:port pair %s" % args.conn, file=sys.stderr)
sys.exit(2)
try:
sock = socket.socket()
sock.connect((host, port))
conn = sock.makefile("r+b", bufsize=0)
except IOError:
print("error connecting to %s:%s" % (host, port), file=sys.stderr)
sys.exit(2)
return conn
def main():
if sys.version_info > (3,):
default_outfile = sys.stdout.buffer
else:
default_outfile = sys.stdout
p = argparse.ArgumentParser()
p.add_argument(
"-b",
"--baudrate",
type=int,
default=DEFAULT_BAUDRATE,
help="Baudrate of the serial port (only evaluated "
"for non TCP-terminal, default: %d)" % DEFAULT_BAUDRATE,
)
p.add_argument(
"conn",
metavar="tty/host:port",
type=str,
help="Serial port or TCP (host, port) tuple to "
"terminal with sniffer application",
)
p.add_argument("channel", type=int, help="Channel to sniff on")
p.add_argument(
"outfile",
type=argparse.FileType("w+b"),
default=default_outfile,
nargs="?",
help="PCAP file to output to (default: stdout)",
)
args = p.parse_args()
conn = connect(args)
sleep(1)
configure_interface(conn, args.channel)
sleep(1)
try:
generate_pcap(conn, args.outfile)
except KeyboardInterrupt:
conn.close()
print()
sys.exit(2)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
# Set the name of your application:
APPLICATION = spectrum-scanner
# If no BOARD is found in the environment, use this default:
BOARD ?= samr21-xpro
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Define modules that are used
USEMODULE += gnrc
USEMODULE += netdev_default
USEMODULE += auto_init_gnrc_netif
USEMODULE += xtimer
USEMODULE += ztimer64_xtimer_compat
USEMODULE += fmt
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,36 @@
About
=====
This application is designed to run together with the script
`tools/plot_rssi.py` to monitor energy levels on all
available wireless channels. This application works with any board with a
network device that supports the gnrc network stack (or precisely the gnrc parts
up to the link-layer). Further, in order to get any measurements from the
device, the network device and its driver needs to support the netopt options
for executing a manual CCA and getting the last ED level
(NETOPT_IS_CHANNEL_CLR, and NETOPT_LAST_ED_LEVEL, respectively). Finally the
board needs to include auto-initialization code for the targeted network device.
Usage
=====
Compile and flash this application to the board of your choice. You can check if
everything on the RIOT side works by connecting to the board via UART and
looking at the output.
Data format
===========
The format of the data on the UART is:
```
[interface_number, timestamp, count] channel: ed_level, channel: ed_level, ...
```
where interface_number is the interface number (in RIOT's `ifconfig`), timestamp
is the time since power on in microseconds, count is the number of measurements
that were averaged, channel is a channel number, ed_level is the average energy
level for that channel since the last update.
For further information on setting up the host part, see
`tools/README.md`.

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2017 Eistec AB
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup app_spectrum_scanner
* @brief Scanner application to find free channels
* @{
*
* @file
* @brief Spectrum scanner application for RIOT
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/
#include <stdint.h>
#include <math.h>
#include "fmt.h"
#include "thread.h"
#include "xtimer.h"
#include "net/ieee802154.h"
#include "net/gnrc.h"
/* Scanning interval */
#define INTERVAL (500U * US_PER_MS)
/**
* @brief Measure the radio energy spectrum and print on stdout
*
* Algorithm description:
*
* The process will repeat as many measurements as possible during the
* measurement interval, before the average power is computed. This reduces the
* noise in the measurement and will yield a better image of what the radio
* environment contains.
*
* Still, 122 measurements per second (frdm-kw41z) and 128 us per measurement
* will only give a time coverage of about 1.5%, but because the measurements are
* spread out over time they should still give a good representation of which
* channels are free.
*
* Note that because the ED values are given in decibels, the average radio
* power is not the same as the arithmetic mean of the ED measurements. To
* compute the average of the dB measurements this algorithm requires both
* logarithm and exponentiation, quite heavy operations on the kinds of CPUs
* that RIOT targets. Increasing the CPU clock frequency may therefore reduce
* the noise in the output, because of the more frequent energy measurements
* possible.
*/
void spectrum_scanner(unsigned long interval_us)
{
size_t netif_numof = gnrc_netif_numof();
/* Using expf(x) (natural exponent) gives quicker computations on Cortex-M0+,
* compared to using powf(10, x). */
/*
* This was optimized by testing different combinations of expf, powf, logf, log10f:
*
* functions used | measurement iterations per 0.5 s on reference system (frdm-kw41z)
* ------------------------------------------------------------------
* expf, logf | 64
* powf, log10f | 46
* expf, log10f | 61
* no-op (baseline) | 83 (but the measurements are useless)
*/
while (1) {
/* Stack optimization, statically allocate this buffer */
float ed_average[netif_numof][IEEE802154_CHANNEL_MAX + 1];
memset(ed_average, 0, sizeof(ed_average));
uint64_t last_wakeup = xtimer_now_usec64();
uint64_t target = last_wakeup + interval_us;
/* We spin and try to do as many measurements as possible in the
* interval time */
unsigned int count = 0;
do {
gnrc_netif_t *netif = NULL;
for (unsigned int k = 0; (netif = gnrc_netif_iter(netif)); k++) {
for (unsigned int ch = IEEE802154_CHANNEL_MIN; ch <= IEEE802154_CHANNEL_MAX; ++ch) {
uint16_t tmp_ch = ch;
int res;
res = gnrc_netapi_set(netif->pid, NETOPT_CHANNEL, 0, &tmp_ch, sizeof(uint16_t));
if (res < 0) {
continue;
}
netopt_enable_t tmp;
/* Perform CCA to update ED level */
res = gnrc_netapi_get(netif->pid, NETOPT_IS_CHANNEL_CLR, 0, &tmp, sizeof(netopt_enable_t));
if (res < 0) {
continue;
}
int8_t level = 0;
res = gnrc_netapi_get(netif->pid, NETOPT_LAST_ED_LEVEL, 0, &level, sizeof(int8_t));
if (res < 0) {
continue;
}
/* Convert dB to pseudo-energy before summing together the
* measurements. "Pseudo" because we use the natural
* exponential function e^x instead of computing 10^x which
* would be required if we needed the real measured energy.
* There is no need to know the real energy level because we
* will be converting back to dB again before printing. */
ed_average[k][ch] += expf((float)level / 128.f);
}
}
++count;
thread_yield();
} while (xtimer_now_usec64() < target);
for (unsigned int k = 0; k < netif_numof; ++k) {
print("[", 1);
print_u32_dec(k);
print(", ", 2);
print_u64_dec(target);
print(", ", 2);
print_u32_dec(count);
print("] ", 2);
for (unsigned int ch = IEEE802154_CHANNEL_MIN; ch <= IEEE802154_CHANNEL_MAX; ++ch) {
/* Compute the average pseudo-energy and convert back to dB */
ed_average[k][ch] = logf(ed_average[k][ch] / count) * 128.f;
print_u32_dec(ch);
print(": ", 2);
print_float(ed_average[k][ch], 4);
print(", ", 2);
}
print("\n", 1);
}
}
}
int main(void)
{
puts("RIOT scanner application");
spectrum_scanner(INTERVAL);
return 0;
}

View File

@ -0,0 +1,40 @@
# RIOT Spectrum Scanner Application
## About
This script can be used to plot the radio signal spectrum when a connected node
is running the spectrum-scanner application application located in the parent
directory.
This node scans over the available radio channels performing CCA measurements
and outputting the measured ED level via a serial port. This output is then
parsed by the `plot_rssi.py` script included in this folder run on a host computer.
## Dependencies
The `plot_rssi.py` script requires [pyserial](https://pypi.python.org/pypi/pyserial)
for the serial port access, and [matplotlib](https://matplotlib.org) and
[numpy](http://www.numpy.org) for the plotting functionality.
Installing the dependencies:
#### Debian/Ubuntu
apt-get install python-serial python-matplotlib python-numpy
#### PIP
pip install pyserial matplotlib numpy
## Usage
General usage:
1. Flash a RIOT node with the spectrum-scanner application from
(https://github.com/RIOT-OS/applications/tree/master/spectrum-scanner)
2. Run the `plot_rssi.py` script
```
$ ./plot_rssi.py <tty> -b <baudrate>
```
### Examples
![Example screenshot](https://github.com/RIOT-OS/applications/tree/master/spectrum-scanner/tools/example.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,162 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Eistec AB
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import re
import logging
import argparse
import serial
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class SpectrumEmitter(object):
def __init__(self, port):
self.port = port
def data_gen(self):
logging.info("Begin collecting data from serial port")
while True:
# Read one line from the spectrum device
line = self.port.readline().rstrip()
pkt_data = re.match(
r"\[([-+]?\d+),\s*([-+]?\d+),\s*([-+]?\d+)\]\s*(.*)",
line.decode(errors="replace"),
)
if pkt_data:
ed = {}
try:
iface_id = int(pkt_data.group(1))
timestamp = int(pkt_data.group(2))
count = int(pkt_data.group(3))
except ValueError:
# Incorrect data received, probably UART noise or debugging
# messages from the device, not much else we can do other
# than try again with the next line
continue
logging.debug("data: if=%d cnt=%d t=%d", iface_id, count, timestamp)
raw = pkt_data.group(4)
for ch_ed in raw.split(","):
try:
pair = ch_ed.split(":")
ch = int(pair[0])
ed[ch] = float(pair[1])
except (ValueError, IndexError):
continue
yield ed
class RSSIPlot(object):
def __init__(self, ax, *args, tlen=120, dt=0.5, nchannels=27):
self.ax = ax
self.count = 0
self.dt = dt
self.tlen = tlen
# Generate mesh for plotting, this creates a grid of nchannel rows and
# (tlen / dt) columns
self.Y, self.X = np.mgrid[
slice(0 - 0.5, nchannels + 0.5, 1),
slice(-self.tlen - self.dt / 2, 0 + 1 - self.dt / 2, self.dt),
]
Z = np.zeros_like(self.X)
# X and Y are the bounds, so Z should be the value *inside* those bounds.
# Therefore, remove the last row and column from the Z array.
self.Z = Z[:-1, :-1]
self.pcm = self.ax.pcolormesh(
self.X, self.Y, self.Z, vmin=-100, vmax=-20, cmap=plt.cm.get_cmap("jet")
)
self.ax.get_figure().colorbar(self.pcm, label="Measured signal level [dB]")
self.ax.set_ylabel("Channel number")
self.ax.set_xlabel("Time [s]")
self.ch_min = nchannels
self.ch_max = 0
def update(self, ed):
resize = False
for ch in ed.keys():
if ch < self.ch_min:
self.ch_min = ch
resize = True
if ch > self.ch_max:
self.ch_max = ch
resize = True
col = np.zeros((self.Z.shape[0], 1))
for ch in ed.keys():
col[ch, 0] = ed[ch]
self.Z = np.hstack((self.Z[:, 1:], col))
if resize:
self.ax.set_ylim([self.ch_min - 0.5, self.ch_max + 0.5])
self.ax.set_yticks(range(self.ch_min, self.ch_max + 1))
self.pcm.set_array(self.Z.ravel())
return (self.pcm,)
def main(argv):
loglevels = [
logging.CRITICAL,
logging.ERROR,
logging.WARN,
logging.INFO,
logging.DEBUG,
]
parser = argparse.ArgumentParser(argv)
parser.add_argument(
"-v",
"--verbosity",
type=int,
default=4,
help="set logging verbosity, 1=CRITICAL, 5=DEBUG",
)
parser.add_argument("tty", help="Serial port device file name")
parser.add_argument(
"-b", "--baudrate", default=115200, type=int, help="Serial port baudrate"
)
args = parser.parse_args()
# logging setup
logging.basicConfig(level=loglevels[args.verbosity - 1])
# open serial port
try:
logging.debug("Open serial port %s, baud=%d", args.tty, args.baudrate)
port = serial.Serial(
port=args.tty, baudrate=9600, dsrdtr=0, rtscts=0, timeout=0.3
)
# This baudrate reconfiguration is necessary for certain USB to serial
# adapters, the Linux cdc_acm driver will keep repeating stale buffer
# contents otherwise. No idea about the cause, but this fixes the symptom.
port.baudrate = args.baudrate
except IOError:
logging.critical("error opening serial port", file=sys.stderr)
sys.exit(2)
try:
logging.debug("Creating figure")
fig, ax = plt.subplots()
graph = RSSIPlot(ax)
emitter = SpectrumEmitter(port)
animation.FuncAnimation(
fig, graph.update, emitter.data_gen, interval=10, blit=True
)
plt.show()
except KeyboardInterrupt:
port.close()
sys.exit(2)
if __name__ == "__main__":
main(sys.argv)