Hello World 3
Introduction
As you have seen in these tutorials, Component Types have many different features which can be used when building flight software. Using the right features, and combining them in the right way, is key to producing powerful, flexible Component Types.
In this tutorial you will develop a new Component Type which will regularly report some information to the ground. It will do this autonomously, without an operator requesting it via Actions or Parameters.
You will implement this Component Type using some features you’ve already seen and some new ones which will be introduced along the way:
-
The desired information will be delivered to the ground by Publishing Events.
-
To Publish these Events regularly and autonomously you’ll use a Task.
-
You’ll then use a Mutable Parameter, introduced in the previous tutorial, to control what information is delivered, and when.
|
In this tutorial, you will:
|
Before You Begin
Guidance on the styles and conventions used in these tutorials is available here.
Unless otherwise directed, all commands in this tutorial must be run in the the_basics/HelloWorld3 workspace.
You must have Flightkit installed as described in the Getting Started Guide.
Navigate to the the_basics/HelloWorld3 workspace in a terminal and run hfk workspace prepare to ensure it is ready to use.
Tasks
Flightkit enables the flight software you build to be naturally portable across different hardware platforms and operating systems.
This is achieved in part by using a consistent model of an execution context called a Task.
As you might expect, Tasks are part of the definition of a Component Type.
There are 3 kinds of Tasks, each with their own defined behaviour:
-
Periodic Tasks are triggered each time a fixed time interval has elapsed.
-
Sporadic Tasks are triggered when data is added to their associated queue.
-
Interrupt Tasks are triggered when a hardware interrupt occurs.
In this tutorial you will use a Periodic Task.
We use Periodic Tasks whenever periodic behaviour is required in our Component Types, examples include:
-
The
cdh.auto.ScheduleComponent Type, which makes use of a Periodic Task to autonomously execute activity defined in a schedule uplinked by operators. -
The
cdh.logging.DataLoggerComponent Type, which carries out periodic data logging, and uses a pair of Periodic Tasks to do so in a way that is robust to IO delays. -
The
cdh.tm.BeaconComponent Type, which uses a Periodic Task to regularly deliver housekeeping data in telemetry packets.
|
In this section of the tutorial you’ll add a Periodic Task named
|
Adding the publishEvent Task
To add a Periodic Task to a Component Type, you must add a <PeriodicTask>
element to its componentType.xml.
EventPublisher currently has no Tasks, so in this case you will also need a
<Tasks> element to contain the <PeriodicTask> element.
Open the EventPublisher componentType.xml. Add the following below
the <!-- Tasks --> comment:
<Tasks>
<PeriodicTask name="publishEvent" defaultPeriod="3.0">
<Description>
A task used to periodically publish events
</Description>
</PeriodicTask>
</Tasks>
This adds a new Periodic Task named publishEvent to the Component Type,
with a default period of 3 seconds. This period can be overridden when the
EventPublisher Component Type is instantiated.
Having updated the componentType.xml, you now need to generate the
Component Type.
Generate the Component Type using the following command:
hfk component-type generate EventPublisher
This creates a Task function in the Component Type's template file called
EventPublisher_taskPublishEvent().
Copy this function, along with its banner comment, and paste it into
EventPublisher.c below EventPublisher_localFini() to maintain the function
order.
Replace the TODO in the Task function with a call to
EVENTPUBLISHER_CONTAINER_LOG_INFO() so that a message will be printed to the
terminal when the Task executes.
Show the updated task code
/*---------------------------------------------------------------------------*
* A task used to periodically publish events
*---------------------------------------------------------------------------*/
void EventPublisher_taskPublishEvent
(
EventPublisher_t *pt_EventPublisher
)
{
- /* TODO: Insert task code here */
+ EVENTPUBLISHER_CONTAINER_LOG_INFO(
+ pt_EventPublisher, "EventPublisher.publishEvent executed!");
}
Updating the EventPublisherDemo Deployment Type
In the previous section you added a new Task to the Component Type. A Component Type is a blueprint for Component Instances, so every Component Instance must also now include the new Task.
There is already an instance of the EventPublisher Component Type in the
EventPublisherDemo Deployment Type. You will now update the
Deployment Type so that you can see the new Task execute.
Open library/deployment_types/HelloWorld3/deploymentType.xml and update the
MyEventPublisher Component Instance to include the new publishEvent
Task.
Show the updated MyEventPublisher Component Instance
<Component name="MyEventPublisher" type="EventPublisher">
<Description>
An instance of the EventPublisher which will periodically publish events
</Description>
+ <Tasks>
+ <PeriodicTask name="publishEvent" priority="0"/>
+ </Tasks>
</Component>
When adding a Task to a Component Instance you must also specify its
priority. This sets the priority that the OSAL (Operating System Abstraction
Layer) will apply to this Task when running multiple Tasks concurrently.
The higher the number, the higher the priority.
On Linux systems we leave scheduling decisions up to the Linux scheduler. That
means this value is ignored in this case, so we have set it to "0".
We could have also specified a period which would take precedence over the
defaultPeriod specified in the componentType.xml. Omitting this attribute
results in the defaultPeriod being used.
To test the new Task, build the Mission and run the Deployment Instance using the same approach as previous tutorials:
hfk mission build HelloWorld3
./output/missions/HelloWorld3/EventPublisherDemo/Default/Default
You should see the following message being printed to the terminal every three seconds:
INF: EventPublisher.c:80 MyEventPublisher: EventPublisher.publishEvent executed!
Once you are happy the Task is working as it should, terminate the Deployment Instance in the terminal.
Events
In Flightkit Events are used for asynchronous signalling by Component Types. Component Types may Publish Events (produce them) and Subscribe to Events (receive them). There are usually more Component Types Publishing Events than Subscribing to them.
Events are used for many different purposes, examples include:
-
Publishing an Event when a mode manager enters safe mode.
-
Publishing an Event when execution of an onboard schedule completes, or fails.
-
Publishing an Event when some Parameter value exceeds pre-defined limits.
|
In this section of the tutorial you will define a new Event, and learn how to Publish it from the Task you added in the previous section. |
Adding the SomethingHappened Event
To add an Event to a Component Type, you must add an <Event> element to
its componentType.xml.
EventPublisher currently has no Events, so in this case you will also need
an <Events> element to contain the <Event> element.
Open the EventPublisher componentType.xml. Add the following below
the <!-- Events --> comment:
<Events defaultBaseId="10000">
<Event name="SomethingHappened" severity="info">
<Description>
An event to notify the operator that something happened!
</Description>
</Event>
</Events>
Each Event has a numeric identifier associated with it, and the defaultBaseId
is the identifier used by the first Event defined in this Component Type. Each
subsequent Event is numbered sequentially from defaultBaseId.
defaultBaseId should be set such that each of the Events defined by the
Component Type will be given a unique identifier when used in a
Deployment Type.
A value of 10000 here is sufficiently unique for our purposes.
When defining an Event you must also specify its severity. The lowest
severity is info and is appropriate for this tutorial.
Generate the Component Type using the same command as before.
This creates a new header file in
library/component_types/EventPublisher/include/type/ named
EventPublisher_events.h, containing the symbol which defines the new
Event's identifier.
You will now see how the new Event can be Published.
Using EDS
In Flightkit, Services are a way of formally describing the connections between Component Types. You will learn more about Services in the later Using A Service tutorial.
For now it is sufficient to know that, in order to Publish or Subscribe to an Event, Component Types must use the Event Distribution Service (EDS). EDS is the Service which defines the Event Message. It is an example of a Publish-Subscribe Service.
To Publish Event Messages from the EventPublisher Component Type you
must add a <Services><Published><Service> element to its componentType.xml.
Open the EventPublisher componentType.xml. Add the following below
the <!-- Services (Provided, Required, Subscribed, Brokered and Published) -->
comment:
<Services>
<Published>
<Service name="task" type="component.EDS">
<Description>
A publication for publishing Event messages from the publishEvent
periodic task
</Description>
</Service>
</Published>
</Services>
Generate the EventPublisher Component Type.
This generates a new function that you can call from the EventPublisher
Component Implementation to Publish Events. Its prototype can be found in
EventPublisher_EDSPublisher_package.h:
/**
* Publish the task EDS event message
* A message publisher for publishing EDS.Event messages from the publishEvent periodic task
* @param pt_EventPublisher The EventPublisher component
* @param t_EventId The Event ID
* @param u32_Status A status (exception code) associated with an event.
* @param pu8_Info An optional blob of information.
* @param u8_InfoLength The length of the data in the buffer, in bytes
*/
void EventPublisher_EDSPublisher_publishEventTask
(
EventPublisher_t *pt_EventPublisher,
EDS_EventId_t t_EventId,
uint32_t u32_Status,
const uint8_t *pu8_Info,
uint8_t u8_InfoLength
);
You will now call this function from the publishEvent Task to Publish an
Event periodically.
Publishing the SomethingHappened Event
Include the EventPublisher_EDSPublisher_package.h header in your
EventPublisher.c source file.
Show the added #include directive
#include "types.h"
#include "status.h"
#include "EventPublisher.h"
#include "container/EventPublisher_Container_package.h"
+#include "container/EventPublisher_EDSPublisher_package.h"
#include "EventPublisher_config.h"
#include "Task_ProtectionLock.h"
#include "Util_Log.h"
To Publish the SomethingHappened Event from the publishEvent Task,
update the Task function to call
EventPublisher_EDSPublisher_publishEventTask(), passing in
EVENTPUBLISHER_EVENTID_SOMETHING_HAPPENED as the Event ID argument.
Show the updated task code
void EventPublisher_taskPublishEvent
(
EventPublisher_t *pt_EventPublisher
)
{
EVENTPUBLISHER_CONTAINER_LOG_INFO(
pt_EventPublisher, "EventPublisher.publishEvent executed!");
+
+ EventPublisher_EDSPublisher_publishEventTask(
+ pt_EventPublisher,
+ EVENTPUBLISHER_EVENTID_SOMETHING_HAPPENED,
+ STATUS_SUCCESS,
+ NULL,
+ 0);
}
Notice that, as well as an Event ID, the
EventPublisher_EDSPublisher_publishEventTask() function allows you to provide
u32_Status and pu8_Info arguments. These allow you to add more detailed
information to the Published Event.
u32_Status is typically used to indicate what kind of failure caused the
Event to be Published. For Events not caused by failures,
STATUS_SUCCESS is appropriate for this argument.
pu8_Info allows you to include up to 8 arbitrary bytes of data in the
Event. If you do not wish to pass any additional information pass NULL and
a length of 0.
Having added an EDS Publication to EventPublisher you now need to update
the EventPublisherDemo Deployment Type again. You’ll do that in the next
section.
Updating the EventPublisherDemo Deployment Type
As described above, Publish-Subscribe Services like EDS define Messages which can be Published and Subscribed to by Component Types.
Component Types which Publish Messages are referred to as Publishers. Those which Subscribe to Messages are referred to as Subscribers.
Component Types may also Broker Messages. Brokers are
responsible for receiving Messages from Publishers and dispatching them to
Subscribers. As a result, Publishers, like the EventPublisher
Component Type, must be connected to Brokers when used in
Deployment Types.
Open library/deployment_types/EventPublisherDemo/deploymentType.xml
Notice that an instance of the InContextEDSBroker Component Type is
included in EventPublisherDemo Deployment Type. This is an EDS Broker
which we provide in the FlightkitFoundation bundle.
To test the EventPublisher Component Type you need to connect
MyEventPublisher Component Instance to the InContextEDSBroker
Component Instance.
Add the following <Connections><Services> element to MyEventPublisher.
Show the updated MyEventPublisher Component Instance
<Component name="MyEventPublisher" type="EventPublisher">
<Description>
An instance of the EventPublisher which will periodically publish events
</Description>
+ <Connections><Services>
+ <Service name="task" component="core.InContextEDSBroker" service="eventBroker"/>
+ </Services></Connections>
<Tasks>
<PeriodicTask name="publishEvent" priority="0"/>
</Tasks>
</Component>
When making a Publish-Subscribe Service connection like this you must always specify:
-
The name of the Service Publication on the connecting Component Instance (the Publisher or Subscriber) using the
nameattribute. In this casenameistask, as that is the name of the EDS Publication in theEventPublishercomponentType.xml. -
The name of the Component Instance serving as the Broker using the
componentattribute. In this casecomponentiscore.InContextEDSBroker, as that is the name of the desired Broker. -
The name of the Service Brokerage on the Broker being connected to using the
serviceattribute. In this caseserviceiseventBroker, as that is the name of theInContextEDSBrokerComponent Type's EDS Brokerage.
To test that MyEventPublisher now correctly Publishes Events via the
InContextEDSBroker you need to build the HelloWorld3 Mission and run the
Default Deployment Instance as before.
You will see the same message as before being printed to your terminal every 3 seconds. To see the Events which the Component Instance is now raising, you will need to use Lab.
Open Lab, load the MDB and connect to the Deployment Instance.
Open the 'Forwarded Events' view by clicking on the 'Communication' tab and
ensuring 'Forwarded Events' is selected. You should see the Events arriving
in the Forwarded Events table.
Once you are happy to proceed, disconnect from the Deployment Instance and terminate the process in the terminal.
|
You have successfully added a Task to the There is currently no logic controlling when it Publishes an Event. In the next section you’ll add a Parameter to control this behaviour. |
Controlling the publishEvent Task
In the previous tutorial you learned that Parameters are used to give insight into, or control over, a Component Instance's behaviour, and worked with a Read-Only Parameter.
It is also possible to define Mutable Parameters, whose values may be set externally to the Component Type. When implementing a Mutable Parameter you must provide a Parameter setter function as well as a Parameter getter.
|
In this section of the tutorial you will add a Mutable Parameter to the
|
Adding the eventPeriod Parameter
Currently EventPublisher Publishes an Event every time its
publishEvent Task runs.
You will now add a new eventPeriod Parameter so that the
Component Implementation Publishes an Event 1 in every eventPeriod
times the Task runs.
To add the new Parameter, add the following <Parameters> element to
the Component Type's componentType.xml below the <!-- Parameters -->
comment:
<Parameters>
<Parameter name="eventPeriod">
<Description>
Controls how often the SomethingHappened event is published by the
publishEvent task
</Description>
<Documentation><Markdown>
A value of zero indicates the `SomethingHappened` event will never be
published.
A non-zero value indicates the `SomethingHappened` event will be published
1 in every `eventPeriod` times the `publishEvent` task runs.
- A value of '1' publishes the event *every* time the task runs
- A value of '2' publishes the event *every second* time the task runs
- A value of '3' publishes the event *every third* time the task runs
- etc.
</Markdown></Documentation>
<Type>
<Integer signed="false" bits="8"/>
</Type>
</Parameter>
</Parameters>
Notice that this Parameter does not specify a value for the readOnly
attribute. It is not required for Mutable Parameters as its default value is
"false".
Generate the Component Type.
In the template file you will now see EventPublisher_getEventPeriod() and
EventPublisher_setEventPeriod() - the Parameter getter and setter
functions.
Copy and paste these functions into EventPublisher.c.
Uncomment the gt_LockTimeout global variable at the top of
EventPublisher.c. You will need it to implement the new Parameter getter
and setter functions.
Show the change in EventPublisher.c
/** The lock access timeout */
- // static const ShortTime_t gt_LockTimeout = EVENTPUBLISHER_CONFIG_LOCK_TIMEOUT;
+ static const ShortTime_t gt_LockTimeout = EVENTPUBLISHER_CONFIG_LOCK_TIMEOUT;
You will now use the same pattern as in the previous tutorial to store this Parameter's value.
Add a member to the EventPublisher_t Component Type Structure (defined in
library/component_types/EventPublisher/include/EventPublisher.h) to hold the
value of the eventPeriod Parameter.
Use a type and name consistent with the Parameter's definition in
componentType.xml.
Show the new member added to EventPublisher_t
/** The EventPublisher component */
typedef struct
{
/** A protection lock to protect the EventPublisher state */
Task_ProtectionLock_t t_Lock;
- /* TODO: Add component state variables here */
+ /** Data storage for the eventPeriod parameter */
+ uint8_t u8_EventPeriod;
}
EventPublisher_t;
Set u8_EventPeriod to 0 in EventPublisher_localInit(). This will ensure
Component Instances do not Publish the SomethingHappened Event by
default.
Show the initialisation added to EventPublisher_localInit()
status_t EventPublisher_localInit
(
EventPublisher_t *pt_EventPublisher,
const EventPublisher_Init_t *pt_InitData
)
{
+ /* By default, disable raising the SomethingHappened event */
+ pt_EventPublisher->u8_EventPeriod = 0;
+
/* Initialise the protection lock */
return Task_ProtectionLock_init(
&pt_EventPublisher->t_Lock,
EVENTPUBLISHER_CONTAINER_TASKING_TYPE(pt_EventPublisher));
}
You now need to update the getter and setter functions you copied into
EventPublisher.c earlier so that they provide read and write access,
respectively, to u8_EventPeriod.
As in the previous tutorial, these functions must only access the
u8_EventPeriod member from within a locked section.
Update EventPublisher_getEventPeriod() so that it accesses u8_EventPeriod
within a locked section.
Show the changes to the EventPublisher_getEventPeriod() implementation
/*---------------------------------------------------------------------------*
* Get the eventPeriod parameter
*---------------------------------------------------------------------------*/
status_t EventPublisher_getEventPeriod
(
EventPublisher_t *pt_EventPublisher,
uint8_t *pu8_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status)
{
- /* TODO: Implement eventPeriod get accessor */
- t_Status = STATUS_NOT_IMPLEMENTED;
+ *pu8_Value = pt_EventPublisher->u8_EventPeriod;
}
TASK_PROTECTED_END(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
Update EventPublisher_setEventPeriod() so that it sets the value of
u8_EventPeriod within a locked section.
Show the changes to the EventPublisher_setEventPeriod() implementation
/*---------------------------------------------------------------------------*
* Set the eventPeriod parameter
*---------------------------------------------------------------------------*/
status_t EventPublisher_setEventPeriod
(
EventPublisher_t *pt_EventPublisher,
uint8_t u8_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status)
{
- /* TODO: Implement eventPeriod set accessor */
- t_Status = STATUS_NOT_IMPLEMENTED;
+ pt_EventPublisher->u8_EventPeriod = u8_Value;
}
TASK_PROTECTED_END(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
Although this Parameter doesn’t control the Event yet, you can still test the Parameter accessor functions at this stage.
To do so, carry out the following steps, which you should now be familiar with:
-
Build the Mission
-
Run the Deployment Instance
-
Open Lab
-
Load the MDB
-
Connect to the Deployment Instance
-
Add a command card for the
eventPeriodParameter to theMy Commandssection of theCommandingview. -
Expand the
eventPeriodcommand card.
Notice that the expanded command card contains both a Get ⬇ button and
a Set ⬆ button. You can use these to get or set the value stored in
u8_EventPeriod, respectively.
Give this a try and make sure that the Parameter value changes as expected.
When you are happy that it is working as expected, disconnect from, and terminate the Deployment Instance.
Now that you can set the value of u8_EventPeriod, you need to update the
publishEvent Task to make use of it. You’ll do this in the next section.
Updating the publishEvent Task
As described in the eventPeriod <Documentation> element you added to the
componentType.xml, your goal here is to Publish an Event every
eventPeriod times the publishEvent Task runs.
To implement this functionality you will need a counter to keep track of how
many times the publishEvent Task has run.
This counter will form part of an EventPublisher Component Instance's
state, so as with u8_EventPeriod, you need to add it to the
EventPublisher_t Component Type Structure.
Show the new member added to EventPublisher_t
/** The EventPublisher component */
typedef struct
{
/** A protection lock to protect the EventPublisher state */
Task_ProtectionLock_t t_Lock;
/** Data storage for the eventPeriod parameter */
uint8_t u8_EventPeriod;
+ /** Counter to track how many times publishEvent has run */
+ uint8_t u8_TaskCounter;
}
EventPublisher_t;
As with u8_EventPeriod, set this member to 0 in EventPublisher_localInit().
Show the initialisation added to EventPublisher_localInit()
status_t EventPublisher_localInit
(
EventPublisher_t *pt_EventPublisher,
const EventPublisher_Init_t *pt_InitData
)
{
/* By default, disable raising the SomethingHappened event */
pt_EventPublisher->u8_EventPeriod = 0;
+
+ /* Start counting how many times publishEvent has run from zero */
+ pt_EventPublisher->u8_TaskCounter = 0;
/* Initialise the protection lock */
return Task_ProtectionLock_init(
&pt_EventPublisher->t_Lock,
EVENTPUBLISHER_CONTAINER_TASKING_TYPE(pt_EventPublisher));
}
You’ll now need to implement logic in EventPublisher.c to make the behaviour
of the eventPeriod parameter match its documentation.
There are several ways to do this. One way is to update the publishEvent
Task so that each time it runs, u8_TaskCounter is incremented. When
u8_TaskCounter reaches u8_EventPeriod, Publish the Event and reset the
counter to 0.
Remember that to guarantee thread safety, you must use a locked section when
accessing u8_EventPeriod, because it is now accessed from several different
contexts.
Try and make the changes to publishEvent yourself if you wish, or, if you’d
prefer, use the implementation we’ve provided below.
Show the updated implementation of the publishEvent task
/*---------------------------------------------------------------------------*
* A task used to periodically publish events
*---------------------------------------------------------------------------*/
void EventPublisher_taskPublishEvent
(
EventPublisher_t *pt_EventPublisher
)
{
+ status_t t_Status;
+
EVENTPUBLISHER_CONTAINER_LOG_INFO(
pt_EventPublisher, "EventPublisher.publishEvent executed!");
- EventPublisher_EDSPublisher_publishEventTask(
- pt_EventPublisher,
- EVENTPUBLISHER_EVENTID_SOMETHING_HAPPENED,
- STATUS_SUCCESS,
- NULL,
- 0);
+ TASK_PROTECTED_START(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status)
+ {
+ /* Only publish events if eventPeriod is non-zero */
+ if (pt_EventPublisher->u8_EventPeriod != 0)
+ {
+ pt_EventPublisher->u8_TaskCounter++;
+
+ /* Once we've counted up to eventPeriod, publish the event and reset
+ * the counter */
+ if (pt_EventPublisher->u8_TaskCounter >=
+ pt_EventPublisher->u8_EventPeriod)
+ {
+ EventPublisher_EDSPublisher_publishEventTask(
+ pt_EventPublisher,
+ EVENTPUBLISHER_EVENTID_SOMETHING_HAPPENED,
+ STATUS_SUCCESS,
+ &pt_EventPublisher->u8_EventPeriod,
+ sizeof(pt_EventPublisher->u8_EventPeriod));
+
+ pt_EventPublisher->u8_TaskCounter = 0;
+ }
+ }
+ }
+ TASK_PROTECTED_END(&pt_EventPublisher->t_Lock, >_LockTimeout, t_Status);
}
Notice that we used the value of eventPeriod as the information field for the
Published Event. As mentioned previously, this field allows you to provide
extra information associated with your Event.
Build the Mission and run the Deployment Instance. Using Lab, confirm
that your implementation functions correctly and meets the requirements laid
out in the eventPeriod Parameter documentation.
Remember that the value is set to 0 during start up, so you won’t see any Events being Published until you set it to a non-zero value.
Wrap Up
In this tutorial you have:
-
Reinforced your understanding of how to work with Component Types and their implementations.
-
Learned how to use Periodic Tasks to make a Component Type do some work regularly and autonomously.
-
Been introduced to Services, specifically Publish-Subscribe Services.
-
Learned how to define Events, and how to Publish them from Component Types.
-
Learned how Parameters can be used to control the behaviour of Component Instances.
-
Used these concepts within a Component Implementation to meet some basic functional requirements. This approach of combining the features of Component Types to produce solutions is a key part of successful development with Flightkit.
What Now?
This concludes the basic Flightkit tutorials. These tutorials have given you the basic tools you need to produce operational flight software with Flightkit.
We have provided further tutorials which will help you learn how to use some more advanced features of Component Types. The first of these looks at details of using Services which were not covered in this tutorial.
Click here to move on to that now.