|
@@ -4,7 +4,7 @@
|
|
|
|
|
|
This is platform-neural tutorial, although hardware implementation refers to
|
|
|
arduino. The full code can be found at `examples/atmega328p/blink-led.cpp`. In
|
|
|
-this example the blinking with LED using `rotor-light`.
|
|
|
+this example the blinking with LED is performed using `rotor-light`.
|
|
|
|
|
|
Let's start from the inclusion of headers:
|
|
|
|
|
@@ -20,7 +20,7 @@ namespace rl = rotor_light;
|
|
|
|
|
|
In this example, there is only single message - `BlinkCommand`. It is good
|
|
|
practice to define messages in it's own namespace, as it is much more
|
|
|
-convenient to deail with messages later (handle and send).
|
|
|
+convenient to deal with messages later (handle and send).
|
|
|
|
|
|
```
|
|
|
namespace message {
|
|
@@ -37,10 +37,10 @@ struct BlinkCommand : rl::Message { // (1)
|
|
|
|
|
|
The `BlinkCommand` message have to be derived from `rotor_ligth` `Message` (1),
|
|
|
and as the `BlinkCommand` has no payload, it just reuses its parent
|
|
|
-consturctor (`using rl::Message::Message`, (3)).
|
|
|
+constructor (`using rl::Message::Message`, (3)).
|
|
|
|
|
|
-As the `rotor_ligth` does not uses RTTI it should somehow identify
|
|
|
-message types at runtime. That's why overriden `get_type_id()` method (4)
|
|
|
+As the `rotor_ligth` does not use RTTI it should somehow identify
|
|
|
+message types at runtime. That's why overridden `get_type_id()` method (4)
|
|
|
should be available on every message type. `rotor_light` does not care
|
|
|
how you, as the user, define your message type, the only requirement is the
|
|
|
uniqueness of them. The easiest way to achieve that is just have line (2)
|
|
@@ -66,10 +66,10 @@ void initialize() override { // (7)
|
|
|
}
|
|
|
|
|
|
void blink() {
|
|
|
- toggle_led(); // (13)
|
|
|
- add_event(delay, [](void *data) { // (14)
|
|
|
- auto self = static_cast<Blinker *>(data); // (15)
|
|
|
- self->send<message::BlinkCommand>(0, id); // (16)
|
|
|
+ toggle_led(); // (13)
|
|
|
+ add_event(delay, [](void *data) { // (14)
|
|
|
+ auto self = static_cast<Blinker *>(data); // (15)
|
|
|
+ self->send<message::BlinkCommand>(0, self->id); // (16)
|
|
|
}, this);
|
|
|
}
|
|
|
|
|
@@ -82,20 +82,20 @@ rl::Duration delay; // (18)
|
|
|
```
|
|
|
|
|
|
First, your own actor have to inherit from `rl::Actor` (5). The magic number `2` is
|
|
|
-used for preallocation space for actor's message handlers: the one handler from the
|
|
|
-base class defined by the framework (might be changed in the future), and the other
|
|
|
-one use the user-provided `on_blink_command`. The alias (6) makes it more convenient
|
|
|
-to refer the base class in the future.
|
|
|
+used for preallocation of space for actor's message handlers: one handler from
|
|
|
+the base class defined by the framework (might be changed in the future), and the
|
|
|
+other one use the user-provided `on_blink_command`. The alias (6) makes it more
|
|
|
+convenient to refer the base class in the future.
|
|
|
|
|
|
To react on the `BlinkCommand` message, the actor should subscribe to it (8). The
|
|
|
-proper place to do that is to override `initialize()` method (7), of the parent class.
|
|
|
+proper place to do that is to override `initialize()` method (7) of the parent class.
|
|
|
Of course, parent class **must** be initialized too, that's why don't forget to
|
|
|
invoke its initialization (9).
|
|
|
|
|
|
-When actor is ready, its `advance_start()` (10) is invoked; as usually correspondig
|
|
|
-parents method should be called (11) and the initial blink (12) is performed. In
|
|
|
+When actor is ready, its `advance_start()` (10) is invoked; as usually corresponding
|
|
|
+parent method should be called (11) and the initial blink (12) is performed. In
|
|
|
the implementation of `blink()` the platform-dependent LED toggle is done at (13),
|
|
|
-and then delayed the same method invokation is scheduled (14-16). In (14) the
|
|
|
+and then delayed the same method invocation is scheduled (14-16). In (14) the
|
|
|
capture-less lambda is scheduled to be invoked with `this` as the parameter when
|
|
|
`delay` (18) amount of time passes. The lambda *must* be capture-less. After casting
|
|
|
back `void*` to the actor class (15), it sends self a `BlinkCommand` message (16)
|
|
@@ -122,7 +122,7 @@ using Supervisor = rl::Supervisor< // (21)
|
|
|
`rotor-light` uses queues to store and process messages. There are no dynamic
|
|
|
allocations, so, all sizes have to be known at compile-time, including the sizes
|
|
|
of all used messages. The `Storage` helper (18) is used to determine the maximum
|
|
|
-allowed storage size per message: here **all messages** in the application
|
|
|
+allowed space size per message: here **all messages** in the application
|
|
|
should be enumerated, i.e. messages from the framework and user-defined.
|
|
|
|
|
|
Next, the application queue (or master queue) should be defined (19). It uses
|
|
@@ -131,7 +131,7 @@ Next, the application queue (or master queue) should be defined (19). It uses
|
|
|
queue, but user-defined messages might use different queues. Queues with
|
|
|
higher priorities are processed earlier than queues with lower priorities. So,
|
|
|
to define master queue with two subqueues (priorities 0 and 1) with sizes
|
|
|
-enought to store 15 and 10 messages respectively, the following code should
|
|
|
+enough to store 15 and 10 messages respectively, the following code should
|
|
|
be used
|
|
|
|
|
|
```
|
|
@@ -139,16 +139,16 @@ using Queue = rl::Queue<Storage, 15, 10>;
|
|
|
```
|
|
|
|
|
|
What is the recommended queue sizes? Well, it is completely application defined, but
|
|
|
-for `rotor-light` messages there should be enought space to store `N * 2` messages,
|
|
|
+for `rotor-light` messages there should be enough space to store `N * 2` messages,
|
|
|
where `N` is the total number of actors (including supervisors).
|
|
|
|
|
|
-What happens, if a queue is full? The next send mesage is simply discarded, you'll
|
|
|
+What happens, if a queue is full? The next send message is simply discarded, you'll
|
|
|
be notified about that as the `send()` method returns `false`. If the discarded
|
|
|
-message is `rotor-light` message, then the framework behavior is undefined. So,
|
|
|
+message is `rotor-light` message, then *the framework behavior is undefined*. So,
|
|
|
either do not overload default queue (i.e. with zero-priority), or use different
|
|
|
queues for user-messages. NB: there is a spike of `rotor-light` messages only
|
|
|
during (re)starting supervisor; when everything is *operational* there is almost no
|
|
|
-framework mesages.
|
|
|
+framework messages.
|
|
|
|
|
|
The Planner (20) is used to schedule future events, like it is shown at `add_event`
|
|
|
(14). The number in angle braces defines planner capacity at compile-time. When event
|
|
@@ -156,10 +156,10 @@ time arrives, it is removed from the planner. Please note, that `rotor-light` do
|
|
|
not schedules any events into the planner, so its capacity and usage is completely
|
|
|
controlled by the user code.
|
|
|
|
|
|
-How the framework interracts with the planner? If the root supervisor is used in
|
|
|
+How the framework interacts with the planner? If the root supervisor is used in
|
|
|
*poll mode*, then, when there are no more messages left, the current time is
|
|
|
requested, and all timed out event handlers are executed. Then the special message
|
|
|
-`refresh-time` is send to the supervisor and the procedure repeates enlessly.
|
|
|
+`refresh-time` is send to the supervisor and the procedure repeats endlessly.
|
|
|
Indirectly, this is similar to the code like:
|
|
|
|
|
|
```
|
|
@@ -184,12 +184,12 @@ above and the event awaiting (22) code. This makes it possible to enter into
|
|
|
power-save mode and wake up on external timer or interrupt. See `*-await.cpp`
|
|
|
examples.
|
|
|
|
|
|
-In the line (22) the used supervisor is defined: the amount of handlers is specified
|
|
|
+In the line (21) the used supervisor is defined: the amount of handlers is specified
|
|
|
(the same as for actor at (5), as each supervisor is an actor) and all its
|
|
|
child actors (including other supervisors) are enumerated. The only `Blinker`
|
|
|
child actor is specified in the current example.
|
|
|
|
|
|
-Let's move forward. In the folowing code `rotor-light` global variables are
|
|
|
+Let's move forward. In the following code `rotor-light` global variables are
|
|
|
allocated:
|
|
|
|
|
|
```
|
|
@@ -247,7 +247,7 @@ runtime overhead (as it uses `std::get` under the hood) and additional actors
|
|
|
setup can be performed here (26), i.e. delay 1/4 of seconds between LED toggle.
|
|
|
|
|
|
Please note, that setup phase (23-26) need to be performed **only once** despite
|
|
|
-of possible mutiple actors restarts, i.e. actor identities are preserved during
|
|
|
+of possible multiple actors restarts, i.e. actor identities are preserved during
|
|
|
object lifetimes.
|
|
|
|
|
|
Then, the whole machinery receive initial impulse (26). The `true` value here means
|
|
@@ -261,7 +261,7 @@ failure, there is nothing left to do than exit and the whole board restart.
|
|
|
|
|
|
## ping-pong messaging, poll timer
|
|
|
|
|
|
-In this example how to doi messaging with `rotor-light` is demonstrated: `ping`
|
|
|
+In this example how to do messaging with `rotor-light` is demonstrated: `ping`
|
|
|
message is sent from `pinger` actor to `ponger` actor. The `ponger` actor will
|
|
|
reply back with `pong` message, then after some delay `pinger` actor repeats
|
|
|
the same procedure. The full code can be found at
|
|
@@ -287,7 +287,8 @@ struct Pong : rl::Message {
|
|
|
```
|
|
|
|
|
|
The `ping` and `pong` messages are content-less, why there is need of them for all?
|
|
|
-Because there is need to demonstrate how to send and receive messages.
|
|
|
+Because there is need to demonstrate how to send and receive messages and
|
|
|
+distinguish them by type.
|
|
|
|
|
|
The `Pinger` actor code is:
|
|
|
|
|
@@ -322,9 +323,9 @@ struct Pinger : rl::Actor<2> {
|
|
|
|
|
|
```
|
|
|
|
|
|
-As usually, the `initialize()` should be overriden to subscribe on `pong` messages
|
|
|
+As usually, the `initialize()` should be overridden to subscribe on `pong` messages
|
|
|
(28). The `pinger` actor plays an *active role*, i.e. it sends initial `ping`
|
|
|
-message. This is performed in the overriden `advance_start()` method (29), which is
|
|
|
+message. This is performed in the overrisden `advance_start()` method (29), which is
|
|
|
invoked as soon as the actor is ready: the default implementation machinery is
|
|
|
invoked (30), and for convenience `ping()` (31) method is called. The `ping()` (32)
|
|
|
method implementation is simple: after LED toggle, it sends the `ping` message (33).
|
|
@@ -374,7 +375,7 @@ using Storage = rl::traits::MessageStorage<rl::message::ChangeState,
|
|
|
message::Pong>;
|
|
|
```
|
|
|
|
|
|
-The standard supervisor; it owns `pinger` and `ponger` actors. The `Storage`
|
|
|
+The standard supervisor is used; it owns `pinger` and `ponger` actors. The `Storage`
|
|
|
allocates enough space for `Ping` and `Pong` messages.
|
|
|
|
|
|
|
|
@@ -398,17 +399,17 @@ int main(int, char **) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-The most iteresting part is the setup-phase (39-40). `pinger` and `ponger` actors
|
|
|
+The most interesting part is the setup-phase (39-40). `pinger` and `ponger` actors
|
|
|
should know each others addresses, and the addresses are available only after
|
|
|
context binding.
|
|
|
|
|
|
-When everything is ready, it enters into main loop (43). In it it either delivers
|
|
|
-`rotor-light` messages or waits, until the next event time occurs. It uses
|
|
|
+When everything is ready, it enters into main loop (43). In the loop it either
|
|
|
+delivers `rotor-light` messages or waits, until the next event time occurs. It uses
|
|
|
**busy waiting** by actively polling (querying) timer, whether the next event time
|
|
|
happend. As usually for busy waiting, it consumes 100% CPU time, which is common
|
|
|
strategy for embedded/real-time applications.
|
|
|
|
|
|
-It is possible however do something else, instead of endless timer polling, i.e.
|
|
|
+It is possible however to do something else, instead of endless timer polling, i.e.
|
|
|
do CPU sleep (and consume less current and do less CO2 emission). See the
|
|
|
next section.
|
|
|
|
|
@@ -419,12 +420,12 @@ From user point of view the example does the same as the previous one:
|
|
|
it sends `ping` message, receives `pong` message, blinks with LED and after some
|
|
|
delay the procedure repeats.
|
|
|
|
|
|
-However, the noteable difference is in the delay: in the previous example it
|
|
|
-endlessly polls the timer whether the time arrives for the next event burning
|
|
|
+However, the notable difference is in the delay: in the previous example it
|
|
|
+endlessly polls the timer whether the time arrives for the next event, burning
|
|
|
CPU cycles, in the current example it does energy-efficient sleeping while
|
|
|
there is noting to do (i.e. no messages) between events.
|
|
|
|
|
|
-As the main logic is the same, the artor-related code is also the same; only
|
|
|
+As the main logic is the same, the actor-related code is also the same; only
|
|
|
`main()` function differs.
|
|
|
|
|
|
```
|
|
@@ -459,7 +460,7 @@ int main(int, char **) {
|
|
|
|
|
|
```
|
|
|
|
|
|
-Firsly, the timer poll via recursively sending self `refresh-timer` message should
|
|
|
+Firstly, the timer poll via recursively sending self `refresh-timer` message should
|
|
|
be disabled (44). Then the infinite loop (45) should be started, as the supervisor
|
|
|
exits, when it has no more messages to process (46). The first nearby event should
|
|
|
be extracted from planner (47), if any (48), and then perform platform-specific
|
|
@@ -469,15 +470,15 @@ loop iteration in (46).
|
|
|
## integration
|
|
|
|
|
|
Lets suppose, that there is a need to read a custom port on idle or perform some
|
|
|
-I/O activity, maybe indirecly via calling some other library on each iteration.
|
|
|
+I/O activity, maybe indirectly via calling some other library on each iteration.
|
|
|
How can that be done?
|
|
|
|
|
|
One of the ways to accomplish that is shown above: disable timer poll and do
|
|
|
-the needed activity istead of entering into powersave mode, or in addition to
|
|
|
+the needed activity instead of entering into powersave mode, or in addition to
|
|
|
the powersave mode.
|
|
|
|
|
|
Then second way is to have a custom supervisor with `on_refhesh_timer` method
|
|
|
-overriden, like that:
|
|
|
+overridden, like that:
|
|
|
|
|
|
```
|
|
|
struct MySupervisor : rl::Supervisor<3, MyActor1, MyActor2, ...> {
|