Using a Service
Introduction
This tutorial introduces Services as a powerful way to allow one component instance to make use of functionality provided by another.
|
In this tutorial, you will:
|
Before You Begin
The GEN1_ROOT environment variable should be set to the FSDK root directory. This is used by codegen to locate the necessary files and directories.
Open a terminal in the FSDK root directory and navigate to the Documentation/Tutorials directory using the following command.
cd Documentation/Tutorials
Execute the script to set the GEN1_ROOT environment variable.
. set_gen1_root.sh
Echo the GEN1_ROOT environment variable to confirm it has been set to the correct directory.
echo $GEN1_ROOT
|
If this command fails, check that |
Introducing Services
Services are essential to how the FSDK provides reusable functionality. They allow parts of the behaviour of a Component Type to be precisely described so it can be reused later.
Contracts
In earlier tutorials you learned that Components are how we encapsulate functionality for reuse in the FSDK.
In order to use Components to build more complex systems they need to be connected together. A Component which encodes data for downlink, for example, needs to be connected to a Component for transmitting data via a radio.
Services are the primary means of making these connections between Components in the FSDK.
A Service is a contract between Components:
-
A Component Type which provides a Service is promising to fulfil the contract which the Service defines. It must do whatever is required to handle requests from a connected component in line with the definition of the provided Service.
-
Conversely, a Component Type which consumes a Service is stating that, when instantiated, it must be connected to a provider of that Service. The Component Implementation can assume this will be true.
A given Service's contract consists of the operations in it. A Service provider must implement these Service operations. A Service consumer can issue them.
|
Thinking of Services in contractual terms is useful because it allows you to ignore the other characteristics of the Components involved. For the purposes of connecting Components together with Services, all that matters is that a given Component provides or consumes the relevant Service. |
Services in the FSDK
There are many examples of provision and consumption of Services in the FSDK:
-
Request-response interaction with hardware is represented by the Memory Access Service (MAS). SPI and I2C drivers provide MAS, while Components which access subsystems via these buses consume it.
-
Packet data in communications stacks is moved around a Deployment using the Packet Service (PS). Radio subsystems will usually provide PS, and Components which wish to send and receive data will consume it.
-
Access to files and directories on filesystems is represented by the File Store Service (FSS). Components which give access to a filesystem provide FSS, while those which need to read and write file data consume it.
You will learn more about this final example now, and you will see MAS and PS again in later tutorials.
The File Store Service
FSS is a Service consisting of operations for accessing file systems. It is modelled after the POSIX file API.
The contract which a given Service defines is captured in the model, and may be found in that Service's service.xml file. The service.xml for FSS can be found here:
$GEN1_ROOT/OBSW/Source/framework/inc/io/FSS/service.xml
|
As discussed above, a Service represents a contract between two component instances. An important difference between Services and other kinds of API is that a Service is defined independently of either of the Components which make use of it. Indeed, a single Service can be used as the contract between many different pairs of Components. |
FSS defines many operations, including those for opening files, writing to them, reading from them, and manipulating the directory structure of a filesystem.
A provider of FSS must implement these operations, while a consumer may issue any of the operations on offer, as long as it follows the rules given in the FSS service.xml.
There are two interesting examples of FSS providers in the FSDK:
-
PosixFS. You will be using this FSS provider later on. It implements the FSS Service operations by making POSIX filesystem API calls. This means it can provide FSS on POSIX-compliant platforms, namely Linux. -
FlashFS. This Component Type implements the FSS operations by calling functions provided by the open source UFFS (ultra low cost flash file system) file system. This makes it useful on resource-constrained embedded platforms like the ACS Kryten.
Note that, in a Deployment, any FSS consumer can be connected to either of these providers. This means FSS consumers are trivially portable between the platforms these providers work on!
|
In the next section you will see these concepts in action by creating an FSS consumer and connecting it to a |
Implementing a Consumer
Typically you will need to implement Service consumers more often than providers. The strict definition of Services means you can usually make use of pre-existing Service providers to meet the needs of new Service consumers.
In this section you’ll work on a SimpleImager Component Type which emulates some features you might expect a real imager subsystem to have. To keep things simple, you’ll be focusing on adding functionality to allow the imager’s settings to be read in from a file.
Once this is done, you’ll test it by adding it to a Deployment and connecting it to a PosixFS component instance.
Creating the Component Type
We will begin by creating a component library to store the SimpleImager Component Type. Then, we will create the Component Type itself.
Create a new component library to store the component type using the following command:
codegen library new components
The above command generates the following directory structure within your working directory for the new library:
components
├── config
| └── project.mk
├── inc
├── src
├── test
| └── src
| └── common
| └── deployment
| └── Deployment.c
└── Makefile
The project.mk file controls how the library is built, and the Deployment.c
file is used to facilitate building and running unit tests.
This library can now be used to hold new component types.
Since we are using FSS, we need to include the framework directory in the component library’s project.mk.
Update component libraries project.mk to include the framework directory.
# Dependencies (library directories)
DEPEND_DIRS = $(OBSW_ROOT)/framework
|
We use |
While we are here we should also increase our DUTIL_LOG_LEVEL to 3 to enable more verbose logging.
Update component libraries DUTIL_LOG_LEVEL to 3.
# Preprocessor flags
CPPFLAGS += -DUTIL_LOG_LEVEL=3
Create the Component Type using the following command:
codegen componenttype new components --name SimpleImager
This creates a componentType.xml for your new Component Type in the
components/inc/SimpleImager directory.
The SimpleImager Component Type
Open the SimpleImager componentType.xml found here: components/inc/SimpleImager/componentType.xml and add the following Parameters and Actions:
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file describes the SimpleImager component type.
-->
<ModelElement xmlns="http://www.brightascension.com/schemas/gen1/model"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentType name="SimpleImager">
<Description>
The SimpleImager component.
<!-- Note: The order of the tags is important and should not be changed. -->
</Description>
<!-- Component Imports -->
<!-- Exceptions (Status Codes) -->
<!-- Events -->
<!-- Required Components -->
<!-- Services (Provided and Required) -->
<!-- Tasks -->
<!-- Event Sources -->
<!-- Event Sinks -->
<!-- Actions -->
<Actions>
<Action name="captureImage">
<Description>
Capture an image, using the settings previously loaded by loadSettings
</Description>
</Action>
<Action name="loadSettings">
<Description>
Load imager settings from the given file
</Description>
<Argument name="settingsPath" minBytes="2" maxBytes="127">
<Description>
The absolute path to the settings file to be loaded
</Description>
<Documentation><Markdown>
This path *must* start with a `/` character and have at least 1
following character.
</Markdown></Documentation>
</Argument>
</Action>
</Actions>
<!-- Parameters -->
<Parameters>
<Parameter name="settingsLoaded" readOnly="true">
<Description>
Indicates whether imager settings were successfully loaded using loadSettings
</Description>
<Value type="bitfield" bits="1"/>
</Parameter>
<Parameter name="exposureTime" readOnly="true">
<Description>
The exposure time, in milliseconds, loaded from the settings file
</Description>
<Value type="unsigned" bits="16"/>
</Parameter>
<Parameter name="imageHeight" readOnly="true">
<Description>
The image height, in pixels, loaded from the settings file
</Description>
<Value type="unsigned" bits="16"/>
</Parameter>
<Parameter name="imageWidth" readOnly="true">
<Description>
The image width, in pixels, loaded from the settings file
</Description>
<Value type="unsigned" bits="16"/>
</Parameter>
</Parameters>
<!-- Implementation -->
</ComponentType>
</ModelElement>
As you can see, the Component Type already defines the interface the operator will use:
-
It has a
captureImageAction. Since this Component emulates an imager, invoking this Action just prints some information about the commanded image capture to the terminal. -
It has a
loadSettingsAction, which, as you can see in the documentation, takes a path to a settings file as its argument. -
It has Parameters which, once a settings file is loaded, will allow the values from the file to be inspected by the operator.
loadSettings is the Action you will be implementing.
Adding an FSS Requirement
At the moment SimpleImager looks like Component Types found in earlier tutorials. It has Actions and Parameters and not much else!
To make use of FSS, you need to make SimpleImager an FSS consumer. This is achieved by adding a <Services><Required><Service> element to its componentType.xml.
Add the <Services> element just below the `<!-- Services(Provided and Required) --> comment:
<Services>
<Required>
<Service name="fs" type="io.FSS">
<Description>
Access to the file system where SimpleImager settings files are found
</Description>
</Service>
</Required>
</Services>
As usual, this addition to the componentType.xml requires the Component Type to be regenerated.
Generate the Component Type.
codegen componenttype generate components --name SimpleImager
|
If you are not comfortable with using codegen to generate Component Types and Deployments, you can revisit earlier tutorials to refresh your memory. |
We will provide some of the component implementation for you so we can focus on the FSS operations. We will update SimpleImager.c to include some basic functionality for the parameters and actions we have defined. We will also add a SimpleImager_Parser_package.h and a SimpleImager_Parser.c file to the components/src/SimpleImager directory. These files will contain the functions required to parse the settings file. Finally, we will update SimpleImager_types.h to include the types required for the SimpleImager component.
Update the SimpleImager.c Component Type implementation to include the following.
Show the updated SimpleImager.c file
/**
* @file SimpleImager.c
* @brief Brief description of file.
*
* Full description of file
*
* @author ed
* @date 2023-01-13
*/
/*
* Copyright (c) Bright Ascension Ltd, 2023
* Licensed Material - Property of Bright Ascension Ltd
*/
#include "core/types.h"
#include "core/status.h"
#include "SimpleImager/SimpleImager.h"
#include "SimpleImager/SimpleImager_Container_package.h"
#include "SimpleImager/SimpleImager_Parser_package.h"
#include "SimpleImager/SimpleImager_config.h"
#include "task/Task_ProtectionLock.h"
#include "util/Util_Log.h"
#include "util/Util_String.h"
#include <string.h>
/* Doxygen commands to add this component to the generated documentation */
/**
* @addtogroup SimpleImager
* @{
*/
/*---------------------------------------------------------------------------*
* Global variables
*---------------------------------------------------------------------------*/
/** The lock access timeout */
static const ShortTime_t gt_LockTimeout = SIMPLEIMAGER_CONFIG_LOCK_TIMEOUT;
/*---------------------------------------------------------------------------*
* Public functions
*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*
* Carry out local component initialisation for the SimpleImager component
*---------------------------------------------------------------------------*/
status_t SimpleImager_localInit
(
SimpleImager_t *pt_SimpleImager,
const SimpleImager_Init_t *pt_InitData
)
{
/* Initialise the protection lock */
return Task_ProtectionLock_init(
&pt_SimpleImager->t_Lock,
SIMPLEIMAGER_CONTAINER_TASKING_TYPE(pt_SimpleImager));
}
/*---------------------------------------------------------------------------*
* Carry out local component finalisation for the SimpleImager component.
*---------------------------------------------------------------------------*/
status_t SimpleImager_localFini
(
SimpleImager_t *pt_SimpleImager
)
{
/* Finalise the protection lock */
return Task_ProtectionLock_fini(&pt_SimpleImager->t_Lock);
}
/*---------------------------------------------------------------------------*
* Capture an image, using the settings previously loaded by loadSettings
*---------------------------------------------------------------------------*/
status_t SimpleImager_captureImage
(
SimpleImager_t *pt_SimpleImager
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
if (pt_SimpleImager->b_SettingsLoaded)
{
/* This is not a real imager! Just print out the settings we would
* use to capture an image. */
UTIL_LOG_INFO("Capturing image with loaded settings:");
UTIL_LOG_INFO(
" Exposure time: %u ms",
pt_SimpleImager->t_Settings.u16_ExposureTime);
UTIL_LOG_INFO(
" Image height: %u px",
pt_SimpleImager->t_Settings.u16_ImageHeight);
UTIL_LOG_INFO(
" Image width: %u px",
pt_SimpleImager->t_Settings.u16_ImageWidth);
}
else
{
UTIL_LOG_ERROR("Cannot capture an image with no settings loaded!");
UTIL_LOG_ERROR("Invoke SimpleImager.loadSettings first");
t_Status = STATUS_FAILURE;
}
}
TASK_PROTECTED_END(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
/*---------------------------------------------------------------------------*
* Load imager settings from the given file
*---------------------------------------------------------------------------*/
status_t SimpleImager_loadSettings
(
SimpleImager_t *pt_SimpleImager,
const ui8_t *pu8_SettingsPath,
ui8_t u8_Length
)
{
status_t t_Status; /* The current status */
/* Check the length of the argument is valid */
if (u8_Length < SIMPLEIMAGER_LOAD_SETTINGS_MIN_ARG_SIZE)
{
/* Error: invalid argument length */
t_Status = STATUS_INVALID_PARAM;
}
else if (u8_Length > SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE)
{
/* Error: invalid argument length */
t_Status = STATUS_INVALID_PARAM;
}
else
{
/* Make a copy of pu8_SettingsPath guaranteed to be null terminated */
ui8_t ru8_SettingsPath[SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE + 1];
strncpy(
(string_t)&ru8_SettingsPath[0],
(cstring_t)pu8_SettingsPath,
u8_Length);
ru8_SettingsPath[u8_Length] = 0;
TASK_PROTECTED_START(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
/* TODO: Implement loadSettings action handler */
t_Status = STATUS_NOT_IMPLEMENTED;
}
TASK_PROTECTED_END(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
}
return t_Status;
}
/*---------------------------------------------------------------------------*
* Get the settingsLoaded parameter
*---------------------------------------------------------------------------*/
status_t SimpleImager_getSettingsLoaded
(
SimpleImager_t *pt_SimpleImager,
boolean_t *pb_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
*pb_Value = pt_SimpleImager->b_SettingsLoaded;
}
TASK_PROTECTED_END(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
/*---------------------------------------------------------------------------*
* Get the exposureTime parameter
*---------------------------------------------------------------------------*/
status_t SimpleImager_getExposureTime
(
SimpleImager_t *pt_SimpleImager,
ui16_t *pu16_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
if (pt_SimpleImager->b_SettingsLoaded)
{
*pu16_Value = pt_SimpleImager->t_Settings.u16_ExposureTime;
}
else
{
UTIL_LOG_ERROR("Cannot get exposureTime with no settings loaded!");
UTIL_LOG_ERROR("Invoke SimpleImager.loadSettings first");
t_Status = STATUS_FAILURE;
}
}
TASK_PROTECTED_END(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
/*---------------------------------------------------------------------------*
* Get the imageHeight parameter
*---------------------------------------------------------------------------*/
status_t SimpleImager_getImageHeight
(
SimpleImager_t *pt_SimpleImager,
ui16_t *pu16_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
if (pt_SimpleImager->b_SettingsLoaded)
{
*pu16_Value = pt_SimpleImager->t_Settings.u16_ImageHeight;
}
else
{
UTIL_LOG_ERROR("Cannot get imageHeight with no settings loaded!");
UTIL_LOG_ERROR("Invoke SimpleImager.loadSettings first");
t_Status = STATUS_FAILURE;
}
}
TASK_PROTECTED_END(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
/*---------------------------------------------------------------------------*
* Get the imageWidth parameter
*---------------------------------------------------------------------------*/
status_t SimpleImager_getImageWidth
(
SimpleImager_t *pt_SimpleImager,
ui16_t *pu16_Value
)
{
status_t t_Status; /* The current status */
TASK_PROTECTED_START(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
if (pt_SimpleImager->b_SettingsLoaded)
{
*pu16_Value = pt_SimpleImager->t_Settings.u16_ImageWidth;
}
else
{
UTIL_LOG_ERROR("Cannot get imageWidth with no settings loaded!");
UTIL_LOG_ERROR("Invoke SimpleImager.loadSettings first");
t_Status = STATUS_FAILURE;
}
}
TASK_PROTECTED_END(&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
return t_Status;
}
/** @} */
Update SimpleImager_Parser.c to the components/src/SimpleImager directory.
Show the completed SimpleImager_Parser.c file
/**
* @file SimpleImager_Parser.c
* @brief Provides functionality for parsing the SimpleImager settings format
*
* Provides functionality for parsing the SimpleImager settings format
*
* @author ed
* @date 2023-01-13
*/
/*
* Copyright (c) Bright Ascension Ltd, 2023
* Licensed Material - Property of Bright Ascension Ltd
*/
#include "core/types.h"
#include "core/status.h"
#include "SimpleImager/SimpleImager_Parser_package.h"
#include "util/Util_Log.h"
#include "util/Util_String.h"
/* Doxygen commands to add this component to the generated documentation */
/**
* @addtogroup SimpleImager
* @{
*/
/*---------------------------------------------------------------------------*
* Package functions
*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*
* Take data read from a SimpleImager settings file and parse it into a struct
*---------------------------------------------------------------------------*/
status_t SimpleImager_Parser_parseSettings
(
const ui8_t *pu8_SettingsData,
ui32_t u32_SettingsDataLen,
SimpleImager_Settings_t *pt_Settings
)
{
boolean_t b_HaveT, b_HaveH, b_HaveW;
SimpleImager_Settings_t t_TempSettings;
ui32_t u32_SettingsIdx = 0;
status_t t_Status = STATUS_SUCCESS;
b_HaveT = b_HaveH = b_HaveW = FALSE;
/* Iterate over pu8_SettingsData as long as there are no errors, we're not
* reading off the end, and we are missing one of the settings. */
while ((t_Status == STATUS_SUCCESS) &&
(u32_SettingsIdx < u32_SettingsDataLen) &&
(!b_HaveT || !b_HaveH || !b_HaveW))
{
ui32_t u32_AsciiLen;
ui16_t u16_SettingValue;
/* The setting character is the first we read each loop iteration. We
* will validate it after we've parsed the value so we only need one
* switch-case. */
char_t c_Setting = pu8_SettingsData[u32_SettingsIdx];
u32_SettingsIdx++;
/* Require the = sign */
if (pu8_SettingsData[u32_SettingsIdx] != '=')
{
t_Status = STATUS_FAILURE;
}
else
{
u32_SettingsIdx++;
}
if (t_Status == STATUS_SUCCESS)
{
/* Work out how many ASCII digits we have by looking for a
* semicolon. */
u32_AsciiLen = 0;
while (((u32_SettingsIdx + u32_AsciiLen) < u32_SettingsDataLen) &&
(pu8_SettingsData[u32_SettingsIdx + u32_AsciiLen] != ';'))
{
u32_AsciiLen++;
}
/* Catch the case where no ASCII digits are provided. */
if (u32_AsciiLen == 0)
{
t_Status = STATUS_FAILURE;
}
}
if (t_Status == STATUS_SUCCESS)
{
/* Try and extract the 16-bit unsigned integer field value. */
t_Status = Util_String_u16FromDec(
(const char_t *)&pu8_SettingsData[u32_SettingsIdx],
u32_AsciiLen,
&u16_SettingValue);
}
if (t_Status == STATUS_SUCCESS)
{
/* If we got here then we parsed a valid value into
* u16_SettingValue and the format is OK. Now check c_Setting to
* determine which setting we've parsed. */
switch (c_Setting)
{
case 'T':
if (b_HaveT)
{
t_Status = STATUS_FAILURE;
}
else
{
b_HaveT = TRUE;
t_TempSettings.u16_ExposureTime = u16_SettingValue;
}
break;
case 'H':
if (b_HaveH)
{
t_Status = STATUS_FAILURE;
}
else
{
b_HaveH = TRUE;
t_TempSettings.u16_ImageHeight = u16_SettingValue;
}
break;
case 'W':
if (b_HaveW)
{
t_Status = STATUS_FAILURE;
}
else
{
b_HaveW = TRUE;
t_TempSettings.u16_ImageWidth = u16_SettingValue;
}
break;
default:
t_Status = STATUS_FAILURE;
}
}
if (t_Status == STATUS_SUCCESS)
{
/* Move on to the next field by adding the number of ASCII digits
* we've read, and 1 more for the semicolon. */
u32_SettingsIdx += (u32_AsciiLen + 1);
}
}
/* If we parsed all of the settings return t_TempSettings, else fail */
if (t_Status == STATUS_SUCCESS)
{
if (b_HaveT && b_HaveH && b_HaveW)
{
*pt_Settings = t_TempSettings;
}
else
{
t_Status = STATUS_FAILURE;
}
}
return t_Status;
}
/** @} */
Update SimpleImager_Parser_package.h in the components/inc/SimpleImager directory.
Show the completed SimpleImager_Parser_package.h file
/**
* @file SimpleImager_Parser_package.h
* @brief Provides functionality for parsing the SimpleImager settings format
*
* Provides functionality for parsing the SimpleImager settings format
*
* @author ed
* @date 2023-01-13
*/
/*
* Copyright (c) Bright Ascension Ltd, 2023
* Licensed Material - Property of Bright Ascension Ltd
*/
#ifndef SIMPLEIMAGER_PARSER_PACKAGE_H_
#define SIMPLEIMAGER_PARSER_PACKAGE_H_
#include "core/types.h"
#include "core/status.h"
#include "SimpleImager/SimpleImager.h"
/* Doxygen commands to add this component to the generated documentation */
/**
* @addtogroup SimpleImager
* @{
*/
/*---------------------------------------------------------------------------*
* Defines
*---------------------------------------------------------------------------*/
/** The maximum number of bytes required to hold the 3 settings members. */
#define SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES (24u)
/*---------------------------------------------------------------------------*
* Package functions
*---------------------------------------------------------------------------*/
/**
* Take data read from a SimpleImager settings file and parse it into a struct
* @param pu8_SettingsData The input data to parse
* @param u32_SettingsDataLen The amount of data, in bytes, in pu8_SettingsData
* @param pt_Settings Pointer to a struct populated on successful exit
* @return Whether the data in pu8_SettingsData was successfully parsed into
* pt_Settings
* @note The expected format of one setting is:
*
* - A single character setting indicator: T for exposureTime, H for
* imageHeight, W for imageWidth.
* - A single "=" character
* - Up to 5 ASCII digits, given the setting value
* - A single ";" character
*
* Settings data must consist of exactly three settings, each with unique
* setting indicators.
*
* Some example valid setting data is "T=10000;H=640;W=480;"
*/
status_t SimpleImager_Parser_parseSettings
(
const ui8_t *pu8_SettingsData,
ui32_t u32_SettingsDataLen,
SimpleImager_Settings_t *pt_Settings
);
/** @} */
#endif /*SIMPLEIMAGER_PARSER_PACKAGE_H_*/
Update SimpleImager_types.h to include the following content:
Show the updated SimpleImager_types.h file
/**
* @file SimpleImager_types.h
* @brief Brief description of file.
*
* Full description of file
*
* @author ed
* @date 2023-01-13
*/
/*
* Copyright (c) Bright Ascension Ltd, 2023
* Licensed Material - Property of Bright Ascension Ltd
*/
#ifndef SIMPLEIMAGER_TYPES_H_
#define SIMPLEIMAGER_TYPES_H_
#include "core/types.h"
#include "core/status.h"
#include "SimpleImager/SimpleImager_sizes.h"
#include "SimpleImager/SimpleImager_config.h"
#include "task/Task_ProtectionLock.h"
/* Doxygen commands to add this component to the generated documentation */
/**
* @addtogroup SimpleImager
* @{
*/
/*---------------------------------------------------------------------------*
* Type definitions
*---------------------------------------------------------------------------*/
/** The SimpleImager component initialisation data */
typedef struct
{
/* No component initialisation data required */
}
SimpleImager_Init_t;
/** A structure to hold SimpleImager's settings */
typedef struct
{
/** The exposure time, in milliseconds */
ui16_t u16_ExposureTime;
/** The image height, in pixels */
ui16_t u16_ImageHeight;
/** The image width, in pixels */
ui16_t u16_ImageWidth;
}
SimpleImager_Settings_t;
/** The SimpleImager component */
typedef struct
{
/** A protection lock to protect the SimpleImager state */
Task_ProtectionLock_t t_Lock;
/**
* Indicates whether imager settings were successfully loaded using
* loadSettings.
*/
boolean_t b_SettingsLoaded;
/** The settings loaded, if b_SettingsLoaded is TRUE */
SimpleImager_Settings_t t_Settings;
}
SimpleImager_t;
/** @} */
#endif /*SIMPLEIMAGER_TYPES_H_*/
Designing loadSettings
The key file for issuing FSS operations is SimpleImager_FSS_package.h. This file contains the functions which the SimpleImager implementation must call to issue FSS operations.
|
Note that each Component Type which requires a given Service type calls its own functions to issue Service operations. This is in contrast to a typical API, where all clients of an API make the same calls. |
Now that your Component Implementation can issue FSS operations, it’s time to use them to implement the loadSettings Action. The FSS service.xml and the documentation of your FSS functions in SimpleImager_FSS_package.h would help you with this, but we can jump straight in as follows:
-
The
loadSettingsAction must first open the file indicated bysettingsPath. The FSSopenFileoperation can achieve this. -
It then needs to read the settings data from the open file. The FSS
readFileoperation can achieve this. -
The settings data then needs to be parsed. We have provided the
SimpleImager_Parser_parseSettings()function to do this. -
The file needs to be closed, regardless of any failures in processing the data. The FSS
closeFileoperation can be used for this.
|
If you are confident working from this outline, you can try and implement the |
Implementing loadSettings
To issue the FSS operations defined in SimpleImager_FSS_package.h, it needs to be included in the Component Implementation.
Open the SimpleImager.c Component Implementation and add an #include for SimpleImager_FSS_package.h at the top.
Show the added #include directive
#include "core/types.h"
#include "core/status.h"
#include "SimpleImager/SimpleImager.h"
#include "SimpleImager/SimpleImager_Container_package.h"
#include "SimpleImager/SimpleImager_Parser_package.h"
+#include "SimpleImager/SimpleImager_FSS_package.h"
#include "SimpleImager/SimpleImager_config.h"
#include "task/Task_ProtectionLock.h"
#include "util/Util_Log.h"
#include "util/Util_String.h"
#include <string.h>
In the loadSettings Action implementation, you will now add a call to SimpleImager_FSS_openFileFs(). Much like a POSIX fopen() call, this function takes a path to a file and returns a file handle to you.
In the FSDK Services can pass selection identifiers around to reference the resources they use. FSS uses selection identifiers as file handles.
In the SimpleImager_loadSettings() Action implementation, add a call to SimpleImager_FSS_openFileFs(). Use the documentation in SimpleImager_FSS_package.h to determine the arguments, or refer to the snippet below.
Show the added call to SimpleImager_FSS_openFileFs()
/*---------------------------------------------------------------------------*
* Load imager settings from the given file
*---------------------------------------------------------------------------*/
status_t SimpleImager_loadSettings
(
SimpleImager_t *pt_SimpleImager,
const ui8_t *pu8_SettingsPath,
ui8_t u8_Length
)
{
status_t t_Status; /* The current status */
/* Check the length of the argument is valid */
if (u8_Length < SIMPLEIMAGER_LOAD_SETTINGS_MIN_ARG_SIZE)
{
/* Error: invalid argument length */
t_Status = STATUS_INVALID_PARAM;
}
else if (u8_Length > SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE)
{
/* Error: invalid argument length */
t_Status = STATUS_INVALID_PARAM;
}
else
{
/* Make a copy of pu8_SettingsPath guaranteed to be null terminated */
ui8_t ru8_SettingsPath[SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE + 1];
strncpy(
(string_t)&ru8_SettingsPath[0],
(cstring_t)pu8_SettingsPath,
u8_Length);
ru8_SettingsPath[u8_Length] = 0;
TASK_PROTECTED_START(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status)
{
- /* TODO: Implement loadSettings action handler */
- t_Status = STATUS_NOT_IMPLEMENTED;
+ Service_SelectionID_t t_SettingsFile;
+
+ /* We haven't loaded any settings yet. */
+ pt_SimpleImager->b_SettingsLoaded = FALSE;
+
+ t_Status = SimpleImager_FSS_openFileFs(
+ pt_SimpleImager,
+ NULL,
+ (cstring_t)&ru8_SettingsPath[0],
+ FALSE,
+ FALSE,
+ FSS_ACCESSMODE_READ_ONLY,
+ FALSE,
+ &t_SettingsFile);
}
TASK_PROTECTED_END(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
}
return t_Status;
}
Notice that we must take some care to make sure the file name argument passed to SimpleImager_FSS_openFileFs() is null-terminated.
Next, add a call to SimpleImager_FSS_readFileFs() to read SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES bytes from the open settings file.
The SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES symbol is defined by the parser code we have written for you in SimpleImager_Parser.c. The file format we have created does not create large files, so you can add a buffer on the stack to hold all of the settings data.
Show the added call to SimpleImager_FSS_readFileFs()
/* We haven't loaded any settings yet. */
pt_SimpleImager->b_SettingsLoaded = FALSE;
t_Status = SimpleImager_FSS_openFileFs(
pt_SimpleImager,
NULL,
(cstring_t)&ru8_SettingsPath[0],
FALSE,
FALSE,
FSS_ACCESSMODE_READ_ONLY,
FALSE,
&t_SettingsFile);
+
+ if (t_Status == STATUS_SUCCESS)
+ {
+ ui8_t ru8_SettingsData[SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES];
+ ui32_t u32_SettingsDataLen = ARRAY_COUNT(ru8_SettingsData);
+
+ t_Status = SimpleImager_FSS_readFileFs(
+ pt_SimpleImager,
+ NULL,
+ t_SettingsFile,
+ &ru8_SettingsData[0],
+ &u32_SettingsDataLen);
+ }
}
TASK_PROTECTED_END(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
}
return t_Status;
}
Notice how SimpleImager_FSS_readFileFs() takes pu32_DataLength pointer argument. This allows the FSS provider to indicate if less data was read than requested by modifying the value of *pu32_DataLength.
Now, add a call passing ru8_SettingsData to the provided SimpleImager_Parser_parseSettings() function.
The SimpleImager component type structure already contains a structure to hold parsed settings. Pass a pointer to this structure to the parsing function.
Be sure to set the b_SettingsLoaded member of the component type structure to TRUE if parsing the settings succeeds!
Show the added call to SimpleImager_Parser_parseSettings()
if (t_Status == STATUS_SUCCESS)
{
ui8_t ru8_SettingsData[SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES];
ui32_t u32_SettingsDataLen = ARRAY_COUNT(ru8_SettingsData);
t_Status = SimpleImager_FSS_readFileFs(
pt_SimpleImager,
NULL,
t_SettingsFile,
&ru8_SettingsData[0],
&u32_SettingsDataLen);
+
+ if (t_Status == STATUS_SUCCESS)
+ {
+ t_Status = SimpleImager_Parser_parseSettings(
+ &ru8_SettingsData[0],
+ u32_SettingsDataLen,
+ &pt_SimpleImager->t_Settings);
+ }
+
+ if (t_Status == STATUS_SUCCESS)
+ {
+ UTIL_LOG_INFO(
+ "Successfully loaded settings from '%s'!",
+ (cstring_t)&ru8_SettingsPath[0]);
+ pt_SimpleImager->b_SettingsLoaded = TRUE;
+ }
}
}
TASK_PROTECTED_END(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
}
return t_Status;
}
Finally, add a call to SimpleImager_FSS_closeFileFs(). This must be called regardless of read errors or parsing failures.
Make sure you don’t overwrite failures in the earlier steps with a failure to close the settings file.
Show the added call to SimpleImager_FSS_closeFileFs()
if (t_Status == STATUS_SUCCESS)
{
ui8_t ru8_SettingsData[SIMPLEIMAGER_PARSER_MAX_SETTINGS_BYTES];
ui32_t u32_SettingsDataLen = ARRAY_COUNT(ru8_SettingsData);
+ status_t t_CloseStatus;
t_Status = SimpleImager_FSS_readFileFs(
pt_SimpleImager,
NULL,
t_SettingsFile,
&ru8_SettingsData[0],
&u32_SettingsDataLen);
if (t_Status == STATUS_SUCCESS)
{
t_Status = SimpleImager_Parser_parseSettings(
&ru8_SettingsData[0],
u32_SettingsDataLen,
&pt_SimpleImager->t_Settings);
}
if (t_Status == STATUS_SUCCESS)
{
UTIL_LOG_INFO(
"Successfully loaded settings from '%s'!",
(cstring_t)&ru8_SettingsPath[0]);
pt_SimpleImager->b_SettingsLoaded = TRUE;
}
+
+ t_CloseStatus = SimpleImager_FSS_closeFileFs(
+ pt_SimpleImager, NULL, t_SettingsFile);
+
+ if (t_CloseStatus != STATUS_SUCCESS)
+ {
+ UTIL_LOG_ERROR(
+ "Failed to close settings file '%s'",
+ (cstring_t)&ru8_SettingsPath[0]);
+ }
}
}
TASK_PROTECTED_END(
&pt_SimpleImager->t_Lock, >_LockTimeout, t_Status);
}
return t_Status;
}
|
Once you’ve completed the above steps, the |
The using_a_service Deployment
We need a deployment to instantiate the SimpleImager component instance and connect it to a PosixFS component instance. This will allow us to test the loadSettings Action.
We have provided a deployment using_a_service which instantiates the SimpleImager component instance and connects it to a PosixFS component instance.
|
You can refer to Creating a Flight Software Project for a refresher on how to create a new Deployment. |
Create a new deployment using_a_service in the OBSW/Source directory and update it with the following content.
Show the using_a_service Deployment
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file describes the using_a_service deployment.
Author: callum
Date: 2023-01-02
-->
<ModelElement xmlns="http://www.brightascension.com/schemas/gen1/model"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Deployment name="using_a_service">
<Description>
The using_a_service deployment used in the "Using a Service" tutorial
</Description>
<!-- Component type imports -->
<!-- Component deployment -->
<Deploy>
<!-- Component Groups -->
<ComponentGroup name="comms">
<Description>
The communications stack component group
</Description>
<Documentation>
<Markdown>
Contains the components and component sub-groups used to form the comms
stack. These component instances can be ignored for this tutorial!
</Markdown>
</Documentation>
</ComponentGroup>
<ComponentGroup name="comms.pus">
<Description>
Group containing the PUS components
</Description>
<Documentation>
<Markdown>
These component instances can be ignored for this tutorial!
</Markdown>
</Documentation>
</ComponentGroup>
<ComponentGroup name="comms.services">
<Description>
Group containing the service proxy components
</Description>
<Documentation>
<Markdown>
These component instances can be ignored for this tutorial!
</Markdown>
</Documentation>
</ComponentGroup>
<!-- Component Instances -->
<Component name="comms.TCPServer" type="io.net.tcp.TCPServer">
<Tasks>
<PeriodicTask name="receive" priority="3" />
<SporadicTask name="receiveTimeout" priority="3" />
</Tasks>
</Component>
<Component name="comms.PacketStream" type="io.net.PacketStream">
<Connections>
<Services>
<Service name="stream" component="comms.TCPServer" service="data" />
</Services>
</Connections>
<Tasks>
<SporadicTask name="receive" priority="3" />
<SporadicTask name="receiveTimeout" priority="3" />
</Tasks>
</Component>
<Component name="comms.SpacePacket" type="io.net.SpacePacket">
<Connections>
<Services>
<Service name="spacePacket" component="comms.PacketStream" service="packet" />
</Services>
</Connections>
<Tasks>
<SporadicTask name="transmit" priority="3" />
<SporadicTask name="transmitTimeout" priority="3" />
<SporadicTask name="receive" priority="3" />
<SporadicTask name="receiveTimeout" priority="3" />
</Tasks>
</Component>
<Component name="comms.pus.PUSCore" type="io.net.pus.PUSCore">
<Connections>
<Services>
<Service name="pusPacket" component="comms.SpacePacket" service="dataPacket" />
</Services>
</Connections>
<Tasks>
<SporadicTask name="transmit" priority="3" />
<SporadicTask name="transmitTimeout" priority="3" />
<SporadicTask name="receive" priority="3" />
<SporadicTask name="receiveTimeout" priority="3" />
</Tasks>
</Component>
<Component name="comms.pus.PUSToAAS" type="io.net.pus.PUSAMS">
<Connections>
<Services>
<Service name="pusSend" component="comms.pus.PUSCore" service="dataPacket" channel="0" />
<Service name="pusReceive" component="comms.pus.PUSCore" service="dataPacket"
channel="0" />
<Service name="target" component="comms.services.AASTarget" service="remote" />
</Services>
</Connections>
<Tasks>
<SporadicTask name="transmit" priority="3" />
<SporadicTask name="receive" priority="3" />
<SporadicTask name="requestCompleteTarget" priority="3" />
</Tasks>
</Component>
<Component name="comms.services.AASTarget" type="component.AAS.AASTarget" />
<Component name="comms.pus.PUSToPAS" type="io.net.pus.PUSAMS">
<Connections>
<Services>
<Service name="pusSend" component="comms.pus.PUSCore" service="dataPacket" channel="1" />
<Service name="pusReceive" component="comms.pus.PUSCore" service="dataPacket"
channel="1" />
<Service name="target" component="comms.services.PASTarget" service="remote" />
</Services>
</Connections>
<Tasks>
<SporadicTask name="transmit" priority="3" />
<SporadicTask name="receive" priority="3" />
<SporadicTask name="requestCompleteTarget" priority="3" />
</Tasks>
</Component>
<Component name="comms.services.PASTarget" type="component.PAS.PASTarget" />
<Component name="core.FileSystem" type="storage.fs.PosixFS" />
<Component name="SimpleImager" type="SimpleImager">
<Description>
An instance of the SimpleImager component type
</Description>
</Component>
</Deploy>
</Deployment>
</ModelElement>
Update deployment’s libraries project.mk to include the components directory.
# Dependencies (library directories)
DEPEND_DIRS = ../app ../framework ../components
There is already a SimpleImager component instance in the Deployment, but, if you try and generate the using_a_service Deployment now, it will fail. This is because the new SimpleImager.fs Service requirement you added is not fulfilled.
In this section, you’ll connect the Service requirement to a PosixFS component instance and test the Deployment.
Connecting fs
Open the using_a_service deployment.xml and find the SimpleImager component instance near the bottom.
You will now need to connect the SimpleImager.fs Service requirement up to an FSS provider.
To connect it, you must add a <Connections><Services><Service> element to the SimpleImager component instance in using_a_service.
Add the following <Connections> element just below the SimpleImager instance’s </Description> tag:
<Component name="SimpleImager" type="SimpleImager">
<Description>
An instance of the SimpleImager component type
</Description>
+ <Connections><Services>
+ <Service name="fs" component="core.FileSystem" service="fs" />
+ </Services></Connections>
</Component>
This connects the SimpleImager Service requirement specified by the name attribute to the Service provision specified by the component and service attributes.
In this case SimpleImager.fs will be fulfilled by core.FileSystem.fs.
Generate the using_a_service deployment.
codegen deployment generate using_a_service
|
We have provided a basic communication stack in the
You can use the initialisation data from the |
Copy the init data from fsdk-22.2/Documentation/Tutorials/the_basics/hello_world_1/hello_world_1/src/init/ directory to fulfill:
-
comms.TCPServer -
comms.SpacePacket -
comms.pus.PUSCore -
comms.pus.PUSToAAS -
comms.pus.PUSToPAS -
comms.services.PASTarget
You will need to add the initialisation data for core.FileSystem seperately, as it is not included in the hello_world_1 deployment, seen below.
core.FileSystem component instance/**
* @file FileSystem_Init.c
* @brief Brief description of file.
*
* Full description of file
*
* @author ed
* @date 2023-01-23
*/
/*
* Copyright (c) Bright Ascension Ltd, 2023
* Licensed Material - Property of Bright Ascension Ltd
*/
#include "Deploy.h"
#include "init/core/FileSystem_Init.h"
#include "storage/fs/PosixFS/PosixFS.h"
/*---------------------------------------------------------------------------*
* Global variables
*---------------------------------------------------------------------------*/
/** The core_FileSystem initialisation data */
const PosixFS_Init_t gt_core_FileSystemInit =
{
.z_MountPoint = "/"
};
To test SimpleImager and its FSS requirement, build and then run the Deployment using the same approach as in previous tutorials:
make -C using_a_service/ force target
./using_a_service/bin/linux/using_a_service
You should see the familiar message, Deployment initialisation successful.
Creating a Test File
Before testing the loadSettings Action, you will need some "settings" stored in a file on your machine. You will pass the path to this file to loadSettings. The code you’ve written will open the file and read the settings within.
Create a file with the correct format containing some SimpleImager settings.
The correct format is given in the documentation for SimpleImager_Parser_parseSettings() in SimpleImager_Parser_package.h. The following is an example of some valid settings:
T=10000;H=640;W=480;
You can use any text editor you like to create this file anywhere you like on your machine, as long as the path to it is less than 127 characters long.
|
This restriction initially comes from the definition of the
While these limits slightly restrict functionality on Linux systems, they allow us to support filesystems on resource-constrained platforms using the same Components and Services as those we use on Linux. |
Testing loadSettings
You will now use tmtclab to test SimpleImager, in particular the loadSettings Action which you have implemented.
Launch tmtclab and connect it to the running Deployment:
-
Use the
Deployment Managementdialog to select theusing_a_serviceSCDB. -
Use the
Connect…dialog to connect to the Deployment. As before, selectConnection type: TCP Clientand setAddress,PortandFramingto their default values.]
Once connected, navigate to the loadSettings Action in the tree on the left.
Double-click the Action to open an Action invocation dialog.
The Action invocation dialog for loadSettings has a field for entering the path to the settings file to load.
Change the argument field to string formatting by clicking the H (for "hex") so it changes to an S (for "string").
Enter the path to the settings file you created earlier
The path must be an absolute path to the file. You can find the absolute path in the file explorer via Right click → Properties. On the command line you can use:
realpath my_settings.txt
Show example output
/home/example/my_settings.txt
Invoke loadSettings Action by clicking the invoke button.
If the settings file was successfully loaded you should see output similar to:
INF: SimpleImager.c:160 Successfully loaded settings from '/home/example/my_settings.txt'!
Your implementation of loadSettings took the three settings values and stored them in the t_Settings structure within the SimpleImager component type structure.
You can access the members of this structure using the exposureTime, imageHeight and imageWidth Parameters. The values should match those you put in your settings file.
You can also invoke the captureImage Action which will report the settings it has in place for "capturing" an image.
|
This tutorial has demonstrated using FSS to open and read files. The Service is also capable of writing files, creating directories, listing directory contents, and many other useful file system operations. In later tutorials you will learn more about other Services defined in the FSDK, including PS and MAS. |
Wrap Up
In this tutorial you have:
-
Learned about the concept of a Service in the FSDK.
-
Learned the difference between a Service consumer and Service provider.
-
Made use of the generated functions which grant consumers access to their required Service.
-
Learned how to connect consumers and providers within a Deployment.
In the next tutorial you will learn about how unit testing is carried out in the FSDK. This will allow you to test and maintain your Component Implementations more efficiently than through the ad hoc approach we’ve been relying on so far.
Click here to move on to that now.