From 26638d113d03d3e7893d882661a9fcc534906972 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 20 Jun 2023 17:29:06 -0700 Subject: [PATCH] Reformat according to new state of formatter. --- C/src/ChatApplication/SimpleChat.lf | 18 +- C/src/Delay.lf | 67 ++-- .../DistributedDatabase/FederatedDatabase.lf | 28 +- .../DistributedDatabase/ReplicatedDatabase.lf | 289 +++++++-------- .../ReplicatedDatabaseThree.lf | 36 +- C/src/DistributedHelloWorld/HelloWorld.lf | 62 ++-- .../DistributedHelloWorld/HelloWorldAfter.lf | 16 +- .../HelloWorldDecentralized.lf | 46 ++- .../HelloWorldDecentralizedSTP.lf | 55 ++- .../HelloWorldPhysical.lf | 30 +- .../HelloWorldPhysicalAfter.lf | 24 +- .../docker/HelloWorldContainerized.lf | 17 +- .../FederatedResourceManagement.lf | 75 ++-- .../ResourceManagement.lf | 153 ++++---- C/src/Parallelism/ForkJoin.lf | 82 +++-- C/src/Parallelism/Pipeline.lf | 84 ++--- C/src/ProtocolBuffers/HelloProtocolBuffers.lf | 47 ++- C/src/ROS/BasicROS.lf | 332 +++++++++-------- C/src/ROS/PTIDES-ROS.lf | 344 +++++++++--------- C/src/ReflexGame/ReflexGame.lf | 22 +- C/src/ReflexGame/ReflexGameTest.lf | 15 +- C/src/RockPaperScissors.lf | 11 +- C/src/SleepingBarber.lf | 262 ++++++------- C/src/Smokers.lf | 191 +++++----- C/src/TrainDoor/TrainDoor.lf | 60 +-- C/src/TrainDoor/TrainDoorAsymmetric.lf | 75 ++-- C/src/TrainDoor/TrainDoorSimplest.lf | 52 +-- C/src/TrainDoor/TrainDoorWithDeadlines.lf | 98 ++--- C/src/TrainDoor/TrainDoorWithDoorOpenState.lf | 80 ++-- C/src/TrainDoor/TrainDoorWithOpen.lf | 79 ++-- C/src/browser-ui/BrowserUI.lf | 235 ++++++------ C/src/browser-ui/WebSocket.lf | 27 +- C/src/browser-ui/WebSocketServer.lf | 51 ++- C/src/car-brake/CarBrake.lf | 60 ++- C/src/car-brake/CarBrake2.lf | 19 +- C/src/car-brake/CarBrake3.lf | 19 +- C/src/deadlines/AnytimePrime.lf | 23 +- C/src/deadlines/Deadline.lf | 9 +- C/src/deadlines/PeriodicDeadline.lf | 72 ++-- C/src/keyboard/Keyboard.lf | 98 ++--- C/src/leader-election/Election.lf | 54 ++- C/src/lib/PoissonClock.lf | 29 +- C/src/lib/PrintToFile.lf | 18 +- C/src/lib/Random.lf | 43 +-- C/src/lib/RandomDelay.lf | 24 +- .../FurutaPendulum/FurutaPendulum.lf | 55 ++- .../FurutaPendulumDisturbance.lf | 43 ++- .../FurutaPendulum/PendulumController.lf | 94 ++--- .../FurutaPendulum/PendulumSimulation.lf | 112 +++--- C/src/modal_models/FurutaPendulum/Print.lf | 24 +- C/src/mqtt/MQTTDistributed.lf | 79 ++-- C/src/mqtt/MQTTDistributedActivity.lf | 59 ++- C/src/mqtt/MQTTLegacy.lf | 83 ++--- C/src/mqtt/MQTTLogical.lf | 64 ++-- C/src/mqtt/MQTTPhysical.lf | 59 ++- C/src/mqtt/lib/MQTTPublisher.lf | 131 ++++--- C/src/mqtt/lib/MQTTSubscriber.lf | 147 ++++---- C/src/mqtt/lib/MQTTTestReactors.lf | 23 +- C/src/patterns/Chain_01_SendReceive.lf | 16 +- C/src/patterns/Chain_02_Pipeline.lf | 39 +- C/src/patterns/FullyConnected_00_Broadcast.lf | 44 +-- .../patterns/FullyConnected_01_Addressable.lf | 42 +-- C/src/patterns/Loop_01_Single.lf | 20 +- C/src/patterns/Loop_02_SingleDelay.lf | 27 +- C/src/patterns/lib/SendersAndReceivers.lf | 180 ++++----- C/src/patterns/lib/TakeTime.lf | 34 +- C/src/rhythm/PlayWaveform.lf | 148 ++++---- C/src/rhythm/Rhythm.lf | 94 ++--- C/src/rhythm/RhythmDistributed.lf | 80 ++-- C/src/rhythm/RhythmDistributedNoUI.lf | 64 ++-- C/src/rhythm/SensorSimulator.lf | 30 +- C/src/rhythm/Sound.lf | 30 +- C/src/robot/CompositeRobot.lf | 89 ++--- C/src/rosace/AircraftSimulator.lf | 218 +++++------ C/src/rosace/Rosace.lf | 102 +++--- C/src/rosace/RosaceController.lf | 266 ++++++-------- C/src/rosace/RosaceWithUI.lf | 106 +++--- C/src/sdv/ParkingAssist.lf | 203 +++++------ C/src/simulation/MemoryHierarchy.lf | 126 +++---- C/src/simulation/PoissonProcess.lf | 21 +- CCpp/src/DoorLock/DoorLock.lf | 85 +++-- CCpp/src/DoorLock/lib/AuthSim.lf | 13 +- CCpp/src/DoorLock/lib/PropagationDelaySim.lf | 16 +- CCpp/src/DoorLock/lib/UserInteraction.lf | 37 +- .../ROS/MigrationGuide/lf-project/src/Main.lf | 9 +- .../MigrationGuide/lf-project/src/Receiver.lf | 11 +- .../MigrationGuide/lf-project/src/Sender.lf | 14 +- CCpp/src/ROS/ROSBuiltInSerialization.lf | 81 ++--- CCpp/src/ROS/ROSSerialization.lf | 72 ++-- Cpp/AlarmClock/src/AlarmClock.lf | 55 ++- Cpp/AlarmClock/src/Clock.lf | 77 ++-- Cpp/AlarmClock/src/Network.lf | 68 ++-- Cpp/CarBrake/src/CarBrake.lf | 76 ++-- Cpp/CarBrake/src/CarBrake2.lf | 64 ++-- .../src/FullyConnected_00_Broadcast.lf | 20 +- .../src/FullyConnected_01_Addressable.lf | 23 +- .../src/MatrixConnectedRowsAndColumns.lf | 31 +- Cpp/ROS2/src/MinimalPublisher.lf | 19 +- Cpp/ROS2/src/MinimalSubscriber.lf | 21 +- Cpp/ReflexGame/src/ReflexGame.lf | 68 ++-- Cpp/RequestResponse/src/Add.lf | 25 +- Cpp/RequestResponse/src/AddWithContext.lf | 18 +- Cpp/RequestResponse/src/ContextManager.lf | 4 +- Cpp/RequestResponse/src/MAC.lf | 35 +- .../DoubleUnlock/DoubleUnlockDemo.lf | 85 ++--- .../src/DigitalTwin/DoubleUnlock/Simulator.lf | 41 +-- Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 48 ++- Python/src/Piano/Piano.lf | 201 +++++----- .../src/ROS/PythonMigration/lf-python/Main.lf | 24 +- .../ROS/PythonMigration/lf-python/Receiver.lf | 26 +- .../ROS/PythonMigration/lf-python/Sender.lf | 33 +- Python/src/ReflexGame/ReflexGame.lf | 163 ++++----- Python/src/TrainDoor/TrainDoor.lf | 36 +- Python/src/YOLOv5/YOLOv5_Webcam.lf | 130 +++---- Python/src/YOLOv5/YOLOv5_Webcam_Timer.lf | 46 +-- Python/src/acas/ACASXu.lf | 80 ++-- Python/src/acas/ACASXu2.lf | 71 ++-- Python/src/acas/lib/ACASController.lf | 91 +++-- Python/src/acas/lib/ACASNN.lf | 63 ++-- Python/src/acas/lib/Aircraft.lf | 67 ++-- Python/src/acas/lib/XYPlotter.lf | 36 +- Rust/src/CounterProgram.lf | 27 +- Rust/src/Snake/KeyboardEvents.lf | 17 +- Rust/src/Snake/Snake.lf | 54 +-- TypeScript/src/ChatApplication/SimpleChat.lf | 54 ++- .../src/DistributedHelloWorld/HelloWorld.lf | 60 +-- .../src/HTTPSRequestReactor/HTTPSRequest.lf | 36 +- .../src/HTTPServerReactor/HTTPServer.lf | 42 ++- TypeScript/src/SimpleWebserver.lf | 36 +- 129 files changed, 4211 insertions(+), 4781 deletions(-) diff --git a/C/src/ChatApplication/SimpleChat.lf b/C/src/ChatApplication/SimpleChat.lf index 10bcb7ab..9e9a8a90 100644 --- a/C/src/ChatApplication/SimpleChat.lf +++ b/C/src/ChatApplication/SimpleChat.lf @@ -1,14 +1,12 @@ /** - * This program is a simple federated chat application written in Lingua Franca - * (LF) for two users, represented by two instances of the `ChatHandler` - * reactor, `a` and `b`. Each `ChatHandler` consists of an `InputHandler` and a - * `Printer`. The `InputHandler` is responsible for receiving user input from - * the console and sending it as a message. When the user enters a message, the - * `InputHandler` reads it, prints it, and then sends it via its `out` port. The - * message then gets relayed by the `ChatHandler` via its `send` port. On the - * receiving side, the `ChatHandler` directs the incoming message through its - * `receive` port to the `Printer` reactor, which prints the received message. - * This process creates a two-way communication where `a` and `b` can send and + * This program is a simple federated chat application written in Lingua Franca (LF) for two users, + * represented by two instances of the `ChatHandler` reactor, `a` and `b`. Each `ChatHandler` + * consists of an `InputHandler` and a `Printer`. The `InputHandler` is responsible for receiving + * user input from the console and sending it as a message. When the user enters a message, the + * `InputHandler` reads it, prints it, and then sends it via its `out` port. The message then gets + * relayed by the `ChatHandler` via its `send` port. On the receiving side, the `ChatHandler` + * directs the incoming message through its `receive` port to the `Printer` reactor, which prints + * the received message. This process creates a two-way communication where `a` and `b` can send and * receive messages from each other. * * @author Byeonggil Jun (junbg@hanyang.ac.kr) diff --git a/C/src/Delay.lf b/C/src/Delay.lf index 4538f4d0..69f55614 100644 --- a/C/src/Delay.lf +++ b/C/src/Delay.lf @@ -1,58 +1,53 @@ /** - * This (rather trivial) example illustrates a logical action used - * to model a delay. The delay is also realized a second time - * using the `after` keyword. - * + * This (rather trivial) example illustrates a logical action used to model a delay. The delay is + * also realized a second time using the `after` keyword. + * * @author Marten Lohstroh * @author Edward A. Lee */ -target C {timeout: 1 sec}; +target C { + timeout: 1 sec +} main reactor { - ramp = new Ramp(); - delay = new Delay2(); - print = new Print(); - ramp.y -> delay.x; - delay.y -> print.x; - - ramp2 = new Ramp(); - print2 = new Print(); - ramp2.y -> print2.x after 50 msec; + ramp = new Ramp() + delay = new Delay2() + print = new Print() + ramp.y -> delay.x + delay.y -> print.x + + ramp2 = new Ramp() + print2 = new Print() + ramp2.y -> print2.x after 50 msec } -/** - * Generate a counting sequence with outputs every 100 msec. - */ +/** Generate a counting sequence with outputs every 100 msec. */ reactor Ramp { - timer t(0, 100 msec); - output y:int; - state count:int(0); + timer t(0, 100 msec) + output y: int + state count: int = 0 + reaction(t) -> y {= lf_set(y, self->count); self->count++; =} } -/** - * Realize a logical delay of 50 msec. - */ +/** Realize a logical delay of 50 msec. */ reactor Delay2 { - logical action a(50 msec):int; - input x:int; - output y:int; - reaction(a) -> y {= - lf_set(y, a->value); - =} - reaction(x) -> a {= - lf_schedule_int(a, 0, x->value); - =} + logical action a(50 msec): int + input x: int + output y: int + + reaction(a) -> y {= lf_set(y, a->value); =} + + reaction(x) -> a {= lf_schedule_int(a, 0, x->value); =} } -/** - * Print the (elapsed) logical and physical times at which inputs are received. - */ +/** Print the (elapsed) logical and physical times at which inputs are received. */ reactor Print { - input x:int; + input x: int + reaction(x) {= printf("Logical time: %lld, Physical time %lld" ", Value: %d\n", diff --git a/C/src/DistributedDatabase/FederatedDatabase.lf b/C/src/DistributedDatabase/FederatedDatabase.lf index ff3a1ef8..b79e330c 100644 --- a/C/src/DistributedDatabase/FederatedDatabase.lf +++ b/C/src/DistributedDatabase/FederatedDatabase.lf @@ -1,7 +1,6 @@ /** - * Federated version of ReplicatedDatabase. - * This is identical, except that it is federated. - * + * Federated version of ReplicatedDatabase. This is identical, except that it is federated. + * * @author Edward A. Lee * @author Soroush Bateni */ @@ -12,22 +11,19 @@ target C { import Platform from "ReplicatedDatabase.lf" -federated reactor ( - query_period:time(1 sec), - num_remote_inputs:int(1) -) { +federated reactor(query_period: time = 1 sec, num_remote_inputs: int = 1) { a = new Platform( - query_period = query_period, + query_period=query_period, update_period = 5 sec, - update_amount = 100, + update_amount=100, name = "San Francisco", - num_remote_inputs = num_remote_inputs); + num_remote_inputs=num_remote_inputs) b = new Platform( - query_period = query_period, + query_period=query_period, update_period = 1 sec, - update_amount = -20, - name = "Berkeley", - num_remote_inputs = num_remote_inputs); - b.publish -> a.update; - a.publish -> b.update; + update_amount=-20, + name="Berkeley", + num_remote_inputs=num_remote_inputs) + b.publish -> a.update + a.publish -> b.update } diff --git a/C/src/DistributedDatabase/ReplicatedDatabase.lf b/C/src/DistributedDatabase/ReplicatedDatabase.lf index b10696c5..4b9b1730 100644 --- a/C/src/DistributedDatabase/ReplicatedDatabase.lf +++ b/C/src/DistributedDatabase/ReplicatedDatabase.lf @@ -1,16 +1,15 @@ /** - * Test program illustrating the architecture of a replicated distributed - * database. This models a simple banking system that maintains a single - * balance in multiple locations. Deposits and withdrawals (updates) can - * be performed at any of the locations. In the model, these updates - * are performed by a "Server" model emulating a web server. The server - * also periodically queries for the balance. All locations are required - * to report the same balance given the same time-stamped query. - * - * If the two servers simultaneously update the record, then both updates - * are applied. Any query at the time of these updates is required to - * report the result after both updates have been performed. - * + * Test program illustrating the architecture of a replicated distributed database. This models a + * simple banking system that maintains a single balance in multiple locations. Deposits and + * withdrawals (updates) can be performed at any of the locations. In the model, these updates are + * performed by a "Server" model emulating a web server. The server also periodically queries for + * the balance. All locations are required to report the same balance given the same time-stamped + * query. + * + * If the two servers simultaneously update the record, then both updates are applied. Any query at + * the time of these updates is required to report the result after both updates have been + * performed. + * * @author Edward A. Lee * @author Soroush Bateni */ @@ -18,96 +17,88 @@ target C { timeout: 5 sec } -main reactor ( - query_period:time(1 sec), - num_remote_inputs:int(1) -) { +main reactor(query_period: time = 1 sec, num_remote_inputs: int = 1) { a = new Platform( - query_period = query_period, + query_period=query_period, update_period = 5 sec, - update_amount = 100, + update_amount=100, name = "San Francisco", - num_remote_inputs = num_remote_inputs); + num_remote_inputs=num_remote_inputs) b = new Platform( - query_period = query_period, + query_period=query_period, update_period = 1 sec, - update_amount = -20, - name = "Berkeley", - num_remote_inputs = num_remote_inputs); - b.publish -> a.update; - a.publish -> b.update; + update_amount=-20, + name="Berkeley", + num_remote_inputs=num_remote_inputs) + b.publish -> a.update + a.publish -> b.update } /** - * Mockup for a web server that issues deposits or withdrawals - * as well as periodic queries for the balance in the database. - * The queries and updates are issued periodically with the period - * and amount of the update controlled by parameters. - * In a real server, these outputs would not be periodic, but - * rather would be triggered by external events, such as incoming - * HTTP requests. - * - * This reactor expects a reply to each query. It prints those - * replies. It produces an error in its shutdown reaction if - * the number of replies does not match the number of queries. - * + * Mockup for a web server that issues deposits or withdrawals as well as periodic queries for the + * balance in the database. The queries and updates are issued periodically with the period and + * amount of the update controlled by parameters. In a real server, these outputs would not be + * periodic, but rather would be triggered by external events, such as incoming HTTP requests. + * + * This reactor expects a reply to each query. It prints those replies. It produces an error in its + * shutdown reaction if the number of replies does not match the number of queries. + * * @param query_period The period of query outputs. * @param update_period The period of update outputs. * @param update_amount The amount of each deposit (or withdrawal if negative). * @param server_name The name (for reporting). - * + * * @input reply Accepts the reply to a query for the balance. - * + * * @output query Issue a query, expecting a reply. * @output update Issue an update. */ reactor Server( - name:char*("unnamed server"), - query_period:time(150 msec), - update_period:time(100 msec), - update_deadline:time(200 msec), - update_amount:int(0), - server_name:char*("unnamed server") -) { - timer query_trigger(0, query_period); - timer update_trigger(0, update_period); - input reply:int; - output query:bool; - output update:int; - state queries_outstanding:int(0); + name: char* = "unnamed server", + query_period: time = 150 msec, + update_period: time = 100 msec, + update_deadline: time = 200 msec, + update_amount: int = 0, + server_name: char* = "unnamed server") { + timer query_trigger(0, query_period) + timer update_trigger(0, update_period) + input reply: int + output query: bool + output update: int + state queries_outstanding: int = 0 + reaction(query_trigger) -> query {= lf_set(query, true); self->queries_outstanding++; =} - - reaction(update_trigger) -> update {= - lf_set(update, self->update_amount); - =} deadline(update_deadline) {= + + reaction(update_trigger) -> update {= lf_set(update, self->update_amount); =} deadline( + update_deadline) {= tag_t tag = lf_tag(); lf_print_error("At tag (%lld, %u), deadline missed at database \"%s\". Rejecting update.\n" " Elapsed physical time is %lld.", - tag.time - lf_time_start(), + tag.time - lf_time_start(), tag.microstep, self->name, lf_time_physical_elapsed() ); =} - + reaction(reply) {= lf_print("***** At tag (%lld, %u), server \"%s\" reports balance: %d.", lf_time_logical_elapsed(), lf_tag().microstep, self->server_name, reply->value ); self->queries_outstanding--; =} - + reaction(shutdown) {= if (self->queries_outstanding != 0) { - lf_print_error("Server \"%s\": Number of queries with no reply: %d.", + lf_print_error("Server \"%s\": Number of queries with no reply: %d.", self->server_name, self->queries_outstanding ); } else { - lf_print("Server \"%s\" successfully replied to all queries.", + lf_print("Server \"%s\" successfully replied to all queries.", self->server_name ); } @@ -115,37 +106,31 @@ reactor Server( } /** - * A mockup for a replicated database. This simple database contains - * only one record, and the value of that record is an integer. - * It represents a bank balance, where deposits and withdrawals - * (updates) can occur at any node in the system. - * If two or more updates occur at the same logical time, then all - * are applied. - * - * This reactor has two update inputs, `local_update` and `remote_update`. - * The first is intended to be used for updates to the database that are - * generated on the local platform. The second receives notifications - * of updates on remote platforms. When a `local_update` is received, - * the update will also be sent to the `publish` output so that it can - * be forwarded to other replicas. When any `remote_update` input arrives, - * its value will be added to the balance. + * A mockup for a replicated database. This simple database contains only one record, and the value + * of that record is an integer. It represents a bank balance, where deposits and withdrawals + * (updates) can occur at any node in the system. If two or more updates occur at the same logical + * time, then all are applied. + * + * This reactor has two update inputs, `local_update` and `remote_update`. The first is intended to + * be used for updates to the database that are generated on the local platform. The second receives + * notifications of updates on remote platforms. When a `local_update` is received, the update will + * also be sent to the `publish` output so that it can be forwarded to other replicas. When any + * `remote_update` input arrives, its value will be added to the balance. + * + * The `query` input is used to retrieve the current balance. The balance will be sent to the + * `balance` output in response. If a `query` and an update arrive simultaneously, the reply will + * include the cumulative effect of all the updates. + * + * Instances of these `Database` reactors can be arranged in ring or in a broadcast configuration, + * where each replica sends updates to all other replicas. + * + * There is a deadline on the `local_update` inputs. If this deadline is violated, then the update + * is rejected and has no effect on the balance. * - * The `query` input is used to retrieve the current balance. The - * balance will be sent to the `balance` output in response. - * If a `query` and an update arrive simultaneously, the reply - * will include the cumulative effect of all the updates. - * - * Instances of these `Database` reactors can be arranged in ring - * or in a broadcast configuration, where each replica sends updates to - * all other replicas. - * - * There is a deadline on the `local_update` inputs. If this deadline is - * violated, then the update is rejected and has no effect on the balance. - * * @param update_deadline A deadline imposed on the reaction to `local_update` inputs. * @param name A name for the database instance (used in reporting). * @param num_remote_inputs The number of inputs from other database replicas. - * + * * @input local_update An update (deposit or withdrawal) to add to the record. * @input remote_update A multiport input for receiving updates from other replicas. * @input query A trigger to read the current value of the record. @@ -153,16 +138,13 @@ reactor Server( * @output publish This is just the update passed through. * @output balance The time value of the record. */ -reactor Database( - name:char*("unnamed database"), - num_remote_inputs:int(1) -) { - input local_update:int; - input[num_remote_inputs] remote_update:int; - input query:bool; - output balance:int; - state record:int(0); - +reactor Database(name: char* = "unnamed database", num_remote_inputs: int = 1) { + input local_update: int + input[num_remote_inputs] remote_update: int + input query: bool + output balance: int + state record: int = 0 + reaction(local_update, remote_update) {= if (local_update->is_present) { self->record += local_update->value; @@ -179,79 +161,70 @@ reactor Database( self->record, lf_time_physical_elapsed() ); - =} STP (4 msec) {= -#ifdef FEDERATED_DECENTRALIZED - for (int i = 0; i < remote_update_width; i++) { - if (remote_update[i]->is_present) { - lf_print_warning("At tag (%lld, %u), database \"%s\" " - "received remote update (%d) with intended tag (%lld, %u).\n" - " Balance previously reported may have been incorrect.\n" - " Elapsed physical time is %lld.", - current_tag.time - lf_time_start(), - current_tag.microstep, - self->name, - remote_update[i]->value, - remote_update[i]->intended_tag.time - lf_time_start(), - remote_update[i]->intended_tag.microstep, - lf_time_physical_elapsed() - ); - self->record += remote_update[i]->value; - } - } -#else - // The tardy handler should not be invoked - lf_print_error_and_exit("FATAL: Update is tardy and coordination is not decentralized."); -#endif - =} - - reaction(query) -> balance {= - lf_set(balance, self->record); + =} STP(4 msec) {= + #ifdef FEDERATED_DECENTRALIZED + for (int i = 0; i < remote_update_width; i++) { + if (remote_update[i]->is_present) { + lf_print_warning("At tag (%lld, %u), database \"%s\" " + "received remote update (%d) with intended tag (%lld, %u).\n" + " Balance previously reported may have been incorrect.\n" + " Elapsed physical time is %lld.", + current_tag.time - lf_time_start(), + current_tag.microstep, + self->name, + remote_update[i]->value, + remote_update[i]->intended_tag.time - lf_time_start(), + remote_update[i]->intended_tag.microstep, + lf_time_physical_elapsed() + ); + self->record += remote_update[i]->value; + } + } + #else + // The tardy handler should not be invoked + lf_print_error_and_exit("FATAL: Update is tardy and coordination is not decentralized."); + #endif =} + + reaction(query) -> balance {= lf_set(balance, self->record); =} } /** - * A mockup of a platform (e.g. a pod, a virtual machine, a physical - * machine, etc.) that hosts a replicated database and a web server. - * The platform performs local updates and accepts remote updates. - * Instances of this platform can be arranged in a ring or in a - * broadcast configuration, where each platform broadcasts updates - * to all other platforms. - * + * A mockup of a platform (e.g. a pod, a virtual machine, a physical machine, etc.) that hosts a + * replicated database and a web server. The platform performs local updates and accepts remote + * updates. Instances of this platform can be arranged in a ring or in a broadcast configuration, + * where each platform broadcasts updates to all other platforms. + * * @param query_period The period at which the balance is queried. * @param update_period The period at which local updates are generated. * @param update_amount The amount of each local update. * @param num_remote_inputs The number of remote inputs from other replicas. * @param STP The safe-to-process offset (used only in decentralized coordination). * @param name The name assigned to this platform (for reporting). - * + * * @input update An update notification from another replica. - * + * * @output publish A copy of any local updates. */ reactor Platform( - query_period:time(150 msec), - update_period:time(100 msec), - update_amount:int(0), - num_remote_inputs:int(1), - name:char*("unnamed platform") // Used for more visible logging -) { - input[num_remote_inputs] update:int; - output publish:int; + query_period: time = 150 msec, + update_period: time = 100 msec, + update_amount: int = 0, + num_remote_inputs: int = 1, + // Used for more visible logging + name: char* = "unnamed platform") { + input[num_remote_inputs] update: int + output publish: int server = new Server( - name = name, - query_period = query_period, - update_period = update_period, - update_amount = update_amount, - server_name = name - ); - database = new Database( - name = name, - num_remote_inputs = num_remote_inputs - ); - server.query -> database.query; - server.update -> database.local_update; - database.balance -> server.reply; - server.update -> publish; - update -> database.remote_update; + name=name, + query_period=query_period, + update_period=update_period, + update_amount=update_amount, + server_name=name) + database = new Database(name=name, num_remote_inputs=num_remote_inputs) + server.query -> database.query + server.update -> database.local_update + database.balance -> server.reply + server.update -> publish + update -> database.remote_update } - diff --git a/C/src/DistributedDatabase/ReplicatedDatabaseThree.lf b/C/src/DistributedDatabase/ReplicatedDatabaseThree.lf index 74e7746f..8056f319 100644 --- a/C/src/DistributedDatabase/ReplicatedDatabaseThree.lf +++ b/C/src/DistributedDatabase/ReplicatedDatabaseThree.lf @@ -1,6 +1,5 @@ /** - * A version of ReplicatedDatabase.lf with three replicas arranged in - * a broadcast pattern. + * A version of ReplicatedDatabase.lf with three replicas arranged in a broadcast pattern. * @author Edward A. Lee * @author Soroush Bateni */ @@ -10,29 +9,26 @@ target C { import Platform from "ReplicatedDatabase.lf" -main reactor ( - query_period:time(1 sec), - num_remote_inputs:int(2) -) { +main reactor(query_period: time = 1 sec, num_remote_inputs: int = 2) { a = new Platform( - query_period = query_period, + query_period=query_period, update_period = 5 sec, - update_amount = 100, + update_amount=100, name = "San Francisco", - num_remote_inputs = num_remote_inputs); + num_remote_inputs=num_remote_inputs) b = new Platform( - query_period = query_period, + query_period=query_period, update_period = 1 sec, - update_amount = -20, - name = "Berkeley", - num_remote_inputs = num_remote_inputs); + update_amount=-20, + name="Berkeley", + num_remote_inputs=num_remote_inputs) c = new Platform( - query_period = query_period, + query_period=query_period, update_period = 3 sec, - update_amount = 10, - name = "Berkeley", - num_remote_inputs = num_remote_inputs); - b.publish, c.publish -> a.update; - a.publish, c.publish -> b.update; - a.publish, b.publish -> c.update; + update_amount=10, + name="Berkeley", + num_remote_inputs=num_remote_inputs) + b.publish, c.publish -> a.update + a.publish, c.publish -> b.update + a.publish, b.publish -> c.update } diff --git a/C/src/DistributedHelloWorld/HelloWorld.lf b/C/src/DistributedHelloWorld/HelloWorld.lf index 46940a34..91c0ff6f 100644 --- a/C/src/DistributedHelloWorld/HelloWorld.lf +++ b/C/src/DistributedHelloWorld/HelloWorld.lf @@ -1,46 +1,43 @@ /** - * Distributed LF program where a MessageGenerator creates a string - * message that is sent via the RTI (runtime infrastructure) to a - * receiver that prints the message. - * + * Distributed LF program where a MessageGenerator creates a string message that is sent via the RTI + * (runtime infrastructure) to a receiver that prints the message. + * * The code generator generates three programs: - * - * * bin/DistributedHelloWorld: A script that launches the other three - * programs on localhost. Run this program. - * - * * bin/DistrubtedHelloWorld_source: The program that produces the sequence - * of messages. - * - * * bin/DistrubtedHelloWorld_print: The program that produces the sequence - * of messages. - * - * To run this manually, you can start the RTI and then each of the last - * two programs. They will synchronize the start time and run for 10 seconds. - * - * Note: The RTI is a separate program that has to be installed separately. - * See https://github.com/lf-lang/reactor-c/tree/main/core/federated/RTI - * + * + * * bin/DistributedHelloWorld: A script that launches the other three programs on localhost. Run + * this program. + * + * * bin/DistrubtedHelloWorld_source: The program that produces the sequence of messages. + * + * * bin/DistrubtedHelloWorld_print: The program that produces the sequence of messages. + * + * To run this manually, you can start the RTI and then each of the last two programs. They will + * synchronize the start time and run for 10 seconds. + * + * Note: The RTI is a separate program that has to be installed separately. See + * https://github.com/lf-lang/reactor-c/tree/main/core/federated/RTI + * * @author Edward A. Lee */ target C { timeout: 10 secs -}; +} /** - * Reactor that generates a sequence of messages, one per second. - * The message will be a string consisting of a prefix string followed - * by a count. + * Reactor that generates a sequence of messages, one per second. The message will be a string + * consisting of a prefix string followed by a count. * @param prefix The prefix string. * @output message The message. */ -reactor MessageGenerator(prefix:string("")) { +reactor MessageGenerator(prefix: string = "") { // Output type char* instead of string is used for dynamically // allocated character arrays (as opposed to static constant strings). - output message:char*; - state count:int(1); + output message: char* + state count: int = 1 // Send first message after 1 sec so that the startup reactions // do not factor into the transport time measurement on the first message. - timer t(1 sec, 1 sec); + timer t(1 sec, 1 sec) + reaction(t) -> message {= // With NULL, 0 arguments, snprintf tells us how many bytes are needed. // Add one for the null terminator. @@ -64,7 +61,8 @@ reactor MessageGenerator(prefix:string("")) { * @input message The message. */ reactor PrintMessage { - input message:char*; + input message: char* + reaction(message) {= tag_t tag = lf_tag(); lf_print("At (elapsed) logical tag (%lld, %u), print receives: %s", @@ -75,7 +73,7 @@ reactor PrintMessage { } federated reactor HelloWorld { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessage(); - source.message -> print.message; + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessage() + source.message -> print.message } diff --git a/C/src/DistributedHelloWorld/HelloWorldAfter.lf b/C/src/DistributedHelloWorld/HelloWorldAfter.lf index 64172656..13a8f29a 100644 --- a/C/src/DistributedHelloWorld/HelloWorldAfter.lf +++ b/C/src/DistributedHelloWorld/HelloWorldAfter.lf @@ -1,18 +1,18 @@ /** - * Version of HelloWorld with an `after` on the connection. - * The after value on the connection gives a logical time offset - * between the sender and the receiver. - * + * Version of HelloWorld with an `after` on the connection. The after value on the connection gives + * a logical time offset between the sender and the receiver. + * * @author Edward A. Lee */ target C { timeout: 10 secs -}; +} + import MessageGenerator from "HelloWorld.lf" import PrintMessage from "HelloWorld.lf" federated reactor HelloWorldAfter { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessage(); - source.message -> print.message after 10 msec; + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessage() + source.message -> print.message after 10 msec } diff --git a/C/src/DistributedHelloWorld/HelloWorldDecentralized.lf b/C/src/DistributedHelloWorld/HelloWorldDecentralized.lf index d0862b9c..93270547 100644 --- a/C/src/DistributedHelloWorld/HelloWorldDecentralized.lf +++ b/C/src/DistributedHelloWorld/HelloWorldDecentralized.lf @@ -1,41 +1,41 @@ /** - * Version of HelloWorld that uses decentralized coordination. - * The `offset` parameter at the top level specifies an `after` delay to - * use on the connection. If this delay is too small, then the `print` - * receiving federate will report tardy messages, which are messages where - * the intended timestamp cannot be assigned because the message arrived - * too late. - * - * If the sender and receiver are running in the same machine - * then there is no clock synchronization error and the communication latency - * should be well less than 10msec, so an `offset` of 10 msec should be plenty - * adequate to avoid any tardy messages. If you change the offset to - * 10 usec, then tardy messages will likely occur, unless, on your machine, - * communication between two processes can reliably occur within 10 microseconds. - * + * Version of HelloWorld that uses decentralized coordination. The `offset` parameter at the top + * level specifies an `after` delay to use on the connection. If this delay is too small, then the + * `print` receiving federate will report tardy messages, which are messages where the intended + * timestamp cannot be assigned because the message arrived too late. + * + * If the sender and receiver are running in the same machine then there is no clock synchronization + * error and the communication latency should be well less than 10msec, so an `offset` of 10 msec + * should be plenty adequate to avoid any tardy messages. If you change the offset to 10 usec, then + * tardy messages will likely occur, unless, on your machine, communication between two processes + * can reliably occur within 10 microseconds. + * * @author Edward A. Lee */ target C { timeout: 10 secs, coordination: decentralized -}; +} + import MessageGenerator from "HelloWorld.lf" import PrintMessage from "HelloWorld.lf" -reactor PrintMessageWithDetector(offset:time(10 msec)) extends PrintMessage { +reactor PrintMessageWithDetector(offset: time = 10 msec) extends PrintMessage { // The timer here creates a worst-case scenario where the receiving // federate has an event to process whose timestamp matches that of an // incoming message. Without this timer, you will not see any tardy // messages because the `print` reactor has no reason to advance its // logical time, and hence any incoming intended tag can be handled. - timer local(offset, 1 sec); - reaction (message) {= + timer local(offset, 1 sec) + + reaction(message) {= // Empty. The base class will react and report the incoming message. =} STP(0) {= lf_print_warning("Message is tardy. Intended tag is (%lld, %u).", message->intended_tag.time - start_time, message->intended_tag.microstep ); =} + reaction(local) {= tag_t tag = lf_tag(); lf_print("Timer triggered at logical tag (%lld, %u).", @@ -44,10 +44,8 @@ reactor PrintMessageWithDetector(offset:time(10 msec)) extends PrintMessage { =} } -federated reactor HelloWorldDecentralized(offset:time(10 msec)) { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessageWithDetector( - offset = offset - ); - source.message -> print.message after offset; +federated reactor HelloWorldDecentralized(offset: time = 10 msec) { + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessageWithDetector(offset=offset) + source.message -> print.message after offset } diff --git a/C/src/DistributedHelloWorld/HelloWorldDecentralizedSTP.lf b/C/src/DistributedHelloWorld/HelloWorldDecentralizedSTP.lf index 50729a0b..6c922d08 100644 --- a/C/src/DistributedHelloWorld/HelloWorldDecentralizedSTP.lf +++ b/C/src/DistributedHelloWorld/HelloWorldDecentralizedSTP.lf @@ -1,47 +1,38 @@ /** - * Version of HelloWorldDecentralized that uses decentralized - * coordination with an STP (safe to process) offset instead of an `after` - * delay. In this version, the timestamps at the receiving federate `print` - * are the same as the timestamps at the sender `source`. - * - * The `STP_offset` parameter on the `print` federate tells the runtime - * system to wait the amount of time specified before processing events. - * That is, before processing an event with timestamp *t*, the federate - * waits until physical time *T* exceeds *t* + `STP`. - * - * If the `STP_offset` is too small, then the `print` - * receiving federate will report tardy messages, which are messages where - * the intended timestamp cannot be assigned because the message arrived + * Version of HelloWorldDecentralized that uses decentralized coordination with an STP (safe to + * process) offset instead of an `after` delay. In this version, the timestamps at the receiving + * federate `print` are the same as the timestamps at the sender `source`. + * + * The `STP_offset` parameter on the `print` federate tells the runtime system to wait the amount of + * time specified before processing events. That is, before processing an event with timestamp *t*, + * the federate waits until physical time *T* exceeds *t* + `STP`. + * + * If the `STP_offset` is too small, then the `print` receiving federate will report tardy messages, + * which are messages where the intended timestamp cannot be assigned because the message arrived * too late. - * - * If the sender and receiver are running in the same machine - * then there is no clock synchronization error and the communication latency - * should be well less than 10msec, so an `offset` of 10 msec should be plenty - * adequate to avoid any tardy messages. If you change the offset to - * 10 usec, then tardy messages will likely occur, unless, on your machine, - * communication between two processes can reliably occur within 10 microseconds. + * + * If the sender and receiver are running in the same machine then there is no clock synchronization + * error and the communication latency should be well less than 10msec, so an `offset` of 10 msec + * should be plenty adequate to avoid any tardy messages. If you change the offset to 10 usec, then + * tardy messages will likely occur, unless, on your machine, communication between two processes + * can reliably occur within 10 microseconds. * * @author Edward A. Lee */ target C { timeout: 10 secs, coordination: decentralized -}; +} + import MessageGenerator from "HelloWorld.lf" import PrintMessageWithDetector from "HelloWorldDecentralized.lf" -/** - * Subclass that simply adds an `STP` parameter, nothing more. - */ -reactor PrintMessageWithSTP(STP_offset:time(10 msec)) extends PrintMessageWithDetector { - +/** Subclass that simply adds an `STP` parameter, nothing more. */ +reactor PrintMessageWithSTP(STP_offset: time = 10 msec) extends PrintMessageWithDetector { } federated reactor { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessageWithSTP( - offset = 0, - STP_offset = 10 msec - ); - source.message -> print.message; + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessageWithSTP(offset=0, STP_offset = 10 msec) + source.message -> print.message } diff --git a/C/src/DistributedHelloWorld/HelloWorldPhysical.lf b/C/src/DistributedHelloWorld/HelloWorldPhysical.lf index fc70ce80..2611039c 100644 --- a/C/src/DistributedHelloWorld/HelloWorldPhysical.lf +++ b/C/src/DistributedHelloWorld/HelloWorldPhysical.lf @@ -1,27 +1,25 @@ /** - * Version of HelloWorld that uses a physical connection, - * indicated by ~>. - * - * The timestamp assigned at the receiving end depends on the value - * of the physical clock at the receiving end. The extend to which this - * disagrees with the timestamp at the sending end is a measure of the - * communication time between the processes. - * - * Note that with physical connections, messages go directly from one - * federate to another, not through the RTI as they normally would with - * logical connections and centralized control (the default). - * + * Version of HelloWorld that uses a physical connection, indicated by ~>. + * + * The timestamp assigned at the receiving end depends on the value of the physical clock at the + * receiving end. The extend to which this disagrees with the timestamp at the sending end is a + * measure of the communication time between the processes. + * + * Note that with physical connections, messages go directly from one federate to another, not + * through the RTI as they normally would with logical connections and centralized control (the + * default). + * * @author Edward A. Lee */ target C { timeout: 10 secs -}; +} import MessageGenerator from "HelloWorld.lf" import PrintMessage from "HelloWorld.lf" federated reactor HelloWorldPhysical { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessage(); - source.message ~> print.message; + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessage() + source.message ~> print.message } diff --git a/C/src/DistributedHelloWorld/HelloWorldPhysicalAfter.lf b/C/src/DistributedHelloWorld/HelloWorldPhysicalAfter.lf index 049c3117..6f01ab82 100644 --- a/C/src/DistributedHelloWorld/HelloWorldPhysicalAfter.lf +++ b/C/src/DistributedHelloWorld/HelloWorldPhysicalAfter.lf @@ -1,23 +1,23 @@ /** - * Version of HelloWorldPhysical that uses a physical connection, - * indicated by ~>, with an `after` on the connection. - * - * The after value on the connection gives a logical time offset - * between the sender and the receiver. If the connection were not a physical one, - * indicated with ~>, then this logical time offset would simply be the logical - * time offset obtained. However, since it is a physical one, the offset between - * the timestamps is the the communication latency plus 10 msec. - * + * Version of HelloWorldPhysical that uses a physical connection, indicated by ~>, with an `after` + * on the connection. + * + * The after value on the connection gives a logical time offset between the sender and the + * receiver. If the connection were not a physical one, indicated with ~>, then this logical time + * offset would simply be the logical time offset obtained. However, since it is a physical one, the + * offset between the timestamps is the the communication latency plus 10 msec. + * * @author Edward A. Lee */ target C { timeout: 10 secs -}; +} + import MessageGenerator from "HelloWorld.lf" import PrintMessage from "HelloWorld.lf" federated reactor HelloWorldPhysicalAfter { - source = new MessageGenerator(prefix = "Hello World"); + source = new MessageGenerator(prefix = "Hello World") print = new PrintMessage() at localhost; - source.message ~> print.message after 10 msec; + source.message ~> print.message after 10 msec } diff --git a/C/src/DistributedHelloWorld/docker/HelloWorldContainerized.lf b/C/src/DistributedHelloWorld/docker/HelloWorldContainerized.lf index 52e934e5..5067f2af 100644 --- a/C/src/DistributedHelloWorld/docker/HelloWorldContainerized.lf +++ b/C/src/DistributedHelloWorld/docker/HelloWorldContainerized.lf @@ -1,22 +1,21 @@ /** - * Containerized distributed LF program where a MessageGenerator creates a string - * message that is sent via the RTI (runtime infrastructure) to a - * receiver that prints the message. - * + * Containerized distributed LF program where a MessageGenerator creates a string message that is + * sent via the RTI (runtime infrastructure) to a receiver that prints the message. + * * For run instructions, see README. - * + * * @author Edward A. Lee */ target C { timeout: 10 secs, docker: true -}; +} import MessageGenerator from "../HelloWorld.lf" import PrintMessage from "../HelloWorld.lf" federated reactor HelloWorldContainerized at rti { - source = new MessageGenerator(prefix = "Hello World"); - print = new PrintMessage(); - source.message -> print.message; + source = new MessageGenerator(prefix = "Hello World") + print = new PrintMessage() + source.message -> print.message } diff --git a/C/src/DistributedResourceManagement/FederatedResourceManagement.lf b/C/src/DistributedResourceManagement/FederatedResourceManagement.lf index 90716394..9ae2dfba 100644 --- a/C/src/DistributedResourceManagement/FederatedResourceManagement.lf +++ b/C/src/DistributedResourceManagement/FederatedResourceManagement.lf @@ -1,34 +1,29 @@ /** - * Demo program illustrating the architecture of a distributed resource management - * problem like that described in: - * - * Lamport, L. (1984). "Using Time Instead of Timeout for Fault-Tolerant Distributed Systems." - * ACM Transactions on Programming Languages and Systems 6(2): 254-280. + * Demo program illustrating the architecture of a distributed resource management problem like that + * described in: + * + * Lamport, L. (1984). "Using Time Instead of Timeout for Fault-Tolerant Distributed Systems." ACM + * Transactions on Programming Languages and Systems 6(2): 254-280. + * + * The goal is distributed first-come, first-served exclusive access to a shared resource. + * + * Each instance of Client is initially idle for a time given by its `idle_time` parameter. It then + * requests exclusive access to the resource by sending a `request` message to its local instance of + * ResourceManager. If the resource is available, then the ResourceManager immediately replies with + * a `grant` message. The Client will then hold the resource for an amount of time given by its + * `use_time` parameter, after which it will send a `release` message to the ResourceManager. If the + * resource is not available when the Client requests it, then the ResourceManager queues the + * request and sends the `grant` message when the resource becomes available. + * + * Each instance of ResourceManager maintains a copy of the queue of pending requests to the + * resource. The entries in the queue are ordered by tag (logical time and microstep) so that they + * will be granted using a first-come, first-served policy. If two requests are made simultaneously + * (with the same tag), then they will be granted access in order of their IDs. + * + * At all logical tags, every instance of ResourceManager should have exactly the same queue + * contents. Hence, at every tag, all ResourceManagers agree on which manager has access to the + * resource. It is the manager at the head of their queue. * - * The goal is distributed first-come, first-served exclusive access to a shared - * resource. - * - * Each instance of Client is initially idle for a time given by its - * `idle_time` parameter. It then requests exclusive access to the resource by - * sending a `request` message to its local instance of ResourceManager. - * If the resource is available, then the ResourceManager immediately replies - * with a `grant` message. The Client will then hold the resource for an - * amount of time given by its `use_time` parameter, after which it will send - * a `release` message to the ResourceManager. If the resource is not available - * when the Client requests it, then the ResourceManager queues the request - * and sends the `grant` message when the resource becomes available. - * - * Each instance of ResourceManager maintains a copy of the queue of pending - * requests to the resource. The entries in the queue are ordered by tag - * (logical time and microstep) so that they will be granted using a first-come, - * first-served policy. If two requests are made simultaneously (with the same - * tag), then they will be granted access in order of their IDs. - * - * At all logical tags, every instance of ResourceManager should have exactly - * the same queue contents. Hence, at every tag, all ResourceManagers agree on - * which manager has access to the resource. It is the manager at the head of - * their queue. - * * @author Edward A. Lee */ target C { @@ -37,24 +32,22 @@ target C { import Platform from "ResourceManagement.lf" -federated reactor FederatedResourceManagement( - num_other_resource_managers:int(1) -) { +federated reactor FederatedResourceManagement(num_other_resource_managers: int = 1) { // Each platform needs a unique id that is between 0 and QUEUE_SIZE-1. a = new Platform( idle_time = 50 msec, use_time = 60 msec, name = "San Francisco", - id = 0, - num_other_resource_managers = num_other_resource_managers); + id=0, + num_other_resource_managers=num_other_resource_managers) b = new Platform( idle_time = 200 msec, use_time = 40 msec, - name = "Berkeley", - id = 1, - num_other_resource_managers = num_other_resource_managers); - b.request -> a.remote_request; - b.release -> a.remote_release; - a.request -> b.remote_request; - a.release -> b.remote_release; + name="Berkeley", + id=1, + num_other_resource_managers=num_other_resource_managers) + b.request -> a.remote_request + b.release -> a.remote_release + a.request -> b.remote_request + a.release -> b.remote_release } diff --git a/C/src/DistributedResourceManagement/ResourceManagement.lf b/C/src/DistributedResourceManagement/ResourceManagement.lf index 47a3335f..6d3acd17 100644 --- a/C/src/DistributedResourceManagement/ResourceManagement.lf +++ b/C/src/DistributedResourceManagement/ResourceManagement.lf @@ -1,34 +1,28 @@ /** - * Demo program illustrating the architecture of a distributed resource - * management problem like that described in: + * Demo program illustrating the architecture of a distributed resource management problem like that + * described in: * - * Lamport, L. (1984). "Using Time Instead of Timeout for Fault-Tolerant - * Distributed Systems." ACM Transactions on Programming Languages and Systems - * 6(2): 254-280. + * Lamport, L. (1984). "Using Time Instead of Timeout for Fault-Tolerant Distributed Systems." ACM + * Transactions on Programming Languages and Systems 6(2): 254-280. * - * The goal is distributed first-come, first-served exclusive access to a shared - * resource. + * The goal is distributed first-come, first-served exclusive access to a shared resource. * - * Each instance of Client is initially idle for a time given by its `idle_time` - * parameter. It then requests exclusive access to the resource by sending a - * `request` message to its local instance of ResourceManager. If the resource - * is available, then the ResourceManager immediately replies with a `grant` - * message. The Client will then hold the resource for an amount of time given - * by its `use_time` parameter, after which it will send a `release` message to - * the ResourceManager. If the resource is not available when the Client - * requests it, then the ResourceManager queues the request and sends the - * `grant` message when the resource becomes available. + * Each instance of Client is initially idle for a time given by its `idle_time` parameter. It then + * requests exclusive access to the resource by sending a `request` message to its local instance of + * ResourceManager. If the resource is available, then the ResourceManager immediately replies with + * a `grant` message. The Client will then hold the resource for an amount of time given by its + * `use_time` parameter, after which it will send a `release` message to the ResourceManager. If the + * resource is not available when the Client requests it, then the ResourceManager queues the + * request and sends the `grant` message when the resource becomes available. * - * Each instance of ResourceManager maintains a copy of the queue of pending - * requests to the resource. The entries in the queue are ordered by tag - * (logical time and microstep) so that they will be granted using a first-come, - * first-served policy. If two requests are made simultaneously (with the same - * tag), then they will be granted access in order of their IDs. + * Each instance of ResourceManager maintains a copy of the queue of pending requests to the + * resource. The entries in the queue are ordered by tag (logical time and microstep) so that they + * will be granted using a first-come, first-served policy. If two requests are made simultaneously + * (with the same tag), then they will be granted access in order of their IDs. * - * At all logical tags, every instance of ResourceManager should have exactly - * the same queue contents. Hence, at every tag, all ResourceManagers agree on - * which manager has access to the resource. It is the manager at the head of - * their queue. + * At all logical tags, every instance of ResourceManager should have exactly the same queue + * contents. Hence, at every tag, all ResourceManagers agree on which manager has access to the + * resource. It is the manager at the head of their queue. * * @author Edward A. Lee */ @@ -50,9 +44,9 @@ preamble {= =} /** - * Mockup for a client that requests for access to the resource. This client - * issues a `request` after `idle_time`. When it receives a `grant`, it waits an - * amount of time `use_time` and then issues a `release` output. + * Mockup for a client that requests for access to the resource. This client issues a `request` + * after `idle_time`. When it receives a `grant`, it waits an amount of time `use_time` and then + * issues a `release` output. * * @param idle_time The time until the next `request`. * @param use_time The time after a `grant` input before a `release` output. @@ -63,11 +57,11 @@ preamble {= * @output update Release the resource. */ reactor Client( - idle_time: time = 150 msec, - use_time: time = 30 msec, - id: int = 0, - name: char* = "unnamed Client" // Used for more visible logging -) { + idle_time: time = 150 msec, + use_time: time = 30 msec, + id: int = 0, + // Used for more visible logging + name: char* = "unnamed Client") { logical action request_trigger(idle_time) logical action release_trigger(use_time) @@ -123,23 +117,20 @@ reactor Client( } /** - * A distributed resource manager that grants exclusive access to a shared - * resource to a local client. A local client requests and releases access to - * the resource through the `local_request` and `local_release` input ports. The - * `request` and `release` output ports must be connected to every other - * instance of this ResourceManager. The `remote_request` and `remote_release` - * input ports come from all other instances of this ResourceManager. + * A distributed resource manager that grants exclusive access to a shared resource to a local + * client. A local client requests and releases access to the resource through the `local_request` + * and `local_release` input ports. The `request` and `release` output ports must be connected to + * every other instance of this ResourceManager. The `remote_request` and `remote_release` input + * ports come from all other instances of this ResourceManager. * * The total number of other resource managers is required to match the - * `num_other_resource_managers` parameter, and each resource manager is - * required to have a unique `id` between 0 and `num_other_resource_managers`. A - * resource manager may also optionally have a `name`, which is used when - * reporting errors. + * `num_other_resource_managers` parameter, and each resource manager is required to have a unique + * `id` between 0 and `num_other_resource_managers`. A resource manager may also optionally have a + * `name`, which is used when reporting errors. * * @param name A name for the database instance (used in reporting). * @param id A unique id between 0 and `num_other_resource_managers`. - * @param num_other_resource_managers The total number of other resource - * managers. + * @param num_other_resource_managers The total number of other resource managers. * * @input local_request A local request for access to the resource. * @input local_release Release the resource held locally. @@ -151,10 +142,9 @@ reactor Client( * @output release Broadcast release to other resource managers. */ reactor ResourceManager( - name: char* = "unnamed ResourceManager", - id: int = 0, - num_other_resource_managers: int = 1 -) { + name: char* = "unnamed ResourceManager", + id: int = 0, + num_other_resource_managers: int = 1) { input local_request: bool input local_release: bool input[num_other_resource_managers] remote_request: int @@ -370,51 +360,40 @@ reactor ResourceManager( } /** - * A mockup of a platform (e.g. a pod, a virtual machine, a physical machine, - * etc.) that hosts a client that needs exclusive access to some resource - * managed by a ResourceManager. + * A mockup of a platform (e.g. a pod, a virtual machine, a physical machine, etc.) that hosts a + * client that needs exclusive access to some resource managed by a ResourceManager. * - * @param idle_time The amount of time the client is idle before requesting the - * resource. + * @param idle_time The amount of time the client is idle before requesting the resource. * @param use_time The amount of time the client holds on to the resource. - * @param num_other_resource_managers The number of other resource managers - * requiring access. + * @param num_other_resource_managers The number of other resource managers requiring access. * @param id A unique ID between 0 and num_other_resource_managers. * @param name A name (for reporting). * - * @input remote_request A multiport input accepting remote requests for the - * resource. - * @input remote_release A multiport input accepting remote releases of the - * resource. + * @input remote_request A multiport input accepting remote requests for the resource. + * @input remote_release A multiport input accepting remote releases of the resource. * * @output request An output to broadcast a local request for the resource. * @output release An output to broadcast a local release of the resource. */ reactor Platform( - idle_time: time = 150 msec, - use_time: time = 30 msec, - num_other_resource_managers: int = 1, - id: int = 0, - name: char* = "unnamed Platform" // Used for more visible logging -) { + idle_time: time = 150 msec, + use_time: time = 30 msec, + num_other_resource_managers: int = 1, + id: int = 0, + // Used for more visible logging + name: char* = "unnamed Platform") { input[num_other_resource_managers] remote_request: int input[num_other_resource_managers] remote_release: int output request: int output release: int - client = new Client( - name = name, - id = id, - idle_time = idle_time, - use_time = use_time - ) + client = new Client(name=name, id=id, idle_time=idle_time, use_time=use_time) manager = new ResourceManager( - name = name, - id = id, - num_other_resource_managers = num_other_resource_managers - ) + name=name, + id=id, + num_other_resource_managers=num_other_resource_managers) client.request -> manager.local_request client.release -> manager.local_release @@ -428,19 +407,17 @@ reactor Platform( main reactor ResourceManagement(num_other_resource_managers: int = 1) { // Each platform needs a unique id that is between 0 and QUEUE_SIZE-1. a = new Platform( - idle_time = 50 msec, - use_time = 60 msec, - name = "San Francisco", - id = 0, - num_other_resource_managers = num_other_resource_managers - ) + idle_time = 50 msec, + use_time = 60 msec, + name = "San Francisco", + id=0, + num_other_resource_managers=num_other_resource_managers) b = new Platform( - idle_time = 200 msec, - use_time = 40 msec, - name = "Berkeley", - id = 1, - num_other_resource_managers = num_other_resource_managers - ) + idle_time = 200 msec, + use_time = 40 msec, + name="Berkeley", + id=1, + num_other_resource_managers=num_other_resource_managers) b.request -> a.remote_request b.release -> a.remote_release a.request -> b.remote_request diff --git a/C/src/Parallelism/ForkJoin.lf b/C/src/Parallelism/ForkJoin.lf index d99d9a82..03c5f296 100644 --- a/C/src/Parallelism/ForkJoin.lf +++ b/C/src/Parallelism/ForkJoin.lf @@ -1,50 +1,54 @@ /** - * Each instance of TakeTime takes 200 ms wall clock time to - * transport the input to the output. Four of them are - * instantiated. Note that without parallel execution, there is - * no way this program can keep up with real time since in every - * 200 msec cycle it has 800 msec of work to do. Given 4 workers, - * however, this program can complete 800 msec of work in about - * 225 msec. + * Each instance of TakeTime takes 200 ms wall clock time to transport the input to the output. Four + * of them are instantiated. Note that without parallel execution, there is no way this program can + * keep up with real time since in every 200 msec cycle it has 800 msec of work to do. Given 4 + * workers, however, this program can complete 800 msec of work in about 225 msec. */ target C { timeout: 2 sec, - workers: 1, // Change to 4 to see speed up. -}; -main reactor(width:int(4)) { - a = new Source(); - t = new[width] TakeTime(); - (a.out)+ -> t.in; - b = new Destination(width = width); - t.out -> b.in; + workers: 1 // Change to 4 to see speed up. } + +main reactor(width: int = 4) { + a = new Source() + t = new[width] TakeTime() + (a.out)+ -> t.in + b = new Destination(width=width) + t.out -> b.in +} + reactor Source { - timer t(0, 200 msec); - output out:int; - state s:int(0); - reaction(t) -> out {= - lf_set(out, self->s); - self->s++; - =} + timer t(0, 200 msec) + output out: int + state s: int = 0 + + reaction(t) -> out {= + lf_set(out, self->s); + self->s++; + =} } + reactor TakeTime { - input in:int; - output out:int; - reaction(in) -> out {= - struct timespec sleep_time = {(time_t) 0, (long)200000000}; - struct timespec remaining_time; - nanosleep(&sleep_time, &remaining_time); - int offset = 0; - for (int i = 0; i < 100000000; i++) { - offset++; - } - lf_set(out, in->value + offset); - =} + input in: int + output out: int + + reaction(in) -> out {= + struct timespec sleep_time = {(time_t) 0, (long)200000000}; + struct timespec remaining_time; + nanosleep(&sleep_time, &remaining_time); + int offset = 0; + for (int i = 0; i < 100000000; i++) { + offset++; + } + lf_set(out, in->value + offset); + =} } -reactor Destination(width:int(4)) { - state s:int(400000000); - input[width] in:int; - reaction(in) {= + +reactor Destination(width: int = 4) { + state s: int = 400000000 + input[width] in: int + + reaction(in) {= int sum = 0; for (int i = 0; i < in_width; i++) { sum += in[i]->value; @@ -55,5 +59,5 @@ reactor Destination(width:int(4)) { exit(1); } self->s += in_width; - =} + =} } diff --git a/C/src/Parallelism/Pipeline.lf b/C/src/Parallelism/Pipeline.lf index 57334d1d..98fd7025 100644 --- a/C/src/Parallelism/Pipeline.lf +++ b/C/src/Parallelism/Pipeline.lf @@ -1,40 +1,33 @@ /** - * Basic pipeline pattern where a periodic source feeds - * a chain of reactors that can all execute in parallel - * at each logical time step. - * - * The workers argument specifies the number of worker - * workers, which enables the reactors in the chain to - * execute on multiple cores simultaneously. - * - * This uses the TakeTime reactor to perform computation. - * If you reduce the number of worker workers to 1, the - * execution time will be approximately four times as long. - * + * Basic pipeline pattern where a periodic source feeds a chain of reactors that can all execute in + * parallel at each logical time step. + * + * The workers argument specifies the number of worker workers, which enables the reactors in the + * chain to execute on multiple cores simultaneously. + * + * This uses the TakeTime reactor to perform computation. If you reduce the number of worker workers + * to 1, the execution time will be approximately four times as long. + * * @author Edward A. Lee * @author Marten Lohstroh */ - target C { - workers: 4, +target C { + workers: 4 } - + /** * Send counting sequence periodically. - * + * * @param offset The starting time. * @param period The period. * @param init The first output. * @param increment The increment between outputs */ -reactor SendCount( - offset:time(0), - period:time(1 sec), - init:int(0), - increment:int(1) -) { - state count:int(init); - output out:int; - timer t(offset, period); +reactor SendCount(offset: time = 0, period: time = 1 sec, init: int = 0, increment: int = 1) { + state count: int = init + output out: int + timer t(offset, period) + reaction(t) -> out {= lf_set(out, self->count); self->count += self->increment; @@ -42,12 +35,12 @@ reactor SendCount( } /** - * Receive an input and report the elapsed logical tag - * and the value of the input. Request stop when the 10th - * value has been received. + * Receive an input and report the elapsed logical tag and the value of the input. Request stop when + * the 10th value has been received. */ reactor Receive { - input in:int; + input in: int + reaction(in) {= lf_print("At elapsed tag (%lld, %d), received %d.", lf_time_logical_elapsed(), lf_tag().microstep, @@ -60,21 +53,19 @@ reactor Receive { } /** - * When triggered, take the specified amount of physical time - * before outputting the value of the trigger. - * - * @param approximate_time The approximate amount of physical - * time to take for each input. - * + * When triggered, take the specified amount of physical time before outputting the value of the + * trigger. + * + * @param approximate_time The approximate amount of physical time to take for each input. + * * @input in A triggering input. - * - * @output out The triggering input. + * + * @output out The triggering input. */ -reactor TakeTime( - approximate_time:time(100 msec) -) { - input in:int; - output out:int; +reactor TakeTime(approximate_time: time = 100 msec) { + input in: int + output out: int + reaction(in) -> out {= instant_t start_time = lf_time_physical(); while (lf_time_physical() < start_time + self->approximate_time) { @@ -85,9 +76,10 @@ reactor TakeTime( } main reactor { - r0 = new SendCount(period = 100 msec); - rp = new[4] TakeTime(approximate_time = 100 msec); - r5 = new Receive(); + r0 = new SendCount(period = 100 msec) + rp = new[4] TakeTime(approximate_time = 100 msec) + r5 = new Receive() // Uncomment the "after" clause to expose parallelism. - r0.out, rp.out -> rp.in, r5.in // after 100 msec; + // after 100 msec; + r0.out, rp.out -> rp.in, r5.in } diff --git a/C/src/ProtocolBuffers/HelloProtocolBuffers.lf b/C/src/ProtocolBuffers/HelloProtocolBuffers.lf index 7ab2b424..66115eaf 100644 --- a/C/src/ProtocolBuffers/HelloProtocolBuffers.lf +++ b/C/src/ProtocolBuffers/HelloProtocolBuffers.lf @@ -1,12 +1,10 @@ /** - * This example demonstrates a very simple use of protocol buffers - * within a reactor. It encodes and decodes a very simple protocol - * buffer definition in hello_string.proto. This reactor is heavily + * This example demonstrates a very simple use of protocol buffers within a reactor. It encodes and + * decodes a very simple protocol buffer definition in hello_string.proto. This reactor is heavily * based on the examples at https: * github.com/protobuf-c/protobuf-c/wiki/Examples. * - * To run this example first install the protocol buffers compiler - * from https: * github.com/protocolbuffers/protobuf. For Mac, it is - * available from homebrew via + * To run this example first install the protocol buffers compiler from https: * + * github.com/protocolbuffers/protobuf. For Mac, it is available from homebrew via * ``` * brew install protobuf * ``` @@ -15,18 +13,17 @@ * brew install protobuf-c * brew install coreutils * ``` - * Building protobuf from source is very slow, so avoid doing that - * if possible. Next install the C plugin for protocol buffers from - * [https://github.com/protobuf-c/protobuf-c]. + * Building protobuf from source is very slow, so avoid doing that if possible. Next install the C + * plugin for protocol buffers from [https://github.com/protobuf-c/protobuf-c]. * - * Navigate to the directory containing the protocol buffer definition - * hello_string.proto. To compile it, run the command: + * Navigate to the directory containing the protocol buffer definition hello_string.proto. To + * compile it, run the command: * ``` * protoc-c --c_out=. hello_string.proto * ``` - * This should generate the files "hello_string.pb-c.c" and "hello_string.pb-c.h". - * Move both files to the src-gen directory where the C code for this - * reactor is generated by Lingua Franca. Compile there with: + * This should generate the files "hello_string.pb-c.c" and "hello_string.pb-c.h". Move both files + * to the src-gen directory where the C code for this reactor is generated by Lingua Franca. Compile + * there with: * ``` * cc HelloProtocolBuffers.c hello_string.pb-c.c -l protobuf-c * ``` @@ -34,37 +31,37 @@ * ``` * ./a.out * ``` - * + + * * @author Matt Weber * @author Edward A. Lee */ - target C { protobufs: hello_string.proto -}; +} main reactor HelloProtocolBuffers { - - timer t; + timer t + reaction(t) {= HelloString pack_msg = HELLO_STRING__INIT; // Macro to create the protocol buffer void *buf; // Buffer to store the serialized data unsigned len; // Length of the packed message - + char* hello_msg = "Hello Protocol Buffers!"; - pack_msg.message = hello_msg; - + pack_msg.message = hello_msg; + //Pack the message into buf. len = hello_string__get_packed_size(&pack_msg); buf = malloc(len); hello_string__pack(&pack_msg,buf); - + //Now unpack the message from buf. HelloString *unpacked_msg; unpacked_msg = hello_string__unpack(NULL, len, buf); - + //Extract and print the unpacked message. printf("Read: %s\n", unpacked_msg->message); - free(buf); // Free the allocated serialized buffer + free(buf); // Free the allocated serialized buffer =} } diff --git a/C/src/ROS/BasicROS.lf b/C/src/ROS/BasicROS.lf index 3a6e406b..15580bf3 100644 --- a/C/src/ROS/BasicROS.lf +++ b/C/src/ROS/BasicROS.lf @@ -1,169 +1,163 @@ -/** - * This is an example of exchanging messages between reactors using ROS2. - * - * There is a MessageGenerator reactor that publishes String messages on 'topic' - * and a MessageReceiver reactor that subscribes to 'topic'. - * - * 1- To get this example working, install full ROS 2 desktop - * ('https://index.ros.org/doc/ros2/Installation/Foxy/'). - * - * Please note that 'colcon' should also be installed. - * See 'https://index.ros.org/doc/ros2/Tutorials/Colcon-Tutorial/' for more details. - * - * 2- Follow the instruction in - * https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber/ - * **section 1** to create a 'cpp_pubsub' package in the current (example/ROS) folder. - * - * 3- Follow section 2.2 and 2.3 to modify the CMakeLists.txt and package.xml. - * - * 4- Replace the default C++14 standard in CMakeLists.txt (i.e., set(CMAKE_CXX_STANDARD 14)) - * with: - * - * # Default to C++20 - * if(NOT CMAKE_CXX_STANDARD) - * set(CMAKE_CXX_STANDARD 20) - * endif() - * - * 5- Add the following lines to CMakeLists.txt: - * - * add_compile_definitions(NUMBER_OF_WORKERS=4) - * - * add_library(platform_support src/core/platform/lf_linux_support.cpp) - * - * add_executable(talker src/BasicROS.cpp) - * - * target_link_libraries(talker platform_support) - * - * install(TARGETS - * platform_support - * DESTINATION lib - * ) - * - * 6- Use lfc (in bin/) to compile the provided .lf file - * - * lfc BasicROS.lf - * - * 7- Run the provided build-ROS-node.sh: - * - * ./build-ROS-node.sh BasicROS cpp_pubsub - * - * This will create a 'talker' node in the package cpp_pubsub (these names can be changed in - * CMakeLists.txt and in the argument to build-ROS-node.sh). - * - * 7- Source the appropriate setup.bash and run the node: - * - * source cpp_pubsub/install/setup.bash - * ros2 run cpp_pubsub talker - * - */ -target C { - keepalive: true, - logging: DEBUG, - no-compile: true -}; - -preamble {= - #include - #include - #include - #include - - #include "rclcpp/rclcpp.hpp" - #include "std_msgs/msg/string.hpp" -=} - -reactor MessageGenerator { - preamble {= - class MinimalPublisher : public rclcpp::Node { - public: - MinimalPublisher() - : Node("minimal_publisher") - { - publisher_ = this->create_publisher("topic", 10); - } - - rclcpp::Publisher::SharedPtr publisher_; - }; - =} - state minimal_publisher:{=std::shared_ptr=}; - state i:int(0); - timer t(0, 500 msec); - reaction(startup) {= - std::cout << "Executing startup." << std::endl; - char *argv[] = {(char*)"BasicROSPub", NULL}; - rclcpp::init(1, argv); - self->minimal_publisher = std::make_shared(); - =} - reaction(t) {= - auto message = std_msgs::msg::String(); - std::cout << "Executing timer reaction." << std::endl; - message.data = "Hello, world! " + std::to_string(self->i++); - RCLCPP_INFO(self->minimal_publisher->get_logger(), - "Sender publishing: '%s'", message.data.c_str()); - self->minimal_publisher->publisher_->publish(message); - rclcpp::spin_some(self->minimal_publisher); - std::cout << "Done executing timer reaction." << std::endl; - =} - - reaction(shutdown) {= - std::cout << "Executing shutdown reaction." << std::endl; - rclcpp::shutdown(); - =} -} - -reactor MessageReceiver { - preamble {= - class MinimalSubscriber : public rclcpp::Node { - public: - MinimalSubscriber(void* physical_action) - : Node("minimal_subscriber"), physical_action_(physical_action) { - subscription_ = this->create_subscription( - "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)); - } - - private: - void topic_callback(const std_msgs::msg::String::SharedPtr msg) const { - char* writable_string = (char*)malloc(msg->data.length() + 1); - strcpy(writable_string, msg->data.c_str()); - // writable_string[msg->data.length()] = '\0'; // Terminate with 0 - RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); - std::cout << "At tag (" << lf_time_logical_elapsed() << "," - << lf_tag().microstep << ") calling schedule_value with value " - << writable_string << " and length " << msg->data.length() - << "." << std::endl; - lf_schedule_copy(physical_action_, 0, &(writable_string), msg->data.length() + 1); - // std::cout << "Done calling schedule_value." << std::endl; - } - rclcpp::Subscription::SharedPtr subscription_; - void* physical_action_; - }; - =} - physical action ros_message_a:string; - state minimal_subscriber:{=std::shared_ptr=}; - reaction(startup) -> ros_message_a {= - // std::cout << "Executing startup." << std::endl; - self->minimal_subscriber = std::make_shared(ros_message_a); - =} - - reaction(ros_message_a){= - std::cout << "Physical action triggered." << std::endl; - printf("Received: %s.\n", ros_message_a->value); - =} - - - timer t(0, 500 msec); - reaction(t) {= - rclcpp::spin_some(self->minimal_subscriber); - // std::cout << "Timer triggered." << std::endl; - =} - - reaction(shutdown) {= - // std::cout << "Executing shutdown reaction." << std::endl; - rclcpp::shutdown(); - =} -} - -main reactor { - sender = new MessageGenerator(); - receiver = new MessageReceiver(); -} +/** + * This is an example of exchanging messages between reactors using ROS2. + * + * There is a MessageGenerator reactor that publishes String messages on 'topic' and a + * MessageReceiver reactor that subscribes to 'topic'. + * + * 1- To get this example working, install full ROS 2 desktop + * ('https://index.ros.org/doc/ros2/Installation/Foxy/'). + * + * Please note that 'colcon' should also be installed. See + * 'https://index.ros.org/doc/ros2/Tutorials/Colcon-Tutorial/' for more details. + * + * 2- Follow the instruction in + * https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber/ + * **section 1** to create a 'cpp_pubsub' package in the current (example/ROS) folder. + * + * 3- Follow section 2.2 and 2.3 to modify the CMakeLists.txt and package.xml. + * + * 4- Replace the default C++14 standard in CMakeLists.txt (i.e., set(CMAKE_CXX_STANDARD 14)) with: + * + * # Default to C++20 if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) endif() + * + * 5- Add the following lines to CMakeLists.txt: + * + * add_compile_definitions(NUMBER_OF_WORKERS=4) + * + * add_library(platform_support src/core/platform/lf_linux_support.cpp) + * + * add_executable(talker src/BasicROS.cpp) + * + * target_link_libraries(talker platform_support) + * + * install(TARGETS platform_support DESTINATION lib ) + * + * 6- Use lfc (in bin/) to compile the provided .lf file + * + * lfc BasicROS.lf + * + * 7- Run the provided build-ROS-node.sh: + * + * ./build-ROS-node.sh BasicROS cpp_pubsub + * + * This will create a 'talker' node in the package cpp_pubsub (these names can be changed in + * CMakeLists.txt and in the argument to build-ROS-node.sh). + * + * 7- Source the appropriate setup.bash and run the node: + * + * source cpp_pubsub/install/setup.bash ros2 run cpp_pubsub talker + */ +target C { + keepalive: true, + logging: DEBUG, + no-compile: true +} + +preamble {= + #include + #include + #include + #include + + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor MessageGenerator { + preamble {= + class MinimalPublisher : public rclcpp::Node { + public: + MinimalPublisher() + : Node("minimal_publisher") + { + publisher_ = this->create_publisher("topic", 10); + } + + rclcpp::Publisher::SharedPtr publisher_; + }; + =} + state minimal_publisher: {= std::shared_ptr =} + state i: int = 0 + timer t(0, 500 msec) + + reaction(startup) {= + std::cout << "Executing startup." << std::endl; + char *argv[] = {(char*)"BasicROSPub", NULL}; + rclcpp::init(1, argv); + self->minimal_publisher = std::make_shared(); + =} + + reaction(t) {= + auto message = std_msgs::msg::String(); + std::cout << "Executing timer reaction." << std::endl; + message.data = "Hello, world! " + std::to_string(self->i++); + RCLCPP_INFO(self->minimal_publisher->get_logger(), + "Sender publishing: '%s'", message.data.c_str()); + self->minimal_publisher->publisher_->publish(message); + rclcpp::spin_some(self->minimal_publisher); + std::cout << "Done executing timer reaction." << std::endl; + =} + + reaction(shutdown) {= + std::cout << "Executing shutdown reaction." << std::endl; + rclcpp::shutdown(); + =} +} + +reactor MessageReceiver { + preamble {= + class MinimalSubscriber : public rclcpp::Node { + public: + MinimalSubscriber(void* physical_action) + : Node("minimal_subscriber"), physical_action_(physical_action) { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)); + } + + private: + void topic_callback(const std_msgs::msg::String::SharedPtr msg) const { + char* writable_string = (char*)malloc(msg->data.length() + 1); + strcpy(writable_string, msg->data.c_str()); + // writable_string[msg->data.length()] = '\0'; // Terminate with 0 + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + std::cout << "At tag (" << lf_time_logical_elapsed() << "," + << lf_tag().microstep << ") calling schedule_value with value " + << writable_string << " and length " << msg->data.length() + << "." << std::endl; + lf_schedule_copy(physical_action_, 0, &(writable_string), msg->data.length() + 1); + // std::cout << "Done calling schedule_value." << std::endl; + } + rclcpp::Subscription::SharedPtr subscription_; + void* physical_action_; + }; + =} + physical action ros_message_a: string + state minimal_subscriber: {= std::shared_ptr =} + + timer t(0, 500 msec) + + reaction(startup) -> ros_message_a {= + // std::cout << "Executing startup." << std::endl; + self->minimal_subscriber = std::make_shared(ros_message_a); + =} + + reaction(ros_message_a) {= + std::cout << "Physical action triggered." << std::endl; + printf("Received: %s.\n", ros_message_a->value); + =} + + reaction(t) {= + rclcpp::spin_some(self->minimal_subscriber); + // std::cout << "Timer triggered." << std::endl; + =} + + reaction(shutdown) {= + // std::cout << "Executing shutdown reaction." << std::endl; + rclcpp::shutdown(); + =} +} + +main reactor { + sender = new MessageGenerator() + receiver = new MessageReceiver() +} diff --git a/C/src/ROS/PTIDES-ROS.lf b/C/src/ROS/PTIDES-ROS.lf index 1ff6426d..b8f4011e 100644 --- a/C/src/ROS/PTIDES-ROS.lf +++ b/C/src/ROS/PTIDES-ROS.lf @@ -1,175 +1,169 @@ -/** - * This is an example of exchanging messages between reactors using ROS2. - * This version uses a 25 msec delay to demonstrate the usage of PTIDES - * to conserve message timestamp sent over the ROS2 network. - * - * There is a MessageGenerator reactor that publishes String messages on 'topic' - * and a MessageReceiver reactor that subscribes to 'topic'. - * - * 1- To get this example working, install full ROS 2 desktop - * ('https://index.ros.org/doc/ros2/Installation/Foxy/'). - * - * Please note that 'colcon' should also be installed. - * See 'https://index.ros.org/doc/ros2/Tutorials/Colcon-Tutorial/' for more details. - * - * 2- Follow the instruction in - * https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber/ - * **section 1** to create a 'cpp_pubsub' package in the current (example/ROS) folder. - * - * 3- Follow section 2.2 and 2.3 to modify the CMakeLists.txt and package.xml. - * - * 4- Replace the default C++14 standard in CMakeLists.txt (i.e., set(CMAKE_CXX_STANDARD 14)) - * with: - * - * # Default to C++20 - * if(NOT CMAKE_CXX_STANDARD) - * set(CMAKE_CXX_STANDARD 20) - * endif() - * - * 5- Add the following lines to CMakeLists.txt: - * - * add_compile_definitions(NUMBER_OF_WORKERS=4) - * - * add_library(platform_support src/core/platform/lf_linux_support.cpp) - * - * add_executable(talker src/PTIDES-ROS.cpp) - * - * target_link_libraries(talker platform_support) - * - * install(TARGETS - * platform_support - * DESTINATION lib - * ) - * - * 6- Use lfc (in bin/) to compile the provided .lf file - * - * lfc PTIDES-ROS.lf - * - * 7- Run the provided build-ROS-node.sh: - * - * ./build-ROS-node.sh PTIDES-ROS cpp_pubsub - * - * This will create a 'talker' node in the package cpp_pubsub (these names can be changed in - * CMakeLists.txt and in the argument to build-ROS-node.sh). - * - * 7- Source the appropriate setup.bash and run the node: - * - * source cpp_pubsub/install/setup.bash - * ros2 run cpp_pubsub talker - * - */ -target C { - keepalive: true, - no-compile: true -}; - -preamble {= - #include - #include - #include - #include - - #include "rclcpp/rclcpp.hpp" - #include "std_msgs/msg/int64.hpp" -=} - -reactor MessageGenerator { - preamble {= - class MinimalPublisher : public rclcpp::Node { - public: - MinimalPublisher() - : Node("minimal_publisher") - { - publisher_ = this->create_publisher("topic", 10); - } - - rclcpp::Publisher::SharedPtr publisher_; - }; - =} - state minimal_publisher:{=std::shared_ptr=}; - state i:int(0); - timer t(0, 500 msec); - reaction(startup) {= - // std::cout << "Executing startup." << std::endl; - char *argv[] = {(char*)"BasicROSPub", NULL}; - rclcpp::init(1, argv); - self->minimal_publisher = std::make_shared(); - =} - reaction(t) {= - auto message = std_msgs::msg::Int64(); - // std::cout << "Executing timer reaction." << std::endl; - message.data = lf_time_logical() + MSEC(25); // Add a 25 msec delay - // RCLCPP_INFO(self->minimal_publisher->get_logger(), - // "Sender publishing: '%lld'", message.data); - self->minimal_publisher->publisher_->publish(message); - rclcpp::spin_some(self->minimal_publisher); - // std::cout << "Done executing timer reaction." << std::endl; - =} - - reaction(shutdown) {= - std::cout << "Executing shutdown reaction." << std::endl; - rclcpp::shutdown(); - =} -} - -reactor MessageReceiver { - preamble {= - class MinimalSubscriber : public rclcpp::Node { - public: - MinimalSubscriber(void* physical_action) - : Node("minimal_subscriber"), physical_action_(physical_action) { - subscription_ = this->create_subscription( - "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)); - } - - private: - void topic_callback(const std_msgs::msg::Int64::SharedPtr msg) const { - // writable_string[msg->data.length()] = '\0'; // Terminate with 0 - // RCLCPP_INFO(this->get_logger(), "I heard: '%lld'", msg->data); - // std::cout << "At tag (" << lf_time_logical_elapsed() << "," - // << lf_tag().microstep << ") calling schedule_copy with value " - // << msg->data << "." << std::endl; - lf_schedule_copy(physical_action_, 0, &(msg->data), sizeof(instant_t)); - // std::cout << "Done calling schedule_value." << std::endl; - } - rclcpp::Subscription::SharedPtr subscription_; - void* physical_action_; - }; - =} - physical action ros_message_a:instant_t; - logical action ros_message_l; - state minimal_subscriber:{=std::shared_ptr=}; - reaction(startup) -> ros_message_a {= - // std::cout << "Executing startup." << std::endl; - self->minimal_subscriber = std::make_shared(ros_message_a); - =} - - reaction(ros_message_a) -> ros_message_l {= - // std::cout << "Physical action triggered." << std::endl; - // printf("Received: %lld.\n", ros_message_a->value); - lf_schedule(ros_message_l, ros_message_a->value - lf_time_logical()); - =} - - reaction(ros_message_l) {= - printf("PTIDES reaction called at tag (%lld, %u).\n", - lf_time_logical_elapsed(), - lf_tag().microstep); - =} - - - timer t(0, 5 msec); - reaction(t) {= - rclcpp::spin_some(self->minimal_subscriber); - // std::cout << "Timer triggered." << std::endl; - =} - - reaction(shutdown) {= - // std::cout << "Executing shutdown reaction." << std::endl; - rclcpp::shutdown(); - =} -} - -main reactor { - sender = new MessageGenerator(); - receiver = new MessageReceiver(); -} +/** + * This is an example of exchanging messages between reactors using ROS2. This version uses a 25 + * msec delay to demonstrate the usage of PTIDES to conserve message timestamp sent over the ROS2 + * network. + * + * There is a MessageGenerator reactor that publishes String messages on 'topic' and a + * MessageReceiver reactor that subscribes to 'topic'. + * + * 1- To get this example working, install full ROS 2 desktop + * ('https://index.ros.org/doc/ros2/Installation/Foxy/'). + * + * Please note that 'colcon' should also be installed. See + * 'https://index.ros.org/doc/ros2/Tutorials/Colcon-Tutorial/' for more details. + * + * 2- Follow the instruction in + * https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber/ + * **section 1** to create a 'cpp_pubsub' package in the current (example/ROS) folder. + * + * 3- Follow section 2.2 and 2.3 to modify the CMakeLists.txt and package.xml. + * + * 4- Replace the default C++14 standard in CMakeLists.txt (i.e., set(CMAKE_CXX_STANDARD 14)) with: + * + * # Default to C++20 if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) endif() + * + * 5- Add the following lines to CMakeLists.txt: + * + * add_compile_definitions(NUMBER_OF_WORKERS=4) + * + * add_library(platform_support src/core/platform/lf_linux_support.cpp) + * + * add_executable(talker src/PTIDES-ROS.cpp) + * + * target_link_libraries(talker platform_support) + * + * install(TARGETS platform_support DESTINATION lib ) + * + * 6- Use lfc (in bin/) to compile the provided .lf file + * + * lfc PTIDES-ROS.lf + * + * 7- Run the provided build-ROS-node.sh: + * + * ./build-ROS-node.sh PTIDES-ROS cpp_pubsub + * + * This will create a 'talker' node in the package cpp_pubsub (these names can be changed in + * CMakeLists.txt and in the argument to build-ROS-node.sh). + * + * 7- Source the appropriate setup.bash and run the node: + * + * source cpp_pubsub/install/setup.bash ros2 run cpp_pubsub talker + */ +target C { + keepalive: true, + no-compile: true +} + +preamble {= + #include + #include + #include + #include + + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/int64.hpp" +=} + +reactor MessageGenerator { + preamble {= + class MinimalPublisher : public rclcpp::Node { + public: + MinimalPublisher() + : Node("minimal_publisher") + { + publisher_ = this->create_publisher("topic", 10); + } + + rclcpp::Publisher::SharedPtr publisher_; + }; + =} + state minimal_publisher: {= std::shared_ptr =} + state i: int = 0 + timer t(0, 500 msec) + + reaction(startup) {= + // std::cout << "Executing startup." << std::endl; + char *argv[] = {(char*)"BasicROSPub", NULL}; + rclcpp::init(1, argv); + self->minimal_publisher = std::make_shared(); + =} + + reaction(t) {= + auto message = std_msgs::msg::Int64(); + // std::cout << "Executing timer reaction." << std::endl; + message.data = lf_time_logical() + MSEC(25); // Add a 25 msec delay + // RCLCPP_INFO(self->minimal_publisher->get_logger(), + // "Sender publishing: '%lld'", message.data); + self->minimal_publisher->publisher_->publish(message); + rclcpp::spin_some(self->minimal_publisher); + // std::cout << "Done executing timer reaction." << std::endl; + =} + + reaction(shutdown) {= + std::cout << "Executing shutdown reaction." << std::endl; + rclcpp::shutdown(); + =} +} + +reactor MessageReceiver { + preamble {= + class MinimalSubscriber : public rclcpp::Node { + public: + MinimalSubscriber(void* physical_action) + : Node("minimal_subscriber"), physical_action_(physical_action) { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)); + } + + private: + void topic_callback(const std_msgs::msg::Int64::SharedPtr msg) const { + // writable_string[msg->data.length()] = '\0'; // Terminate with 0 + // RCLCPP_INFO(this->get_logger(), "I heard: '%lld'", msg->data); + // std::cout << "At tag (" << lf_time_logical_elapsed() << "," + // << lf_tag().microstep << ") calling schedule_copy with value " + // << msg->data << "." << std::endl; + lf_schedule_copy(physical_action_, 0, &(msg->data), sizeof(instant_t)); + // std::cout << "Done calling schedule_value." << std::endl; + } + rclcpp::Subscription::SharedPtr subscription_; + void* physical_action_; + }; + =} + physical action ros_message_a: instant_t + logical action ros_message_l + state minimal_subscriber: {= std::shared_ptr =} + + timer t(0, 5 msec) + + reaction(startup) -> ros_message_a {= + // std::cout << "Executing startup." << std::endl; + self->minimal_subscriber = std::make_shared(ros_message_a); + =} + + reaction(ros_message_a) -> ros_message_l {= + // std::cout << "Physical action triggered." << std::endl; + // printf("Received: %lld.\n", ros_message_a->value); + lf_schedule(ros_message_l, ros_message_a->value - lf_time_logical()); + =} + + reaction(ros_message_l) {= + printf("PTIDES reaction called at tag (%lld, %u).\n", + lf_time_logical_elapsed(), + lf_tag().microstep); + =} + + reaction(t) {= + rclcpp::spin_some(self->minimal_subscriber); + // std::cout << "Timer triggered." << std::endl; + =} + + reaction(shutdown) {= + // std::cout << "Executing shutdown reaction." << std::endl; + rclcpp::shutdown(); + =} +} + +main reactor { + sender = new MessageGenerator() + receiver = new MessageReceiver() +} diff --git a/C/src/ReflexGame/ReflexGame.lf b/C/src/ReflexGame/ReflexGame.lf index 258cf510..2746f114 100644 --- a/C/src/ReflexGame/ReflexGame.lf +++ b/C/src/ReflexGame/ReflexGame.lf @@ -1,12 +1,10 @@ /** - * 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. + * 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. * - * The example is fashioned after an Esterel implementation given by Berry and - * Gonthier in "The ESTEREL synchronous programming language: design, semantics, - * implementation," Science of Computer Programming, 19(2) pp. 87-152, Nov. - * 1992, DOI: 10.1016/0167-6423(92)90005-V. + * The example is fashioned after an Esterel implementation given by Berry and Gonthier in "The + * ESTEREL synchronous programming language: design, semantics, implementation," Science of Computer + * Programming, 19(2) pp. 87-152, Nov. 1992, DOI: 10.1016/0167-6423(92)90005-V. * * @author Edward A. Lee * @author Marten Lohstroh @@ -20,8 +18,8 @@ preamble {= =} /** - * Produce a counting sequence at random times with a minimum and maximum time - * between outputs specified as parameters. + * 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. @@ -67,9 +65,9 @@ reactor RandomSource(min_time: time = 2 sec, max_time: time = 8 sec) { } /** - * 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. + * 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 { preamble {= diff --git a/C/src/ReflexGame/ReflexGameTest.lf b/C/src/ReflexGame/ReflexGameTest.lf index 4f596b31..8621be15 100644 --- a/C/src/ReflexGame/ReflexGameTest.lf +++ b/C/src/ReflexGame/ReflexGameTest.lf @@ -1,12 +1,11 @@ /** - * The Lingua Franca (LF) program above is designed to read characters from user - * input and display them along with the elapsed time since the last input. The - * program starts a separate thread to read characters from the keyboard, and it - * sends the input characters as valued physical actions to the main reactor. - * Upon receiving a character, the program calculates the time elapsed since the - * last input and prints the character along with the elapsed time in - * nanoseconds. If the received character is an end-of-file (EOF) signal (e.g., - * Control-D or Control-Z), the program stops execution. + * The Lingua Franca (LF) program above is designed to read characters from user input and display + * them along with the elapsed time since the last input. The program starts a separate thread to + * read characters from the keyboard, and it sends the input characters as valued physical actions + * to the main reactor. Upon receiving a character, the program calculates the time elapsed since + * the last input and prints the character along with the elapsed time in nanoseconds. If the + * received character is an end-of-file (EOF) signal (e.g., Control-D or Control-Z), the program + * stops execution. */ target C { keepalive: true, diff --git a/C/src/RockPaperScissors.lf b/C/src/RockPaperScissors.lf index afb91fd6..fce8375b 100644 --- a/C/src/RockPaperScissors.lf +++ b/C/src/RockPaperScissors.lf @@ -1,9 +1,8 @@ /** * Demonstration of the classic rock-paper-scissors game. See - * [https://en.wikipedia.org/wiki/Rock_paper_scissors]. This illustrates - * interactive feedback in Lingua Franca and the importance of reaction order. - * If you reorder the second and third reactions in the Player reactor, a - * causality loop emerges. + * [https://en.wikipedia.org/wiki/Rock_paper_scissors]. This illustrates interactive feedback in + * Lingua Franca and the importance of reaction order. If you reorder the second and third reactions + * in the Player reactor, a causality loop emerges. * * @author Marten Lohstroh */ @@ -12,8 +11,8 @@ target C preamble {= typedef enum {paper=0, rock=1, scissors=2} symbol_t; =} main reactor RockPaperScissors { - player1 = new Player(id = 1) - player2 = new Player(id = 2) + player1 = new Player(id=1) + player2 = new Player(id=2) player1.reveal -> player2.observe player2.reveal -> player1.observe diff --git a/C/src/SleepingBarber.lf b/C/src/SleepingBarber.lf index a3012928..b1d211b4 100644 --- a/C/src/SleepingBarber.lf +++ b/C/src/SleepingBarber.lf @@ -1,42 +1,35 @@ /** - * This example illustrates the classic problem in concurrent - * computing called the "sleeping barber problem," often attributed - * to Edsger Dijkstra (see https://en.wikipedia.org/wiki/Sleeping_barber_problem). - * Using the logical times of Lingua Franca, many of the potential - * problems that can arise do not arise. - * - * Upon startup, the barber goes to sleep and is woken up by - * the arrival of a customer. After serving each customer, - * the barber checks the waiting room for the next customer. - * If there is no customer waiting, the barber goes back - * to sleep. Customers arrive independently from each other - * at random times. If the barber is sleeping, the customer - * gets served right away. Otherwise, if there is room in the - * waiting room, the customer waits. If the waiting room is - * full, the customer goes away and returns a random amount - * of time later. Execution ends when all customers have been + * This example illustrates the classic problem in concurrent computing called the "sleeping barber + * problem," often attributed to Edsger Dijkstra (see + * https://en.wikipedia.org/wiki/Sleeping_barber_problem). Using the logical times of Lingua Franca, + * many of the potential problems that can arise do not arise. + * + * Upon startup, the barber goes to sleep and is woken up by the arrival of a customer. After + * serving each customer, the barber checks the waiting room for the next customer. If there is no + * customer waiting, the barber goes back to sleep. Customers arrive independently from each other + * at random times. If the barber is sleeping, the customer gets served right away. Otherwise, if + * there is room in the waiting room, the customer waits. If the waiting room is full, the customer + * goes away and returns a random amount of time later. Execution ends when all customers have been * served. - * - * This example is inspired by Savina benchmark by the same name, - * which has a C target implementation here: + * + * This example is inspired by Savina benchmark by the same name, which has a C target + * implementation here: * [https://github.com/lf-lang/lingua-franca/blob/master/benchmark/C/Savina/src/concurrency/SleepingBarber.lf]. - * Unlike the benchmark, this example is a direct implementation - * of the problem at hand using the features of Lingua Franca. - * It resembles the Savina benchmark only in overall structure. - * + * Unlike the benchmark, this example is a direct implementation of the problem at hand using the + * features of Lingua Franca. It resembles the Savina benchmark only in overall structure. + * * @author Christian Menard * @author Hannes Klein * @author Matthew Chorlian * @author Edward A. Lee * @author Thee Ho */ - target C { fast: true, threading: false, cmake-include: "/lib/c/reactor-c/util/deque.cmake", files: ["/lib/c/reactor-c/util/deque.h", "/lib/c/reactor-c/util/deque.c"] -}; +} preamble {= #include "deque.h" @@ -50,25 +43,19 @@ preamble {= =} /** - * Upon startup or the triggering of the `next` action, - * produce the send_customer output, then schedule another occurrence of the - * action `next` a random amount of time later. - * The first customer will be produced at logical time 0. - * The time between new customers is uniformly between 0 and - * `max_time_between_customers`. - * - * When a `customer_returned` input arrives, schedule a - * resending of the same customer a random amount of time later, - * where the random number is again uniformly distributed between + * Upon startup or the triggering of the `next` action, produce the send_customer output, then + * schedule another occurrence of the action `next` a random amount of time later. The first + * customer will be produced at logical time 0. The time between new customers is uniformly between * 0 and `max_time_between_customers`. - * - * This reactor uses the customer_done input to count customers and request - * halting the program when all customers have been served. + * + * When a `customer_returned` input arrives, schedule a resending of the same customer a random + * amount of time later, where the random number is again uniformly distributed between 0 and + * `max_time_between_customers`. + * + * This reactor uses the customer_done input to count customers and request halting the program when + * all customers have been served. */ -reactor CustomerFactory( - num_customers:size_t(20), - max_time_between_customers:time(10 minutes) -) { +reactor CustomerFactory(num_customers: size_t = 20, max_time_between_customers: time = 10 minutes) { preamble {= /* Note that this function is shared with Barber reactor. */ interval_t random_time(interval_t max) { @@ -76,22 +63,22 @@ reactor CustomerFactory( return result; } =} - output send_customer: size_t; - input[num_customers] customer_done: bool; - input[num_customers] customer_returned: bool; + output send_customer: size_t + input[num_customers] customer_done: bool + input[num_customers] customer_returned: bool - logical action next; // Next new customer. - logical action again:int; // Returning customer. Payload is customer ID. + logical action next // Next new customer. + logical action again: int // Returning customer. Payload is customer ID. - state done_customers: size_t(0); // Count of finished customers. - state attempts: size_t(0); // Count of customer attempts. - state next_customer_id: size_t(0); // ID of next new customer. + state done_customers: size_t = 0 // Count of finished customers. + state attempts: size_t = 0 // Count of customer attempts. + state next_customer_id: size_t = 0 // ID of next new customer. reaction(startup, next) -> send_customer, next {= // send the new customer to the waiting room self->attempts++; lf_set(send_customer, self->next_customer_id++); - + if (self->next_customer_id < self->num_customers) { // Schedule again. interval_t delay = random_time(self->max_time_between_customers); @@ -99,13 +86,13 @@ reactor CustomerFactory( } =} - reaction (again) -> send_customer {= + reaction(again) -> send_customer {= size_t customer_id = again->value; self->attempts++; lf_set(send_customer, customer_id); =} - reaction (customer_returned) -> again {= + reaction(customer_returned) -> again {= for (int i = 0; i < self->num_customers; i++) { if (customer_returned[i]->is_present) { // The customer returned because the waiting room is full. @@ -116,7 +103,7 @@ reactor CustomerFactory( } =} - reaction (customer_done) {= + reaction(customer_done) {= // Only one customer can be done at any logical tag, so we // only need to count invocations of this reaction. self->done_customers++; @@ -125,7 +112,7 @@ reactor CustomerFactory( } =} - reaction (shutdown) {= + reaction(shutdown) {= char buffer[LF_TIME_BUFFER_LENGTH]; lf_readable_time(buffer, lf_time_logical_elapsed()); lf_print("Finished: %zu customers got haircuts in %zu attempts over %s.", @@ -135,31 +122,28 @@ reactor CustomerFactory( } /** - * When a customer_enters input arrives, if the barber is asleep, - * then wake the barber up and send the customer ID to the - * `barber_leaves_with_customer` output. If the barber is - * awake and there is room in the queue, - * then put the customer ID on the queue and produce a wait output - * on the channel corresponding to the customer ID. If the queue - * is full, then produce a full output on the channel corresponding - * to the customer ID. + * When a customer_enters input arrives, if the barber is asleep, then wake the barber up and send + * the customer ID to the `barber_leaves_with_customer` output. If the barber is awake and there is + * room in the queue, then put the customer ID on the queue and produce a wait output on the channel + * corresponding to the customer ID. If the queue is full, then produce a full output on the channel + * corresponding to the customer ID. */ -reactor WaitingRoom(capacity:size_t(1000), num_customers:size_t(2000)) { +reactor WaitingRoom(capacity: size_t = 1000, num_customers: size_t = 2000) { preamble {= #include "deque.h" =} - input customer_enters: size_t; + input customer_enters: size_t - output[num_customers] full: bool; - output[num_customers] wait: bool; + output[num_customers] full: bool + output[num_customers] wait: bool - input barber_arrives: bool; - output barber_leaves_with_customer: int; + input barber_arrives: bool + output barber_leaves_with_customer: int - state queue: deque_t; - state barber_asleep: bool(true); + state queue: deque_t + state barber_asleep: bool = true - reaction (customer_enters) -> full, wait, barber_leaves_with_customer {= + reaction(customer_enters) -> full, wait, barber_leaves_with_customer {= size_t customer_id = customer_enters->value; if (deque_size(&self->queue) == self->capacity) { @@ -180,7 +164,7 @@ reactor WaitingRoom(capacity:size_t(1000), num_customers:size_t(2000)) { } =} - reaction (barber_arrives) -> barber_leaves_with_customer {= + reaction(barber_arrives) -> barber_leaves_with_customer {= if (deque_is_empty(&self->queue)) { self->barber_asleep = true; } else { @@ -190,21 +174,20 @@ reactor WaitingRoom(capacity:size_t(1000), num_customers:size_t(2000)) { } /** - * Reactor representing a customer. - * This reactor reports on what is happening from the perspective of - * a customer. It also relays a `room_full` input to its `returned` - * output and its `done_cutting` input to its `done` output. + * Reactor representing a customer. This reactor reports on what is happening from the perspective + * of a customer. It also relays a `room_full` input to its `returned` output and its `done_cutting` + * input to its `done` output. */ -reactor Customer(bank_index:size_t(0)) { - input room_full: bool; - input wait: bool; - input start_cutting: bool; - input done_cutting: bool; +reactor Customer(bank_index: size_t = 0) { + input room_full: bool + input wait: bool + input start_cutting: bool + input done_cutting: bool - output returned: bool; - output done: bool; + output returned: bool + output done: bool - reaction (room_full) -> returned {= + reaction(room_full) -> returned {= char buffer[LF_TIME_BUFFER_LENGTH]; lf_readable_time(buffer, lf_time_logical_elapsed()); lf_print("Customer %zu: Turned away at %s. Will try later.", @@ -213,7 +196,7 @@ reactor Customer(bank_index:size_t(0)) { lf_set(returned, true); =} - reaction (wait) {= + reaction(wait) {= char buffer[LF_TIME_BUFFER_LENGTH]; lf_readable_time(buffer, lf_time_logical_elapsed()); lf_print("Customer %zu: Entered waiting room at %s. Waiting.", @@ -221,15 +204,15 @@ reactor Customer(bank_index:size_t(0)) { ); =} - reaction (start_cutting) {= + reaction(start_cutting) {= char buffer[LF_TIME_BUFFER_LENGTH]; lf_readable_time(buffer, lf_time_logical_elapsed()); lf_print("Customer %zu: Started a haircut at %s.", self->bank_index, buffer ); =} - - reaction (done_cutting) -> done {= + + reaction(done_cutting) -> done {= char buffer[LF_TIME_BUFFER_LENGTH]; lf_readable_time(buffer, lf_time_logical_elapsed()); lf_print("Customer %zu: Finished a haircut at %s.", @@ -240,39 +223,36 @@ reactor Customer(bank_index:size_t(0)) { } /** - * Upon receiving an `enter` input (whose value is a customer ID), - * send a `start_cutting` output to corresponding customer, then - * then schedule the logical action `done` to trigger a random - * amount of time later. Upon triggering the `done` action, send a - * `done_cutting` output to the corresponding customer and a - * `next` output to ask for the next customer. - * - * The random time the haircut takes is uniformly distributed between - * `min_cut_time` and `max_cut_time`. + * Upon receiving an `enter` input (whose value is a customer ID), send a `start_cutting` output to + * corresponding customer, then then schedule the logical action `done` to trigger a random amount + * of time later. Upon triggering the `done` action, send a `done_cutting` output to the + * corresponding customer and a `next` output to ask for the next customer. + * + * The random time the haircut takes is uniformly distributed between `min_cut_time` and + * `max_cut_time`. */ reactor Barber( - min_cut_time:time(5 minutes), - max_cut_time:time(15 minutes), - num_customers:size_t(20) -) { - input enter: int; - - output[num_customers] start_cutting: bool; - output[num_customers] done_cutting: bool; - output next: bool; - - logical action done: int; - - reaction (done) -> done_cutting, next {= + min_cut_time: time = 5 minutes, + max_cut_time: time = 15 minutes, + num_customers: size_t = 20) { + input enter: int + + output[num_customers] start_cutting: bool + output[num_customers] done_cutting: bool + output next: bool + + logical action done: int + + reaction(done) -> done_cutting, next {= int customer_id = done->value; lf_set(done_cutting[customer_id], true); lf_set(next, true); =} - reaction (enter) -> start_cutting, done {= + reaction(enter) -> start_cutting, done {= int customer_id = enter->value; lf_set(start_cutting[customer_id], true); - + // Calculate a random delay. interval_t delay = self->min_cut_time + random_time(self->max_cut_time - self->min_cut_time); @@ -282,39 +262,31 @@ reactor Barber( =} } - -main reactor ( - waiting_room_capacity:size_t(7), - max_time_between_customers:time(10 minutes), - min_cut_time:time(5 minutes), - max_cut_time:time(15 minutes), - num_customers:size_t(20) -) { - +main reactor( + waiting_room_capacity: size_t = 7, + max_time_between_customers: time = 10 minutes, + min_cut_time: time = 5 minutes, + max_cut_time: time = 15 minutes, + num_customers: size_t = 20) { factory = new CustomerFactory( - num_customers=num_customers, - max_time_between_customers=max_time_between_customers - ); - room = new WaitingRoom( - capacity=waiting_room_capacity, - num_customers=num_customers - ); + num_customers=num_customers, + max_time_between_customers=max_time_between_customers) + room = new WaitingRoom(capacity=waiting_room_capacity, num_customers=num_customers) barber = new Barber( - min_cut_time=min_cut_time, - max_cut_time=max_cut_time, - num_customers=num_customers - ) - customers = new[num_customers] Customer(); - - factory.send_customer -> room.customer_enters; - room.full -> customers.room_full; - room.wait -> customers.wait; - room.barber_leaves_with_customer -> barber.enter; - barber.next -> room.barber_arrives; - barber.start_cutting -> customers.start_cutting; - barber.done_cutting -> customers.done_cutting; - customers.done -> factory.customer_done; - - customers.returned -> factory.customer_returned; + min_cut_time=min_cut_time, + max_cut_time=max_cut_time, + num_customers=num_customers) + customers = new[num_customers] Customer() + + factory.send_customer -> room.customer_enters + room.full -> customers.room_full + room.wait -> customers.wait + room.barber_leaves_with_customer -> barber.enter + barber.next -> room.barber_arrives + barber.start_cutting -> customers.start_cutting + barber.done_cutting -> customers.done_cutting + customers.done -> factory.customer_done + + customers.returned -> factory.customer_returned } diff --git a/C/src/Smokers.lf b/C/src/Smokers.lf index 3f951e56..d7f7b10e 100644 --- a/C/src/Smokers.lf +++ b/C/src/Smokers.lf @@ -1,23 +1,19 @@ /** - * A classic challenge program in concurrent programming is called the - * "cigarette smoker's problem" was introduced by Suhas Patil in 1971 - * (Patil, 1971) and is discussed in Downey's *Little Book of Semaphores* - * (Downey, 2016). Patil's original formulation goes like this: - * - * "Three smokers are sitting at a table. One of them has tobacco, - * another has cigarette papers, and the third one has matches---each - * one has a different ingredient required to make and smoke a cigarette - * but he may not give any ingredient to another. On the table in front - * of them, two of the three ingredients will be placed, and the smoker - * who has the necessary third ingredient should pick up the ingredients - * from the table, make the cigarette and smoke it. Since a new set of - * ingredients will not be placed on the table until this action is - * completed, the other smokers who cannot make and smoke a cigarette - * with the ingredients on the table must not interfere with the fellow - * who can." - * - * A naive solution realizes each smoker as follows (in pseudo code, - * shown for the smoker that holds tobacco): + * A classic challenge program in concurrent programming is called the "cigarette smoker's problem" + * was introduced by Suhas Patil in 1971 (Patil, 1971) and is discussed in Downey's *Little Book of + * Semaphores* (Downey, 2016). Patil's original formulation goes like this: + * + * "Three smokers are sitting at a table. One of them has tobacco, another has cigarette papers, and + * the third one has matches---each one has a different ingredient required to make and smoke a + * cigarette but he may not give any ingredient to another. On the table in front of them, two of + * the three ingredients will be placed, and the smoker who has the necessary third ingredient + * should pick up the ingredients from the table, make the cigarette and smoke it. Since a new set + * of ingredients will not be placed on the table until this action is completed, the other smokers + * who cannot make and smoke a cigarette with the ingredients on the table must not interfere with + * the fellow who can." + * + * A naive solution realizes each smoker as follows (in pseudo code, shown for the smoker that holds + * tobacco): * ``` * while(true) { * acquire_paper(); @@ -26,90 +22,81 @@ * release(); * } * ``` - * The two "acquire" functions block until the specified resource is - * available on the table and then acquire exclusive access to that resource. - * This realization, however, very likely deadlocks because after this smoker - * acquires paper, another smoker may acquire the matches (or the supplier - * process supplies tobacco instead of matches). At that point, no further - * progress is possible and all smokers freeze. + * The two "acquire" functions block until the specified resource is available on the table and + * then acquire exclusive access to that resource. This realization, however, very likely deadlocks + * because after this smoker acquires paper, another smoker may acquire the matches (or the supplier + * process supplies tobacco instead of matches). At that point, no further progress is possible and + * all smokers freeze. + * + * Patil imposed some constraints, that "the process which supplies the ingredients cannot be + * changed," and that "no conditional statements can be used." Patil argued that under these + * constraints, the problem cannot be solved using Dijkstra's semaphores. * - * Patil imposed some constraints, that "the process which supplies the - * ingredients cannot be changed," and that "no conditional statements - * can be used." Patil argued that under these constraints, the problem - * cannot be solved using Dijkstra's semaphores. + * In 1975, Parnas showed that Patil had imposed some additional unstated constraints on the use of + * semaphores and gave a solution that uses vector semaphores, but still avoids conditional + * statements (Parnas, 1975). Downey argued that the constraint to avoid conditional statements is + * rather artificial, but with the less artificial constraint that the supplier not be modified (it + * could, after all, represent an operating system), then the problem is interesting and the + * solutions can get quite convoluted (Downey, 2016). Searching the web for solutions to this + * problem yields a few other attempts to solve it, including one that argues that the problem + * demonstrates the requirement for tests that enrich semaphores such as POSIX operations such as + * `sem_try_wait()` or `pthread_mutex_trylock()`. See for example OpenCSF: + * [https://w3.cs.jmu.edu/kirkpams/OpenCSF/Books/csf/html/CigSmokers.html], although, unfortunately, + * the solution given there still exhibits the possibility of a form of deadlock, where one thread + * repeatedly, unfairly acquires a semaphore in a busy wait. * - * In 1975, Parnas showed that Patil had imposed some additional unstated - * constraints on the use of semaphores and gave a solution that uses - * vector semaphores, but still avoids conditional statements - * (Parnas, 1975). Downey argued that the constraint to avoid conditional - * statements is rather artificial, but with the less artificial constraint - * that the supplier not be modified (it could, after all, represent an - * operating system), then the problem is interesting and the solutions - * can get quite convoluted (Downey, 2016). Searching the web for solutions - * to this problem yields a few other attempts to solve it, including one - * that argues that the problem demonstrates the requirement for tests - * that enrich semaphores such as POSIX operations such as `sem_try_wait()` - * or `pthread_mutex_trylock()`. See for example OpenCSF: - * [https://w3.cs.jmu.edu/kirkpams/OpenCSF/Books/csf/html/CigSmokers.html], - * although, unfortunately, the solution given there still exhibits the - * possibility of a form of deadlock, where one thread repeatedly, - * unfairly acquires a semaphore in a busy wait. - * - * A more commonly accepted solution, one implemented for example in - * the Savina actor benchmark suite (Imam and Sarkar, 2014), defines - * a centralized coordinator that first determines what the supplier - * has supplied, then decides which smoker should be given permission - * to take the supplies and dispatches a message to that smoker. - * - * Here, we give a Lingua Franca solution to Patil's original problem - * statement that does not use semaphores (except under the hood in the - * Lingua Franca implementation). This does not really solve Patil's - * problem, which was explicitly about how to accomplish this with - * semaphores, but instead demonstrates that some problems that become - * challenging puzzles with semaphores becomes trivially easy and - * uninteresting in Lingua Franca. + * A more commonly accepted solution, one implemented for example in the Savina actor benchmark + * suite (Imam and Sarkar, 2014), defines a centralized coordinator that first determines what the + * supplier has supplied, then decides which smoker should be given permission to take the supplies + * and dispatches a message to that smoker. + * + * Here, we give a Lingua Franca solution to Patil's original problem statement that does not use + * semaphores (except under the hood in the Lingua Franca implementation). This does not really + * solve Patil's problem, which was explicitly about how to accomplish this with semaphores, but + * instead demonstrates that some problems that become challenging puzzles with semaphores becomes + * trivially easy and uninteresting in Lingua Franca. * * References: - * - * Downey, A.B.: The Little Book of Semaphores, vol. Version 2.2.1. - * Green Tea Press, second edition edn. (2016), - * [https://greenteapress.com/semaphores/ LittleBookOfSemaphores.pdf] - * - * Imam,S.,Sarkar,V.: "Savina---An Actor Benchmark Suite Enabling Empirical - * Evaluation of Actor Libraries." In: Workshop on Programming based on - * Actors, Agents, and Decentralized Control (AGERE) (2014). - * [https://doi.org/10.1145/2687357.2687368] - * - * Parnas, D.L.: "On a Solution to the Cigarette Smokers’ Problem - * (Without Conditional Statements)." Communications of the ACM 18(3), - * 181–183 (March 1975). [https://doi.org/10.1145/360680.360709]. - * - * Patil,S.S.: "Limitations and Capabilities of Dijkstra’s Semaphore - * Primitives for Coordination among Processes." Report, Computation - * Structures Group, Project MAC, MIT (February 1971) - * + * + * Downey, A.B.: The Little Book of Semaphores, vol. Version 2.2.1. Green Tea Press, second edition + * edn. (2016), [https://greenteapress.com/semaphores/ LittleBookOfSemaphores.pdf] + * + * Imam,S.,Sarkar,V.: "Savina---An Actor Benchmark Suite Enabling Empirical Evaluation of Actor + * Libraries." In: Workshop on Programming based on Actors, Agents, and Decentralized Control + * (AGERE) (2014). [https://doi.org/10.1145/2687357.2687368] + * + * Parnas, D.L.: "On a Solution to the Cigarette Smokers’ Problem (Without Conditional Statements)." + * Communications of the ACM 18(3), 181–183 (March 1975). [https://doi.org/10.1145/360680.360709]. + * + * Patil,S.S.: "Limitations and Capabilities of Dijkstra’s Semaphore Primitives for Coordination + * among Processes." Report, Computation Structures Group, Project MAC, MIT (February 1971) + * * @author Edward A. Lee */ -target C; +target C + main reactor { - a = new Agent(); - s1 = new Smoker(smoke_time = 1 sec, has = 0); // Has tobacco. - s2 = new Smoker(smoke_time = 2 sec, has = 1); // Has paper. - s3 = new Smoker(smoke_time = 3 sec, has = 2); // Has matches. - (a.tobacco)+ -> s1.tobacco, s2.tobacco, s3.tobacco; - (a.paper)+ -> s1.paper, s2.paper, s3.paper; - (a.matches)+ -> s1.matches, s2.matches, s3.matches; - s1.done, s2.done, s3.done -> a.trigger; + a = new Agent() + s1 = new Smoker(smoke_time = 1 sec, has=0) // Has tobacco. + s2 = new Smoker(smoke_time = 2 sec, has=1) // Has paper. + s3 = new Smoker(smoke_time = 3 sec, has=2) // Has matches. + (a.tobacco)+ -> s1.tobacco, s2.tobacco, s3.tobacco + (a.paper)+ -> s1.paper, s2.paper, s3.paper + (a.matches)+ -> s1.matches, s2.matches, s3.matches + s1.done, s2.done, s3.done -> a.trigger } + reactor Agent { - input[3] trigger:bool; - output tobacco:bool; - output paper:bool; - output matches:bool; + input[3] trigger: bool + output tobacco: bool + output paper: bool + output matches: bool + reaction(startup) {= // At start, seed the random number. srand((unsigned)lf_time_logical()); =} + reaction(startup, trigger) -> tobacco, paper, matches {= int choice = rand() % 3; if (choice == 0) { @@ -127,22 +114,24 @@ reactor Agent { } =} } + reactor Smoker( - smoke_time:time(1 sec), - has:int(0) // 0 for tobacco, 1 for paper, 2 for matches -) { - input tobacco:bool; - input paper:bool; - input matches:bool; - - output done:bool; - - logical action smoke; - + smoke_time: time = 1 sec, + // 0 for tobacco, 1 for paper, 2 for matches + has: int = 0) { + input tobacco: bool + input paper: bool + input matches: bool + + output done: bool + + logical action smoke + reaction(smoke) -> done {= lf_print("Smoker is done smoking."); lf_set(done, true); =} + reaction(tobacco, paper, matches) -> smoke {= if (self->has == 0 && paper->is_present && matches->is_present) { lf_print("Smoker with tobacco starts smoking."); diff --git a/C/src/TrainDoor/TrainDoor.lf b/C/src/TrainDoor/TrainDoor.lf index 9da251bc..09340d89 100644 --- a/C/src/TrainDoor/TrainDoor.lf +++ b/C/src/TrainDoor/TrainDoor.lf @@ -1,11 +1,12 @@ /** - * Program that emulates a train door controller. It has two components: - * one that controls the door and one that senses motion. When the door - * controller receives a request to open the door (a button press), it has - * to first check whether the vehicle was recently in motion. The request - * will be denied if motion has been detected less than two seconds ago. + * Program that emulates a train door controller. It has two components: one that controls the door + * and one that senses motion. When the door controller receives a request to open the door (a + * button press), it has to first check whether the vehicle was recently in motion. The request will + * be denied if motion has been detected less than two seconds ago. */ -target C {keepalive: true}; +target C { + keepalive: true +} preamble {= #include @@ -14,7 +15,7 @@ preamble {= void* open; void* close; } buttons; - + void* read_input(void* arg) { printf("***************************************************************\n"); printf("Press 'o' and hit return or enter to open the door\n"); @@ -38,23 +39,26 @@ preamble {= } request_stop(); return NULL; - } + } =} reactor MotionDetector { - physical action movement; - state timestamp:time(0); - input check:bool; - output ok:bool; + physical action movement + state timestamp: time = 0 + input check: bool + output ok: bool + reaction(startup) -> movement {= buttons.move = movement; lf_thread_t thread_id; lf_thread_create(&thread_id, &read_input, NULL); =} + reaction(movement) {= printf("Motion detected!\n"); self->timestamp = lf_time_logical(); =} + reaction(check) -> ok {= if (self->timestamp == 0L || (lf_time_logical() - self->timestamp) > SECS(2)) { lf_set(ok, true); @@ -65,19 +69,19 @@ reactor MotionDetector { } reactor DoorController { - - physical action open; - physical action close; - - output check:bool; - input ok:bool; - state opened:bool(false); - state requested:bool(false); + physical action open + physical action close + + output check: bool + input ok: bool + state opened: bool = false + state requested: bool = false + reaction(startup) -> open, close {= buttons.open = open; buttons.close = close; =} - + reaction(open) -> check {= if (self->opened) { printf("The door is already open\n"); @@ -87,16 +91,16 @@ reactor DoorController { self->requested = true; } =} - + reaction(close) {= printf("Closing the door\n"); self->opened = false; =} - + reaction(ok) {= if (self->requested && ok->value) { self->opened = true; - printf("Opening the door.\n"); + printf("Opening the door.\n"); } else { printf("Cannot open the door; recent motion detected.\n"); } @@ -105,8 +109,8 @@ reactor DoorController { } main reactor TrainDoor { - motion = new MotionDetector(); - door = new DoorController(); - door.check -> motion.check; - motion.ok -> door.ok; + motion = new MotionDetector() + door = new DoorController() + door.check -> motion.check + motion.ok -> door.ok } diff --git a/C/src/TrainDoor/TrainDoorAsymmetric.lf b/C/src/TrainDoor/TrainDoorAsymmetric.lf index 1e97554f..7cc42c9a 100644 --- a/C/src/TrainDoor/TrainDoorAsymmetric.lf +++ b/C/src/TrainDoor/TrainDoorAsymmetric.lf @@ -1,49 +1,60 @@ // This is a variant of the example is considered in this paper: -// https://www.mdpi.com/2227-7390/8/7/1068 +// https://www.mdpi.com/2227-7390/8/7/1068 // where it is studied for its verifiability. -target C; +target C + reactor Controller { - output lock:bool; output unlock:bool; - output move:bool; output stop:bool; - physical action external:bool; + output lock: bool + output unlock: bool + output move: bool + output stop: bool + physical action external: bool + reaction(startup) {= - // ... Set up external sensing. + // ... Set up external sensing. =} - reaction(external)->lock, unlock, move, stop {= - if (external->value) { - lf_set(lock, true); lf_set(move, true); - } else { - lf_set(unlock, true); lf_set(stop, true); - } + + reaction(external) -> lock, unlock, move, stop {= + if (external->value) { + lf_set(lock, true); lf_set(move, true); + } else { + lf_set(unlock, true); lf_set(stop, true); + } =} } + reactor Train { - input move:bool; input stop:bool; - state moving:bool(false); - reaction(move) {= - self->moving = true; - =} - reaction(stop) {= - self->moving = false; - =} + input move: bool + input stop: bool + state moving: bool = false + + reaction(move) {= self->moving = true; =} + + reaction(stop) {= self->moving = false; =} } + reactor Door { - input lock:bool; input unlock:bool; - state locked:bool(false); + input lock: bool + input unlock: bool + state locked: bool = false + reaction(lock) {= - // ... Actuate to lock door. - self->locked = true; + // ... Actuate to lock door. + self->locked = true; =} + reaction(unlock) {= - // ... Actuate to unlock door. - self->locked = false; + // ... Actuate to unlock door. + self->locked = false; =} } + main reactor { - c = new Controller(); d = new Door(); - t = new Train(); - c.lock -> d.lock; - c.unlock -> d.unlock after 100 msec; // |\label{line:unlockafter}| - c.move -> t.move after 100 msec; // |\label{line:moveafter}| - c.stop -> t.stop; + c = new Controller() + d = new Door() + t = new Train() + c.lock -> d.lock + c.unlock -> d.unlock after 100 msec // |label{line:unlockafter}| + c.move -> t.move after 100 msec // |label{line:moveafter}| + c.stop -> t.stop } diff --git a/C/src/TrainDoor/TrainDoorSimplest.lf b/C/src/TrainDoor/TrainDoorSimplest.lf index 747635ad..2e98c42e 100644 --- a/C/src/TrainDoor/TrainDoorSimplest.lf +++ b/C/src/TrainDoor/TrainDoorSimplest.lf @@ -1,39 +1,47 @@ // This simple example is considered in this paper: -// https://www.mdpi.com/2227-7390/8/7/1068 +// https://www.mdpi.com/2227-7390/8/7/1068 // where it is studied for its verifiability. -target C; +target C + reactor Controller { - output lock:bool; - output move:bool; - physical action external_move:bool; + output lock: bool + output move: bool + physical action external_move: bool + reaction(startup) {= - // ... Set up sensing. + // ... Set up sensing. =} - reaction(external_move)->lock, move {= - lf_set(lock, external_move->value); - lf_set(move, external_move->value); + + reaction(external_move) -> lock, move {= + lf_set(lock, external_move->value); + lf_set(move, external_move->value); =} } + reactor Train { - input move:bool; - state moving:bool(false); + input move: bool + state moving: bool = false + reaction(move) {= - // ... actuate to move or stop - self->moving = move->value; + // ... actuate to move or stop + self->moving = move->value; =} } + reactor Door { - input lock:bool; - state locked:bool(false); + input lock: bool + state locked: bool = false + reaction(lock) {= - // ... Actuate to lock or unlock door. - self->locked = lock->value; + // ... Actuate to lock or unlock door. + self->locked = lock->value; =} } + main reactor { - controller = new Controller(); - door = new Door(); - train = new Train(); - controller.lock -> door.lock; - controller.move -> train.move; + controller = new Controller() + door = new Door() + train = new Train() + controller.lock -> door.lock + controller.move -> train.move } diff --git a/C/src/TrainDoor/TrainDoorWithDeadlines.lf b/C/src/TrainDoor/TrainDoorWithDeadlines.lf index 0afdabfa..92a7d35b 100644 --- a/C/src/TrainDoor/TrainDoorWithDeadlines.lf +++ b/C/src/TrainDoor/TrainDoorWithDeadlines.lf @@ -1,66 +1,72 @@ // This is a variant of the example is considered in this paper: -// https://www.mdpi.com/2227-7390/8/7/1068 +// https://www.mdpi.com/2227-7390/8/7/1068 // where it is studied for its verifiability. -target C; +target C + reactor Controller { - output lock:bool; - output unlock:bool; - output move:bool; - output stop:bool; - physical action external_move(100 msec):bool; + output lock: bool + output unlock: bool + output move: bool + output stop: bool + physical action external_move(100 msec): bool + reaction(startup) {= - // ... Set up sensing. + // ... Set up sensing. =} - reaction(external_move)->lock, move, unlock, stop {= - lf_set(lock, external_move->value); - lf_set(move, external_move->value); + + reaction(external_move) -> lock, move, unlock, stop {= + lf_set(lock, external_move->value); + lf_set(move, external_move->value); =} } + realtime reactor Train { - input move:bool; - input stop:bool; - state moving:bool(false); - reaction(stop) {= - self->moving = false; - =} deadline (50 msec) {= =} - reaction(move) {= - self->moving = false; - =} deadline(48 msec) {= =} + input move: bool + input stop: bool + state moving: bool = false + + reaction(stop) {= self->moving = false; =} deadline(50 msec) {= =} + + reaction(move) {= self->moving = false; =} deadline(48 msec) {= =} } + realtime reactor Door { - input lock:bool; - input unlock:bool; - physical action external_open; - state locked:bool(false); - state open:bool(false); + input lock: bool + input unlock: bool + physical action external_open + state locked: bool = false + state open: bool = false + reaction(startup) {= - // ... Set up sensing. - =} deadline(48 msec) {= =} + // ... Set up sensing. + =} deadline(48 msec) {= =} + reaction(lock) {= - if (lock && self->open) { - // ... Actuate to close door. - self->open = false; - } - self->locked = lock->value; + if (lock && self->open) { + // ... Actuate to close door. + self->open = false; + } + self->locked = lock->value; =} deadline(50 msec) {= // ... handle the deadline violation... =} - reaction(unlock) {= - self->locked = true; - =} + + reaction(unlock) {= self->locked = true; =} + reaction(external_open) {= - if (!self->locked) { - // ... Actuate to open door. - self->open = true; - } + if (!self->locked) { + // ... Actuate to open door. + self->open = true; + } =} } + main reactor { - controller = new Controller(); - door = new Door(); - train = new Train(); - controller.lock -> door.lock; - controller.move -> train.move after 51 msec; - controller.unlock -> door.unlock after 51 msec; - controller.stop -> train.stop; + controller = new Controller() + door = new Door() + train = new Train() + controller.lock -> door.lock + controller.move -> train.move after 51 msec + controller.unlock -> door.unlock after 51 msec + controller.stop -> train.stop } diff --git a/C/src/TrainDoor/TrainDoorWithDoorOpenState.lf b/C/src/TrainDoor/TrainDoorWithDoorOpenState.lf index 429e10cb..68b22af0 100644 --- a/C/src/TrainDoor/TrainDoorWithDoorOpenState.lf +++ b/C/src/TrainDoor/TrainDoorWithDoorOpenState.lf @@ -1,56 +1,66 @@ // This is a variant of the example is considered in this paper: -// https://www.mdpi.com/2227-7390/8/7/1068 +// https://www.mdpi.com/2227-7390/8/7/1068 // where it is studied for its verifiability. -target C; +target C + reactor Controller { - output lock:bool; - output move:bool; - physical action external_move:bool; + output lock: bool + output move: bool + physical action external_move: bool + reaction(startup) {= - // ... Set up sensing. + // ... Set up sensing. =} - reaction(external_move)->lock, move {= - lf_set(lock, external_move->value); - lf_set(move, external_move->value); + + reaction(external_move) -> lock, move {= + lf_set(lock, external_move->value); + lf_set(move, external_move->value); =} } + reactor Train { - input move:bool; - state moving:bool(false); + input move: bool + state moving: bool = false + reaction(move) {= - if (move) { - self->moving = true; - } else { - self->moving = false; - } + if (move) { + self->moving = true; + } else { + self->moving = false; + } =} } + reactor Door { - input lock:bool; - physical action external_open; - state locked:bool(false); - state open:bool(false); + input lock: bool + physical action external_open + state locked: bool = false + state open: bool = false + reaction(startup) {= - // ... Set up sensing. + // ... Set up sensing. =} + reaction(lock) {= - if (lock && self->open) { - // ... Actuate to close door. - self->open = false; - } - self->locked = lock->value; + if (lock && self->open) { + // ... Actuate to close door. + self->open = false; + } + self->locked = lock->value; =} + reaction(external_open) {= - if (!self->locked) { - // ... Actuate to open door. - self->open = true; - } + if (!self->locked) { + // ... Actuate to open door. + self->open = true; + } =} } + main reactor { - controller = new Controller(); - door = new Door(); - train = new Train(); - controller.lock -> door.lock; - controller.move -> train.move; + controller = new Controller() + door = new Door() + train = new Train() + controller.lock -> door.lock + controller.move -> train.move } diff --git a/C/src/TrainDoor/TrainDoorWithOpen.lf b/C/src/TrainDoor/TrainDoorWithOpen.lf index f2f8b562..18925896 100644 --- a/C/src/TrainDoor/TrainDoorWithOpen.lf +++ b/C/src/TrainDoor/TrainDoorWithOpen.lf @@ -1,52 +1,65 @@ // This is a variant of the example is considered in this paper: -// https://www.mdpi.com/2227-7390/8/7/1068 +// https://www.mdpi.com/2227-7390/8/7/1068 // where it is studied for its verifiability. -target C; +target C + reactor Controller { - output close:bool; output lock:bool; - output unlock:bool; output open:bool; - physical action external:bool; + output close: bool + output lock: bool + output unlock: bool + output open: bool + physical action external: bool + reaction(startup) {= - // ... Set up external sensing. + // ... Set up external sensing. =} - reaction(external)->close, lock, open, unlock {= - if (external->value) { - lf_set(close, true); lf_set(lock, true); - } else { - lf_set(open, true); lf_set(unlock, true); - } + + reaction(external) -> close, lock, open, unlock {= + if (external->value) { + lf_set(close, true); lf_set(lock, true); + } else { + lf_set(open, true); lf_set(unlock, true); + } =} } + reactor Door { - input close:bool; input lock:bool; - input unlock:bool; input open:bool; - physical action ext_open:bool; - state locked:bool(false); - state is_open:bool(false); + input close: bool + input lock: bool + input unlock: bool + input open: bool + physical action ext_open: bool + state locked: bool = false + state is_open: bool = false + reaction(close) {= - // ... Actuate to close door. - self->is_open = false; + // ... Actuate to close door. + self->is_open = false; =} + reaction(lock) {= - // ... Actuate to lock door. - if(!self->is_open) - self->locked = true; + // ... Actuate to lock door. + if(!self->is_open) + self->locked = true; =} + reaction(unlock) {= - // ... Actuate to unlock door. - self->locked = false; + // ... Actuate to unlock door. + self->locked = false; =} + reaction(open, ext_open) {= - // ... Actuate to open door. - if(!self->locked) - self->is_open = true; + // ... Actuate to open door. + if(!self->locked) + self->is_open = true; =} } + main reactor { - c = new Controller(); - d = new Door(); - c.lock -> d.lock after 5 msec; // |\label{line:unlockafter}| - c.unlock -> d.unlock after 4 msec; // |\label{line:unlockafter}| - c.open -> d.open after 7 msec; // |\label{line:unlockafter}| - c.close -> d.close after 3 msec; // |\label{line:unlockafter}| + c = new Controller() + d = new Door() + c.lock -> d.lock after 5 msec // |label{line:unlockafter}| + c.unlock -> d.unlock after 4 msec // |label{line:unlockafter}| + c.open -> d.open after 7 msec // |label{line:unlockafter}| + c.close -> d.close after 3 msec // |label{line:unlockafter}| } diff --git a/C/src/browser-ui/BrowserUI.lf b/C/src/browser-ui/BrowserUI.lf index 657e9a6f..30eb6eef 100644 --- a/C/src/browser-ui/BrowserUI.lf +++ b/C/src/browser-ui/BrowserUI.lf @@ -1,20 +1,18 @@ /** - * A basic user interface realized in the browser. - * This creates a basic web server that listens on a port (default 8080) - * for requests. When it receives a request with no path - * (e.g. "http://localhost:8080/"), it responds with the contents - * of a file specified by the `initial_file` parameter, which is expected - * to be an HTML file and outputs a true on its initialized output. - * - * When it receives a request with a path (e.g. "http://localhost:8080/test"), - * it responds with text. Normally, this would be JSON-formatted, but in this - * simple illustration, it is just plain text that reports the count of the - * number of requests that have been received. - * - * The default `initial_file` provides HTML containing a button that the user - * can push to issue a request with a path. This results in a display in the - * web page of the number of times the button has been pushed. - * + * A basic user interface realized in the browser. This creates a basic web server that listens on a + * port (default 8080) for requests. When it receives a request with no path (e.g. + * "http://localhost:8080/"), it responds with the contents of a file specified by the + * `initial_file` parameter, which is expected to be an HTML file and outputs a true on its + * initialized output. + * + * When it receives a request with a path (e.g. "http://localhost:8080/test"), it responds with + * text. Normally, this would be JSON-formatted, but in this simple illustration, it is just plain + * text that reports the count of the number of requests that have been received. + * + * The default `initial_file` provides HTML containing a button that the user can push to issue a + * request with a path. This results in a display in the web page of the number of times the button + * has been pushed. + * * @author Edward A. Lee */ target C { @@ -44,44 +42,107 @@ preamble {= =} /** - * Start an HTTP server, listen for requests, and provide responses. - * When an HTTP request comes in, this reactor outputs on the request - * output the part of the path starting with the first slash. - * For example, if the URL is http://localhost:8080/foo, then the - * request output will produce "/foo". - * If there is nothing after the first slash or no first slash, - * then this reactor will instead return the contents of the initial_file - * parameter, output true on the initialized output, and output nothing - * on the request output. + * Start an HTTP server, listen for requests, and provide responses. When an HTTP request comes in, + * this reactor outputs on the request output the part of the path starting with the first slash. + * For example, if the URL is http://localhost:8080/foo, then the request output will produce + * "/foo". If there is nothing after the first slash or no first slash, then this reactor will + * instead return the contents of the initial_file parameter, output true on the initialized output, + * and output nothing on the request output. * @param initial_file Path to the initial HTML file to serve, relative to the source directory. - * Defaults to "page.html". + * Defaults to "page.html". * @param hostport The host port number, which defults to 8080. */ -reactor ServerUI( - initial_file:string = "page.html", - hostport:uint16_t = 8080 -) { - output initialized:bool - output request:char* - input response:char* - - physical action req_action:char* - - state browser_ui:browser_ui_t - +reactor ServerUI(initial_file: string = "page.html", hostport: uint16_t = 8080) { + output initialized: bool + output request: char* + input response: char* + + physical action req_action: char* + + state browser_ui: browser_ui_t + + preamble {= + const char html_header[] = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n\r\n"; + + void* listener_thread(void* args) { + browser_ui_t* browser_ui = (browser_ui_t*)args; + + int server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { + lf_print_error_and_exit("Error creating socket."); + } + + int one = 1; + // Allow reusing of local addresses. + setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); + + struct sockaddr_in server_address; + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = INADDR_ANY; + server_address.sin_port = htons(browser_ui->hostport); + + if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { + close(server_socket); + lf_print_error_and_exit("Error binding socket."); + } + // Pending queue length of 5. + listen(server_socket, 5); + + lf_print("****** Point your browser to http://localhost:%d", browser_ui->hostport); + + while(browser_ui->running) { + struct sockaddr_in client_address; + int client_address_length = sizeof(client_address); + + browser_ui->client_socket = accept(server_socket, (struct sockaddr *)&client_address, (socklen_t*)&client_address_length); + + if (browser_ui->client_socket < 0) { + lf_print_error_and_exit("Error accepting connection."); + } + + char buffer[1024] = {0}; + read(browser_ui->client_socket, buffer, 1024); + + // The response depends on the path part of the request. + const char *start_of_path = strchr(buffer, ' ') + 1; + if (start_of_path != NULL && strncmp("/ ", start_of_path, 2) != 0) { + const char *end_of_path = strchr(start_of_path, ' ') + 1; + size_t length = end_of_path - start_of_path; + char* path = (char*)malloc(length + 1); + strncpy(path, start_of_path, length); + path[length] = '\0'; + lf_schedule_value(browser_ui->req_action, 0, path, length + 1); + } else { + // Default is to write initial page. + write(browser_ui->client_socket, html_header, strlen(html_header)); + write( + browser_ui->client_socket, + browser_ui->initial_page, + strlen(browser_ui->initial_page) + ); + close(browser_ui->client_socket); + browser_ui->client_socket = -1; + lf_schedule_copy(browser_ui->req_action, 0, "", 1); + } + } + return NULL; + } + =} + reaction(startup) -> req_action {= // Read the default file to serve. self->browser_ui.initial_page = read_file(self->initial_file); - + self->browser_ui.running = true; self->browser_ui.client_socket = -1; // No client socket awaiting response. self->browser_ui.req_action = req_action; self->browser_ui.hostport = self->hostport; - + lf_thread_t listener; lf_thread_create(&listener, &listener_thread, &self->browser_ui); =} - + reaction(req_action) -> request, initialized {= if (strlen(req_action->value) == 0) { lf_set(initialized, true); @@ -89,7 +150,7 @@ reactor ServerUI( lf_set_token(request, req_action->token); } =} - + reaction(response) {= if (self->browser_ui.client_socket < 0) { lf_print_error("No pending request at the server!"); @@ -100,20 +161,18 @@ reactor ServerUI( close(self->browser_ui.client_socket); self->browser_ui.client_socket = -1; =} - + reaction(shutdown) {= self->browser_ui.running = false; free(self->browser_ui.initial_page); =} /** - * Read a file at path relative to the source .lf file - * and return dynamically-allocated memory with its contents. - * The caller is responsible for freeing the pointer returned. + * Read a file at path relative to the source .lf file and return dynamically-allocated memory + * with its contents. The caller is responsible for freeing the pointer returned. * @param path File path relative to the source .lf file. */ - method read_file(path:{=const char*=}): char* {= - + method read_file(path: {= const char* =}): char* {= // Construct the path to the file to be read char* file_path = (char *) malloc(strlen(LF_SOURCE_DIRECTORY) + strlen(path) + 2); if (file_path == NULL) lf_print_error_and_exit("Out of memory."); @@ -141,87 +200,17 @@ reactor ServerUI( fclose(file); return buffer; =} - - preamble {= - const char html_header[] = "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=UTF-8\r\n\r\n"; - - void* listener_thread(void* args) { - browser_ui_t* browser_ui = (browser_ui_t*)args; - - int server_socket = socket(AF_INET, SOCK_STREAM, 0); - if (server_socket < 0) { - lf_print_error_and_exit("Error creating socket."); - } - - int one = 1; - // Allow reusing of local addresses. - setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); - - struct sockaddr_in server_address; - server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = INADDR_ANY; - server_address.sin_port = htons(browser_ui->hostport); - - if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { - close(server_socket); - lf_print_error_and_exit("Error binding socket."); - } - // Pending queue length of 5. - listen(server_socket, 5); - - lf_print("****** Point your browser to http://localhost:%d", browser_ui->hostport); - - while(browser_ui->running) { - struct sockaddr_in client_address; - int client_address_length = sizeof(client_address); - - browser_ui->client_socket = accept(server_socket, (struct sockaddr *)&client_address, (socklen_t*)&client_address_length); - - if (browser_ui->client_socket < 0) { - lf_print_error_and_exit("Error accepting connection."); - } - - char buffer[1024] = {0}; - read(browser_ui->client_socket, buffer, 1024); - - // The response depends on the path part of the request. - const char *start_of_path = strchr(buffer, ' ') + 1; - if (start_of_path != NULL && strncmp("/ ", start_of_path, 2) != 0) { - const char *end_of_path = strchr(start_of_path, ' ') + 1; - size_t length = end_of_path - start_of_path; - char* path = (char*)malloc(length + 1); - strncpy(path, start_of_path, length); - path[length] = '\0'; - lf_schedule_value(browser_ui->req_action, 0, path, length + 1); - } else { - // Default is to write initial page. - write(browser_ui->client_socket, html_header, strlen(html_header)); - write( - browser_ui->client_socket, - browser_ui->initial_page, - strlen(browser_ui->initial_page) - ); - close(browser_ui->client_socket); - browser_ui->client_socket = -1; - lf_schedule_copy(browser_ui->req_action, 0, "", 1); - } - } - return NULL; - } - =} } main reactor { - state count:int = 0 + state count: int = 0 s = new ServerUI() - - reaction(s.initialized) {= - self->count = 0; - =} + + reaction(s.initialized) {= self->count = 0; =} + reaction(s.request) -> s.response {= char* response; - asprintf(&response, "You have pushed %d times. Path of GET request: %s", + asprintf(&response, "You have pushed %d times. Path of GET request: %s", ++self->count, s.request->value ); diff --git a/C/src/browser-ui/WebSocket.lf b/C/src/browser-ui/WebSocket.lf index 97a20641..6d2a87e2 100644 --- a/C/src/browser-ui/WebSocket.lf +++ b/C/src/browser-ui/WebSocket.lf @@ -1,20 +1,17 @@ /** - * Demo of a use of WebSocketServer enabling a user interface realized in the - * browser. Compile and run this program, then open WebSocket.html in your - * favorite browser. This example program sends to the web page a counting - * sequence. It also accepts text messages from the web page and prints them on - * standard output. + * Demo of a use of WebSocketServer enabling a user interface realized in the browser. Compile and + * run this program, then open WebSocket.html in your favorite browser. This example program sends + * to the web page a counting sequence. It also accepts text messages from the web page and prints + * them on standard output. * - * This example also shows how to limit the number of connections to just two. - * If you try to open 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 how to limit the number of connections to just two. If you try to open + * 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. + * 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. * * @author Edward A. Lee */ @@ -35,7 +32,7 @@ main reactor { state connected_instances: hashset_t = {= NULL =} - s = new WebSocketServer(max_clients = 2) // Limit number of clients to 2. + 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); diff --git a/C/src/browser-ui/WebSocketServer.lf b/C/src/browser-ui/WebSocketServer.lf index ad9ff1ed..637443ea 100644 --- a/C/src/browser-ui/WebSocketServer.lf +++ b/C/src/browser-ui/WebSocketServer.lf @@ -1,40 +1,35 @@ /** - * A web socket server enabling a user interface realized in the browser. This - * creates a web server that listens on a port (default 8000) for web socket - * connections. + * A web socket server enabling a user interface realized in the browser. This creates a web server + * that listens on a port (default 8000) for web socket connections. * - * When a connection is established with a client, an output is produced on the - * connected port that is a struct with a unique wsi (web socket interface) for - * the client and a boolean indicating whether the connection is being opened or - * closed. The wsi can be used to provide input at the send port that will - * target this specific client. + * When a connection is established with a client, an output is produced on the connected port that + * is a struct with a unique wsi (web socket interface) for the client and a boolean indicating + * whether the connection is being opened or closed. The wsi can be used to provide input at the + * send port that will target this specific client. * - * To send messages to a client, construct a dynamically allocated struct of - * type web_socket_message_t, set its wsi field to the value provided by the - * connected output, and set its message and length. Only strings can be sent. + * To send messages to a client, construct a dynamically allocated struct of type + * web_socket_message_t, set its wsi field to the value provided by the connected output, and set + * its message and length. Only strings can be sent. * - * When a message is received from a client, a struct of type - * web_socket_message_t will be produced on the received output port. You can - * use the wsi field to determine which client sent the message. + * When a message is received from a client, a struct of type web_socket_message_t will be produced + * on the received output port. You can use the wsi field to determine which client sent the + * message. * - * You can limit the number of clients by setting the max_clients parameter. It - * defaults to 0, which means there is no limit. A common case for an embedded - * application might be 1 to ensure that only one client connects to your - * application. + * You can limit the number of clients by setting the max_clients parameter. It defaults to 0, which + * means there is no limit. A common case for an embedded application might be 1 to ensure that only + * one client connects to your application. * * This uses the libwebsockets (see API - * documentation and API documentation and installation * instructions). To install on MacOS, we recommending using brew: *
 brew install libwebsockets
- * 
This puts the compiled libraries in {@code /usr/local/lib}, and these - * libraries can be linked to providing the {@code -lwebsockets} compile option. + * This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be + * linked to providing the {@code -lwebsockets} compile option. * * There are a number of limitations: *
    - *
  1. **FIXME:** This should use the secure sockets API in libwebsockets to - * get SSL. + *
  2. **FIXME:** This should use the secure sockets API in libwebsockets to get SSL. *
  3. **FIXME:** This currently only supports sending and receiving text. *
* @@ -275,9 +270,7 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { self->status.running = true; =} - reaction(received_action) -> received {= - lf_set_token(received, received_action->token); - =} + reaction(received_action) -> received {= lf_set_token(received, received_action->token); =} reaction(send) {= // NOTE: This send must be before the reaction to connected_action @@ -297,9 +290,7 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { } =} - reaction(connected_action) -> connected {= - lf_set(connected, connected_action->value); - =} + reaction(connected_action) -> connected {= lf_set(connected, connected_action->value); =} reaction(shutdown) {= self->status.running = false; =} } diff --git a/C/src/car-brake/CarBrake.lf b/C/src/car-brake/CarBrake.lf index 6da1d443..3f1d4c98 100644 --- a/C/src/car-brake/CarBrake.lf +++ b/C/src/car-brake/CarBrake.lf @@ -1,36 +1,36 @@ /** - * Sketch of an ADAS system illustrating tradeoffs between consistency and availability. - * This version experiences deadline violations in the Brake component because of - * processing times in the BrakingAssistant component. - * This version is federated for fair comparison with the other two versions. - * + * Sketch of an ADAS system illustrating tradeoffs between consistency and availability. This + * version experiences deadline violations in the Brake component because of processing times in the + * BrakingAssistant component. This version is federated for fair comparison with the other two + * versions. + * * @author Christian Menard * @author Edward A. Lee */ target C { - timeout: 10s, + timeout: 10 s, keepalive: true -}; +} reactor Camera { - timer t(20ms, 33ms) + timer t(20 ms, 33 ms) output frame: bool - reaction (t) -> frame {= + reaction(t) -> frame {= lf_set(frame, true); // send a "frame" =} } reactor BrakingAssistant { - input frame: bool; - output trigger_brake: bool; - - state counter: int(0); + input frame: bool + output trigger_brake: bool + + state counter: int = 0 reaction(frame) -> trigger_brake {= // processing takes some time (10 ms) lf_sleep(MSEC(10)); - + if (self->counter % 10 == 0) { lf_print("[automatic] Send the brake signal at physical time " PRINTF_TIME, lf_time_physical_elapsed()); lf_set(trigger_brake, true); @@ -54,15 +54,13 @@ reactor Braking { return NULL; } =} - input brake_assistant: bool; + input brake_assistant: bool - physical action pedal; + physical action pedal - state thread: lf_thread_t; + state thread: lf_thread_t - reaction(startup) -> pedal {= - lf_thread_create(&self->thread, &press_pedal, pedal); - =} + reaction(startup) -> pedal {= lf_thread_create(&self->thread, &press_pedal, pedal); =} reaction(shutdown) {= stop_thread = true; @@ -74,17 +72,17 @@ reactor Braking { lf_print("[system] Brake manually triggered at physical time " PRINTF_TIME, lf_time_physical_elapsed() ); - =} deadline (3msecs) {= + =} deadline(3 msecs) {= lf_print("\033[1;31m[ERROR]\033[0m Deadline on manual braking violated at physical time " PRINTF_TIME, lf_time_physical_elapsed() ); =} - + reaction(brake_assistant) {= lf_print("[system] Brake automatically triggered at physical time " PRINTF_TIME, lf_time_physical_elapsed() ); - =} deadline (25msecs) {= + =} deadline(25 msecs) {= lf_print("\033[1;31m[error]\033[0m Deadline on automatic braking violated " "at physical time " PRINTF_TIME, lf_time_physical_elapsed() ); @@ -92,17 +90,17 @@ reactor Braking { } reactor Vision { - output trigger_brake: bool; - camera = new Camera(); - assistant = new BrakingAssistant(); + output trigger_brake: bool + camera = new Camera() + assistant = new BrakingAssistant() - camera.frame -> assistant.frame; - assistant.trigger_brake -> trigger_brake; + camera.frame -> assistant.frame + assistant.trigger_brake -> trigger_brake } federated reactor { - braking = new Braking(); - vision = new Vision(); + braking = new Braking() + vision = new Vision() - vision.trigger_brake -> braking.brake_assistant; + vision.trigger_brake -> braking.brake_assistant } diff --git a/C/src/car-brake/CarBrake2.lf b/C/src/car-brake/CarBrake2.lf index 4afd73e2..29805602 100644 --- a/C/src/car-brake/CarBrake2.lf +++ b/C/src/car-brake/CarBrake2.lf @@ -1,20 +1,19 @@ /** - * This version of the CarBreak example decouples the Vision analysis - * using a physical connection, which gives up consistency (and determinacy). - * The execution is federated, so the Vision component has no effect - * on the ability to meet deadlines in the response to brake pedal actions. - * This version is far less likely to experience deadline violations. + * This version of the CarBreak example decouples the Vision analysis using a physical connection, + * which gives up consistency (and determinacy). The execution is federated, so the Vision component + * has no effect on the ability to meet deadlines in the response to brake pedal actions. This + * version is far less likely to experience deadline violations. */ target C { keepalive: true, - timeout: 10s -}; + timeout: 10 s +} import Braking, Vision from "CarBrake.lf" federated reactor { - braking = new Braking(); - vision = new Vision(); + braking = new Braking() + vision = new Vision() - vision.trigger_brake ~> braking.brake_assistant; + vision.trigger_brake ~> braking.brake_assistant } diff --git a/C/src/car-brake/CarBrake3.lf b/C/src/car-brake/CarBrake3.lf index 752b8efa..4201b5aa 100644 --- a/C/src/car-brake/CarBrake3.lf +++ b/C/src/car-brake/CarBrake3.lf @@ -1,20 +1,19 @@ /** - * This version of the CarBreak example decouples the Vision analysis - * using a physical connection, which gives up consistency (and determinacy). - * The execution is federated, so the Vision component has no effect - * on the ability to meet deadlines in the response to brake pedal actions. - * This version is far less likely to experience deadline violations. + * This version of the CarBreak example decouples the Vision analysis using a physical connection, + * which gives up consistency (and determinacy). The execution is federated, so the Vision component + * has no effect on the ability to meet deadlines in the response to brake pedal actions. This + * version is far less likely to experience deadline violations. */ target C { keepalive: true, - timeout: 10s -}; + timeout: 10 s +} import Braking, Vision from "CarBrake.lf" federated reactor { - braking = new Braking(); - vision = new Vision(); + braking = new Braking() + vision = new Vision() - vision.trigger_brake -> braking.brake_assistant after 20ms; + vision.trigger_brake -> braking.brake_assistant after 20 ms } diff --git a/C/src/deadlines/AnytimePrime.lf b/C/src/deadlines/AnytimePrime.lf index 095155b8..c2a6c4a1 100644 --- a/C/src/deadlines/AnytimePrime.lf +++ b/C/src/deadlines/AnytimePrime.lf @@ -1,12 +1,10 @@ /** - * This program is a concept demonstration showing how the lf_check_deadline() - * function works. This program performs "anytime computation" - * (https://en.wikipedia.org/wiki/Anytime_algorithm) to find the largest prime - * within the given deadline. The lf_check_deadline() function is called after - * checking whether each number is prime. If lf_check_deadline() is called after - * the deadline has passed, the function calls the deadline handler and returns - * true. Then the reaction outputs the largest prime found by that time and - * exits. + * This program is a concept demonstration showing how the lf_check_deadline() function works. This + * program performs "anytime computation" (https://en.wikipedia.org/wiki/Anytime_algorithm) to find + * the largest prime within the given deadline. The lf_check_deadline() function is called after + * checking whether each number is prime. If lf_check_deadline() is called after the deadline has + * passed, the function calls the deadline handler and returns true. Then the reaction outputs the + * largest prime found by that time and exits. * * For more discussion on lf_check_deadline(), see: * https://github.com/lf-lang/lingua-franca/issues/403 @@ -17,10 +15,7 @@ */ target C { fast: true, - files: [ - "/lib/c/reactor-c/include/core/utils/vector.h", - "/lib/c/reactor-c/core/utils/vector.c" - ], + files: ["/lib/c/reactor-c/include/core/utils/vector.h", "/lib/c/reactor-c/core/utils/vector.c"], cmake-include: ["/lib/c/reactor-c/core/utils/vector.cmake"] } @@ -60,9 +55,7 @@ reactor Prime { reactor Print { input in: {= long long =} - reaction(in) {= - printf("Largest prime found within the deadline: %lld\n", in->value); - =} + reaction(in) {= printf("Largest prime found within the deadline: %lld\n", in->value); =} } main reactor AnytimePrime { diff --git a/C/src/deadlines/Deadline.lf b/C/src/deadlines/Deadline.lf index 4058e70a..6ec8d499 100644 --- a/C/src/deadlines/Deadline.lf +++ b/C/src/deadlines/Deadline.lf @@ -1,9 +1,8 @@ /** - * Example that demonstrates the detection of deadline misses. This program - * models a sensor, some work done on the sensor data, and an actuator with a - * deadline. The sensor is your keyboard Return or Enter button. The work that - * is done is simulated every second time to take more than 500 microseconds (by - * sleeping for 500 usecs). Hence, you should see that deadline is missed every + * Example that demonstrates the detection of deadline misses. This program models a sensor, some + * work done on the sensor data, and an actuator with a deadline. The sensor is your keyboard Return + * or Enter button. The work that is done is simulated every second time to take more than 500 + * microseconds (by sleeping for 500 usecs). Hence, you should see that deadline is missed every * second time you hit Return. * * @author Edward A. Lee diff --git a/C/src/deadlines/PeriodicDeadline.lf b/C/src/deadlines/PeriodicDeadline.lf index 59777a3c..132ed447 100644 --- a/C/src/deadlines/PeriodicDeadline.lf +++ b/C/src/deadlines/PeriodicDeadline.lf @@ -1,50 +1,46 @@ /** - * In a periodic sense-compute-actuate system, we can put a deadline on the actuator - * to specify a real-time requirement. However, a common situation is that there is - * an initialization phase that takes considerable physical time to complete. - * Without some precautions, this can lead to the deadline being violated during - * the first few cycles. - * - * This example illustrates the problem and two solutions. There are three parallel - * subsystems: - * - * 1. This naive system is the only one that should miss deadlines. - * It starts its periodic sensing at the starting logical time, ignoring the - * lengthy time taken by the startup reaction. - * - * 2. This better system introduces an offset in the timer that an estimate - * of the maximum time that the startup reaction is likely to take. - * Only after that offset does it start periodic sensing. - * - * 3. This even better system does not assume any knowledge of how long the - * startup reaction will take. Instead, at the conclusion of the startup - * reaction, it schedules an action that starts the periodic sensing at - * the current physical time. + * In a periodic sense-compute-actuate system, we can put a deadline on the actuator to specify a + * real-time requirement. However, a common situation is that there is an initialization phase that + * takes considerable physical time to complete. Without some precautions, this can lead to the + * deadline being violated during the first few cycles. + * + * This example illustrates the problem and two solutions. There are three parallel subsystems: + * + * 1. This naive system is the only one that should miss deadlines. It starts its periodic sensing + * at the starting logical time, ignoring the lengthy time taken by the startup reaction. + * + * 2. This better system introduces an offset in the timer that an estimate of the maximum time that + * the startup reaction is likely to take. Only after that offset does it start periodic sensing. + * + * 3. This even better system does not assume any knowledge of how long the startup reaction will + * take. Instead, at the conclusion of the startup reaction, it schedules an action that starts the + * periodic sensing at the current physical time. * * @author Edward A. Lee */ target C { - timeout: 5s + timeout: 5 s } reactor Sensor { - output y:int - state count:int = 0 + output y: int + state count: int = 0 } -reactor OffsetSensor(offset:time = 0) extends Sensor { - timer t(offset, 500ms) +reactor OffsetSensor(offset: time = 0) extends Sensor { + timer t(offset, 500 ms) + reaction(startup) {= // Take a long time. lf_sleep(SEC(2)); =} - reaction(t) -> y {= - lf_set(y, self->count++); - =} + + reaction(t) -> y {= lf_set(y, self->count++); =} } reactor StartupSensor extends Sensor { logical action a + reaction(startup) -> a {= // Take a long time. lf_sleep(SEC(2)); @@ -53,6 +49,7 @@ reactor StartupSensor extends Sensor { instant_t offset = lf_time_physical_elapsed(); lf_schedule(a, offset); =} + reaction(a) -> y, a {= lf_set(y, self->count++); lf_schedule(a, MSEC(500)); @@ -69,14 +66,15 @@ reactor Compute { =} } -reactor Actuator(id:int = 1) { +reactor Actuator(id: int = 1) { input x: int + reaction(x) {= lf_print("Actuator %d: Actuating with lag " PRINTF_TIME, self->id, lf_time_physical_elapsed() - lf_time_logical_elapsed() ); - =} deadline(30ms) {= + =} deadline(30 ms) {= lf_print("Actuator %d: **** Deadline missed with lag " PRINTF_TIME, self->id, lf_time_physical_elapsed() - lf_time_logical_elapsed() @@ -85,21 +83,21 @@ reactor Actuator(id:int = 1) { } main reactor { - s1 = new OffsetSensor(offset = 0) + s1 = new OffsetSensor(offset=0) c1 = new Compute() - a1 = new Actuator(id = 1) + a1 = new Actuator(id=1) s1.y -> c1.x c1.y -> a1.x - - s2 = new OffsetSensor(offset = 2s) + + s2 = new OffsetSensor(offset = 2 s) c2 = new Compute() - a2 = new Actuator(id = 2) + a2 = new Actuator(id=2) s2.y -> c2.x c2.y -> a2.x s3 = new StartupSensor() c3 = new Compute() - a3 = new Actuator(id = 3) + a3 = new Actuator(id=3) s3.y -> c3.x c3.y -> a3.x } diff --git a/C/src/keyboard/Keyboard.lf b/C/src/keyboard/Keyboard.lf index cf50a4bc..25e199ea 100644 --- a/C/src/keyboard/Keyboard.lf +++ b/C/src/keyboard/Keyboard.lf @@ -29,74 +29,74 @@ reactor KeyboardInput(exit_key: int = 0) { physical action keypress: int preamble {= - #include "platform.h" - #include - #include - // Thread to read input characters until an EOF is received. - // Each time a character is received, schedule a keypress action. - void* read_input(void* keyboard_input) { - int c; - while((c = getch()) != EOF) { - if (c == ((keyboard_input_t*)keyboard_input)->exit_key) { - lf_request_stop(); - break; + #include "platform.h" + #include + #include + // Thread to read input characters until an EOF is received. + // Each time a character is received, schedule a keypress action. + void* read_input(void* keyboard_input) { + int c; + while((c = getch()) != EOF) { + if (c == ((keyboard_input_t*)keyboard_input)->exit_key) { + lf_request_stop(); + break; + } + lf_schedule_copy(((keyboard_input_t*)keyboard_input)->keypress, 0, &c, 1); } - lf_schedule_copy(((keyboard_input_t*)keyboard_input)->keypress, 0, &c, 1); + return NULL; + } + // Function to direct printed messages to the curses-managed terminal. + void print_to_terminal(const char* format, va_list args) { + static int line_count = 1; + move(line_count++, 0); + vwprintw(stdscr, format, args); + refresh(); + if(line_count >= getmaxy(stdscr)) line_count = 1; + } + // Function for orderly shutdown upon control-c. + void sig_handler(int sig) { + lf_request_stop(); } - return NULL; - } - // Function to direct printed messages to the curses-managed terminal. - void print_to_terminal(const char* format, va_list args) { - static int line_count = 1; - move(line_count++, 0); - vwprintw(stdscr, format, args); - refresh(); - if(line_count >= getmaxy(stdscr)) line_count = 1; - } - // Function for orderly shutdown upon control-c. - void sig_handler(int sig) { - lf_request_stop(); - } =} reaction(startup) -> keypress {= - initscr(); // Initialize the curses library - cbreak(); // Disable line buffering - noecho(); // Disable automatic echoing of typed characters - keypad(stdscr, TRUE); // Enable special keys + initscr(); // Initialize the curses library + cbreak(); // Disable line buffering + noecho(); // Disable automatic echoing of typed characters + keypad(stdscr, TRUE); // Enable special keys - move(0, 0); - if(self->exit_key != 0) { - printw("Type %c to exit.\n", self->exit_key); - } - refresh(); + move(0, 0); + if(self->exit_key != 0) { + printw("Type %c to exit.\n", self->exit_key); + } + refresh(); - // Register a print function handler so lf_print works. - lf_register_print_function(print_to_terminal, LOG_LEVEL_ALL); + // Register a print function handler so lf_print works. + lf_register_print_function(print_to_terminal, LOG_LEVEL_ALL); - if (signal(SIGINT, sig_handler) == SIG_ERR) { - lf_print_warning("Failed to register signal handler. After exit, may have to reset terminal using 'reset'."); - } + if (signal(SIGINT, sig_handler) == SIG_ERR) { + lf_print_warning("Failed to register signal handler. After exit, may have to reset terminal using 'reset'."); + } - static keyboard_input_t keyboard_input; - keyboard_input.keypress = keypress; - keyboard_input.exit_key = self->exit_key; + static keyboard_input_t keyboard_input; + keyboard_input.keypress = keypress; + keyboard_input.exit_key = self->exit_key; - // Start the thread that listens for key presses. - lf_thread_t thread_id; - lf_thread_create(&thread_id, &read_input, &keyboard_input); + // Start the thread that listens for key presses. + lf_thread_t thread_id; + lf_thread_create(&thread_id, &read_input, &keyboard_input); =} reaction(keypress) -> key {= lf_set(key, keypress->value); =} reaction(shutdown) {= - endwin(); - lf_register_print_function(NULL, -1); + endwin(); + lf_register_print_function(NULL, -1); =} } main reactor { - k = new KeyboardInput(exit_key = 120) + k = new KeyboardInput(exit_key=120) reaction(k.key) {= lf_print("You typed %c (keycode %d).", k.key->value, k.key->value); =} } diff --git a/C/src/leader-election/Election.lf b/C/src/leader-election/Election.lf index 06abf4c1..9f2e90ca 100644 --- a/C/src/leader-election/Election.lf +++ b/C/src/leader-election/Election.lf @@ -1,36 +1,31 @@ /** - * The Lingua Franca (LF) program above represents a distributed leader election - * system with three nodes. Each node has an associated unique ID, and their goal - * is to elect the node with the highest ID as the leader. The program consists of - * a federated reactor that connects three Node reactors in a ring topology. Each - * node initially broadcasts its ID, and upon receiving an incoming ID, it compares - * it with its own. If the received ID is greater than its own, the node forwards - * the received ID after a 10 ms delay. If the received ID is equal to the node's - * own ID, it declares itself as the elected leader and transitions to the Elected - * mode. The leader election process continues until the node with the highest ID - * is elected. - * + * The Lingua Franca (LF) program above represents a distributed leader election system with three + * nodes. Each node has an associated unique ID, and their goal is to elect the node with the + * highest ID as the leader. The program consists of a federated reactor that connects three Node + * reactors in a ring topology. Each node initially broadcasts its ID, and upon receiving an + * incoming ID, it compares it with its own. If the received ID is greater than its own, the node + * forwards the received ID after a 10 ms delay. If the received ID is equal to the node's own ID, + * it declares itself as the elected leader and transitions to the Elected mode. The leader election + * process continues until the node with the highest ID is elected. + * * @author Edward A. Lee * @author Julien Brunel * @author David Chemouil */ target C -reactor Node( - id:int(0) -) { - input in:int - output out:int - - logical action a:int - + +reactor Node(id: int = 0) { + input in: int + output out: int + + logical action a: int + initial mode NotElected { - reaction(startup) -> out {= - lf_set(out, self->id); - =} - reaction(a) -> out {= - lf_set(out, a->value); - =} - reaction(in) -> a, Elected {= + reaction(startup) -> out {= lf_set(out, self->id); =} + + reaction(a) -> out {= lf_set(out, a->value); =} + + reaction(in) -> a, reset(Elected) {= if (in->value > self->id) { lf_schedule_int(a, MSEC(10), in->value); } else if (in->value == self->id) { @@ -39,14 +34,15 @@ reactor Node( } =} } + mode Elected { - } } + federated reactor { i0 = new Node() - i1 = new Node(id = 1) - i2 = new Node(id = 2) + i1 = new Node(id=1) + i2 = new Node(id=2) i0.out -> i1.in i1.out -> i2.in i2.out -> i0.in diff --git a/C/src/lib/PoissonClock.lf b/C/src/lib/PoissonClock.lf index 8e5286d9..540215dd 100644 --- a/C/src/lib/PoissonClock.lf +++ b/C/src/lib/PoissonClock.lf @@ -1,31 +1,34 @@ /** - * This reactor produces output events according to a Poisson process. - * The time between events is given by independent and identically - * distributed exponential random variables. Each output is a boolean - * true. The average number of events per second is given by the lambda - * parameter (1/lambda is the mean time between events). - * - * If the seed is set to anything other than 0, then the output sequence - * will be the same each time the program is run. This will be true even - * if there are multiple instances of this reactor. If any two have the - * same seed, then they will produce identical sequences. - * + * This reactor produces output events according to a Poisson process. The time between events is + * given by independent and identically distributed exponential random variables. Each output is a + * boolean true. The average number of events per second is given by the lambda parameter (1/lambda + * is the mean time between events). + * + * If the seed is set to anything other than 0, then the output sequence will be the same each time + * the program is run. This will be true even if there are multiple instances of this reactor. If + * any two have the same seed, then they will produce identical sequences. + * * @author Edward A. Lee */ target C + import Random from "Random.lf" + preamble {= #include =} -reactor PoissonClock(lambda:double(1.0)) extends Random { - output event:bool + +reactor PoissonClock(lambda: double = 1.0) extends Random { + output event: bool logical action a + reaction(startup) -> a {= double delta = exponential(self->lambda); // Convert seconds to nanoseconds. interval_t interval = (interval_t)(delta * SEC(1)); lf_schedule(a, interval); =} + reaction(a) -> event, a {= lf_set(event, true); double delta = exponential(self->lambda); diff --git a/C/src/lib/PrintToFile.lf b/C/src/lib/PrintToFile.lf index a5182325..5a0a80a4 100644 --- a/C/src/lib/PrintToFile.lf +++ b/C/src/lib/PrintToFile.lf @@ -1,21 +1,21 @@ -/** - * Reactor that prints time-value pairs to a file. - */ +/** Reactor that prints time-value pairs to a file. */ target C -reactor PrintToFile(filename:string("output.data")) { - input y:double; - state file:FILE*({=NULL=}); + +reactor PrintToFile(filename: string = "output.data") { + input y: double + state file: FILE* = {= NULL =} + reaction(startup) {= self->file = fopen(self->filename, "w"); if(self->file == NULL) { lf_print_error_and_exit("Failed to open file: %s", self->filename); } =} + reaction(y) {= double t = lf_time_logical_elapsed() / 1.0e9; fprintf(self->file, "%f %f\n", t, y->value); =} - reaction(shutdown) {= - fclose(self->file); - =} + + reaction(shutdown) {= fclose(self->file); =} } diff --git a/C/src/lib/Random.lf b/C/src/lib/Random.lf index aef15f20..b7bb897a 100644 --- a/C/src/lib/Random.lf +++ b/C/src/lib/Random.lf @@ -1,40 +1,35 @@ /** - * This reactor is a base class for reactors that generate - * or use random numbers. The method random() returns an - * integer between 0 and RAND_MAX. The method - * exponential(lambda) returns an exponential random - * variable with rate lambda (expected value 1/lambda). - * - * This reactor ensures that if a seed is set, then - * the reactor returns a repeatable random number sequence - * regardless of how many other reactors are generating - * random numbers. - * - * The seed defaults to 0, which indicates to use the - * starting logical time (or more precisely, the low-order - * 32 bits of the starting logical time) as a seed. - * If any seed other than 0 is given, then the sequence - * of random numbers will be repeatable in the sense that - * every execution of the program will produce the same - * sequence. - * + * This reactor is a base class for reactors that generate or use random numbers. The method + * random() returns an integer between 0 and RAND_MAX. The method exponential(lambda) returns an + * exponential random variable with rate lambda (expected value 1/lambda). + * + * This reactor ensures that if a seed is set, then the reactor returns a repeatable random number + * sequence regardless of how many other reactors are generating random numbers. + * + * The seed defaults to 0, which indicates to use the starting logical time (or more precisely, the + * low-order 32 bits of the starting logical time) as a seed. If any seed other than 0 is given, + * then the sequence of random numbers will be repeatable in the sense that every execution of the + * program will produce the same sequence. + * * @author Edward A. Lee */ target C + preamble {= #include #include =} -reactor Random(seed:{=unsigned int=}(0)) { + +reactor Random(seed: {= unsigned int =} = 0) { reaction(startup) {= if (self->seed == 0) { self->seed = (unsigned int)lf_time_logical(); } =} - method random():int {= - return rand_r(&self->seed); - =} - method exponential(lambda:double):double {= + + method random(): int {= return rand_r(&self->seed); =} + + method exponential(lambda: double): double {= double u = random() / (RAND_MAX + 1.0); return -log(1.0 - u) / lambda; =} diff --git a/C/src/lib/RandomDelay.lf b/C/src/lib/RandomDelay.lf index e5dddeb5..59d9c5d4 100644 --- a/C/src/lib/RandomDelay.lf +++ b/C/src/lib/RandomDelay.lf @@ -1,21 +1,21 @@ /** - * Delay an input value by a random amount given - * by an exponential random variable with average value - * given by average. To make measuring the - * delay convenient, the type of the input is a time + * Delay an input value by a random amount given by an exponential random variable with average + * value given by average. To make measuring the delay convenient, the type of the input is a time * and the same time will be sent to the output. - * + * * @author Edward A. Lee */ target C + import Random from "Random.lf" -reactor RandomDelay(average:time(1 sec)) extends Random { - input in:time - output out:time - logical action a:time - reaction(a) -> out {= - lf_set(out, a->value); - =} + +reactor RandomDelay(average: time = 1 sec) extends Random { + input in: time + output out: time + logical action a: time + + reaction(a) -> out {= lf_set(out, a->value); =} + reaction(in) -> a {= double lambda = SEC(1) / ((double)self->average); double exp = exponential(lambda); diff --git a/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf b/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf index ed812939..1af7c3e3 100644 --- a/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf +++ b/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf @@ -1,19 +1,15 @@ /** - * A simulation of a Furuta pendulum with a modal controller - * based on the Ptolemy II model constructed by Johan Eker - * and described in this paper: - * - * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, - * “Realistic simulations of embedded control systems,” - * IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. - * - * This program specifies a build script that only code generates - * and compiles the program, as usual, but also executes the - * program and processes its output to generate and open a - * plot. - * You have to have installed gnuplot and have it in your - * PATH for this script to work as expected (and also cmake). - * + * A simulation of a Furuta pendulum with a modal controller based on the Ptolemy II model + * constructed by Johan Eker and described in this paper: + * + * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, “Realistic simulations of embedded control + * systems,” IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. + * + * This program specifies a build script that only code generates and compiles the program, as + * usual, but also executes the program and processes its output to generate and open a plot. You + * have to have installed gnuplot and have it in your PATH for this script to work as expected (and + * also cmake). + * * @author Edward A. Lee * @author Alexander Schulz-Rosengarten */ @@ -23,22 +19,23 @@ target C { flags: "-lm", build: "./build_run_plot.sh FurutaPendulum" } -import PendulumController from "PendulumController.lf"; -import PendulumSimulation from "PendulumSimulation.lf"; -import Print from "Print.lf"; + +import PendulumController from "PendulumController.lf" +import PendulumSimulation from "PendulumSimulation.lf" +import Print from "Print.lf" main reactor { - s = new PendulumSimulation(); - c = new PendulumController(); - p = new Print(); + s = new PendulumSimulation() + c = new PendulumController() + p = new Print() - s.phi, s.d_phi -> c.phi, c.d_phi; - s.theta, s.d_theta -> c.theta, c.d_theta; - c.control -> s.u; + s.phi, s.d_phi -> c.phi, c.d_phi + s.theta, s.d_theta -> c.theta, c.d_theta + c.control -> s.u - c.control -> p.control; - c.modeID -> p.modeID; - c.energy -> p.energy; - s.theta -> p.theta; - s.phi -> p.phi; + c.control -> p.control + c.modeID -> p.modeID + c.energy -> p.energy + s.theta -> p.theta + s.phi -> p.phi } diff --git a/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf b/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf index 389d33a8..48c24c6b 100644 --- a/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf +++ b/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf @@ -1,7 +1,7 @@ /** - * This variant of the Furuta pendulum example introduces - * a disturbance to bring the pendulum out of balance. - * + * This variant of the Furuta pendulum example introduces a disturbance to bring the pendulum out of + * balance. + * * @author Edward A. Lee * @author Alexander Schulz-Rosengarten */ @@ -11,28 +11,27 @@ target C { flags: "-lm", build: "./build_run_plot.sh FurutaPendulumDisturbance" } -import PendulumController from "PendulumController.lf"; -import PendulumSimulation from "PendulumSimulation.lf"; -import Print from "Print.lf"; + +import PendulumController from "PendulumController.lf" +import PendulumSimulation from "PendulumSimulation.lf" +import Print from "Print.lf" main reactor { - s = new PendulumSimulation(); - c = new PendulumController(); - p = new Print(); + s = new PendulumSimulation() + c = new PendulumController() + p = new Print() + + timer disturb(3 sec) - timer disturb(3 sec); - - reaction(disturb) -> s.d {= - lf_set(s.d, 0.5); - =} + s.phi, s.d_phi -> c.phi, c.d_phi + s.theta, s.d_theta -> c.theta, c.d_theta + c.control -> s.u - s.phi, s.d_phi -> c.phi, c.d_phi; - s.theta, s.d_theta -> c.theta, c.d_theta; - c.control -> s.u; + c.control -> p.control + c.modeID -> p.modeID + c.energy -> p.energy + s.theta -> p.theta + s.phi -> p.phi - c.control -> p.control; - c.modeID -> p.modeID; - c.energy -> p.energy; - s.theta -> p.theta; - s.phi -> p.phi; + reaction(disturb) -> s.d {= lf_set(s.d, 0.5); =} } diff --git a/C/src/modal_models/FurutaPendulum/PendulumController.lf b/C/src/modal_models/FurutaPendulum/PendulumController.lf index 5acef877..a6ad66b7 100644 --- a/C/src/modal_models/FurutaPendulum/PendulumController.lf +++ b/C/src/modal_models/FurutaPendulum/PendulumController.lf @@ -1,15 +1,14 @@ /** - * A modal controller for a Furuta pendulum, - * based on the Ptolemy II model constructed by Johan Eker + * A modal controller for a Furuta pendulum, based on the Ptolemy II model constructed by Johan Eker * and described in this paper: * - * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, - * “Realistic simulations of embedded control systems,” - * IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. - * + * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, “Realistic simulations of embedded control + * systems,” IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. + * * @author Edward A. Lee */ -target C; +target C + preamble {= #include #define PI 3.14159265 @@ -21,41 +20,41 @@ preamble {= return((fmod(fabs(theta) + PI, 2 * PI) - PI) * sign(theta)); } =} + reactor PendulumController( - h:double(0.005), // Sample interval - w0:double(6.3), - k:double(0.5), // Energy multiplier to swing up. - n:double(0.5), // Bound on swing up control magnitude. - region1:double(0.1), // Region to exit SwingUp. - region2:double(0.2), // Region to exit Stabilize. - max_speed:double(0.05), // Speed to exit Catch. - ci1:double(-1.04945717118225), - ci2:double(-0.20432286791216), - ci3:double(-0.00735846749875), - ci4:double(-0.00735846749875), - si1:double(-1.70871686211144), - si2:double(-0.30395427746831), - si3:double(-0.03254225945714), - si4:double(-0.05808270221773), - phi2:double(-7.0124562) -) { - input theta:double; - input d_theta:double; - input phi:double; - input d_phi:double; - - output control:double; - output modeID:double; - output energy:double; - - state phi0:double(0.0); - + h: double = 0.005, // Sample interval + w0: double = 6.3, + k: double = 0.5, // Energy multiplier to swing up. + n: double = 0.5, // Bound on swing up control magnitude. + region1: double = 0.1, // Region to exit SwingUp. + region2: double = 0.2, // Region to exit Stabilize. + max_speed: double = 0.05, // Speed to exit Catch. + ci1: double = -1.04945717118225, + ci2: double = -0.20432286791216, + ci3: double = -0.00735846749875, + ci4: double = -0.00735846749875, + si1: double = -1.70871686211144, + si2: double = -0.30395427746831, + si3: double = -0.03254225945714, + si4: double = -0.05808270221773, + phi2: double = -7.0124562) { + input theta: double + input d_theta: double + input phi: double + input d_phi: double + + output control: double + output modeID: double + output energy: double + + state phi0: double = 0.0 + initial mode SwingUp { reaction(theta, d_theta) d_phi -> control, modeID, energy {= double th = restrictAngle(theta->value); - double E = 0.5 - * d_theta->value - * d_theta->value + double E = 0.5 + * d_theta->value + * d_theta->value / (self->w0 * self->w0) + cos(th) - 1.0; double c = sign(d_theta->value * cos(th)); @@ -64,13 +63,14 @@ reactor PendulumController( lf_set(energy, E); lf_set(modeID, -1); =} - reaction(theta) -> Catch {= + + reaction(theta) -> reset(Catch) {= if (fabs(theta->value) < self->region1) { lf_set_mode(Catch); } =} } - + mode Catch { reaction(theta, d_theta, phi, d_phi) -> control, modeID, energy {= double th = restrictAngle(theta->value); @@ -81,14 +81,15 @@ reactor PendulumController( + d_phi->value * self->ci4 )); lf_set(modeID, 0); - double E = 0.5 + double E = 0.5 * d_theta->value * d_theta->value / (self->w0 * self->w0) + cos(th) - 1.0; lf_set(energy, E); =} - reaction(phi, d_phi) -> Stabilize {= + + reaction(phi, d_phi) -> reset(Stabilize) {= if (fabs(d_phi->value) < self->max_speed) { lf_set_mode(Stabilize); self->phi0 = phi->value; @@ -105,15 +106,16 @@ reactor PendulumController( + (phi->value - self->phi0) * self->si3 + d_phi->value * self->si4 )); - double E = 0.5 - * d_theta->value - * d_theta->value + double E = 0.5 + * d_theta->value + * d_theta->value / (self->w0 * self->w0) + cos(th) - 1.0; lf_set(energy, E); lf_set(modeID, 1); =} - reaction(theta) -> SwingUp {= + + reaction(theta) -> reset(SwingUp) {= double th = restrictAngle(theta->value); if (fabs(th) > self->region2) { lf_set_mode(SwingUp); diff --git a/C/src/modal_models/FurutaPendulum/PendulumSimulation.lf b/C/src/modal_models/FurutaPendulum/PendulumSimulation.lf index 0b597ca2..c8b1e7e0 100644 --- a/C/src/modal_models/FurutaPendulum/PendulumSimulation.lf +++ b/C/src/modal_models/FurutaPendulum/PendulumSimulation.lf @@ -1,66 +1,56 @@ -target C; +target C + /** - * A simple forward-Euler simulation of a Furuta pendulum, - * based on the Ptolemy II model constructed by Johan Eker - * and described in this paper: + * A simple forward-Euler simulation of a Furuta pendulum, based on the Ptolemy II model constructed + * by Johan Eker and described in this paper: * - * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, - * “Realistic simulations of embedded control systems,” - * IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. - * - * The Ptolemy II model is more accurate because it uses an - * RK-45 solver, but this is adequate for many purposes. - * - * This outputs its state every `sample_period`. - * It updates the state before outputting it - * using the most recently received control input. - * - * The `theta` output is the angle of the pendulum, - * which is 0 when the pendulum is pointing straight up, - * and `d_theta` is its initial angular velocity. - * The `phi` output is the angle of the horizontal - * arm and `d_phi` is its angular velocity. - * - * The `u` input is the control input, which applies - * torque to the arm. The `d` input is an impulsive - * disturbance applied to the pendulum, as if you were - * to tap it with a hard object. When an input is - * received on `d`, its value provides an instantaneous - * increment or decrement to `d_theta`. - * Notice that no output is produced when an input - * is received on `d` unless that input is simultaneous - * with the sampling period. The effect of the disturbance - * will not be seen on the outputs until the next sample, - * as would usually be the case for digital controller. + * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, “Realistic simulations of embedded control + * systems,” IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. + * + * The Ptolemy II model is more accurate because it uses an RK-45 solver, but this is adequate for + * many purposes. + * + * This outputs its state every `sample_period`. It updates the state before outputting it using the + * most recently received control input. + * + * The `theta` output is the angle of the pendulum, which is 0 when the pendulum is pointing + * straight up, and `d_theta` is its initial angular velocity. The `phi` output is the angle of the + * horizontal arm and `d_phi` is its angular velocity. + * + * The `u` input is the control input, which applies torque to the arm. The `d` input is an + * impulsive disturbance applied to the pendulum, as if you were to tap it with a hard object. When + * an input is received on `d`, its value provides an instantaneous increment or decrement to + * `d_theta`. Notice that no output is produced when an input is received on `d` unless that input + * is simultaneous with the sampling period. The effect of the disturbance will not be seen on the + * outputs until the next sample, as would usually be the case for digital controller. * * @author Edward A. Lee */ reactor PendulumSimulation( - initial_theta:double(-3.14159), // Initial pendulum angle. - sample_period:time(5 msec), // Sample period. - g:double(9.81), // Acceleration of gravity. - alpha:double(0.00260569), - beta:double(0.05165675), - gamma:double(9.7055e-4), - epsilon:double(0.08103060) -){ + initial_theta: double = -3.14159, // Initial pendulum angle. + sample_period: time = 5 msec, // Sample period. + g: double = 9.81, // Acceleration of gravity. + alpha: double = 0.00260569, + beta: double = 0.05165675, + gamma: double = 9.7055e-4, + epsilon: double = 0.08103060) { preamble {= #include =} - input u:double; // Control input. - input d:double; // Impulsive disturbance - - output theta:double; // Pendulum angle. - output d_theta:double; // Pendulum angular velocity. - output phi:double; // Arm angle. - output d_phi:double; // Arm angular velocity. - - state x:double[4](0.0, 0.0, 0.0, 0.0); // [theta, d_theta, phi, d_phi] - state first:bool(true); - state latest_u:double(0.0); - - timer t(0, sample_period); - + input u: double // Control input. + input d: double // Impulsive disturbance + + output theta: double // Pendulum angle. + output d_theta: double // Pendulum angular velocity. + output phi: double // Arm angle. + output d_phi: double // Arm angular velocity. + + state x: double[4] = {0.0, 0.0, 0.0, 0.0} // [theta, d_theta, phi, d_phi] + state first: bool = true + state latest_u: double = 0.0 + + timer t(0, sample_period) + reaction(t) -> theta, d_theta, phi, d_phi {= if (!self->first) { // Update the state. @@ -95,17 +85,17 @@ reactor PendulumSimulation( * cos(self->x[0]) * self->g * self->latest_u - + + + ( - self->alpha - * self->beta + self->alpha + * self->beta + pow(self->alpha * sin(self->x[0]), 2.0) ) * self->epsilon / self->alpha * sin(self->x[0]) ); double x2_dot = self->x[3]; double x3_dot = (1.0 / ( - self->alpha * self->beta + self->alpha * self->beta + pow(self->alpha * sin(self->x[0]), 2.0) - pow(self->gamma * cos(self->x[0]), 2.0) )) * ( @@ -151,6 +141,7 @@ reactor PendulumSimulation( lf_set(phi, self->x[2]); lf_set(d_phi, self->x[3]); =} + reaction(d) {= // NOTE: If the disturbance is coincident with a sample, // then it won't have any effect on theta until the next sample. @@ -164,7 +155,6 @@ reactor PendulumSimulation( // are implemented in C. self->x[1] += d->value; =} - reaction(u) {= - self->latest_u = u->value; - =} + + reaction(u) {= self->latest_u = u->value; =} } diff --git a/C/src/modal_models/FurutaPendulum/Print.lf b/C/src/modal_models/FurutaPendulum/Print.lf index 85895b84..0c36d602 100644 --- a/C/src/modal_models/FurutaPendulum/Print.lf +++ b/C/src/modal_models/FurutaPendulum/Print.lf @@ -1,21 +1,19 @@ -/** - * A utility reactor to print the pendulum state into a csv file. - */ -target C; +/** A utility reactor to print the pendulum state into a csv file. */ +target C preamble {= #include #define PI 3.14159265 =} -reactor Print(filename:string("pendulum.csv")) { - input control:double; - input modeID:double; - input energy:double; - input theta:double; - input phi:double; +reactor Print(filename: string = "pendulum.csv") { + input control: double + input modeID: double + input energy: double + input theta: double + input phi: double - state file:FILE*({=NULL=}); + state file: FILE* = {= NULL =} reaction(startup) {= self->file = fopen(self->filename, "w"); @@ -39,7 +37,5 @@ reactor Print(filename:string("pendulum.csv")) { ); =} - reaction(shutdown) {= - fclose(self->file); - =} + reaction(shutdown) {= fclose(self->file); =} } diff --git a/C/src/mqtt/MQTTDistributed.lf b/C/src/mqtt/MQTTDistributed.lf index 88b017fd..6152b317 100644 --- a/C/src/mqtt/MQTTDistributed.lf +++ b/C/src/mqtt/MQTTDistributed.lf @@ -1,63 +1,52 @@ /** - * This is a federated LF program consisting of two unconnected federates - * that communicate via MQTT. The publisher has `include_timestamp` set - * to `true`, and the subscriber has `use_physical_time` set to `false`. - * Like `MQTTLogical`, there is no other activity - * in this program, so the subscriber's timestamps will deterministically - * match those of the publisher. Unlike `MQTTLogical`, however, the microstep - * will be zero at the subscriber end. Also, the tags will be deterministic - * at the receiving end regardless of the communication latency because the - * receiving federate has no reason to advance its logical time unless it - * receives an MQTT subscription message. You can change the `use_physical_time` - * parameter of the `MQTTSubscriber` to `true` to get a (nondeterministic) - * physical connection, similar to `MQTTPhysical`. - * - * The code generator produces three programs, bin/MQTTDistributed_RTI, - * bin/MQTTDistributed_source, and bin/MQTTDistributed_destination, - * plus a script bin/MQTTDistributed that runs all three. - * - * Since the source and destination are running in the same - * executable, there is no clock synchronization error. - * + * This is a federated LF program consisting of two unconnected federates that communicate via MQTT. + * The publisher has `include_timestamp` set to `true`, and the subscriber has `use_physical_time` + * set to `false`. Like `MQTTLogical`, there is no other activity in this program, so the + * subscriber's timestamps will deterministically match those of the publisher. Unlike + * `MQTTLogical`, however, the microstep will be zero at the subscriber end. Also, the tags will be + * deterministic at the receiving end regardless of the communication latency because the receiving + * federate has no reason to advance its logical time unless it receives an MQTT subscription + * message. You can change the `use_physical_time` parameter of the `MQTTSubscriber` to `true` to + * get a (nondeterministic) physical connection, similar to `MQTTPhysical`. + * + * The code generator produces three programs, bin/MQTTDistributed_RTI, bin/MQTTDistributed_source, + * and bin/MQTTDistributed_destination, plus a script bin/MQTTDistributed that runs all three. + * + * Since the source and destination are running in the same executable, there is no clock + * synchronization error. + * * See README.md for prerequisites and further information. - * + * * @author Ravi Akella * @author Edward A. Lee */ target C { - cmake-include: [ - "include/paho-extension.cmake", - "include/mosquitto-extension.cmake"], + cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 10 secs, - coordination: centralized, -}; + coordination: centralized +} -import MQTTPublisher from "lib/MQTTPublisher.lf"; -import MQTTSubscriber from "lib/MQTTSubscriber.lf"; -import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf"; +import MQTTPublisher from "lib/MQTTPublisher.lf" +import MQTTSubscriber from "lib/MQTTSubscriber.lf" +import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf" reactor Source { - msg = new MessageGenerator(root = "Hello World"); - pub = new MQTTPublisher( - topic = "my/test", - address = "tcp://localhost:1883", - include_timestamp = true - ); - msg.message->pub.in; + msg = new MessageGenerator(root = "Hello World") + pub = new MQTTPublisher(topic="my/test", address="tcp://localhost:1883", include_timestamp=true) + msg.message -> pub.in } reactor Destination { sub = new MQTTSubscriber( - address = "tcp://localhost:1883", - topic = "my/test", - use_physical_time = false, - offset = 0 sec - ); - dsp = new PrintMessage(); - sub.message->dsp.message; + address="tcp://localhost:1883", + topic="my/test", + use_physical_time=false, + offset = 0 sec) + dsp = new PrintMessage() + sub.message -> dsp.message } federated reactor { - source = new Source(); - destination = new Destination(); + source = new Source() + destination = new Destination() } diff --git a/C/src/mqtt/MQTTDistributedActivity.lf b/C/src/mqtt/MQTTDistributedActivity.lf index cb17816c..525b3973 100644 --- a/C/src/mqtt/MQTTDistributedActivity.lf +++ b/C/src/mqtt/MQTTDistributedActivity.lf @@ -1,48 +1,41 @@ /** - * This is a federated LF program consisting of two unconnected federates - * that communicate via MQTT, but where the destination reactor has activity - * that interferes with its ability to use the incoming timestamps from the - * publisher. This program will print a warning each time it receives a - * message. To get rid of the warnings, you can set the `use_physical_time` - * parameter of the `MQTTSubscriber` to true, and then it will not use - * the incoming timestamps (except to measure apparent latency). - * + * This is a federated LF program consisting of two unconnected federates that communicate via MQTT, + * but where the destination reactor has activity that interferes with its ability to use the + * incoming timestamps from the publisher. This program will print a warning each time it receives a + * message. To get rid of the warnings, you can set the `use_physical_time` parameter of the + * `MQTTSubscriber` to true, and then it will not use the incoming timestamps (except to measure + * apparent latency). + * * See README.md for prerequisites and further information. - * + * * @author Edward A. Lee */ target C { - cmake-include: [ - "include/paho-extension.cmake", - "include/mosquitto-extension.cmake"], + cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 10 secs, - coordination: centralized, -}; + coordination: centralized +} -import MQTTPublisher from "lib/MQTTPublisher.lf"; -import MQTTSubscriber from "lib/MQTTSubscriber.lf"; -import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf"; +import MQTTPublisher from "lib/MQTTPublisher.lf" +import MQTTSubscriber from "lib/MQTTSubscriber.lf" +import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf" reactor Source { - msg = new MessageGenerator(root = "Hello World"); - pub = new MQTTPublisher( - topic = "my/test", - address = "tcp://localhost:1883", - include_timestamp = true - ); - msg.message->pub.in; + msg = new MessageGenerator(root = "Hello World") + pub = new MQTTPublisher(topic="my/test", address="tcp://localhost:1883", include_timestamp=true) + msg.message -> pub.in } reactor Destination { timer t(1001 ms, 1 s) sub = new MQTTSubscriber( - address = "tcp://localhost:1883", - topic = "my/test", - use_physical_time = false, - offset = 0 sec - ); - dsp = new PrintMessage(); - sub.message->dsp.message; + address="tcp://localhost:1883", + topic="my/test", + use_physical_time=false, + offset = 0 sec) + dsp = new PrintMessage() + sub.message -> dsp.message + reaction(t) {= tag_t tag = lf_tag(); lf_print("Destination: Activity at " PRINTF_TAG, @@ -52,6 +45,6 @@ reactor Destination { } federated reactor { - source = new Source(); - destination = new Destination(); + source = new Source() + destination = new Destination() } diff --git a/C/src/mqtt/MQTTLegacy.lf b/C/src/mqtt/MQTTLegacy.lf index 1c910982..209e70eb 100644 --- a/C/src/mqtt/MQTTLegacy.lf +++ b/C/src/mqtt/MQTTLegacy.lf @@ -1,68 +1,55 @@ /** - * This program illustrates how to interface to a legacy MQTT - * service that has no connection with Lingua Franca. - * The "Publisher" reactor publishes messages every 5s - * on topic "legacy" that any other MQTT application can subscribe - * to. For example, you can subscribe to these messages using + * This program illustrates how to interface to a legacy MQTT service that has no connection with + * Lingua Franca. The "Publisher" reactor publishes messages every 5s on topic "legacy" that any + * other MQTT application can subscribe to. For example, you can subscribe to these messages using * the command-line utility (in another window): - * - * mosquitto_sub -t 'legacy' - * - * You can publish your own messages on this topic using any - * MQTT publisher, such as the command line utility: - * - * mosquitto_pub -t 'legacy' -m '******* My own message!' - * - * This is a federated program, the publisher and subscriber run - * in separate programs. This would work pretty much the same - * way, however, as an unfederated program. To run as an - * unfederated program, add to cmake-include the following file: - * - * "include/net_utils.cmake" - * + * + * mosquitto_sub -t 'legacy' + * + * You can publish your own messages on this topic using any MQTT publisher, such as the command + * line utility: + * + * mosquitto_pub -t 'legacy' -m '******* My own message!' + * + * This is a federated program, the publisher and subscriber run in separate programs. This would + * work pretty much the same way, however, as an unfederated program. To run as an unfederated + * program, add to cmake-include the following file: + * + * "include/net_utils.cmake" + * * and change the `federated` keyword to `main`. - * + * * See README.md for prerequisites and further information. * * @author Edward A. Lee */ target C { - cmake-include: [ - "include/paho-extension.cmake", - "include/mosquitto-extension.cmake"], + cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 5 min, - coordination: centralized, -}; + coordination: centralized +} -import MQTTPublisher from "lib/MQTTPublisher.lf"; -import MQTTSubscriber from "lib/MQTTSubscriber.lf"; -import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf"; +import MQTTPublisher from "lib/MQTTPublisher.lf" +import MQTTSubscriber from "lib/MQTTSubscriber.lf" +import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf" reactor Publisher { - msg = new MessageGenerator( - root = "Legacy Message", - period = 5 sec - ); - pub = new MQTTPublisher( - topic = "legacy", - address = "tcp://localhost:1883", - include_timestamp = false - ); - msg.message->pub.in; + msg = new MessageGenerator(root = "Legacy Message", period = 5 sec) + pub = new MQTTPublisher(topic="legacy", address="tcp://localhost:1883", include_timestamp=false) + msg.message -> pub.in } reactor Subscriber { sub = new MQTTSubscriber( - address = "tcp://localhost:1883", - topic = "legacy", - use_physical_time = true, - offset = 0 sec - ); - dsp = new PrintMessage(); - sub.message->dsp.message; + address="tcp://localhost:1883", + topic="legacy", + use_physical_time=true, + offset = 0 sec) + dsp = new PrintMessage() + sub.message -> dsp.message } federated reactor { - source = new Publisher(); - destination = new Subscriber(); + source = new Publisher() + destination = new Subscriber() } diff --git a/C/src/mqtt/MQTTLogical.lf b/C/src/mqtt/MQTTLogical.lf index 55ba4ffc..3a6afb56 100644 --- a/C/src/mqtt/MQTTLogical.lf +++ b/C/src/mqtt/MQTTLogical.lf @@ -1,47 +1,39 @@ /** - * This program periodically publishes on a topic and, in a different - * part of the program, subscribes to the same topic. The publisher has - * `include_timestamp` set to `true`, and the subscriber has `use_physical_time` - * set to `false`. The program has no other activity, so as long as - * the time between publishing of events is larger than the total - * latency through the MQTT broker, the subscriber will see messages - * one microstep later than the publisher. By setting a positive `offset` - * at the subscriber, you can increase the logical time of the received - * message and get a zero microstep. This gives behavior similar to an - * LF connection with an `after` delay. - * + * This program periodically publishes on a topic and, in a different part of the program, + * subscribes to the same topic. The publisher has `include_timestamp` set to `true`, and the + * subscriber has `use_physical_time` set to `false`. The program has no other activity, so as long + * as the time between publishing of events is larger than the total latency through the MQTT + * broker, the subscriber will see messages one microstep later than the publisher. By setting a + * positive `offset` at the subscriber, you can increase the logical time of the received message + * and get a zero microstep. This gives behavior similar to an LF connection with an `after` delay. + * * See README.md for prerequisites and further information. - * + * * @author Ravi Akella * @author Edward A. Lee */ target C { cmake-include: [ - "include/paho-extension.cmake", // For #include "MQTTClient.h" - "include/net_utils.cmake" // For encode_int64() - ], - timeout: 10 secs, -}; - -import MQTTPublisher from "lib/MQTTPublisher.lf"; -import MQTTSubscriber from "lib/MQTTSubscriber.lf"; -import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf"; + "include/paho-extension.cmake", // For #include "MQTTClient.h" + // For encode_int64() + "include/net_utils.cmake"], + timeout: 10 secs +} + +import MQTTPublisher from "lib/MQTTPublisher.lf" +import MQTTSubscriber from "lib/MQTTSubscriber.lf" +import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf" main reactor { - pub = new MQTTPublisher( - topic = "my/test", - address = "tcp://localhost:1883", - include_timestamp = true - ); - msg = new MessageGenerator(root = "Hello World"); - msg.message->pub.in; - + pub = new MQTTPublisher(topic="my/test", address="tcp://localhost:1883", include_timestamp=true) + msg = new MessageGenerator(root = "Hello World") + msg.message -> pub.in + sub = new MQTTSubscriber( - address = "tcp://localhost:1883", - topic = "my/test", - use_physical_time = false, - offset = 0 - ); - dsp = new PrintMessage(); - sub.message->dsp.message; + address="tcp://localhost:1883", + topic="my/test", + use_physical_time=false, + offset=0) + dsp = new PrintMessage() + sub.message -> dsp.message } diff --git a/C/src/mqtt/MQTTPhysical.lf b/C/src/mqtt/MQTTPhysical.lf index f430c621..df455ab7 100644 --- a/C/src/mqtt/MQTTPhysical.lf +++ b/C/src/mqtt/MQTTPhysical.lf @@ -1,43 +1,38 @@ /** - * This program periodically publishes on a topic and, in a different - * part of the program, subscribes to the same topic. The timestamp at the - * receiving end will be nondeterministically determined from the local - * physical clock. The difference between the publisher's logical time - * and the subscriber's logical time is a reasonable measure of the - * latency through the MQTT broker. This gives behavior similar to an - * LF [physical connection](https://www.lf-lang.org/docs/handbook/composing-reactors?target=c#physical-connections). - * + * This program periodically publishes on a topic and, in a different part of the program, + * subscribes to the same topic. The timestamp at the receiving end will be nondeterministically + * determined from the local physical clock. The difference between the publisher's logical time and + * the subscriber's logical time is a reasonable measure of the latency through the MQTT broker. + * This gives behavior similar to an LF [physical + * connection](https://www.lf-lang.org/docs/handbook/composing-reactors?target=c#physical-connections). + * * See README.md for prerequisites and further information. - * + * * @author Ravi Akella * @author Edward A. Lee */ target C { cmake-include: [ - "include/paho-extension.cmake", // For #include "MQTTClient.h" - "include/net_utils.cmake" // For encode_int64() - ], - timeout: 10 secs, -}; - -import MQTTPublisher from "lib/MQTTPublisher.lf"; -import MQTTSubscriber from "lib/MQTTSubscriber.lf"; -import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf"; + "include/paho-extension.cmake", // For #include "MQTTClient.h" + // For encode_int64() + "include/net_utils.cmake"], + timeout: 10 secs +} + +import MQTTPublisher from "lib/MQTTPublisher.lf" +import MQTTSubscriber from "lib/MQTTSubscriber.lf" +import MessageGenerator, PrintMessage from "lib/MQTTTestReactors.lf" main reactor { - pub = new MQTTPublisher( - topic = "my/test", - address = "tcp://localhost:1883" - ); - msg = new MessageGenerator(root = "Hello World"); - msg.message->pub.in; - + pub = new MQTTPublisher(topic="my/test", address="tcp://localhost:1883") + msg = new MessageGenerator(root = "Hello World") + msg.message -> pub.in + sub = new MQTTSubscriber( - address = "tcp://localhost:1883", - topic = "my/test", - use_physical_time = true, - offset = 0 - ); - dsp = new PrintMessage(); - sub.message->dsp.message; + address="tcp://localhost:1883", + topic="my/test", + use_physical_time=true, + offset=0) + dsp = new PrintMessage() + sub.message -> dsp.message } diff --git a/C/src/mqtt/lib/MQTTPublisher.lf b/C/src/mqtt/lib/MQTTPublisher.lf index 7f9cdbbf..4a305fae 100644 --- a/C/src/mqtt/lib/MQTTPublisher.lf +++ b/C/src/mqtt/lib/MQTTPublisher.lf @@ -1,30 +1,25 @@ target C + /** - * Reactor that publishes strings (or arbitrary byte arrays cast - * to `char*`) to a specified MQTT topic. - * - * This publisher ensures in-order delivery messages to - * subscribers. If an attempt is made to send a message before - * the delivery of the previous message has completed, then the reaction - * that sends the message (the reaction to an input 'in') will - * block until the previous delivery has completed. - * - * If `include_timestamp` is true (the default is `false`), - * then two things happen: - * - * 1. The publisher ensures that the message is null terminated by - * adding a null terminator if needed. This ensures that the message - * can be treated as a string at the receiving end. - * 2. The publisher appends to the end of the message the current logical - * time at which the publishing occurs. - * - * This can be useful if the receiving end will be an instance - * of `MQTTSubscriber` in another Lingua Franca program. - * Note that `include_timestamp` *must* be true if an `MQTTSubcriber` - * that subscribes to this topic has its `use_physical_time` parameter - * set to false (its default is `true`). Otherwise, the subscriber - * will issue a warning. - * + * Reactor that publishes strings (or arbitrary byte arrays cast to `char*`) to a specified MQTT + * topic. + * + * This publisher ensures in-order delivery messages to subscribers. If an attempt is made to send a + * message before the delivery of the previous message has completed, then the reaction that sends + * the message (the reaction to an input 'in') will block until the previous delivery has completed. + * + * If `include_timestamp` is true (the default is `false`), then two things happen: + * + * 1. The publisher ensures that the message is null terminated by adding a null terminator if + * needed. This ensures that the message can be treated as a string at the receiving end. 2. The + * publisher appends to the end of the message the current logical time at which the publishing + * occurs. + * + * This can be useful if the receiving end will be an instance of `MQTTSubscriber` in another Lingua + * Franca program. Note that `include_timestamp` *must* be true if an `MQTTSubcriber` that + * subscribes to this topic has its `use_physical_time` parameter set to false (its default is + * `true`). Otherwise, the subscriber will issue a warning. + * * @param address The IP address of the MQTT broker. * @param timeout Timeout for completion of message sending in milliseconds. * @see MQTTSubscriber. @@ -32,31 +27,30 @@ target C * @author Ravi Akella * @author Edward A. Lee */ -reactor MQTTPublisher ( - topic:string("DefaultTopic"), - address:string("tcp://localhost:1883"), - include_timestamp:bool(false), - timeout:time(10 sec) -) { +reactor MQTTPublisher( + topic: string = "DefaultTopic", + address: string = "tcp://localhost:1883", + include_timestamp: bool = false, + timeout: time = 10 sec) { preamble {= #include "MQTTClient.h" #include "core/federated/net_util.h" - + // Count of instances of this reactor so that unique client IDs are generated. static size_t _lf_MQTTPublisher_count = 0; - + // Connection options for the client. // Making this global means that all instances of this reactor have // the same connection options. MQTTClient_connectOptions pub_connect_options = MQTTClient_connectOptions_initializer; - + // Struct type used to keep track of messages in flight between reactions. typedef struct inflight_t { bool message_in_flight; MQTTClient_deliveryToken delivery_token; char* message; } inflight_t; - + // Callback invoked once delivery is complete. void pub_delivered(void *inflight, MQTTClient_deliveryToken dt) { LF_PRINT_LOG("MQTTPublisher: Message with token value %d delivery confirmed\n", dt); @@ -70,36 +64,36 @@ reactor MQTTPublisher ( lf_print_error("\nMQTTPublisher: Connection lost. Cause: %s\n", cause); } =} - + /** - * Input type char* instead of string is used for dynamically - * allocated character arrays (as opposed to static constant strings). + * Input type char* instead of string is used for dynamically allocated character arrays (as + * opposed to static constant strings). */ - input in:char*; - + input in: char* + /** State variable that keeps track of a message in flight. */ - state inflight:inflight_t({={false, 0, NULL}=}); - + state inflight: inflight_t = {= {false, 0, NULL} =} + /** Client ID. This is automatically generated. */ - state clientID:char*({= NULL =}); - + state clientID: char* = {= NULL =} + /** The client object. */ - state client:MQTTClient({=NULL=}); - + state client: MQTTClient = {= NULL =} + /** The message object. */ - state mqtt_msg:MQTTClient_message({=MQTTClient_message_initializer=}); - + state mqtt_msg: MQTTClient_message = {= MQTTClient_message_initializer =} + /** Connect to the broker. Exit if this fails. */ - reaction(startup){= + reaction(startup) {= // In case there are multiple instances of this or the subscriber, enter // a critical section. The Paho MQTT functions are not thread safe. lf_critical_section_enter(); - + // Create a unique ID. if (asprintf(&self->clientID, "LF_MQTTPublisher_%zu", _lf_MQTTPublisher_count++) < 0) { lf_print_error_and_exit("MQTTPublisher: Failed to create client ID."); } - + MQTTClient_create(&self->client, self->address, self->clientID, MQTTCLIENT_PERSISTENCE_NONE, NULL); pub_connect_options.keepAliveInterval = 20; pub_connect_options.cleansession = 1; @@ -111,31 +105,30 @@ reactor MQTTPublisher ( // Second argument is a pointer to context that will be passed to pub_delivered, // which in this case is a pointer to the inflight state variable. MQTTClient_setCallbacks(self->client, &self->inflight, pub_connection_lost, NULL, pub_delivered); - + // Connect to the broker. int rc; // response code. if ((rc = MQTTClient_connect(self->client, &pub_connect_options)) != MQTTCLIENT_SUCCESS) { lf_print_error_and_exit("MQTTPublisher: Failed to connect to MQTT broker.\n" "Perhaps one is not running? Return code: %d", rc); } - + lf_critical_section_exit(); - + LF_PRINT_LOG("MQTTPublisher: connected to broker."); =} - + /** - * React to an input by sending a message with the value of the input as the payload. - * If delivery has not yet completed for a previously sent message, then wait for - * it to complete before proceeding (blocking this reaction). - * This copies the message from the input into a buffer, so the input can - * freed upon return from this reaction. + * React to an input by sending a message with the value of the input as the payload. If delivery + * has not yet completed for a previously sent message, then wait for it to complete before + * proceeding (blocking this reaction). This copies the message from the input into a buffer, so + * the input can freed upon return from this reaction. */ reaction(in) {= // In case there are multiple instances of this or the subscriber, enter // a critical section. The Paho MQTT functions are not thread safe. lf_critical_section_enter(); - + if(self->inflight.message_in_flight) { // Wait for message delivery to be complete. LF_PRINT_LOG("MQTTPublisher: Waiting for confirmation of publication of previous message"); @@ -150,7 +143,7 @@ reactor MQTTPublisher ( } LF_PRINT_LOG("MQTTPublisher: Publishing message: %s", in->value); LF_PRINT_LOG("MQTTPublisher: on topic '%s' for publisher with ClientID: %s", self->topic, self->clientID); - + // Allocate memory for a copy of the message. // The default length is just the length of the incoming message. int length = in->length; @@ -187,25 +180,25 @@ reactor MQTTPublisher ( } self->mqtt_msg.payload = self->inflight.message; self->mqtt_msg.payloadlen = length; - + // QoS 2 means that the message will be delivered exactly once. self->mqtt_msg.qos = 2; - + // Retained messages are held by the server and sent to future new subscribers. // Specify that this message should not be retained. // It will be sent only to subscribers currently subscribed. self->mqtt_msg.retained = 0; - + MQTTClient_publishMessage(self->client, self->topic, &self->mqtt_msg, &self->inflight.delivery_token); self->inflight.message_in_flight = true; - + lf_critical_section_exit(); - + // It is not clear why the following is needed, but the message // does not go out until the next invocation without it. - MQTTClient_yield(); + MQTTClient_yield(); =} - + /** Disconnect the client. */ reaction(shutdown) {= LF_PRINT_LOG("MQTTPublisher: Client ID %s disconnecting.", self->clientID); diff --git a/C/src/mqtt/lib/MQTTSubscriber.lf b/C/src/mqtt/lib/MQTTSubscriber.lf index a0f205bd..dce172cf 100644 --- a/C/src/mqtt/lib/MQTTSubscriber.lf +++ b/C/src/mqtt/lib/MQTTSubscriber.lf @@ -1,42 +1,34 @@ target C + /** - * Reactor that subscribes to a specified MQTT topic on which - * string messages are published. The timestamp of the output - * will depend on the use_physical_time parameter and (if present) - * the timestamp carried by the incoming message. - * - * If `use_physical_time` is `tru`e (the default), then this reactor - * uses the current physical time when the subscription notification - * arrives, plus the `offset`, as the desired output timestamp. - * If the incoming message is carrying a timestamp (the publisher - * is an instance of `MQTTPublisher` with `include_timestamp` set - * to `true), then this reactor measures the *apparent latency* - * (the physical time of arrival minus the timestamp in the message). - * At shutdown, this reactor will report that maximum and average - * apparent latencies. - * - * If `use_physical_time` is `false`, then this reactor - * extracts the publisher's timestamp from the message and adds the - * specified offset to get the desired output timestamp. If there - * is no timestamp on the incoming message, then this prints a - * warning and uses physical time. If the received timestamp equals - * current logical time, then a microstep is added. If the desired - * output timestamp is in the past, then a warning will be printed and - * the tag of the message will be one microstep later than - * the current tag when it arrives. - * - * Note that if the publisher and subscriber are both Lingua Franca - * programs, then the communication behaves a physical connection - * if `use_physical_time` is true (the default). The offset is + * Reactor that subscribes to a specified MQTT topic on which string messages are published. The + * timestamp of the output will depend on the use_physical_time parameter and (if present) the + * timestamp carried by the incoming message. + * + * If `use_physical_time` is `tru`e (the default), then this reactor uses the current physical time + * when the subscription notification arrives, plus the `offset`, as the desired output timestamp. + * If the incoming message is carrying a timestamp (the publisher is an instance of `MQTTPublisher` + * with `include_timestamp` set to `true), then this reactor measures the *apparent latency* (the + * physical time of arrival minus the timestamp in the message). At shutdown, this reactor will + * report that maximum and average apparent latencies. + * + * If `use_physical_time` is `false`, then this reactor extracts the publisher's timestamp from the + * message and adds the specified offset to get the desired output timestamp. If there is no + * timestamp on the incoming message, then this prints a warning and uses physical time. If the + * received timestamp equals current logical time, then a microstep is added. If the desired output + * timestamp is in the past, then a warning will be printed and the tag of the message will be one + * microstep later than the current tag when it arrives. + * + * Note that if the publisher and subscriber are both Lingua Franca programs, then the communication + * behaves a physical connection if `use_physical_time` is true (the default). The offset is * equivalent to an `after` delay. - * - * If `use_physical_time` is false, then the communication attempts - * to behave like a logical connection, but this is not always possible. - * Logical time can advance between when the publisher launches a message, - * sending it to the MQTT broker, and when the subscriber receives it. - * This may make it impossible to match the desired timestamp and will - * result in warning messages being printed. - * + * + * If `use_physical_time` is false, then the communication attempts to behave like a logical + * connection, but this is not always possible. Logical time can advance between when the publisher + * launches a message, sending it to the MQTT broker, and when the subscriber receives it. This may + * make it impossible to match the desired timestamp and will result in warning messages being + * printed. + * * @param address The IP address of the MQTT broker. * @param topic The topic name to which to subscribe. * @param use_physical_time If true, then use physical time (the default). @@ -46,22 +38,21 @@ target C * @author Ravi Akella * @author Edward A. Lee */ -reactor MQTTSubscriber ( - address:string("tcp://localhost:1883"), - topic:string("DefaultTopic"), - use_physical_time:bool(true), - offset:time(0) -) { +reactor MQTTSubscriber( + address: string = "tcp://localhost:1883", + topic: string = "DefaultTopic", + use_physical_time: bool = true, + offset: time = 0) { preamble {= #include "MQTTClient.h" #include "core/federated/net_util.h" - + // Fix the QoS to indicate that the message will be delivered reliably exactly once. #define QOS 2 // Count of instances of this reactor so that unique client IDs are generated. static size_t _lf_MQTTSubscriber_count = 0; - + typedef struct MQTTSubscriber_info_t { void* logical_action; interval_t offset; @@ -70,12 +61,12 @@ reactor MQTTSubscriber ( interval_t max_latency; size_t count; } MQTTSubscriber_info_t; - + // Connection options for the client. // Making this global means that all instances of this reactor have // the same connection options. MQTTClient_connectOptions sub_connect_options = MQTTClient_connectOptions_initializer; - + // Callback function invoked by MQTT when a message arrives. int message_arrived( void *info, @@ -89,17 +80,17 @@ reactor MQTTSubscriber ( LF_PRINT_LOG( "MQTTSubscriber: Message arrived on topic %s: %s", topic_name, (char*)message->payload ); - + MQTTSubscriber_info_t* my_info = (MQTTSubscriber_info_t*)info; - + // Enter a critical section so that logical time does not elapse while // we calculate the delay to the logical time for the message. lf_critical_section_enter(); - + interval_t delay; instant_t current_time = lf_time_logical(); interval_t offset = my_info->offset; - + // Extract the publisher's timestamp from the message, if it is present. if ( // Is the string null terminated? @@ -108,15 +99,15 @@ reactor MQTTSubscriber ( && memcmp("LFts", &message->payload[message->payloadlen - sizeof(instant_t) - 4], 4) == 0 ) { my_info->count++; - + instant_t timestamp = extract_int64( (unsigned char*)message->payload + message->payloadlen - sizeof(instant_t) ); instant_t physical_time = lf_time_physical(); - + interval_t latency = physical_time - timestamp; my_info->latencies += latency; - + if (latency > my_info->max_latency) { my_info->max_latency = latency; } @@ -153,52 +144,50 @@ reactor MQTTSubscriber ( message->payloadlen ); } - + LF_PRINT_LOG( "MQTTSubscriber: Received message. Timestamp will be " PRINTF_TIME " ahead of current (elapsed) time, " PRINTF_TIME, delay, current_time - lf_time_start() ); - + lf_critical_section_exit(); // MQTTClient_freeMessage() also frees the memory allocated to the payload, // which is why we have to copy the message here. MQTTClient_freeMessage(&message); MQTTClient_free(topic_name); - + // Return true to indicate that the message has been successfully handled. return 1; } - + /** Callback invoked if the connection is lost. */ void sub_connection_lost(void *info, char *cause) { lf_print_warning("MQTTSubscriber: Connection lost. Cause: %s", cause); } =} - + /** - * Output for sending the incoming MQTT message. - * Use type char* rather than string because it is not - * a static string, but rather dynamically allocated memory. + * Output for sending the incoming MQTT message. Use type char* rather than string because it is + * not a static string, but rather dynamically allocated memory. */ - output message:char*; + output message: char* /** - * Action that is triggered when there is an incoming MQTT message. - * Use a logical action here so that the callback function can - * precisely control timestamp of the received message. + * Action that is triggered when there is an incoming MQTT message. Use a logical action here so + * that the callback function can precisely control timestamp of the received message. */ - logical action act:char*; - + logical action act: char* + /** Client ID. This is automatically generated. */ - state clientID:char*({= NULL =}); + state clientID: char* = {= NULL =} /** State variable storing the MQTT client created for each instance of this reactor. */ - state client:MQTTClient({=NULL=}); - + state client: MQTTClient = {= NULL =} + /** Struct containing the action and offset. */ - state info:MQTTSubscriber_info_t({= {NULL, 0LL, false, 0LL, 0LL, 0} =}) - + state info: MQTTSubscriber_info_t = {= {NULL, 0LL, false, 0LL, 0LL, 0} =} + reaction(startup) -> act {= int rc; // response code. @@ -220,17 +209,17 @@ reactor MQTTSubscriber ( sub_connect_options.keepAliveInterval = 20; sub_connect_options.cleansession = 1; - + self->info.logical_action = act; self->info.offset = self->offset; self->info.use_physical_time = self->use_physical_time; - + // Set up callback functions. // Last argument should be a pointer to a function to // handle notification of delivery of a sent message. // But this reactor isn't sending any messages. MQTTClient_setCallbacks(self->client, &self->info, sub_connection_lost, message_arrived, NULL); - + // Connect to the broker. rc = MQTTClient_connect(self->client, &sub_connect_options); if (rc != MQTTCLIENT_SUCCESS) { @@ -238,19 +227,19 @@ reactor MQTTSubscriber ( "MQTTSubscriber: Failed to connect to MQTT broker.\n" "Perhaps one is not running? Return code: %d\n", rc); } - + MQTTClient_subscribe(self->client, self->topic, QOS); - + lf_critical_section_exit(); =} - + reaction(act) -> message {= // The action contains a token that we can just forward. // The allocated memory will be freed when the token's reference count hits 0. // Note that this token will still contain the publisher's timestamp. lf_set_token(message, act->token); =} - + reaction(shutdown) {= if (self->info.count > 0) { lf_print( diff --git a/C/src/mqtt/lib/MQTTTestReactors.lf b/C/src/mqtt/lib/MQTTTestReactors.lf index 0aed0127..754bfbf0 100644 --- a/C/src/mqtt/lib/MQTTTestReactors.lf +++ b/C/src/mqtt/lib/MQTTTestReactors.lf @@ -1,26 +1,26 @@ /** * Reactors used to test MQTT publishing and subscribing. - * + * * @author Ravi Akella * @author Edward A. Lee */ target C - + /** - * Reactor that generates a sequence of messages, one per second. - * The message will be a string consisting of a root string followed - * by a count. + * Reactor that generates a sequence of messages, one per second. The message will be a string + * consisting of a root string followed by a count. * @param root The root string. * @output message The message. */ -reactor MessageGenerator(root:string(""), period:time(1 sec)) { +reactor MessageGenerator(root: string = "", period: time = 1 sec) { // Output type char* instead of string is used for dynamically // allocated character arrays (as opposed to static constant strings). - output message:char*; - state count:int(1); + output message: char* + state count: int = 1 // Send first message after 1 sec so that the startup reactions // do not factor into the transport time measurement on the first message. - timer t(1 sec, period); + timer t(1 sec, period) + reaction(t) -> message {= // With NULL, 0 arguments, snprintf tells us how many bytes are needed. // Add one for the null terminator. @@ -36,14 +36,15 @@ reactor MessageGenerator(root:string(""), period:time(1 sec)) { ); =} } - + /** * Reactor that prints an incoming string. * @param prefix A prefix for the message. * @input message The message. */ reactor PrintMessage { - input message:char*; + input message: char* + reaction(message) {= tag_t tag = lf_tag(); lf_print("PrintMessage: At (elapsed) time " PRINTF_TAG ", subscriber receives: %s", diff --git a/C/src/patterns/Chain_01_SendReceive.lf b/C/src/patterns/Chain_01_SendReceive.lf index 6395ad08..c5d53102 100644 --- a/C/src/patterns/Chain_01_SendReceive.lf +++ b/C/src/patterns/Chain_01_SendReceive.lf @@ -1,15 +1,15 @@ /** - * Very basic chain with just two reactors, one that sends - * a message a startup feeding one that reports receiving it. - * + * Very basic chain with just two reactors, one that sends a message a startup feeding one that + * reports receiving it. + * * @author Edward A. Lee */ -target C; +target C -import SendOnce, Receive from "lib/SendersAndReceivers.lf"; +import SendOnce, Receive from "lib/SendersAndReceivers.lf" main reactor { - r1 = new SendOnce(); - r2 = new Receive(); - r1.out -> r2.in; + r1 = new SendOnce() + r2 = new Receive() + r1.out -> r2.in } diff --git a/C/src/patterns/Chain_02_Pipeline.lf b/C/src/patterns/Chain_02_Pipeline.lf index 97bb3497..d19312ce 100644 --- a/C/src/patterns/Chain_02_Pipeline.lf +++ b/C/src/patterns/Chain_02_Pipeline.lf @@ -1,32 +1,27 @@ /** - * Basic pipeline pattern where a periodic source feeds - * a chain of reactors that can all execute in parallel - * at each logical time step. - * - * The threads argument specifies the number of worker - * threads, which enables the reactors in the chain to - * execute on multiple cores simultaneously. - * - * This uses the TakeTime reactor to perform computation - * (it computes Fibonacci numbers). If you reduce the - * number of worker threads to 1, the execution time - * will be approximately four times as long. - * + * Basic pipeline pattern where a periodic source feeds a chain of reactors that can all execute in + * parallel at each logical time step. + * + * The threads argument specifies the number of worker threads, which enables the reactors in the + * chain to execute on multiple cores simultaneously. + * + * This uses the TakeTime reactor to perform computation (it computes Fibonacci numbers). If you + * reduce the number of worker threads to 1, the execution time will be approximately four times as + * long. + * * @author Edward A. Lee */ target C { workers: 4, timeout: 1 sec } - -import SendCount, Receive from "lib/SendersAndReceivers.lf"; -import TakeTime from "lib/TakeTime.lf"; + +import SendCount, Receive from "lib/SendersAndReceivers.lf" +import TakeTime from "lib/TakeTime.lf" main reactor { - r0 = new SendCount(period = 100 msec); - rp = new[4] TakeTime(approximate_time = 100 msec); - r5 = new Receive(); - r0.out, rp.out -> rp.in, r5.in after 100 msec; + r0 = new SendCount(period = 100 msec) + rp = new[4] TakeTime(approximate_time = 100 msec) + r5 = new Receive() + r0.out, rp.out -> rp.in, r5.in after 100 msec } - - \ No newline at end of file diff --git a/C/src/patterns/FullyConnected_00_Broadcast.lf b/C/src/patterns/FullyConnected_00_Broadcast.lf index 3563bff2..32d72c06 100644 --- a/C/src/patterns/FullyConnected_00_Broadcast.lf +++ b/C/src/patterns/FullyConnected_00_Broadcast.lf @@ -1,41 +1,37 @@ /** - * This illustrates bank of reactors where each reactor produces an - * output that is broadcast to all reactors in the bank, including - * itself. - * + * This illustrates bank of reactors where each reactor produces an output that is broadcast to all + * reactors in the bank, including itself. + * * @author Christian Menard * @author Edward A. Lee */ -target C; +target C -reactor Node( - num_nodes: size_t(4), - bank_index: int(0) -) { - input[num_nodes] in: int; - output out: int; - - state received: bool(false); - - reaction (startup) -> out{= - lf_print("Hello from node %d!", self->bank_index); - // broadcast my ID to everyone - lf_set(out, self->bank_index); +reactor Node(num_nodes: size_t = 4, bank_index: int = 0) { + input[num_nodes] in: int + output out: int + + state received: bool = false + + reaction(startup) -> out {= + lf_print("Hello from node %d!", self->bank_index); + // broadcast my ID to everyone + lf_set(out, self->bank_index); =} - - reaction (in) {= + + reaction(in) {= printf("Node %d received messages from ", self->bank_index); for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { self->received = true; printf("%d, ", in[i]->value); } - } + } printf("\n"); =} } -main reactor(num_nodes: size_t(4)) { - nodes = new[num_nodes] Node(num_nodes=num_nodes); - (nodes.out)+ -> nodes.in; +main reactor(num_nodes: size_t = 4) { + nodes = new[num_nodes] Node(num_nodes=num_nodes) + (nodes.out)+ -> nodes.in } diff --git a/C/src/patterns/FullyConnected_01_Addressable.lf b/C/src/patterns/FullyConnected_01_Addressable.lf index 0c524e7d..445548a9 100644 --- a/C/src/patterns/FullyConnected_01_Addressable.lf +++ b/C/src/patterns/FullyConnected_01_Addressable.lf @@ -1,42 +1,36 @@ /** - * This illustrates bank of reactors where each reactor produces an - * output that is sent to a reactor in the bank of its choice. In this - * particular example, each reactor chooses to send its output to the - * reactor with the next higher bank_index, wrapping around when it gets - * to the end of the bank. - * + * This illustrates bank of reactors where each reactor produces an output that is sent to a reactor + * in the bank of its choice. In this particular example, each reactor chooses to send its output to + * the reactor with the next higher bank_index, wrapping around when it gets to the end of the bank. + * * @author Christian Menard * @author Edward A. Lee */ // In this pattern, each node can send direct messages to individual other nodes +target C -target C; +reactor Node(num_nodes: size_t = 4, bank_index: int = 0) { + input[num_nodes] in: int + output[num_nodes] out: int -reactor Node( - num_nodes: size_t(4), - bank_index: int(0) -) { - input[num_nodes] in: int; - output[num_nodes] out: int; - - reaction (startup) -> out{= - lf_print("Hello from node %d!", self->bank_index); - // broadcast my ID to everyone - lf_set(out[(self->bank_index + 1) % self->num_nodes], self->bank_index); + reaction(startup) -> out {= + lf_print("Hello from node %d!", self->bank_index); + // broadcast my ID to everyone + lf_set(out[(self->bank_index + 1) % self->num_nodes], self->bank_index); =} - - reaction (in) {= + + reaction(in) {= for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { lf_print("Node %d received %d on channel %d.", self->bank_index, in[i]->value, i ); } - } + } =} } -main reactor(num_nodes: size_t(4)) { - nodes = new[num_nodes] Node(num_nodes=num_nodes); - nodes.out -> interleaved(nodes.in); +main reactor(num_nodes: size_t = 4) { + nodes = new[num_nodes] Node(num_nodes=num_nodes) + nodes.out -> interleaved(nodes.in) } diff --git a/C/src/patterns/Loop_01_Single.lf b/C/src/patterns/Loop_01_Single.lf index 27039160..4e5455a1 100644 --- a/C/src/patterns/Loop_01_Single.lf +++ b/C/src/patterns/Loop_01_Single.lf @@ -1,20 +1,18 @@ /** - * A single reactor sends data to itself. - * The receiving reaction has to be appear lexically later - * in the source code, so that the sending reaction has - * higher priority, or else a causality loop arises. - * You can observe the causality loop by replacing the - * imported reactor with ReceiveAndSendPeriodically. - * + * A single reactor sends data to itself. The receiving reaction has to be appear lexically later in + * the source code, so that the sending reaction has higher priority, or else a causality loop + * arises. You can observe the causality loop by replacing the imported reactor with + * ReceiveAndSendPeriodically. + * * @author Edward A. Lee */ target C { timeout: 5 sec -}; +} -import SendPeriodicallyAndReceive from "lib/SendersAndReceivers.lf"; +import SendPeriodicallyAndReceive from "lib/SendersAndReceivers.lf" main reactor { - r1 = new SendPeriodicallyAndReceive(); - r1.out -> r1.in; + r1 = new SendPeriodicallyAndReceive() + r1.out -> r1.in } diff --git a/C/src/patterns/Loop_02_SingleDelay.lf b/C/src/patterns/Loop_02_SingleDelay.lf index 2be5e599..eeff2933 100644 --- a/C/src/patterns/Loop_02_SingleDelay.lf +++ b/C/src/patterns/Loop_02_SingleDelay.lf @@ -1,24 +1,19 @@ /** - * A single reactor sends data to itself with delay. - * Unlike Loop_01_Single.lf, this can have reactions - * appear in reverse order, where the receiving reaction - * occurs before the sending reaction. This is because the - * delay of a single microstep breaks the causality loop. - * - * Note that the received values are reported at microstep 1 - * rather than 0. - * + * A single reactor sends data to itself with delay. Unlike Loop_01_Single.lf, this can have + * reactions appear in reverse order, where the receiving reaction occurs before the sending + * reaction. This is because the delay of a single microstep breaks the causality loop. + * + * Note that the received values are reported at microstep 1 rather than 0. + * * @author Edward A. Lee */ target C { timeout: 5 sec -}; +} -import ReceiveAndSendPeriodically from "lib/SendersAndReceivers.lf"; +import ReceiveAndSendPeriodically from "lib/SendersAndReceivers.lf" -main reactor ( - a_1_1:time(0) -) { - r1 = new ReceiveAndSendPeriodically(); - r1.out -> r1.in after a_1_1; +main reactor(a_1_1: time = 0) { + r1 = new ReceiveAndSendPeriodically() + r1.out -> r1.in after a_1_1 } diff --git a/C/src/patterns/lib/SendersAndReceivers.lf b/C/src/patterns/lib/SendersAndReceivers.lf index f36a7ea6..bc706b13 100644 --- a/C/src/patterns/lib/SendersAndReceivers.lf +++ b/C/src/patterns/lib/SendersAndReceivers.lf @@ -1,49 +1,40 @@ /** * Library of reactors that are reused in various design pattern examples. - * + * * @author Edward A. Lee */ -target C; +target C -/** - * Send an output (42) at startup. - */ +/** Send an output (42) at startup. */ reactor SendOnce { - output out:int; - reaction(startup) -> out {= - lf_set(out, 42); - =} + output out: int + + reaction(startup) -> out {= lf_set(out, 42); =} } /** * Send counting sequence periodically. - * + * * @param offset The starting time. * @param period The period. * @param start The first output. * @param increment The increment between outputs */ -reactor SendCount( - offset:time(0), - period:time(1 sec), - start:int(0), - increment:int(1) -) { - state count:int(start); - output out:int; - timer t(offset, period); +reactor SendCount(offset: time = 0, period: time = 1 sec, start: int = 0, increment: int = 1) { + state count: int = start + output out: int + timer t(offset, period) + reaction(t) -> out {= lf_set(out, self->count); self->count += self->increment; =} } -/** - * Receive an input and report the elpased logical tag - * and the value of the input. - */ +/** Receive an input and report the elpased logical tag and the value of the input. */ reactor Receive { - input in:int; + input in: int + reaction(in) {= lf_print("At elapsed tag (%lld, %d), received %d.", lf_time_logical_elapsed(), lf_tag().microstep, @@ -53,18 +44,18 @@ reactor Receive { } reactor ReceiveAndSend { - input in:int; - output out:int; - reaction(in) -> out {= - lf_set(out, in->value); - =} + input in: int + output out: int + + reaction(in) -> out {= lf_set(out, in->value); =} } + reactor SendOnceAndReceive { - input in:int; - output out:int; - reaction(startup) -> out {= - lf_set(out, 42); - =} + input in: int + output out: int + + reaction(startup) -> out {= lf_set(out, 42); =} + reaction(in) {= lf_print("At tag (%lld, %d), received %d.", lf_time_logical_elapsed(), @@ -75,64 +66,62 @@ reactor SendOnceAndReceive { } /** - * This reactor periodically increments its state and sends it out. - * When an input is received, it simply reports the value. - * + * This reactor periodically increments its state and sends it out. When an input is received, it + * simply reports the value. + * * @param offset The time of the first output. * @param period The period of the outputs. * @param start The initial output value. * @param increment The increment between outputs. - * + * * @input in The input to report. * @output out The counting output. - * + * * @label Increment the state, send, report received. */ reactor SendPeriodicallyAndReceive extends SendCount, Receive { - // This reactor simply composes SendCount and Receive, in that order. } /** - * This reactor periodically increments its state and sends it out. - * When an input is received, it simply reports the value. - * + * This reactor periodically increments its state and sends it out. When an input is received, it + * simply reports the value. + * * @param offset The time of the first output. * @param period The period of the outputs. * @param start The initial output value. * @param increment The increment between outputs. - * + * * @input in The input to report. * @output out The counting output. - * + * * @label Increment the state, send, report received. */ reactor ReceiveAndSendPeriodically extends Receive, SendCount { - // This reactor simply composes Receive and SendCount, in that order. } /** - * This reactor periodically increments its state and sends it out. - * When an input is received, it simply reports the value. + * This reactor periodically increments its state and sends it out. When an input is received, it + * simply reports the value. * @label Increment the state, send, report received. */ -reactor SendPeriodicallyAndReceiveMultiport ( - offset:time(0), - period:time(1 sec), - start:int(0), - increment:int(1), - width:int(4) -) { - input[width] in:int; - output out:int; - - timer t(offset, period); - - state count:int(start); - +reactor SendPeriodicallyAndReceiveMultiport( + offset: time = 0, + period: time = 1 sec, + start: int = 0, + increment: int = 1, + width: int = 4) { + input[width] in: int + output out: int + + timer t(offset, period) + + state count: int = start + reaction(t) -> out {= lf_set(out, self->count); self->count += self->increment; =} + reaction(in) {= lf_print("At tag (%lld, %d), received:", lf_time_logical_elapsed(), lf_tag().microstep @@ -144,26 +133,23 @@ reactor SendPeriodicallyAndReceiveMultiport ( } /** - * This reactor maintains a local state that periodically incremented - * and incremented whenever an input arrives. + * This reactor maintains a local state that periodically incremented and incremented whenever an + * input arrives. * @label Increment the state, send increment, accept increment. */ -reactor LocalRemoteUpdates( - offset:time(0), - period:time(1 sec), - increment:int(1) -) { - input in:int; - output out:int; - - timer t(offset, period); - - state count:int(0); - +reactor LocalRemoteUpdates(offset: time = 0, period: time = 1 sec, increment: int = 1) { + input in: int + output out: int + + timer t(offset, period) + + state count: int = 0 + reaction(t) -> out {= lf_set(out, self->increment); self->count += self->increment; =} + reaction(in) {= self->count += in->value; lf_print("At tag (%lld, %d), count is %d", @@ -172,34 +158,24 @@ reactor LocalRemoteUpdates( ); =} } + // @label Accumulate local/remote increments, locally query. -reactor SendAndReceiveWithLocalQuery( - query_offset:time(0), - query_period:time(1 sec) -) extends LocalRemoteUpdates { - local_query = new SendPeriodicallyAndReceive( - offset = query_offset, - period = query_period - ); - reaction(local_query.out) -> local_query.in {= - lf_set(local_query.in, self->count); - =} +reactor SendAndReceiveWithLocalQuery(query_offset: time = 0, query_period: time = 1 sec) + extends LocalRemoteUpdates { + local_query = new SendPeriodicallyAndReceive(offset=query_offset, period=query_period) + + reaction(local_query.out) -> local_query.in {= lf_set(local_query.in, self->count); =} } + // @label Accumulate local/remote increments, delayed query. reactor SendAndReceiveWithDelayedQuery( - query_offset:time(0), - query_period:time(1 sec), - query_delay:time(10 msec) -) extends LocalRemoteUpdates { - local_query = new SendPeriodicallyAndReceive( - offset = query_offset, - period = query_period - ); - logical action a(query_delay); - reaction(local_query.out) -> a {= - lf_schedule(a, 0); - =} - reaction(a) -> local_query.in {= - lf_set(local_query.in, self->count); - =} + query_offset: time = 0, + query_period: time = 1 sec, + query_delay: time = 10 msec) extends LocalRemoteUpdates { + local_query = new SendPeriodicallyAndReceive(offset=query_offset, period=query_period) + logical action a(query_delay) + + reaction(local_query.out) -> a {= lf_schedule(a, 0); =} + + reaction(a) -> local_query.in {= lf_set(local_query.in, self->count); =} } diff --git a/C/src/patterns/lib/TakeTime.lf b/C/src/patterns/lib/TakeTime.lf index 5c3f3440..9946ef49 100644 --- a/C/src/patterns/lib/TakeTime.lf +++ b/C/src/patterns/lib/TakeTime.lf @@ -1,25 +1,21 @@ -target C; +target C + /** - * Compute Fibonacci numbers until at least the specified - * physical time has passed. This will compute numbers until - * they overflow the 64-bit representation, and then start over. - * When at least the specified physical time has elapased, - * report the last number computed. - * This is used in some design patterns to provide - * significant computation. - * - * @param approximate_time The approximate amount of physical - * time to take for each input. - * + * Compute Fibonacci numbers until at least the specified physical time has passed. This will + * compute numbers until they overflow the 64-bit representation, and then start over. When at least + * the specified physical time has elapased, report the last number computed. This is used in some + * design patterns to provide significant computation. + * + * @param approximate_time The approximate amount of physical time to take for each input. + * * @input in A triggering input. - * - * @output out The computed Fibonacci number. + * + * @output out The computed Fibonacci number. */ -reactor TakeTime( - approximate_time:time(100 msec) -) { - input in:int; - output out:int; +reactor TakeTime(approximate_time: time = 100 msec) { + input in: int + output out: int + reaction(in) -> out {= instant_t start_time = lf_time_physical(); int f0 = 0; diff --git a/C/src/rhythm/PlayWaveform.lf b/C/src/rhythm/PlayWaveform.lf index 57750094..4840e4b7 100644 --- a/C/src/rhythm/PlayWaveform.lf +++ b/C/src/rhythm/PlayWaveform.lf @@ -1,107 +1,93 @@ /** - * @brief Reactor to play a waveform defined in a .wav file. - * This serves as a demonstration for how to write Lingua Franca - * programs that use Apple's AudioToolbox. - * - * To use this, you must include the target parameters that are - * given below. - * - * This reactor provides a small collection of built-in audio - * waveforms which are read at startup time from .wav files. - * The waveform input specifies which of the waveforms to play - * upon the next `note` input received. - * It is a number between 0 and NUM_WAVEFORMS. If a number outside - * this range is received, then simple tick sounds will be produced. - * Number 0 is specially interpreted for silence. + * @brief Reactor to play a waveform defined in a .wav file. This serves as a demonstration for how + * to write Lingua Franca programs that use Apple's AudioToolbox. + * + * To use this, you must include the target parameters that are given below. + * + * This reactor provides a small collection of built-in audio waveforms which are read at startup + * time from .wav files. The waveform input specifies which of the waveforms to play upon the next + * `note` input received. It is a number between 0 and NUM_WAVEFORMS. If a number outside this range + * is received, then simple tick sounds will be produced. Number 0 is specially interpreted for + * silence. + * + * The `note` input is a number, normally between 0.0 and 1.0, that specifies the loudness of the + * note. If the loudness exceeds 1.0, or if too many notes are played at once, clipping may occur. + * + * The sound files come from here: https://freewavesamples.com + * + * Sound files are assumed to be wav files with sample rate 44,100, 16-bit samples, linear PCM + * encoded. Use afconvert on Mac to convert to the assumed input format. * - * The `note` input is a number, normally between 0.0 and 1.0, - * that specifies the loudness of the note. If the loudness exceeds - * 1.0, or if too many notes are played at once, clipping may occur. - * - * The sound files come from here: - * https://freewavesamples.com - * - * Sound files are assumed to be wav files with sample rate 44,100, - * 16-bit samples, linear PCM encoded. - * Use afconvert on Mac to convert to the assumed input format. - * * @author Edward A. Lee */ target C { files: [ - "/lib/c/reactor-c/util/wave_file_reader.c", - "/lib/c/reactor-c/util/wave_file_reader.h", - "/lib/c/reactor-c/util/audio_loop_mac.c", - "/lib/c/reactor-c/util/audio_loop.h", - "/lib/c/reactor-c/util/audio_loop_linux.c", - ], + "/lib/c/reactor-c/util/wave_file_reader.c", + "/lib/c/reactor-c/util/wave_file_reader.h", + "/lib/c/reactor-c/util/audio_loop_mac.c", + "/lib/c/reactor-c/util/audio_loop.h", + "/lib/c/reactor-c/util/audio_loop_linux.c"], cmake-include: [ - "/lib/c/reactor-c/util/audio_loop.cmake", - "/lib/c/reactor-c/util/wave_file_reader.cmake" - ] + "/lib/c/reactor-c/util/audio_loop.cmake", + "/lib/c/reactor-c/util/wave_file_reader.cmake"] } - -/** - * Produce a note when a `note` input is received. - */ -reactor PlayWaveform ( - default_waveform_id:int(0) // Silent waveform -) { - preamble {= + +/** Produce a note when a `note` input is received. */ +reactor PlayWaveform( + // Silent waveform + default_waveform_id: int = 0) { + preamble {= #include // Defines strlen() - #include "audio_loop.h" - #include "wave_file_reader.h" - - // wav files giving the waveforms. - // These have to also be included in the files target directive. - #define NUM_WAVEFORMS 9 // Number of waveforms. - #define SOUNDS LF_PACKAGE_DIRECTORY LF_FILE_SEPARATOR "src" LF_FILE_SEPARATOR "rhythm" LF_FILE_SEPARATOR "sounds" LF_FILE_SEPARATOR - char* waveform_files[] = { - SOUNDS "Bass-Drum-1.wav", - SOUNDS "Hi-Bongo.wav", - SOUNDS "Claves.wav", - SOUNDS "High-Conga-1.wav", - SOUNDS "Cowbell-1.wav", - SOUNDS "Cuica-1.wav", - SOUNDS "Guiro.wav", - SOUNDS "Ensoniq-ESQ-1-Snare.wav", - SOUNDS "Floor-Tom-1.wav" - }; - - // The waveforms themselves. - lf_waveform_t* waveforms[NUM_WAVEFORMS + 1]; - - lf_waveform_t empty_waveform = { 0 }; + #include "audio_loop.h" + #include "wave_file_reader.h" + + // wav files giving the waveforms. + // These have to also be included in the files target directive. + #define NUM_WAVEFORMS 9 // Number of waveforms. + #define SOUNDS LF_PACKAGE_DIRECTORY LF_FILE_SEPARATOR "src" LF_FILE_SEPARATOR "rhythm" LF_FILE_SEPARATOR "sounds" LF_FILE_SEPARATOR + char* waveform_files[] = { + SOUNDS "Bass-Drum-1.wav", + SOUNDS "Hi-Bongo.wav", + SOUNDS "Claves.wav", + SOUNDS "High-Conga-1.wav", + SOUNDS "Cowbell-1.wav", + SOUNDS "Cuica-1.wav", + SOUNDS "Guiro.wav", + SOUNDS "Ensoniq-ESQ-1-Snare.wav", + SOUNDS "Floor-Tom-1.wav" + }; + + // The waveforms themselves. + lf_waveform_t* waveforms[NUM_WAVEFORMS + 1]; + + lf_waveform_t empty_waveform = { 0 }; =} - input note:float; - input waveform:int; - + input note: float + input waveform: int + /** * Index of the current waveform. * -1 means no waveform (just make ticks)). */ - state waveform_id:int(default_waveform_id); - + state waveform_id: int = default_waveform_id + reaction(startup) {= - // First waveform is empty. waveforms[0] = &empty_waveform; - + // Open and read waveform files. for (int i = 0; i < NUM_WAVEFORMS; i++) { waveforms[i + 1] = read_wave_file(waveform_files[i]); } - + // Start an audio loop that will become ready to receive // amplitude samples of audio data. lf_start_audio_loop(lf_time_logical()); =} - - reaction(waveform) {= - self->waveform_id = waveform->value; - =} - + + reaction(waveform) {= self->waveform_id = waveform->value; =} + reaction(note) {= if (self->waveform_id < 0 || self->waveform_id > NUM_WAVEFORMS) { lf_play_audio_waveform(NULL, note->value, lf_time_logical()); @@ -109,8 +95,6 @@ reactor PlayWaveform ( lf_play_audio_waveform(waveforms[self->waveform_id], note->value, lf_time_logical()); } =} - - reaction(shutdown) {= - lf_stop_audio_loop(); - =} + + reaction(shutdown) {= lf_stop_audio_loop(); =} } diff --git a/C/src/rhythm/Rhythm.lf b/C/src/rhythm/Rhythm.lf index 23d12610..c0fc1c1f 100644 --- a/C/src/rhythm/Rhythm.lf +++ b/C/src/rhythm/Rhythm.lf @@ -1,25 +1,23 @@ /** - * Rhythm generator using samples of percussion instruments. This program runs - * on MacOS and Linux, at least. + * Rhythm generator using samples of percussion instruments. This program runs on MacOS and Linux, + * at least. * - * This program opens a simple, terminal-based user interface for specifying a - * rhythmic audio output. The rhythm is displayed in the terminal as it is - * generated and produced as audio using sample audio files. + * This program opens a simple, terminal-based user interface for specifying a rhythmic audio + * output. The rhythm is displayed in the terminal as it is generated and produced as audio using + * sample audio files. * - * This program also uses ncurses, which needs to be installed on your machine - * for this to work. It also uses the library utility sensor_simulator, provided - * with Lingua Franca, which uses keyboard input to simulate asynchronous - * sensors and beeps to simulate timed output. See sensor_simulator.h for - * documentation. + * This program also uses ncurses, which needs to be installed on your machine for this to work. It + * also uses the library utility sensor_simulator, provided with Lingua Franca, which uses keyboard + * input to simulate asynchronous sensors and beeps to simulate timed output. See sensor_simulator.h + * for documentation. * * The merengue rhythm comes from here: * https://www.8notes.com/school/lessons/percussion/merengue.asp * * The sound files come from here: https://freewavesamples.com * - * Sound files are assumed to be wav files with sample rate 44,100, 16-bit - * samples, linear PCM encoded. Use afconvert on Mac to convert to the assumed - * input format. + * Sound files are assumed to be wav files with sample rate 44,100, 16-bit samples, linear PCM + * encoded. Use afconvert on Mac to convert to the assumed input format. * * @author Edward A. Lee * @@ -28,13 +26,8 @@ */ target C { keepalive: true, - files: [ - "/lib/c/reactor-c/util/sensor_simulator.c", - "/lib/c/reactor-c/util/sensor_simulator.h", - ], - cmake-include: [ - "/lib/c/reactor-c/util/sensor_simulator.cmake", - ] + files: ["/lib/c/reactor-c/util/sensor_simulator.c", "/lib/c/reactor-c/util/sensor_simulator.h"], + cmake-include: ["/lib/c/reactor-c/util/sensor_simulator.cmake"] } import PlayWaveform from "PlayWaveform.lf" @@ -70,31 +63,28 @@ preamble {= #define SAMBA 0xddda #define SAMBA_EMPHASIS 0x99ca #endif - + extern const char* instructions[]; extern int instructions_length; =} /** - * Reactor that outputs notes (which carry an emphasis)) according to a - * specified rhythm. The minimum time between notes is given by the - * 'tick_duration' state variable. This can be adjusted up or down. This is - * designed to coordinate between multiple instances of this RhythmSource so + * Reactor that outputs notes (which carry an emphasis)) according to a specified rhythm. The + * minimum time between notes is given by the 'tick_duration' state variable. This can be adjusted + * up or down. This is designed to coordinate between multiple instances of this RhythmSource so * each can change the rhythm and tempo while keeping the others in sync. * * @param sixteenth Initial duration of one sixteenth note. - * @param delta The amount by which to change sixteenth when tempo is increased - * or decreased. + * @param delta The amount by which to change sixteenth when tempo is increased or decreased. * @param message An array of strings to display. * @param message_length The length of the message array. */ reactor RhythmSource( - sixteenth: time = 200 msec, - delta: time = 10 msec, - message: {= const char** =} = {= instructions =}, - message_length: int = {= instructions_length =}, - log_to_file: bool = false -) { + sixteenth: time = 200 msec, + delta: time = 10 msec, + message: {= const char** =} = {= instructions =}, + message_length: int = {= instructions_length =}, + log_to_file: bool = false) { preamble {= const char* instructions[] = { "Basic control:", @@ -118,46 +108,34 @@ reactor RhythmSource( " b: bossa nova", " s: samba" }; - int instructions_length = 20; + int instructions_length = 20; =} - // To change the rhythm. - input rhythm_change_in: char - // To change the tempo. - input tempo_change_in: interval_t + input rhythm_change_in: char // To change the rhythm. + input tempo_change_in: interval_t // To change the tempo. - // To play a note with the given emphasis. - output note: float - // Instrument selection. - output instrument: int - // To change the rhythm. - output rhythm_change: char - // To change the tempo. - output tempo_change: interval_t + output note: float // To play a note with the given emphasis. + output instrument: int // Instrument selection. + output rhythm_change: char // To change the rhythm. + output tempo_change: interval_t // To change the tempo. state tick_duration: time = 200 msec logical action tick - // Count of sixteenth notes. - state count: int = 0 + state count: int = 0 // Count of sixteenth notes. - // Action to be invoked when a key is pressed. - physical action key: char + physical action key: char // Action to be invoked when a key is pressed. - // Indicator of when to make a sound. - state rhythm: int = {= DOWNBEAT =} + state rhythm: int = {= DOWNBEAT =} // Indicator of when to make a sound. - // Indicator of whether to emphasize the sound. - state emphasis: int = {= DOWNBEAT =} + state emphasis: int = {= DOWNBEAT =} // Indicator of whether to emphasize the sound. - // Currently active rhythm. This becomes active from rhythm on the downbeat. - state active_rhythm: int = {= DOWNBEAT =} + state active_rhythm: int = {= DOWNBEAT =} // Currently active rhythm. This becomes active from rhythm on the downbeat. // Currently active emphasis. This becomes active from rhythm on the // downbeat. state active_emphasis: int = {= DOWNBEAT =} - // Position of the cursor in the terminal window. - state cursor: int = 0 + state cursor: int = 0 // Position of the cursor in the terminal window. reaction(startup) -> key, note, tick {= // Start the sensor simulator, which starts ncurses. diff --git a/C/src/rhythm/RhythmDistributed.lf b/C/src/rhythm/RhythmDistributed.lf index 4c61adda..58afa21e 100644 --- a/C/src/rhythm/RhythmDistributed.lf +++ b/C/src/rhythm/RhythmDistributed.lf @@ -1,59 +1,57 @@ /** - * Demonstration of timed distributed Lingua Franca programs. - * This program runs on MacOS and Linux, at least. - * - * This program elaborates Rhythm to have two players that - * run on different machines. Both players can select a musical - * instrument, but only one of the players can control the rhythm - * and the tempo. - * - * To run this program, open three distinct terminal windows - * and run the following generated binaries, one in each window: - * + * Demonstration of timed distributed Lingua Franca programs. This program runs on MacOS and Linux, + * at least. + * + * This program elaborates Rhythm to have two players that run on different machines. Both players + * can select a musical instrument, but only one of the players can control the rhythm and the + * tempo. + * + * To run this program, open three distinct terminal windows and run the following generated + * binaries, one in each window: + * * * RTI -n 2 * * fed-gen/RhythmDistributed/bin/player1 * * fed-gen/RhythmDistributed/bin/player2 - * - * The `-n 2` argument specifies the number of federates. - * Note that you have to have installed the RTI on your path. - * See the instructions in the README file in this directory in the LF repo: - * - * org.lflang/src/lib/core/federated/RTI - * - * You can also map these three to distinct machines by specifying an `at` clause - * on the lines at the end of this file that instantiate the reactors. - * See [[https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution]]. - * + * + * The `-n 2` argument specifies the number of federates. Note that you have to have installed the + * RTI on your path. See the instructions in the README file in this directory in the LF repo: + * + * org.lflang/src/lib/core/federated/RTI + * + * You can also map these three to distinct machines by specifying an `at` clause on the lines at + * the end of this file that instantiate the reactors. See + * [[https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution]]. + * * @see Rhythm.lf * @see RhythmDistributedNoUI.lf * * @author Edward A. Lee */ target C - + import RhythmSource from "Rhythm.lf" import PlayWaveform from "PlayWaveform.lf" reactor Player { - input tempo_change_in:interval_t; // To accept a tempo change. - input rhythm_change_in:char; // To accept a rhythm change. - output tempo_change:interval_t; // To change the tempo. - output rhythm_change:char; // To change the rhythm. - source = new RhythmSource(); - play = new PlayWaveform(); - source.note -> play.note; - source.instrument -> play.waveform; - source.rhythm_change -> rhythm_change; - source.tempo_change -> tempo_change; - rhythm_change_in -> source.rhythm_change_in; - tempo_change_in -> source.tempo_change_in; + input tempo_change_in: interval_t // To accept a tempo change. + input rhythm_change_in: char // To accept a rhythm change. + output tempo_change: interval_t // To change the tempo. + output rhythm_change: char // To change the rhythm. + source = new RhythmSource() + play = new PlayWaveform() + source.note -> play.note + source.instrument -> play.waveform + source.rhythm_change -> rhythm_change + source.tempo_change -> tempo_change + rhythm_change_in -> source.rhythm_change_in + tempo_change_in -> source.tempo_change_in } federated reactor { - player1 = new Player(); - player2 = new Player(); - player1.rhythm_change -> player2.rhythm_change_in; - player1.tempo_change -> player2.tempo_change_in; - player2.rhythm_change -> player1.rhythm_change_in; - player2.tempo_change -> player1.tempo_change_in; + player1 = new Player() + player2 = new Player() + player1.rhythm_change -> player2.rhythm_change_in + player1.tempo_change -> player2.tempo_change_in + player2.rhythm_change -> player1.rhythm_change_in + player2.tempo_change -> player1.tempo_change_in } diff --git a/C/src/rhythm/RhythmDistributedNoUI.lf b/C/src/rhythm/RhythmDistributedNoUI.lf index 8852ee38..96a60794 100644 --- a/C/src/rhythm/RhythmDistributedNoUI.lf +++ b/C/src/rhythm/RhythmDistributedNoUI.lf @@ -1,17 +1,14 @@ /** - * Rhythm generator using samples of percussion instruments. - * This version can run on multiple machines leveraging - * synchronized clocks to stay in sync. - * This program runs on MacOS and Linux, at least. - * - * This program tests clock synchronization by producing - * sounds on each of two computers that, if the clocks are - * synchronized well enough, sound like they are occurring - * at identical times. In this version, there is no communication - * between the components other than clock synchronization. - * - * To map these programs onto distinct machines, at the end - * of this file, change the lines like this: + * Rhythm generator using samples of percussion instruments. This version can run on multiple + * machines leveraging synchronized clocks to stay in sync. This program runs on MacOS and Linux, at + * least. + * + * This program tests clock synchronization by producing sounds on each of two computers that, if + * the clocks are synchronized well enough, sound like they are occurring at identical times. In + * this version, there is no communication between the components other than clock synchronization. + * + * To map these programs onto distinct machines, at the end of this file, change the lines like + * this: * ``` * player1 = new RhythmPlayer(); * ``` @@ -19,13 +16,12 @@ * ``` * player1 = new RhythmPlayer() at 10.0.0.42; * ``` - * where the last part is the IP address or machine name of - * a machine on which you want to run the particular component. - * See [[https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution]]. - * + * where the last part is the IP address or machine name of a machine on which you want to run the + * particular component. See [[https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution]]. + * * @see Rhythm.lf * @see RhythmDistributed.lf - * + * * @author Edward A. Lee */ target C { @@ -39,30 +35,26 @@ target C { trials: 10, attenuation: 10 } -}; +} + import PlayWaveform from "PlayWaveform.lf" -/** - * Reactor that outputs a note request at the specified period. - */ -reactor BeatSource(period:time(1600 msec)) { - output note:float; // To play a note with the given emphasis. - - timer tick(0, period); - - reaction(tick) -> note {= - lf_set(note, 1.0f); - =} +/** Reactor that outputs a note request at the specified period. */ +reactor BeatSource(period: time = 1600 msec) { + output note: float // To play a note with the given emphasis. + + timer tick(0, period) + + reaction(tick) -> note {= lf_set(note, 1.0f); =} } reactor RhythmPlayer { - source = new BeatSource(); - play = new PlayWaveform(default_waveform_id = 3); - source.note -> play.note; + source = new BeatSource() + play = new PlayWaveform(default_waveform_id=3) + source.note -> play.note } federated reactor { - player1 = new RhythmPlayer(); - player2 = new RhythmPlayer(); + player1 = new RhythmPlayer() + player2 = new RhythmPlayer() } - diff --git a/C/src/rhythm/SensorSimulator.lf b/C/src/rhythm/SensorSimulator.lf index 50ad7a42..197cee8b 100644 --- a/C/src/rhythm/SensorSimulator.lf +++ b/C/src/rhythm/SensorSimulator.lf @@ -1,38 +1,36 @@ /** - * Simple demonstration of the sensor simulator used in the Rhythm examples. - * This has no audio output, but just tests the ncurses interface. + * Simple demonstration of the sensor simulator used in the Rhythm examples. This has no audio + * output, but just tests the ncurses interface. */ target C { keepalive: true, - files: [ - "/lib/c/reactor-c/util/sensor_simulator.c", - "/lib/c/reactor-c/util/sensor_simulator.h", - ], - cmake-include: [ - "/lib/c/reactor-c/util/sensor_simulator.cmake", - ] -}; + files: ["/lib/c/reactor-c/util/sensor_simulator.c", "/lib/c/reactor-c/util/sensor_simulator.h"], + cmake-include: ["/lib/c/reactor-c/util/sensor_simulator.cmake"] +} + main reactor { preamble {= #include "sensor_simulator.h" const char* messages[] = {"Hello", "World"}; int num_messages = 2; =} - timer t(0, 1 sec); - timer r(0, 2 sec); - physical action key:char*; + timer t(0, 1 sec) + timer r(0, 2 sec) + physical action key: char* + reaction(startup) -> key {= lf_print("Starting sensor simulator."); start_sensor_simulator(messages, num_messages, 16, NULL, LOG_LEVEL_INFO); register_sensor_key('\0', key); - =} - reaction(t) {= - show_tick("*"); =} + + reaction(t) {= show_tick("*"); =} + reaction(r) {= lf_print("Elapsed logical time: %lld.", lf_time_logical_elapsed()); show_tick("."); =} + reaction(key) {= lf_print("You typed '%s' at elapsed time %lld.", key->value, lf_time_logical_elapsed()); =} diff --git a/C/src/rhythm/Sound.lf b/C/src/rhythm/Sound.lf index 4cebb4de..02dc5f3c 100644 --- a/C/src/rhythm/Sound.lf +++ b/C/src/rhythm/Sound.lf @@ -1,6 +1,5 @@ /** - * Sound test. This program runs on MacOS and Linux, at least. - * This program plays a simple rhythm. + * Sound test. This program runs on MacOS and Linux, at least. This program plays a simple rhythm. * * The merengue rhythm comes from here: * https://www.8notes.com/school/lessons/percussion/merengue.asp @@ -12,7 +11,7 @@ * @see Rhythm.lf */ target C { - keepalive: true, + keepalive: true } import PlayWaveform from "PlayWaveform.lf" @@ -29,29 +28,24 @@ preamble {= =} /** - * Reactor that outputs notes (which carry an emphasis)) according to a - * specified rhythm. The minimum time between notes is given by the - * 'sixteenth' parameter. - * + * Reactor that outputs notes (which carry an emphasis)) according to a specified rhythm. The + * minimum time between notes is given by the 'sixteenth' parameter. + * * @param sixteenth Duration of one sixteenth note. * @param rhythm Binary specification of a rhythm in reverse order. * @param emphasis Binary specification of emphasis in reverse order. */ reactor RhythmSource( - sixteenth: time = 200 msec, - rhythm: int = {= MERENGUE =}, - emphasis: int = {= MERENGUE_EMPHASIS =} -) { + sixteenth: time = 200 msec, + rhythm: int = {= MERENGUE =}, + emphasis: int = {= MERENGUE_EMPHASIS =}) { output note: float - + logical action tick - // Count of sixteenth notes. - state count: int = 0 + state count: int = 0 // Count of sixteenth notes. - reaction(startup) -> note, tick {= - lf_schedule(tick, self->sixteenth); - =} + reaction(startup) -> note, tick {= lf_schedule(tick, self->sixteenth); =} reaction(tick) -> note, tick {= int position = 1 << self->count; @@ -73,6 +67,6 @@ reactor RhythmSource( main reactor { source = new RhythmSource() - play = new PlayWaveform(default_waveform_id = 1) + play = new PlayWaveform(default_waveform_id=1) source.note -> play.note } diff --git a/C/src/robot/CompositeRobot.lf b/C/src/robot/CompositeRobot.lf index dd8a91c2..394f8308 100644 --- a/C/src/robot/CompositeRobot.lf +++ b/C/src/robot/CompositeRobot.lf @@ -1,8 +1,7 @@ /** - * This is a (very incomplete) starting point for a composite robot - * controller, where a handler coordinates control of a gripper, - * an autonomous vehicle, and a robot arm. - * + * This is a (very incomplete) starting point for a composite robot controller, where a handler + * coordinates control of a gripper, an autonomous vehicle, and a robot arm. + * * @author Yi Peng Zhu * @author Edward A. Lee */ @@ -11,65 +10,69 @@ target C import PoissonClock from "../lib/PoissonClock.lf" reactor Handler { - input job:int - input pause:bool // true to pause, false to resume - input arm_status:bool // true for done, false for error - input agv_status:int // batter full/low, errors, etc. - input gripper_status:int - - output move_arm:int[3] // assuming vector command - output pause_arm:bool // true to pause, false to resume - output detect:bool // to request vision detection - output move_agv:int[3] // assuming vector command - output pause_agv:bool // true to pause, false to resume - output gripper_open:bool // true to open, false to close - output pause_gripper:bool - + input job: int + input pause: bool // true to pause, false to resume + input arm_status: bool // true for done, false for error + input agv_status: int // batter full/low, errors, etc. + input gripper_status: int + + output move_arm: int[3] // assuming vector command + output pause_arm: bool // true to pause, false to resume + output detect: bool // to request vision detection + output move_agv: int[3] // assuming vector command + output pause_agv: bool // true to pause, false to resume + output gripper_open: bool // true to open, false to close + output pause_gripper: bool + initial mode IDLE { - reaction(job) -> HANDLING {= + reaction(job) -> reset(HANDLING) {= lf_print("Received job at %lld ns", lf_time_logical_elapsed()); lf_set_mode(HANDLING); =} } + mode HANDLING { - reaction(arm_status, agv_status, gripper_status) -> IDLE {= - lf_set_mode(IDLE); - =} - reaction(job) {= - lf_print("Job rejected at %lld ns. Busy.", lf_time_logical_elapsed()); - =} + reaction(arm_status, agv_status, gripper_status) -> reset(IDLE) {= lf_set_mode(IDLE); =} + + reaction(job) {= lf_print("Job rejected at %lld ns. Busy.", lf_time_logical_elapsed()); =} } } + reactor Jobs { - output job:int - state job_count:int(0) - + output job: int + state job_count: int = 0 + p = new PoissonClock() - reaction(p.event) -> job {= - lf_set(job, self->job_count++); - =} + + reaction(p.event) -> job {= lf_set(job, self->job_count++); =} } + reactor ManualControl { - output pause:bool // true to pause, false to resume + output pause: bool // true to pause, false to resume } + reactor Arm { - input move:int[3] - input[2] pause:bool - output status:bool // true for done, false for error + input move: int[3] + input[2] pause: bool + output status: bool // true for done, false for error } + reactor Vision { - input detect:bool + input detect: bool } + reactor AGV { - input move:int[3] - input[2] pause:bool - output status:int + input move: int[3] + input[2] pause: bool + output status: int } + reactor Gripper { - input open:bool // true for open, false for close - input[2] pause:bool - output status:int + input open: bool // true for open, false for close + input[2] pause: bool + output status: int } + main reactor { h = new Handler() j = new Jobs() @@ -78,7 +81,7 @@ main reactor { i = new Vision() v = new AGV() g = new Gripper() - + j.job -> h.job m.pause -> h.pause m.pause, h.pause_arm -> a.pause diff --git a/C/src/rosace/AircraftSimulator.lf b/C/src/rosace/AircraftSimulator.lf index 35ad161a..5d6baa18 100644 --- a/C/src/rosace/AircraftSimulator.lf +++ b/C/src/rosace/AircraftSimulator.lf @@ -1,34 +1,33 @@ /** * Aircraft model from the ROSACE case study, from: - * - * Claire Pagetti , David Saussiéy, Romain Gratia , Eric Noulard , Pierre Siron, - * "The ROSACE Case Study: From Simulink Specification to Multi/Many-Core Execution," - * RTAS (2014). - * + * + * Claire Pagetti , David Saussiéy, Romain Gratia , Eric Noulard , Pierre Siron, "The ROSACE Case + * Study: From Simulink Specification to Multi/Many-Core Execution," RTAS (2014). + * * This implementation is based on code from: - * - * Deschamps, Henrick and Cappello, Gerlando and Cardoso, Janette and Siron, Pierre - * Coincidence Problem in CPS Simulations: the R-ROSACE Case Study. - * (2018) In: 9th European Congress Embedded Real Time Software and Systems ERTS2 2018, - * 31 January 2018 - 2 February 2018 (Toulouse, France). + * + * Deschamps, Henrick and Cappello, Gerlando and Cardoso, Janette and Siron, Pierre Coincidence + * Problem in CPS Simulations: the R-ROSACE Case Study. (2018) In: 9th European Congress Embedded + * Real Time Software and Systems ERTS2 2018, 31 January 2018 - 2 February 2018 (Toulouse, France). * https://www.erts2018.org/authors_detail_inverted_Cappello%20Gerlando.html - * + * * The code was download from https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. - * + * * Since that original code bears an LGPL license, so does this program: - * + * * (c) 2023 Regents of the University of California, LGPL-v3 * (https://www.gnu.org/licenses/lgpl-3.0.html) * - * This is a forward Euler simulation of an aircraft designed to run at 200 Hz. - * The inputs are elevator controls `delta_ec` and throttle controls `delta_thc`. - * This runs at a fixed 200 Hz rate reading the most recent inputs. - * + * This is a forward Euler simulation of an aircraft designed to run at 200 Hz. The inputs are + * elevator controls `delta_ec` and throttle controls `delta_thc`. This runs at a fixed 200 Hz rate + * reading the most recent inputs. + * * @author Edward A. Lee * @author David Saussie * @author Claire Pagetti */ target C + preamble {= #include // Trimming parameters @@ -41,51 +40,48 @@ preamble {= #define delta_e_eq (0.012009615652468) #endif =} + reactor AircraftDynamics( - period: time(5 ms), - // Trimming parameters - theta_eq: double(0.026485847681737), // Initial angle - - // Atmosphere parameters - rho0: double(1.225), - g0: double(9.80665), // Acceleration of gravity in m/s - T0_0: double(288.15), - T0_h: double(-0.0065), - Rs: double(287.05), - - // Aircraft parameters - masse: double(57837.5), - I_y: double(3781272.0), - S: double(122.6), - cbar: double(4.29), - CD_0: double(0.016), - CD_alpha: double(2.5), - CD_deltae: double(0.05), - CL_alpha: double(5.5), - CL_deltae: double(0.193), - alpha_0: double(-0.05), - Cm_0: double(0.04), - Cm_alpha: double(-0.83), - Cm_deltae: double(-1.5), - Cm_q: double(-30) -) { - timer t(0, period) // 200 Hz Forward Euler simulation rate - - input T: double // Thrust - input delta_e: double // Elevator control - - output Vz:double // Vertical speed - output Va:double // True airspeed - output h:double // Altitude - output az:double // Vertical acceleration - output q:double // Pitch rate - - state u: double(0.0) - state w: double(0.0) - state q: double(0.0) - state theta: double(0.0) // Angle (0.0 is horizontal) - state h: double(0.0) - + period: time = 5 ms, + // Trimming parameters + // Initial angle + theta_eq: double = 0.026485847681737, + rho0: double = 1.225, // Atmosphere parameters + g0: double = 9.80665, // Acceleration of gravity in m/s + T0_0: double = 288.15, + T0_h: double = -0.0065, + Rs: double = 287.05, + masse: double = 57837.5, // Aircraft parameters + I_y: double = 3781272.0, + S: double = 122.6, + cbar: double = 4.29, + CD_0: double = 0.016, + CD_alpha: double = 2.5, + CD_deltae: double = 0.05, + CL_alpha: double = 5.5, + CL_deltae: double = 0.193, + alpha_0: double = -0.05, + Cm_0: double = 0.04, + Cm_alpha: double = -0.83, + Cm_deltae: double = -1.5, + Cm_q: double = -30) { + timer t(0, period) // 200 Hz Forward Euler simulation rate + + input T: double // Thrust + input delta_e: double // Elevator control + + output Vz: double // Vertical speed + output Va: double // True airspeed + output h: double // Altitude + output az: double // Vertical acceleration + output q: double // Pitch rate + + state u: double = 0.0 + state w: double = 0.0 + state q: double = 0.0 + state theta: double = 0.0 // Angle (0.0 is horizontal) + state h: double = 0.0 + reaction(startup) {= self->u = Va_eq * cos(self->theta_eq); // Horizontal speed. self->w = Va_eq * sin(self->theta_eq); // Vertical speed. @@ -93,15 +89,15 @@ reactor AircraftDynamics( self->theta = self->theta_eq; self->h = h_eq; =} - + reaction(t) delta_e, T -> Vz, Va, h, az, q {= const double dt = ((double)self->period)/1e9; // Period in seconds. (1.0/200.0) - + double u_dot, w_dot, q_dot, theta_dot, h_dot; double CD, CL, Cm; double Xa, Za, Ma; double alpha, qbar, V, rho; - + rho = self->rho0 * pow(1.0 + self->T0_h / self->T0_0 * self->h,- self->g0 / (self->Rs * self->T0_h) - 1.0); alpha = atan(self->w / self->u); V = sqrt(self->u * self->u + self->w * self->w); @@ -112,7 +108,7 @@ reactor AircraftDynamics( Xa = - qbar * self->S * (CD * cos(alpha) - CL * sin(alpha)); Za = - qbar * self->S * (CD * sin(alpha) + CL * cos(alpha)); Ma = qbar * self->cbar * self->S * Cm; - + // Output lf_set(Va, V); lf_set(Vz, self->w * cos(self->theta) - self->u * sin(self->theta)); @@ -126,7 +122,7 @@ reactor AircraftDynamics( q_dot = Ma / self->I_y; theta_dot = self->q; h_dot = self->u * sin(self->theta) - self->w * cos(self->theta); - + // Update State self->u += dt * u_dot; self->w += dt * w_dot; @@ -136,22 +132,16 @@ reactor AircraftDynamics( =} } -reactor Engine( - period: time(5 ms), - scale: double(26350.0), - tau: double(0.75) -) { - input delta_thc: double // Engine control - output T: double // Thrust - - timer t(0, period) // 200 Hz Forward Euler simulation rate - - state x1: double({= delta_th_eq =}) - - reaction(t) -> T {= - lf_set(T, self->scale * self->x1); - =} - +reactor Engine(period: time = 5 ms, scale: double = 26350.0, tau: double = 0.75) { + input delta_thc: double // Engine control + output T: double // Thrust + + timer t(0, period) // 200 Hz Forward Euler simulation rate + + state x1: double = {= delta_th_eq =} + + reaction(t) -> T {= lf_set(T, self->scale * self->x1); =} + reaction(t) delta_thc {= const double dt = ((double)self->period)/1e9; // Period in seconds. (1.0/200.0) @@ -162,32 +152,26 @@ reactor Engine( =} } -reactor Elevator( - period: time(5 ms), - omega: double(25.0), - xi: double(0.85) -) { - input delta_ec: double // Elevator control +reactor Elevator(period: time = 5 ms, omega: double = 25.0, xi: double = 0.85) { + input delta_ec: double // Elevator control output delta_e: double - - timer t(0, period) // 200 Hz Forward Euler simulation rate - - state x1: double({= delta_e_eq =}) - state x2: double(0.0) - - reaction(t) -> delta_e {= - lf_set(delta_e, self->x1); - =} - + + timer t(0, period) // 200 Hz Forward Euler simulation rate + + state x1: double = {= delta_e_eq =} + state x2: double = 0.0 + + reaction(t) -> delta_e {= lf_set(delta_e, self->x1); =} + reaction(t) delta_ec {= const double dt = ((double)self->period)/1e9; // Period in seconds. (1.0/200.0) - + // State Equation double x1_dot = self->x2; - double x2_dot = -self->omega * self->omega * self->x1 + double x2_dot = -self->omega * self->omega * self->x1 - 2.0 * self->xi * self->omega * self->x2 + self->omega * self->omega * delta_ec->value; - + // Update State self->x1 += dt * x1_dot; self->x2 += dt * x2_dot; @@ -195,22 +179,22 @@ reactor Elevator( } reactor Aircraft( - period: time(5 ms), - theta_eq: double(0.026485847681737) // Initial angle -) { - input delta_thc: double // Engine control - input delta_ec: double // Elevator control - - output Vz:double // Vertical speed - output Va:double // True airspeed - output h:double // Altitude - output az:double // Vertical acceleration - output q:double // Pitch rate - - a = new AircraftDynamics(period = period, theta_eq = theta_eq) - e = new Engine(period = period) - el = new Elevator(period = period) - + period: time = 5 ms, + // Initial angle + theta_eq: double = 0.026485847681737) { + input delta_thc: double // Engine control + input delta_ec: double // Elevator control + + output Vz: double // Vertical speed + output Va: double // True airspeed + output h: double // Altitude + output az: double // Vertical acceleration + output q: double // Pitch rate + + a = new AircraftDynamics(period=period, theta_eq=theta_eq) + e = new Engine(period=period) + el = new Elevator(period=period) + a.Vz, a.Va, a.h, a.az, a.q -> Vz, Va, h, az, q delta_thc -> e.delta_thc e.T -> a.T diff --git a/C/src/rosace/Rosace.lf b/C/src/rosace/Rosace.lf index 83ddd7f7..127efcd4 100644 --- a/C/src/rosace/Rosace.lf +++ b/C/src/rosace/Rosace.lf @@ -1,34 +1,30 @@ /** * ROSACE case study, from: - * - * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, - * "The ROSACE Case Study: From Simulink Specification to Multi/Many-Core Execution," - * RTAS (2014). - * + * + * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, "The ROSACE Case + * Study: From Simulink Specification to Multi/Many-Core Execution," RTAS (2014). + * * This implementation is based on code from: - * - * https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. + * + * https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. * * Since that original code bears an LGPL license, so does this program: - * + * * (c) 2023 Regents of the University of California, LGPL-v3 * (https://www.gnu.org/licenses/lgpl-3.0.html) - * - * This program uses a forward Euler simulation of aircraft dynamics and implements - * throttle and elevator control. The parameters specified execute an elevation - * climb from an initial 10,000 feet to a target 11,000 feet. - * - * The style of execution is that each component has a `period` parameter that - * determines the frequency at which it runs. The program relies on persistence - * of inputs in the C target and on the dependency analysis of the *uses* field - * of reactor signature. - * - * To run this, it should be sufficient to just run `lfc` to compile it. - * The "build" parameter of the target provides a script that compiles the - * generated code, runs it, runs gnuplot to generate the plots, and then - * opens the resulting PDF files. If this script fails for any reason, - * comment the `build` attribute of the target declaration and do this by hand: - * + * + * This program uses a forward Euler simulation of aircraft dynamics and implements throttle and + * elevator control. The parameters specified execute an elevation climb from an initial 10,000 feet + * to a target 11,000 feet. + * + * The style of execution is that each component has a `period` parameter that determines the + * frequency at which it runs. The program relies on persistence of inputs in the C target and on + * the dependency analysis of the *uses* field of reactor signature. + * + * To run this, it should be sufficient to just run `lfc` to compile it. The "build" parameter of + * the target provides a script that compiles the generated code, runs it, runs gnuplot to generate + * the plots, and then opens the resulting PDF files. If this script fails for any reason, comment + * the `build` attribute of the target declaration and do this by hand: * ``` * cd examples-lingua-franca/C * lfc src/rosace/Rosace.lf @@ -36,19 +32,17 @@ * gnuplot src/rosace/rosace.gnuplot * open rosace.pdf * ``` - * - * You should see a smooth climb from 10,000 feet to 11,000 feet. - * You can experiment, for example, with the period with which the filters - * sample the sensor outputs from the aircraft model. If you change the - * `filter_period` parameter of the `RosaceController` from its default - * 10 ms to 100 ms, for example, you will far worse behavior from the - * aircraft. - * + + * + * You should see a smooth climb from 10,000 feet to 11,000 feet. You can experiment, for example, + * with the period with which the filters sample the sensor outputs from the aircraft model. If you + * change the `filter_period` parameter of the `RosaceController` from its default 10 ms to 100 ms, + * for example, you will far worse behavior from the aircraft. + * * @author Edward A. Lee * @author David Saussie * @author Claire Pagetti */ - target C { fast: true, build: "./build_run_plot.sh Rosace", @@ -66,42 +60,38 @@ preamble {= #define h_eq (10000.0) #define delta_th_eq (1.5868660794926) - #define delta_e_eq (0.012009615652468) + #define delta_e_eq (0.012009615652468) =} // Documentation @icon("Variables.png") -reactor Variables {} +reactor Variables { +} -reactor Command( - period: time(100 ms), - value: double(0.0) -) { +reactor Command(period: time = 100 ms, value: double = 0.0) { timer t(0, period) - output c:double - reaction(t) -> c {= - lf_set(c, self->value); - =} + output c: double + + reaction(t) -> c {= lf_set(c, self->value); =} } -main reactor(filter_period: time(10 ms)) { +main reactor(filter_period: time = 10 ms) { variables = new Variables() a = new Aircraft() - c = new RosaceController(filter_period = filter_period) - altitude = new Command(value = 11000) // Altitude command - speed = new Command(value = 0.0) // Delta airspeed from nominal Va_eq (230) - - p_h = new PrintToFile(filename = "altitude.data") - p_Va = new PrintToFile(filename = "airspeed.data") - + c = new RosaceController(filter_period=filter_period) + altitude = new Command(value=11000) // Altitude command + speed = new Command(value=0.0) // Delta airspeed from nominal Va_eq (230) + + p_h = new PrintToFile(filename="altitude.data") + p_Va = new PrintToFile(filename="airspeed.data") + a.h, a.az, a.Vz, a.q, a.Va -> c.h, c.az, c.Vz, c.q, c.Va - altitude.c -> c.c + altitude.c -> c.c speed.c -> c.s - + c.delta_ec -> a.delta_ec c.delta_thc -> a.delta_thc - - // Print connections. - a.h -> p_h.y + + a.h -> p_h.y // Print connections. a.Va -> p_Va.y } diff --git a/C/src/rosace/RosaceController.lf b/C/src/rosace/RosaceController.lf index c0262e86..eb7a45d2 100644 --- a/C/src/rosace/RosaceController.lf +++ b/C/src/rosace/RosaceController.lf @@ -1,37 +1,33 @@ /** * Controller for the ROSACE case study, from: - * - * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, - * "The ROSACE Case Study: From Simulink Specification to Multi/Many-Core Execution," - * RTAS (2014). - * + * + * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, "The ROSACE Case + * Study: From Simulink Specification to Multi/Many-Core Execution," RTAS (2014). + * * This implementation is based on code from: - * - * Deschamps, Henrick and Cappello, Gerlando and Cardoso, Janette and Siron, Pierre - * Coincidence Problem in CPS Simulations: the R-ROSACE Case Study. - * (2018) In: 9th European Congress Embedded Real Time Software and Systems ERTS2 2018, - * 31 January 2018 - 2 February 2018 (Toulouse, France). + * + * Deschamps, Henrick and Cappello, Gerlando and Cardoso, Janette and Siron, Pierre Coincidence + * Problem in CPS Simulations: the R-ROSACE Case Study. (2018) In: 9th European Congress Embedded + * Real Time Software and Systems ERTS2 2018, 31 January 2018 - 2 February 2018 (Toulouse, France). * https://www.erts2018.org/authors_detail_inverted_Cappello%20Gerlando.html - * + * * The code was download from https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. - * + * * Since that original code bears an LGPL license, so does this program: - * + * * (c) 2023 Regents of the University of California, LGPL-v3 * (https://www.gnu.org/licenses/lgpl-3.0.html) - * + * * This program implements throttle and elevator control. - * - * The style of execution is that each component has a `period` parameter that - * determines the frequency at which it runs. The program relies on persistence - * of inputs in the C target and on the dependency analysis of the *uses* field - * of reactor signature. - * + * + * The style of execution is that each component has a `period` parameter that determines the + * frequency at which it runs. The program relies on persistence of inputs in the C target and on + * the dependency analysis of the *uses* field of reactor signature. + * * @author Edward A. Lee * @author David Saussie * @author Claire Pagetti */ - target C preamble {= @@ -47,76 +43,64 @@ preamble {= =} reactor Filter( - period: time(10 ms), - - a:double[](0.0, 0.0), - b:double[](0.0, 0.0), - - init_x1:double(0.0), - init_x2:double(0.0) -) { - input x:double - output y:double - - state x1: double(0.0) - state x2: double(0.0) + period: time = 10 ms, + a: double[] = {0.0, 0.0}, + b: double[] = {0.0, 0.0}, + init_x1: double = 0.0, + init_x2: double = 0.0) { + input x: double + output y: double + + state x1: double = 0.0 + state x2: double = 0.0 timer t(0, period) - + reaction(startup) {= self->x1 = self->init_x1; self->x2 = self->init_x2; =} - - reaction(t) -> y {= - lf_set(y, self->x2); - =} - + + reaction(t) -> y {= lf_set(y, self->x2); =} + reaction(t) x {= double x1_tmp = - self->a[0] * self->x2 + self->b[0] * x->value; double x2_tmp = self->x1 - self->a[1] * self->x2 + self->b[1] * x->value; // Update self->x1 = x1_tmp; - self->x2 = x2_tmp; + self->x2 = x2_tmp; =} } -reactor Command( - period: time(100 ms), - value: double(0.0) -) { +reactor Command(period: time = 100 ms, value: double = 0.0) { timer t(0, period) - output c:double - reaction(t) -> c {= - lf_set(c, self->value); - =} + output c: double + + reaction(t) -> c {= lf_set(c, self->value); =} } reactor Hold( - period:time(20 ms), - - Kp_h: double(0.1014048), - Ki_h: double(0.0048288), - h_switch: double(50.0), - - Vz_c: double(-2.5), - Va_c: double(0.0), - h_c: double(11000) -) { + period: time = 20 ms, + Kp_h: double = 0.1014048, + Ki_h: double = 0.0048288, + h_switch: double = 50.0, + Vz_c: double = -2.5, + Va_c: double = 0.0, + h_c: double = 11000) { timer t(0, period) - + input s: double // Set point input x: double // Measurement - + output c: double // Command - state integrator: double(532.2730285) - + state integrator: double = 532.2730285 + reaction(t) s, x -> c {= double y = 0.0; double Ts_h = ((double)self->period)/1e9; // Period in seconds. (1.0/50.0) - + if ((x->value - s->value) < -self->h_switch) { // Output y = self->Vz_c; @@ -129,145 +113,135 @@ reactor Hold( // State self->integrator += Ts_h * (x->value - s->value); } - + lf_set(c, y); =} } reactor VzControl( - period:time(20 ms), - - K2_intVz: double(0.000627342822264), - K2_Vz: double(-0.003252836726554), - K2_q: double(0.376071446897134), - K2_az: double(-0.001566907423747) -) { + period: time = 20 ms, + K2_intVz: double = 0.000627342822264, + K2_Vz: double = -0.003252836726554, + K2_q: double = 0.376071446897134, + K2_az: double = -0.001566907423747) { timer t(0, period) - + input Vzc: double input azf: double input Vzf: double input qf: double - + output delta_ec: double - - state integrator: double(0.0) + + state integrator: double = 0.0 reaction(t) Vzc, azf, Vzf, qf -> delta_ec {= - double Ts_K2 = ((double)self->period)/1e9; // Period in seconds. (1.0/50.0) - + // Output double y = self->K2_intVz * self->integrator - + self->K2_Vz * Vzf->value + + self->K2_Vz * Vzf->value + self->K2_q * qf->value + self->K2_az * azf->value + delta_e_eq; // State self->integrator += Ts_K2 * (Vzc->value - Vzf->value); - + lf_set(delta_ec, y); =} } reactor VaControl( - period: time(20 ms), - K1_intVa: double(0.049802610664357), - K1_Va: double(-0.486813084356079), - K1_Vz: double(-0.077603095495388), - K1_q: double(21.692383376322041) -) { + period: time = 20 ms, + K1_intVa: double = 0.049802610664357, + K1_Va: double = -0.486813084356079, + K1_Vz: double = -0.077603095495388, + K1_q: double = 21.692383376322041) { timer t(0, period) - + input Vzf: double input Vaf: double input Vac: double input qf: double - + output delta_thc: double - - state integrator: double(0.0) - + + state integrator: double = 0.0 + reaction(t) Vzf, Vaf, Vac, qf -> delta_thc {= double Ts_K1 = ((double)self->period)/1e9; // Period in seconds. (1.0/50.0) - + // Output - double y = self->K1_intVa * self->integrator + double y = self->K1_intVa * self->integrator + self->K1_Va * (Vaf->value - Va_eq) + self->K1_Vz * Vzf->value + self->K1_q * qf->value + delta_th_eq; - + // State self->integrator += Ts_K1 * (Vac->value - Vaf->value + Va_eq); - + lf_set(delta_thc, y); - =} } -reactor RosaceController( - filter_period: time(10 ms) -) { +reactor RosaceController(filter_period: time = 10 ms) { // Sensor inputs from aircraft - input Vz:double // Vertical speed - input Va:double // True airspeed - input h:double // Altitude measurement - input az:double // Vertical acceleration - input q:double // Pitch rate - + // Vertical speed + input Vz: double + input Va: double // True airspeed + input h: double // Altitude measurement + input az: double // Vertical acceleration + input q: double // Pitch rate + // Command inputs - input c:double // Altitude command - input s:double // Speed command - - output delta_thc: double // Engine control - output delta_ec: double // Elevator control - + // Altitude command + input c: double + input s: double // Speed command + + output delta_thc: double // Engine control + output delta_ec: double // Elevator control + h_c = new Hold() - + h_f = new Filter( - period = 100 ms, - init_x1 = {= h_eq * (1.0 - 1.477888930110354 /*a[1]*/ - 0.049596808318647 /*b1*/) =}, - init_x2 = {= h_eq =}, - a = (0.586756156020839, -1.477888930110354), - b = (0.049596808318647, 0.059270417591839) - ) + period = 100 ms, + init_x1 = {= h_eq * (1.0 - 1.477888930110354 /*a[1]*/ - 0.049596808318647 /*b1*/) =}, + init_x2 = {= h_eq =}, + a = {0.586756156020839, -1.477888930110354}, + b = {0.049596808318647, 0.059270417591839}) az_f = new Filter( - period = filter_period, - init_x1 = 0.0, - init_x2 = 0.0, - a = (0.169118914523145, -0.518588903229759), - b = (0.229019233988375, 0.421510777305010) - ) + period=filter_period, + init_x1=0.0, + init_x2=0.0, + a = {0.169118914523145, -0.518588903229759}, + b = {0.229019233988375, 0.421510777305010}) Vz_f = new Filter( - period = filter_period, - init_x1 = 0.0, - init_x2 = 0.0, - a = (0.914975803093201, -1.911199519984605), - b = (0.001860178914816, 0.001916104193780) - ) + period=filter_period, + init_x1=0.0, + init_x2=0.0, + a = {0.914975803093201, -1.911199519984605}, + b = {0.001860178914816, 0.001916104193780}) q_f = new Filter( - period = filter_period, - init_x1 = 0.0, - init_x2 = 0.0, - a = (0.586756156020839, -1.477888930110354), - b = (0.049596808318647, 0.059270417591839) - ) + period=filter_period, + init_x1=0.0, + init_x2=0.0, + a = {0.586756156020839, -1.477888930110354}, + b = {0.049596808318647, 0.059270417591839}) Va_f = new Filter( - period = filter_period, - init_x1 = {= Va_eq * (1.0 - 1.911199519984605 /*a[1]*/ - 0.001916104193780 /*b[1]*/) =}, - init_x2 = {= Va_eq =}, - a = (0.914975803093201, -1.911199519984605), - b = (0.001860178914816, 0.001916104193780) - ) - + period=filter_period, + init_x1 = {= Va_eq * (1.0 - 1.911199519984605 /*a[1]*/ - 0.001916104193780 /*b[1]*/) =}, + init_x2 = {= Va_eq =}, + a = {0.914975803093201, -1.911199519984605}, + b = {0.001860178914816, 0.001916104193780}) + Vz_ct = new VzControl() Va_ct = new VaControl() - + h, az, Vz, q, Va -> h_f.x, az_f.x, Vz_f.x, q_f.x, Va_f.x c -> h_c.s h_f.y -> h_c.x - + Vz_f.y, az_f.y, h_c.c, q_f.y -> Vz_ct.Vzf, Vz_ct.azf, Vz_ct.Vzc, Vz_ct.qf Vz_ct.delta_ec -> delta_ec - + Va_f.y, Vz_f.y, s, q_f.y -> Va_ct.Vaf, Va_ct.Vzf, Va_ct.Vac, Va_ct.qf Va_ct.delta_thc -> delta_thc } diff --git a/C/src/rosace/RosaceWithUI.lf b/C/src/rosace/RosaceWithUI.lf index 0d54b2f2..884d4f16 100644 --- a/C/src/rosace/RosaceWithUI.lf +++ b/C/src/rosace/RosaceWithUI.lf @@ -1,34 +1,30 @@ /** * ROSACE case study, from: - * - * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, - * "The ROSACE Case Study: From Simulink Specification to Multi/Many-Core Execution," - * RTAS (2014). - * + * + * Claire Pagetti , David Saussié, Romain Gratia , Eric Noulard , Pierre Siron, "The ROSACE Case + * Study: From Simulink Specification to Multi/Many-Core Execution," RTAS (2014). + * * This implementation is based on code from: - * - * https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. + * + * https://svn.onera.fr/schedmcore/branches/ROSACE_CaseStudy/. * * Since that original code bears an LGPL license, so does this program: - * + * * (c) 2023 Regents of the University of California, LGPL-v3 * (https://www.gnu.org/licenses/lgpl-3.0.html) - * - * This program uses a forward Euler simulation of aircraft dynamics and implements - * throttle and elevator control. The parameters specified execute an elevation - * climb from an initial 10,000 feet to a target 11,000 feet. - * - * The style of execution is that each component has a `period` parameter that - * determines the frequency at which it runs. The program relies on persistence - * of inputs in the C target and on the dependency analysis of the *uses* field - * of reactor signature. - * - * To run this, it should be sufficient to just run `lfc` to compile it. - * The "build" parameter of the target provides a script that compiles the - * generated code, runs it, runs gnuplot to generate the plots, and then - * opens the resulting PDF files. If this script fails for any reason, - * comment the `build` attribute of the target declaration and do this by hand: - * + * + * This program uses a forward Euler simulation of aircraft dynamics and implements throttle and + * elevator control. The parameters specified execute an elevation climb from an initial 10,000 feet + * to a target 11,000 feet. + * + * The style of execution is that each component has a `period` parameter that determines the + * frequency at which it runs. The program relies on persistence of inputs in the C target and on + * the dependency analysis of the *uses* field of reactor signature. + * + * To run this, it should be sufficient to just run `lfc` to compile it. The "build" parameter of + * the target provides a script that compiles the generated code, runs it, runs gnuplot to generate + * the plots, and then opens the resulting PDF files. If this script fails for any reason, comment + * the `build` attribute of the target declaration and do this by hand: * ``` * cd examples-lingua-franca/C * lfc src/rosace/Rosace.lf @@ -36,19 +32,17 @@ * gnuplot src/rosace/rosace.gnuplot * open rosace.pdf * ``` - * - * You should see a smooth climb from 10,000 feet to 11,000 feet. - * You can experiment, for example, with the period with which the filters - * sample the sensor outputs from the aircraft model. If you change the - * `filter_period` parameter of the `RosaceController` from its default - * 10 ms to 100 ms, for example, you will far worse behavior from the - * aircraft. - * + + * + * You should see a smooth climb from 10,000 feet to 11,000 feet. You can experiment, for example, + * with the period with which the filters sample the sensor outputs from the aircraft model. If you + * change the `filter_period` parameter of the `RosaceController` from its default 10 ms to 100 ms, + * for example, you will far worse behavior from the aircraft. + * * @author Edward A. Lee * @author David Saussie * @author Claire Pagetti */ - target C { keepalive: true, timeout: 10 min @@ -56,7 +50,6 @@ target C { import Aircraft from "AircraftSimulator.lf" import RosaceController from "RosaceController.lf" - import ServerUI from "../browser-ui/BrowserUI.lf" preamble {= @@ -66,30 +59,30 @@ preamble {= #define h_eq (10000.0) #define delta_th_eq (1.5868660794926) - #define delta_e_eq (0.012009615652468) - - #include // Define strtol() + #define delta_e_eq (0.012009615652468) + + #include // Define strtol() =} reactor UserInterface( - period: time = 100 ms, - altitude_target: double = 10000, - airspeed_delta: double = 0.0 // From nominal Va_eq (230) -) { - timer command(0, period) // Frequency with which to issue commands. - output altitude:double - output airspeed:double + period: time = 100 ms, + altitude_target: double = 10000, + // From nominal Va_eq (230) + airspeed_delta: double = 0.0) { + timer command(0, period) // Frequency with which to issue commands. + output altitude: double + output airspeed: double + + input Va: double + input h: double + + s = new ServerUI() - input Va:double - input h:double - reaction(command) -> altitude, airspeed {= lf_set(altitude, self->altitude_target); lf_set(airspeed, self->airspeed_delta); =} - - s = new ServerUI() - + reaction(s.request) Va, h -> s.response {= char* response; if(strncmp("/data", s.request->value, 5) == 0) { @@ -111,17 +104,16 @@ reactor UserInterface( main reactor(filter_period: time = 10 ms) { a = new Aircraft() - c = new RosaceController(filter_period = filter_period) + c = new RosaceController(filter_period=filter_period) ui = new UserInterface() - + a.h, a.az, a.Vz, a.q, a.Va -> c.h, c.az, c.Vz, c.q, c.Va - ui.altitude -> c.c + ui.altitude -> c.c ui.airspeed -> c.s - + c.delta_ec -> a.delta_ec c.delta_thc -> a.delta_thc - - // Print connections. - a.h -> ui.h + + a.h -> ui.h // Print connections. a.Va -> ui.Va } diff --git a/C/src/sdv/ParkingAssist.lf b/C/src/sdv/ParkingAssist.lf index e139db06..3373a0e3 100644 --- a/C/src/sdv/ParkingAssist.lf +++ b/C/src/sdv/ParkingAssist.lf @@ -1,11 +1,11 @@ /** * This is an illustration of one way to build a software-defined vehicle. The program has three * components, one that emulates the sensors in a vehicle, one that provides a dashboard display, - * and one that provides sound alerts. The ParkingAssist.html page provides a user interface - * with a dashboard display at the top and controls at the bottom. - * - * The program assumes that the vehicle starts in a position with an obstacle in front of it. - * FIXME: more detail. + * and one that provides sound alerts. The ParkingAssist.html page provides a user interface with a + * dashboard display at the top and controls at the bottom. + * + * The program assumes that the vehicle starts in a position with an obstacle in front of it. FIXME: + * more detail. * * The dashboard display is emulated by an HTML page that connects to this application via a web * socket. After starting the program, open ParkingAssist.html in your favorite browser and a simple @@ -48,78 +48,73 @@ import PlayWaveform from "../rhythm/PlayWaveform.lf" * it at the specified initial front distance. */ reactor Sensors( - sample_interval: time = 100 ms, - initial_front_distance: int = 100 // In pixels -) { + sample_interval: time = 100 ms, + // In pixels + initial_front_distance: int = 100) { preamble {= - #include // Defines abs() - #include // Defines lround() + #include // Defines abs() + #include // Defines lround() =} - output speed: int // In pixels/second + output speed: int // In pixels/second output front_distance: int // In pixels state velocity: double = 0 // In pixels/second state position: double = 0 // In pixels - state gear: int = 1 // 1 for forward, -1 for reverse. + state gear: int = 1 // 1 for forward, -1 for reverse. timer t(0, sample_interval) - s = new WebSocketServer(hostport=240, max_clients = 1) // Allow only one client - + s = new WebSocketServer(hostport=240, max_clients=1) // Allow only one client + /** - * Given JSON of the form {..., "key":"value", ... }, if the value is an int, - * put that int value into the result and return 0. Otherwise, return -1 if the - * key is not present, -2 if the value is not an int, and -3 if the JSON string - * does not have the expected form. + * Given JSON of the form {..., "key":"value", ... }, if the value is an int, put that int value + * into the result and return 0. Otherwise, return -1 if the key is not present, -2 if the value + * is not an int, and -3 if the JSON string does not have the expected form. */ - method json_to_int( - json: {= const char* =}, - key: {= const char* =}, - result: int* - ): int {= - char* key_position = strstr(json, key); - if (key_position == NULL) return -1; - // Check that the next character is closing quotation mark. - if (strncmp((key_position + strlen(key)), "\"", 1) != 0) return -3; - // Find the opening quotation mark for the value string. - char* start = strchr((key_position + strlen(key) + 1), '"'); - if (start == NULL) return -3; - *result = atoi(start + 1); - return 0; + method json_to_int(json: {= const char* =}, key: {= const char* =}, result: int*): int {= + char* key_position = strstr(json, key); + if (key_position == NULL) return -1; + // Check that the next character is closing quotation mark. + if (strncmp((key_position + strlen(key)), "\"", 1) != 0) return -3; + // Find the opening quotation mark for the value string. + char* start = strchr((key_position + strlen(key) + 1), '"'); + if (start == NULL) return -3; + *result = atoi(start + 1); + return 0; =} - + reaction(s.received) {= - char* json = s.received->value->message; - lf_print("Received control: %s", json); - int accelerator; - if (json_to_int(json, "accelerator", &accelerator) == 0) { - // FIXME: Setting the velocity to be a simple linear - // function of the accelerator position. Pretty naive - // vehicle model. - self->velocity = accelerator * 0.1; - } - int reset_value; - if (json_to_int(json, "reset", &reset_value) == 0) { - self->velocity = 0.0; - self->position = 0.0; - self->gear = 1; - } - int gear; - if (json_to_int(json, "gear", &gear) == 0) { - self->gear = gear; - } + char* json = s.received->value->message; + lf_print("Received control: %s", json); + int accelerator; + if (json_to_int(json, "accelerator", &accelerator) == 0) { + // FIXME: Setting the velocity to be a simple linear + // function of the accelerator position. Pretty naive + // vehicle model. + self->velocity = accelerator * 0.1; + } + int reset_value; + if (json_to_int(json, "reset", &reset_value) == 0) { + self->velocity = 0.0; + self->position = 0.0; + self->gear = 1; + } + int gear; + if (json_to_int(json, "gear", &gear) == 0) { + self->gear = gear; + } =} reaction(t) -> speed, front_distance {= - lf_set(speed, abs((int)lround(self->velocity))); + lf_set(speed, abs((int)lround(self->velocity))); - // Update position. - // Careful with rounding. - self->position += self->gear * self->velocity * (self->sample_interval / MSEC(1)) / 1000.0; + // Update position. + // Careful with rounding. + self->position += self->gear * self->velocity * (self->sample_interval / MSEC(1)) / 1000.0; - // lf_print("Position: %d\n", (int)lround(self->position)); + // lf_print("Position: %d\n", (int)lround(self->position)); - lf_set(front_distance, self->initial_front_distance - (int)lround(self->position)); + lf_set(front_distance, self->initial_front_distance - (int)lround(self->position)); =} } @@ -129,23 +124,23 @@ reactor Dashboard { state connection: web_socket_instance_t = {= {0} =} - s = new WebSocketServer(hostport = 241, max_clients = 1) // Allow only one client + s = new WebSocketServer(hostport=241, max_clients=1) // Allow only one client reaction(speed, front_distance) -> s.send {= - // Ignore the inputs if we are not connected. - if (self->connection.connected) { - // Construct payload. - char* message; - asprintf(&message, "{\"front_distance\": %d, \"speed\": %d}", front_distance->value, speed->value); - - // Construct struct to send. - web_socket_message_t* response = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); - response->length = strlen(message); // Do not include the null terminator. - response->wsi = self->connection.wsi; - response->message = message; - - lf_set(s.send, response); - } + // Ignore the inputs if we are not connected. + if (self->connection.connected) { + // Construct payload. + char* message; + asprintf(&message, "{\"front_distance\": %d, \"speed\": %d}", front_distance->value, speed->value); + + // Construct struct to send. + web_socket_message_t* response = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); + response->length = strlen(message); // Do not include the null terminator. + response->wsi = self->connection.wsi; + response->message = message; + + lf_set(s.send, response); + } =} // Make sure disconnections occur after messages are sent. @@ -156,42 +151,42 @@ reactor SoundAlert { input front_distance: int logical action ding state ding_interval: time = 0 // 0 means no sound - p = new PlayWaveform(default_waveform_id = 3) + p = new PlayWaveform(default_waveform_id=3) reaction(front_distance) -> ding, p.waveform {= - instant_t previous_interval = self->ding_interval; - // Change the period of the sound. - if (front_distance->value > 75) { - // Go silent. - self->ding_interval = MSEC(0); - lf_set(p.waveform, 0); - } else if (front_distance->value > 50) { - self->ding_interval = MSEC(2000); - lf_set(p.waveform, 3); - } else if (front_distance->value > 25) { - self->ding_interval = MSEC(1000); - lf_set(p.waveform, 3); - } else if (front_distance->value > 15) { - self->ding_interval = MSEC(500); - lf_set(p.waveform, 9); - } else if (front_distance->value > 5) { - self->ding_interval = MSEC(200); - lf_set(p.waveform, 9); - } else { - self->ding_interval = MSEC(100); - lf_set(p.waveform, 8); - } - // If no sound is playing, start it playing. - if (self->ding_interval > MSEC(0) && previous_interval == MSEC(0)) { - lf_schedule(ding, 0); - } + instant_t previous_interval = self->ding_interval; + // Change the period of the sound. + if (front_distance->value > 75) { + // Go silent. + self->ding_interval = MSEC(0); + lf_set(p.waveform, 0); + } else if (front_distance->value > 50) { + self->ding_interval = MSEC(2000); + lf_set(p.waveform, 3); + } else if (front_distance->value > 25) { + self->ding_interval = MSEC(1000); + lf_set(p.waveform, 3); + } else if (front_distance->value > 15) { + self->ding_interval = MSEC(500); + lf_set(p.waveform, 9); + } else if (front_distance->value > 5) { + self->ding_interval = MSEC(200); + lf_set(p.waveform, 9); + } else { + self->ding_interval = MSEC(100); + lf_set(p.waveform, 8); + } + // If no sound is playing, start it playing. + if (self->ding_interval > MSEC(0) && previous_interval == MSEC(0)) { + lf_schedule(ding, 0); + } =} reaction(ding) -> p.note, ding {= - if (self->ding_interval > MSEC(0)) { - lf_set(p.note, 1); - lf_schedule(ding, self->ding_interval); - } + if (self->ding_interval > MSEC(0)) { + lf_set(p.note, 1); + lf_schedule(ding, self->ding_interval); + } =} } diff --git a/C/src/simulation/MemoryHierarchy.lf b/C/src/simulation/MemoryHierarchy.lf index 5789cb1c..583b661d 100644 --- a/C/src/simulation/MemoryHierarchy.lf +++ b/C/src/simulation/MemoryHierarchy.lf @@ -1,55 +1,47 @@ /** - * This program simulates a simple memory hierarchy - * where a random client (with Poisson arrivals) accesses - * a cache. With a specified probability, the cache misses - * and delegates the request to a memory controller. - * The memory controller, in turn farms out the request - * to a bank of memories, which return after a random - * amount of time (with an exponential distribution). - * When all the requests have sent responses to the cache, - * the cache replies to the client. - * If a request arrives while there are requests pending, - * then it queues the request in finite-size queue. - * If the queue is full, then it returns an error signal - * to the client. - * + * This program simulates a simple memory hierarchy where a random client (with Poisson arrivals) + * accesses a cache. With a specified probability, the cache misses and delegates the request to a + * memory controller. The memory controller, in turn farms out the request to a bank of memories, + * which return after a random amount of time (with an exponential distribution). When all the + * requests have sent responses to the cache, the cache replies to the client. If a request arrives + * while there are requests pending, then it queues the request in finite-size queue. If the queue + * is full, then it returns an error signal to the client. + * * @author Edward A. Lee */ target C { timeout: 10 us } + import Random from "../lib/Random.lf" import PoissonClock from "../lib/PoissonClock.lf" import RandomDelay from "../lib/RandomDelay.lf" main reactor { - client = new Client(seed = 0) - cache = new Cache(seed = 0, miss_probability = 0.3) - memory = new Memory(seed = 0, average_delay = 100 ns) + client = new Client(seed=0) + cache = new Cache(seed=0, miss_probability=0.3) + memory = new Memory(seed=0, average_delay = 100 ns) client.request -> cache.request - cache.response ->client.response + cache.response -> client.response cache.miss -> memory.request memory.response -> cache.fill } /** - * Generate requests according to a PoissonProcess with rate lambda - * (1/lambda is the average time between requests). - * This defaults to 10,000,000 requests per second (100 ns - * average time between requests). - * Each request carries the time that the request is initiated. - * When the same time-value is received at the response input, - * print the time it took for that response to be received. - * If a negative time is received at the response input, then - * interpret this to be an error and print an error message. + * Generate requests according to a PoissonProcess with rate lambda (1/lambda is the average time + * between requests). This defaults to 10,000,000 requests per second (100 ns average time between + * requests). Each request carries the time that the request is initiated. When the same time-value + * is received at the response input, print the time it took for that response to be received. If a + * negative time is received at the response input, then interpret this to be an error and print an + * error message. */ -reactor Client(seed:int(0), lambda:double(10000000.0)) { - input response:time - output request:time - p = new PoissonClock(seed = seed, lambda = lambda) - reaction(p.event) -> request {= - lf_set(request, lf_time_logical_elapsed()); - =} +reactor Client(seed: int = 0, lambda: double = 10000000.0) { + input response: time + output request: time + p = new PoissonClock(seed=seed, lambda=lambda) + + reaction(p.event) -> request {= lf_set(request, lf_time_logical_elapsed()); =} + reaction(response) {= if (response->value > 0) { interval_t delay = lf_time_logical_elapsed() - response->value; @@ -62,33 +54,24 @@ reactor Client(seed:int(0), lambda:double(10000000.0)) { } /** - * Simulate a cache. A cache miss will occur at - * random with the probability given by miss_probability, - * a double between 0.0 and 1.0, with default 0.1. - * The input is a time value and the response will be - * either the same value or its negative if an error occurs. - * Upon a cache hit, the output will be delayed by the - * time value given by hit_delay. - * Upon a cache miss, the memory fetch will be delegated - * to the miss output and the Cache will wait for a - * response on the fill input. If a miss occurs - * while it is waiting, then an error will occur. - * Upon an error, the response will be sent immediately - * and will have the negative of the request value. + * Simulate a cache. A cache miss will occur at random with the probability given by + * miss_probability, a double between 0.0 and 1.0, with default 0.1. The input is a time value and + * the response will be either the same value or its negative if an error occurs. Upon a cache hit, + * the output will be delayed by the time value given by hit_delay. Upon a cache miss, the memory + * fetch will be delegated to the miss output and the Cache will wait for a response on the fill + * input. If a miss occurs while it is waiting, then an error will occur. Upon an error, the + * response will be sent immediately and will have the negative of the request value. */ -reactor Cache( - miss_probability:double(0.1), - hit_delay:time(10 ns) -) extends Random { - input request:time - output response:time - output miss:time - input fill:time - state pending:bool(false) - logical action a:time - reaction(a) -> response {= - lf_set(response, a->value); - =} +reactor Cache(miss_probability: double = 0.1, hit_delay: time = 10 ns) extends Random { + input request: time + output response: time + output miss: time + input fill: time + state pending: bool = false + logical action a: time + + reaction(a) -> response {= lf_set(response, a->value); =} + reaction(request) -> a, response, miss {= bool hit = random() >= (int)(self->miss_probability * RAND_MAX); if (self->pending && !hit) { @@ -101,23 +84,20 @@ reactor Cache( lf_set(miss, request->value); } =} + reaction(fill) -> response {= lf_set(response, fill->value); self->pending = false; =} } -/** - * Simulate a memory that responds after a random time. - */ -reactor Memory(seed:int(0), average_delay:time(100 ns)) { - input request:time - output response:time - delay = new RandomDelay(average = average_delay) - reaction(request) -> delay.in {= - lf_set(delay.in, request->value); - =} - reaction(delay.out) -> response {= - lf_set(response, delay.out->value); - =} +/** Simulate a memory that responds after a random time. */ +reactor Memory(seed: int = 0, average_delay: time = 100 ns) { + input request: time + output response: time + delay = new RandomDelay(average=average_delay) + + reaction(request) -> delay.in {= lf_set(delay.in, request->value); =} + + reaction(delay.out) -> response {= lf_set(response, delay.out->value); =} } diff --git a/C/src/simulation/PoissonProcess.lf b/C/src/simulation/PoissonProcess.lf index 4f0cb572..d8c95738 100644 --- a/C/src/simulation/PoissonProcess.lf +++ b/C/src/simulation/PoissonProcess.lf @@ -1,24 +1,24 @@ /** - * Generate two Poisson processes, one with a fixed seed - * and the other using the default seed. The one with the - * fixed seed will produce repeatable outputs. - * A third random process is generated using the - * RandomDelay reactor. - * - * This demonstrates the use of the PoissonClock - * and RandomDelay reactors. - * + * Generate two Poisson processes, one with a fixed seed and the other using the default seed. The + * one with the fixed seed will produce repeatable outputs. A third random process is generated + * using the RandomDelay reactor. + * + * This demonstrates the use of the PoissonClock and RandomDelay reactors. + * * @author Edward A. Lee */ target C { timeout: 5 sec } + import PoissonClock from "../lib/PoissonClock.lf" import RandomDelay from "../lib/RandomDelay.lf" + main reactor { non_repeatable = new PoissonClock() - repeatable = new PoissonClock(seed = 1) + repeatable = new PoissonClock(seed=1) delay = new RandomDelay() + reaction(non_repeatable.event, repeatable.event) {= double seconds = ((double)lf_time_logical_elapsed())/SEC(1); if (non_repeatable.event->is_present) { @@ -28,6 +28,7 @@ main reactor { lf_print("repeatable event at time %f seconds", seconds); } =} + reaction(startup, delay.out) -> delay.in {= lf_set(delay.in, lf_time_logical_elapsed()); if(delay.out->is_present) { diff --git a/CCpp/src/DoorLock/DoorLock.lf b/CCpp/src/DoorLock/DoorLock.lf index 46995099..724a466b 100644 --- a/CCpp/src/DoorLock/DoorLock.lf +++ b/CCpp/src/DoorLock/DoorLock.lf @@ -1,22 +1,20 @@ /** - * A simple car door lock system with a single door that can be - * controlled in any of three ways: - * 1. buttons on the physical door, - * 2. an external system (like an RFID key fob), or - * 3. a mobile device (like a phone) via a cloud service. - * + * A simple car door lock system with a single door that can be controlled in any of three ways: 1. + * buttons on the physical door, 2. an external system (like an RFID key fob), or 3. a mobile device + * (like a phone) via a cloud service. + * * Potentially interesting behaviors: - * - * If the door is locked and closed and receives simultaneous open - * and unlock commands, it unlocks the door, but the door remains closed. - * + * + * If the door is locked and closed and receives simultaneous open and unlock commands, it unlocks + * the door, but the door remains closed. + * * @author Ravi Akella * @author Edward A. Lee */ - target CCpp { - keepalive: true, + keepalive: true } + import UserInteraction from "lib/UserInteraction.lf" import PropagationDelaySim from "lib/PropagationDelaySim.lf" import AuthSim from "lib/AuthSim.lf" @@ -28,18 +26,17 @@ preamble {= =} reactor DoorLockController { - input door:OpenEvent - input button:LockCommand - input fob:LockCommand - input mobile:LockCommand + input door: OpenEvent + input button: LockCommand + input fob: LockCommand + input mobile: LockCommand + + output actuate: LockCommand - output actuate:LockCommand - initial mode ClosedLocked { - reaction(reset) {= - lf_print("*** Door is closed and has been locked."); - =} - reaction(button, fob, mobile) -> ClosedUnlocked, actuate {= + reaction(reset) {= lf_print("*** Door is closed and has been locked."); =} + + reaction(button, fob, mobile) -> reset(ClosedUnlocked), actuate {= if ( (button->is_present && button->value == UNLOCK) || (fob->is_present && fob->value == UNLOCK) @@ -51,15 +48,14 @@ reactor DoorLockController { lf_print("Door is already locked."); } =} - reaction(door) {= - lf_print("Door is closed and locked."); - =} + + reaction(door) {= lf_print("Door is closed and locked."); =} } + mode ClosedUnlocked { - reaction(reset) {= - lf_print("*** Door has been closed and is unlocked."); - =} - reaction(button, fob, mobile) -> ClosedLocked, actuate {= + reaction(reset) {= lf_print("*** Door has been closed and is unlocked."); =} + + reaction(button, fob, mobile) -> reset(ClosedLocked), actuate {= if ( (button->is_present && button->value == LOCK) || (fob->is_present && fob->value == LOCK) @@ -71,27 +67,27 @@ reactor DoorLockController { lf_print("Door is already unlocked."); } =} - reaction(door) -> Open {= + + reaction(door) -> reset(Open) {= if (door->value == OPEN) lf_set_mode(Open); else lf_print("Door is already closed."); =} } + mode Open { - reaction(reset) {= - lf_print("*** Door has been opened."); - =} - reaction(door) -> ClosedUnlocked {= + reaction(reset) {= lf_print("*** Door has been opened."); =} + + reaction(door) -> reset(ClosedUnlocked) {= if (door->value == CLOSE) lf_set_mode(ClosedUnlocked); else lf_print("Door is already open."); =} - reaction(button, fob, mobile) {= - lf_print("Door is open."); - =} + + reaction(button, fob, mobile) {= lf_print("Door is open."); =} } } -reactor DoorLockActuator(tolerance:time(1000 msec)) { - input in:LockCommand +reactor DoorLockActuator(tolerance: time = 1000 msec) { + input in: LockCommand reaction(in) {= interval_t lag = (lf_time_physical() - lf_time_logical()); @@ -100,7 +96,7 @@ reactor DoorLockActuator(tolerance:time(1000 msec)) { std::cout << lf_time_physical_elapsed() << ": Command " << command_string << " executed successfully. Time lag: " << lag << " ns." << std::endl; - =} deadline(tolerance){= + =} deadline(tolerance) {= interval_t lag = (lf_time_physical() - lf_time_logical()); std::string command_string = (in->value == LOCK) ? "LOCK" : "UNLOCK"; @@ -108,11 +104,12 @@ reactor DoorLockActuator(tolerance:time(1000 msec)) { << " executed " << lag - self->tolerance << " nsecs later then expected." << std::endl; =} } + reactor DoorLockSystem { - input door:OpenEvent - input button:LockCommand - input fob:LockCommand - input mobile:LockCommand + input door: OpenEvent + input button: LockCommand + input fob: LockCommand + input mobile: LockCommand dlc = new DoorLockController() a = new DoorLockActuator() diff --git a/CCpp/src/DoorLock/lib/AuthSim.lf b/CCpp/src/DoorLock/lib/AuthSim.lf index 45b651f8..2641372d 100644 --- a/CCpp/src/DoorLock/lib/AuthSim.lf +++ b/CCpp/src/DoorLock/lib/AuthSim.lf @@ -1,19 +1,18 @@ /** - * Simulate authentication. This simulator rejects - * authentication one of every five attempts, prints + * Simulate authentication. This simulator rejects authentication one of every five attempts, prints * a message, and produces no output. - * + * * @author Ravi Akella * @author Edward A. Lee */ target CCpp reactor AuthSim { - input in:LockCommand - output out:LockCommand + input in: LockCommand + output out: LockCommand + + state count: int = 0 - state count:int(0) - reaction(in) -> out {= if (++self->count % 5 == 0) { lf_print("!!! Authentication rejected."); diff --git a/CCpp/src/DoorLock/lib/PropagationDelaySim.lf b/CCpp/src/DoorLock/lib/PropagationDelaySim.lf index 65417170..f4640bb1 100644 --- a/CCpp/src/DoorLock/lib/PropagationDelaySim.lf +++ b/CCpp/src/DoorLock/lib/PropagationDelaySim.lf @@ -1,9 +1,7 @@ /** - * Simulate propagation delay (in physical time) by - * taking time to copy the input to the output. - * The time taken is uniformly between min_delay - * and ten times min_delay. - * + * Simulate propagation delay (in physical time) by taking time to copy the input to the output. The + * time taken is uniformly between min_delay and ten times min_delay. + * * @author Ravi Akella * @author Edward A. Lee */ @@ -12,14 +10,16 @@ target CCpp preamble {= #include /* srand, rand */ =} -reactor PropagationDelaySim(min_delay:time(100 msec)) { - input in:LockCommand - output out:LockCommand + +reactor PropagationDelaySim(min_delay: time = 100 msec) { + input in: LockCommand + output out: LockCommand reaction(startup) {= // Seed the random number generator. srand(lf_time_logical()); =} + reaction(in) -> out {= lf_nanosleep((rand()%10+1) * self->min_delay); lf_set(out, in->value); diff --git a/CCpp/src/DoorLock/lib/UserInteraction.lf b/CCpp/src/DoorLock/lib/UserInteraction.lf index 0b7267c6..1e08fa38 100644 --- a/CCpp/src/DoorLock/lib/UserInteraction.lf +++ b/CCpp/src/DoorLock/lib/UserInteraction.lf @@ -1,24 +1,19 @@ /** - * A simulator that generates command for a simple car door - * lock system with a single door that can be - * controlled in any of three ways: - * 1. buttons on the physical door, - * 2. an external system (like an RFID key fob), or - * 3. a mobile device (like a phone) via a cloud service. - * + * A simulator that generates command for a simple car door lock system with a single door that can + * be controlled in any of three ways: 1. buttons on the physical door, 2. an external system (like + * an RFID key fob), or 3. a mobile device (like a phone) via a cloud service. + * * @author Ravi Akella * @author Edward A. Lee */ - target C { keepalive: true, - files: [ - "../include/types.h", - ], + files: ["../include/types.h"] } + preamble {= #include "types.h" - + void* read_input(void* user_response) { lf_print("****************************************************\n" "Press the following keys for the following actions:\n" @@ -43,15 +38,15 @@ preamble {= return NULL; } =} - + reactor UserInteraction { - output door:OpenEvent; - output button:LockCommand; - output fob:LockCommand; - output mobile:LockCommand; - - physical action key:int; - + output door: OpenEvent + output button: LockCommand + output fob: LockCommand + output mobile: LockCommand + + physical action key: int + reaction(startup) -> key {= // start new thread lf_thread_t thread_id; @@ -60,7 +55,7 @@ reactor UserInteraction { reaction(key) -> door, button, fob, mobile {= // lf_print("You typed '%c' at elapsed time %lld.", key->value, lf_time_logical_elapsed()); - + switch(key->value) { case 'o': lf_set(door, OPEN); diff --git a/CCpp/src/ROS/MigrationGuide/lf-project/src/Main.lf b/CCpp/src/ROS/MigrationGuide/lf-project/src/Main.lf index 2c3f32e7..d9db6d51 100644 --- a/CCpp/src/ROS/MigrationGuide/lf-project/src/Main.lf +++ b/CCpp/src/ROS/MigrationGuide/lf-project/src/Main.lf @@ -1,10 +1,11 @@ // src/Main.lf -target CCpp; +target CCpp + import Sender from "Sender.lf" import Receiver from "Receiver.lf" federated reactor { - sender = new Sender(); - receiver = new Receiver(); - sender.out -> receiver.in serializer "ROS2"; + sender = new Sender() + receiver = new Receiver() + sender.out -> receiver.in serializer "ROS2" } diff --git a/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf b/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf index 881b6a82..e21400b8 100644 --- a/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf +++ b/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf @@ -1,7 +1,7 @@ // src/Receiver.lf target CCpp { cmake-include: "include/composition.cmake" -}; +} preamble {= #include "subscriber_member_function.h" @@ -9,8 +9,8 @@ preamble {= reactor Receiver { // Instantiate the subscriber node as a sate variable - state subscriber_node : std::shared_ptr; - input in:std::shared_ptr; + state subscriber_node: std::shared_ptr + input in: std::shared_ptr reaction(startup) {= // Initialize rclcpp @@ -20,12 +20,9 @@ reactor Receiver { =} reaction(in) {= - lf_print("[LF receiver] Received %s", in->value->data.c_str()); self->subscriber_node->topic_callback(in->value); =} - reaction(shutdown) {= - rclcpp::shutdown(); - =} + reaction(shutdown) {= rclcpp::shutdown(); =} } diff --git a/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf b/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf index bd4910e0..88f92246 100644 --- a/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf +++ b/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf @@ -1,7 +1,7 @@ // src/Sender.lf target CCpp { cmake-include: "include/composition.cmake" -}; +} preamble {= #include "publisher_member_function.h" @@ -9,10 +9,10 @@ preamble {= reactor Sender { // Instantiate the publisher node as a sate variable - state publisher_node : std::shared_ptr; - output out:std::shared_ptr; - timer t(0, 1 sec); - state count:int(0); + state publisher_node: std::shared_ptr + output out: std::shared_ptr + timer t(0, 1 sec) + state count: int = 0 reaction(startup) {= // Initialize rclcpp @@ -28,7 +28,5 @@ reactor Sender { lf_set(out, message); =} - reaction(shutdown) {= - rclcpp::shutdown(); - =} + reaction(shutdown) {= rclcpp::shutdown(); =} } diff --git a/CCpp/src/ROS/ROSBuiltInSerialization.lf b/CCpp/src/ROS/ROSBuiltInSerialization.lf index c01fc36b..cd234972 100644 --- a/CCpp/src/ROS/ROSBuiltInSerialization.lf +++ b/CCpp/src/ROS/ROSBuiltInSerialization.lf @@ -1,68 +1,61 @@ /** - * This example showcases the infrastructure that is built into the - * CCpp target that can automatically serialize and deserialize ROS2 - * messages in federated programs. - * - * This example contains a sender-receiver federated program in which - * the 'sender' federate sends a std_msgs::msg::Int32 message to the - * 'receiver' federate. - * - * To run this example, make sure that your terminal is properly sourced - * for ROS2. See + * This example showcases the infrastructure that is built into the CCpp target that can + * automatically serialize and deserialize ROS2 messages in federated programs. + * + * This example contains a sender-receiver federated program in which the 'sender' federate sends a + * std_msgs::msg::Int32 message to the 'receiver' federate. + * + * To run this example, make sure that your terminal is properly sourced for ROS2. See * https://docs.ros.org/en/foxy/Tutorials/Configuring-ROS2-Environment.html. - * + * * Then you can use lfc to compile this program: - * - * lfc ROSBuiltInSerialization.lf - * + * + * lfc ROSBuiltInSerialization.lf + * * And launch the federated program in the `bin` folder: - * + * * example/C/bin/ROSBuiltInSerialization - * + * * @author Soroush Bateni */ - target CCpp { +target CCpp { cmake: true, // Only CMake is supported - cmake-include: "include/CMakeListsExtension.txt", -}; + cmake-include: "include/CMakeListsExtension.txt" +} preamble {= #include "std_msgs/msg/int32.hpp" - =} reactor Sender { - output out:std_msgs::msg::Int32; - - // state serialized_msg_pcl:rclcpp::SerializedMessage({=0u=}); - state count:int(0); - - timer t (0, 1 sec); - - reaction (t) -> out {= - std_msgs::msg::Int32 ros_message; - ros_message.data = self->count++; - lf_set(out, ros_message); - + output out: std_msgs::msg::Int32 + + state count: int = 0 // state serialized_msg_pcl:rclcpp::SerializedMessage({=0u=}); + + timer t(0, 1 sec) + + reaction(t) -> out {= + std_msgs::msg::Int32 ros_message; + ros_message.data = self->count++; + lf_set(out, ros_message); =} } - reactor Receiver { - input in:std_msgs::msg::Int32; - - reaction (in) {= + input in: std_msgs::msg::Int32 + + reaction(in) {= // Print the ROS2 message data - lf_print( - "Serialized integer after deserialization: %d", - in->value.data - ); + lf_print( + "Serialized integer after deserialization: %d", + in->value.data + ); =} } federated reactor { - sender = new Sender(); - receiver = new Receiver(); - - sender.out -> receiver.in serializer "ros2"; + sender = new Sender() + receiver = new Receiver() + + sender.out -> receiver.in serializer "ros2" } diff --git a/CCpp/src/ROS/ROSSerialization.lf b/CCpp/src/ROS/ROSSerialization.lf index 8765e155..0e745ce5 100644 --- a/CCpp/src/ROS/ROSSerialization.lf +++ b/CCpp/src/ROS/ROSSerialization.lf @@ -1,20 +1,19 @@ /** - * A simple source-sink example that uses the federated Lingua Franca runtime to - * send ROS messages of type std_msgs::msg::Int32. The serialization/deserialization - * techniques are exposed here in the sender/receiver reactions. This mechanism should - * work for any ROS2 message type. - * - * Note: the terminal must be properly sourced for ROS2. See + * A simple source-sink example that uses the federated Lingua Franca runtime to send ROS messages + * of type std_msgs::msg::Int32. The serialization/deserialization techniques are exposed here in + * the sender/receiver reactions. This mechanism should work for any ROS2 message type. + * + * Note: the terminal must be properly sourced for ROS2. See * https://docs.ros.org/en/foxy/Tutorials/Configuring-ROS2-Environment.html. * - * There is a variant of this example called ROSBuiltInSerialization.lf that uses - * the built-in ROS 2 serialization feature that is provided in LF. + * There is a variant of this example called ROSBuiltInSerialization.lf that uses the built-in ROS 2 + * serialization feature that is provided in LF. */ target CCpp { coordination: decentralized, timeout: 10 sec, cmake-include: "include/CMakeListsExtension.txt" -}; +} preamble {= #include "rclcpp/rclcpp.hpp" @@ -22,43 +21,45 @@ preamble {= #include "rcutils/allocator.h" #include "rclcpp/rclcpp.hpp" #include "rclcpp/serialization.hpp" - - + + #include "rclcpp/serialized_message.hpp" - - #include "rosidl_typesupport_cpp/message_type_support.hpp" + + #include "rosidl_typesupport_cpp/message_type_support.hpp" =} -reactor Clock(offset:time(0), period:time(1 sec)) { - output y:uint8_t*; - - timer t(0, period); - state count:int(0); - state serialized_msg:rclcpp::SerializedMessage({=0u=}); +reactor Clock(offset: time = 0, period: time = 1 sec) { + output y: uint8_t* + + timer t(0, period) + state count: int = 0 + state serialized_msg: rclcpp::SerializedMessage = {= 0u =} + reaction(t) -> y {= - (self->count)++; - + // From https://github.com/ros2/demos/blob/master/demo_nodes_cpp/src/topics/talker_serialized_message.cpp - + auto msg = std::make_shared(); msg->data = self->count; - + auto message_header_length = 8u; auto message_payload_length = 10u; self->serialized_msg.reserve(message_header_length + message_payload_length); - + static rclcpp::Serialization 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; =} } + reactor Destination { - input x:uint8_t*; - - state s:int(1); + input x: uint8_t* + + state s: int = 1 + reaction(x) {= auto message = std::make_unique( rcl_serialized_message_t{ .buffer = (uint8_t*)x->token->value, @@ -66,11 +67,11 @@ reactor Destination { .buffer_capacity = x->token->length, .allocator = rcl_get_default_allocator() }); - + // Check the ref count == 1 // lf_print("%d", x->token->ref_count); // assert(x->token->ref_count == 1); // Might be optimized away (see validate in cpp target) - + //rclcpp::SerializedMessage* msg = new rclcpp::SerializedMessage(x->token->length, rcl_get_default_allocator()); auto msg = std::make_unique(std::move(*message.get())); x->token->value = NULL; // Manually move it @@ -80,7 +81,7 @@ reactor Destination { std_msgs::msg::Int32 int_msg; auto serializer_obj = rclcpp::Serialization(); serializer_obj.deserialize_message(msg.get(), &int_msg); - + lf_print("Received %d.", int_msg.data); if (int_msg.data != self->s) { lf_print_warning("Expected %d and got %d.", self->s, int_msg.data); @@ -88,8 +89,9 @@ reactor Destination { self->s++; =} } -federated reactor (period:time(1 sec)) { - c = new Clock(period = period); - d = new Destination(); - c.y -> d.x; + +federated reactor(period: time = 1 sec) { + c = new Clock(period=period) + d = new Destination() + c.y -> d.x } diff --git a/Cpp/AlarmClock/src/AlarmClock.lf b/Cpp/AlarmClock/src/AlarmClock.lf index 36b1d97f..113186a5 100644 --- a/Cpp/AlarmClock/src/AlarmClock.lf +++ b/Cpp/AlarmClock/src/AlarmClock.lf @@ -1,40 +1,31 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock -* -* This file contains the networking implementation it is really just an -* simple socket application which parses simple http headers and respondes -* in text/plain -* -* @author Tassilo Tanneberer -*/ - -target Cpp{ +/** + * This is a minimal example of an alarmclock implemeted using the features lingua franca supplies. + * + * This is just an extract and simplification from the main project which you can find here: + * https://github.com/revol-xut/lf-alarm-clock + * + * This file contains the networking implementation it is really just an simple socket application + * which parses simple http headers and respondes in text/plain + * + * @author Tassilo Tanneberer + */ +target Cpp { cmake-include: "AlarmClock.cmake", keepalive: true -}; - -import Network from "./Network.lf"; -import Clock from "./Clock.lf"; +} -#import Network.lf; -#import Clock.lf; +import Network from "./Network.lf" +import Clock from "./Clock.lf" +// import Network.lf; +// import Clock.lf; main reactor AlarmClock { - clock = new Clock(); - network = new Network(); + clock = new Clock() + network = new Network() - // additon of a new event - network.event -> clock.event; - network.delete_index -> clock.cancel_by_index; - clock.event_dump -> network.updated_events; + network.event -> clock.event // additon of a new event + network.delete_index -> clock.cancel_by_index + clock.event_dump -> network.updated_events - reaction (startup) {= - std::cout << "Starting Lingua Franca AlarmClock" << std::endl; - =} + reaction(startup) {= std::cout << "Starting Lingua Franca AlarmClock" << std::endl; =} } - - diff --git a/Cpp/AlarmClock/src/Clock.lf b/Cpp/AlarmClock/src/Clock.lf index 753a1964..196d1d49 100644 --- a/Cpp/AlarmClock/src/Clock.lf +++ b/Cpp/AlarmClock/src/Clock.lf @@ -1,18 +1,15 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock -* -* Author: Tassilo Tanneberer -*/ - - -target Cpp{ +/** + * This is a minimal example of an alarmclock implemeted using the features lingua franca supplies. + * + * This is just an extract and simplification from the main project which you can find here: + * https://github.com/revol-xut/lf-alarm-clock + * + * Author: Tassilo Tanneberer + */ +target Cpp { cmake-include: "AlarmClock.cmake", keepalive: true -}; +} public preamble {= #include "shared_header.hpp" @@ -23,21 +20,21 @@ reactor Trigger { auto convert_to_relative = [](long time_stamp){ const auto t = std::chrono::system_clock::now(); std::chrono::seconds desired_time = std::chrono::seconds(time_stamp); - std::chrono::seconds current_time = + std::chrono::seconds current_time = std::chrono::duration_cast(t.time_since_epoch()); std::chrono::seconds delta_t = desired_time - current_time; return delta_t; }; =} - input input_event: {=Event=}; - input input_interrupt: long; - logical action interrupt; - logical action triggered_event: {=std::string=}; - state ignore_flag: bool; + input input_event: {= Event =} + input input_interrupt: long + logical action interrupt + logical action triggered_event: {= std::string =} + state ignore_flag: bool - //the input_event will scheduled - reaction (input_event) -> triggered_event {= + // the input_event will scheduled + reaction(input_event) -> triggered_event {= if(input_event.is_present()) { auto extracted = input_event.get().get(); auto delta_t = convert_to_relative(extracted->time_stamp_); @@ -45,14 +42,14 @@ reactor Trigger { } =} - reaction (input_interrupt) -> interrupt {= + reaction(input_interrupt) -> interrupt {= if(input_interrupt.is_present()){ auto delta_t = convert_to_relative(*(input_interrupt.get().get())); interrupt.schedule(delta_t); } =} - // reaction which will be triggered when a event is due + // reaction which will be triggered when a event is due reaction(triggered_event) {= auto select_random_file = []{ std::vector files; @@ -74,9 +71,7 @@ reactor Trigger { ignore_flag = false; =} - reaction (interrupt) {= - ignore_flag = true; - =} + reaction(interrupt) {= ignore_flag = true; =} } reactor Clock { @@ -89,22 +84,17 @@ reactor Clock { } =} - // trigger reactor which handles the execution of the scheduled reaction - trigger = new Trigger(); - // this event will be scheduled and added to persistent storage - input event: Event; - input cancel_by_index: std::size_t; - // list of events - output event_dump: {= std::vector =}; - // timer which triggers clear and save - timer maintance(10 sec, 30 sec); + trigger = new Trigger() // trigger reactor which handles the execution of the scheduled reaction + input event: Event // this event will be scheduled and added to persistent storage + input cancel_by_index: std::size_t + output event_dump: {= std::vector =} // list of events + timer maintance(10 sec, 30 sec) // timer which triggers clear and save - // persistant storage - state events: std::vector(); + state events: std::vector() // persistant storage // reaction that appends new events which will be scheduled // the newtwork reactor is updated - reaction (event) -> trigger.input_event, event_dump {= + reaction(event) -> trigger.input_event, event_dump {= if (event.is_present() and not time_over(*event.get())){ trigger.input_event.set(*event.get()); events.push_back(*event.get()); @@ -112,8 +102,8 @@ reactor Clock { } =} - // initiation ... reading file to create state - reaction (startup) -> trigger.input_event, event_dump {= + // initiation ... reading file to create state + reaction(startup) -> trigger.input_event, event_dump {= // if the calender file doesn't exists it's created if (not std::filesystem::exists(kFile)){ std::ofstream{kFile}; @@ -155,16 +145,16 @@ reactor Clock { =} // state needs to be saved to file - reaction (shutdown, maintance) -> event_dump {= + reaction(shutdown, maintance) -> event_dump {= remove_events(); save(); event_dump.set(events); =} - reaction (cancel_by_index) -> trigger.input_interrupt, event_dump {= + reaction(cancel_by_index) -> trigger.input_interrupt, event_dump {= if(cancel_by_index.is_present()) { std::size_t index = *(cancel_by_index.get().get()); - + if( index < events.size()){ auto tag = events.at(index).time_stamp_; trigger.input_interrupt.set(tag); @@ -203,4 +193,3 @@ reactor Clock { file.close(); =} } - diff --git a/Cpp/AlarmClock/src/Network.lf b/Cpp/AlarmClock/src/Network.lf index 38ee1599..45aa78cc 100644 --- a/Cpp/AlarmClock/src/Network.lf +++ b/Cpp/AlarmClock/src/Network.lf @@ -1,21 +1,18 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock - -* This file contains the networking implementation it is really just an -* simple socket application which parses simple http headers and respondes -* in text/plain -* -* @author Tassilo Tanneberer -*/ - -target Cpp{ +/** + * This is a minimal example of an alarmclock implemeted using the features lingua franca supplies. + * + * This is just an extract and simplification from the main project which you can find here: + * https://github.com/revol-xut/lf-alarm-clock + * + * This file contains the networking implementation it is really just an simple socket application + * which parses simple http headers and respondes in text/plain + * + * @author Tassilo Tanneberer + */ +target Cpp { cmake-include: "AlarmClock.cmake", keepalive: true -}; +} public preamble {= #include "shared_header.hpp" @@ -30,33 +27,33 @@ reactor Network { #include =} - // physical event which is triggered by receiving a request - physical action new_event: Event; - physical action delete_request: std::size_t; + physical action new_event: Event // physical event which is triggered by receiving a request + physical action delete_request: std::size_t // variables for the receive thread - state thread: std::thread; // receive thread - state events: std::vector; // copy + // receive thread + state thread: std::thread + state events: std::vector // copy - input updated_events: std::vector; - output event: Event; // event which will be added to the clock - output delete_index: std::size_t; + input updated_events: std::vector + output event: Event // event which will be added to the clock + output delete_index: std::size_t // this reaction transforms a physical action into a logical reaction - reaction (new_event) -> event {= + reaction(new_event) -> event {= if(new_event.is_present()){ event.set(new_event.get()); } =} - reaction (delete_request) -> delete_index {= + reaction(delete_request) -> delete_index {= if(delete_request.is_present()){ delete_index.set(delete_request.get()); } - =} + =} // main starts receive thread - reaction (startup) -> delete_request, new_event{= + reaction(startup) -> delete_request, new_event {= thread = std::thread([&] { crow::SimpleApp app; @@ -105,7 +102,7 @@ reactor Network { return crow::response(response); }); - // adds new event by relativ times + // adds new event by relativ times CROW_ROUTE(app, "/add_event_relative").methods("POST"_method) ([&new_event](const crow::request& req){ auto relativ_time = 0l; @@ -130,7 +127,7 @@ reactor Network { const auto now = std::chrono::system_clock::now(); auto current_time = std::chrono::duration_cast(now.time_since_epoch()).count(); - + std::cout << "current_time: " << current_time << " offset:" << relativ_time << std::endl; Event serialized_event { json_body["message"].s(), @@ -147,7 +144,7 @@ reactor Network { // will set the timer in the text 24 hours CROW_ROUTE(app, "/add_event_time").methods("POST"_method) ([&new_event](const crow::request& req){ - // just % doesn't work because it is the remainder operator + // just % doesn't work because it is the remainder operator // and does not behave like modulo for negative numbers auto mod = [](int a, int b) { int r = a % b; @@ -215,11 +212,8 @@ reactor Network { app.port(kPort).multithreaded().run(); }); =} - reaction (updated_events) {= - events = std::move(*updated_events.get()); - =} - reaction ( shutdown ) {= - thread.join(); - =} + reaction(updated_events) {= events = std::move(*updated_events.get()); =} + + reaction(shutdown) {= thread.join(); =} } diff --git a/Cpp/CarBrake/src/CarBrake.lf b/Cpp/CarBrake/src/CarBrake.lf index ac857777..a1f3957a 100644 --- a/Cpp/CarBrake/src/CarBrake.lf +++ b/Cpp/CarBrake/src/CarBrake.lf @@ -1,40 +1,38 @@ /** - * The given Lingua Franca (LF) program models a simplified automatic braking - * system in a car, which consists of a camera, a braking assistant, a brake pedal, - * and a brake. The camera continuously generates frames at a regular interval of - * 20 milliseconds. The braking assistant processes these frames and sends a brake - * signal every 10 frames, simulating an automatic braking decision based on the - * image processing. Simultaneously, a simulated brake pedal is "pressed" roughly - * every second, triggering a manual brake signal. Both automatic and manual brake - * signals are directed to the brake reactor, which reacts by outputting a message - * indicating that the brake has been triggered and the source of the signal. It - * also checks whether the reactions to these signals meet certain deadlines (3 - * milliseconds for manual and 15 milliseconds for automatic braking), printing an - * error message if a deadline is violated. + * The given Lingua Franca (LF) program models a simplified automatic braking system in a car, which + * consists of a camera, a braking assistant, a brake pedal, and a brake. The camera continuously + * generates frames at a regular interval of 20 milliseconds. The braking assistant processes these + * frames and sends a brake signal every 10 frames, simulating an automatic braking decision based + * on the image processing. Simultaneously, a simulated brake pedal is "pressed" roughly every + * second, triggering a manual brake signal. Both automatic and manual brake signals are directed to + * the brake reactor, which reacts by outputting a message indicating that the brake has been + * triggered and the source of the signal. It also checks whether the reactions to these signals + * meet certain deadlines (3 milliseconds for manual and 15 milliseconds for automatic braking), + * printing an error message if a deadline is violated. */ target Cpp { cmake-include: "threads.cmake" -}; +} reactor Camera { - timer t(20msecs, 20msecs) + timer t(20 msecs, 20 msecs) output frame: void - reaction (t) -> frame {= + reaction(t) -> frame {= frame.set(); // send a "frame" =} } reactor BrakingAssistant { - input frame: void; - output trigger_brake: void; - - state counter: int(0); + input frame: void + output trigger_brake: void + + state counter: int(0) reaction(frame) -> trigger_brake {= // processing takes some time std::this_thread::sleep_for(10ms); - + if (counter % 10 == 0) { std::cout << "[automatic] Send the brake signal - " << get_physical_time() << std::endl; trigger_brake.set(); @@ -44,10 +42,10 @@ reactor BrakingAssistant { } reactor BrakePedal { - physical action pedal; - output trigger_brake: void; + physical action pedal + output trigger_brake: void - state thread: std::thread; + state thread: std::thread reaction(startup) -> pedal {= this->thread = std::thread([&] () { @@ -65,9 +63,7 @@ reactor BrakePedal { trigger_brake.set(); =} - reaction(shutdown) {= - thread.join(); - =} + reaction(shutdown) {= thread.join(); =} } reactor Brake { @@ -75,31 +71,31 @@ reactor Brake { #include =} - input brake_assistant: void; - input brake_pedal: void; - + input brake_assistant: void + input brake_pedal: void + reaction(brake_pedal) {= std::cout << "[system] Brake triggered - " << get_physical_time() << std::endl; std::cout << "[system] source: manual" << std::endl; - =} deadline (3msecs) {= + =} deadline(3 msecs) {= std::cout << "\033[1;31m[error]\033[0m Deadline on manual braking violated - " << get_physical_time() << std::endl; =} - + reaction(brake_assistant) {= std::cout << "[system] Brake triggered - " << get_physical_time() << std::endl; std::cout << "[system] source: assistant" << std::endl; - =} deadline (15msecs) {= + =} deadline(15 msecs) {= std::cout << "\033[1;31m[error]\033[0m Deadline on automatic braking violated - " << get_physical_time() << std::endl; =} } main reactor { - camera = new Camera(); - assistant = new BrakingAssistant(); - pedal = new BrakePedal(); - brake = new Brake(); - - camera.frame -> assistant.frame; - assistant.trigger_brake -> brake.brake_assistant; - pedal.trigger_brake -> brake.brake_pedal; + camera = new Camera() + assistant = new BrakingAssistant() + pedal = new BrakePedal() + brake = new Brake() + + camera.frame -> assistant.frame + assistant.trigger_brake -> brake.brake_assistant + pedal.trigger_brake -> brake.brake_pedal } diff --git a/Cpp/CarBrake/src/CarBrake2.lf b/Cpp/CarBrake/src/CarBrake2.lf index 56b7841a..d3ede35a 100644 --- a/Cpp/CarBrake/src/CarBrake2.lf +++ b/Cpp/CarBrake/src/CarBrake2.lf @@ -1,26 +1,26 @@ /** - * This version of the CarBreak example decouples the Vision analysis. - * If this execution is federated, then the Vision component has no effect - * on the ability to meet deadlines in the response to brake pedal actions. + * This version of the CarBreak example decouples the Vision analysis. If this execution is + * federated, then the Vision component has no effect on the ability to meet deadlines in the + * response to brake pedal actions. */ target Cpp { cmake-include: "threads.cmake" -}; +} reactor Camera { - timer t(20msecs, 20msecs) + timer t(20 msecs, 20 msecs) output frame: void - reaction (t) -> frame {= + reaction(t) -> frame {= frame.set(); // send a "frame" =} } reactor BrakingAssistant { - input frame: void; - output trigger_brake: void; + input frame: void + output trigger_brake: void - state counter: int(0); + state counter: int(0) reaction(frame) -> trigger_brake {= // processing takes some time @@ -30,15 +30,15 @@ reactor BrakingAssistant { std::cout << "[automatic] Send the brake signal - " << get_physical_time() << std::endl; trigger_brake.set(); } - counter++; + counter++; =} } reactor BrakePedal { - physical action pedal; - output trigger_brake: void; + physical action pedal + output trigger_brake: void - state thread: std::thread; + state thread: std::thread reaction(startup) -> pedal {= this->thread = std::thread([&] () { @@ -57,9 +57,7 @@ reactor BrakePedal { trigger_brake.set(); =} - reaction(shutdown) {= - thread.join(); - =} + reaction(shutdown) {= thread.join(); =} } reactor Brake { @@ -67,45 +65,45 @@ reactor Brake { #include =} - input brake_assistant: void; - input brake_pedal: void; + input brake_assistant: void + input brake_pedal: void reaction(brake_pedal) {= std::cout << "[system] Brake triggered - " << get_physical_time() << std::endl; std::cout << "[system] source: manual" << std::endl; - =} deadline (3msecs) {= + =} deadline(3 msecs) {= std::cout << "\033[1;31m[error]\033[0m Deadline on manual braking violated - " << get_physical_time() << std::endl; =} reaction(brake_assistant) {= std::cout << "[system] Brake triggered - " << get_physical_time() << std::endl; std::cout << "[system] source: assistant" << std::endl; - =} deadline (15msecs) {= + =} deadline(15 msecs) {= std::cout << "\033[1;31m[error]\033[0m Deadline on automatic braking violated - " << get_physical_time() << std::endl; =} } reactor Braking { - input brake_assistant: void; - pedal = new BrakePedal(); - brake = new Brake(); + input brake_assistant: void + pedal = new BrakePedal() + brake = new Brake() - pedal.trigger_brake -> brake.brake_pedal; - brake_assistant -> brake.brake_assistant; + pedal.trigger_brake -> brake.brake_pedal + brake_assistant -> brake.brake_assistant } reactor Vision { - output trigger_brake: void; - camera = new Camera(); - assistant = new BrakingAssistant(); + output trigger_brake: void + camera = new Camera() + assistant = new BrakingAssistant() - camera.frame -> assistant.frame; - assistant.trigger_brake -> trigger_brake; + camera.frame -> assistant.frame + assistant.trigger_brake -> trigger_brake } main reactor { - braking = new Braking(); - vision = new Vision(); + braking = new Braking() + vision = new Vision() - vision.trigger_brake ~> braking.brake_assistant; + vision.trigger_brake ~> braking.brake_assistant } diff --git a/Cpp/Patterns/src/FullyConnected_00_Broadcast.lf b/Cpp/Patterns/src/FullyConnected_00_Broadcast.lf index 52e9b46e..9f620071 100644 --- a/Cpp/Patterns/src/FullyConnected_00_Broadcast.lf +++ b/Cpp/Patterns/src/FullyConnected_00_Broadcast.lf @@ -1,26 +1,23 @@ /** - * This illustrates bank of reactors where each reactor produces an - * output that is broadcast to all reactors in the bank, including - * itself. - * + * This illustrates bank of reactors where each reactor produces an output that is broadcast to all + * reactors in the bank, including itself. + * * @author Christian Menard * @author Edward A. Lee */ - -target Cpp { -} +target Cpp reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { input[num_nodes] in: size_t output out: size_t - reaction (startup) -> out{= + reaction(startup) -> out {= std::cout << "Hello from node " << bank_index << "!\n"; // broadcast my ID to everyone out.set(bank_index); =} - reaction (in) {= + reaction(in) {= std::cout << "Node " << bank_index << " received messages from "; for (auto& port : in) { if (port.is_present()) { @@ -32,7 +29,6 @@ reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { } main reactor(num_nodes: size_t(4)) { - nodes = new[num_nodes] Node(num_nodes=num_nodes); - (nodes.out)+ -> nodes.in; + nodes = new[num_nodes] Node(num_nodes=num_nodes) + (nodes.out)+ -> nodes.in } - diff --git a/Cpp/Patterns/src/FullyConnected_01_Addressable.lf b/Cpp/Patterns/src/FullyConnected_01_Addressable.lf index 23752214..e007d3c9 100644 --- a/Cpp/Patterns/src/FullyConnected_01_Addressable.lf +++ b/Cpp/Patterns/src/FullyConnected_01_Addressable.lf @@ -1,28 +1,24 @@ /** - * This illustrates bank of reactors where each reactor produces an - * output that is sent to a reactor in the bank of its choice. In this - * particular example, each reactor chooses to send its output to the - * reactor with the next higher bank_index, wrapping around when it gets - * to the end of the bank. - * + * This illustrates bank of reactors where each reactor produces an output that is sent to a reactor + * in the bank of its choice. In this particular example, each reactor chooses to send its output to + * the reactor with the next higher bank_index, wrapping around when it gets to the end of the bank. + * * @author Christian Menard * @author Edward A. Lee */ - -target Cpp { -} +target Cpp reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { input[num_nodes] in: size_t output[num_nodes] out: size_t - reaction (startup) -> out{= + reaction(startup) -> out {= std::cout << "Hello from node " << bank_index << "!\n"; // send my ID only to my right neighbour out[(bank_index + 1) % num_nodes].set(bank_index); =} - reaction (in) {= + reaction(in) {= std::cout << "Node " << bank_index << " received messages from "; for (auto& port : in) { if (port.is_present()) { @@ -34,7 +30,6 @@ reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { } main reactor(num_nodes: size_t(4)) { - nodes = new[num_nodes] Node(num_nodes=num_nodes); - nodes.out -> interleaved(nodes.in); + nodes = new[num_nodes] Node(num_nodes=num_nodes) + nodes.out -> interleaved(nodes.in) } - diff --git a/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf b/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf index 16e0dfb2..796b116f 100644 --- a/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf +++ b/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf @@ -1,10 +1,8 @@ -// This pattern creates a matrix of nodes, where each of the nodes can send +// This pattern creates a matrix of nodes, where each of the nodes can send // messages to all other nodes in the same row or in the same column. Since -// banks in LF are one dimensional, we use hierachy to implement the second +// banks in LF are one dimensional, we use hierachy to implement the second // dimension. Nodes are organized in Rows which are grouped to form the matrix. - -target Cpp { -}; +target Cpp public preamble {= struct Pos { @@ -22,7 +20,11 @@ private preamble {= } =} -reactor Node(bank_index: size_t(0), row_index: size_t(0), num_rows: size_t(4), num_cols:size_t(4)) { +reactor Node( + bank_index: size_t(0), + row_index: size_t(0), + num_rows: size_t(4), + num_cols: size_t(4)) { input[num_cols] fromRow: Pos input[num_rows] fromCol: Pos @@ -30,13 +32,13 @@ reactor Node(bank_index: size_t(0), row_index: size_t(0), num_rows: size_t(4), n state pos: Pos{bank_index, row_index} - reaction (startup) -> toRowAndCol {= + reaction(startup) -> toRowAndCol {= std::cout << "Hello from " << pos << '\n'; // send my position to everyone else in my row and column toRowAndCol.set(pos); =} - reaction (fromRow) {= + reaction(fromRow) {= std::cout << pos << " received row messages from: "; for (auto& port : fromRow) { if (port.is_present()) { @@ -46,7 +48,7 @@ reactor Node(bank_index: size_t(0), row_index: size_t(0), num_rows: size_t(4), n std::cout << '\n'; =} - reaction (fromCol) {= + reaction(fromCol) {= std::cout << pos << " received col messages from: "; for (auto& port : fromCol) { if (port.is_present()) { @@ -57,19 +59,18 @@ reactor Node(bank_index: size_t(0), row_index: size_t(0), num_rows: size_t(4), n =} } -reactor Row(bank_index: size_t(0), num_rows:size_t(4), num_cols:size_t(4)) { +reactor Row(bank_index: size_t(0), num_rows: size_t(4), num_cols: size_t(4)) { nodes = new[num_cols] Node(row_index=bank_index, num_rows=num_rows, num_cols=num_cols) - input[{=num_rows * num_cols=}] fromCol: Pos + input[{= num_rows * num_cols =}] fromCol: Pos output[num_cols] toCol: Pos (nodes.toRowAndCol)+ -> nodes.fromRow - nodes.toRowAndCol -> toCol + nodes.toRowAndCol -> toCol fromCol -> interleaved(nodes.fromCol) } -main reactor (num_rows:size_t(4), num_cols:size_t(4)) { +main reactor(num_rows: size_t(4), num_cols: size_t(4)) { rows = new[num_rows] Row(num_rows=num_rows, num_cols=num_cols) - (rows.toCol)+ -> rows.fromCol; + (rows.toCol)+ -> rows.fromCol } - diff --git a/Cpp/ROS2/src/MinimalPublisher.lf b/Cpp/ROS2/src/MinimalPublisher.lf index 8206eea7..18fe48a1 100644 --- a/Cpp/ROS2/src/MinimalPublisher.lf +++ b/Cpp/ROS2/src/MinimalPublisher.lf @@ -1,15 +1,14 @@ /** - * The provided Lingua Franca (LF) program is designed to publish messages onto a - * ROS2 topic at a regular interval. Specifically, it publishes a - * 'std_msgs/msg/String' message on the topic named "topic" every 500 milliseconds. - * The message contains a string "Hello, world!" followed by a counter value that - * increments with each published message. This LF program illustrates a simple - * example of a ROS2 publisher, making use of LF's precise timing semantics to - * ensure regular message publication. + * The provided Lingua Franca (LF) program is designed to publish messages onto a ROS2 topic at a + * regular interval. Specifically, it publishes a 'std_msgs/msg/String' message on the topic named + * "topic" every 500 milliseconds. The message contains a string "Hello, world!" followed by a + * counter value that increments with each published message. This LF program illustrates a simple + * example of a ROS2 publisher, making use of LF's precise timing semantics to ensure regular + * message publication. */ target Cpp { ros2: true, - ros2-dependencies: ["std_msgs"], + ros2-dependencies: ["std_msgs"] } public preamble {= @@ -28,9 +27,7 @@ main reactor { timer t(0, 500 ms) - reaction(startup) {= - publisher = lf_node->create_publisher("topic", 10); - =} + reaction(startup) {= publisher = lf_node->create_publisher("topic", 10); =} reaction(t) {= auto message = std_msgs::msg::String(); diff --git a/Cpp/ROS2/src/MinimalSubscriber.lf b/Cpp/ROS2/src/MinimalSubscriber.lf index 899bc7c5..406fc07a 100644 --- a/Cpp/ROS2/src/MinimalSubscriber.lf +++ b/Cpp/ROS2/src/MinimalSubscriber.lf @@ -1,16 +1,15 @@ /** - * The provided Lingua Franca (LF) program represents a simple ROS2 subscriber that - * listens for messages on a particular topic. Specifically, it subscribes to a - * topic named "topic" and waits for 'std_msgs/msg/String' type messages. When a - * message arrives, it is scheduled as a physical action, triggering a reaction - * that logs the received message. Therefore, this program demonstrates how an LF - * program can interface with ROS2 to receive and process messages from a ROS2 - * topic in a reactive manner. + * The provided Lingua Franca (LF) program represents a simple ROS2 subscriber that listens for + * messages on a particular topic. Specifically, it subscribes to a topic named "topic" and waits + * for 'std_msgs/msg/String' type messages. When a message arrives, it is scheduled as a physical + * action, triggering a reaction that logs the received message. Therefore, this program + * demonstrates how an LF program can interface with ROS2 to receive and process messages from a + * ROS2 topic in a reactive manner. */ target Cpp { ros2: true, keepalive: true, - ros2-dependencies: ["std_msgs"], + ros2-dependencies: ["std_msgs"] } public preamble {= @@ -27,7 +26,7 @@ main reactor { state subscription: {= rclcpp::Subscription::SharedPtr =} state count: unsigned(0) - physical action message: std::string; + physical action message: std::string reaction(startup) -> message {= subscription = lf_node->create_subscription( @@ -36,7 +35,5 @@ main reactor { // const std_msgs::msg::String::SharedPtr& msg =} - reaction(message) {= - reactor::log::Info() << "I heard: " << *message.get(); - =} + reaction(message) {= reactor::log::Info() << "I heard: " << *message.get(); =} } diff --git a/Cpp/ReflexGame/src/ReflexGame.lf b/Cpp/ReflexGame/src/ReflexGame.lf index 35d13d69..4f5074da 100644 --- a/Cpp/ReflexGame/src/ReflexGame.lf +++ b/Cpp/ReflexGame/src/ReflexGame.lf @@ -1,8 +1,7 @@ /** - * 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. - * + * 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 Felix Wittwer * @author Edward A. Lee * @author Marten Lohstroh @@ -10,29 +9,29 @@ target Cpp { keepalive: true, cmake-include: "ReflexGame.cmake" -}; +} /** - * Produce a counting sequence at random times with a minimum - * and maximum time between outputs specified as parameters. - * + * 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)) { +reactor RandomSource(min_time: time(2 sec), max_time: time(8 sec)) { private preamble {= - // Generate a random additional delay over the minimum. + // 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); } =} - input another: void; - output out: void; - logical action prompt(min_time); - state count: int(0); - + input another: void + output out: void + logical action prompt(min_time) + state count: int(0) + reaction(startup) -> prompt {= std::cout << "***********************************************" << std::endl; std::cout << "Watch for the prompt, then hit Return or Enter." << std::endl; @@ -45,11 +44,13 @@ reactor RandomSource(min_time:time(2 sec), max_time:time(8 sec)) { // Schedule the first event. prompt.schedule(additional_time(0ms, max_time - min_time)); =} + reaction(prompt) -> out {= count++; std::cout << count << ". Hit Return or Enter!" << std::endl << std::flush; out.set(); =} + reaction(another) -> prompt {= // Schedule the next event. prompt.schedule(additional_time(0ms, max_time - min_time)); @@ -57,24 +58,23 @@ reactor RandomSource(min_time:time(2 sec), max_time:time(8 sec)) { } /** - * 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. + * 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 =} - physical action user_response: char; - state prompt_time: {= reactor::TimePoint =} ({= reactor::TimePoint::min() =}); - state total_time: time(0); - state count: int(0); - state thread: {= std::thread =}; + physical action user_response: char + state prompt_time: {= reactor::TimePoint =}({= reactor::TimePoint::min() =}) + state total_time: time(0) + state count: int(0) + state thread: {= std::thread =} - input prompt: void; - output another: void; + input prompt: void + output another: void reaction(startup) -> user_response {= // Start the thread that listens for Enter or Return. @@ -86,13 +86,11 @@ reactor GetUserInput { } user_response.schedule(c, 0ms); if (c == EOF) break; - } + } }); =} - reaction(prompt) {= - prompt_time = get_physical_time(); - =} + reaction(prompt) {= prompt_time = get_physical_time(); =} reaction(user_response) -> another {= auto c = user_response.get(); @@ -128,10 +126,10 @@ reactor GetUserInput { } =} } + main reactor ReflexGame { - p = new RandomSource(); - g = new GetUserInput(); - p.out -> g.prompt; - g.another -> p.another; + p = new RandomSource() + g = new GetUserInput() + p.out -> g.prompt + g.another -> p.another } - diff --git a/Cpp/RequestResponse/src/Add.lf b/Cpp/RequestResponse/src/Add.lf index 2cd17aac..16d0f6f6 100644 --- a/Cpp/RequestResponse/src/Add.lf +++ b/Cpp/RequestResponse/src/Add.lf @@ -1,27 +1,24 @@ /** - * The given Lingua Franca (LF) program represents a client-service model where the - * client sends addition requests to a service and receives responses. The client - * reactor generates a request every 100 milliseconds, each time asking the - * addition service to add its current counter value (which increments with each - * request) to 42. The request is sent to an instance of the 'AddService' (not - * shown in the code), which presumably performs the addition and returns the - * result. The client then logs the received response, displaying the result of the - * addition. This cycle continues indefinitely, with the client sending - * incrementally changing requests and logging the responses. + * The given Lingua Franca (LF) program represents a client-service model where the client sends + * addition requests to a service and receives responses. The client reactor generates a request + * every 100 milliseconds, each time asking the addition service to add its current counter value + * (which increments with each request) to 42. The request is sent to an instance of the + * 'AddService' (not shown in the code), which presumably performs the addition and returns the + * result. The client then logs the received response, displaying the result of the addition. This + * cycle continues indefinitely, with the client sending incrementally changing requests and logging + * the responses. */ target Cpp import AddService from "AddService.lf" - reactor Client { - timer t(0, 100ms) + timer t(0, 100 ms) output add_request: Request> input add_response: Response state counter: int(0) - reaction(t) -> add_request {= auto req = Request(std::make_pair(counter, 42)); add_request.set(req); @@ -29,9 +26,7 @@ reactor Client { counter++; =} - reaction(add_response) {= - reactor::log::Info() << "It is " << add_response.get()->data(); - =} + reaction(add_response) {= reactor::log::Info() << "It is " << add_response.get()->data(); =} } main reactor { diff --git a/Cpp/RequestResponse/src/AddWithContext.lf b/Cpp/RequestResponse/src/AddWithContext.lf index ccbc2eae..282f1097 100644 --- a/Cpp/RequestResponse/src/AddWithContext.lf +++ b/Cpp/RequestResponse/src/AddWithContext.lf @@ -3,20 +3,25 @@ target Cpp import AddService from "AddService.lf" import ContextManager from "ContextManager.lf" - reactor Client { - timer t(0, 100ms) + timer t(0, 100 ms) output add_request: Request> input add_response: Response state counter: int(0) - add_cm = new ContextManager<{=Request>=}, {=Response=}, {=std::function=}>() + add_cm = new ContextManager< + {= Request> =}, + {= Response =}, + {= std::function =}>() + + add_cm.request_out -> add_request + add_response -> add_cm.response_in reaction(t) -> add_cm.request_in {= auto req = Request(std::make_pair(counter, 42)); - int c = counter; // This is a weird corner case in C++ where the clojure below - // cannot capture counter by value. Copying it to a local variable helps... + int c = counter; // This is a weird corner case in C++ where the clojure below + // cannot capture counter by value. Copying it to a local variable helps... auto callback = [c](int sum) { reactor::log::Info() << "Result: " << c << " + 42 = " << sum; }; @@ -30,9 +35,6 @@ reactor Client { auto const& callback = add_cm.response_out.get()->second; callback(resp.data()); =} - - add_cm.request_out -> add_request - add_response -> add_cm.response_in } main reactor { diff --git a/Cpp/RequestResponse/src/ContextManager.lf b/Cpp/RequestResponse/src/ContextManager.lf index d3bfdd96..238c194c 100644 --- a/Cpp/RequestResponse/src/ContextManager.lf +++ b/Cpp/RequestResponse/src/ContextManager.lf @@ -13,14 +13,14 @@ reactor ContextManager { state context_buffer: {= std::map =} - reaction (request_in) -> request_out {= + reaction(request_in) -> request_out {= const auto& req = request_in.get()->first; const auto& ctx = request_in.get()->second; context_buffer[req.uid()] = ctx; request_out.set(req); =} - reaction (response_in) -> response_out {= + reaction(response_in) -> response_out {= const auto& resp = *response_in.get(); const auto& ctx = context_buffer[resp.uid()]; response_out.set(std::make_pair(resp, ctx)); diff --git a/Cpp/RequestResponse/src/MAC.lf b/Cpp/RequestResponse/src/MAC.lf index ce3db56c..42c93c62 100644 --- a/Cpp/RequestResponse/src/MAC.lf +++ b/Cpp/RequestResponse/src/MAC.lf @@ -13,7 +13,6 @@ public preamble {= =} reactor MACService { - input request: Request output response: Response @@ -23,8 +22,19 @@ reactor MACService { output add_request: Request> input add_response: Response - add_cm = new ContextManager<{=Request>=}, {=Response=}, {=Request=}>() - mul_cm = new ContextManager<{=Request>=}, {=Response=}, {=Request=}>() + add_cm = new ContextManager< + {= Request> =}, + {= Response =}, + {= Request =}>() + mul_cm = new ContextManager< + {= Request> =}, + {= Response =}, + {= Request =}>() + + mul_cm.request_out -> mul_request + add_cm.request_out -> add_request + mul_response -> mul_cm.response_in + add_response -> add_cm.response_in reaction(request) -> mul_cm.request_in {= auto& original_request = *request.get(); @@ -45,27 +55,25 @@ reactor MACService { auto& original_request = add_cm.response_out.get()->second; response.set(original_request.make_response(result)); =} - - mul_cm.request_out -> mul_request - add_cm.request_out -> add_request - mul_response -> mul_cm.response_in - add_response -> add_cm.response_in } reactor Client { - timer t(0, 100ms) + timer t(0, 100 ms) output mac_request: Request input mac_response: Response state counter: int(0) - mac_cm = new ContextManager<{=Request=}, {=Response=}, MACData>() + mac_cm = new ContextManager<{= Request =}, {= Response =}, MACData>() + + mac_cm.request_out -> mac_request + mac_response -> mac_cm.response_in reaction(t) -> mac_cm.request_in {= auto data = MACData{counter, counter + 1, counter + 2}; auto req = Request(data); mac_cm.request_in.set(std::make_pair(req, data)); - reactor::log::Info() << "Client asks what " << data.factor1 << " * " << data.factor2 + reactor::log::Info() << "Client asks what " << data.factor1 << " * " << data.factor2 << " + " << data.summand << " is"; counter++; =} @@ -73,12 +81,9 @@ reactor Client { reaction(mac_cm.response_out) {= auto const result = mac_cm.response_out.get()->first.data(); auto const& data = mac_cm.response_out.get()->second; - reactor::log::Info() << "Result: " << data.factor1 << " * " << data.factor2 + reactor::log::Info() << "Result: " << data.factor1 << " * " << data.factor2 << " + " << data.summand << " = " << result; =} - - mac_cm.request_out -> mac_request - mac_response -> mac_cm.response_in } main reactor { diff --git a/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 795c7956..a110e132 100644 --- a/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -1,16 +1,15 @@ /** - * Example of a basic digital twin setup, with two federates - * maintaining a shared state "lock state". + * Example of a basic digital twin setup, with two federates maintaining a shared state "lock + * state". * * For run instructions, see README.md in the same directory. - * + * * @author Hou Seng Wong (housengw@berkeley.edu) */ - - target Python { +target Python { docker: true, files: ["../utils.py"] -}; +} preamble {= import curses @@ -30,32 +29,32 @@ preamble {= =} /** - * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock state to and from other key fobs. + * A key fob that detects "lock" and "unlock" key presses, and sends and receives lock state to and + * from other key fobs. */ -reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { - /* logger / window related state variables */ - state logger({=None=}); - state window({=None=}); - state main_message_begins(0); - - /* KeyFob related state variables */ - state lock_state(0); - state listener({=None=}); - state auto_lock_counter; - - /* I/O ports and actions */ - input get_lock_action; - input get_lock_press_from_tester; - output send_lock_action; - physical action press_lock; - physical action press_unlock; - logical action handle_press_lock; - logical action handle_press_unlock; - logical action do_lock; - - /* Autolock timer */ - timer autolock_timer(0, 100 msec); +reactor DoubleUnlockKeyFob(auto_lock_duration=5) { + /** logger / window related state variables */ + state logger = {= None =} + state window = {= None =} + state main_message_begins = 0 + + /** KeyFob related state variables */ + state lock_state = 0 + state listener = {= None =} + state auto_lock_counter + + /** I/O ports and actions */ + input get_lock_action + input get_lock_press_from_tester + output send_lock_action + physical action press_lock + physical action press_unlock + logical action handle_press_lock + logical action handle_press_unlock + logical action do_lock + + /** Autolock timer */ + timer autolock_timer(0, 100 msec) preamble {= def lock_state_str(self, lock_state): @@ -75,7 +74,7 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { if self.logger.log_size() > 0: for i, line in enumerate(self.logger.get_log()): self.window.change_line(self.main_message_begins + 1 + i, line) - + def format_log_message(self, line): elapsed_ptime, tag, remote, do_lock, auto = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " @@ -92,12 +91,12 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { key = "" while key != ord("q"): key = self.window.getch() - if key == ord("l"): + if key == ord("l"): press_lock.schedule(0) elif key == ord("u"): press_unlock.schedule(0) request_stop() - + def reset_autolock_counter(self): self.auto_lock_counter = self.auto_lock_duration =} @@ -132,16 +131,12 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { t.start() =} - reaction(press_lock) -> handle_press_lock {= - handle_press_lock.schedule(0) - =} + reaction(press_lock) -> handle_press_lock {= handle_press_lock.schedule(0) =} - reaction(press_unlock) -> handle_press_unlock {= - handle_press_unlock.schedule(0) - =} + reaction(press_unlock) -> handle_press_unlock {= handle_press_unlock.schedule(0) =} reaction(handle_press_lock) -> do_lock, send_lock_action {= - self.append_log(auto=False, remote=False, do_lock=True) + self.append_log(auto=False, remote=False, do_lock=True) do_lock.schedule(0, True) send_lock_action.set(True) =} @@ -193,8 +188,8 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { } federated reactor { - fob = new DoubleUnlockKeyFob(); - twin = new DoubleUnlockKeyFob(); - fob.send_lock_action -> twin.get_lock_action; - twin.send_lock_action -> fob.get_lock_action; + fob = new DoubleUnlockKeyFob() + twin = new DoubleUnlockKeyFob() + fob.send_lock_action -> twin.get_lock_action + twin.send_lock_action -> fob.get_lock_action } diff --git a/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf b/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf index a0527167..3d07efae 100644 --- a/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf +++ b/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf @@ -1,30 +1,27 @@ /** - * Example of a reactor that sends simulated key presses at arbitrary - * logical time to the key fobs. + * Example of a reactor that sends simulated key presses at arbitrary logical time to the key fobs. * * For run instructions, see README.md in the same directory. - * + * * @author Hou Seng Wong (housengw@berkeley.edu) */ - target Python { docker: true, files: ["../utils.py"] -}; +} -import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; +import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf" +reactor DoubleUnlockKeyFobTester(initial_delay=5) { + state logger = {= None =} + state window = {= None =} -reactor DoubleUnlockKeyFobTester(initial_delay(5)) { - state logger({=None=}); - state window({=None=}); + logical action do_test + logical action simulate_press_fob + logical action simulate_press_twin + output send_lock_press_to_fob + output send_lock_press_to_twin - logical action do_test; - logical action simulate_press_fob; - logical action simulate_press_twin; - output send_lock_press_to_fob; - output send_lock_press_to_twin; - reaction(startup) -> do_test {= print(f"Test starts in {self.initial_delay} seconds...") do_test.schedule(SEC(self.initial_delay)) @@ -53,11 +50,11 @@ reactor DoubleUnlockKeyFobTester(initial_delay(5)) { } federated reactor { - fob = new DoubleUnlockKeyFob(); - twin = new DoubleUnlockKeyFob(); - tester = new DoubleUnlockKeyFobTester(); - tester.send_lock_press_to_fob -> fob.get_lock_press_from_tester; - tester.send_lock_press_to_twin -> twin.get_lock_press_from_tester; - fob.send_lock_action -> twin.get_lock_action; - twin.send_lock_action -> fob.get_lock_action; + fob = new DoubleUnlockKeyFob() + twin = new DoubleUnlockKeyFob() + tester = new DoubleUnlockKeyFobTester() + tester.send_lock_press_to_fob -> fob.get_lock_press_from_tester + tester.send_lock_press_to_twin -> twin.get_lock_press_from_tester + fob.send_lock_action -> twin.get_lock_action + twin.send_lock_action -> fob.get_lock_action } diff --git a/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index e7afef5e..bd8f90fc 100644 --- a/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -1,16 +1,14 @@ /** - * Example of a basic digital twin setup, with two federates - * maintaining a shared state "locked". + * Example of a basic digital twin setup, with two federates maintaining a shared state "locked". * * For run instructions, see README.md in the same directory. - * + * * @author Hou Seng Wong (housengw@berkeley.edu) */ - target Python { docker: true, files: ["../utils.py"] -}; +} preamble {= import curses @@ -19,28 +17,28 @@ preamble {= =} /** - * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock state to and from other key fobs. + * A key fob that detects "lock" and "unlock" key presses, and sends and receives lock state to and + * from other key fobs. */ reactor KeyFob { - /* logger / window related state variables */ - state logger({=None=}); - state window({=None=}); + /** logger / window related state variables */ + state logger = {= None =} + state window = {= None =} - /* KeyFob related state variables */ - state locked({=False=}); - state listener({=None=}); + /** KeyFob related state variables */ + state locked = {= False =} + state listener = {= None =} - /* I/O ports and actions */ - input get_lock_state; - output send_lock_state; - physical action press_lock; - physical action press_unlock; + /** I/O ports and actions */ + input get_lock_state + output send_lock_state + physical action press_lock + physical action press_unlock preamble {= def lock_state_str(self, locked): return "Locked" if locked else "Unlocked" - + def print_lock_state(self): self.window.change_line(1, f"Lock Status: {self.lock_state_str(self.locked)}") @@ -48,7 +46,7 @@ reactor KeyFob { if self.logger.log_size() > 0: for i, line in enumerate(self.logger.get_log()): self.window.change_line(2 + i, line) - + def format_log_message(self, line): elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " @@ -65,7 +63,7 @@ reactor KeyFob { key = "" while key != ord("q"): key = self.window.getch() - if key == ord("l"): + if key == ord("l"): press_lock.schedule(0) elif key == ord("u"): press_unlock.schedule(0) @@ -120,8 +118,8 @@ reactor KeyFob { } federated reactor { - fob = new KeyFob(); - twin = new KeyFob(); - fob.send_lock_state -> twin.get_lock_state; - twin.send_lock_state -> fob.get_lock_state; + fob = new KeyFob() + twin = new KeyFob() + fob.send_lock_state -> twin.get_lock_state + twin.send_lock_state -> fob.get_lock_state } diff --git a/Python/src/Piano/Piano.lf b/Python/src/Piano/Piano.lf index f4ac1e74..b4cd8d3d 100644 --- a/Python/src/Piano/Piano.lf +++ b/Python/src/Piano/Piano.lf @@ -1,24 +1,19 @@ /** - * This Lingua Franca program simulates a virtual piano that listens for user key - * presses, translates them into corresponding piano notes, plays the associated - * sounds, and updates the graphical interface accordingly. At the start, it - * initializes a FluidSynth instance for playing MIDI sounds and a GUI for the - * piano interface. As the user interacts with the program by pressing keys, these - * key presses are captured and translated into notes. The program then plays the - * corresponding sounds using FluidSynth and updates the GUI to reflect the keys - * that are currently being pressed. The program is designed to run continuously - * until the user decides to stop. + * This Lingua Franca program simulates a virtual piano that listens for user key presses, + * translates them into corresponding piano notes, plays the associated sounds, and updates the + * graphical interface accordingly. At the start, it initializes a FluidSynth instance for playing + * MIDI sounds and a GUI for the piano interface. As the user interacts with the program by pressing + * keys, these key presses are captured and translated into notes. The program then plays the + * corresponding sounds using FluidSynth and updates the GUI to reflect the keys that are currently + * being pressed. The program is designed to run continuously until the user decides to stop. */ target Python { files: [gui.py, keys.png, soundfont.sf2], threading: true, keepalive: true -}; - +} -/* - * Receives key presses from the pygame piano process - */ +/** Receives key presses from the pygame piano process */ reactor GetUserInput { preamble {= import threading @@ -29,40 +24,33 @@ reactor GetUserInput { except EOFError: request_stop() return - # Each time a key press is received, schedule a user_response event + # Each time a key press is received, schedule a user_response event user_response.schedule(0, c) =} - physical action user_response; - input user_input_pipe_init; - output user_input; - state user_input({=None=}) # multiprocessing.connection.PipeConnection - + physical action user_response + input user_input_pipe_init + output user_input + state user_input = {= None =} # multiprocessing.connection.PipeConnection + reaction(user_input_pipe_init) -> user_response {= # starts a thread to receive key presses from the pygame process self.user_input = user_input_pipe_init.value t = self.threading.Thread(target=self.listen_for_input, args=(user_response, )) t.start() =} - - reaction(user_response) -> user_input {= - user_input.set(user_response.value) - =} -} + reaction(user_response) -> user_input {= user_input.set(user_response.value) =} +} -/* - * Sends graphics updates to the pygame piano process - */ +/** Sends graphics updates to the pygame piano process */ reactor UpdateGraphics { - input note; - input update_graphics_pipe_init; - state update_graphics({=None=}); # multiprocessing.connection.PipeConnection - state pressed_keys({=set()=}) - - reaction(update_graphics_pipe_init) {= - self.update_graphics = update_graphics_pipe_init.value - =} - + input note + input update_graphics_pipe_init + state update_graphics = {= None =} # multiprocessing.connection.PipeConnection + state pressed_keys = {= set() =} + + reaction(update_graphics_pipe_init) {= self.update_graphics = update_graphics_pipe_init.value =} + reaction(note) {= key_down, note_t = note.value if key_down and note_t not in self.pressed_keys: @@ -74,22 +62,17 @@ reactor UpdateGraphics { =} } - -/* - * Plays sound using fluidsynth upon receiving signal from TranslateKeyToNote - */ +/** Plays sound using fluidsynth upon receiving signal from TranslateKeyToNote */ reactor PlaySound { - state lowest(4); # the octave of the lowest "C" on the piano. - state channel(8); - state Note; - state fluidsynth; - input note; - input play_sound_init; - - reaction(play_sound_init) {= - self.fluidsynth, self.Note = play_sound_init.value - =} - + state lowest = 4 # the octave of the lowest "C" on the piano. + state channel = 8 + state Note + state fluidsynth + input note + input play_sound_init + + reaction(play_sound_init) {= self.fluidsynth, self.Note = play_sound_init.value =} + reaction(note) {= # upon receiving a note, play or stop the note depending on if its a key down or key up. key_down, note_t = note.value @@ -100,48 +83,44 @@ reactor PlaySound { =} } -/* - * Translates key presses to piano keys and triggers the initialization of StartGui - */ +/** Translates key presses to piano keys and triggers the initialization of StartGui */ reactor TranslateKeyToNote { - preamble {= - piano_keys = { - "z": ("C", 0), - "s": ("C#", 0), - "x": ("D", 0), - "d": ("D#", 0), - "c": ("E", 0), - "v": ("F", 0), - "g": ("F#", 0), - "b": ("G", 0), - "h": ("G#", 0), - "n": ("A", 0), - "j": ("A#", 0), - "m": ("B", 0), - "w": ("C", 1), - "3": ("C#", 1), - "e": ("D", 1), - "4": ("D#", 1), - "r": ("E", 1), - "t": ("F", 1), - "6": ("F#", 1), - "y": ("G", 1), - "7": ("G#", 1), - "u": ("A", 1), - "8": ("A#", 1), - "i": ("B", 1) - } - =} - - input user_input; - input translate_init; - output note; - output gui_init; - - reaction(translate_init) -> gui_init {= - gui_init.set(self.piano_keys) + preamble {= + piano_keys = { + "z": ("C", 0), + "s": ("C#", 0), + "x": ("D", 0), + "d": ("D#", 0), + "c": ("E", 0), + "v": ("F", 0), + "g": ("F#", 0), + "b": ("G", 0), + "h": ("G#", 0), + "n": ("A", 0), + "j": ("A#", 0), + "m": ("B", 0), + "w": ("C", 1), + "3": ("C#", 1), + "e": ("D", 1), + "4": ("D#", 1), + "r": ("E", 1), + "t": ("F", 1), + "6": ("F#", 1), + "y": ("G", 1), + "7": ("G#", 1), + "u": ("A", 1), + "8": ("A#", 1), + "i": ("B", 1) + } =} - + + input user_input + input translate_init + output note + output gui_init + + reaction(translate_init) -> gui_init {= gui_init.set(self.piano_keys) =} + reaction(user_input) -> note {= key_down, c = user_input.value if c in self.piano_keys: @@ -153,7 +132,7 @@ reactor StartFluidSynth { preamble {= import sys import os - + try: from mingus.containers.note import Note except: @@ -173,10 +152,10 @@ reactor StartFluidSynth { request_stop() sys.exit(1) =} - state soundfont({=self.os.path.join(self.os.path.dirname(__file__), "soundfont.sf2")=}) - output translate_init; - output play_sound_init; - + state soundfont = {= self.os.path.join(self.os.path.dirname(__file__), "soundfont.sf2") =} + output translate_init + output play_sound_init + reaction(startup) -> play_sound_init, translate_init {= if not self.os.path.exists(self.soundfont): print("Error: Soundfont file does not exist.") @@ -185,7 +164,7 @@ reactor StartFluidSynth { print("Alternatively, pick and download a soundfont from here:") print("https://github.com/FluidSynth/fluidsynth/wiki/SoundFont") print("Rename the soundfont to \"soundfont.sf2\" and put it under the same directory as Piano.lf.") - request_stop() + request_stop() return # initialize fluidsynth @@ -196,23 +175,19 @@ reactor StartFluidSynth { print("Error: Failed to initialize fluidsynth") request_stop() return - + play_sound_init.set((self.fluidsynth, self.Note)) translate_init.set(0) =} } -/* - * Starts the GUI and triggers initialization of UpdateGraphics and GetUserInput reactors. - */ +/** Starts the GUI and triggers initialization of UpdateGraphics and GetUserInput reactors. */ reactor StartGui { - preamble {= - import gui - =} - input gui_init; - output user_input_pipe; - output update_graphics_pipe; - + preamble {= import gui =} + input gui_init + output user_input_pipe + output update_graphics_pipe + reaction(gui_init) -> user_input_pipe, update_graphics_pipe {= piano_keys = gui_init.value user_input_pout, update_graphics_pin = self.gui.start_gui(piano_keys) @@ -226,11 +201,11 @@ main reactor { fs = new StartFluidSynth() translate = new TranslateKeyToNote() update_graphics = new UpdateGraphics() - get_user_input = new GetUserInput() + get_user_input = new GetUserInput() play_sound = new PlaySound() - - fs.translate_init -> translate.translate_init; - fs.play_sound_init -> play_sound.play_sound_init; + + fs.translate_init -> translate.translate_init + fs.play_sound_init -> play_sound.play_sound_init gui.user_input_pipe -> get_user_input.user_input_pipe_init gui.update_graphics_pipe -> update_graphics.update_graphics_pipe_init get_user_input.user_input -> translate.user_input diff --git a/Python/src/ROS/PythonMigration/lf-python/Main.lf b/Python/src/ROS/PythonMigration/lf-python/Main.lf index 91920a86..60ece336 100644 --- a/Python/src/ROS/PythonMigration/lf-python/Main.lf +++ b/Python/src/ROS/PythonMigration/lf-python/Main.lf @@ -1,21 +1,19 @@ /** - * This Lingua Franca program demonstrates a federated reactor system that - * integrates a sender and a receiver, both interfacing with the Robot Operating - * System (ROS). The sender periodically publishes "Hello World" messages to a ROS - * topic, while the receiver listens to the same topic and processes incoming - * messages. The federated design ensures that the sender's output is directed to - * the receiver's input, creating a communication link between the two. It - * showcases the ability of Lingua Franca to support modular, distributed, - * real-time systems where different components interact and communicate - * seamlessly. + * This Lingua Franca program demonstrates a federated reactor system that integrates a sender and a + * receiver, both interfacing with the Robot Operating System (ROS). The sender periodically + * publishes "Hello World" messages to a ROS topic, while the receiver listens to the same topic and + * processes incoming messages. The federated design ensures that the sender's output is directed to + * the receiver's input, creating a communication link between the two. It showcases the ability of + * Lingua Franca to support modular, distributed, real-time systems where different components + * interact and communicate seamlessly. */ -target Python; +target Python import Sender from "Sender.lf" import Receiver from "Receiver.lf" federated reactor { - sender = new Sender(); - receiver = new Receiver(); - sender.topic -> receiver.topic; + sender = new Sender() + receiver = new Receiver() + sender.topic -> receiver.topic } diff --git a/Python/src/ROS/PythonMigration/lf-python/Receiver.lf b/Python/src/ROS/PythonMigration/lf-python/Receiver.lf index 13444af4..f3ab7194 100644 --- a/Python/src/ROS/PythonMigration/lf-python/Receiver.lf +++ b/Python/src/ROS/PythonMigration/lf-python/Receiver.lf @@ -1,20 +1,18 @@ /** - * This Lingua Franca program interfaces with the Robot Operating System (ROS) to - * receive and handle data from a specific topic using the MinimalSubscriber class - * from the ROS py_pubsub package. Upon startup, the program initializes the ROS - * communication and creates a MinimalSubscriber instance. When data arrives on the - * subscribed topic, it is passed to a callback function on the ROS side for - * further processing. Upon shutdown, the program destroys the node and shuts down - * the ROS communication, ensuring a clean exit. The program serves as an example - * of real-time, event-driven programming using Lingua Franca in combination with - * ROS. + * This Lingua Franca program interfaces with the Robot Operating System (ROS) to receive and handle + * data from a specific topic using the MinimalSubscriber class from the ROS py_pubsub package. Upon + * startup, the program initializes the ROS communication and creates a MinimalSubscriber instance. + * When data arrives on the subscribed topic, it is passed to a callback function on the ROS side + * for further processing. Upon shutdown, the program destroys the node and shuts down the ROS + * communication, ensuring a clean exit. The program serves as an example of real-time, event-driven + * programming using Lingua Franca in combination with ROS. */ -// Receiver.lf -target Python; +# Receiver.lf +target Python preamble {= # Locate the MinimalPublisher class written for ROS - # After source the ros package, + # After source the ros package, # Python import would be: # from [package name].[file name] import [class name] from py_pubsub.subscriber_member_function import MinimalSubscriber @@ -26,8 +24,8 @@ preamble {= =} reactor Receiver { - state minimal_subscriber; - input topic; + state minimal_subscriber + input topic reaction(startup) {= rclpy.init(args=None) diff --git a/Python/src/ROS/PythonMigration/lf-python/Sender.lf b/Python/src/ROS/PythonMigration/lf-python/Sender.lf index 2222fc08..1154d343 100644 --- a/Python/src/ROS/PythonMigration/lf-python/Sender.lf +++ b/Python/src/ROS/PythonMigration/lf-python/Sender.lf @@ -1,21 +1,20 @@ /** - * This Lingua Franca program interfaces with the Robot Operating System (ROS) to - * periodically publish messages on a specific topic using the MinimalPublisher - * class from the ROS py_pubsub package. The program initializes ROS communication - * and creates an instance of MinimalPublisher upon startup. Using a timer, it - * generates and publishes a new "Hello World" message every 500 milliseconds, - * incrementing a counter with each message. The message is output to the defined - * topic for consumption by other systems or processes. On shutdown, the program - * destroys the node and shuts down ROS communication, ensuring a clean exit. This - * program showcases the utility of Lingua Franca in enabling deterministic, - * real-time communication in robotics applications using ROS. + * This Lingua Franca program interfaces with the Robot Operating System (ROS) to periodically + * publish messages on a specific topic using the MinimalPublisher class from the ROS py_pubsub + * package. The program initializes ROS communication and creates an instance of MinimalPublisher + * upon startup. Using a timer, it generates and publishes a new "Hello World" message every 500 + * milliseconds, incrementing a counter with each message. The message is output to the defined + * topic for consumption by other systems or processes. On shutdown, the program destroys the node + * and shuts down ROS communication, ensuring a clean exit. This program showcases the utility of + * Lingua Franca in enabling deterministic, real-time communication in robotics applications using + * ROS. */ -// Sender.lf -target Python; +# Sender.lf +target Python preamble {= # Locate the MinimalPublisher class written for ROS - # After source the ros package, + # After source the ros package, # Python import would be: # from [package name].[file name] import [class name] from py_pubsub.publisher_member_function import MinimalPublisher @@ -27,14 +26,14 @@ preamble {= =} reactor Sender { - state minimal_publisher; - timer t(0, 500ms); - output topic; + state minimal_publisher + timer t(0, 500 ms) + output topic reaction(startup) {= rclpy.init(args=None) self.minimal_publisher = MinimalPublisher() - + # rclpy.spin is commented out. #rclpy.spin(self.minimal_publisher) =} diff --git a/Python/src/ReflexGame/ReflexGame.lf b/Python/src/ReflexGame/ReflexGame.lf index fe07b954..d75acd51 100644 --- a/Python/src/ReflexGame/ReflexGame.lf +++ b/Python/src/ReflexGame/ReflexGame.lf @@ -1,32 +1,30 @@ /** - * This Lingua Franca program implements a simple reaction game, where the user - * interacts with a graphical user interface (GUI) and responds to prompts that - * appear at random intervals. The user is asked to press any key in response to - * these prompts, with the time taken to respond being calculated and displayed. - * The game ends when a user presses a key before a prompt shows up, indicating - * they have cheated, with the average response time displayed at the end. This - * program integrates threading for listening to user input, randomization of - * prompt intervals, and inter-process communication to update the GUI, providing - * an interactive, real-time game experience. + * This Lingua Franca program implements a simple reaction game, where the user interacts with a + * graphical user interface (GUI) and responds to prompts that appear at random intervals. The user + * is asked to press any key in response to these prompts, with the time taken to respond being + * calculated and displayed. The game ends when a user presses a key before a prompt shows up, + * indicating they have cheated, with the average response time displayed at the end. This program + * integrates threading for listening to user input, randomization of prompt intervals, and + * inter-process communication to update the GUI, providing an interactive, real-time game + * experience. */ target Python { keepalive: true, files: [gui.py] } -reactor RandomSource(min_time(2 sec), max_time(8 sec)) { +reactor RandomSource(min_time = 2 sec, max_time = 8 sec) { preamble {= import random def additional_time(self, min_time, max_time): return self.random.randint(min_time, max_time) =} - input another; - output out; - logical action prompt(min_time); - state count(0); - reaction(startup) {= - self.random.seed() - =} + input another + output out + logical action prompt(min_time) + state count = 0 + + reaction(startup) {= self.random.seed() =} reaction(prompt) -> out {= self.count += 1 @@ -34,14 +32,12 @@ reactor RandomSource(min_time(2 sec), max_time(8 sec)) { =} reaction(another) -> prompt {= - # schedule a prompt event + # schedule a prompt event prompt.schedule(self.additional_time(0, self.max_time - self.min_time)) =} } -/* - * Receives key presses from the pygame process. - */ +/** Receives key presses from the pygame process. */ reactor GetUserInput { preamble {= import threading @@ -52,104 +48,99 @@ reactor GetUserInput { except EOFError: request_stop() return - # Each time a key press is received, schedule a user_response event + # Each time a key press is received, schedule a user_response event user_response.schedule(0, c) =} - - physical action user_response; - state user_input({=None=}); # multiprocessing.connection.PipeConnection - input user_input_pipe_init; - output user_input; - + + physical action user_response + state user_input = {= None =} # multiprocessing.connection.PipeConnection + input user_input_pipe_init + output user_input + reaction(user_input_pipe_init) -> user_response {= # Stores the Pipe object that will be used to receive key presses from # the pygame process self.user_input = user_input_pipe_init.value - + # Starts the thread that receives key presses from the pygame process t = self.threading.Thread(target=self.listen_for_input, args=(user_response, )) t.start() =} - - reaction(user_response) -> user_input {= - user_input.set(user_response.value) - =} -} + reaction(user_response) -> user_input {= user_input.set(user_response.value) =} +} -/* - * Sends graphics updates to the pygame process. - */ +/** Sends graphics updates to the pygame process. */ reactor UpdateGraphics { - input prompt; - input update_graphics_pipe_init; - input user_input; - output another; - state update_graphics({=None=}); # multiprocessing.connection.PipeConnection - state first({=True=}) - state count(0); - state total_time_in_ms(0); - state prompt_time(0); - + input prompt + input update_graphics_pipe_init + input user_input + output another + state update_graphics = {= None =} # multiprocessing.connection.PipeConnection + state first = {= True =} + state count = 0 + state total_time_in_ms = 0 + state prompt_time = 0 + reaction(update_graphics_pipe_init) {= # Stores the Pipe object that will be used to send graphics update to - # the pygame process + # the pygame process self.update_graphics = update_graphics_pipe_init.value - - # Displays an introductory prompt to the user. + + # Displays an introductory prompt to the user. self.update_graphics.send(((0,0,0), # Color of background (255, 255, 255), # Color of text - "Press any key to begin", - "To end the game, you can either: ", - "1. Close this window", - "2. Press CTRL+C in the Terminal", + "Press any key to begin", + "To end the game, you can either: ", + "1. Close this window", + "2. Press CTRL+C in the Terminal", "3. Press any key before the prompt shows up.")) =} - + reaction(prompt) {= # Ask the user for input upon receiving a prompt input from RandomSource - self.update_graphics.send(((152,251,152), - (0, 0, 0), + self.update_graphics.send(((152,251,152), + (0, 0, 0), "{}. Press any key!".format(prompt.value))) self.prompt_time = lf.time.physical() =} - + reaction(user_input) -> another {= if self.first: # if the first ever key press is detected, set "another" to trigger a prompt from RandomSource self.first = False - self.update_graphics.send(((205,92,92), - (0, 0, 0), + self.update_graphics.send(((205,92,92), + (0, 0, 0), "Wait for the prompt...")) - - # ask for the first ever prompt + + # ask for the first ever prompt another.set(42) elif self.prompt_time == 0: if self.count > 0: - self.update_graphics.send(((205,92,92), - (0, 0, 0), - "YOU CHEATED!", + self.update_graphics.send(((205,92,92), + (0, 0, 0), + "YOU CHEATED!", "Average response time: {:.2f} ms".format(self.total_time_in_ms / self.count))) else: - self.update_graphics.send(((205,92,92), - (0, 0, 0), - "YOU CHEATED!", + self.update_graphics.send(((205,92,92), + (0, 0, 0), + "YOU CHEATED!", "Average response time: undefined")) request_stop() else: time_in_ms = (lf.time.logical() - self.prompt_time) // MSEC(1) - self.update_graphics.send(((205,92,92), - (0, 0, 0), - "Response time in milliseconds: {}".format(time_in_ms), + self.update_graphics.send(((205,92,92), + (0, 0, 0), + "Response time in milliseconds: {}".format(time_in_ms), "Wait for the prompt...")) self.count += 1 self.total_time_in_ms += time_in_ms self.prompt_time = 0 - + # ask for another prompt another.set(42) =} - + reaction(shutdown) {= if self.count > 0: print("Average response time: {:.2f} ms".format(self.total_time_in_ms / self.count)) @@ -158,30 +149,26 @@ reactor UpdateGraphics { =} } -/* - * Starts the GUI and pass the user_input_pout and - * update_graphics_pin Pipe objects - * to GetUserInput and UpdateGraphics +/** + * Starts the GUI and pass the user_input_pout and update_graphics_pin Pipe objects to GetUserInput + * and UpdateGraphics */ reactor StartGui { - preamble {= - import gui - =} + preamble {= import gui =} + + output user_input_pipe + output update_graphics_pipe - output user_input_pipe; - output update_graphics_pipe; - reaction(startup) -> user_input_pipe, update_graphics_pipe {= # Starts the gui pygame process user_input_pout, update_graphics_pin = self.gui.start_gui() - - # Sets the outputs to trigger the initialization of GetUserInput and UpdateGrpahics + + # Sets the outputs to trigger the initialization of GetUserInput and UpdateGrpahics user_input_pipe.set(user_input_pout) update_graphics_pipe.set(update_graphics_pin) =} } - main reactor { random_source = new RandomSource() get_user_input = new GetUserInput() @@ -190,6 +177,6 @@ main reactor { get_user_input.user_input -> update_graphics.user_input update_graphics.another -> random_source.another gui = new StartGui() - gui.user_input_pipe -> get_user_input.user_input_pipe_init; - gui.update_graphics_pipe -> update_graphics.update_graphics_pipe_init; + gui.user_input_pipe -> get_user_input.user_input_pipe_init + gui.update_graphics_pipe -> update_graphics.update_graphics_pipe_init } diff --git a/Python/src/TrainDoor/TrainDoor.lf b/Python/src/TrainDoor/TrainDoor.lf index d26e6c35..d63e72c6 100644 --- a/Python/src/TrainDoor/TrainDoor.lf +++ b/Python/src/TrainDoor/TrainDoor.lf @@ -1,9 +1,8 @@ /** - * Program that emulates a train door controller. It has two components: - * one that controls the door and one that senses motion. When the door - * controller receives a request to open the door (a button press), it has - * to first check whether the vehicle was recently in motion. The request - * will be denied if motion has been detected less than two seconds ago. + * Program that emulates a train door controller. It has two components: one that controls the door + * and one that senses motion. When the door controller receives a request to open the door (a + * button press), it has to first check whether the vehicle was recently in motion. The request will + * be denied if motion has been detected less than two seconds ago. */ target Python @@ -16,11 +15,11 @@ reactor MotionDetector { print("Press 'c' and hit return or enter to close the door") print("Press 'm' and hit return or enter perturb the motion sensor") print("Press 'Control-d' to exit") - + global move_action global open_action global close_action - + while 1: try: c = input("> ") @@ -35,20 +34,23 @@ reactor MotionDetector { close_action.schedule(0) =} physical action movement - state timestamp(0) + state timestamp = 0 input check output ok + reaction(startup) -> movement {= global move_action move_action = movement - + t = self.threading.Thread(target=self.listen_for_input) t.start() =} + reaction(movement) {= print("Motion detected!") self.timestamp = lf.time.logical_elapsed() =} + reaction(check) -> ok {= if self.timestamp == 0 or lf.time.logical_elapsed() - self.timestamp > SECS(2): ok.set(True) @@ -58,21 +60,21 @@ reactor MotionDetector { } reactor DoorController { - physical action open physical action close - + output check input ok - state opened(False) - state requested(False) + state opened = False + state requested = False + reaction(startup) -> open, close {= global open_action open_action = open global close_action close_action = close =} - + reaction(open) -> check {= if self.opened: print("The door is already open") @@ -81,16 +83,16 @@ reactor DoorController { check.set(False) self.requested = True =} - + reaction(close) {= print("Closing the door") self.opened = False =} - + reaction(ok) {= if self.requested and ok.value: self.opened = True - print("Opening the door.") + print("Opening the door.") else: print("Cannot open the door recent motion detected.") diff --git a/Python/src/YOLOv5/YOLOv5_Webcam.lf b/Python/src/YOLOv5/YOLOv5_Webcam.lf index 5aafd433..f91e703a 100644 --- a/Python/src/YOLOv5/YOLOv5_Webcam.lf +++ b/Python/src/YOLOv5/YOLOv5_Webcam.lf @@ -1,25 +1,20 @@ /** - * Example of a Deep Neural Network (YOLOv5) in LF. - * Please see README.md for instructions. - * Adapted from + * Example of a Deep Neural Network (YOLOv5) in LF. Please see README.md for instructions. Adapted + * from * https://towardsdatascience.com/implementing-real-time-object-detection-system-using-pytorch-and-opencv-70bac41148f7 */ -target Python; +target Python -preamble {= - BILLION = 1_000_000_000 -=} +preamble {= BILLION = 1_000_000_000 =} /** * Use OpenCV2 to read from the user webcam. - * - * Camera frames are captured into the LF program - * via a physical action. - * - * 'webcam_id' (default 0) can be adjusted - * according your the local setup. + * + * Camera frames are captured into the LF program via a physical action. + * + * 'webcam_id' (default 0) can be adjusted according your the local setup. */ -reactor WebCam(webcam_id(0)) { +reactor WebCam(webcam_id=0) { output camera_frame state stream state video_capture_thread @@ -28,7 +23,7 @@ reactor WebCam(webcam_id(0)) { preamble {= from cv2 import cv2 import threading - + def video_capture(self, frame_action, running): # Read a frame ret, frame = self.stream.read() @@ -39,24 +34,24 @@ reactor WebCam(webcam_id(0)) { ret, frame = self.stream.read() return None =} + reaction(startup) -> frame_action {= self.stream = self.cv2.VideoCapture(self.webcam_id, self.cv2.CAP_ANY) if (self.stream.isOpened() is not True): sys.stderr.write("Error: Failed to capture from the webcam.\n") exit(1) - + self.stream.set(self.cv2.CAP_PROP_FPS, 30) # Set the camera's FPS to 30 - + self.thread_should_be_running = self.threading.Event() self.thread_should_be_running.set() - + self.video_capture_thread = self.threading.Thread(target=self.video_capture, args=(frame_action, self.thread_should_be_running)) self.video_capture_thread.start() =} - reaction(frame_action) -> camera_frame {= - camera_frame.set(frame_action.value) - =} - + + reaction(frame_action) -> camera_frame {= camera_frame.set(frame_action.value) =} + reaction(shutdown) {= self.thread_should_be_running.clear() self.video_capture_thread.join() @@ -65,37 +60,34 @@ reactor WebCam(webcam_id(0)) { } /** - * A YOLOv5 DNN that takes a frame as input and - * produces object 'labels' and object label coordinates - * (where each label/object is on the frame). + * A YOLOv5 DNN that takes a frame as input and produces object 'labels' and object label + * coordinates (where each label/object is on the frame). */ reactor DNN { - // Image input frame - input frame - - // Label outputs - output labels - // Label coordinates - output label_coordinates - // Send the model to anyone who's interested - output model - - state _model # The DNN model - state _device # The device to use (e.g., cpu or cuda) + input frame # Image input frame + + output labels # Label outputs + output label_coordinates # Label coordinates + output model # Send the model to anyone who's interested + + state _model # The DNN model + state _device # The device to use (e.g., cpu or cuda) preamble {= import torch from torch import hub =} + reaction(startup) -> model {= # Load YOLOv5 self._model = self.torch.hub.load("ultralytics/yolov5", "yolov5s", pretrained=True) - # Find out if CUDA is supported + # Find out if CUDA is supported self._device = "cuda" if self.torch.cuda.is_available() else 'cpu' # Send the model to device self._model.to(self._device) # Send the model to whoever is interested (other reactors) model.set(self._model) =} + reaction(frame) -> labels, label_coordinates {= _, frame_data = frame.value # Convert the frame into a tuple @@ -109,50 +101,41 @@ reactor DNN { =} } -/** - * Plot frames with labels superimposed on top of - * each object in the frame. - */ -reactor Plotter(label_deadline(100 msec)) { +/** Plot frames with labels superimposed on top of each object in the frame. */ +reactor Plotter(label_deadline = 100 msec) { input frame input labels input label_coordinates input model - state _model # Keep the model - state _prev_time(0); + state _model # Keep the model + state _prev_time = 0 - preamble {= - from cv2 import cv2 - =} - - /** - * Receive the DNN model - */ + preamble {= from cv2 import cv2 =} + + /** Receive the DNN model */ reaction(model) {= self._model = model.value print("\n******* Press 'q' to exit *******\n") =} - - /** - * Impose a deadline on object labels - */ + + /** Impose a deadline on object labels */ reaction(labels) {= # DNN output was on time =} deadline(label_deadline) {= print(f"Received the DNN output late by about {(lf.time.physical() - lf.time.logical())/1000000}ms.") =} - + /** - * Given a frame, object labels, and the corresponding - * object label coordinates, draw an interactive OpenCV window. + * Given a frame, object labels, and the corresponding object label coordinates, draw an + * interactive OpenCV window. */ reaction(frame, labels, label_coordinates) {= - if (not frame.is_present or - not labels.is_present or + if (not frame.is_present or + not labels.is_present or not label_coordinates.is_present): sys.stderr.write("Error: Expected all inputs to be present at the same time.\n") request_stop() - + elapsed_time, frame_data = frame.value # Get how many labels we have n = len(labels.value) @@ -160,7 +143,7 @@ reactor Plotter(label_deadline(100 msec)) { for i in range(n): row = label_coordinates.value[i] # If score is less than 0.2 we avoid making a prediction. - if row[4] < 0.2: + if row[4] < 0.2: continue x1 = int(row[0]*x_shape) y1 = int(row[1]*y_shape) @@ -176,18 +159,18 @@ reactor Plotter(label_deadline(100 msec)) { classes[int(labels.value[i])], \ (x1, y1), \ label_font, 0.9, bgr, 2) #Put a label over box. - + fps = int(1 / (elapsed_time / BILLION - self._prev_time / BILLION)) self._prev_time = elapsed_time - self.cv2.putText(frame_data, str(fps), (7, 70), - self.cv2.FONT_HERSHEY_SIMPLEX, 3, + self.cv2.putText(frame_data, str(fps), (7, 70), + self.cv2.FONT_HERSHEY_SIMPLEX, 3, (100, 255, 0), 3, self.cv2.LINE_AA) self.cv2.imshow("frame", frame_data) # press 'Q' if you want to exit if self.cv2.waitKey(1) & 0xFF == ord('q'): request_stop() =} - + reaction(shutdown) {= # Destroy the all windows now self.cv2.destroyAllWindows() @@ -198,14 +181,13 @@ main reactor { webcam = new WebCam() dnn = new DNN() plotter = new Plotter() - - // Send the camera frame to the DNN to be process and to the plotter to be depicted + + # Send the camera frame to the DNN to be process and to the plotter to be depicted (webcam.camera_frame)+ -> dnn.frame, plotter.frame - // Send outputs of the DNN (object labels and their coordinates) to the plotter + # Send outputs of the DNN (object labels and their coordinates) to the plotter dnn.labels, dnn.label_coordinates -> plotter.labels, plotter.label_coordinates - - // Send the DNN model to the plotter. It will be used to extract the human-readable names - // of each label. + + # Send the DNN model to the plotter. It will be used to extract the human-readable names + # of each label. dnn.model -> plotter.model - } diff --git a/Python/src/YOLOv5/YOLOv5_Webcam_Timer.lf b/Python/src/YOLOv5/YOLOv5_Webcam_Timer.lf index fe6ee5fd..bf0db6b0 100644 --- a/Python/src/YOLOv5/YOLOv5_Webcam_Timer.lf +++ b/Python/src/YOLOv5/YOLOv5_Webcam_Timer.lf @@ -1,63 +1,55 @@ /** - * Example of a Deep Neural Network (YOLOv5) in LF. - * Please see README.md for instructions. - * This example is similar to YOLOv5_Webcam but uses a timer to - * get camera frames instead of physical actions. - * Adapted from + * Example of a Deep Neural Network (YOLOv5) in LF. Please see README.md for instructions. This + * example is similar to YOLOv5_Webcam but uses a timer to get camera frames instead of physical + * actions. Adapted from * https://towardsdatascience.com/implementing-real-time-object-detection-system-using-pytorch-and-opencv-70bac41148f7 */ -target Python; +target Python import DNN, Plotter from "YOLOv5_Webcam.lf" /** * Use OpenCV2 to read from the user webcam. - * - * Camera frames are captured periodically - * using a timer. - * - * 'webcam_id' (default 0) can be adjusted - * according to your local setup. + * + * Camera frames are captured periodically using a timer. + * + * 'webcam_id' (default 0) can be adjusted according to your local setup. */ reactor WebCam { output camera_frame state stream state video_capture_thread state thread_should_be_running - - preamble {= - import cv2 - =} + + preamble {= import cv2 =} + + timer camera_tick(3 sec, 100 msec) + reaction(startup) {= self.stream = self.cv2.VideoCapture(0, self.cv2.CAP_ANY) if (self.stream.isOpened() is not True): sys.stderr.write("Error: Failed to capture from the webcam.\n") exit(1) - + self.stream.set(self.cv2.CAP_PROP_FPS, 30) # Set the camera's FPS to 30 =} - - timer camera_tick(3 sec, 100 msec); - + reaction(camera_tick) -> camera_frame {= ret, frame = self.stream.read() if ret is True: camera_frame.set((lf.time.physical_elapsed(), frame)) =} - - reaction(shutdown) {= - self.stream.release() - =} + + reaction(shutdown) {= self.stream.release() =} } main reactor { webcam = new WebCam() dnn = new DNN() plotter = new Plotter(label_deadline = 100 msec) - + (webcam.camera_frame)+ -> dnn.frame, plotter.frame dnn.labels, dnn.label_coordinates -> plotter.labels, plotter.label_coordinates - + dnn.model -> plotter.model - } diff --git a/Python/src/acas/ACASXu.lf b/Python/src/acas/ACASXu.lf index 4a67b839..71b9f9da 100644 --- a/Python/src/acas/ACASXu.lf +++ b/Python/src/acas/ACASXu.lf @@ -1,38 +1,35 @@ /** - * This program models two aircraft moving in a two-dimensional space - * for testing an Airborne Collision Avoidance System (ACAS) that is - * realized using neural networks. - * - * It includes a main reactor that applies a simple test where the - * intruder aircraft follows a straight trajectory and the "own" aircraft - * maneuvers to avoid a collision. - * + * This program models two aircraft moving in a two-dimensional space for testing an Airborne + * Collision Avoidance System (ACAS) that is realized using neural networks. + * + * It includes a main reactor that applies a simple test where the intruder aircraft follows a + * straight trajectory and the "own" aircraft maneuvers to avoid a collision. + * * It is based on the following paper: - * - * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, - * Christophe Garion, ans Claire Pagetti, "Verification of machine - * learning based cyber-physical systems: a comparative study," - * International Conference on Hybrid Systems: Computation and Control - * (HSCC), May 2022, Pages 1–16, https://doi.org/10.1145/3501710.3519540 * - * The original Python code on which this is based was provided by - * Arthur Clavière and can be found at: - * - * https://svn.onera.fr/schedmcore/branches/ACAS_CaseStudy/ + * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, Christophe Garion, ans Claire Pagetti, + * "Verification of machine learning based cyber-physical systems: a comparative study," + * International Conference on Hybrid Systems: Computation and Control (HSCC), May 2022, Pages 1–16, + * https://doi.org/10.1145/3501710.3519540 + * + * The original Python code on which this is based was provided by Arthur Clavière and can be found + * at: + * + * https://svn.onera.fr/schedmcore/branches/ACAS_CaseStudy/ * * Since that original code bears an LGPL license, so does this program: - * - * The ML models (in code/src/systems/acasxu/nnets) - * come from https://github.com/guykatzz/ReluplexCav2017 - * + * + * The ML models (in code/src/systems/acasxu/nnets) come from + * https://github.com/guykatzz/ReluplexCav2017 + * * # Prerequisites - * * ``` * pip install wheel * pip install pandas * pip install matplotlib * ``` - * + + * * @author Arthur Clavière * @author Edward A. Lee * @author Claire Pagetti @@ -41,6 +38,7 @@ target Python { fast: true, timeout: 16 s } + import XYPlotter from "lib/XYPlotter.lf" import Aircraft from "lib/Aircraft.lf" import ACASController from "lib/ACASController.lf" @@ -54,6 +52,7 @@ reactor Diff { input y2 output x output y + reaction(x1, y1, x2, y2) -> x, y {= x.set(x1.value - x2.value) y.set(y1.value - y2.value) @@ -62,34 +61,33 @@ reactor Diff { main reactor { own = new Aircraft( - x_init = 0.0, # Initial x position in feet. - y_init = 0.0, # Initial y position in feet. - psi_init = 0.0, # Angle in radians, relative to vertical, positive counterclockwise - v_init = 248.74685927665496, # Initial velocity, in feet/second. - period = 10 ms # Rate of updates. - ) - + x_init=0.0, # Initial x position in feet. + y_init=0.0, # Initial y position in feet. + psi_init=0.0, # Angle in radians, relative to vertical, positive counterclockwise + v_init=248.74685927665496, # Initial velocity, in feet/second. + # Rate of updates. + period = 10 ms) + intruder = new Aircraft( - x_init = -6000.0, - y_init = 0.0, - psi_init = -0.9851107833377457, - v_init = 450.0, - period = 10 ms - ) + x_init=-6000.0, + y_init=0.0, + psi_init=-0.9851107833377457, + v_init=450.0, + period = 10 ms) controller = new ACASController(period = 1 s) diff = new Diff() - + plot = new XYPlotter() intruder.x, intruder.y -> diff.x1, diff.y1 own.x, own.y -> diff.x2, diff.y2 - + diff.x, diff.y -> controller.x, controller.y own.psi, intruder.psi -> controller.psi_own, controller.psi_int own.v, intruder.v -> controller.v_own, controller.v_int - + controller.command -> own.turn - + own.x, own.y -> plot.x1, plot.y1 intruder.x, intruder.y -> plot.x2, plot.y2 } diff --git a/Python/src/acas/ACASXu2.lf b/Python/src/acas/ACASXu2.lf index 9317bd8b..fee861cc 100644 --- a/Python/src/acas/ACASXu2.lf +++ b/Python/src/acas/ACASXu2.lf @@ -1,29 +1,28 @@ /** - * This program models two aircraft moving in a two-dimensional space - * for testing an Airborne Collision Avoidance System (ACAS). - * In this example, both aircraft are equipped with the same ACAS system. - * + * This program models two aircraft moving in a two-dimensional space for testing an Airborne + * Collision Avoidance System (ACAS). In this example, both aircraft are equipped with the same ACAS + * system. + * * It is based on the following paper: - * - * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, - * Christophe Garion, ans Claire Pagetti, "Verification of machine - * learning based cyber-physical systems: a comparative study," - * International Conference on Hybrid Systems: Computation and Control - * (HSCC), May 2022, Pages 1–16, https://doi.org/10.1145/3501710.3519540 + * + * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, Christophe Garion, ans Claire Pagetti, + * "Verification of machine learning based cyber-physical systems: a comparative study," + * International Conference on Hybrid Systems: Computation and Control (HSCC), May 2022, Pages 1–16, + * https://doi.org/10.1145/3501710.3519540 * * The original Python code on which this is based was provided by Arthur Clavière. - * - * The ML models (in code/src/systems/acasxu/nnets) - * come from https://github.com/guykatzz/ReluplexCav2017 - * + * + * The ML models (in code/src/systems/acasxu/nnets) come from + * https://github.com/guykatzz/ReluplexCav2017 + * * # Prerequisites - * * ``` * pip install wheel * pip install pandas * pip install matplotlib * ``` - * + + * * @author Arthur Clavière * @author Edward A. Lee * @author Claire Pagetti @@ -32,6 +31,7 @@ target Python { fast: true, timeout: 16 s } + import XYPlotter from "lib/XYPlotter.lf" import Aircraft from "lib/Aircraft.lf" import ACASController from "lib/ACASController.lf" @@ -45,6 +45,7 @@ reactor Diff { input y2 output x output y + reaction(x1, y1, x2, y2) -> x, y {= x.set(x1.value - x2.value) y.set(y1.value - y2.value) @@ -53,46 +54,44 @@ reactor Diff { main reactor { own = new Aircraft( - x_init = 0.0, # Initial x position in feet. - y_init = 0.0, # Initial y position in feet. - psi_init = 0.0, # Angle in radians, relative to vertical, positive counterclockwise - v_init = 248.74685927665496, # Initial velocity, in feet/second. - period = 10 ms # Rate of updates. - ) - + x_init=0.0, # Initial x position in feet. + y_init=0.0, # Initial y position in feet. + psi_init=0.0, # Angle in radians, relative to vertical, positive counterclockwise + v_init=248.74685927665496, # Initial velocity, in feet/second. + # Rate of updates. + period = 10 ms) + intruder = new Aircraft( - x_init = -6000.0, - y_init = 0.0, - psi_init = -0.9851107833377457, - v_init = 450.0, - period = 10 ms - ) + x_init=-6000.0, + y_init=0.0, + psi_init=-0.9851107833377457, + v_init=450.0, + period = 10 ms) controller1 = new ACASController(period = 1 s) diff1 = new Diff() - + controller2 = new ACASController(period = 1 s) diff2 = new Diff() - + plot = new XYPlotter() intruder.x, intruder.y -> diff1.x1, diff1.y1 own.x, own.y -> diff1.x2, diff1.y2 - + intruder.x, intruder.y -> diff2.x2, diff2.y2 own.x, own.y -> diff2.x1, diff2.y1 - + diff1.x, diff1.y -> controller1.x, controller1.y own.psi, intruder.psi -> controller1.psi_own, controller1.psi_int own.v, intruder.v -> controller1.v_own, controller1.v_int controller1.command -> own.turn - + # Reverse role of intruder and own for second aircraft. diff2.x, diff2.y -> controller2.x, controller2.y own.psi, intruder.psi -> controller2.psi_int, controller2.psi_own own.v, intruder.v -> controller2.v_int, controller2.v_own controller2.command -> intruder.turn - - + own.x, own.y -> plot.x1, plot.y1 intruder.x, intruder.y -> plot.x2, plot.y2 } diff --git a/Python/src/acas/lib/ACASController.lf b/Python/src/acas/lib/ACASController.lf index 40cf5a1e..0b282e7c 100644 --- a/Python/src/acas/lib/ACASController.lf +++ b/Python/src/acas/lib/ACASController.lf @@ -1,28 +1,27 @@ /** - * This program realizes a neural-network-based controller for an - * Airborne Collision Avoidance System (ACAS). - * + * This program realizes a neural-network-based controller for an Airborne Collision Avoidance + * System (ACAS). + * * It is based on the following paper: - * - * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, - * Christophe Garion, ans Claire Pagetti, "Verification of machine - * learning based cyber-physical systems: a comparative study," - * International Conference on Hybrid Systems: Computation and Control - * (HSCC), May 2022, Pages 1–16, https://doi.org/10.1145/3501710.3519540 + * + * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, Christophe Garion, ans Claire Pagetti, + * "Verification of machine learning based cyber-physical systems: a comparative study," + * International Conference on Hybrid Systems: Computation and Control (HSCC), May 2022, Pages 1–16, + * https://doi.org/10.1145/3501710.3519540 * * The original Python code on which this is based was provided by Arthur Clavière. - * - * The ML models (in code/src/systems/acasxu/nnets) - * come from https://github.com/guykatzz/ReluplexCav2017 - * + * + * The ML models (in code/src/systems/acasxu/nnets) come from + * https://github.com/guykatzz/ReluplexCav2017 + * * # Prerequisites - * * ``` * pip install wheel * pip install pandas * pip install matplotlib * ``` - * + + * * @author Arthur Clavière * @author Edward A. Lee * @author Claire Pagetti @@ -31,6 +30,7 @@ target Python { fast: true, timeout: 16 s } + import NN from "ACASNN.lf" preamble {= @@ -38,27 +38,24 @@ preamble {= import math =} -reactor PreProcessor( - nn_period(10 ms) -) { - input x # x position of intruder relative to own - input y # y position of intruder relative to own +reactor PreProcessor(nn_period = 10 ms) { + input x # x position of intruder relative to own + input y # y position of intruder relative to own input psi_own # angle of own trajectory input psi_int # angle of intruder trajectory input v_own # speed of own trajectory input v_int # speed of intruder trajectory output vector # [rho, theta, psi, v_own, v_int] - + timer t(0, nn_period) - + reaction(t) x, y, psi_own, psi_int, v_own, v_int -> vector {= - pi = math.pi - + # 1) compute rho rho = np.sqrt(x.value**2 + y.value**2) - + # 2) compute theta if y.value > 0: angle = -np.arctan(x.value/y.value) @@ -74,7 +71,7 @@ reactor PreProcessor( theta += 2*pi while theta > pi: theta -= 2*pi - + # 3) compute psi psi = psi_int.value - psi_own.value # wrap psi into [-pi,pi] @@ -82,55 +79,53 @@ reactor PreProcessor( psi += 2*pi while psi > pi: psi -= 2*pi - + vector.set(np.array([rho, theta, psi, v_own.value, v_int.value])) =} } # Output is produced one microstep later. reactor PostProcessor( - initial_label(0), - available_commands({=[0.0,1.5,-1.5,3.5,-3.5]=}) # Angles in degrees/second -) { + initial_label=0, + # Angles in degrees/second + available_commands = {= [0.0,1.5,-1.5,3.5,-3.5] =}) { input score output previous_label output command - + logical action next - + reaction(startup) -> previous_label, command {= previous_label.set(self.initial_label) command.set(self.available_commands[self.initial_label]) =} + reaction(next) -> previous_label, command {= previous_label.set(next.value) command.set(self.available_commands[next.value] * math.pi/180.0) =} - reaction(score) -> next {= - next.schedule(0, np.argmin(score.value)) - =} + + reaction(score) -> next {= next.schedule(0, np.argmin(score.value)) =} } -reactor ACASController( - period(1 s) -) { - input x # x position of intruder relative to own - input y # y position of intruder relative to own - input psi_own # angle of own trajectory - input psi_int # angle of intruder trajectory - input v_own # speed of own trajectory - input v_int # speed of intruder trajectory +reactor ACASController(period = 1 s) { + input x # x position of intruder relative to own + input y # y position of intruder relative to own + input psi_own # angle of own trajectory + input psi_int # angle of intruder trajectory + input v_own # speed of own trajectory + input v_int # speed of intruder trajectory - output command # Turn command for own in radians/sec + output command # Turn command for own in radians/sec - pre = new PreProcessor(nn_period = period) + pre = new PreProcessor(nn_period=period) nn = new NN() post = new PostProcessor() - + x, y -> pre.x, pre.y psi_own, psi_int -> pre.psi_own, pre.psi_int v_own, v_int -> pre.v_own, pre.v_int - + pre.vector -> nn.vector nn.score -> post.score post.command -> command diff --git a/Python/src/acas/lib/ACASNN.lf b/Python/src/acas/lib/ACASNN.lf index 3141df51..129874f0 100644 --- a/Python/src/acas/lib/ACASNN.lf +++ b/Python/src/acas/lib/ACASNN.lf @@ -1,33 +1,35 @@ /** * A bank of neural networks for an ACAS (Airborne Collision Avoidance System). - * + * * This is based on the following paper: - * - * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, - * Christophe Garion, ans Claire Pagetti, "Verification of machine - * learning based cyber-physical systems: a comparative study," - * International Conference on Hybrid Systems: Computation and Control - * (HSCC), May 2022, Pages 1–16, https://doi.org/10.1145/3501710.3519540 + * + * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, Christophe Garion, ans Claire Pagetti, + * "Verification of machine learning based cyber-physical systems: a comparative study," + * International Conference on Hybrid Systems: Computation and Control (HSCC), May 2022, Pages 1–16, + * https://doi.org/10.1145/3501710.3519540 * * The original Python code on which this is based was provided by Arthur Clavière. - * - * The ML models (in code/src/systems/acasxu/nnets) - * come from https://github.com/guykatzz/ReluplexCav2017 - * + * + * The ML models (in code/src/systems/acasxu/nnets) come from + * https://github.com/guykatzz/ReluplexCav2017 + * * # Prerequisites - * * ``` * pip install wheel * pip install pandas * pip install matplotlib * ``` - * + + * * @author Arthur Clavière * @author Edward A. Lee * @author Claire Pagetti */ target Python { - files: ["../code/src/utils.py", "../code/src/mlmodels/ffnn.py", "../code/src/systems/acasxu/nnets/"] + files: [ + "../code/src/utils.py", + "../code/src/mlmodels/ffnn.py", + "../code/src/systems/acasxu/nnets/"] } preamble {= @@ -35,18 +37,16 @@ preamble {= from utils import parse_nnet_format =} -reactor NN( - prefix_nnet_names("nnet_acas_") -) { +reactor NN(prefix_nnet_names="nnet_acas_") { input vector - input index # Index of neural network to use. - - output score # A vector with six scores. - - state nnets({=[]=}) # Empty list initially. - state norm_parameters({={}=}) # Empty map initially. - state nnets_dict({={}=}) # Empty map initially. - + input index # Index of neural network to use. + + output score # A vector with six scores. + + state nnets = {= [] =} # Empty list initially. + state norm_parameters = {= {} =} # Empty map initially. + state nnets_dict = {= {} =} # Empty map initially. + # Read and parse neural network definitions. reaction(startup) {= for i in range(1,6): @@ -58,29 +58,28 @@ reactor NN( # name the network nnet_name = self.prefix_nnet_names + str(i-1) # append the network to the nnets list - self.nnets.append((nnet_name, nnet, path_nnet)) + self.nnets.append((nnet_name, nnet, path_nnet)) # update the norm_parameters dictionary self.norm_parameters[nnet_name] = norm_params # Create a dictionary with the neural networks for (nnet_name, nnet, path_nnet) in self.nnets: self.nnets_dict[nnet_name] = nnet - =} - + =} + reaction(vector) index -> score {= - # (ii) select the neural network to be executed, depending on the index nnet_name = self.prefix_nnet_names + str(index.value) - + # Normalize the vector norm_params = self.norm_parameters[nnet_name] x_mean = norm_params[2] x_range = norm_params[3] x_norm = (vector.value - x_mean) / x_range - + # (iv) evaluate the neural network score.set(self.nnets_dict[nnet_name].compute_output(x_norm)) - + # score.set([1.0, 0.0, 1.0, 1.0, 1.0, 1.0]) =} } diff --git a/Python/src/acas/lib/Aircraft.lf b/Python/src/acas/lib/Aircraft.lf index f56df7f2..c73b806b 100644 --- a/Python/src/acas/lib/Aircraft.lf +++ b/Python/src/acas/lib/Aircraft.lf @@ -1,63 +1,60 @@ /** - * Model of a constant speed aircraft moving in two dimensions - * under control of an input turn command - * for testing an Airborne Collision Avoidance System (ACAS). - * + * Model of a constant speed aircraft moving in two dimensions under control of an input turn + * command for testing an Airborne Collision Avoidance System (ACAS). + * * It is based on the following paper: - * - * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, - * Christophe Garion, ans Claire Pagetti, "Verification of machine - * learning based cyber-physical systems: a comparative study," - * International Conference on Hybrid Systems: Computation and Control - * (HSCC), May 2022, Pages 1–16, https://doi.org/10.1145/3501710.3519540 + * + * Arthur Clavière, Laura Altieri Sambartolomé, Eric Asselin, Christophe Garion, ans Claire Pagetti, + * "Verification of machine learning based cyber-physical systems: a comparative study," + * International Conference on Hybrid Systems: Computation and Control (HSCC), May 2022, Pages 1–16, + * https://doi.org/10.1145/3501710.3519540 * * The original Python code on which this is based was provided by Arthur Clavière. - * + * * # Prerequisites - * * ``` * pip install wheel * pip install pandas * ``` - * + + * * @author Arthur Clavière * @author Edward A. Lee * @author Claire Pagetti */ target Python -preamble {= - import math -=} +preamble {= import math =} reactor Aircraft( - x_init(0.0), # Initial x position in feet. - y_init(0.0), # Initial y position in feet. - psi_init(0.0), # Angle in radians, relative to vertical, positive counterclockwise - v_init(250.0), # Initial velocity, in feet/second. - period(10 ms) # Rate of updates. -) { - input turn # Angle change, in radians/second. Leave unconnected for no turns. - + x_init=0.0, # Initial x position in feet. + y_init=0.0, # Initial y position in feet. + psi_init=0.0, # Angle in radians, relative to vertical, positive counterclockwise + v_init=250.0, # Initial velocity, in feet/second. + # Rate of updates. + period = 10 ms) { + input turn # Angle change, in radians/second. Leave unconnected for no turns. + # Outputs are used as state variables. - output x # x position - output y # y position - output psi # Angle in radians - output v # Velocity in feet/second - - state latest_turn(0.0) # Most recently received turn command. + # x position + output x + output y # y position + output psi # Angle in radians + output v # Velocity in feet/second + + state latest_turn = 0.0 # Most recently received turn command. timer t(period, period) # Offset of period to not overwrite initial state. - + reaction(startup) -> x, y, psi, v {= x.set(self.x_init) y.set(self.y_init) psi.set(self.psi_init) v.set(self.v_init) =} - reaction(turn) {= - self.latest_turn = turn.value - =} + + reaction(turn) {= self.latest_turn = turn.value =} + reaction(t) -> x, y, psi, v {= delta_t = self.period / 1e9 # Period is seconds x.set( @@ -69,6 +66,6 @@ reactor Aircraft( # Update the angle after updating the position. psi.set( psi.value + delta_t * self.latest_turn - ) + ) =} } diff --git a/Python/src/acas/lib/XYPlotter.lf b/Python/src/acas/lib/XYPlotter.lf index 1df93b43..4d44342d 100644 --- a/Python/src/acas/lib/XYPlotter.lf +++ b/Python/src/acas/lib/XYPlotter.lf @@ -1,9 +1,10 @@ /** * Plotter that accepts two x-y input sets and plots them. - * + * * @author Edward A. Lee */ target Python + preamble {= import numpy as np import matplotlib.pyplot as plt @@ -14,11 +15,11 @@ reactor XYPlotter { input y1 input x2 input y2 - - state x1_list({=[]=}) # collected x values - state y1_list({=[]=}) # collected y values - state x2_list({=[]=}) # collected x values - state y2_list({=[]=}) # collected y values + + state x1_list = {= [] =} # collected x values + state y1_list = {= [] =} # collected y values + state x2_list = {= [] =} # collected x values + state y2_list = {= [] =} # collected y values reaction(x1, y1, x2, y2) {= self.x1_list.append(x1.value) @@ -26,17 +27,18 @@ reactor XYPlotter { self.x2_list.append(x2.value) self.y2_list.append(y2.value) =} - reaction(shutdown) {= + + reaction(shutdown) {= # make data - x1 = np.array(self.x1_list) - y1 = np.array(self.y1_list) - x2 = np.array(self.x2_list) - y2 = np.array(self.y2_list) - - # plot - plt.plot(x1, y1, "b") - plt.plot(x2, y2, "r") - - plt.show() + x1 = np.array(self.x1_list) + y1 = np.array(self.y1_list) + x2 = np.array(self.x2_list) + y2 = np.array(self.y2_list) + + # plot + plt.plot(x1, y1, "b") + plt.plot(x2, y2, "r") + + plt.show() =} } diff --git a/Rust/src/CounterProgram.lf b/Rust/src/CounterProgram.lf index 67feb598..00905927 100644 --- a/Rust/src/CounterProgram.lf +++ b/Rust/src/CounterProgram.lf @@ -3,32 +3,35 @@ //! ./counter_program --main-stride 20 --main-period 500msec //! //! Author: Clément Fournier - target Rust { timeout: 3 sec, cargo-features: ["cli"] -}; +} + +reactor Counter(stride: u32 = 1, period: time = 1 sec) { + state count: u32 = 0 + state stride = stride + timer t(0, period) + output out: u32 -reactor Counter(stride: u32(1), period: time(1 sec)) { - state count: u32(0); - state stride(stride); - timer t(0, period); - output out: u32; reaction(t) -> out {= ctx.set(out, self.count); self.count += self.stride; =} } + reactor Printer { - input in: u32; + input in: u32 + reaction(in) {= if let Some(value) = ctx.get(r#in) { println!("Hello World! value={}.", value); } =} } -main reactor CounterProgram(stride: u32(10), period: time(1 sec)) { - counter = new Counter(stride=stride, period=period); - printer = new Printer(); - counter.out -> printer.in; + +main reactor CounterProgram(stride: u32 = 10, period: time = 1 sec) { + counter = new Counter(stride=stride, period=period) + printer = new Printer() + counter.out -> printer.in } diff --git a/Rust/src/Snake/KeyboardEvents.lf b/Rust/src/Snake/KeyboardEvents.lf index 192d506b..3c869adc 100644 --- a/Rust/src/Snake/KeyboardEvents.lf +++ b/Rust/src/Snake/KeyboardEvents.lf @@ -6,13 +6,14 @@ //! If the Ctrl+C combination is detected, the program requests to stop. Upon //! shutdown, the program exits the raw mode. This reactor is designed to support //! other applications that require real-time keyboard interaction, particularly -//! within terminal-based interfaces. +//! within terminal-based interfaces. //! //! Support reactor for the Snake.lf example. - target Rust { - cargo-dependencies: { termion: "1", } -}; + cargo-dependencies: { + termion: "1" + } +} /// Capture asynchronous key presses, and sends them through an output port. /// Used by the other examples. @@ -24,12 +25,11 @@ reactor KeyboardEvents { use termion::raw::{RawTerminal, IntoRawMode}; =} - /// The latest key press. - output arrow_key_pressed: Key; + output arrow_key_pressed: Key /// The latest key press. - physical action key_press: Key; + physical action key_press: Key - state raw_terminal: Option>; + state raw_terminal: Option> reaction(key_press) -> arrow_key_pressed {= ctx.set(arrow_key_pressed, ctx.get(key_press).unwrap()); @@ -40,7 +40,6 @@ reactor KeyboardEvents { =} reaction(startup) -> key_press {= - let stdin = std::io::stdin(); // enter raw mode, to get key presses one by one diff --git a/Rust/src/Snake/Snake.lf b/Rust/src/Snake/Snake.lf index c36b32af..95042599 100644 --- a/Rust/src/Snake/Snake.lf +++ b/Rust/src/Snake/Snake.lf @@ -15,29 +15,25 @@ //! //! Note: Git history of this file may be found in https://github.com/lf-lang/reactor-rust //! under the path examples/src/Snake.lf - target Rust { // LF-Rust programs integrate well with Cargo cargo-dependencies: { termcolor: "1", - termion: "1", // (this doesn't support windows) - rand: "0.8", + termion: "1", // (this doesn't support windows) + rand: "0.8" }, // This will be linked into the root of the crate as a Rust module: `pub mod snakes;` rust-include: "snakes.rs", // This is a conditional compilation flag that enables the CLI. // Without it, command-line arguments are ignored and produce a warning. - cargo-features: ["cli"], -}; + cargo-features: ["cli"] +} -// Import a shared reactor -import KeyboardEvents from "KeyboardEvents.lf"; +import KeyboardEvents from "KeyboardEvents.lf" // Import a shared reactor // main reactor parameters can be set on the CLI, eg: // ./snake --main-grid-side 48 -main reactor Snake(grid_side: usize(32), - tempo_step: time(40 msec), - food_limit: u32(2)) { +main reactor Snake(grid_side: usize = 32, tempo_step: time = 40 msec, food_limit: u32 = 2) { preamble {= use crate::snakes::*; use crate::snakes; @@ -45,33 +41,26 @@ main reactor Snake(grid_side: usize(32), use rand::prelude::*; =} - /// this thing helps capturing key presses - keyboard = new KeyboardEvents(); + keyboard = new KeyboardEvents() /// this thing helps capturing key presses - // model classes for the game. - state snake: CircularSnake ({= CircularSnake::new(grid_side) =}); - state grid: SnakeGrid ({= SnakeGrid::new(grid_side, &snake) =}); // note that this one borrows snake temporarily + state snake: CircularSnake = {= CircularSnake::new(grid_side) =} // model classes for the game. + state grid: SnakeGrid = {= SnakeGrid::new(grid_side, &snake) =} // note that this one borrows snake temporarily /// Triggers a screen refresh, not a timer because we can /// shrink the period over time to speed up the game. - logical action screen_refresh; - /// The game speed level - state tempo: u32(1); - state tempo_step(tempo_step); + logical action screen_refresh + state tempo: u32 = 1 /// The game speed level + state tempo_step = tempo_step /// Changes with arrow key presses, might be invalid. /// Only committed to snake_direction on grid update. - state pending_direction: Direction ({= Direction::RIGHT =}); - /// Whither the snake has slithered last - state snake_direction: Direction ({= Direction::RIGHT =}); - - /// manually triggered - logical action manually_add_more_food; - /// periodic - timer add_more_food(0, 5 sec); - // state vars for food - state food_on_grid: u32(0); - state max_food_on_grid(food_limit); + state pending_direction: Direction = {= Direction::RIGHT =} + state snake_direction: Direction = {= Direction::RIGHT =} /// Whither the snake has slithered last + + logical action manually_add_more_food /// manually triggered + timer add_more_food(0, 5 sec) /// periodic + state food_on_grid: u32 = 0 // state vars for food + state max_food_on_grid = food_limit // @label startup reaction(startup) -> screen_refresh {= @@ -136,8 +125,5 @@ main reactor Snake(grid_side: usize(32), } =} - // @label shutdown - reaction(shutdown) {= - println!("New high score: {}", self.snake.len()); - =} + reaction(shutdown) {= println!("New high score: {}", self.snake.len()); =} // @label shutdown } diff --git a/TypeScript/src/ChatApplication/SimpleChat.lf b/TypeScript/src/ChatApplication/SimpleChat.lf index 37199d82..d1d3a362 100644 --- a/TypeScript/src/ChatApplication/SimpleChat.lf +++ b/TypeScript/src/ChatApplication/SimpleChat.lf @@ -4,19 +4,18 @@ * @author Byeonggil Jun (junbg@hanyang.ac.kr) * @author Hokeun Kim (hokeunkim@berkeley.edu) */ - target TypeScript { - coordination-options: {advance-message-interval: 100 msec} + coordination-options: { + advance-message-interval: 100 msec + } } reactor InputHandler { - output out:string; - physical action response; - - preamble {= - import * as readline from "readline"; - =} - + output out: string + physical action response + + preamble {= import * as readline from "readline"; =} + reaction(startup, response) -> out, response {= const rl = readline.createInterface({ input: process.stdin, @@ -26,39 +25,34 @@ reactor InputHandler { if (response !== undefined) { out = response as string; } - + rl.question("Enter message to send: ", (buf) => { actions.response.schedule(0, buf as string); rl.close(); }); =} - } +} reactor Printer { - input inp:string; + input inp: string - reaction(inp) {= - console.log("Received: " + inp); - =} + reaction(inp) {= console.log("Received: " + inp); =} } reactor ChatHandler { - input receive:string; - output send:string; - u = new InputHandler(); - p = new Printer(); - - reaction(u.out) -> send {= - send = u.out; - =} - reaction(receive) -> p.inp {= - p.inp = receive; - =} + input receive: string + output send: string + u = new InputHandler() + p = new Printer() + + reaction(u.out) -> send {= send = u.out; =} + + reaction(receive) -> p.inp {= p.inp = receive; =} } federated reactor SimpleChat { - a = new ChatHandler(); - b = new ChatHandler(); - b.send -> a.receive; - a.send -> b.receive; + a = new ChatHandler() + b = new ChatHandler() + b.send -> a.receive + a.send -> b.receive } diff --git a/TypeScript/src/DistributedHelloWorld/HelloWorld.lf b/TypeScript/src/DistributedHelloWorld/HelloWorld.lf index 27bb26b1..64d56c05 100644 --- a/TypeScript/src/DistributedHelloWorld/HelloWorld.lf +++ b/TypeScript/src/DistributedHelloWorld/HelloWorld.lf @@ -1,52 +1,51 @@ /** - * Distributed LF program (in TypeScript) where a MessageGenerator creates - * a string message that is sent via the RTI (runtime infrastructure) to a - * receiver that prints the message. - * + * Distributed LF program (in TypeScript) where a MessageGenerator creates a string message that is + * sent via the RTI (runtime infrastructure) to a receiver that prints the message. + * * The code generator generates two TypeScript files: - * - * * src-gen/DistrubtedHelloWorld/HelloWorld/src/DistrubtedHelloWorld_source.ts: - * The program that produces the sequence of messages. - * * src-gen/DistrubtedHelloWorld/HelloWorld/src/DistrubtedHelloWorld_print.ts: - * The program that prints the sequence of messages received from source. * - * The code generator also creates compiled JavaScript files out of TypeScript - * files above, respectively. + * * src-gen/DistrubtedHelloWorld/HelloWorld/src/DistrubtedHelloWorld_source.ts: The program that + * produces the sequence of messages. + * * src-gen/DistrubtedHelloWorld/HelloWorld/src/DistrubtedHelloWorld_print.ts: The program that + * prints the sequence of messages received from source. + * + * The code generator also creates compiled JavaScript files out of TypeScript files above, + * respectively. * - * * src-gen/DistributedHelloWorld/HelloWorld/dist/HelloWorld_source.js - * * src-gen/DistributedHelloWorld/HelloWorld/dist/HelloWorld_print.js + * * src-gen/DistributedHelloWorld/HelloWorld/dist/HelloWorld_source.js + * * src-gen/DistributedHelloWorld/HelloWorld/dist/HelloWorld_print.js * * To run this example, first you need to start the standalone RTI program in * org.lflang/src/lib/core/federated/RTI using `build/RTI -n 2`. * - * After starting the RTI, you need to run both compiled JavaScript files using - * node as shown below (inside src-gen/DistributedHelloWorld/HelloWorld/). + * After starting the RTI, you need to run both compiled JavaScript files using node as shown below + * (inside src-gen/DistributedHelloWorld/HelloWorld/). + * + * * node dist/HelloWorld_source.js + * * node dist/HelloWorld_print.js * - * * node dist/HelloWorld_source.js - * * node dist/HelloWorld_print.js - * * @author Edward A. Lee * @author Hokeun Kim */ target TypeScript { timeout: 10 secs -}; +} /** - * Reactor that generates a sequence of messages, one per second. - * The message will be a string consisting of a root string followed - * by a count. + * Reactor that generates a sequence of messages, one per second. The message will be a string + * consisting of a root string followed by a count. * @param root The root string. * @output message The message. */ -reactor MessageGenerator(root:string("")) { +reactor MessageGenerator(root: string = "") { // Output type char* instead of string is used for dynamically // allocated character arrays (as opposed to static constant strings). - output message:string; - state count:number(1); + output message: string + state count: number = 1 // Send first message after 1 sec so that the startup reactions // do not factor into the transport time measurement on the first message. - timer t(1 sec, 1 sec); + timer t(1 sec, 1 sec) + reaction(t) -> message {= message = root + " " + count++; console.log(`At time (elapsed) logical tag ${util.getElapsedLogicalTime()}, source sends message: ${message}`); @@ -59,14 +58,15 @@ reactor MessageGenerator(root:string("")) { * @input message The message. */ reactor PrintMessage { - input message:string; + input message: string + reaction(message) {= console.log(`PrintMessage: At (elapsed) logical time ${util.getElapsedLogicalTime()}, receiver receives: ${message}`); =} } federated reactor HelloWorld { - source = new MessageGenerator(root = "Hello World"); - print = new PrintMessage(); - source.message -> print.message; + source = new MessageGenerator(root = "Hello World") + print = new PrintMessage() + source.message -> print.message } diff --git a/TypeScript/src/HTTPSRequestReactor/HTTPSRequest.lf b/TypeScript/src/HTTPSRequestReactor/HTTPSRequest.lf index 1743d952..44db1aed 100644 --- a/TypeScript/src/HTTPSRequestReactor/HTTPSRequest.lf +++ b/TypeScript/src/HTTPSRequestReactor/HTTPSRequest.lf @@ -1,25 +1,25 @@ /** - * This Lingua Franca program is designed to retrieve content from a webpage using - * HTTPS GET request. Upon startup, it sends a GET request to the specified URL - * ("https://ptolemy.berkeley.edu/projects/icyphy/"). As it receives data from the - * server, it schedules a physical action to process that data, which is then - * printed to the console. When the server indicates that there is no more data to - * be sent, another physical action is scheduled which ends the program. This - * reactor demonstrates the capability of Lingua Franca to handle and react to - * asynchronous network events in a timely manner. + * This Lingua Franca program is designed to retrieve content from a webpage using HTTPS GET + * request. Upon startup, it sends a GET request to the specified URL + * ("https://ptolemy.berkeley.edu/projects/icyphy/"). As it receives data from the server, it + * schedules a physical action to process that data, which is then printed to the console. When the + * server indicates that there is no more data to be sent, another physical action is scheduled + * which ends the program. This reactor demonstrates the capability of Lingua Franca to handle and + * react to asynchronous network events in a timely manner. */ -target TypeScript{ - keepalive : true - //logging : "debug" -}; +target TypeScript { + keepalive: true // logging : "debug" +} + main reactor { preamble {= import * as https from "https" import * as http from "http" =} - physical action data:{= Buffer =}; - physical action done; - reaction (startup) -> data, done {= + physical action data: {= Buffer =} + physical action done + + reaction(startup) -> data, done {= https.get("https://ptolemy.berkeley.edu/projects/icyphy/", (res : http.IncomingMessage) => { console.log("statusCode:", res.statusCode); console.log("headers:", res.headers); @@ -31,13 +31,15 @@ main reactor { }); }); =} - reaction (data) {= + + reaction(data) {= let serverData = data; if (serverData) { console.log(serverData.toString()); } =} - reaction (done) {= + + reaction(done) {= console.log("No more data in response."); util.requestStop(); =} diff --git a/TypeScript/src/HTTPServerReactor/HTTPServer.lf b/TypeScript/src/HTTPServerReactor/HTTPServer.lf index f2559344..d29476b7 100644 --- a/TypeScript/src/HTTPServerReactor/HTTPServer.lf +++ b/TypeScript/src/HTTPServerReactor/HTTPServer.lf @@ -1,28 +1,28 @@ /** - * This Lingua Franca program sets up a simple HTTP server that listens on port - * 8000 and responds to incoming requests. On startup, the server is created and - * starts listening for incoming connections. When a request is received, it is - * scheduled as a physical action, which triggers a reaction that writes a response - * back to the client. This response includes a count of the number of requests - * processed so far, incremented each time a new request is received. The program - * also ensures proper cleanup on shutdown by closing the server. This reactor - * serves as a basic example of how to integrate network interactions within the - * Lingua Franca framework. + * This Lingua Franca program sets up a simple HTTP server that listens on port 8000 and responds to + * incoming requests. On startup, the server is created and starts listening for incoming + * connections. When a request is received, it is scheduled as a physical action, which triggers a + * reaction that writes a response back to the client. This response includes a count of the number + * of requests processed so far, incremented each time a new request is received. The program also + * ensures proper cleanup on shutdown by closing the server. This reactor serves as a basic example + * of how to integrate network interactions within the Lingua Franca framework. */ target TypeScript { - keepalive : true - //logging : "debug" - //timeout : 10 sec -}; + // logging : "debug" + // timeout : 10 sec + keepalive: true +} + main reactor { preamble {= import * as https from "https" import * as http from "http" =} - state count:number(0); - state server:{=http.Server | undefined=}({=undefined=}); - physical action serverRequest:{= [ http.IncomingMessage, http.ServerResponse ] =}; - reaction (startup) -> serverRequest {= + state count: number = 0 + state server: {= http.Server | undefined =} = {= undefined =} + physical action serverRequest: {= [ http.IncomingMessage, http.ServerResponse ] =} + + reaction(startup) -> serverRequest {= let options = {}; server = http.createServer(options, (req : http.IncomingMessage, res : http.ServerResponse) => { // Generally browsers make two requests, the first is for favicon.ico. @@ -32,7 +32,8 @@ main reactor { } }).listen(8000); =} - reaction (serverRequest) {= + + reaction(serverRequest) {= let requestArray = serverRequest; if (requestArray) { let req = requestArray[0]; @@ -43,9 +44,10 @@ main reactor { res.end("hello world\n"); } =} - reaction (shutdown) {= + + reaction(shutdown) {= if (server) { server.close(); } - =} + =} } diff --git a/TypeScript/src/SimpleWebserver.lf b/TypeScript/src/SimpleWebserver.lf index 07c5fe71..5e781015 100644 --- a/TypeScript/src/SimpleWebserver.lf +++ b/TypeScript/src/SimpleWebserver.lf @@ -1,23 +1,21 @@ /** - * This Lingua Franca program creates a simple web server that responds to HTTP - * requests. Upon startup, the server is initiated and begins to listen on port - * 8000. When an HTTP request is received that is not for the favicon.ico file, the - * server schedules a reaction that processes the request and sends a response with - * the message "Hello world!". This program efficiently handles incoming requests - * and performs the necessary actions in response to each request. The server stops - * and closes upon the shutdown of the program. + * This Lingua Franca program creates a simple web server that responds to HTTP requests. Upon + * startup, the server is initiated and begins to listen on port 8000. When an HTTP request is + * received that is not for the favicon.ico file, the server schedules a reaction that processes the + * request and sends a response with the message "Hello world!". This program efficiently handles + * incoming requests and performs the necessary actions in response to each request. The server + * stops and closes upon the shutdown of the program. */ target TypeScript { - keepalive : true -}; + keepalive: true +} main reactor { - preamble {= - import * as http from "http" - =} - state server:{=http.Server | undefined=}({=undefined=}); - physical action serverRequest:{= [http.IncomingMessage, http.ServerResponse] =}; - reaction (startup) -> serverRequest {= + preamble {= import * as http from "http" =} + state server: {= http.Server | undefined =} = {= undefined =} + physical action serverRequest: {= [http.IncomingMessage, http.ServerResponse] =} + + reaction(startup) -> serverRequest {= let options = {}; server = http.createServer(options, (req : http.IncomingMessage, res : http.ServerResponse) => { // Generally, browsers make two requests; the first is for favicon.ico. @@ -28,7 +26,8 @@ main reactor { }).listen(8000); console.log("Started web server at http://localhost:8000/") =} - reaction (serverRequest) {= + + reaction(serverRequest) {= let requestArray = serverRequest; if (requestArray) { let req = requestArray[0]; @@ -37,9 +36,10 @@ main reactor { res.end("Hello world!\n"); } =} - reaction (shutdown) {= + + reaction(shutdown) {= if (server) { server.close(); } - =} + =} }