mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-11 16:40:17 +00:00
Add radio simulation
This commit is contained in:
1 parent
9337a78cd0
commit
d39519b92f
66 files changed
+5659
-1000
No files matched your search
+437
-94
@@ -1,20 +1,25 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Radio mesh simulator. Bridges and nodes are stations at positions in an
|
||||
// area. Time is virtual: see mg_millis(). On the LAN: a DHCP server, and a
|
||||
// test station that sends HTTP requests to the nodes
|
||||
// Radio mesh simulator. Bridges and radio nodes have positions in an area. Time
|
||||
// is virtual: see mg_millis(). On the LAN: a DHCP server, and a test node that
|
||||
// sends HTTP requests to the nodes
|
||||
|
||||
#include "simulator.h"
|
||||
|
||||
#define STEP_MICROSECONDS 250 // Virtual time per main loop step, see README.md
|
||||
#define STEP_MICROSECONDS 1000 // Periodic stack timer polling only
|
||||
|
||||
static const struct mg_mesh *s_mesh = &mg_mesh_default;
|
||||
static struct simulator s_sim; // The simulator
|
||||
|
||||
// Mongoose's clock: virtual time. A minute of network traffic takes far less
|
||||
// than a minute to simulate
|
||||
uint64_t mg_micros(void) {
|
||||
return s_sim.now;
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return s_sim.now / 1000;
|
||||
return mg_micros() / 1000;
|
||||
}
|
||||
|
||||
// Parse "X,Y,Z", or "X,Y" with Z 0. Return false if it is not that
|
||||
@@ -40,9 +45,14 @@ static void usage(const struct simulator *sim, const char *prog) {
|
||||
" -bridge X,Y,Z - a bridge; repeat for more\n"
|
||||
" -node X,Y,Z - a radio node; repeat for more\n"
|
||||
" -t SEC - simulated time, seconds, default: %g\n"
|
||||
" -w SEC - mesh settle time before requests, default: %g\n",
|
||||
" -w SEC - mesh settle time before requests, default: %g\n"
|
||||
" -range METRES - radio range, default: %g\n"
|
||||
" -loss PERCENT - random reception loss, default: %g\n"
|
||||
" -m A|default - mesh strategy, default: default\n",
|
||||
prog, sim->area.x, sim->area.y, sim->area.z,
|
||||
(double) sim->duration / 1e6, (double) sim->tester.wait / 1e6);
|
||||
(double) (int64_t) sim->duration / 1e6,
|
||||
(double) (int64_t) sim->tester.wait / 1e6, sim->radio_range,
|
||||
sim->radio_loss);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -57,7 +67,9 @@ static void next_mac(struct simulator *sim, uint8_t *mac) {
|
||||
static bool add_bridge(struct simulator *sim, const char *s) {
|
||||
struct bridge *b = (struct bridge *) calloc(1, sizeof(*b));
|
||||
if (b == NULL || !parse_pos(s, &b->pos)) return free(b), false;
|
||||
if (sim->last_mac >= 0xffffu - 1u) return free(b), false;
|
||||
next_mac(sim, b->rif.mac);
|
||||
b->radio.address = (uint16_t) sim->last_mac;
|
||||
LIST_ADD_TAIL(struct bridge, &sim->bridges, b);
|
||||
sim->num_bridges++;
|
||||
return true;
|
||||
@@ -67,7 +79,9 @@ static bool add_bridge(struct simulator *sim, const char *s) {
|
||||
static bool add_node(struct simulator *sim, const char *s) {
|
||||
struct radio_node *n = (struct radio_node *) calloc(1, sizeof(*n));
|
||||
if (n == NULL || !parse_pos(s, &n->pos)) return free(n), false;
|
||||
if (sim->last_mac >= 0xffffu - 1u) return free(n), false;
|
||||
next_mac(sim, n->rif.mac);
|
||||
n->radio.address = (uint16_t) sim->last_mac;
|
||||
LIST_ADD_TAIL(struct radio_node, &sim->nodes, n);
|
||||
sim->num_nodes++;
|
||||
return true;
|
||||
@@ -95,15 +109,18 @@ static void check_area(const struct simulator *sim) {
|
||||
// it is not the sender, from
|
||||
static void lan_put(struct mg_tcpip_if *to, struct mg_tcpip_if *from,
|
||||
const void *buf, size_t len) {
|
||||
if (to != from) mg_tcpip_qwrite((void *) buf, len, to);
|
||||
if (to != from) {
|
||||
mg_tcpip_qwrite((void *) buf, len, to);
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// LAN driver, all LAN interfaces share it. The LAN is infinitely fast: tx
|
||||
// puts the frame straight into the receive queues of the interfaces it is
|
||||
// for: all, if broadcast; else the one with the destination MAC. Bridges get
|
||||
// every frame: some are for their radio nodes. Each station takes frames
|
||||
// from its queue when polled, see dhcp_poll(), bridges_poll(); Mongoose does
|
||||
// it for the test station
|
||||
// every frame: some are for their radio nodes. Each node takes frames
|
||||
// from its queue when polled, see dhcp_poll() and the mesh bridge callback;
|
||||
// Mongoose does it for the test node
|
||||
static size_t lan_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
const uint8_t *dst = (const uint8_t *) buf;
|
||||
bool all = dst[0] & 1; // Broadcast, multicast
|
||||
@@ -143,7 +160,7 @@ static uint16_t ip_csum(const uint8_t *buf, size_t len) {
|
||||
}
|
||||
|
||||
// A node's address, from its MAC 02:00:00:X:Y:Z: 10.X+1.Y.Z, so it never
|
||||
// clashes with the LAN stations' 10.0.0.x
|
||||
// clashes with the LAN nodes' 10.0.0.x
|
||||
static void node_ip(const uint8_t *mac, uint8_t *ip) {
|
||||
ip[0] = 10, ip[1] = (uint8_t) (mac[3] + 1), ip[2] = mac[4], ip[3] = mac[5];
|
||||
}
|
||||
@@ -192,19 +209,42 @@ static void dhcp_rx(struct simulator *sim, const uint8_t *buf, size_t len) {
|
||||
sim->dhcp.lif.driver->tx(r, rlen, &sim->dhcp.lif);
|
||||
}
|
||||
|
||||
// The DHCP node also owns the advertised gateway IP, 10.0.0.1.
|
||||
static bool dhcp_arp(struct simulator *sim, const uint8_t *f, size_t n) {
|
||||
static const uint8_t header[] = {0, 1, 8, 0, 6, 4, 0, 1};
|
||||
static const uint8_t ip[] = {10, 0, 0, 1};
|
||||
uint8_t reply[60];
|
||||
if (n < 42 || f[12] != 8 || f[13] != 6 ||
|
||||
memcmp(f + 14, header, sizeof(header)) != 0 ||
|
||||
memcmp(f + 38, ip, sizeof(ip)) != 0)
|
||||
return false;
|
||||
memset(reply, 0, sizeof(reply));
|
||||
memcpy(reply, f + 6, 6);
|
||||
memcpy(reply + 6, sim->dhcp.lif.mac, 6);
|
||||
memcpy(reply + 12, f + 12, 8);
|
||||
reply[21] = 2;
|
||||
memcpy(reply + 22, sim->dhcp.lif.mac, 6);
|
||||
memcpy(reply + 28, ip, sizeof(ip));
|
||||
memcpy(reply + 32, f + 22, 10);
|
||||
sim->dhcp.lif.driver->tx(reply, sizeof(reply), &sim->dhcp.lif);
|
||||
return true;
|
||||
}
|
||||
|
||||
// DHCP server's work: take the frames from its queue
|
||||
static void dhcp_poll(struct simulator *sim) {
|
||||
char *buf;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&sim->dhcp.lif.recv_queue, &buf)) > 0) {
|
||||
dhcp_rx(sim, (const uint8_t *) buf, len);
|
||||
if (!dhcp_arp(sim, (const uint8_t *) buf, len)) {
|
||||
dhcp_rx(sim, (const uint8_t *) buf, len);
|
||||
}
|
||||
mg_queue_del(&sim->dhcp.lif.recv_queue, len);
|
||||
}
|
||||
}
|
||||
|
||||
// The request on the way finished: ok, or failed
|
||||
static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
|
||||
double ms = (double) (now - t->start) / 1000;
|
||||
double ms = (double) (int64_t) (now - t->start) / 1000;
|
||||
if (ok) {
|
||||
t->ok++, t->sum += ms;
|
||||
if (t->ok == 1 || ms < t->min) t->min = ms;
|
||||
@@ -214,6 +254,7 @@ static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
|
||||
}
|
||||
if (t->c != NULL) t->c->is_closing = 1, t->c->fn_data = NULL;
|
||||
t->c = NULL;
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
|
||||
// HTTP client: GET /, the reply ends the request
|
||||
@@ -230,8 +271,8 @@ static void tester_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
// Test station's work: Mongoose, then requests, one at a time, once the mesh
|
||||
// had time to settle. A node without an IP address fails at once
|
||||
// Test node's work: Mongoose, then requests, one at a time, once the mesh
|
||||
// had time to settle. Cycle through nodes until five seconds before the end.
|
||||
static void tester_poll(struct simulator *sim) {
|
||||
struct radio_node *n;
|
||||
char url[64];
|
||||
@@ -241,28 +282,34 @@ static void tester_poll(struct simulator *sim) {
|
||||
tester_done(&sim->tester, sim->now, false);
|
||||
}
|
||||
if (sim->now < sim->tester.wait || sim->tester.c != NULL ||
|
||||
sim->tester.next == NULL)
|
||||
sim->tester.next == NULL || sim->duration <= 5000000U ||
|
||||
sim->now >= sim->duration - 5000000U)
|
||||
return;
|
||||
n = sim->tester.next;
|
||||
sim->tester.next = n->next, sim->tester.start = sim->now;
|
||||
if (n->rif.ip == 0) {
|
||||
tester_done(&sim->tester, sim->now, false);
|
||||
sim->tester.next = n->next == NULL ? sim->nodes : n->next;
|
||||
if (n->mgr.ifp == NULL) return;
|
||||
sim->tester.start = sim->now;
|
||||
if (n->mgr.ifp->ip == 0) {
|
||||
// Wait for the next poll rather than endlessly cycling at this time.
|
||||
sim->tester.failed++;
|
||||
return;
|
||||
}
|
||||
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->rif.ip);
|
||||
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->mgr.ifp->ip);
|
||||
if ((sim->tester.c = mg_http_connect(&sim->tester.mgr, url, tester_fn,
|
||||
&sim->tester)) == NULL) {
|
||||
tester_done(&sim->tester, sim->now, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up sim: defaults, LAN stations
|
||||
// Set up sim: defaults, LAN nodes
|
||||
static void init(struct simulator *sim) {
|
||||
static const uint8_t dmac[6] = {2, 1, 0, 0, 0, 0}, // DHCP server MAC
|
||||
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test station MAC
|
||||
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test node MAC
|
||||
memset(sim, 0, sizeof(*sim));
|
||||
sim->area.x = sim->area.y = 100;
|
||||
sim->duration = 600 * 1000000ULL, sim->tester.wait = 60 * 1000000ULL;
|
||||
sim->radio_range = 185;
|
||||
sim->loss_random = 1;
|
||||
sim->duration = 600 * 1000000U, sim->tester.wait = 60 * 1000000U;
|
||||
sim->request_timeout_ms = 10000;
|
||||
memcpy(sim->dhcp.lif.mac, dmac, 6), sim->dhcp.lif.driver = &s_lan_driver;
|
||||
mg_queue_init(&sim->dhcp.lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||
@@ -275,42 +322,258 @@ static void init(struct simulator *sim) {
|
||||
sim->tester.lif.recv_queue.size = 128 * 1024;
|
||||
}
|
||||
|
||||
// Radio driver, all radio interfaces share it. An ideal radio for now:
|
||||
// whole Ethernet frames, no range, no loss, no airtime. It works like the
|
||||
// LAN, see lan_tx(): a frame goes into the receive queues of all other
|
||||
// radio interfaces if broadcast, else of the one with the destination MAC.
|
||||
// Bridges get every frame: some are for the LAN. Who forwards what, and
|
||||
// loops with several bridges, are for mg_mesh
|
||||
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
const uint8_t *dst = (const uint8_t *) buf;
|
||||
bool all = dst[0] & 1; // Broadcast, multicast
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
lan_put(&b->rif, ifp, buf, len);
|
||||
// The driver queues each submitted frame for transmission over the air.
|
||||
// All nodes hear one channel. CCA and turnaround are separate events:
|
||||
// two idle checks can succeed before either node starts transmitting.
|
||||
// Inter-frame spacing is per sender, not a reservation of the shared air.
|
||||
uint64_t radio_airtime(size_t len) {
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||
radio->bitrate;
|
||||
}
|
||||
|
||||
static void radio_schedule(struct simulator *sim, struct radio_frame *f,
|
||||
bool busy) {
|
||||
struct radio_port *port = (struct radio_port *) f->from->driver_data;
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
bool checked = f->state == RADIO_CCA;
|
||||
if (s_mesh->scheduled_tx || !radio->csma || (checked && !busy)) {
|
||||
f->state = RADIO_START;
|
||||
f->due = sim->now + (checked ? radio->turnaround_us : 0);
|
||||
return;
|
||||
}
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
if (all || memcmp(dst, n->rif.mac, 6) == 0) {
|
||||
lan_put(&n->rif, ifp, buf, len);
|
||||
if (!checked) {
|
||||
f->backoff = radio->backoff_min;
|
||||
} else if (f->backoff < radio->backoff_max) {
|
||||
f->backoff++;
|
||||
}
|
||||
if (port->random == 0) {
|
||||
size_t i;
|
||||
port->random = (uint32_t) mg_now();
|
||||
for (i = 0; i < 6; i++) {
|
||||
port->random = port->random * 33u + f->from->mac[i];
|
||||
}
|
||||
if (port->random == 0) port->random = 1;
|
||||
}
|
||||
port->random ^= port->random << 13;
|
||||
port->random ^= port->random >> 17;
|
||||
port->random ^= port->random << 5;
|
||||
f->state = RADIO_BACKOFF;
|
||||
f->due = sim->now + (port->random % (1U << f->backoff)) * radio->slot_us;
|
||||
if (f->due > sim->now) port->rx_off_until = f->due;
|
||||
}
|
||||
|
||||
static size_t radio_send(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
|
||||
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||
struct simulator *sim = port->sim;
|
||||
struct radio_frame *f, **p = &sim->air_head;
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
if (len < radio->header_len || len > sizeof(f->buf) ||
|
||||
len > radio->max_frame_size || port->tx_ready_at > sim->now ||
|
||||
radio->bitrate == 0)
|
||||
return 0;
|
||||
while (*p != NULL) {
|
||||
if ((*p)->from == ifp) return 0;
|
||||
p = &(*p)->next;
|
||||
}
|
||||
if ((f = (struct radio_frame *) calloc(1, sizeof(*f))) == NULL) return 0;
|
||||
f->from = ifp, f->len = len;
|
||||
memcpy(f->buf, buf, len);
|
||||
*p = f;
|
||||
sim->work_pending = true;
|
||||
radio_schedule(sim, f, false);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void radio_receive(struct radio_port *port,
|
||||
const struct radio_frame *f) {
|
||||
char *buf;
|
||||
if (mg_queue_book(&port->rx, &buf, f->len) >= f->len) {
|
||||
memcpy(buf, f->buf, f->len);
|
||||
mg_queue_add(&port->rx, f->len);
|
||||
port->sim->work_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test range and sample independent interference loss for this reception.
|
||||
static bool visible(struct simulator *sim, const struct pos *a,
|
||||
const struct pos *b) {
|
||||
double dx = a->x - b->x, dy = a->y - b->y, dz = a->z - b->z;
|
||||
double range = sim->radio_range;
|
||||
if (dx * dx + dy * dy + dz * dz > range * range) return false;
|
||||
if (sim->radio_loss == 0) return true;
|
||||
sim->loss_random ^= sim->loss_random << 13;
|
||||
sim->loss_random ^= sim->loss_random >> 17;
|
||||
sim->loss_random ^= sim->loss_random << 5;
|
||||
return (double) sim->loss_random * (100.0 / 4294967296.0) >= sim->radio_loss;
|
||||
}
|
||||
|
||||
static void radio_deliver_to(struct radio_port *port,
|
||||
const struct radio_port *from,
|
||||
const struct radio_frame *f, uint16_t dst) {
|
||||
bool all = dst == 0xffffu;
|
||||
if (port == from || (!all && port->address != dst)) return;
|
||||
if (!visible(port->sim, from->pos, port->pos)) {
|
||||
port->sim->air_lost++;
|
||||
} else if (port->tx_until > f->start) {
|
||||
if (!all) port->sim->air_lost++;
|
||||
} else if (!f->collided && port->rx_off_until <= f->start) {
|
||||
radio_receive(port, f);
|
||||
}
|
||||
}
|
||||
|
||||
static void radio_deliver(struct simulator *sim, const struct radio_frame *f) {
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
const struct radio_port *from = f->from->driver_data;
|
||||
uint16_t dst, src;
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
if (radio->parse_hdr(radio, f->buf, f->len, &dst, &src) == 0) return;
|
||||
for (b = sim->bridges; b != NULL; b = b->next)
|
||||
radio_deliver_to(&b->radio, from, f, dst);
|
||||
for (n = sim->nodes; n != NULL; n = n->next)
|
||||
radio_deliver_to(&n->radio, from, f, dst);
|
||||
}
|
||||
|
||||
// Process ends, then all channel checks, then starts at this timestamp.
|
||||
// Checks therefore see the same air state regardless of list order.
|
||||
static void radio_poll(struct simulator *sim) {
|
||||
struct radio_frame **p = &sim->air_head, *f, *g;
|
||||
while ((f = *p) != NULL) {
|
||||
if (f->state != RADIO_END || f->due > sim->now) {
|
||||
p = &f->next;
|
||||
continue;
|
||||
}
|
||||
if (f->collided) {
|
||||
sim->air_collisions++;
|
||||
}
|
||||
radio_deliver(sim, f);
|
||||
*p = f->next;
|
||||
free(f);
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->state == RADIO_BACKOFF && f->due <= sim->now) {
|
||||
f->state = RADIO_CCA;
|
||||
f->due = sim->now + s_mesh->radio->cca_us;
|
||||
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||
}
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->state != RADIO_CCA || f->due > sim->now) continue;
|
||||
radio_schedule(sim, f,
|
||||
sim->air_busy_until > sim->now - s_mesh->radio->cca_us);
|
||||
if (f->state == RADIO_START) {
|
||||
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||
}
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
struct radio_port *port = f->from->driver_data;
|
||||
if (f->state != RADIO_START || f->due > sim->now) continue;
|
||||
f->state = RADIO_END;
|
||||
f->start = sim->now;
|
||||
f->due = sim->now + radio_airtime(f->len);
|
||||
port->rx_off_until = port->tx_until = f->due;
|
||||
port->tx_ready_at = f->due + s_mesh->radio->ifs_us;
|
||||
for (g = sim->air_head; g != NULL; g = g->next) {
|
||||
if (g != f && g->state == RADIO_END && g->due > sim->now) {
|
||||
f->collided = g->collided = true;
|
||||
}
|
||||
}
|
||||
if (f->due > sim->air_busy_until) sim->air_busy_until = f->due;
|
||||
sim->air_sent++;
|
||||
}
|
||||
}
|
||||
|
||||
// Radio events keep microsecond precision between periodic timer polls.
|
||||
static uint64_t radio_next(const struct simulator *sim, uint64_t next) {
|
||||
const struct radio_frame *f;
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->due < next) next = f->due;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
static size_t radio_receive_frame(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||
char *src;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&port->rx, &src)) > 0) {
|
||||
if (len <= cap) memcpy(buf, src, len);
|
||||
mg_queue_del(&port->rx, len);
|
||||
if (len <= cap) {
|
||||
if (mg_queue_next(&port->rx, &src) > 0) port->sim->work_pending = true;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
return port->mesh->strategy->tx(port->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t radio_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
size_t len = port->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
port->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool radio_driver_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
uint8_t frame[128];
|
||||
size_t len;
|
||||
(void) s1;
|
||||
port->mesh->now = mg_micros();
|
||||
while (port->mesh->rx_len == 0 &&
|
||||
(len = radio_receive_frame(frame, sizeof(frame), ifp)) != 0)
|
||||
port->mesh->strategy->rx(port->mesh, frame, len);
|
||||
s_mesh->tick(port->mesh, mg_micros());
|
||||
return port->mesh->joined;
|
||||
}
|
||||
|
||||
static bool radio_init(struct mg_tcpip_if *ifp) {
|
||||
(void) ifp;
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
char *buf = malloc(LAN_QUEUE_SIZE);
|
||||
if (buf == NULL) return false;
|
||||
mg_queue_init(&port->rx, buf, LAN_QUEUE_SIZE);
|
||||
port->mesh = calloc(1, sizeof(*port->mesh));
|
||||
if (port->mesh == NULL ||
|
||||
!s_mesh->init(port->mesh, ifp, port->lan, port->address)) {
|
||||
free(port->mesh);
|
||||
port->mesh = NULL;
|
||||
free(buf);
|
||||
port->rx.buf = NULL;
|
||||
return false;
|
||||
}
|
||||
port->mesh->strategy = s_mesh;
|
||||
port->mesh->tx = radio_send;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool radio_link(struct mg_tcpip_if *ifp, bool s1) {
|
||||
(void) ifp, (void) s1;
|
||||
return true;
|
||||
}
|
||||
static struct mg_tcpip_driver s_radio_driver = {
|
||||
radio_init, radio_tx, radio_rx, radio_driver_poll};
|
||||
|
||||
static struct mg_tcpip_driver s_radio_driver = {radio_init, radio_tx, NULL,
|
||||
radio_link};
|
||||
#define MG_TCPIP_DRIVER_SIM_RADIO_INIT(manager, interface) \
|
||||
do { \
|
||||
struct mg_tcpip_if *mif_ = (interface); \
|
||||
mif_->mgr = (manager); \
|
||||
mg_tcpip_init((manager), mif_); \
|
||||
MG_INFO(("Driver: sim_radio, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||
} while (0)
|
||||
|
||||
// A node's HTTP server: the test station requests GET /
|
||||
#define MG_TCPIP_DRIVER_SIM_LAN_INIT(manager, interface) \
|
||||
do { \
|
||||
struct mg_tcpip_if *mif_ = (interface); \
|
||||
mg_tcpip_init((manager), mif_); \
|
||||
MG_INFO(("Driver: sim_lan, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||
} while (0)
|
||||
|
||||
// A node's HTTP server: the test node requests GET /
|
||||
static void node_http_ev_handler(struct mg_connection *c, int ev,
|
||||
void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
@@ -326,56 +589,38 @@ static void nodes_init(struct simulator *sim) {
|
||||
struct radio_node *n;
|
||||
for (n = sim->nodes; n != NULL; n = n->next) {
|
||||
n->rif.driver = &s_radio_driver;
|
||||
n->rif.recv_queue.size = LAN_QUEUE_SIZE;
|
||||
n->rif.driver_data = &n->radio;
|
||||
n->radio.sim = sim;
|
||||
n->radio.pos = &n->pos;
|
||||
mg_mgr_init(&n->mgr);
|
||||
mg_tcpip_init(&n->mgr, &n->rif);
|
||||
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
|
||||
n->rif.mac);
|
||||
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&n->mgr, &n->rif);
|
||||
if (n->mgr.ifp != NULL) {
|
||||
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
|
||||
n->mgr.ifp->mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start each bridge: its LAN and radio drivers. No Mongoose, no IP
|
||||
// Start each bridge: its LAN and radio drivers, with no IP stack.
|
||||
static void bridges_init(struct simulator *sim) {
|
||||
struct bridge *b;
|
||||
for (b = sim->bridges; b != NULL; b = b->next) {
|
||||
b->lif.driver = &s_lan_driver, b->rif.driver = &s_radio_driver;
|
||||
b->rif.driver_data = &b->radio;
|
||||
b->radio.sim = sim;
|
||||
b->radio.pos = &b->pos;
|
||||
mg_mgr_init(&b->mgr);
|
||||
b->radio.lan = &b->lif;
|
||||
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&b->mgr, &b->rif);
|
||||
mg_queue_init(&b->lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||
LAN_QUEUE_SIZE);
|
||||
mg_queue_init(&b->rif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||
LAN_QUEUE_SIZE);
|
||||
b->lif.driver->init(&b->lif), b->rif.driver->init(&b->rif);
|
||||
b->lif.driver->init(&b->lif);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the frames waiting in the receive queue of interface from out of
|
||||
// interface to
|
||||
static void bridge_forward(struct mg_tcpip_if *from, struct mg_tcpip_if *to) {
|
||||
char *buf;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&from->recv_queue, &buf)) > 0) {
|
||||
// MG_DEBUG(("Bridging ETH frame, size %zu", len));
|
||||
to->driver->tx(buf, len, to);
|
||||
mg_queue_del(&from->recv_queue, len);
|
||||
}
|
||||
}
|
||||
|
||||
// Bridges' work: forward every frame, LAN to radio and radio to LAN. No
|
||||
// filtering yet
|
||||
static void bridges_poll(struct simulator *sim) {
|
||||
struct bridge *b;
|
||||
for (b = sim->bridges; b != NULL; b = b->next) {
|
||||
bridge_forward(&b->lif, &b->rif);
|
||||
bridge_forward(&b->rif, &b->lif);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll each node's Mongoose
|
||||
static void nodes_poll(struct simulator *sim) {
|
||||
struct radio_node *n;
|
||||
for (n = sim->nodes; n != NULL; n = n->next) mg_mgr_poll(&n->mgr, 0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
uint64_t poll_at = 0, mesh_at = (uint64_t) -1;
|
||||
char *pending;
|
||||
clock_t started; // CPU time, to show how fast the simulation runs
|
||||
int i;
|
||||
|
||||
@@ -393,32 +638,82 @@ int main(int argc, char *argv[]) {
|
||||
s_sim.duration = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||
} else if (strcmp(argv[i], "-w") == 0) {
|
||||
s_sim.tester.wait = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||
} else if (strcmp(argv[i], "-range") == 0 ||
|
||||
strcmp(argv[i], "-loss") == 0) {
|
||||
bool loss = strcmp(argv[i], "-loss") == 0;
|
||||
const char *arg = argv[++i];
|
||||
char *end;
|
||||
double value = strtod(arg, &end);
|
||||
if (end == arg || *end != '\0' ||
|
||||
!(value >= 0 && value <= (loss ? 100 : 1e100)))
|
||||
usage(&s_sim, argv[0]);
|
||||
if (loss)
|
||||
s_sim.radio_loss = value;
|
||||
else
|
||||
s_sim.radio_range = value;
|
||||
} else if (strcmp(argv[i], "-m") == 0) {
|
||||
const char *name = argv[++i];
|
||||
if (strcmp(name, mg_mesh_a.name) == 0)
|
||||
s_mesh = &mg_mesh_a;
|
||||
else if (strcmp(name, mg_mesh_default.name) == 0)
|
||||
s_mesh = &mg_mesh_default;
|
||||
else
|
||||
usage(&s_sim, argv[0]);
|
||||
} else {
|
||||
usage(&s_sim, argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Area %gx%gx%g m, radio %s, mesh %s, bridges: %d, nodes: %d\n",
|
||||
s_sim.area.x, s_sim.area.y, s_sim.area.z, mg_radio_802154.name,
|
||||
mg_mesh_star.name, s_sim.num_bridges, s_sim.num_nodes);
|
||||
s_sim.area.x, s_sim.area.y, s_sim.area.z, s_mesh->radio->name,
|
||||
s_mesh->name, s_sim.num_bridges, s_sim.num_nodes);
|
||||
check_area(&s_sim);
|
||||
if (s_mesh->single_bridge && s_sim.num_bridges != 1) {
|
||||
fprintf(stderr, "%s requires exactly one bridge\n", s_mesh->name);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// mg_log_set(MG_LL_INFO);
|
||||
|
||||
mg_mgr_init(&s_sim.tester.mgr);
|
||||
mg_tcpip_init(&s_sim.tester.mgr, &s_sim.tester.lif);
|
||||
MG_TCPIP_DRIVER_SIM_LAN_INIT(&s_sim.tester.mgr, &s_sim.tester.lif);
|
||||
s_sim.tester.next = s_sim.nodes;
|
||||
bridges_init(&s_sim);
|
||||
nodes_init(&s_sim);
|
||||
|
||||
// Main loop: STEP_MICROSECONDS of virtual time per step
|
||||
// Drain packet processing at the current time. Only idle timer polling
|
||||
// waits for STEP_MICROSECONDS; radio deadlines remain exact.
|
||||
started = clock();
|
||||
while (s_sim.now < s_sim.duration) {
|
||||
dhcp_poll(&s_sim);
|
||||
tester_poll(&s_sim);
|
||||
bridges_poll(&s_sim);
|
||||
nodes_poll(&s_sim);
|
||||
s_sim.now += STEP_MICROSECONDS;
|
||||
radio_poll(&s_sim);
|
||||
if (s_sim.now == poll_at || s_sim.now == mesh_at || s_sim.work_pending) {
|
||||
do {
|
||||
s_sim.work_pending = false;
|
||||
dhcp_poll(&s_sim);
|
||||
tester_poll(&s_sim);
|
||||
{
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
mesh_at = (uint64_t) -1;
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
mg_mgr_poll(&b->mgr, 0);
|
||||
if (b->radio.mesh->next_tick < mesh_at)
|
||||
mesh_at = b->radio.mesh->next_tick;
|
||||
}
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
mg_mgr_poll(&n->mgr, 0);
|
||||
if (n->radio.mesh->next_tick < mesh_at)
|
||||
mesh_at = n->radio.mesh->next_tick;
|
||||
}
|
||||
}
|
||||
if (mg_queue_next(&s_sim.tester.lif.recv_queue, &pending) > 0) {
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
} while (s_sim.work_pending);
|
||||
if (s_sim.now == poll_at) poll_at += STEP_MICROSECONDS;
|
||||
}
|
||||
s_sim.now = radio_next(&s_sim, mesh_at < poll_at ? mesh_at : poll_at);
|
||||
if (s_sim.now > s_sim.duration) s_sim.now = s_sim.duration;
|
||||
}
|
||||
printf("Requests: %d ok, %d failed, of %d nodes", s_sim.tester.ok,
|
||||
s_sim.tester.failed, s_sim.num_nodes);
|
||||
@@ -426,8 +721,56 @@ int main(int argc, char *argv[]) {
|
||||
printf("; ms: min %.1f, mean %.1f, max %.1f", s_sim.tester.min,
|
||||
s_sim.tester.sum / s_sim.tester.ok, s_sim.tester.max);
|
||||
}
|
||||
printf("\nSimulated %g s in %.3f s\n", (double) s_sim.now / 1e6,
|
||||
printf("\nSimulated %g s in %.3f s", (double) (int64_t) s_sim.now / 1e6,
|
||||
(double) (clock() - started) / CLOCKS_PER_SEC);
|
||||
if (s_sim.nodes != NULL) {
|
||||
const struct radio_node *n;
|
||||
size_t min = (size_t) -1, max = 0;
|
||||
double total = 0;
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
size_t peak = n->radio.mesh->ram_peak;
|
||||
if (peak < min) min = peak;
|
||||
if (peak > max) max = peak;
|
||||
total += (double) peak;
|
||||
}
|
||||
printf("; peak mesh RAM per node (bytes): min %zu, max %zu, avg %.1f", min,
|
||||
max, total / s_sim.num_nodes);
|
||||
}
|
||||
putchar('\n');
|
||||
|
||||
while (s_sim.air_head != NULL) {
|
||||
struct radio_frame *f = s_sim.air_head;
|
||||
s_sim.air_head = f->next;
|
||||
free(f);
|
||||
}
|
||||
printf("Radio: %lu sent, %lu collided, %lu lost\n",
|
||||
s_sim.air_sent, s_sim.air_collisions, s_sim.air_lost);
|
||||
{
|
||||
struct radio_node *n;
|
||||
int count = 0, joined = 0;
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
if (n->mgr.ifp != NULL && n->mgr.ifp->ip != 0) count++;
|
||||
if (n->radio.mesh->joined) joined++;
|
||||
}
|
||||
printf("Nodes with IP: %d of %d, joined: %d of %d\n", count,
|
||||
s_sim.num_nodes, joined, s_sim.num_nodes);
|
||||
}
|
||||
{
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
mg_log_set(MG_LL_NONE);
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
mg_mgr_free(&b->mgr);
|
||||
s_mesh->free(b->radio.mesh);
|
||||
free(b->radio.mesh);
|
||||
free(b->radio.rx.buf);
|
||||
}
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
mg_mgr_free(&n->mgr);
|
||||
s_mesh->free(n->radio.mesh);
|
||||
free(n->radio.mesh);
|
||||
free(n->radio.rx.buf);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user