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:
commit
f738c9bb41
23
examples/sniffer/Makefile
Normal file
23
examples/sniffer/Makefile
Normal 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
|
22
examples/sniffer/README.md
Normal file
22
examples/sniffer/README.md
Normal 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
137
examples/sniffer/main.c
Normal 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;
|
||||
}
|
114
examples/sniffer/tools/README.md
Normal file
114
examples/sniffer/tools/README.md
Normal 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
184
examples/sniffer/tools/sniffer.py
Executable 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()
|
21
examples/spectrum-scanner/Makefile
Normal file
21
examples/spectrum-scanner/Makefile
Normal 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
|
36
examples/spectrum-scanner/README.md
Normal file
36
examples/spectrum-scanner/README.md
Normal 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`.
|
145
examples/spectrum-scanner/main.c
Normal file
145
examples/spectrum-scanner/main.c
Normal 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;
|
||||
}
|
40
examples/spectrum-scanner/tools/README.md
Normal file
40
examples/spectrum-scanner/tools/README.md
Normal 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)
|
BIN
examples/spectrum-scanner/tools/example.png
Normal file
BIN
examples/spectrum-scanner/tools/example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
162
examples/spectrum-scanner/tools/plot_rssi.py
Executable file
162
examples/spectrum-scanner/tools/plot_rssi.py
Executable 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)
|
Loading…
Reference in New Issue
Block a user