Skip to content

Commit

Permalink
Merge branch 'main' into cpp-reflex
Browse files Browse the repository at this point in the history
  • Loading branch information
lhstrh authored Apr 21, 2024
2 parents f09009c + 808c3c2 commit 9a24dd3
Show file tree
Hide file tree
Showing 157 changed files with 9,578 additions and 6,835 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
check_mode: "compile"
no_compile_flag: false
exclude_dirs: '["failing", "experiments"]'
compiler_ref: ${{ needs.find-latest-release.outputs.ref }}
compiler_ref: "master" # ${{ needs.find-latest-release.outputs.ref }}

check-format:
runs-on: ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
**/src-gen/
**/fed-gen/
C/include
Cpp/include
Cpp/share
Cpp/lib
Cpp/**/include
Cpp/**/share
Cpp/**/lib
# Created by https://www.toptal.com/developers/gitignore/api/intellij,gradle,eclipse,maven,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,gradle,eclipse,maven,visualstudiocode

Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
[![CI](https://github.com/lf-lang/examples-lingua-franca/actions/workflows/ci.yml/badge.svg)](https://github.com/lf-lang/examples-lingua-franca/actions/workflows/ci.yml)

## 🛝 Lingua Franca Playground
Get to know the language and tinker with some example Lingua Franca programs!
Get to know the [Lingua Franca coordination language](https://lf-lang.org) and browse [example programs](examples/README.md).
To view, edit, and run the programs, you can either run locally on your computer or run in the cloud using either GitHub Codespaces or Gitpod.

### :rocket: Cloud-based dev environment
## 💻 Running Locally
Quick start:

1. Clone this repo (`git clone [email protected]:lf-lang/playground-lingua-franca.git`)
2. Open with [VS Code](https://code.visualstudio.com) (`code playground-lingua-franca`)
3. Install the [Lingua Franca extension](https://github.com/lf-lang/vscode-lingua-franca) (<kbd>Ctrl</kbd>+<kbd>P</kbd> and enter `ext install lf-lang.vscode-lingua-franca`).

Once in VSCode, navigate to [./examples](./examples) and click on any of the `.lf` files to open them into your editor. To build and run, use <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> and select `Lingua Franca: Build and Run`. Note: You might need not install additional dependencies in order to successfully build some of the code you find in this repository. For more information, see the [setup-env.bash](./utils/scripts/setup-env.bash) script that we use to configure our Docker-based environments.


## :rocket: Running in the Cloud
Spin up a fully configured dev environment in the cloud that start in seconds.
Any dependencies required for building or running any of the examples are preinstalled.
A web-based VS Code editor, preloaded with the Lingua Franca extension, is accessible through either **GitHub Codespaces** or **GitPod**. Simply click on either of the links below to get started.

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json) (NOTE: This can be quite slow to start.)

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/new#https://github.com/lf-lang/playground-lingua-franca/tree/main)

Expand All @@ -32,9 +43,3 @@ bash ./utils/scripts/setup-lf.bash dev

Please note that running these commands will remove the current lingua-franca directory.

## 💻 Local dev environment
1. Clone this repo (`git clone [email protected]:lf-lang/playground-lingua-franca.git`)
2. Open with VS Code (`code playground-lingua-franca`)
3. Install the [Lingua Franca extension](https://github.com/lf-lang/vscode-lingua-franca) (<kbd>Ctrl</kbd>+<kbd>P</kbd> and enter `ext install lf-lang.vscode-lingua-franca`).

Once in VSCode, navigate to [./examples](./examples) and click on any of the `.lf` files to open them into your editor. To build and run, use <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> and select `Lingua Franca: Build and Run`. Note: You might need not install additional dependencies in order to successfully build some of the code you find in this repository. For more information, see the [setup-env.bash](./utils/scripts/setup-env.bash) script that we use to configure our Docker-based environments.
19 changes: 13 additions & 6 deletions examples/C/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
# C Examples
* [Patterns](src/patterns/README.md): Common communication patterns.

(in alphabetical order)

* [Browser UIs](src/browser-ui/README.md): How to create a browser-based UI for an LF program.
* [Car Brake](src/car-brake/README.md): Sketch of ADAS system illustrating the CAL theorem.
* [Deadlines](src/deadlines/README.md): Uses of deadlines in Lingua Franca.
* [Car Brake](src/car-brake/README.md): Sketch of ADAS system illustrating CAL theorem.
* [Rosace](src/rosace/README.md): Aircraft controller illustrating periodic systems with multiple periods.
* [Simulation](src/simulation/README.md): Using Lingua Franca for simulation.
* [Distributed](src/distributed/README.md): Basic federated hello-world examples.
* [Furuta Pendulum](src/modal_models/FurutaPendulum/README.md): A controller and simulation illustrating a modal reactor.
* [Keyboard](src/keyboard/README.md): Responding to keyboard input using ncurses.
* [Leader Election](src/leader-election/README.md): Federated fault-tolerant system with leader election.
* [MQTT](src/mqtt/README.md): Interacting with MQTT-based services.
* [Furuta Pendulum](src/modal_models/FurutaPendulum/README.md): A controller and simulation illustrating a modal reactor.
* [Patterns](src/patterns/README.md): Common communication patterns.
* [Rhythm](src/rhythm/README.md): Sound generation and terminal user interface demos.
* [Rosace](src/rosace/README.md): Aircraft controller illustrating periodic systems with multiple periods.
* [SDV](src/sdv/README.md): Software defined vehicle sketch integrating user input, a web display, and sound.
* [Shared Memory](src/shared-memory/README.md): Using shared memory to exchange large data objects between federates.
* [Simulation](src/simulation/README.md): Using Lingua Franca for simulation.
* [Train Door](src/train-door/README.md): Train door controller from a verification paper.
* [Distributed](src/distributed/README.md): Basic federated hello-world examples.
* [Watchdog](src/watchdog/README.md): Federated illustration of watchdogs.
4 changes: 0 additions & 4 deletions examples/C/src/ReflexGame/ReflexGame.lf
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ target C {
keepalive: true
}

preamble {=
#include "include/core/platform.h"
=}

/**
* Produce a counting sequence at random times with a minimum and maximum time between outputs
* specified as parameters.
Expand Down
4 changes: 0 additions & 4 deletions examples/C/src/ReflexGame/ReflexGameTest.lf
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ target C {
timeout: 5 sec
}

preamble {=
#include "include/core/platform.h"
=}

main reactor {
preamble {=
// Specify a thread that sends all keyboard characters
Expand Down
2 changes: 1 addition & 1 deletion examples/C/src/SleepingBarber.lf
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/
target C {
fast: true,
threading: false,
single-threaded: true,
cmake-include: "/lib/c/reactor-c/util/deque.cmake",
files: ["/lib/c/reactor-c/util/deque.h", "/lib/c/reactor-c/util/deque.c"]
}
Expand Down
23 changes: 23 additions & 0 deletions examples/C/src/browser-ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Browser UI

These examples show how to create user interfaces for a Lingua Franca program.
The UI runs in the browser and connects to the program via either HTTP or via a web socket.

<table>
<tr>
<td> <img src="img/BrowserUI.png" alt="BrowserUI" width="400">
<td> <a href="BrowserUI.lf">BrowserUI.lf</a>: This version starts a web server that serves a specified web page and enables implementing an HTTP-based API to control your LF program. When the program is running, you can point your browser to <a href="http://localhost:8080">http://localhost:8080</a> to get a page. Adding a path to the URL, as in for example, http://localhost:8080/count, will cause the ServerUI reactor to produce an output that your LF program can react to and send a (text) response.</td>
</tr>
<tr>
<td> <img src="img/WebSocket.png" alt="WebSocket" width="400">
<td> <a href="WebSocket.lf">WebSocket.lf</a>: This example uses the much more versatile WebSocketServer reactor. When the program is running, you can open an HTML page that includes JavaScript that connects to the server. Messages can be sent in both directions over the web socket, from the LF program to the browser and vice versa.</td>
</tr>
<tr>
<td> <img src="img/WebSocketString.png" alt="WebSocketString" width="400">
<td> <a href="WebSocketString.lf">WebSocketString.lf</a>: This version uses the simpler WebSocketServerString reactor, which is simpler in that it restricts the messages transported over the web socket to be of string types and it allows only one client to connect.</td>
</tr>
<tr>
<td> <img src="img/Uptime.png" alt="Uptime" width="400">
<td> <a href="Uptime.lf"> Uptime.lf</a>: This version combines ServerUI with WebSocketServer to serve a web page and then feed it data continuously through a web socket. The application displays the total time that application has been running and updates this time once per second.</td>
</tr>
</table>
53 changes: 53 additions & 0 deletions examples/C/src/browser-ui/Uptime.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* This example combines `ServerUI` with `WebSocketServer`. The former starts a web server that
* listens for HTTP requests on port 8080 and serves the web page defined in `uptime.html`. That web
* page includes JavaScript that connects to a web socket on port 8080 that is provided by the
* `WebSocketServer` reactor. The resulting web page simply reports the total time that this program
* has been running. That time is updated on the web page once per second.
*
* This uses the <a href="https://libwebsockets.org">libwebsockets</a> (see <a
* href="https://libwebsockets.org/lws-api-doc-main/html/index.html">API documentation</a> and <a
* href="https://libwebsockets.org/lws-api-doc-main/html/md_READMEs_README_build.html">installation
* instructions</a>). To install on MacOS, we recommending using brew:
* <pre> brew install libwebsockets
* </pre> This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be
* linked to using the {@code -lwebsockets} compile option or the {@code WebSocketCmake.txt} Cmake
* include file.
*
* @author Edward A. Lee
*/
target C {
build-type: debug,
keepalive: true
}

import WebSocketServer from "../lib/WebSocketServer.lf"

main reactor {
timer seconds(0, 1 s)

w = new WebSocketServer(
hostport=8080,
initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "Uptime.html" =})

reaction(startup) {=
lf_print("Point your browser to http://localhost:8080");
=}

reaction(seconds) -> w.send {=
instant_t uptime = lf_time_logical_elapsed();
// Truncate to the nearest second.
uptime = (uptime / SEC(1)) * SEC(1);
char* message = (char*)malloc(LF_TIME_BUFFER_LENGTH * sizeof(char));
size_t length = lf_readable_time(message, uptime) + 1; // +1 to add a null character.
message[length] = '\0';

// Broadcast to all connected sockets. This is accomplished by providing a NULL wsi.
web_socket_message_t* to_send = (web_socket_message_t*)malloc(sizeof(web_socket_message_t));
to_send->wsi = NULL;
to_send->length = length;
to_send->message = message;
to_send->binary = false;
lf_set(w.send, to_send);
=}
}
18 changes: 10 additions & 8 deletions examples/C/src/browser-ui/WebSocket.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
// Get references to elements on the page.
var form = document.getElementById('message-form');
var messageField = document.getElementById('message');
var messagesList = document.getElementById('messages');
var incoming = document.getElementById('messages');
var outgoing = document.getElementById('outgoing');
var socketStatus = document.getElementById('status');
var closeBtn = document.getElementById('close');

const socket = new WebSocket('ws://localhost:8000', 'ws');
const socket = new WebSocket('ws://localhost:8080', 'ws');

socket.addEventListener('open', (event) => {
console.log('WebSocket connection established');
Expand All @@ -38,9 +39,9 @@
// Send the message through the WebSocket.
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
messagesList.innerHTML = '<li class="sent"><span>Sent:</span>' + message + '</li>';
outgoing.innerHTML = message;
} else {
messagesList.innerHTML = '<li class="sent"><span>Socket is not open!</span></li>';
outgoing.innerHTML = 'Socket is not open!';
}

// Clear out the message field.
Expand All @@ -52,8 +53,7 @@
// Handle messages sent by the server.
socket.onmessage = function(event) {
var message = event.data;
messagesList.innerHTML = '<li class="received"><span>Received:</span>' +
message + '</li>';
incoming.innerHTML = message;
};

// Show a disconnected message when the WebSocket is closed.
Expand All @@ -76,8 +76,10 @@
<h1>WebSockets Demo</h1>

<div id="status">Connecting...</div>

<ul id="messages"></ul>
<ul>
<li> Incoming message: <span id="messages">nothing yet</span></li>
<li> Outgoing message: <span id="outgoing">nothing yet</span></li>
</ul>

<form id="message-form" action="#" method="post">
<textarea id="message" placeholder="Write your message here..." required></textarea>
Expand Down
73 changes: 30 additions & 43 deletions examples/C/src/browser-ui/WebSocket.lf
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@
* the WebSocket.html web page more than twice, only the first two attempts will succeed in
* connecting. By default, WebSocketServer imposes no such limit.
*
* This example also shows one way to keep track of multiple connections. It uses a hashset
* containing pointers to the wsi (web socket interface) fields, which are unique to each
* connection. In this example, if you connect two clients, one will receive even numbered counts,
* and the other will receive odd numbered counts.
* In this example, if you connect two clients, one will receive even numbered counts, and the other
* will receive odd numbered counts. This is because the count state variable is shared across all
* clients.
*
* This uses the <a href="https://libwebsockets.org">libwebsockets</a> (see <a
* href="https://libwebsockets.org/lws-api-doc-main/html/index.html">API documentation</a> and <a
* href="https://libwebsockets.org/lws-api-doc-main/html/md_READMEs_README_build.html">installation
* instructions</a>). To install on MacOS, we recommending using brew:
* <pre> brew install libwebsockets
* </pre> This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be
* linked to using the {@code -lwebsockets} compile option or the {@code WebSocketCmake.txt} Cmake
* include file.
*
* @author Edward A. Lee
*/
Expand All @@ -21,68 +29,47 @@ target C {

import WebSocketServer from "../lib/WebSocketServer.lf"

preamble {=
#include "hashset/hashset.h" // For storing web socket instances that are connected.
=}

main reactor {
state count: int = 0
logical action send_action: web_socket_instance_t*

state connected_instances: hashset_t = {= NULL =}

s = new WebSocketServer(max_clients=2) // Limit number of clients to 2.

reaction(startup) -> s.send {=
lf_set_destructor(s.send, web_socket_message_destructor);
lf_set_copy_constructor(s.send, web_socket_message_copy_constructor);
s = new WebSocketServer(
hostport=8080,
max_clients=2, // Limit number of clients to 2.
initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "WebSocket.html" =})

reaction(startup) {=
lf_print("======== Starting server. Open WebSocket.html in your favorite browser.");
self->connected_instances = hashset_create(2); // Default capacity for four instances.
=}

reaction(send_action) -> s.send, send_action {=
// If the web socket is no longer connected, the instance will not be in the hashset.
if (hashset_is_member(self->connected_instances, send_action->value->wsi)) {
char* message;
asprintf(&message, "Count is: %d", self->count++);
char* message;
asprintf(&message, "Count is: %d", self->count++);

web_socket_message_t* container = (web_socket_message_t*)malloc(sizeof(web_socket_message_t));
container->message = message;
container->length = strlen(message) + 1;
container->wsi = send_action->value->wsi;
web_socket_message_t* container = (web_socket_message_t*)malloc(sizeof(web_socket_message_t));
container->message = message;
container->length = strlen(message) + 1;
container->wsi = send_action->value->wsi;
container->binary = false; // Sending text.

lf_set(s.send, container);
// Schedule the next send.
lf_schedule_token(send_action, SEC(1), send_action->token);
}
lf_set(s.send, container);
// Schedule the next send.
lf_schedule_token(send_action, SEC(1), send_action->token);
=}

reaction(s.connected) -> send_action {=
if (s.connected->value.connected) {
lf_print("======== Connected a new client");
} else {
lf_print("======== Disconnected client");
}
if (s.connected->value.connected) {
// New connection.
hashset_add(self->connected_instances, s.connected->value.wsi);

// Start sending.
lf_schedule_copy(send_action, 0, &s.connected->value, 1);
} else {
// Disconnecting. Remove from hashset to prevent further scheduling.
hashset_remove(self->connected_instances, s.connected->value.wsi);
lf_print("======== Disconnected client");
}
=}

reaction(s.received) {=
lf_print("======== Application received: %s", s.received->value->message);
=}

reaction(shutdown) {=
if (self->connected_instances != NULL) {
hashset_destroy(self->connected_instances);
}
// NOTE: The WebSocketServer ensures that the received message is null terminated.
lf_print("======== Application received: %s", (char*)s.received->value->message);
=}
}
Loading

0 comments on commit 9a24dd3

Please sign in to comment.