Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more NetworkMonitor examples #1

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/src-gen/
**/build/
**/bin/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ This repo contains artifacts such as scripts and sample programs used in demonst
## Demos

* [federated-decentralized](federated-decentralized/README.md)
* [network-monitor](network-monitor/README.md)
* [simulation](simulation/README.md)
* [mujoco](mujoco/README.md)
1 change: 1 addition & 0 deletions mujoco/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ The [mujoco.cmake](src/include/mujoco.cmake) file in the [mujoco-c library](http
## Demos

* [Car.lf](src/Car.lf): A simple car simulation driven with the arrow keys and providing sensor outputs.
* [CarAuto.lf](src/CarAuto.lf): A version of the car simulation where the simulator advances automatically.
33 changes: 22 additions & 11 deletions mujoco/src/Car.lf
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
/**
* Basic car driving program for Mujoco. This is the same as the MuJoCoCarDemo
* example in the mujoco-c library, which you need to install first using `lingo build`
* in this directory.
* @file Basic car driving program for MuJoCo.
*
* This is the same as the MuJoCoCarDemo example in the mujoco-c library, which you need to install
* first using `lingo build` in this directory.
*
* This program uses the [MuJoCoCar](lib/MuJoCoCar.lf) reactor to build a simple interactive
* simulation. The arrow keys can be used to drive the car. The _backspace_ or _delete_ key will
* reset the simulation to its initial conditions, and the `q` or `Q` key will quit the simulation.
*
* See [README.md](../README.md) for prerequisites and installation instructions.
*
* @author Edward A. Lee
*/
target C {
keepalive: true, // Because of physical action in MuJoCoCar.
keepalive: true // Because of physical action.
}

import MuJoCoCar from <mujoco-c/MuJoCoCar.lf>

main reactor(period: time = 33333333 ns, speed_sensitivity: double = 0.05, turn_sensitivity: double = 0.01) {
main reactor(
period: time = 33333333 ns,
speed_sensitivity: double = 0.05,
turn_sensitivity: double = 0.01) {
timer t(0, period)
state speed:double = 0;
state speed: double = 0
state turn: double = 0

m = new MuJoCoCar()

Expand All @@ -34,6 +43,8 @@ main reactor(period: time = 33333333 ns, speed_sensitivity: double = 0.05, turn_
if (m.key->value.act==GLFW_PRESS) {
if (m.key->value.key==GLFW_KEY_BACKSPACE) {
lf_set(m.restart, true);
self->speed = 0.0;
self->turn = 0.0;
} else if (m.key->value.key==GLFW_KEY_Q) {
lf_request_stop();
} else if (m.key->value.key==GLFW_KEY_UP) {
Expand All @@ -43,11 +54,11 @@ main reactor(period: time = 33333333 ns, speed_sensitivity: double = 0.05, turn_
self->speed -= self->speed_sensitivity;
lf_set(m.forward, self->speed);
} else if (m.key->value.key==GLFW_KEY_RIGHT) {
self->speed -= self->turn_sensitivity;
lf_set(m.turn, self->speed);
self->turn -= self->turn_sensitivity;
lf_set(m.turn, self->turn);
} else if (m.key->value.key==GLFW_KEY_LEFT) {
self->speed += self->turn_sensitivity;
lf_set(m.turn, self->speed);
self->turn += self->turn_sensitivity;
lf_set(m.turn, self->turn);
}
}
=}
Expand All @@ -66,4 +77,4 @@ main reactor(period: time = 33333333 ns, speed_sensitivity: double = 0.05, turn_
reaction(shutdown) {=
lf_print("\nExiting.");
=}
}
}
73 changes: 73 additions & 0 deletions mujoco/src/CarAuto.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @file Basic car driving program for MuJoCo.
*
* This is the same as the MuJoCoCarAutoDemo example in the mujoco-c library, which you need to
* install first using `lingo build` in this directory.
*
* This MuJoCoCarAuto reactor to build a simple interactive simulation. The arrow keys can be used
* to drive the car. The _backspace_ or _delete_ key will reset the simulation to its initial
* conditions, and the `q` or `Q` key will quit the simulation.
*
* See [README.md](../README.md) for prerequisites and installation instructions.
*
* @author Edward A. Lee
*/
target C {
keepalive: true // Because of physical action.
}

import MuJoCoCarAuto from <mujoco-c/MuJoCoCarAuto.lf>

main reactor(speed_sensitivity: double = 0.05, turn_sensitivity: double = 0.01) {
state speed: double = 0
state turn: double = 0

m = new MuJoCoCarAuto()

reaction(startup) {=
lf_print("*** Backspace to reset.");
lf_print("*** Type q to quit.\n");
=}

reaction(m.key) -> m.restart, m.forward, m.turn {=
// If backspace: reset simulation
// If q or Q: quit
if (m.key->value.act==GLFW_PRESS) {
if (m.key->value.key==GLFW_KEY_BACKSPACE) {
lf_set(m.restart, true);
self->speed = 0.0;
self->turn = 0.0;
} else if (m.key->value.key==GLFW_KEY_Q) {
lf_request_stop();
} else if (m.key->value.key==GLFW_KEY_UP) {
self->speed += self->speed_sensitivity;
lf_set(m.forward, self->speed);
} else if (m.key->value.key==GLFW_KEY_DOWN) {
self->speed -= self->speed_sensitivity;
lf_set(m.forward, self->speed);
} else if (m.key->value.key==GLFW_KEY_RIGHT) {
self->turn -= self->turn_sensitivity;
lf_set(m.turn, self->turn);
} else if (m.key->value.key==GLFW_KEY_LEFT) {
self->turn += self->turn_sensitivity;
lf_set(m.turn, self->turn);
}
}
=}

reaction(m.right_force, m.left_force, m.tick) {=
printf("\r" PRINTF_TIME, lf_time_logical_elapsed());
if (m.left_force->is_present) {
printf("\t<--- %f", m.left_force->value);
}
if (m.right_force->is_present) {
printf("\t %f --->", m.right_force->value);
}
// Flush the output buffer to ensure the line updates immediately
fflush(stdout);
=}

reaction(shutdown) {=
lf_print("\nExiting.");
=}
}
37 changes: 37 additions & 0 deletions network-monitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Network latency monitoring

These examples show how to use LF to monitor the network underlying a federation.

## Prerequisits
- Java 17
- libwebsockets
- libmicrohttpd `apt install libmicrohttpd` or `brew install libmicrohttpd`

## Examples
### [Probe4.lf](src/Probe4.lf)
This example introduces several library reactors for monitoring network latencies within a federation.
The federation is instantiated as a bank with all-to-all connections and each federate measures the network
latency to all other federates. The measurements are filtered with a moving worst case filter and exposed
by a HTTP server. This program could run in the background, as a daemon and let other programs on the host
utilize the latency measurements.

To run
```sh
lfc src/Probe4.lf
bin/Probe4
```

To inspect the latency measurements performed by federate 0, open [](src/lib/HttpPlotClient.html) in a browser

### [Probe5.lf](src/Probe5.lf)
This examples builds on Probe4, and shows how we can leverage the latencies measurements
within the very same LF program. Federate 0 sends out a shell command to all other federates together with a timestamp
for when to invoke it which is the maximum of all filtered latencies.

To run
```sh
lfc src/Probe5.lf
bin/Probe5
```

Latency measurements can again be inspected with [](src/lib/HttpPlotClient.html)
28 changes: 28 additions & 0 deletions network-monitor/src/Probe4.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* This examples uses the NetworkMonitor reactor to measure the latency to all other nodes in the federation.
* The NetworkMonitor runs a HTTP server that exposes the latency data in JSON format. This program can
* run asynchronously in the background as a daemon and be queried by other programs (e.g. LF programs) to get the latency data.
*/
target C {
coordination: decentralized,
}

import NetworkMonitor from "lib/NetworkMonitor.lf"

preamble {=
#include "network_latency_probe.h"
=}

reactor NodeAsync(bank_index:int = 0, width:int = 1) {
input [width] probe_in: LatencyMessage
output [width] probe_out: LatencyMessage

monitor = new NetworkMonitor(bank_index=bank_index, width=width)
monitor.probe_out -> probe_out
probe_in -> monitor.probe_in
}

federated reactor(num_nodes: int = 2) {
m = new [num_nodes] NodeAsync(width=num_nodes)
m.probe_out ~> interleaved(m.probe_in)
}
96 changes: 96 additions & 0 deletions network-monitor/src/Probe5.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* This examples also uses the NetworkMonitor, but instead of interacting with it through HTTP. It puts the
* other program inside the same federate. It gets the latency mesaurements from the output ports of the
* NetworkMonitor. Federate 0, periodically sends a command string to the other federates together with a
* timestamp at which the command should be executed. The timestamp is based off the latency measurements.
*/
target C {
coordination: decentralized,
}

import NetworkMonitor from "lib/NetworkMonitor.lf"

preamble {=
#include "network_latency_probe.h"

// A command to be sent between the nodes
typedef struct {
char cmd[256]; // Shell command to execute
instant_t timestamp; // The instant at which to execute the command
} CommandMessage;

// Macro for generating a shell command to say something
#if defined(PLATFORM_Linux)
#define SAY_SOMETHING(x) "spd-say '" x "'"
#elif defined(PLATFORM_Darwin)
#define SAY_SOMETHING(x) "say '" x "'"
#else
#error "Unsupported platform"
#endif
=}

reactor NodeSync(bank_index:int = 0, width:int = 1) {
input [width] probe_in: LatencyMessage
output [width] probe_out: LatencyMessage

output [width] cmd_out: CommandMessage
input [width] cmd_in: CommandMessage

monitor = new NetworkMonitor(bank_index=bank_index, width=width)
monitor.probe_out -> probe_out
probe_in -> monitor.probe_in

timer t(1 sec, 2 sec)
// FIXME: We could receive `width` commands with the same timestamp and get into trouble here.
logical action a_cmd: CommandMessage

reaction(t) monitor.latencies_filtered -> cmd_out {=
interval_t max_latency = NEVER;
instant_t cmd_timestamp = NEVER;
if (self->bank_index == 0) {
for (int i = 0; i<self->width; i++) {
if (monitor.latencies_filtered[i]->value > max_latency) {
max_latency = monitor.latencies_filtered[i]->value;
}
}

if (max_latency == FOREVER) {
lf_print_error("Max latency is FOREVER. Not sending any commands.");
return;
}

cmd_timestamp = lf_time_physical() + max_latency + MSEC(1);

for (int i = 0; i<self->width; i++) {
strcpy(cmd_out[i]->value.cmd, SAY_SOMETHING("Hello Lingua Franca!"));
cmd_out[i]->value.timestamp = cmd_timestamp;
lf_set_present(cmd_out[i]);
}
}
=}

reaction(cmd_in) -> a_cmd {=
for (int i = 0; i < self->width; i++) {
if (cmd_in[i]->is_present) {
interval_t delay = cmd_in[i]->value.timestamp - lf_time_logical();
if (delay < 0) {
lf_print_error("Command timestamp: " PRINTF_TIME" is in the past. Executing ASAP!", cmd_in[i]->value.timestamp);
delay = 0;
}
lf_schedule_copy(a_cmd, delay, &cmd_in[i]->value, 1);
}
}
=}

reaction(a_cmd) {=
if (system(a_cmd->value.cmd) != 0) {
lf_print_error("Command failed");
}
=}
}

federated reactor(num_nodes: int = 2) {
m = new [num_nodes] NodeSync(width=num_nodes)
m.probe_out ~> interleaved(m.probe_in)
m.cmd_out ~> interleaved(m.cmd_in)
}
Loading