mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-21 04:23:50 +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
|
||||||
|
|
||||||
|

|
||||||
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…
x
Reference in New Issue
Block a user