diff --git a/dist/tools/zep_dispatch/.gitignore b/dist/tools/zep_dispatch/.gitignore index 0a293e49d7..3c13b01d75 100644 --- a/dist/tools/zep_dispatch/.gitignore +++ b/dist/tools/zep_dispatch/.gitignore @@ -1,2 +1,3 @@ *.gv *.pdf +*.svg diff --git a/dist/tools/zep_dispatch/Makefile b/dist/tools/zep_dispatch/Makefile index 9e4ffed41f..95f5a3fef3 100644 --- a/dist/tools/zep_dispatch/Makefile +++ b/dist/tools/zep_dispatch/Makefile @@ -2,8 +2,9 @@ CFLAGS ?= -g -O3 -Wall -Wextra CFLAGS += $(RIOT_INCLUDE) CFLAGS += -DNDEBUG # avoid assert re-definition -BINARY := bin/zep_dispatch -all: bin $(BINARY) +DISPATCH := bin/zep_dispatch +TOPOGEN := bin/topogen +all: $(DISPATCH) $(TOPOGEN) bin: mkdir bin @@ -11,26 +12,32 @@ bin: RIOTBASE := ../../.. ZEP_PORT_BASE ?= 17754 -TOPOLOGY ?= example.topo -GV_OUT ?= example.gv +TOPOLOGY ?= network.topo +GV_OUT ?= $(TOPOLOGY).gv RIOT_INCLUDE += -I$(RIOTBASE)/core/include RIOT_INCLUDE += -I$(RIOTBASE)/cpu/native/include RIOT_INCLUDE += -I$(RIOTBASE)/drivers/include RIOT_INCLUDE += -I$(RIOTBASE)/sys/include -SRCS := $(wildcard *.c) +SRCS := main.c topology.c zep_parser.c SRCS += $(RIOTBASE)/sys/net/link_layer/ieee802154/ieee802154.c -$(BINARY): $(SRCS) +$(DISPATCH): $(SRCS) bin $(CC) $(CFLAGS) $(CFLAGS_EXTRA) $(SRCS) -o $@ +$(TOPOGEN): topogen.c bin + $(CC) $(CFLAGS) $< -o $@ -lm + .PHONY: clean run graph help clean: - rm -f $(BINARY) + rm -fr bin -run: $(BINARY) - $(BINARY) -t $(TOPOLOGY) -g $(GV_OUT) ::1 $(ZEP_PORT_BASE) +run: $(DISPATCH) $(TOPOLOGY) + $(DISPATCH) -t $(TOPOLOGY) -g $(GV_OUT) ::1 $(ZEP_PORT_BASE) + +$(TOPOLOGY): $(TOPOGEN) + ./topogen.sh $(TOPOLOGY) graph: killall -USR1 zep_dispatch diff --git a/dist/tools/zep_dispatch/topogen.c b/dist/tools/zep_dispatch/topogen.c new file mode 100644 index 0000000000..bfe8647eb5 --- /dev/null +++ b/dist/tools/zep_dispatch/topogen.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2021 Benjamin Valentin + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct node { + char name[8]; + int x; + int y; + unsigned r; + bool rx_from_root; + bool tx_to_root; +}; + +struct world { + unsigned w; + unsigned h; + unsigned num_nodes; + struct node *nodes; +}; + +static unsigned random_range(unsigned lower, unsigned upper) +{ + unsigned range = upper - lower + 1; + return lower + rand() % range; +} + +static struct node *node_generate(struct node *n, const struct world *w, + unsigned range) +{ + n->x = random_range(0, w->w); + n->y = random_range(0, w->h); + n->r = range; + return n; +} + +static double node_distance(const struct node *a, const struct node *b) +{ + return sqrt(pow(a->x - b->x, 2) + pow(a->y - b->y, 2)); +} + +static double node_distance_weight(const struct node *a, const struct node *b) +{ + double w = 1 - node_distance(a, b) / a->r; + + if (w < 0) { + return 0; + } + return w; +} + +static void node_name(struct node *n, unsigned idx) +{ + char *s = n->name; + const char *end = s + sizeof(n->name) - 1; + + do { + uint8_t rem = idx % 26; + *s++ = 'A' + rem; + idx -= rem; + } while (idx && s != end); + *s = 0; +} + +static void world_gen(struct world *w, unsigned num_nodes, + unsigned width, unsigned height, + unsigned range, unsigned var) +{ + w->w = width; + w->h = height; + w->num_nodes = num_nodes; + w->nodes = calloc(num_nodes, sizeof(*w->nodes)); + + for (unsigned i = 0; i < num_nodes; ++i) { + node_generate(&w->nodes[i], w, random_range(range - var, range + var)); + node_name(&w->nodes[i], i); + } +} + +static unsigned _color(const struct node *n, unsigned base) +{ + if (n->rx_from_root && n->tx_to_root) { + return base; + } else if (n->rx_from_root) { + return base / 2; + } else if (n->tx_to_root) { + return base / 3; + } + + return 0; +} + +static void _print_nodes(struct node *nodes, unsigned num, unsigned color) +{ + for (unsigned i = 0; i < num; ++i) { + printf("# %s\t%d\t%d\t%u\t0x%x\n", nodes[i].name, + nodes[i].x, nodes[i].y, + nodes[i].r, _color(&nodes[i], color)); + } +} + +/* To visualize the network we color the nodes based on whether they have + * a (bi-directional) connection to the root node via some other node(s). + */ +static void _calc_connections(struct node *nodes, unsigned num) +{ + bool changes = true; + nodes->rx_from_root = true; + nodes->tx_to_root = true; + + /* super basic algorithm - just loop unti there are no more changes + * in the node's connection states. */ + while (changes) { + changes = false; + + for (unsigned i = 0; i < num; ++i) { + const struct node *n = &nodes[i]; + + for (unsigned j = 0; j < num; ++j) { + struct node *m = &nodes[j]; + + /* node is already fully connected */ + if (m->rx_from_root && m->tx_to_root) { + continue; + } + + /* m can receive from n and n can receive from root */ + if (node_distance_weight(n, m) > 0) { + if (!m->rx_from_root && n->rx_from_root) { + m->rx_from_root = true; + changes = true; + } + } + + /* m can send to n and n can send to root */ + if (node_distance_weight(m, n) > 0) { + if (!m->tx_to_root && n->tx_to_root) { + m->tx_to_root = true; + changes = true; + } + } + } + } + } +} + +static void _print_distance(struct node *nodes, unsigned num, bool recursive) +{ + struct node *start = nodes; + + for (unsigned i = 1; i < num; ++i) { + struct node *n = &nodes[i]; + + double to_node = node_distance_weight(start, n); + double from_node = node_distance_weight(n, start); + + if (to_node > 0 || from_node > 0) { + printf("%s\t%s\t%.2f\t%.2f\n", start->name, n->name, to_node, from_node); + } + + if (recursive) { + _print_distance(n, num - i, false); + } + } +} + +static void _print_help(const char *name) +{ + puts("Generate a number of nodes that are randomly placed in a rectangular area"); + printf("usage: %s [-s ]" + " [-w ]" + " [-h ]" + " [-r ]" + " [-v ]" + " [-n ]" + "\n", name); +} + +int main(int argc, char** argv) +{ + const char *progname = argv[0]; + + unsigned width = 100; + unsigned height = 100; + unsigned seed = time(NULL); + unsigned range = 25; + unsigned var = 0; + unsigned num = 10; + char c; + + while ((c = getopt(argc, argv, "s:w:h:r:v:n:")) != -1) { + switch (c) { + case 's': + seed = atoi(optarg); + break; + case 'w': + width = atoi(optarg); + break; + case 'h': + height = atoi(optarg); + break; + case 'r': + range = atoi(optarg); + break; + case 'v': + var = atoi(optarg); + break; + case 'n': + num = atoi(optarg); + break; + default: + _print_help(progname); + exit(1); + } + } + + srand(seed); + + struct world w; + world_gen(&w, num, width, height, range, var); + + printf("# seed = %u\n", seed); + puts("# Connections"); + _print_distance(w.nodes, w.num_nodes, true); + puts(""); + puts(""); + puts("# Node\tX\tY\trange\tcolor"); + _calc_connections(w.nodes, w.num_nodes); + _print_nodes(w.nodes, w.num_nodes, rand()); + + return 0; +} diff --git a/dist/tools/zep_dispatch/topogen.sh b/dist/tools/zep_dispatch/topogen.sh new file mode 100755 index 0000000000..733bfbdc2e --- /dev/null +++ b/dist/tools/zep_dispatch/topogen.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +if [ -z "$1" ]; then + FILE=out.top +else + FILE="$1" +fi + +if tput colors &> /dev/null && [ "$(tput colors)" -ge 8 ]; then + COK="\e[1;32m" + CWARN="\e[1;33m" + CRESET="\e[0m" +else + COK= + CWARN= + CRESET= +fi + +WIDTH=100 +HEIGHT=100 +RANGE=30 +VARIANCE=15 +NUM=10 + +echo "writing to $FILE" + +./bin/topogen -w $WIDTH -h $HEIGHT -r $RANGE -v $VARIANCE -n $NUM > "$FILE" + +if ! command -v gnuplot > /dev/null; then + printf "${CWARN}%s${CRESET}\n" "gnuplot not installed" + exit 0 +fi + +printf "${COK}%s${CRESET}\n" "rendering to ${FILE}.svg" +gnuplot -e "set xrange [0:$WIDTH]" \ + -e "set yrange [0:$HEIGHT]" \ + -e "set terminal svg size $(( WIDTH * 8 )), $(( HEIGHT * 8 ))" \ + -e "set output '$FILE.svg'" \ + -e "set datafile commentschars '!'" \ + -e "set style fill transparent solid 0.25" \ + -e "plot '$FILE' index 1 using 3:4:(\$5*2):(\$5*2):6 with ellipses lc rgb var notitle, \ + '$FILE' index 1 using 3:4:2 with labels point pt 2 offset char 0.5,0.5 notitle"