Skip to content

Commit

Permalink
Merge branch 'main' into release-0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lhstrh authored May 1, 2024
2 parents 91f624d + 59df69b commit 7d275b5
Show file tree
Hide file tree
Showing 15 changed files with 590 additions and 88 deletions.
2 changes: 1 addition & 1 deletion examples/C/src/mqtt/include/mosquitto-extension.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ mark_as_advanced(MOSQUITTO_INCLUDE_DIR MOSQUITTO_LIBRARY)
########

include_directories(/usr/local/include)
target_link_libraries(${LF_MAIN_TARGET} ${MOSQUITTO_LIBRARY})
target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${MOSQUITTO_LIBRARY})
2 changes: 1 addition & 1 deletion examples/C/src/mqtt/include/paho-extension.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ FIND_PACKAGE_HANDLE_STANDARD_ARGS(PahoMqttC
########

include_directories(${PAHO_MQTT_C_INCLUDE_DIRS})
target_link_libraries(${LF_MAIN_TARGET} ${PAHO_MQTT_C_LIBRARIES})
target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${PAHO_MQTT_C_LIBRARIES})
7 changes: 4 additions & 3 deletions examples/C/src/mqtt/lib/MQTTTestReactors.lf
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ reactor MessageGenerator(root: string = "", period: time = 1 sec) {
reaction(t) -> message {=
// With NULL, 0 arguments, snprintf tells us how many bytes are needed.
// Add one for the null terminator.
int length = snprintf(NULL, 0, "%s %d", self->root, self->count) + 1;
size_t length = snprintf(NULL, 0, "%s %d", self->root, self->count) + 1;
// Dynamically allocate memory for the output.
SET_NEW_ARRAY(message, length);
char* buffer = (char*)malloc(length);
// Populate the output string and increment the count.
snprintf(message->value, length, "%s %d", self->root, self->count++);
snprintf(buffer, length, "%s %d", self->root, self->count++);
lf_set_array(message, buffer, length);
tag_t tag = lf_tag();
lf_print("MessageGenerator: At (elapsed) tag " PRINTF_TAG ", publish message: %s",
tag.time - lf_time_start(), tag.microstep,
Expand Down
7 changes: 5 additions & 2 deletions examples/CCpp/ROS/src/ROSSerialization.lf
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ reactor Clock(offset: time = 0, period: time = 1 sec) {

auto message_header_length = 8u;
auto message_payload_length = 10u;
// The following allocates memory for serialization. The memory will later be
// freed by the LF token management scheme.
// TODO: Should this also call release_rcl_serialized_message()??
// Otherwise, there is risk of a double free?
self->serialized_msg->reserve(message_header_length + message_payload_length);

static rclcpp::Serialization<std_msgs::msg::Int32> serializer_obj;
serializer_obj.serialize_message(msg.get(), self->serialized_msg);

SET_NEW_ARRAY(y, self->serialized_msg->size());
y->value = self->serialized_msg->get_rcl_serialized_message().buffer;
lf_set_array(y, self->serialized_msg->get_rcl_serialized_message().buffer, self->serialized_msg->size());
=}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/CCpp/ROS/src/include/CMakeListsExtension.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ find_package(rmw REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(std_msgs REQUIRED)

ament_target_dependencies( ${LF_MAIN_TARGET} rclcpp sensor_msgs std_msgs rmw)
ament_target_dependencies( ${LF_MAIN_TARGET} PUBLIC rclcpp sensor_msgs std_msgs rmw)
2 changes: 0 additions & 2 deletions examples/Cpp/ReflexGame/src/ReflexGame.cmake

This file was deleted.

157 changes: 79 additions & 78 deletions examples/Cpp/ReflexGame/src/ReflexGame.lf
Original file line number Diff line number Diff line change
Expand Up @@ -2,103 +2,97 @@
* This example illustrates the use of logical and physical actions, asynchronous external inputs,
* the use of startup and shutdown reactions, and the use of actions with values.
*
* @author Christian Menard
* @author Felix Wittwer
* @author Edward A. Lee
* @author Marten Lohstroh
*/
target Cpp {
cmake-include: "ReflexGame.cmake"
}
target Cpp

/**
* Produce a counting sequence at random times with a minimum and maximum time between outputs
* specified as parameters.
*
* @param min_time The minimum time between outputs.
* @param max_time The maximum time between outputs.
*/
reactor RandomSource(min_time: time(2 sec), max_time: time(8 sec)) {
private preamble {=
// Generate a random additional delay over the minimum.
// Assume millisecond precision is enough.
reactor::Duration additional_time(reactor::Duration min_time, reactor::Duration max_time) {
int interval_in_msec = (max_time - min_time) / std::chrono::milliseconds(1);
return (std::rand() % interval_in_msec) * std::chrono::milliseconds(1);
}
reactor RandomDelay(min_delay: time(2 sec), max_delay: time(8 sec)) {
public preamble {=
#include <random>
=}
input another: void

input in: void
output out: void
logical action prompt(min_time)
state count: int(0)
logical action delay(min_delay): void

reaction(startup) -> prompt {=
std::cout << "***********************************************" << std::endl;
std::cout << "Watch for the prompt, then hit Return or Enter." << std::endl;
std::cout << "Type Control-D (EOF) to quit." << std::endl << std::endl;
// a random number generator seeded with the current physical time
state rand: std::mt19937({= get_physical_time().time_since_epoch().count() =})
// a uniform random distribution with a range from 1 to (max_delay - min_delay) milliseconds
state dist: std::uniform_int_distribution<int>(
1,
{= (max_delay - min_delay) / std::chrono::milliseconds(1) =})

// TODO: Manual inclusion of header necessary?
// Set a seed for random number generation based on the current time.
std::srand(std::time(nullptr));
reaction(delay) -> out {=
out.set();
=}

// Schedule the first event.
prompt.schedule(additional_time(0ms, max_time - min_time));
reaction(in) -> delay {=
delay.schedule(dist(rand) * std::chrono::milliseconds(1));
=}
}

reaction(prompt) -> out {=
count++;
std::cout << count << ". Hit Return or Enter!" << std::endl << std::flush;
out.set();
reactor KeyboardInput {
state thread: std::thread
state terminate: std::atomic<bool> = false
physical action keyboard_input: char

output enter: void
output quit: void

reaction(startup) -> keyboard_input {=
// Start the thread that listens for keyboard input.
thread = std::thread([&] () {
int c{0};
while(!terminate.load()) {
c = getchar();
keyboard_input.schedule(c);
}
});
=}

reaction(another) -> prompt {=
// Schedule the next event.
prompt.schedule(additional_time(0ms, max_time - min_time));
reaction(keyboard_input) -> enter, quit {=
char key = *keyboard_input.get();
if(key == '\n') {
enter.set();
} else if (key == EOF) {
quit.set();
}
=}
}

/**
* Upon receiving a prompt, record the time of the prompt, then listen for user input. When the user
* hits return, then schedule a physical action that records the time of this event and then report
* the response time.
*/
reactor GetUserInput {
public preamble {=
#include <thread>
reaction(shutdown) {=
terminate.store(true);
thread.join();
=}
}

physical action user_response: char
state prompt_time: {= reactor::TimePoint =}({= reactor::TimePoint::min() =})
reactor GameLogic {
output request_prompt: void
input prompt: void
input enter: void
input quit: void

state prompt_time: {= reactor::TimePoint =} = {= reactor::TimePoint::min() =}
state count: unsigned = 0
state total_time: time(0)
state count: int(0)
state thread: {= std::thread =}

input prompt: void
output another: void
reaction(startup) -> request_prompt {=
std::cout << "***********************************************" << std::endl;
std::cout << "Watch for the prompt, then hit Return or Enter." << std::endl;
std::cout << "Type Control-D (EOF) to quit." << std::endl << std::endl;

reaction(startup) -> user_response {=
// Start the thread that listens for Enter or Return.
thread = std::thread([&] () {
int c;
while(1) {
while((c = getchar()) != '\n') {
if (c == EOF) break;
}
user_response.schedule(c, 0ms);
if (c == EOF) break;
}
});
// request the first prompt
request_prompt.set();
=}

reaction(prompt) {=
prompt_time = get_physical_time();
std::cout << std::endl << "Hit Return or Enter!" << std::endl;
=}

reaction(user_response) -> another {=
auto c = user_response.get();
if (*c == EOF) {
environment()->sync_shutdown();
return;
}
reaction(enter) -> request_prompt {=
// If the prompt_time is 0, then the user is cheating and
// hitting return before being prompted.
if (prompt_time == reactor::TimePoint::min()) {
Expand All @@ -113,13 +107,16 @@ reactor GetUserInput {
total_time += time_in_ms;
// Reset the prompt_time to indicate that there is no new prompt.
prompt_time = reactor::TimePoint::min();
// Trigger another prompt.
another.set();
// Request another prompt.
request_prompt.set();
}
=}

reaction(quit) {=
environment()->sync_shutdown();
=}

reaction(shutdown) {=
thread.join();
if (count > 0) {
std::cout << std::endl << "**** Average response time: " << std::chrono::duration_cast<std::chrono::milliseconds>(total_time/count) << std::endl;
} else {
Expand All @@ -128,9 +125,13 @@ reactor GetUserInput {
=}
}

main reactor ReflexGame {
p = new RandomSource()
g = new GetUserInput()
p.out -> g.prompt
g.another -> p.another
main reactor {
delay = new RandomDelay()
keyboard = new KeyboardInput()
logic = new GameLogic()

logic.request_prompt -> delay.in
delay.out -> logic.prompt
keyboard.enter -> logic.enter
keyboard.quit -> logic.quit
}
52 changes: 52 additions & 0 deletions examples/Cpp/ReflexGame/src/ReflexGameEnclave.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* This example illustrates the use of enclaves to avoid spawning an extra thread for handling user
* input.
*
* The thread that is created in the startup reaction of KeyboardInput in eflexgame.lf uses a while
* loop. This loop is replaced by a enclave with a reaction that schedules using a physical action.
* A physical action was chosen here, because it alligns the progress of logical time in the enclave
* with the progress of physical time. Also it avoids the need for specifying or calculating a
* delay.
*
* The outputs that the EnclaveKeybardInput reactor produce are forwarded to the other reactors via
* physical actions. This fully decouples the input from the rest of the system, and time stamps of
* keyboard events are assigned based on the current physical time.
*
* @author Christian Menard
*/
target Cpp

import GameLogic from "ReflexGame.lf"
import RandomDelay from "ReflexGame.lf"

reactor EnclaveKeyboardInput {
physical action get_next

output enter: void
output quit: void

reaction(startup, get_next) -> get_next, enter, quit {=
int key = getchar();
if(key == '\n') {
enter.set();
get_next.schedule();
} else if (key == EOF) {
quit.set();
environment()->sync_shutdown();
} else {
get_next.schedule();
}
=}
}

main reactor {
@enclave
keyboard = new EnclaveKeyboardInput()
delay = new RandomDelay()
logic = new GameLogic()

logic.request_prompt -> delay.in
delay.out -> logic.prompt
keyboard.enter ~> logic.enter
keyboard.quit ~> logic.quit
}
74 changes: 74 additions & 0 deletions examples/Python/src/CARLA/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CARLA

This guide provides detailed instructions for running the CARLA simulator with LF. Ensure the following prerequisites are met before proceeding with the installation:

- **System requirements.** CARLA is built for Windows and Linux systems.
- **An adequate GPU.** CARLA aims for realistic simulations, so the server needs at least a 6 GB GPU although we would recommend 8 GB. A dedicated GPU is highly recommended for machine learning.
- **Disk space.** CARLA will use about 20 GB of space.
- **Two TCP ports and good internet connection.** 2000 and 2001 by default. Make sure that these ports are not blocked by firewalls or any other applications.
- **Other requirements.** CARLA requires some Python dependencies. Install the dependencies with the following command:

```bash
pip3 install --user pygame numpy carla
```

### **Downloading CARLA**

Download CARLA version 0.9.15 from [the official repository](https://github.com/carla-simulator/carla/releases/tag/0.9.15/). Extract the release file, which contains a precompiled version of the simulator.

### **Running the CARLA Server**

1. **On Windows:** Navigate to the extracted CARLA folder and double-click the `CarlaUE4.exe` file to start the server. A window will appear indicating the server is active.
2. **On Linux:** In the terminal, navigate to the CARLA folder and run `./CarlaUE4.sh` to initiate the server.

Note: Please restart the CARLA Server before running each of the examples below.

### **Compiling LF**

Compile the `carla_sync.lf` and `carla_async.lf` using the `lfc` command. Make sure that `carla_client.py` is in the include folder.

### Synchronous Example - Manual Control

Run the generated script `/.carla_sync` which connects to the CARLA server in synchronous mode and initiates a driving simulator in pygame. Use ARROWS or WASD keys for control. Image and IMU data will be displayed on the console.

<img src="img/carla_manual.png" alt="drawing" width="800"/>

### Asynchronous Example - Driving in Circles

Run the generated script `/.carla_async` which connects to the CARLA server in asynchronous mode and initiates a driving simulator in pygame. The vehicle will drive in circles. Image and IMU data will be displayed on the console.

<img src="img/carla_circles.gif" alt="drawing" width="800"/>

### Possible Configurations of CARLA Server

The configuration of time-step and synchrony, leads for different settings. Here is a brief summary on the possibilities.

| | Fixed time-step | Variable time-step |
| ----------------- | ---------------------------------------------------------------------- | ---------------------------------- |
| Synchronous mode | Client is in total control over the simulation and its information. | Risk of non reliable simulations. |
| Asynchronous mode | Good time references for information. Server runs as fast as possible. | Non easily repeatable simulations. |

- **Synchronous mode + variable time-step.** This is almost for sure a non-desirable state. Physics cannot run properly when the time-step is bigger than 0.1s, and if the server has to wait for the client to compute the steps, this is likely to happen. Simulation time and physics will not be in synchrony. The simulation will not be reliable.
- **Asynchronous mode + variable time-step.** This is the default CARLA state. Client and server are asynchronous. The simulation time flows according to the real time. Reenacting the simulation needs to take into account float-arithmetic error, and possible differences in time steps between servers.
- **Asynchronous mode + fixed time-step.** The server will run as fast as possible. The information retrieved will be easily related with an exact moment in the simulation. This configuration makes possible to simulate long periods of time in much less real time, if the server is fast enough.
- **Synchronous mode + fixed time-step.** The client will rule the simulation. The time step will be fixed. The server will not compute the following step until the client sends a tick. This is the best mode when synchrony and precision is relevant, especially when dealing with slow clients or different elements retrieving information.

**Fixed time-step**

Fixed delta seconds can be set in the world settings.

```python
settings = world.get_settings()
settings.fixed_delta_seconds = 0.05
world.apply_settings(settings)
```

**Variable time-step**

The default mode in CARLA. The simulation time that goes by between steps will be the time that the server takes to compute these.

```python
settings = world.get_settings()
settings.fixed_delta_seconds = None # Set a variable time-step
world.apply_settings(settings)
```
Loading

0 comments on commit 7d275b5

Please sign in to comment.