From e76f7d6110c9332a706df3555afead4ba258e497 Mon Sep 17 00:00:00 2001 From: Justine West Date: Mon, 26 Jun 2023 09:57:17 -0700 Subject: [PATCH 01/12] include arm toolchain files, use helper in raspberrypi.cmake --- cmake/toolchain/aarch64-linux.cmake | 18 +++++++++ cmake/toolchain/arm-hf-linux.cmake | 20 ++++++++++ cmake/toolchain/arm-sf-linux.cmake | 20 ++++++++++ cmake/toolchain/helpers/arm-linux-base.cmake | 42 ++++++++++++++++++++ cmake/toolchain/raspberrypi.cmake | 30 ++------------ 5 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 cmake/toolchain/aarch64-linux.cmake create mode 100644 cmake/toolchain/arm-hf-linux.cmake create mode 100644 cmake/toolchain/arm-sf-linux.cmake create mode 100644 cmake/toolchain/helpers/arm-linux-base.cmake diff --git a/cmake/toolchain/aarch64-linux.cmake b/cmake/toolchain/aarch64-linux.cmake new file mode 100644 index 0000000000..0c5dc4b421 --- /dev/null +++ b/cmake/toolchain/aarch64-linux.cmake @@ -0,0 +1,18 @@ +#### +# ARM 64-bit Toolchain +# +# This ARM toolchain will compile for 64-bit ARM systems running linux. It uses the arm packages installed on the system +# path for cross-compilation. To override the location of the tools use -DARM_TOOLS_PATH=... to specify the root +# directory of the tools installation. That directory should contain folders bin, lib, etc where the tools are located. +# +# These toolchains will use the linux libraries shipped with the compiler to build the final image. To override this, +# users should set -DCMAKE_SYSROOT=... to a directory containing a valid sysroot for their device. +# +# Cautions: +# 1. Care must be taken to ensure that the Linux OS running on the target is newer than the cross-compilers, or sysroot +# should be used to specify fixed library targets to compile against. +# 2. Specifying sysroot should be used with care as both libraries and headers must exist both in the sysroot +# +#### +set(CMAKE_SYSTEM_PROCESSOR "aarch64") +include("${CMAKE_CURRENT_LIST_DIR}/helpers/arm-linux-base.cmake") diff --git a/cmake/toolchain/arm-hf-linux.cmake b/cmake/toolchain/arm-hf-linux.cmake new file mode 100644 index 0000000000..47e7f098f2 --- /dev/null +++ b/cmake/toolchain/arm-hf-linux.cmake @@ -0,0 +1,20 @@ +#### +# ARM 32-bit Toolchain with Hardware Floating Point +# +# This ARM toolchain will compile for 32-bit ARM linux systems supporting hardware floating point operations. It uses +# the arm packages installed on the system path for cross-compilation. To override the location of the tools use +# -DARM_TOOLS_PATH=... to specify the root directory of the tools installation. That directory should contain folders +# bin, lib, etc where the tools are located. +# +# These toolchains will use the linux libraries shipped with the compiler to build the final image. To override this, +# users should set -DCMAKE_SYSROOT=... to a directory containing a valid sysroot for their device. +# +# Cautions: +# 1. Care must be taken to ensure that the Linux OS running on the target is newer than the cross-compilers, or sysroot +# should be used to specify fixed library targets to compile against. +# 2. Specifying sysroot should be used with care as both libraries and headers must both exist in the sysroot +# +#### +set(CMAKE_SYSTEM_PROCESSOR "arm") +set(ARM_TOOL_SUFFIX eabihf) +include("${CMAKE_CURRENT_LIST_DIR}/helpers/arm-linux-base.cmake") diff --git a/cmake/toolchain/arm-sf-linux.cmake b/cmake/toolchain/arm-sf-linux.cmake new file mode 100644 index 0000000000..52ed380e30 --- /dev/null +++ b/cmake/toolchain/arm-sf-linux.cmake @@ -0,0 +1,20 @@ +#### +# ARM 32-bit Toolchain with Software Floating Point +# +# This ARM toolchain will compile for 32-bit ARM linux systems supporting sodtware floating point operations. It uses +# the arm packages installed on the system path for cross-compilation. To override the location of the tools use +# -DARM_TOOLS_PATH=... to specify the root directory of the tools installation. That directory should contain folders +# bin, lib, etc where the tools are located. +# +# These toolchains will use the linux libraries shipped with the compiler to build the final image. To override this, +# users should set -DCMAKE_SYSROOT=... to a directory containing a valid sysroot for their device. +# +# Cautions: +# 1. Care must be taken to ensure that the Linux OS running on the target is newer than the cross-compilers, or sysroot +# should be used to specify fixed library targets to compile against. +# 2. Specifying sysroot should be used with care as both libraries and headers must both exist in the sysroot +# +#### +set(CMAKE_SYSTEM_PROCESSOR "arm") +set(ARM_TOOL_SUFFIX eabi) +include("${CMAKE_CURRENT_LIST_DIR}/helpers/arm-linux-base.cmake") diff --git a/cmake/toolchain/helpers/arm-linux-base.cmake b/cmake/toolchain/helpers/arm-linux-base.cmake new file mode 100644 index 0000000000..388e0bbf51 --- /dev/null +++ b/cmake/toolchain/helpers/arm-linux-base.cmake @@ -0,0 +1,42 @@ +#### +# ARM Linux Toolchain Base: +# +# This file provides the basic work for ARM toolchains running on Linux systems. It uses the ARM_TOOL_PREFIX variable to +# determine the names of the tools to search for. This variable must be set in the calling script. This toolchain will +# find the ARM tools under the path specified with -DARM_TOOLS_PATH=... and if -DCMAKE_SYSROOT=... is specified then +# this path will be used for searching for libraries/headers to compile against. +#### +# Set the system information +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 0.2) + +# Check ARM tools path +set(FIND_INPUTS PATHS ENV ARM_TOOLS_PATH PATH_SUFFIXES bin REQUIRED) +set(PREFIX_1 "${CMAKE_SYSTEM_PROCESSOR}-linux-gnu${ARM_TOOL_SUFFIX}") +set(PREFIX_2 "${CMAKE_SYSTEM_PROCESSOR}-none-linux-gnu${ARM_TOOL_SUFFIX}") +# Set the GNU ARM toolchain +find_program(CMAKE_ASM_COMPILER NAMES ${PREFIX_1}-as ${PREFIX_2}-as ${FIND_INPUTS}) +find_program(CMAKE_C_COMPILER NAMES ${PREFIX_1}-gcc ${PREFIX_2}-gcc ${FIND_INPUTS}) +find_program(CMAKE_CXX_COMPILER NAMES ${PREFIX_1}-g++ ${PREFIX_2}-g++ ${FIND_INPUTS}) +find_program(CMAKE_AR NAMES ${PREFIX_1}-ar ${PREFIX_2}-ar ${FIND_INPUTS}) +find_program(CMAKE_OBJCOPY NAMES ${PREFIX_1}-objcopy ${PREFIX_2}-objcopy ${FIND_INPUTS}) +find_program(CMAKE_OBJDUMP NAMES ${PREFIX_1}-objdump ${PREFIX_2}-objdump ${FIND_INPUTS}) + +# List programs as found +if (CMAKE_DEBUG_OUTPUT) + message(STATUS "[arm-linux] Assembler: ${CMAKE_ASM_COMPILER}") + message(STATUS "[arm-linux] C Compiler: ${CMAKE_C_COMPILER}") + message(STATUS "[arm-linux] CXX Compiler: ${CMAKE_CXX_COMPILER}") +endif() + +# Force sysroot onto +if (DEFINED CMAKE_SYSROOT) + message(STATUS "sysroot set to: ${CMAKE_SYSROOT}") + set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} "${CMAKE_SYSROOT}") +endif() + +# Configure the find commands for finding the toolchain +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/toolchain/raspberrypi.cmake b/cmake/toolchain/raspberrypi.cmake index 799ec18fa1..17b258b22f 100644 --- a/cmake/toolchain/raspberrypi.cmake +++ b/cmake/toolchain/raspberrypi.cmake @@ -1,35 +1,13 @@ #### # Raspberry PI Toolchain # -# This is a toolchain for the Raspberry Pi. This toolchain can be used ot build +# This is a toolchain for the Raspberry Pi. This toolchain can be used to build # against the Raspberry Pi embedded Linux target. In order to use this toolchain, # the Raspberry Pi cross-compiler should be installed on a Linux host. These # tools are installable as follows: # sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf gdb-multiarch #### -# Set the system information -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_PROCESSOR arm) -set(CMAKE_SYSTEM_VERSION 1) -# Configure the find commands for cross-compiling: tools from host, libraries, includes, and packages from cross-compiler/sysroot -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - -# Check CMake sysroot -if (DEFINED CMAKE_SYSROOT) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT} ) -endif() - -# Set the GNU ARM toolchain -find_program(CMAKE_AR NAMES arm-linux-gnueabihf-ar PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) -find_program(CMAKE_C_COMPILER NAMES arm-linux-gnueabihf-gcc PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) -find_program(CMAKE_CXX_COMPILER NAMES arm-linux-gnueabihf-g++ PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) -message(STATUS "[arm-linux] C Compiler: ${CMAKE_C_COMPILER}") -message(STATUS "[arm-linux] CXX Compiler: ${CMAKE_CXX_COMPILER}") -find_program(CMAKE_ASM_COMPILER NAMES arm-linux-gnueabihf-as PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) -find_program(CMAKE_OBJCOPY NAMES arm-linux-gnueabihf-objcopy PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) -find_program(CMAKE_OBJDUMP NAMES arm-linux-gnueabihf-objdump PATHS ENV RPI_TOOLCHAIN_DIR PATH_SUFFIXES bin REQUIRED) - +set(CMAKE_SYSTEM_PROCESSOR "arm") +set(ARM_TOOL_SUFFIX eabihf) +include("${CMAKE_CURRENT_LIST_DIR}/helpers/arm-linux-base.cmake") From 31efbfc6dc93a554c89ccf7560c9fa8f0cec8afb Mon Sep 17 00:00:00 2001 From: Justine West Date: Mon, 26 Jun 2023 11:48:10 -0700 Subject: [PATCH 02/12] spelling --- .github/actions/spelling/expect.txt | 3 +++ cmake/toolchain/arm-sf-linux.cmake | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 1da60d3633..1872cd830e 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -1,4 +1,5 @@ Aadil +aarch abcd ABCDE ABCDEF @@ -391,6 +392,8 @@ dumpobj DVI DWN dylib +eabi +eabihf EACCES EAGAIN eay diff --git a/cmake/toolchain/arm-sf-linux.cmake b/cmake/toolchain/arm-sf-linux.cmake index 52ed380e30..60c25d4429 100644 --- a/cmake/toolchain/arm-sf-linux.cmake +++ b/cmake/toolchain/arm-sf-linux.cmake @@ -1,7 +1,7 @@ #### # ARM 32-bit Toolchain with Software Floating Point # -# This ARM toolchain will compile for 32-bit ARM linux systems supporting sodtware floating point operations. It uses +# This ARM toolchain will compile for 32-bit ARM linux systems supporting software floating point operations. It uses # the arm packages installed on the system path for cross-compilation. To override the location of the tools use # -DARM_TOOLS_PATH=... to specify the root directory of the tools installation. That directory should contain folders # bin, lib, etc where the tools are located. From d8f0deb75e91a237ce6e81dfbefacfd39c2d191c Mon Sep 17 00:00:00 2001 From: Justine West Date: Mon, 26 Jun 2023 12:28:52 -0700 Subject: [PATCH 03/12] use RPI_TOOLCHAIN_DIR if it was specified --- cmake/toolchain/raspberrypi.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/toolchain/raspberrypi.cmake b/cmake/toolchain/raspberrypi.cmake index 17b258b22f..c23a5a828a 100644 --- a/cmake/toolchain/raspberrypi.cmake +++ b/cmake/toolchain/raspberrypi.cmake @@ -10,4 +10,9 @@ set(CMAKE_SYSTEM_PROCESSOR "arm") set(ARM_TOOL_SUFFIX eabihf) + +if(DEFINED ENV{RPI_TOOLCHAIN_DIR}) + set(ENV{ARM_TOOLS_PATH} "$ENV{RPI_TOOLCHAIN_DIR}") +endif() + include("${CMAKE_CURRENT_LIST_DIR}/helpers/arm-linux-base.cmake") From b3493ca03b68b2ade0ab4d3d768186c30856fe80 Mon Sep 17 00:00:00 2001 From: hAkselS <86744896+hAkselS@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:17:22 -0700 Subject: [PATCH 04/12] Migrate HelloWorld tutorial to F' community (#2095) --- README.md | 2 +- docs/Tutorials/HelloWorld/Deployments.md | 185 --------------------- docs/Tutorials/HelloWorld/HelloWorld.md | 198 ----------------------- docs/Tutorials/HelloWorld/NewProject.md | 84 ---------- docs/Tutorials/HelloWorld/README.md | 3 + docs/Tutorials/HelloWorld/Tutorial.md | 78 --------- docs/Tutorials/README.md | 13 +- 7 files changed, 9 insertions(+), 554 deletions(-) delete mode 100644 docs/Tutorials/HelloWorld/Deployments.md delete mode 100644 docs/Tutorials/HelloWorld/HelloWorld.md delete mode 100644 docs/Tutorials/HelloWorld/NewProject.md create mode 100644 docs/Tutorials/HelloWorld/README.md delete mode 100644 docs/Tutorials/HelloWorld/Tutorial.md diff --git a/README.md b/README.md index 29fa2022e2..f5c57a5c9f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Then, create a new project with: fprime-util new --project ``` -See the [HelloWorld Tutorial](https://nasa.github.io/fprime/Tutorials/HelloWorld/Tutorial.html) to guide you through all the steps of developing an F´ project. +See the [HelloWorld Tutorial](https://fprime-community.github.io/fprime-tutorial-hello-world/) to guide you through all the steps of developing an F´ project. New users are encouraged to read through the [User Guide](https://nasa.github.io/fprime/UsersGuide/guide.html) and explore the [other tutorials](https://nasa.github.io/fprime/Tutorials/README.html). diff --git a/docs/Tutorials/HelloWorld/Deployments.md b/docs/Tutorials/HelloWorld/Deployments.md deleted file mode 100644 index 96ee274801..0000000000 --- a/docs/Tutorials/HelloWorld/Deployments.md +++ /dev/null @@ -1,185 +0,0 @@ -# Getting Started: Integration and Testing With F´ Deployments - -This section will walk new users through creating a new F´ [deployment](./Tutorial.md#deployment). This deployment will -build a [topology](./Tutorial.md#topology) containing the standard F´ stack of components and a single `HelloWorld` -component instance. The `HelloWorld` was created in the [last section](./HelloWorld.md). The tutorial will close by -testing the deployment and `HelloWorld` component through the `fprime-gds`. - -### Prerequisites: -- [Getting Started: F´ Hello World Component](./HelloWorld.md) - -### Tutorial Steps: -- [Creating A New Deployment](#creating-a-new-deployment) -- [Adding The Hello World Component](#adding-the-hello-world-component) -- [Testing With `fprime-gds`](#testing-with-fprime-gds) -- [Conclusion](#conclusion) - -## Creating A New Deployment - -F´ deployments represent one flight software executable. All the components we develop for F´ run within a deployment. -The deployment created here will contain the standard command and data handling stack. This stack enables -ground control and data collection of the deployment. - -To create a deployment, run the following commands: -```bash -# In: MyProject -fprime-util new --deployment -``` -This command will ask for some input. Respond with the following answers: - -``` -deployment_name [MyDeployment]: MyDeployment -path_to_fprime [./fprime]: -``` - -> For any other questions, select the default response. - -At this point, the `MyDeployment` has been created, but our `HelloWorld` component has not been added. - -## Adding The Hello World Component - -First, the project's components should be added to this deployment's build. This can be done by adding the following -to `MyDeployment/CMakeLists.txt`. - -```cmake -... -### -# Components and Topology -### -include("${FPRIME_PROJECT_ROOT}/project.cmake") -... -``` -> To build this new deployment generate a build cache and then build. -> ```bash -> # In: MyProject/MyDeployment -> fprime-util generate -> fprime-util build -> ``` -> > Notice `fprime-util generate` was used again. This is because this new deployment builds in a separate environment. - -In this section the `HelloWorld` component will be added to the `MyDeployment` deployment. This can be done by adding -the component to the topology defined in `MyDeployment/Top`. - -Topologies instantiate all the components in a running system and link them together. For some port types, like the -commanding, event, and telemetry ports used by `HelloWorld`, the connections are made automatically. In addition, the -topology specifies how to construct the component instance. This is also done automatically unless the component has -specific configuration. - -In order to add a component to the topology, it must be added to the topology model. An instance definition and an -instance initializer must both be added. - -To add an instance definition, add `instance helloWorld` to the instance definition list in the `topology MyDeployment` section -of `MyDeployment/Top/topology.fpp`. This is shown below. - -Edit `MyDeployment/Top/topology.fpp`: -``` -... - topology MyDeployment { - # ---------------------------------------------------------------------- - # Instances used in the topology - # ---------------------------------------------------------------------- - - instance ... - instance ... - instance helloWorld -``` -> Be careful to not remove any other instances from the list. - -`helloWorld` is the name of the component instance. Like variable names, component instance names should be descriptive -and are typically named in camel or snake case. - -Next, an instance initializer must be added to topology instances defined in `MyDeploymment/Top/instances.fpp` file. -Since the `HelloWorld` component is an `active` component it should be added to the active components section and should -define a priority and queue depth options. This is shown below. - -Add to `MyDeploymment/Top/instances.fpp`: -``` -... - # ---------------------------------------------------------------------- - # Active component instances - # ---------------------------------------------------------------------- - instance ... - ... - ... - ... - - instance ... - - instance helloWorld: MyComponents.HelloWorld base id 0x0F00 \ - queue size Default.QUEUE_SIZE \ - stack size Default.STACK_SIZE \ - priority 50 -``` -> The user must ensure that the base id (0x0F00) does not conflict with any other base ids in the topology. 0x0F00 -> should be safe for deployments created with `fprime-util new --deployment`. - -> Make sure to use the same instance name (i.e. helloWorld) as defined in the instance definition just added to -> `topology.fpp`. - -Finally, our new telemetry channel should be added to our telemetry packet specification. For this tutorial the -channel can be ignored as the deployment will not use the telemetry packetizer. Add the following to the `ignore` -section of `MyDeployment/Top/MyDeploymentPackets.xml`. - -Update `MyDeployment/Top/MyDeploymentPackets.xml`: -``` - - ... - - -``` - -Since this component has no custom ports nor does it require special configuration, our addition to the topology is -completed. The deployment can now be set up and built using the following commands: - -``` -# In: MyProject/MyDeployment -fprime-util build -j4 -``` -> Resolve any errors that occur before continuing to the running section. - -## Running With `fprime-gds` - -It is now time to test the `HelloWorld` component by running the deployment created in this section. This can be -accomplished by running the `fprime-gds` command in the deployment, verifying connection, sending the new SEND_HELLO -command and verifying that the `Hello` event and `GreetingCount` channel appears. - -To start the deployment with default settings, run: -```bash -fprime-gds -``` - -The F´ GDS control page should open up in your web browser. If it does not open up, navigate to `http://127.0.0.1:5000`. - -Once the F´ GDS page is visible, look for a green circle icon in the upper right corner. This shows that the flight -software deployment has connected to the GDS system. If a red X appears instead, navigate to the Logs tab and look for -errors in the various logs. - -Now that communication is verified, navigate to the "Commanding" tab and select `helloWorld.SAY_HELLO` from the -dropdown list. Type a greeting into the argument input box and click the button "Send Command". If the argument has -validated successfully the command will send. Resolve all errors and ensure the command has sent. - -> Notice commands are instance specific. Had several HelloWorld component instances been used, there would be multiple -> `SAY_HELLO` listings, one for each component instance. - -Now that the command has sent, navigate to the "Events" tab. Ensure that the event list contains the Hello event with -the text entered when sending the command. - -Lastly, navigate to the "Channels" tab. Look for "helloWorld.GreetingCount" in the channel list. Ensure it has recorded -the number of times a `helloWorld.SAY_HELLO` was sent. - -Congratulations, you have now set up a project, component, and deployment in F´. - -## Conclusion - -This concludes both the adding deployment section of the Getting Started tutorial and the tutorial itself. The user has -been able to perform the following actions: - -1. Create a new blank F´ projects -2. Create a new F´ components -3. Create a new F´ deployments and add components it - -To explore components more in-depth and see how components communicate with one another, see the -[Math Component Tutorial](../MathComponent/Tutorial.md). - -**Next:** [Math Component Tutorial](../MathComponent/Tutorial.md) - diff --git a/docs/Tutorials/HelloWorld/HelloWorld.md b/docs/Tutorials/HelloWorld/HelloWorld.md deleted file mode 100644 index b034509fdb..0000000000 --- a/docs/Tutorials/HelloWorld/HelloWorld.md +++ /dev/null @@ -1,198 +0,0 @@ -# Getting Started: Creating an F´ Hello World Component - -This tutorial will walk new users through creating a basic F´ component. Users should have completed the new project -tutorial and have the tools sourced as shown in the [conclusion](./NewProject.md#conclusion) portion of that tutorial. - -F´ components encapsulate the various parts of system behavior. These components can interact with the ground system -through [commands](Tutorial.md#command), [events](./Tutorial.md#event), and -[telemetry channels](./Tutorial.md#telemetry-channel). Components communicate with other components through -[ports](./Tutorial.md#port), which covered in-depth in [another tutorial](../MathComponent/Tutorial.md). - -### Prerequisites: -- [Getting Started: Creating an F´ Project](./NewProject.md) - -### Tutorial Steps: -- [Hello World Component](#hello-world-component-requirements) -- [Creating the Hello World Component](#creating-the-hello-world-component) -- [Editing the Component Model](#editing-the-component-model) -- [Implementing Component Behavior](#implementing-component-behavior) -- [Conclusion](#conclusion) - -## Hello World Component Requirements - -The first step for creating a new component is understanding what it is that we wish to implement. This is called -defining requirements. In the spirit of "Hello World" this component will encapsulate greeting behavior. The component -will define three items to implement greeting behaviour: - -1. A [command](./Tutorial.md#command) called `SAY_HELLO` that will command the component to send a greeting -2. An [event](./Tutorial.md#event) called `Hello` that is the greeting sent in response to the `SAY_HELLO` command -3. A [telemetry channel](./Tutorial.md#telemetry-channel) called `GreetingCount` that will count each `Hello` event sent - -These are a simple set of requirements for this component. - -## Creating the Hello World Component - -The next step is to create the new component. First, create a directory called `MyComponents` to contain this project's -components and change into that directory. - -```bash -# In: MyProject -mkdir -p MyComponents -cd MyComponents -``` - -Creating a new component is accomplished with the following command: - -```bash -# In: MyProject/MyComponents -fprime-util new --component -``` -This command will ask for some input. You should respond with the following answers: - -``` -[INFO] Cookiecutter source: using builtin -component_name [MyComponent]: HelloWorld -component_short_description [Example Component for F Prime FSW framework.]: Hello World Tutorial Component -component_namespace [HelloWorld]: MyComponents -Select component_kind: -1 - active -2 - passive -3 - queued -Choose from 1, 2, 3 [1]: 1 -Select enable_commands: -1 - yes -2 - no -Choose from 1, 2 [1]: 1 -Select enable_telemetry: -1 - yes -2 - no -Choose from 1, 2 [1]: 1 -Select enable_events: -1 - yes -2 - no -Choose from 1, 2 [1]: 1 -Select enable_parameters: -1 - yes -2 - no -Choose from 1, 2 [1]: 1 -[INFO] Found CMake file at 'MyProject/project.cmake' -Add component MyComponents/HelloWorld to MyProject/project.cmake at end of file (yes/no)? yes -Generate implementation files (yes/no)? yes -``` - -> For any other questions, select the default response. - -This will create a new component called "HelloWorld" in the "MyProject" namespace. This new component will be able to -define commands, events, telemetry channels, and parameters. - -We should navigate to the component's directory and look around: - -```bash -# In: MyProject/MyComponents -cd HelloWorld -ls -``` -This will show the following files: -1. `HelloWorld.fpp`: design model for the component -2. `HelloWorld.hpp` and `HelloWorld.cpp`: C++ implementation files for the component, currently empty. -3. `CMakeList.txt`: build definitions for the component. -4. `docs` folder to place component documentation - -To build this component run `fprime-util build` in the current folder. - -> Any component in F´ can be built by navigating to the component's folder and running `fprime-util build`. - -## Editing the Component Model - -A component model defines the interface of the component with the rest of the F´ system and with the ground system F´ -communicates with. In this case we intend to define a command, an event, and a telemetry channel as specified above. - -Open the model file `HelloWorld.fpp` and add replace the line: - -``` -async command TODO opcode 0 -``` - -with the following: - -``` -@ Command to issue greeting with maximum length of 20 characters -async command SAY_HELLO( - greeting: string size 20 @< Greeting to repeat in the Hello event -) - -@ Greeting event with maximum greeting length of 20 characters -event Hello( - greeting: string size 20 @< Greeting supplied from the SAY_HELLO command -) severity activity high format "I say: {}" - -@ A count of the number of greetings issued -telemetry GreetingCount: U32 -``` -> You should ensure to replace any existing command, event, and channel definitions with those supplied above but leave -> the 'Standard AC Ports' section untouched. - -With this step completed you can generate a basic implementation with the following command: - -```bash -# In: MyProject/MyComponents/HelloWorld -fprime-util impl -``` - -This creates `HelloWorld.hpp-template` and `HelloWorld.cpp-template` files that contain our new fill-in template. While -normally one would merge new templates with the existing code, we will instead overwrite the existing implementations as -we have not edited those files yet. To do this: - -```bash -mv HelloWorld.hpp-template HelloWorld.hpp -mv HelloWorld.cpp-template HelloWorld.cpp -``` -We are now ready for implementing component behavior. - -## Implementing Component Behavior - -F´ behavior is implemented in two types of methods command handler functions to implement command behavior and handler -functions to implement port behavior (as described in the next tutorial). For this tutorial we need to implement the -`SAY_HELLO` command, so we need to edit the `SAY_HELLO_cmdHandler` function in the `HelloWorld.cpp` file. Ensure its -contents look like: - -```c++ -void HelloWorld:: SAY_HELLO_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const Fw::CmdStringArg& greeting) { - // Copy the command string input into an event string for the Hello event - Fw::LogStringArg eventGreeting(greeting.toChar()); - // Emit the Hello event with the copied string - this->log_ACTIVITY_HI_Hello(eventGreeting); - - this->tlmWrite_GreetingCount(++this->m_greetingCount); - - // Tell the fprime command system that we have completed the processing of the supplied command with OK status - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} -``` -> We must also add the m_greetingCount member variable to the class defined in `HelloWorld.hpp` and the constructor -> defined in `HelloWorld.cpp`. This looks like: -> -> **HelloWorld.hpp: Adding New Member Variable** -> ```c++ -> private: -> U32 m_greetingCount; -> ``` -> Should be added inside the `class` definition in `HelloWorld.hpp`. -> -> **HelloWorld.cpp: Updating Constructor** -> ```c++ -> HelloWorld:: HelloWorld(const char *const compName) : HelloWorldComponentBase(compName), -> m_greetingCount(0) -> { -> ``` -> Should be added to the `HelloWorld` constructor at the top of the file. - -The component should build without errors by running `fprime-util build`. Resolve any errors that occur before -proceeding to the next section. - -## Conclusion - -This tutorial has walked through the creation of component that implements a "Hello World" style greeting behavior for -our F´ system. In the next tutorial, this component will be hooked-up to an F´ deployment and tested! - -**Next:** [Getting Started: Integration and Testing With F´ Deployments](./Deployments.md) diff --git a/docs/Tutorials/HelloWorld/NewProject.md b/docs/Tutorials/HelloWorld/NewProject.md deleted file mode 100644 index 817bd81e24..0000000000 --- a/docs/Tutorials/HelloWorld/NewProject.md +++ /dev/null @@ -1,84 +0,0 @@ -# Getting Started: Creating an F´ Project - -This tutorial will walk new users through creating a new F´ project. - -### Tutorial Steps: -- [Bootstrapping F´](#bootstrapping-f) -- [Creating a New F´ Project](#creating-a-new-f-project) -- [Building the New F´ Project](#building-the-new-f-project) -- [Conclusion](#conclusion) - -## Bootstrapping F´ - -An F´ [project](./Tutorial.md#project) ties to a specific version of tools to work with F´. In order to create -this project and install the correct version of tools, an initial bootstrap version of F´ tools must be installed. This -is accomplished with the following command: - -```bash -pip install fprime-tools -``` - -## Creating a New F´ Project - -Now that to tools are installed a new F´ project should be created. An F´ project internalizes the version of F´ that -the project will build upon and provides the user the basic setup for creating, building, and testing components. - -In order to make a new project, run the following command and answer the questions as indicated below: - -```bash -fprime-util new --project -``` - -This command will ask for some input. Respond with the following answers: -``` -project_name [MyProject]: MyProject -fprime_branch_or_tag [devel]: devel -Select install_venv: -1 - yes -2 - no -Choose from 1, 2 [1]: 1 -``` - -Use the default for anything not specified. This command will take a moment to run. - -The above command creates a new F´ project structure in a folder called `MyProject`, use the `devel` branch of F´ as -the basis for the project, and sets up the matching tools in a new Virtual Environment. - -> Load the tools for this project via the virtual environment. -> -> ```bash -> cd MyProject -> . venv/bin/activate ->``` -> -> Make sure to load these tools any time you are working with the this project. - -## Building the New F´ Project - -The next step is to set up and build the newly created project. This will serve as a build environment for any newly -created components, and will build the F´ framework supplied components. - -```bash -cd MyProject -fprime-util generate -fprime-util build -j4 -``` - -> `fprime-util generate` sets up the build environment for a project/deployment. It only needs to be done once. At the -> end of this tutorial, a new deployment will be created and `fprime-util generate` will also be used then. - -## Conclusion - -A new project has been created with the name `MyProject` and has been placed in a new folder called in `MyProject` in -the current directory. It includes the initial build system setup, and F´ version. It is still empty in that the user -will still need to create components and deployments. - -For the remainder of this Getting Started tutorial we should use the tools installed for our project and issue commands -within this new project's folder. Change into the project directory and load the newly install tools with: - -```bash -cd MyProject -. venv/bin/activate -``` - -**Next:** [Getting Started: Creating an F´ Hello World Component](./HelloWorld.md) diff --git a/docs/Tutorials/HelloWorld/README.md b/docs/Tutorials/HelloWorld/README.md new file mode 100644 index 0000000000..6bf352ed4a --- /dev/null +++ b/docs/Tutorials/HelloWorld/README.md @@ -0,0 +1,3 @@ +## Welcome + +HelloWorld tutorial has been reworked and moved to [fprime-community/fprime-tutorial-hello-world](https://fprime-community.github.io/fprime-tutorial-hello-world/) \ No newline at end of file diff --git a/docs/Tutorials/HelloWorld/Tutorial.md b/docs/Tutorials/HelloWorld/Tutorial.md deleted file mode 100644 index 3c3f952a11..0000000000 --- a/docs/Tutorials/HelloWorld/Tutorial.md +++ /dev/null @@ -1,78 +0,0 @@ -# Getting Started: Introduction and F´ Terminology - -The getting started tutorial is designed to teach new users the basics of F´ usage, instruct existing users on new -command that help in F´ development, and act as the canonical "Hello World" example for F´. - -This tutorial walks through the following steps: -1. [Creating an F´ Project](./NewProject.md) -2. [Creating an F´ Hello World Component](./HelloWorld.md) -3. [Integration and Testing With F´ Deployments](./Deployments.md) - -Once finished, users should have a good understanding of the basic development mechanics for working with F´ and then -could dive deeper into concepts through the [Math Component Tutorial](../MathComponent/Tutorial.md). - -## F´ Terminology - -F´ uses specific terminology to refer to specific parts of the system. This section dives into the basic F´ terminology -used in this tutorial and an explanation of how the terminology is used. - -#### Project - -An F´ project is a collection of files and folders used to work with F´. At its core, a project is just a folder that -can be used to hold F´ code. Projects should specifically exclude the core F´ library to avoid the "clone and own" -problem. Rather projects should link-to a version of F´. - -This tutorial will create a new `MyProject` project used to contain the other things created by the tutorial. - -#### Component - -An F´ component encapsulates a unit of system behavior. Components use ports to communicate with other components and -define commands, events, telemetry channels, and parameters. - -This tutorial will create a new `HelloWorld` component that defines a `SAY_HELLO` command, `Hello` event, and -`GreetingCount` telemetry channel. The component only used built-in ports. It does not use custom ports nor parameters. - -#### Port - -A port is an interface used to communicate between components. This tutorial only uses built-in ports. - -#### Command - -Commands represent actions that a component can execute. Commands can be sent from the ground or via command sequences. -Command behavior is defined in a command handler defined in the component's implementation. - -This tutorial defines one command `SAY_HELLO` with a single argument `greeting`. This command will be sent via the -ground system and will echo the greeting back via the `Hello` event. - -#### Event - -Events represent actions that a component has performed. Events are akin to software log messages. Events are received -and displayed by the ground system. - -This tutorial defines one event `Hello` emitted in response to the `SAY_HELLO` command and containing the same greeting -that the command had. - -#### Telemetry Channel - -Telemetry channels provide the active state of the component. Each channel represents a single item of state that will -be sent to the ground system. - -This tutorial defines a single channel `GreetingCount` that contains the count of the number of `SAY_HELLO` greeting -commands received. - -#### Deployment - -Deployments are a single build of F´ software. Each deployment results in one software executable that can be run along -with other associated files. Each deployment has a topology that defines the system. - -This tutorial will create the `MyDeployment` deployment, run this deployment, and test it through the F´ GDS. - -#### Topology - -Topologies are networks of connected components that define the software. Multiple instances of a component may be added -to the topology. - -This tutorial will use a standard command and data handling topology. A single `HelloWorld` component instance called -`helloWorld` will be added to the standard topology. - -**Next:** [Getting Started: Creating an F´ Project](./NewProject.md) diff --git a/docs/Tutorials/README.md b/docs/Tutorials/README.md index de4086583f..a20aec3854 100644 --- a/docs/Tutorials/README.md +++ b/docs/Tutorials/README.md @@ -8,20 +8,17 @@ users to learn F´ and walk through the most basic steps in developing an F´ ap ## Tutorials Index -1. [HelloWorld](HelloWorld/Tutorial.md): An Introduction to F´ +1. [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´ 2. [LedBlinker](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/README.md): F´ and Embedded Hardware 3. [MathComponent](MathComponent/Tutorial.md) 4. [Cross-Compilation Tutorial](CrossCompilation/Tutorial.md) -## [HelloWorld](HelloWorld/Tutorial.md): An Introduction to F´ +## [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´ + +The HelloWorld tutorial walks a new users through creating a new project, designing their first F´ component, and testing that +component through an F´ deployment. -The HelloWorld tutorial walks a new user through creating a new project, their first F´ component, and testing that -component through an F´ deployment. This tutorial has the following subsections: -1. [Introduction and F´ Terminology](./HelloWorld/Tutorial.md) -2. [Creating an F´ Project](./HelloWorld/NewProject.md) -3. [Creating an F´ Hello World Component](./HelloWorld/HelloWorld.md) -4. [Integration and Testing With F´ Deployments](./HelloWorld/Deployments.md) ## [LedBlinker](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/README.md): F´ and Embedded Hardware From be4088dfa2d315a1caf5e2219b739576d4823012 Mon Sep 17 00:00:00 2001 From: mohitsingh999 <55040049+mohitsingh999@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:54:11 -0700 Subject: [PATCH 05/12] Add SPI Mode Enum to LinuxSpiDriver (#2097) * Add SPI Mode Enum to LinuxSpiDriver SPI Modes specify the clock polarity for each transaction. The default Fprime LinuxSpiDriver assumed SPI Mode 0. This should be generalized to account for SPI devices with different clock polarity and phases. * Add default spiMode parameter value, assert check to ensure SPI Mode is in range * Use switch statement to set SPI Mode when opening SPI Device * Force-fail assert and print SPI Mode for invalid SPI modes * Redefined SpiMode enum variables, added missing break statements * Update SpiMode enum comments for each variable --- .../LinuxSpiDriverComponentImpl.cpp | 27 ++++++++++++++++--- .../LinuxSpiDriverComponentImpl.hpp | 22 ++++++++++++++- .../LinuxSpiDriverComponentImplStub.cpp | 3 ++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.cpp b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.cpp index 180e0103a4..c07fc0e465 100644 --- a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.cpp +++ b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.cpp @@ -70,7 +70,8 @@ namespace Drv { bool LinuxSpiDriverComponentImpl::open(NATIVE_INT_TYPE device, NATIVE_INT_TYPE select, - SpiFrequency clock) { + SpiFrequency clock, + SpiMode spiMode) { this->m_device = device; this->m_select = select; @@ -97,9 +98,29 @@ namespace Drv { // Configure: /* - * SPI Mode 0 + * SPI Mode 0, 1, 2, 3 */ - U8 mode = SPI_MODE_0; // Mode 0 (CPOL = 0, CPHA = 0) + + U8 mode; // Mode Select (CPOL = 0/1, CPHA = 0/1) + switch(spiMode) { + case SpiMode::SPI_MODE_CPOL_LOW_CPHA_LOW: + mode = SPI_MODE_0; + break; + case SpiMode::SPI_MODE_CPOL_LOW_CPHA_HIGH: + mode = SPI_MODE_1; + break; + case SpiMode::SPI_MODE_CPOL_HIGH_CPHA_LOW: + mode = SPI_MODE_2; + break; + case SpiMode::SPI_MODE_CPOL_HIGH_CPHA_HIGH: + mode = SPI_MODE_3; + break; + default: + //Assert if the device SPI Mode is not in the correct range + FW_ASSERT(0, spiMode); + break; + } + ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); if (ret == -1) { DEBUG_PRINT("ioctl SPI_IOC_WR_MODE fd %d failed. %d\n",fd,errno); diff --git a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.hpp b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.hpp index e5c1eabf87..51e2a3b163 100644 --- a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.hpp +++ b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImpl.hpp @@ -33,6 +33,25 @@ namespace Drv { SPI_FREQUENCY_20MHZ = 20000000UL, }; + /** + * SPI Mode Select + * + * Defines the SPI Clock Polarity and Phase for each SPI Transaction. + * + * SPI Clock Polarity(CPOL): Defines clock polarity as idle low (CPOL = 0) or idle high(CPOL = 1) + * SPI Clock Phase(CPHA): Defines if data is shifted out on the rising clock edge and sampled + * on the falling clock edge(CPHA = 0) or if data is shifted out on the + * falling clock edge and sampled on the rising clock edge(CPHA=1) + * + */ + enum SpiMode + { + SPI_MODE_CPOL_LOW_CPHA_LOW, ///< (CPOL = 0, CPHA = 0) + SPI_MODE_CPOL_LOW_CPHA_HIGH,///< (CPOL = 0, CPHA = 1) + SPI_MODE_CPOL_HIGH_CPHA_LOW,///< (CPOL = 1, CPHA = 0) + SPI_MODE_CPOL_HIGH_CPHA_HIGH,///< (CPOL = 1, CPHA = 1) + }; + class LinuxSpiDriverComponentImpl: public LinuxSpiDriverComponentBase { public: @@ -59,7 +78,8 @@ namespace Drv { //! Open device bool open(NATIVE_INT_TYPE device, NATIVE_INT_TYPE select, - SpiFrequency clock); + SpiFrequency clock, + SpiMode spiMode = SpiMode::SPI_MODE_CPOL_LOW_CPHA_LOW); PRIVATE: diff --git a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImplStub.cpp b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImplStub.cpp index 3b3ebe0aa6..b7620dbbf5 100644 --- a/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImplStub.cpp +++ b/Drv/LinuxSpiDriver/LinuxSpiDriverComponentImplStub.cpp @@ -17,7 +17,8 @@ namespace Drv { bool LinuxSpiDriverComponentImpl::open(NATIVE_INT_TYPE device, NATIVE_INT_TYPE select, - SpiFrequency clock) { + SpiFrequency clock, + SpiMode spiMode) { //TODO: fill this function out return false; } From 8498e4fee865c97d94b7f7bb32d94614b6f3ef12 Mon Sep 17 00:00:00 2001 From: M Starch Date: Mon, 17 Jul 2023 13:27:56 -0700 Subject: [PATCH 06/12] Adding NaN, Infinity, and negative Infinity (#2131) --- Ref/Top/RefPackets.xml | 4 ++++ Ref/TypeDemo/TypeDemo.cpp | 12 ++++++++++++ Ref/TypeDemo/TypeDemo.fpp | 25 +++++++++++++++++++++++++ Ref/TypeDemo/TypeDemo.hpp | 6 ++++++ 4 files changed, 47 insertions(+) diff --git a/Ref/Top/RefPackets.xml b/Ref/Top/RefPackets.xml index edd64f2fc8..e597eb95ee 100644 --- a/Ref/Top/RefPackets.xml +++ b/Ref/Top/RefPackets.xml @@ -153,6 +153,10 @@ + + + + diff --git a/Ref/TypeDemo/TypeDemo.cpp b/Ref/TypeDemo/TypeDemo.cpp index 3362a6f58b..321474ed76 100644 --- a/Ref/TypeDemo/TypeDemo.cpp +++ b/Ref/TypeDemo/TypeDemo.cpp @@ -123,4 +123,16 @@ void TypeDemo ::DUMP_TYPED_PARAMETERS_cmdHandler(const FwOpcodeType opCode, cons this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); } +void TypeDemo ::DUMP_FLOATS_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq) { + Ref::FloatSet invalid; + invalid[0] = std::numeric_limits::infinity(); + invalid[1] = -1 * std::numeric_limits::infinity(); + invalid[2] = (std::numeric_limits::has_quiet_NaN) ? std::numeric_limits::quiet_NaN() : 0.0f; + this->log_ACTIVITY_HI_FloatEv(invalid[0], invalid[1], invalid[2], invalid); + this->tlmWrite_Float1Ch(invalid[0]); + this->tlmWrite_Float2Ch(invalid[1]); + this->tlmWrite_Float3Ch(invalid[2]); + this->tlmWrite_FloatSet(invalid); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} } // end namespace Ref diff --git a/Ref/TypeDemo/TypeDemo.fpp b/Ref/TypeDemo/TypeDemo.fpp index 2c5a6b0c93..5dc57da482 100644 --- a/Ref/TypeDemo/TypeDemo.fpp +++ b/Ref/TypeDemo/TypeDemo.fpp @@ -33,6 +33,9 @@ module Ref { choicePair: ChoicePair } + @ Set of floating points to emit + array FloatSet = [3] F32; + @ Component to demonstrate multiple type configurations passive component TypeDemo { ##### @@ -192,6 +195,28 @@ module Ref { @ Dump the typed parameters sync command DUMP_TYPED_PARAMETERS() + ##### + # FloatSet outputs + ##### + @ A set of floats in an event + event FloatEv(float1: F32, float2: F32, float3: F32, floats: FloatSet) severity activity high \ + format "Floats: {} {} {} as a set: {}" + + @ Float output channel 1 + telemetry Float1Ch: F32 + + @ Float output channel 2 + telemetry Float2Ch: F32 + + @ Float output channel 3 + telemetry Float3Ch: F32 + + @ Float set output channel + telemetry FloatSet: FloatSet + + @ Dump the float values + sync command DUMP_FLOATS() + # ---------------------------------------------------------------------- # Special ports # ---------------------------------------------------------------------- diff --git a/Ref/TypeDemo/TypeDemo.hpp b/Ref/TypeDemo/TypeDemo.hpp index 23c251fde3..d3e05d1205 100644 --- a/Ref/TypeDemo/TypeDemo.hpp +++ b/Ref/TypeDemo/TypeDemo.hpp @@ -111,6 +111,12 @@ class TypeDemo : public TypeDemoComponentBase { void DUMP_TYPED_PARAMETERS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ const U32 cmdSeq /*!< The command sequence number*/ ); + + //! Implementation for DUMP_FLOATS command handler + //! + void DUMP_FLOATS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ + const U32 cmdSeq /*!< The command sequence number*/ + ); }; } // end namespace Ref From eabd5a585d3efa93ad04461d4b39faa0b2baabb8 Mon Sep 17 00:00:00 2001 From: Michael Boehm <130305277+Boehm-Michael@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:01:10 -0700 Subject: [PATCH 07/12] Cross-Compiler Documentation Update + Minor CodeQL Alert Resolutions (#2098) * Updates to resolve "Complex multi-line /.../-style comment found. Lint may give bogus warnings. Consider replacing these with //-style comments, with #if 0...#endif, or with more clearly structured multi-line comments." alert. Fixed all - Except external libraries * Resolve "Storage-class specifier (static, extern, typedef, etc) should be at the beginning of the declaration." alert. All resolved. * Updated F Prime Cross Compilation Documentation Correct for most recent version of devel. * Spelling Fixes * Remove URL Pattern * Added `rancherdesktop` to expect.txt * cross-compile tutorial re-organization * add macos.md and add step for running executable * clean up command to run executable * change [...] to <...> --------- Co-authored-by: Justine West --- .github/actions/spelling/expect.txt | 6 + .github/actions/spelling/patterns.txt | 8 +- Ref/TypeDemo/TypeDemo.hpp | 60 ++++----- Svc/ComStub/test/ut/Tester.hpp | 10 +- Svc/TlmChan/test/ut/Tester.hpp | 16 +-- config/UdpReceiverComponentImplCfg.hpp | 2 +- docs/Tutorials/CrossCompilation/Tutorial.md | 118 ------------------ .../CrossCompilationSetup/ArmLinuxTutorial.md | 45 +++++++ .../CrossCompilationSetupTutorial.md | 59 +++++++++ .../CrossCompilationTutorial.md | 52 ++++++++ docs/Tutorials/CrossCompilationSetup/Linux.md | 10 ++ docs/Tutorials/CrossCompilationSetup/MacOS.md | 29 +++++ .../CrossCompilationSetup/Windows.md | 44 +++++++ .../CrossCompilationSetup/appendix-1.md | 42 +++++++ .../img/rancher-config.png | Bin 0 -> 56116 bytes .../img/rancher-running.png | Bin 0 -> 148236 bytes 16 files changed, 330 insertions(+), 171 deletions(-) delete mode 100644 docs/Tutorials/CrossCompilation/Tutorial.md create mode 100644 docs/Tutorials/CrossCompilationSetup/ArmLinuxTutorial.md create mode 100644 docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md create mode 100644 docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md create mode 100644 docs/Tutorials/CrossCompilationSetup/Linux.md create mode 100644 docs/Tutorials/CrossCompilationSetup/MacOS.md create mode 100644 docs/Tutorials/CrossCompilationSetup/Windows.md create mode 100644 docs/Tutorials/CrossCompilationSetup/appendix-1.md create mode 100644 docs/Tutorials/CrossCompilationSetup/img/rancher-config.png create mode 100644 docs/Tutorials/CrossCompilationSetup/img/rancher-running.png diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 690053110e..2d27bda491 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -103,6 +103,7 @@ bfree bibtex Bies bindir +binrel Bitfields bitmaps bitset @@ -602,6 +603,7 @@ Gnc Gnd gnd GNUC +gnueabi gnueabihf google googletest @@ -766,6 +768,7 @@ kislyuk kitware Kooi kthxbye +Kubernetes Lammert lammertbies LASTLOG @@ -914,6 +917,7 @@ namespaced nano nanosleep nargs +nasafprime nathan nbits ncsl @@ -1144,6 +1148,7 @@ RAbrack radd RAII Ramanan +rancherdesktop randtbl rapidscat raspberrypi @@ -1646,6 +1651,7 @@ WRONLY wrs Wshadow Wsign +wsl WSL Wundef www diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index edd3969da1..e65d460bfc 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -2,7 +2,10 @@ # hit-count: 106 file-count: 28 # Compiler flags -(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +(?:^|[\t ,"'`=(])-(?:[DLP](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) + +# http|ftp|file URLs +#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] # hit-count: 48 file-count: 18 # GitHub SHAs (markdown) @@ -123,3 +126,6 @@ GithubProjectProperty # ignore long runs of a single character: \b([A-Za-z])\g{-1}{3,}\b + +# ignore docker platform paths +--platform=(linux|darwin)/(amd64|arm|arm32v5|arm32v6|arm32v7|arm64v8|i386|ppc64le|s390x|x86_64) diff --git a/Ref/TypeDemo/TypeDemo.hpp b/Ref/TypeDemo/TypeDemo.hpp index d3e05d1205..9d2cd55335 100644 --- a/Ref/TypeDemo/TypeDemo.hpp +++ b/Ref/TypeDemo/TypeDemo.hpp @@ -19,12 +19,12 @@ class TypeDemo : public TypeDemoComponentBase { //! Construct object TypeDemo //! - TypeDemo(const char* const compName /*!< The component name*/ + TypeDemo(const char* const compName //!< The component name ); //! Initialize object TypeDemo //! - void init(const NATIVE_INT_TYPE instance = 0 /*!< The instance number*/ + void init(const NATIVE_INT_TYPE instance = 0 //!< The instance number ); //! Destroy object TypeDemo @@ -38,78 +38,70 @@ class TypeDemo : public TypeDemoComponentBase { //! Implementation for CHOICE command handler //! Single choice command - void CHOICE_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void CHOICE_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number Ref::Choice choice); //! Implementation for CHOICES command handler //! Multiple choice command via Array - void CHOICES_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void CHOICES_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number Ref::ManyChoices choices); //! Implementation for CHOICES_WITH_FRIENDS command handler //! Multiple choice command via Array with a preceding and following argument - void CHOICES_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void CHOICES_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number U8 repeat, Ref::ManyChoices choices, U8 repeat_max); //! Implementation for EXTRA_CHOICES command handler //! Multiple choice command via Array - void EXTRA_CHOICES_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void EXTRA_CHOICES_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number Ref::TooManyChoices choices); //! Implementation for EXTRA_CHOICES_WITH_FRIENDS command handler //! Too many choices command via Array with a preceding and following argument - void EXTRA_CHOICES_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void EXTRA_CHOICES_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number U8 repeat, Ref::TooManyChoices choices, U8 repeat_max); //! Implementation for CHOICE_PAIR command handler //! Multiple choice command via Structure - void CHOICE_PAIR_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void CHOICE_PAIR_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number Ref::ChoicePair choices); //! Implementation for CHOICE_PAIR_WITH_FRIENDS command handler //! Multiple choices command via Structure with a preceding and following argument - void CHOICE_PAIR_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ + void CHOICE_PAIR_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number U8 repeat, Ref::ChoicePair choices, U8 repeat_max); //! Implementation for GLUTTON_OF_CHOICE command handler //! Multiple choice command via Complex Structure - void GLUTTON_OF_CHOICE_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ - Ref::ChoiceSlurry choices /*!< - A phenomenal amount of choice - */ + void GLUTTON_OF_CHOICE_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number + Ref::ChoiceSlurry choices //!< A phenomenal amount of choice ); //! Implementation for GLUTTON_OF_CHOICE_WITH_FRIENDS command handler //! Multiple choices command via Complex Structure with a preceding and following argument - void GLUTTON_OF_CHOICE_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq, /*!< The command sequence number*/ - U8 repeat, /*!< - Number of times to repeat the choices - */ - Ref::ChoiceSlurry choices, /*!< - A phenomenal amount of choice - */ - U8 repeat_max /*!< - Limit to the number of repetitions - */ + void GLUTTON_OF_CHOICE_WITH_FRIENDS_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq, //!< The command sequence number + U8 repeat, //!< Number of times to repeat the choices + Ref::ChoiceSlurry choices, //!< A phenomenal amount of choice + U8 repeat_max //!< Limit to the number of repetitions ); //! Implementation for DUMP_TYPED_PARAMETERS command handler //! Dump the typed parameters - void DUMP_TYPED_PARAMETERS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq /*!< The command sequence number*/ + void DUMP_TYPED_PARAMETERS_cmdHandler(const FwOpcodeType opCode, //!< The opcode + const U32 cmdSeq //!< The command sequence number ); //! Implementation for DUMP_FLOATS command handler diff --git a/Svc/ComStub/test/ut/Tester.hpp b/Svc/ComStub/test/ut/Tester.hpp index 6f794d6354..e15c2479d4 100644 --- a/Svc/ComStub/test/ut/Tester.hpp +++ b/Svc/ComStub/test/ut/Tester.hpp @@ -57,21 +57,19 @@ class Tester : public ComStubGTestBase { //! Handler for from_comDataOut //! - void from_comDataOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ + void from_comDataOut_handler(const NATIVE_INT_TYPE portNum, //!< The port number Fw::Buffer& recvBuffer, const Drv::RecvStatus& recvStatus); //! Handler for from_comStatus //! - void from_comStatus_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::Success& condition /*!< - Status of communication state - */ + void from_comStatus_handler(const NATIVE_INT_TYPE portNum, //!< The port number + Fw::Success& condition //!< Status of communication state ); //! Handler for from_drvDataOut //! - Drv::SendStatus from_drvDataOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ + Drv::SendStatus from_drvDataOut_handler(const NATIVE_INT_TYPE portNum, //!< The port number Fw::Buffer& sendBuffer); private: diff --git a/Svc/TlmChan/test/ut/Tester.hpp b/Svc/TlmChan/test/ut/Tester.hpp index 6b61ec2ce7..7370ed4b23 100644 --- a/Svc/TlmChan/test/ut/Tester.hpp +++ b/Svc/TlmChan/test/ut/Tester.hpp @@ -42,21 +42,15 @@ class Tester : public TlmChanGTestBase { //! Handler for from_PktSend //! - void from_PktSend_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::ComBuffer& data, /*!< - Buffer containing packet data - */ - U32 context /*!< - Call context value; meaning chosen by user - */ + void from_PktSend_handler(const NATIVE_INT_TYPE portNum, //!< The port number + Fw::ComBuffer& data, //!< Buffer containing packet data + U32 context //!< Call context value; meaning chosen by user ); //! Handler for from_pingOut //! - void from_pingOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - U32 key /*!< - Value to return to pinger - */ + void from_pingOut_handler(const NATIVE_INT_TYPE portNum, //!< The port number + U32 key //!< Value to return to pinger ); private: diff --git a/config/UdpReceiverComponentImplCfg.hpp b/config/UdpReceiverComponentImplCfg.hpp index f09ee10e07..4b69dd24eb 100644 --- a/config/UdpReceiverComponentImplCfg.hpp +++ b/config/UdpReceiverComponentImplCfg.hpp @@ -11,7 +11,7 @@ #include namespace Svc { - const static NATIVE_UINT_TYPE UDP_RECEIVER_MSG_SIZE = 256; + static const NATIVE_UINT_TYPE UDP_RECEIVER_MSG_SIZE = 256; } #endif /* SVC_UDPRECEIVER_UDPRECEIVERCOMPONENTIMPLCFG_HPP_ */ diff --git a/docs/Tutorials/CrossCompilation/Tutorial.md b/docs/Tutorials/CrossCompilation/Tutorial.md deleted file mode 100644 index 75890b32b8..0000000000 --- a/docs/Tutorials/CrossCompilation/Tutorial.md +++ /dev/null @@ -1,118 +0,0 @@ -# F´ Cross-Compilation Tutorial - -## Table of Contents - -* 1. Introduction - * 1.1. Cross-Compiling - * 1.2. Upload to the Raspberry Pi - * 1.3. Running Ref on the Raspberry Pi - * 1.4. Setting A Default Toolchain -* 2. Conclusion - -## 1. Introduction - -In this section, we will take our Ref topology and cross-compile it for the -Raspberry Pi. In order to fully benefit from this tutorial, the user should -acquire a Raspberry Pi and have the cross-compilation toolchain as described -in [RPI](https://github.com/nasa/fprime/blob/master/RPI/README.md). - -This part of the tutorial requires the user to have gone through the previous -sections and have a complete Ref topology. The user should also have an -understanding of the Raspberry Pi and specifically how to SSH into the Pi and -run applications. - - -### 1.1. Cross-Compiling - -In order to cross-compile for a specific architecture, the user needs to -generate a new build directory using a CMake toolchain file for that specific -architecture. F´ includes a toolchain for the Raspberry Pi (assuming that the -tools are installed correctly). This toolchain is called “raspberrypi”. The -cross-compilation build for the Raspberry Pi can be generated by running the -following commands in the Ref directory: - -```sh -fprime-util generate raspberrypi -``` - -followed by: -```sh -fprime-util build raspberrypi -``` - -This will generate the binary at `Ref/build-artifacts/raspberrypi/bin/Ref`. - - -### 1.2. Upload to the Raspberry Pi - -The user can then run the ground system as before with some additional -arguments: -```sh -fprime-gds -n --dictionary build-artifacts/raspberrypi/dict/RefTopologyAppDictionary.xml -``` - -Assuming that there is no firewall or other network limitations between the -Raspberry Pi and the host, the user can then copy the binary to the Raspberry -Pi by running the following from the Ref directory: - -```sh -scp build-artifacts/raspberrypi/bin/Ref pi@:~ -``` - -This will use the secure copy protocol (scp) to copy the executable over to -your Raspberry Pi's home directory. - - -### 1.3. Running Ref on the Raspberry Pi - -You can log into the Raspberry Pi via SSH by running: -```sh -ssh pi@ -``` - -Finally, you can run the Ref deployment on the Raspberry Pi as follows: -```sh -./Ref -a -p 50000 -``` - -Now the Raspberry Pi should be powered up and running the Ref deployment and -our host system should be running the ground system. - -> If the code worked when running natively but it isn't connecting for this -example then the cause is likely a firewall or network issue. Make sure -port 50000 is exposed to the Pi, and that the Pi can ping the ground system -machine. - - -### 1.4. Setting A Default Toolchain - -As we have seen in the previous sections, cross-compilation builds can be done -explicitly by setting the toolchain. However, some users may wish to make a -particular toolchain the default and don't want to specify it every time. - -A recommended way of setting a default toolchain in F´ is by adding a -[settings.ini file](../../UsersGuide/user/settings.md). This file provides -build-time configuration settings for F´ and can be used to set a default -toolchain as follows: - -```ini -[fprime] -; ... other options in file ... -default_toolchain: raspberrypi -``` - -Now the "raspberrypi" build can be created with a call to -`fprime-util generate` and the original native build can be made by explicitly -setting the native toolchain: `fprime-util generate native`. - -### 2. Conclusion - -The Cross-Compilation tutorial has shown us how to cross-compile our simple Ref -application to the Raspberry Pi. We have seen how to copy our deployment to the -Raspberry Pi and run the ground system on our host computer to interact with -our deployment on the Raspberry Pi. - -The user is now directed back to the [Tutorials](../README.md) for further -reading or to the [GPS Tutorial](../GpsTutorial/Tutorial.md) for a more -advanced tutorial. For information on porting F´ to a new platform please -refer to the [User's Guide](../../UsersGuide/guide.md). \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/ArmLinuxTutorial.md b/docs/Tutorials/CrossCompilationSetup/ArmLinuxTutorial.md new file mode 100644 index 0000000000..94adf9111a --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/ArmLinuxTutorial.md @@ -0,0 +1,45 @@ +# F´ Running on ARM Linux Tutorial + +For this tutorial, the assumption is that the ARM Linux machine is available on the network, is running SSH, and the username, password, device address, and host address are known. Without this configuration, users should skip to the next section of the tutorial. + +First, in a terminal upload the software to hardware platform. This is done with: + +```sh +# For ARM 64-bit hardware +# In: Deployment Folder +scp -r build-artifacts/aarch64-linux @:deployment + +# For ARM 32-bit hardware +# In: Deployment Folder +scp -r build-artifacts/arm-hf-linux @:deployment +``` +> Users must fill in the username and device address above. + +Next run the F´ GDS without launching the native compilation (`-n`) and with the +dictionary from the build above (`--dictionary ./build-artifacts//<.xml document>`). + +```sh +# For in-person workshops and ARM 64-bit hardware +# In: Deployment Folder +fprime-gds -n --dictionary build-artifacts/aarch64-linux/dict/.xml + +# For ARM 32-bit hardware +# In: Deployment Folder +fprime-gds -n --dictionary build-artifacts/aarch64-linux/dict/.xml +``` + +In another terminal SSH into the device and run the uploaded software: +```sh +ssh @ +sudo deployment/bin/ -a -p 50000 +``` +> User should fill in the username and device address above and ensure the executable is supplied the address of the host computer (that ran the GDS). + +> If the device does not connect, ensure that the firewall port 50000 is open on the host computer. + +## Troubleshooting + +If you are getting errors for missing Libc.c files, make sure when you generate +that the logs show that it is using the `/opt/toolchains` path and not `/bin`. +You can additionally verify that the correct toolchain is being used by watching +the logs scroll by when you initially `fprime-util generate `. \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md b/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md new file mode 100644 index 0000000000..e6e047c315 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md @@ -0,0 +1,59 @@ +# F´ Cross-Compilation Setup Tutorial + +## Table of Contents + +* 1. Introduction + * Prerequisites +* 2. Installing Dependencies +* 3. Installing the Toolchain + + +## 1. Introduction + +In this section, we will learn how to install all the dependencies required for cross-compiling for different architectures. +This tutorial will use the Raspberry Pi ARM x64 as an example. In order to fully benefit from this tutorial, the user should acquire a Raspberry Pi. + + +### Prerequisites + +To run through this tutorial, you must have a computer that meets the following basic requirements. + +1. Computer running Windows 10, Mac OS X, or Ubuntu +2. Administrator access +3. 5GB of free disk space, 8 GB of RAM +4. Knowledge of the command line for your operating system (Bash, Powershell, Zsh, etc). + + +## 2. Installing Dependencies + +Choose the operating system you are using to install F Prime: + +- [Windows 10/11 WSL](./Windows.md) +- [Mac OS X](./MacOS.md) +- [Ubuntu 20.04 / 22.04 / Generic Linux](./Linux.md) + + +## 3. Installing the Toolchain + +Installing the cross-compiler will use the pre-built packages provided by ARM. Follow these +instructions to install these tools for the target hardware into the `/opt/toolchains` directory. + +```bash +sudo mkdir -p /opt/toolchains +sudo chown $USER /opt/toolchains +# For in-person workshops, and users running on 64-bit ARM +curl -Ls https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz | tar -JC /opt/toolchains --strip-components=1 -x +# For users running on 32-bit ARM +curl -Ls https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf.tar.xz | tar -JC /opt/toolchains --strip-components=1 -x +``` + +Next, ensure that the ARM toolchains were installed properly. To test, run the following command: +```shell +# For 64-bit ARM hardware +/opt/toolchains/bin/aarch64-none-linux-gnu-gcc -v +# For 32-bit ARM hardware +/opt/toolchains/bin/arm-linux-gnueabi-gcc -v +``` + Any output other than "file/command not found" is good. + +> Note: macOS users must run these commands from within the Docker container described in [Appendix I](./appendix-1.md). diff --git a/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md b/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md new file mode 100644 index 0000000000..92c4dfeb75 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md @@ -0,0 +1,52 @@ +# F´ Cross-Compilation Tutorial + +## Table of Contents + +* 1. Introduction +* 2. Compiling for ARM +* 3. Troubleshooting + + + +## 1. Introduction + +In this section, we will learn how to cross-compile for different architectures. This tutorial will use the Raspberry Pi ARM x64 as an example. In order to fully benefit from this tutorial, the user should acquire a Raspberry Pi. + +The user should also have an understanding of the Raspberry Pi and specifically how to SSH into the Pi and run applications. + + +### Prerequisites +Install the dependencies required for compiling for ARM. See the steps in the [Cross-Compilation Setup Tutorial](./CrossCompilationSetupTutorial.md) for more information. + + + +## 2. Compiling for ARM + +Cross-compiling is as easy as building the deployment for a specific platform. +For users running on 64-bit arm the platform is called `aarch64-linux`, and for users +on 32-bit arm use `arm-hf-linux`. This package expects the environment variable +`ARM_TOOLS_PATH` to point to the installation directory of the ARM cross-compilers. + +> Users need to generate for each platform they wish to run on. + +Here is how to build for the 64-bit Arm Linux platform: + +```sh +export ARM_TOOLS_PATH=/opt/toolchains + +#You can check to make sure the environment variable is set by running: +echo $ARM_TOOLS_PATH + +#This should return the path /opt/toolchains + +# For in-person workshops and ARM 64-bit hardware +# In: Deployment Folder +fprime-util generate aarch64-linux +fprime-util build aarch64-linux + +# For ARM 32-bit hardware +# In: Deployment Folder +fprime-util generate arm-hf-linux +fprime-util build arm-hf-linux +``` +> Note: macOS users must run these commands from within the Docker container described in [Appendix I](./appendix-1.md). \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/Linux.md b/docs/Tutorials/CrossCompilationSetup/Linux.md new file mode 100644 index 0000000000..3ed3d1d514 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/Linux.md @@ -0,0 +1,10 @@ +# Ubuntu 20.04 / 22.04 / Generic Linux + +Ensure that your distribution is up to date. + +```sh +sudo apt update +sudo apt install build-essential git g++ gdb cmake python3 python3-venv python3-pip +``` + +After the steps above are completed, [return to the tutorial.](../CrossCompilationSetup/CrossCompilationSetupTutorial.md) diff --git a/docs/Tutorials/CrossCompilationSetup/MacOS.md b/docs/Tutorials/CrossCompilationSetup/MacOS.md new file mode 100644 index 0000000000..b76b78f971 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/MacOS.md @@ -0,0 +1,29 @@ +# Mac OS X + +MacOS like Linux is a unix system and thus may be used directly for most of this +tutorial. However, Mac users must install the following utilities +*and ensure they are available on the command line path*. + +1. [Python 3](https://www.python.org/downloads/release/python-3913/) +2. [CMake](https://cmake.org/download/) +3. GCC/CLang typically installed with xcode-select + +**Installing GCC/CLang on macOS** +```bash +xcode-select --install +``` + +Installing Python and running the above command to install gcc/CLang should ensure +that those tools are on the path. + +CMake requires one additional step to ensure it is on the path: + +```bash +sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install +``` + +In order to cross-compile, a Linux box is essential. You may choose to use a virtual +machine or may choose to follow the instructions in [Appendix I](./appendix-1.md) to +install a docker container including the necessary tools. + +After the steps above are completed, [return to the tutorial.](../CrossCompilationSetup/CrossCompilationSetupTutorial.md) \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/Windows.md b/docs/Tutorials/CrossCompilationSetup/Windows.md new file mode 100644 index 0000000000..1f6e876c5c --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/Windows.md @@ -0,0 +1,44 @@ +# Microsoft Windows 10/11 WSL + +Windows 10 ships with a technology known as WSL. WSL allows users to run Linux virtual machines transparently within the Windows 10 operating system. + +**Powershell: Install WSL with Default Ubuntu** +```powershell +wsl --install +``` + +To start Ubuntu under WSL, search for Ubuntu in the start menu and select the "Ubuntu on Windows" app. All class commands should be run on these Ubuntu terminals. + +> Full instructions and troubleshooting help is available in the +> [Microsoft documentation](https://learn.microsoft.com/en-us/windows/wsl/install). + +Lastly, Windows users must open up a firewall port and forward that port to WSL to +ensure the hardware can call back into F' ground data system running in WSL. First we'll +need to note the IP address of the WSL machine. This is done with the following +command *in an administrator PowerShell*. + +```powershell +wsl hostname -I +``` + +> Record the output of this command for the next step. For this guide, we will use +> the value `127.0.0.1`. + +Next, we will add a firewall rule and forward it to the WSL instance. This is done with the following commands: + +> Warning: these commands work with the Windows firewall. Security and antivirus tools +> can run extra firewall rules. Users must allow the port `50000` (Or whichever port that +> is going to be used) or disable these extra firewall settings. + +**PowerShell: Add and Forward External Firewall Rule** +```PowerShell +New-NetFirewallRule -DisplayName "fprime" -Direction inbound -Profile Any -Action Allow -LocalPort 50000 -Protocol TCP +``` + +> Remember to change `127.0.0.1` to your recorded ip address as discovered with +> `wsl hostname -I`. Users are advised to remove this rule after the class has been completed. + + +**IMPORTANT:** + +Go to the [Ubuntu 20.04 / 22.04 / Generic](./Linux.md) Linux to finish setting up your WSL environment. diff --git a/docs/Tutorials/CrossCompilationSetup/appendix-1.md b/docs/Tutorials/CrossCompilationSetup/appendix-1.md new file mode 100644 index 0000000000..4f4d937a83 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/appendix-1.md @@ -0,0 +1,42 @@ +# Appendix I: Installing Rancher Desktop and the F´ ARM Container + +Some users may with to run cross-compilers within docker to minimize the impact of those tools on their systems. Macintosh users will be required to use docker as the ARM/Linux cross-compilers are not available natively for macOS and simple virtualization of a Linux box is no longer practical since the introduction of M1 and M2 hardware. + +## Rancher Desktop Setup + +Rancher Desktop is an alternative to Docker Desktop that allows users to run docker containers directly on their desktop computer. It does not require a license for use like Docker Desktop does and also supports both intel and ARM based Macintosh computers. + +> Non-Macintosh users are advised to run without the below Docker container + +To install [Rancher Desktop](https://rancherdesktop.io/), follow the instructions for your operating system. When presented with a "Welcome to Rancher Desktop" dialog, choose the following settings: +1. Disable Kubernetes +2. Select `dockerd` +3. Configure PATH Automatic + +![Rancher Config](./img/rancher-config.png) + +Ensure that Rancher Desktop is running and that the VM it uses has been started. You can confirm this by ensuring no pop-ups nor progress bars are visible in Rancher Desktop's main window as shown below. + +![Rancher Main Window](./img/rancher-running.png) + +Once this is done, users can install the container by running the following command in their host terminal. It should complete without errors. + +```bash +docker pull nasafprime/fprime-arm:latest +``` + +## Running The Container + +In order to run the commands provided by the docker container (i.e. the cross-compilers), users must start the container and attach to a terminal inside. This should be done **after** the user has created a project to work within. + +To run this container, users may wish to download [this script](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/bin/macos-docker) to a `bin` directory in the root of their project. This will start the docker container with appropriate settings. + +Alternatively, the user may run the following command to start the terminal +```bash +docker run --platform=linux/amd64 --net host -e USER=$USER -u "`id -u`:`id -g`" -v "/path/to/project:/project" -it \ + docker pull nasafprime/fprime-arm:latest +``` + +> Anytime Macintosh users run cross-compilation commands, they **must** do so in a terminal inside the docker container. + +[Return to Macintosh tutorial.](./MacOS.md) \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/img/rancher-config.png b/docs/Tutorials/CrossCompilationSetup/img/rancher-config.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6b761fcb75fbee0a6c7500fb81b0fe46be996f GIT binary patch literal 56116 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

k)Jmx@3{#{^Y2N z%@DO}O;m4Lo#63vDKN&Xx9muiG1}JIMxlUUyggBZQT$N!IUIDx-*y{57Ha+R&Wrsn zJkb4aNDFP@`1WMapC3KwdJZa2D1s;*r*ZdH{+!)m5xXb2dDF{==#DNDbJ}oBK9r+( zfXaUt(QLJjxGHN!pU9qs5BXI?mQtEPS#NDEU)5@xKG8B$9sy&pfhb711hQ~(S*`(=fzy-kfbVpgEnw4#DDa3G z`BN&6_RaO8c$j+1p{Igqi!2^L%QnAu*XZ`f7wTEN*bky}?MHK!6icQ$v5Lc4Y&dWV z3qxQVT-n4DeiK{c$5~dX)tQhqL7AMGhyvq9ff<;7LMMghT^N|> zSls(>(F80hM*vJZH9vqLp0Fq@B0WA3>5t^%4$#qt66jlJ4x?92!sRg^$4G*>3AdU@dD*WqOS` zg5CcP{Ghq)JHE|pv{>#UcjfzesmoSfZq+Wqv-xFE6v!nNw0q>PIU4Ql_6);u?yOe7 z%F>Ao-UDRCt!9w|!%JndBXD5W!9@6C;#k&Gx}OmU`IO4}`B-=WywZ9~a-R2R9CF{T zmu^Fso4hwf*dNYUd(zL|E;hd{QB&-?B0`0e7f5Ha9kQMAcDNdUc^VXrg~`0%*>rPv zUl3snY87!#zCd>@^532>j!ecS>NPJW;_h!UU%2u*jKN>O_WE5i<)fEHd|<#w$89yK z9DGXLHD7I!i~^CWKPdV#fw_%aLcZ1e>6bt*X@QChO!G~FMfJiE_wC+hIvMECWgZhu z&R=PpvOO;V2xcZy)KfxyW7cC4LV&QH^yaocVcCQ{jbj=+G~q_dd*Mk3b_fC$p(=N3e6arhF} zM>YwYExqLxd=iv#;D6_jvJSck`2~x<RVsm2Ni z6O%zVB)B_-f8v@XF0HbAG0d9G_X`!{q4S_d4Fcb2Z0^o;*6f}z zNr=qXNwiDE0I02Ga*cBM1Z=jQ&HO1Lg^T%S(YikrCe*&Aoz%pm)ds^fgweO8**W28 zXhTjZO%~(`=+m(Tye^!^ta|WlOgut+^&)2IdhoOE0rtMILOJP6Du!|BOi~U(eRWs; zYP7Tz{gjG|nR+nu!rw&aUCHR2em)84^meFY5!dzN5<)UW?NOc`fv&$%7i) z9%0@mH^!CR#_)S+WinqvNHotkGze0DC6>FF*Cru@npj86wTQ+Ns=ra>`}EzIAyaG= zMw7hpk{5H|zN^z}+I1K*1!QW}U!m}J#{HvZr$zcu!JSrIfh_0?KH(O|>g#@2(WU7QAI!OAt#LpNKYmlHTA|NerRR&*@ zi-KeuH@^xSvlT1+Bd0m>L1A)%6vN_4aU#S&d54`o1R%)uML`CurWRfLFGUMlO6>RC zF0&myW?q~a^dIin9wNY)JRx#LJ%aKtJuVRUm*fSqCxSAPsDE7qcQ%ATgr96HM-(*w zk>XiFAb9avNhxHcf6MoA1p8sf_mr>DCu0Cho05RQqDROP|0@XrI*JnKo)=?C!St`K zoo@5tFfs(ee{8V;kzai$U{(NxQvRP&?&#vDhMPImzrjF2fdbjVadbwqzij|%d~7hD zAS~K0!#r`be^s-B83eD0{Z#%pDrl6rcL96|semtk%jTgV!zz&ytNttZRiMvR)B~DC zOzyvWqzhq0P}T(T?<~O2?~fQE#1&ZuaMryxV1AmAY(KoFt^=z9vj51W3dntlWxyJ* ztVwrvCr?GAu*3fb+`znIF+iV=vGMb27u?;`a}LNwpDb0TPEYF0|6}BLB1v{wq2LbF zDWo+z9FH)yJ6)3dsOV9!EvwY(IsX79I91H@mb686AFNbKB<>Osg#4iA8@;0j%qI(` zL`bek+|DmWKYT4?sA`)V)^1ozQt2&SJZ>KlvcI`5AJaYlosa?;&S-z38;D!4r>p#_ zih%l4K$zpfxUugpZBp&`W{aiUBgD{@>I@C1)|GmzHMk^NRf0>Nb;g`lBPy6S9xw?r2VC&sI{bvv?z^XJLy0s%{nXAx>Zhpd8hat%;+lHcKsJ3A76Rwc1gqohL} zNpw|=as{*Q_vC2L#M;Vk_4)Pm7JoovA@Nj>QWn3JD&NA_vnD2GB8L?mE|B@DItPvh%uov=pq?@5RN&A^~IbG?VEJ31IBe3a5Fda!zeNZ^UXk z4bScS(a6^*dkIPx9z(#AwI>pdn=ArPDCs-%8t8Jq-ft66Dq6zV{i+y&Ufw3W-eHaQ zemleS(5Q^LNGX%mrnM3P3Fip3JC(|ljxJ=d(U!YiE1>gwIC(tZKuV``#{<#Ll(|8e zPPGL>aZA{wJ&?i?0dn~z4aVb&$uvt`lUKXF3!_Mc<(D(OPwn=5he?#Kd~jF{Sp%=> z6-Q3XIHM65nH^3S`S9VeIF%ErjPpa{hRQhxbVfV-l#9eW^9yMeUaHv{?ANUCKsqZ% zSD@``_c;(6Jd4>FY)Ti9`g=4|cALX$;KTiPzpyTXNk&e}{w0bUox_I5V!0~#UW=^Ya-VAP7hbE&w$?$F2H-Cy(`{RGtr6O4~weL;e@&lZ>tx zT}=kV3hUjbr7{t3b<+#r@f%_2%qK}k_>zB%k8Co?F=IPCu{hPQ2* zKvicF0IdF5bC|us_+(l@D4a*aW2aLtlS}`2>{=j5&?M)aR&3sDgzhSXB%^X(4z`tz!V%6t74Fo$b)*GsV?b$Bh zK}X=QP*l=|ym9+6Xm^yQ-Qa1nwZnns!-2vW$lwh?Lo?T<1kRZ|xFQLQupO`37pheR z4R=|k!vQsR4u~CsCx5PLVds^8e;2x8k}KHrwa&;(AhjYn9Fe@^2~ZJR!dkQnTP$Cg2QroF zaFBsnOpb7yJ>HQBHlye)ODym=Wz#3ud!ly|;%C71sc6Ae2IUt}m^k1ZI?Z7iBGqL1 zh^WZR$?}W>A%q89rd@X>^vkTqBy7unVrPgb3M4> z8e3aAhNIDB`7>&;Q3K~oC-fXVKi+V(6@(QfgPe%mm&z4&%Q0zyxe#l!5`%!4IQiJO z-N4vWz!r7#cX%}LM&*=N3pjKo>=u{xFo7C$ST~uEf$PS1}G7YF&FY2rU#~PL#(EpkP+mGyW1yd@e=) zBb(PXguCfd3Yh(9(U6`*xcAvTpCLrWHe3F4QiK!RD}ebGnDGm}ufrx8^b6YMIUXEE z3VLpMKn=Oo(|9EORSNxh>BuTNVL)3OibP0tFXC04ag{t`9UM$ZrgZF6tKB)!AuYMf z1o?&gxeU(aE2v(kO;HgWvVN9dXwA?nlguiT_sS6nY4;rzkV*#9XFtH;SmeigybL zRKUFDxx1}cqFt2or>a7OY=n{lfC>Hpou8)*ISz)yFmCOZzyRU3Yj?m{t4raq1wFl2 zk*WgBC+>rTebGl5MfHAa zS}&_;K+Ig|S9zVvep=(a8}1s8!00W+S@<-XczM1(N`lB3M@N(t%YUrW>BdBg&L<2i zUe6+O7Mg)8l-%wCU&E{L(_&%SZb4ZRO}H? zAX82#x9y6h`FMwZT%7HtXK@zubo|T_=0M+rT+_rvv^p3q>*+)GyiV|K69!3y&}R?K z@$7Wn29np+n9UYzF~u`165Cv|I7uW_7~y;oMzq_#r8Po$4S&XkuhGauvtZ&%+eHwX7gxnAP{o z5F3YB2qF>Pm^wNPg|s1gRYN`6K2Mdr_}uOOG3;=0@TEiYa&)0p3?fXU2MYMi#;yC>kIFTWc^T zZ;goXCvXJ=|MA}c-IygFc@?$j=U+Gh09sA#w-jXdWAd*tzF2PHGo*k22mkt(Q1nO> zDzOyC|KUSFT9P~~EQg{{+8%7`L?CWz;{%Hm(1%CWr&6+pCcf|2-lJbg*{ z4GV;WmLz`CRK~c8mOXgX$^Wr3BP|Z=#-Ymsv7;6q=$h0}^LA{K%-=KYzh}X^G5^pq z&{m2Y?=|ydbnQ4rxpFF#O&)saz{UD|RT5&SY^(X^e8gp{YfA`|gu#StD!Tr9tF3TZ zs@yC}*jFypgUNK#V_0!GVmQ!=ECyudGM#Q3?OvC;JKwz*a&G%KWHy_(Zt7;iz!$|= z7i;Mm;*%2d#*w?*neITXeJAsUgi_N+qEhfGEc&b?%505h3uHUpUYle>S4kw!^d{HZ zoW(prUZ6B!cuIZMD|%p$>I`Gzvlx{;!Z-R4`Alt)-vV|S!)JxEWtrz=uGwbCJvy}l z*^Bitqf&>H`1$*kE-!DU!U_XFHzagn5$u@HpUc8ypy)!W*h0Fr=(8Bjo`PMs;}1|x zS^(T}fYG)C+)3)lhvngs6BJOJit;zzY%$kB?|d>Jc=ixzdMmIpx4FqXYp=~@0MzRI zp-CaVm&qUtPY^k}of|wDS9KU4%Xh)JQ6v%_x1iPbtglZNv3th(Ml0zag~Jkavrqq> z!)B{#pWY@P1(c(i)Z1>^@)>TsaT&NH8Z%{HYu~87#!`eIzd&zAld?^a?1t$-~OZmwe0H+?!2%1RGprW?7;U2 znyIC|S=7o=Yqa=gfQjh8NesUEdJJ|TkY-iQ#`gv0rKiE~&DQ1&al;Bv7#a0zn>af#FRTRWT2 zc`?X#IO0IsT*LEdxvoCgafxPoZjFVKlW_l=;|RJUUt2oIrL2u^$D|#tZVg>(z0k7* z`*wb*LL&&$akxL}DH29tb4y@LB(vE-+1YVCc$I?SFKbN>1)xrN4bQxA6XDewrqxr= z_i0u~B88RuwGhE-yCwX3@nmd?4FzV|T&hq6;Yj0hqjg*Nd@hXJ^;E3W<($}=omV=G zGgzb5LU6YDch^G*!r7efCw(Cl0^Y*&EmoO%qDW9|x9DR{(l=Lz)A?p-3|buxrdVf- z)t4XyN=p^SLNakwMIZ&NI4nVf@0{|D&x5pBHaEsOu2QACbf&GZ4DM!f-IDvK@j?BTHs5}i##DTC7-j@RcchH)`17ywPD5VY-=BVCKbWZsG0uF;g5GY(P27*~fz8BMxsI@AQ)A{Gd@gmh) z0xp zP8ZM{F*`=1kUBi_`|X$~%MXOD_35hdB*DkWbA}SAcq2?QIXuP@!0c^xTG}^yjjo~n z5?h^a598*o5mqNRK&9*D){P-hZ`{T*=kuV0MW-f~NFjZ&1AL>XSiP}CzS&}1egqz8 zksa2i$$ZbmIXCD1@>jd$PtgS+T~q#jtl^Dd7i#272A?;kzq|i96$Pj{lF?aqVIk`% zMV@QG&2TtcC>+M5Y-H-2q{q-;dUZ1p!XHBGMO#j^#}8R}V#=55&}sPOu^d;Y4iDPfumB%@Jd- zzuIq|cKQT;Y~*@taQT{xgJ99*+U#~$>9wBhSiPR8#uK<$lq;0{dtPf$f!u;Dg7#TP z$mvsFlsUmS7cKFqT=E(q>OdnaA@nh;30b^Nvv)mTPW(L@JL3+4PUzpH(WkHq%)HhI z!Ig6XjHuDNaajr&Nu&mOr3Ll9euR*9mtL|!N)bw+s=@2p|Di{_5 zamPx?txzozxe1KuQ#zNe5lothizJbt_kiwGr|X4(ffhem1+!vQqDi2>CSvlR>`LOkpb(Vl?e(?4pjU;}pF-s2?_#QBONF zb463^ohW!t`Aes>8=YUHJf-l-_bY(k4Ty}`L2uXLKvMR}JP;hy`{xm6+J7q=%m1$$Qwe1fQchg}-_Pj^H)_&hab z2eO&>TZE5COzcjDXUsTp5&&mW>{|U(ty7;s$h217P6&LIFeeLOHu%}1afW04NhpVw zo}e)_4u2fV4om5uEY@>I$h;FPoITxI{y~UlO85 z7ck?JQggDg*?%vaMGbnx5chVZ4xKiy_>hYDP{XgBn5-q{ogW(7^x zTT{Kl#uMIKTEHE6ZU$D{kPNhm$UjW7f5LGppV0|n(eGwv_w(z*Zgl9y%_@lHG9d9k zy}x52OZ5AS&lU#^NNNrt&twPNi+G8!7n$+9VNV~#=uXv`pKYcZAP48rZgYzo^2cXQ ztDI*Zy2fYuLOLmI$~WLMjStmI?!CwuS8vu0f;VT05ODbeX9NI>l|?2C^o7CJ2hP*6 zYUW#{17m4(PA2d7yp3LO(-nbdLVj0a06wf)9LCmI!*due;VlDLuws-&b2yEV__8Bn zU%P>S0{rN&MuV16>|qZ#w^VomULW|;iSs&e>DDbP2E=DWfhaVphS6Lq!7dOjK6VVx z0-C{KH3nwO616gEEs0pjAjV;c7K@4Y2;OVMA%c$-C=Qn#Cr@yUU$Cby5+{IAz}@Dw zf1E}Av%1cOCQ^joiF4iUYq^mLg1K~J7%FA{5!cuG)Y>=0*~wd?$+>=wC6*Y-WBpF= z6Gc`|By&#~m}+$?BtOw17nGsWB#~B{lASF;R@7@uP+|SoL+B!dGfx<1N}u_=B$2*1 z2E^;=fIR+YB}ra1EsTotuQRbG0i%1K8`imem$F5O#mT_n*u^!4KQR4My5~f}P>XA+ zCVhUjNOu4`05oG|C@t9V2O80Tk3bNJWb4yp0{CNkr2kZFWKonZE4IIW!4t_;{h0qi zZm@yV4v$uz5_|xl-P|!(o?cDXS|R;U@l%OVW&9V?ysvcmdV5!xJaV1EGpE&~!Sj=-S9yea`|4ye?yOJqW!G2*o-q^}o< z8v{`&89y5G|7*mQP{0g&;9!t1yDI%P^g0lFXT@1F%X3Rt#dQWRxm?JXq2*ax}Af15XvLI5P2UbUkN<$sF$ zM{uo}FBQu6xpW8by;-rOZcCh3b(yLD=F4yY)$Qrlc6xFB z9vm7$`Zjb;A?8ZWK>TmXRu%)BzZj$ecb zguaEow*o*fU60*iGWgB;Wl`e2uflwZiUtpQIc=3qppe@IOAalNOP{3_W8V#X0!EX{ zZ{A)bnV-gAV6O{E#9&gh2-<%ajy0UFlu=>OX-`)a3q4O{s1(RrYHI2DjX1d8JaSOT zr6usb%y(o=1!#!g$%0} z*@-`lK0P7%bUSSiDF}Eto80-5%X##CJDvWq+U6$q_S!mapf3rlI9@ni|0hPW2-!s_ z$H#zHt4cWbk4<#%JGiXC6I1-TmQqAP2N(c#KtZ%+X4ZZ)`7j1y2jHE6@Fz{DS<#UC z>BZee3&Li)iHE_UI?inTHGtD@LDsO`uqTaFDq0+Xt#xdzU6O$dYf&bH{t4U>Kt3N# z73Bd#a4q@X{S*LR+se!KV7~NCY0d_9Kl{4lz-e)pc_!JX2)z*jfF>e^LdHaeu{DG0B`I7K6fO&81M!Ufi`enX^ZZy5K%S_dMw*Ye% zfJIliufMmM+vnc2O2k5JCjghLbiM!gbA6TjjUysp1d_opBYG!~-!{H}(Kpm~dQ1D% zbpSBdK#i)c5I~mx0?Z(S)Uk)|E(>a>#|O^W=xK#0<&q*fziRcGO1UXjuv@CAhN1Rb zo@Ei>n*rrT^RrU2Z~4vvHKNbxOZrrqQZB#2judhgEI9KJ&@5hcy16!eS0)qGfuH4% zZXdtj)iVa2oe~DS-;6piYWvPYqyGM`cB&wss_$$-N>FtwO?Z~iv!KJ8}*MKpT~(ZB<@wgC;v zYD@;5seBpL&x-^?Un6fh^#HX2MGX$y&Ds z=+ifgGfYXdM;d?=dgjYi)48#CaxaahYBK%9>5q;HcqUqW-UMgLbkZyI+a>pybnW@8 z6Gv%K% zFZagHFXy-zbX*;IZcmmJW5W%A?(1>=zwWDJvI5ci?Q)t%VL%@r8QU}(g)DeXzrx>F4>Vn}6icDREI%sFaTRD_kNnNVaWPd| zgl0XbTP(%z_Nkf6cA+H-I1lC9pYB;RyYkZk9)B?>IM=ABNja4jflP=B=OBmr#)A(70ncUXWyX$> zQ-b#EIR`vBc>589F?ge>&iT?mw7$a^nnl^{@Obx-hu8}f0xsY^g>h}~O-7KGSl5s^ z3XF=mJ&R&~3`tuEF5AXy84y6Bxeqk1YR!%k5NvK2n-adLKkFgH3>v?l$eQB2EB4DJ z7>WHre0n`TqXnX@7SmN+p7SF#eB+53gD40 z6oGuAf#FFpTxO-(7fS{pc<=zpsIP3>evf&35u73Tatw(I;} zZ@ivhMEE=wB&XiTnP(DU+7y|_X(>Npa=ZUGk>SE;G*b$w>aMPk%oR}*plAGM?`EnCp9^I}&4X>#yBfk`uImO4N! zleZp38|c~2k-x}N){ zaahZ54JeW`U%7|#9J1=RdS@cAhO*f{%BOJ~(T}FzQDrM;QrVFt3Fhkb36b-{Kez}2 z-Kb%ns!M?x7^3}Ji!C1lK6{@QR7Z$DhiwZxR6BXTJ@RA#3PL8ru(~YE1309Qk&v9w z+v{ESq{tkJ)&)!D4FK~baN6P`e!%EksNGBXn@FP??wRoQ+iDxM#udF5bLR^8`idc{ z0K5$>;ynR^7Hv@z9E1T7^_&2_0Q7sG4 zE=O@2&VuKUcL*V!N0E2>`<9V)Ns?=14eFR99qOG6S3!o`H?_@yY3DWhAqW( zZqe_~7yqx|k-k&TIrFus zd_^DsYdVHOzKW@5)p+xlyq_R$Zf{lgss=yU@Q`QyD}fE5-plW@9MoHTr?es0yHh;? zreLwtB8jt5GKlJXpP!hMfpr8TxO5f0*YQ<^Jhnm;nw9#KfR;HuYYNxo+an(E=uX}N z?2a1^NQEJB^H=mU;0fUOY()JgsnQqJ;I3C z-uS<_c?b%0*kz~GtSnD>pR)jmP13cr@cXO$n7`^I&}{kHA%Le2T>ol!D0xSoxS@f5 zoi*_LhlIFL;Gw4zXkCeCLE$0Y<3z>i{Wu{YI$BwNM_()Ws>A`idw)4$PduvFy9M~& zsf#bxb*IELxGDV~9t7p52pW>mUYxFhzt1xylGwjQIW01R+3Zhr&j0i1{ACe@p;4?>=B2eJ{8PrgtjgP|5Sn z6|*PH<|;d+KLc#K43NuF;PrGn0Srw#q_g7Q9mc>vm(Cf}|4795&1N{31TA^@@y6PD zvybI|0N=I&5dd>ZG29}ti6*ngQ2 z2^=zD*yVO%;7>7MZn`+=3A%FL8?zPsg6LFm9;12=gp2fhUu^YjqLzBe)7#I~5SF>s zCMA$c$R2^i3&y4ZlVcgFXhPPe;a|XkfgrHE%ShU4eY>gz95(Yuh;0dQK6C;JPb!Ob z2Sb1U97Q1R^#&tIy=O<2MdqbAT$fN51!&0Coi5MMa|WW)RkM~KK#>beOX-*lX^Mcq_9u`npd3M@ zeZ`DFa{72v259r-?$7rqJL8(MxSTff>6~^sj;3_fFV;{k-`@zM@OcuL@$F`62+U{7 z50KP4y~)x9JX(0k9{D+JCQ>fU#|-pBKaIo3B97r8}>*(tpwKHGBz?IWoMYVq$f_K17}{zT9;A=Z~~% zRUihPSEiTcGb-)IuNXlw$bQBHkrDzP*Cg2a-gvhk8T48L3s$qA5Wh&GE8vuqN`%V; z*UlmuRmzEHXeYQs|1&(!ciotFpe3tzso9C|{T78Z9#37uqxTs3%j>hN`zwnpw%oL zjw?|xKW?yln5-+ z*vw%F`0KKXP$+?_6d=>IUqiMEKOG%BouwbHJHBfp%bkzmm@75;Wxc-OV&Q$r_zwa6%aF5Q+kD=#LROS` z`l9e+wn(C$A3mH5cmvao?#b$JO^!D;9q$_c<;!2ik>za?F5m}(Op1K2G|X3oj^1k} zt=|AIJrGGs`y0h!1TGkFA0nPy#_Ci8_@YdAaXGBmO?}wY4$j8ae-s89+e~<9l&ECla#>Y4homoPLAH}(+Z;^p6zevJ z+kEMnF7e%8U^5`F)i4F~Pa~GyrcTgBILlR-uQlhA2**zaN>d^wR?HWc6}SkfoUM_H zUB{kcV22oo*Ct#M8yl|ri8v}Y?Ja9pu|&azy!A&q2uy_UCyF+R+m?_z1va=EP`o}= z1;6L(l-&bHd4}Lj0dq=#XiTE$M(On_x(t^wE{^7>lz-0Li$&ZQ3Ot;9%KA0i8~nCu z{`iAMd8>|`dEyFdO!)_Ce~Eh8w_(==KaC3QvAC;k8Mgg760-fvf(U%QQGE_dlbh9I zF~Z#P7htxLgLuL>RkbFmns`=P_8DTXe8B7OlmUp+i6W&NSUD=no*4-+tJA<45gu^V z`6SE_Tw~YENWc&`G^&jgV+Iv7oMlOvc?7d++zr&P_zan!# z1}UJz5IcnzPeY%1t-=mlMX|Pdzeuvs5Gi0jhy%ErIf@0>)ib_ZumXB5c)84c(j){L zFhC2UU;@}9;Fl>*tTRhLmJS>KDByk_S{5dAnX(sFBD=_nlt~qQk`e>wMY3xPBR&zN zZJZT~l~8}cWYBRP&v98@#P3bI;M!HSjItY+ALzCSrq=fUL+?6B5AzK(4nlGEO%cp5 zqCFTDAIsqRl|K+$n;II?!9|kG2AAPBozqTrI8%b9EHhKHaJ=_UT7J!nAOqoLag zlhUOBZk>U|P+umEK<=Ma)ZTBy3{Zi7EwfLtlkcQ3NK_cx;{To^fpX=d#G;Ks|92zx ziwzXUewtkh%%=@{t})J59!W)r#z7qurve7ER$L_(WAbrRHFrZ8Mdrt%aEm>dP9@d0 zo5`f|GV&SRVJi!{C&nL4EU3ywogY)B$H0DhG_JDScU5}F^?Xd_HEkejoVfQTD56wI zQ3!x}E&XOKdp%Q{!lQ~goWfE&`s{OVGb*QH3*$IZYv|X*?m3UG|997LS_xx0l;Jjj z`g2WWiT6zD?6&C$Wr$(NLxrQd)wLH}S#t`|@K&Qv*GTA;ZHUE({82CwjS0z&>)_~V z@hP7^oNmrsgnFFSEE5#e^5V{^7p2#`6+fbk0>(%!_b9?jtS zh@t+()kNFu{^0+SD-t=g1NHakp!R%DfdNh@^BGE@J14x2r_cZU&1%jS{-#O|J>Av! z03Me+p~^_Q#TmyH=OF1lqB0d(;4iL6LZ8|(X-JwV2a%hU~UeVCkJ9fz(4#n4N z5>f=g`woW?pZ?F%vNid=&pEN#cU9OC3S7$+U9Ta}_^*-gHJTs(KT~(B#yx$}&%XgqUAx zyiWQBepa*{y40V+5EG?0CwP_h#LU408?4X_XVW8#9_z2@`Qod<6dIprg4*HRgsZCM(f z-hEDR0H)I>c6<)aRTqZCR%DM0hd$dGliG*(*P?s@VE?$}&M@RsNEUW~$HxTx)T7re zj!scNV@y-&&)1}4Gt3UigphFKe1Zp*AxNxuNfjzNP5=g*=y zi6Q5V+P%P+f$F|7`-|_qM#+TbqNX~+V~}Wxn(`(Vhv1p4G&$yl{i3_IIcOtu$K(2V zgormQ-$_eX_XcB_;L8`*e4&*lw*XmcGXzSTEW|@>#gm+R#~s$52l)webTGjjX^ytmEJI> z@7@HVe=?kKSG7)H0@Bk)*k7NWFuw=GBA#y581FGTbIz{9rfeVcOg*gR>UUu}R4oZ9 zKwHdp6wn6njtV0v0`k`Hk&kCzgK0nB+IN|8%&+%L57Cv=5`j#6Q5cAspMf8leY&U= z%t}hZw*kMIaT=FZjjuDi!a#>@Xr`}a1m43L!Ob9I5~1J(NK(#wr?%c$SkqsPi4*L$ zi*voJ%#dzOPTGwLpEh_H%92y$A1mT74{$++DK&frRL}1gP_I$dgy#b`#we zqFVvz4!6ser~TCztUa4HWS-ae>+w`_A_6UU8t~K+#&K}>ZRDPH+;(s(39DLAgWvI$ zCNZOL=gSc5PCY1eN_;u;T`)vHDd6jVSlpyxIIBf5)_pO{BFs`2#D8v7WI>te@M?9_ z5sSn%l&`)PL`GEM#TMf2h#?Z1@CqUstAqgb8-@0xRe(ccCzQz%lP{1&w^cJoSgyhu z2~U)B`_hX7aWC>veMfie*xyk#yO^o~6GA2`3yWx&-+Fhf7Rko+ecmubD%w*}63<2W z>B{Y__YA@m@^g=NQA}CET^gPFSMTrKYSns6(fIghu+9gQ5~rb+rJP_HvE#fh35bhi zmk^(=_AI!Ph6hnZACQnXFn%)XjXG>e{T)I9f<@~JN-|=L4@*gQpl9%;!cY?n6yo=a z@PGiMnUeskK;rg6%HXTVb53S9_{<5qEej-k>JTG1K`62V6OkOj`;xtJXPr!kx9hMo zxHQ8^Vw*~H@-Llke_(jMpQ-u8Si7?dV{d8IpQB4P%lapmV+~-j^HNV&)LV)}C_T}u zCai1f@`Rqf1iilS>Ixt1+Y5#tXpuUjGFyq1h9n>W{wxXiY&AJ-&(oaeY-vM=PcNtP zn*Hd?5OQabmd6XV5ePVjU{6TLWEnBgkOa}L4<4)8g*FolKjVe*ij_K0Z_e%_R5IZ= zq6g83>`h8mv7r+f^^yP-XS_5OTvi1(wUI3oK6sLwp#ku^cv8*+7hxH+0Yy_Wl<5y? zGKc5jdK|N}JqLH;_WP0czPW|Ku%p=PyDD?<%-0Sb>Bp67c<BA(WGwuA^pG% z!}pF(B8`x+W6HTolbT!zA2o#F+#zKd2scohjIC4)lmrFCuaHou#@Uku>8kG$?F^@e z6tLQq`){xc501TjOkBOHY_3gA*fogT5}zJFP^EG+_io29Y_Q>R6SFw6J8<6`;Gq3n z3eUzZ-O3Ha%X}cb8CFcL;3+@fO=S5pUH0+iY)vAmrvA_j0kRAQ|GMg0Fcv8Pnq79b6b#t>A%fxZR8Cj$Y`G^P=h`_?sf1r+M zJ+_cdphmG>Zib9FJ)U=vY>+fk zFRSSNFhA3O0lD=M5k%EHv$HW%STrr<1t(6BiE?qd+6%qf8})&q`E88p*93O61c-mN z<9+S%dOSxWSUx-R5*B*ampNq)MQ2n~J1~wv*RO|!xi#YV6(aw<&L($I29R8NDVjK( z#G<@`EjbD&Flsr%B&t+l&$)C1Lq&nc&ilpeugt_E7*KF8Rg+}_a5`4zKpHfMy2)Gx zfu}JzykCwUU!!XHnJVpXw<4Oz4!>2bN!tAButV|Su1QIkP3uDW&u-!&H6}>f*drrw z?wSc0wnYBm@Y-qg^-k71y+hDe2!>IJlyxY)Y(=NiZxrDEYQb5GNc{w(co!^Cvtpus zG3Ajk?j|PGSLqy2e!R{$qwUQ3P@nFFSu(2Y7TB%>W7DFbXb>N;L(GycxdvEPTU%S% zAlU}E9(7t8)exnoM4J7W1UZ@Q*8@oG42Mu*{A31I@UQN`Au;K<^;#OtM;yj5)F|~@ z9}Zt)VN&Q)3Mi+MCX2m`xE*n?hz3g*&?S332186%PnCq$znq>9d2sHUcq3UZtO!JQ zUXS)JTt$8Z8yQ(DanlWCsf>ySo`rR=l>JW-SDao-GEIoA5P|| z|Kl3c$PoWVh@|N+qt8|e6SZma1<#|s`{oQghvps?Ny8f!yaSGTkRbXiaknVT1yygU zo|H}`_`ym#IES8lOWBa;EPdLW5hxB)=C~hjJp?yw9r2*>R9th|{ia!77V8_f!jdV_ z2!_{cwbbw{5_$^P1xS%o{>egCR+J5%gH?}F6ZCT8Q_8! z$>m(o)fapj4N;C<-l5Sw7PqQYs7DEY9Q2A@#zLz@bb|lYo)3TA% z%$H_ag<4o1#~BAtM3W*1=3aw1pC4ob8ue9M`}kz%<^k7PHK;4#cPlBM zfY8-v`WFI!jRE7J4f~pTdQ!lc=UErf`-8%t_&f?AcieJfX~~MlWUQPNf;r@&I`@8e z{oO9?Aui90vyk>S0Cbt{T08sI*fL~Jw*kZlU<~;!kyM5U$`3inSCtdZZwyU^B7RYg zQ}_`b#(|Vl#*VFtZVa_+^~Wx{5>GV?%h*_V#cG8(-n3*R_YSLe-F}4{J>YbFmx@69 zy^Tef%-DX+;#@MR;NsIpd8Twbo7E<~DTxmc``n~C^@}e8ro(FVGa})S zVwsDfUc@4nlliJewhg;ttySY&K_B-l4(q~RWfLR_)c-bolG1MU=EyFcZ*p)F#6U^k z0yt>-%vJ7}DHh}(7T3ja>^|MPf`=GV$78B{hsP=*GYhmuEMoY0)gqbXr3Zkl0^&Q_ zLT*Ya736-lJ*e?fcjG<3XyI;!_0Zac$5RRe{Atn8_47z#cKA?S-%s~yGtUXr%bfB6 ztK@jP02yI4#@EStRf3Ok>X~YbK{2Vq+Vko}QV5Dlt?649VZbXi&^99bwe5N z4(c9qw^HB~heLj^U9b0s$+v6)A2VKWk-c~c7W0hpC{_Msrni{!PfS`l%r9kQ&)vCp zhAjk%Lf?##^2p7O#aG2yv@&j+U4IK5T5NBP^n&e{>Pt!rcO%PHw^?F-iWruJNCMt^k$cNEw$^`o%oU+y8TT zx++2EhY}~hxO-tkE$(cXz}i8Q>X(N(dftlYXJ~;YpX|q5f?@>;`a!uG$KXRao?**7-M~h)OW(gFgNzhnevR7_kD<0B4(w*nmPhp^BG_8ERDelZPE;15Gx^HWG-`l@%;tbIgeD++{ly)ZVGA;|F^T% z?p5rXt%a2pF#@*ZKXvPVto+{3^9g-7N1C7Rz!Rs?l~(q*Lh@)~ETaId{CC{X+dCDi z;Ggou8Cm!Hf5}ez=RStZP%38m7wB-OF#QImZM$r%8;xIniyltovSFz2O%%yx`xT7e z90`@7*<;4mlhGg>NCVenda`wNnDBQc4hLIQm+YX{DpC?*R2msE$wbN6<(i(H1vwf; z5<8>*MS}o6!~@>kVYQJ0m*tX6(7Zz|gmKy+b0dna+hKN;qd%0zle38W$BUxChvEzX zd*@NwMa#}N%kvSw+lTKGk4n%uaO-AwfXJulSynp3T0M3-zDJoo&`{l*5Ob-LbHRVi zpem-ccbyk2kouSR3X81dInc?o;CIhUt%2=#&98X!^{BM>uxX$wmJ!JGmjhzZln0_l zro|4}Is7bpx>PGq?-EGFD2KBIideUu4bA+?!W+-#hwr#sIF*0)c_MOdZs|`+aYR`) z@j~L8f4>99093PQE3HRwCwj1k=;~;MaT6YA?RT`)q!PU0;m{%?DBwR+llOAV!?q{g$*>*jAoayajoOA5noW`OiVPWV zcfY;apITHE`1JWc&@uRD@8+j3P!YOHo}bd&C{8S<_g%a#xAPh9Rv^g~sYT@>4GpL; ze;TSY=`>8M;|HIm$aeiPJzYN2+eo1cG89ykKv9As4fa)RVA*h`Bxj9~ruZHDWK(bOOk!Qs_z)tB`P_u&8V%ABgDtdyS(?PFIKI(%(jBSqU=UR((#KJqov`PgGOEokRv=4>18}bp2$3e4& zj{pZhet8|su3^xV3r4krITO`G_}4tkO`(wbI0V(WR6%*gY}5cH)Cy`HOHf}=BS*)x zSXkK-f4mQlte`gHP^b8T4kBDwp2U~LGvVCN|4fxf$mFoWsyW=36qx{I2s*3fnbYql79zdDi?8!{ z)P@B%P=fl24R9QJO`+K1gm`~Gvib$ff*`nceND0y{I9OokwY12wDVIkdhbl#h~2{A zx<%mZ3A8Bq`5l-H`(q+qY;Sx2q^!N-uD+vAiGG| zX2J7S1aT4_t%6D zaBoQbYP}j~hrlREA6tWr5-fTpMKO>M7zBSJUP|Z||CQ10!IZ&#C?gvlS~(Uo>IOH} zm}B}@s2!J&RA{X7_k5lDMf;T(M!l8(2j#k76hyHt&D+Wu0$42%U8tLh2IhZGV z{9E%OnB4S}7mQMR0|U8LuO{xtN}6R`U*!(j#G;++$r>S_B0DV{&7HMMd;APWQJlO% zcoVCZr8vyJMWJ*yxs)SoRzFsvxEw~krQaM)$f-haR3wFxEsmytJ{}2y^19I$e|f-$ z-F@jV+V6*{45h=UBfu<)=U&0eaI|{Wpfj3~>h!P49MXrhk2$_GeT@8FVI#6X*|`?t zIPC(7HYmQ`(e;_A&~nwI5-;9pEtY?S5c-Xj_FQ_R$pLM{6dLZPON1!&hSz&pjZoRf ztf7K+IcdpeG&=72XxdFuw=L(_+spR|9;=vE2GqQqX@1d%^WIl^EIV&3v!z-mdUU!$Pi}E2yrF;T7X{;P2H;So zojp@29IPF#9p@hx#}5f=24#;6KDFJj>imHV!YLIEq6(C>`(w5XQw0(-Pe|F@N7ddX zc1W1y9fvlJC6`gDZp${KKr~&c*YVJ^khrOqE5fTDJT9vfU5kK70YKcXFwIzB)vSHe05{ z`hicTvcssfXW&c~&3H=P$5cS=#@a_91~rBLTKTTXs_?dE*U(I z)e?(3%iq2XC;V3DBqZf7uAS_FhJp$=ZMA*UAb}F$v0o3iCj(LiF}~#$eoFgGG}3RG zJ?B3&nwIx{$cK-RMgyjKE>cVLx(axJ5h z(CWYUC~|I#PtLnjV)W_-pMMMsdk(tZSdLcHgIqaasASrv4DZ{9K`RAv32-O~`66>b7|T zv-iy&<_?1GwDlN6XWS!V?UFYWvWi%kd`x0c*~!c3_~vb#xWcnB=!SVbmZ9|mhfO%9v? zFG2{@>x(9q9^o}$`q^T?(dNmdURn-62qZA&PM4_4>3;XInyZikXq196y~UQ-4KB=h zQqmNzfMf+S!?_T2Y9(pHMShzgY?(ybMCvai)@DPo8yyj*@ZD%~?SCa}<09Crw2_E- z$Pu^CeS28+RS0WLPVXu^lhbYVvcLPEDH(g`_&JF8A9NfWnu4GMEkR9LjLJXRPmInA zPftG%>o!?*94q)z%~;C|f&Orl-a%(U0k}DB-vW=TICuoM2KR1aZaJ}88k8!<>;Y=C z>l>1Ompy6dL8RprRmc(9;d(cuBhgfKVt6v^X?A?h3tP%PZm;Wd2=Q$z^xf3_C^$>@ z-dNU-rT^76cMK*mFdB_zfT&n|>=#1z)radL@3I8G-&X_Fx1C z0VslDXzP2{*)jlmCzuaHU4>}z12OYIb-8>nFE`cK?qG*Jv6ll~QS0DjVL!508Aryr z$_p7{hm=5Rbz{GZ=J>8BLHWa8x1Mz-ozM%7y(skj6`Te}d?mStI#j`!-`-B3N#d$D@7$SJ2i8!_Y(-ono4y%?I} z4Su7H8S)e@>2<76#!GFJ$Aw?W9g2it-ca^3P%i3(gpi6PL*=B>HOENUGUAPm=yvL4pigV^+v>O){i`#En zK2nlyqymSB74#liDBYvlTrY$-p!*kmi)k@rFN*ctZhHXtSArr$fHEExoREV2Y8ynF z1LCIz86Lk5#fS0Ja>*^d7tp|(qnJO`{2u27{w@OLv@pG>FwTL=-_glZTA+6JAwl*{ zG{X2-Go$;3lI285qYTQczSrHP#Grlr;%`v@o+AzOrJaz5DEj@c09S%8^?I+e(fx*q zyN;wvg~=c@3g0^w315^6TlqzV|H^bI2=26BODUw_8I=E?1Ox4JQl7v9yUqcLLx3OW z&XIVM`%aPn`-ATXcHwanODSNlK<}{$3anx|P#y~}jsNe-OI*W2-m@4g%hAjw7lY4NrNj0*+Gag%X5nl6NZzMKM7M~kkm?yts= z=Jr|q&2SyvQOix<0nzvZR^Ghcz|A@gh;tL6Qq1}Zgeo_0<|`&gPv(ExdOSJ(b7MVs zuwQNRWfP#YgBf+3*0BlhDMPb?Ny-|L?cH=WDQ+>Sj~v3rZ{UBrtj33OTySu3cIP2t z_npLKY7u{^+)IIP$K35RKwrMxOsCewU;e9ab&7qWe5%c zyO4m7U%P@y?-BOpZq28nsfrMo*n9#KR&#(@zt$eEuZzcz6$Zc;+rD5C_g}#kQyB`$5up9SHD1EqInf*mR)?m9-qE-EKI!{XTaQ0VGU=%h_+WB~B^#oUS zk7vP#HKmr&d$;Tl8WNlU6*}O(w66+?yREjkL|DzaQ$}8j_Yirf$khsEqS3}63E@jG zHQMHrh1{V8SNwU?m96?2Nv@EQ!u$oF#}T{UZY`M8R##3c<~X^k!)q%j^O&|_y_+|Hq$zpL@<`QZ6!;LU^IY-%Z{U#Ee zVitWWlwu~U@*%|0Q&IEU-`oI?dlMb zS{KItgK-p{f2yYN?7g2iblk6^C=)V%O_E7P*Pu4hip@}TGUNm9N?FVlsUVjptKQ@i z^O&yy6-$bfM0T91!LT2+cQiF)tw{q%x7gJsEE7+wPOBWJh5S2%25o2LM*u)S?vzfp zxe2KpOcuCWctmj8t@dnO6dU(b0l_}Jz>+NzVebnQrG~&)RYN!RoFykFVOc0)Q7cl9 z=Hw9jLkPNGr@)uZRp`lJ3=2`Nv3wawfLrAEUe?m|YJ0hyR#lXpKAIzvm%Bb((d+Tf z;>sS&5Re0w70oA?i9;@AGu8&UO=-Dg`puxe@MqB#s<(3jqo%^2pgt?8FBq#*<{M5| z%eY^bZcp@$AWYBaO-v5}ubJA(fp$giVI+=fvBHV=Ps6Oz=Q=tj>Vh9+gA;>Wl+yE5 zwS}sqX)`4pNCtba>@l**CBkJI6|ru!!Id3jWv_VTgy_HXWwvx5484(^s#758wThbp z&Bv3XfM`L5WF~_`QVAkzZB0_9jxWX%0%jwF>_(ekl(&b7G@NR_$VimrMgdDAkZ4T< ztEJ>(>AOY)ZDZve6JDEA6*p}6`4XtgKN9Eahv!%dOGL1J8zR9XYJ%050+E2d63M8; z=-(GTAjNazqt*70z@m-*v*mhNwC)ds!+#TK(6$NJS4*fNs!_mkdbVPi`fOS+zWv?( z)wr5qyf^l;P{5DEZ0tJZsRt18x|kF_xyzB4eP;``LcOr2z(TZw?Jw~(CNN=uH4t1A z)n?649d-xvbGQF^N;T0Re}n|8{9P~T>%W6r{j`Av2p?Vg!#n#>8FVVj2RAXFFVSol z+Olp>7Zk1*tb$F$+H~qXH7C2n1A@GPwTKov0=vdL?}<7S{7aN_{f&b>=(~C)rqPDi z4zfeOIKq+nzDGV4DXi!Y*UBai2l^mWtLcj2_o*vG8IwU^_dMBPysr5u4tW~={TbBW zL;Af!7u9ZOcqVt#WCKov2Hg>`$9fx2g@&N2!K$g%P%CV9Hp8iXr55%6&N&Oj;C2YPHtz238r!TP44$u z-%l1Fd8sC+6silN}Y^MMs_PP85Gi_ zmKvx^$Df*oy*23m&?HD-y_lxv%NfFNhDm+$spJwyyhqAZibK+4I8_LbUCB?@TBjEh zn*aL!Nfq|u-+%|jOa!=R{LXMSzd8cNQ8pO%=CjJxD4XFVR6GQS;X#9I&VTmsVW}D2 zuym)An=PyZne7~vP@t`%fzp(yCaeH=h1`mW+pOzy^*3n98=utS)fOXUYO|uhtMNrY z@H2WQPxv4Rc)Zp6*bXfZFsOS%d_Jktv9ub+>}T>B0EbfdG;#*D=XRe|*xb<{*z!SY z*Ano=_~tsJUFk4>_xVs|xm<-B_IFnQf?u)s?)UpPU;F!B`jgq(#bIz);#tI~aX2-Fxb^%x3nKRm^>$_+~iMBIsl1X)ra&U9fS!B|$( z5VchR7BVj37(6vdE0-B){`)0xrO{>*N84Sy+ZOxvYM6bj2T?F7=NDDc?I~&94v}J9 zT-%2u{nMz#FtZ+iuzXB{3h(!DT=0W^*5QY{)k#-JcXYQ0HiHn@O`TqGcpSf)RWef% zfGIN44)#f-ib@3w*wG7rAK9*=Wqt+cA@Z)<)klxO@-3=>=8L`~6OB=Xiqj}SJu@8^ z13L9R;es{3oFL&PU#q1g-3o!QKd3RGZ!ECFg-ppn4u-x_5vi50;nc~EYylrERTOH%gt8|$oc{+Cdnm)4&# z!Z323>|kV4>heM5)YQeG>zCJivkw0^7g_HY zkOXVWu_B{3XU}4mW?PA;JT-k==QU zZubmHKcAt`SF-pL_wk<`ey3wsy|^0MJPtK*ORbJPCmWR!MBY7ckR@{<)J+Gne4WML z^I^MWRpAt9I_Mz2v`}og34?>F9Hj$1BOBLvqK@U)wbuHt-p<6du{B{Ewy$|rWP85Y z+P`iCwz(=SGBep^J_$AU&T-N00O`-M%p8GaN~L3yU9K9VC}M%|IV8yqdO;Mxp(y^M>n_d0K|E1PYcHm(ULoASw;2L&Ay_2hQ*>nCiN}^>C{9}Fy1E)4k`2dMf2V_X=^vYF7<7fNC0z$@` z7*epG;hJzY{X^=Hb0A|vh4 zk1l}_LRc>-rb*jM>|c{GzzHmLd~_yMQD~MK|Ilcp$oSFWTx%lO`6K?+0Y3ubz&e~^ zx3llx7QHju=mq_MUCNPEkA=l{LGy}sI`E=E^AG_RsYKj2d?qwK`%$|!QG9e4+HRH2 z|NC_)*g_11=O8RW!1;7O@DG4jbp{%WDM6bV{TiIok8VTMT1HfTTg_|eZng9;a3y}J z;-X3vgIL$vRlsX}lm&2?gfX(U1hADI6eO6xTKAs|{!0mM1A5w@Ma%*1X8| z43SP>l#E(7<_vNA&x^NhBVv#tGYw_+Wlm-6`Xy}Whg$XA#XSs!4a zPN!Ef?-Lo}m=YwV=>|dQTP~&Q!D%3~Xea9Vlxuw+OJZndcn4zlvjC-Z$w6;`(Ov`Q z>7%WEfdOBJJtR1m^w4tE!TwsIb^9am69tgp^3)% zG720K1sHvpp73U!YrLxdplY?Z2NU?euerE-ZB8=;6`LQQG!6v-J)hEbH~h_!ddQu{ zs6=b+&yut6HrwW8er%zbLQh8*I+pAf*=bHvXxH^h3apzCube(Sx|y>0{NfLd__ZW@ z-SVhL%xQ0g9Nwe-QQ%Cv6|8fy1Qs<(q%e_Q<-gXteW323IGggq2mW6g!M98s-ZH@p z_EaGaD#eP0fMfA%(6!R^X?3?4FfKDQ042dyrCI>I>KKhu*`f<2a_hAgU}-u&%f6BO zR;4+Ut3F4-%SNrEKolWQ`VT-|uGDUdt1-9h`VvLJmG?nuWA=s@ykCo$FSZFaxCbs_ zroD1CA_<{+7!YUx6BNylER@gMuLZzu=N$<~s3xl;Hp$%I!$t4sx&oD7G|(F=Ufv(GJkl7FfZK%pen3s5;Tf@2F4UgLh;Oq0i0zodrr#1Q@zEd)av2DFm z0-v_&B(Dif*VLVOEO(2Mf4I%$KjwRmrTDDTyQkJv>hMIc+;LbaVIGhuWj>_*QmbT^ zIG-^Ip6d;Gww$R(JZh$npmV#%%EQSpWV+bsi~#gMfl-$?7Lgf%b#`3OmosuHXl*2J z7^avZ9CpJmDpJRef`U>EoYynJNA_*8DhtDcv_#@jqe?e<^FWDNeXLBSsMxOiIh@;p zu$KLO|NTIgk0#ptV;b`goI|e9YkGU-r-V|G*8o9A3P9vx260*+eJlH~!<9VQV!dr@ z_Lthb)MI!Q{Ig)K8a;(Sr{eY6>tMm9 z&x3S@EM|%B@~~@kJ(<}^?AJtwKERJ|%N+6?Rt82VqM0ephx18OayCowr~l0)mMsa5 zIFOY+3g`+rn$8R0{C>;I?|eq*^)f@{$hS6?Kr6lyoigYL`P?5>js9`)zoWtRIZzqE zagWkkoFeKZb;PaWDvk}gd+2KZ&}3cd_911`y0$7E$4f?iw5Jk!KD)U+Z#Ech#3krZ z0H89lg9XNe@*^+Ba<`j4%}e|flo7H#ZknBEQ=4VZ=kD`Gj;LBh09uFj&o8wUU?42- zaeomO3o-bnNVkhjxOk;|%;x@doX+m^eTQ=w)_6R zH2bi}#hRA;+as^26FlY?7>)lPsX?X=1Mr<&PzaYWZ`n$HInfrF_4_yw~`K+9}2elRf(H?Yn5SPm$x zDCo+rFo&*hMYT`vdkw$eo}4;e?LY&pL}j|qi|Mdko4bG9%MI%w9FtR@10gc`_b~xt zNbuPe=T)xg{n`2#U^jc7dgY2T_y(6DwgPk{5tn2l57tvifQ<5n;Nw!hkYQBu$1wUK+7c)w>d+q|6TjR#?;@_sdLb3gxOqObWU zIPMiNbqBRYbN0IUf3&xGeUwj>Zr6MHo`C=VAe9p!JUand-)P6-D}D$zV&B%~kjjWv zh+F@Uw~ELQV=&?|Spf>+YJxPUesE}PYp0AAd#HwZ(X&YKD;QRcz<$_iv)wx`F!p>I znj{qds)-leu98i62&|+fNuu2`jFV5IjRg!5#<&o|w3vPvo!*8C*73u_O7&{TMkA8O zxP{s}A2%}ta5LKy&Owg!GzDTxUnt@Wg_dppuQup}d$2~QzZ1{p`d{(z1CB25$DaM1 z>c8sZ!5LldO7&k=!Azcu^G&yHbwwR`MG!GW-_TzB87J+(r}YjN1o+Yy(nTl**{z*h zS~*}M=9fuD108~hfCb0L2E8I=4>JH0M(3z$Oi7-NdAo;texfgV>;&{jM1xyP6f*!lca#=yx<{0_Fk;&Pt zHe!+dIr=4Gy7C~Hpc)|PF|mZMPi?nQ0ZnXJ74+`)m+vRwC=h;fRmH1VVP4~bJ6jGQTwsTgR@Nisf}JMr_8~z> zyP+avg0@1$rme}-{eZ*w<&!To!X$JH#u3dr!<_2T`vE&Be?3cnIvSbz_Yv}&CQh&N z(S#Zr&g2gs(F-e)uCw2lTn3$D?GLFE=eJLGeM0pp&l41RH0A$$G&5Qx2yn>&<|&<2 zc;bAQgjM%{*~Q3RuEIGURp8y3DjAHOZ%~&|9SQ>tTf)yTRPoh>(0$QD9?q?q<3d*t z3G;wBZY1m<(Bu~&ytSLz%L&Niz~WbqBu1^^vI|>|f)_e+TA2D*hH6gZyQc^7J2uY& z!?K+O?3{dD!VU_BTalNCO3gz1!4Es9d@>`xo;k!Gn?i?YdK!o}GDq5n`8@zMq}y%q3!dGCbL9f$v=0A0|%(34)6pLY_2qg?|Uc~-iovEdB z*?)@BV=Jd%p6Q*-;x&-1h%1qC;zjaF_;bO)UTu%ZsIqw5b@#7_%;#m63Fdu!F^aI` zMmvQuLCK(3VMtwOMFMt3GM`>9m(+ej-%jg!K9vJTPUi{9JK5O8=UyD3Lq=+~s<$kd zqZK5{WwBXn&|mA_Dg{whO(~LSEu98_MPi+dqb!+b$hUhDyp*8{#-|VDjUwn|z9-qA zcs&--?()JAzu`;&(yxfX|8~M`cg*o9R+{0t)+*KCCm9jVbU#Lgqfn6g855ZAt>>mAp)2eqy z^OYVlS5^RjlM7zPf^Vw9+0|7ki(jgmeTMB|C><2={D_+~Cee1ZXt1}&hp}cBT$J6( zKX)m}X$$HbkC!=AT>r+vV1%T@b3NUs*?BY~6^@lezB9Wigqgdg@O;csvX=*8cMKGf z^vSY-Q-$6keqc6EOb2!_^0#mn{&}SekOm2${@ynb1E9zMG?-7y^E+G{5U_&0iq}>r zC4OHtm<+3*OxJSR7#Dlci8Khow^NMfh=?#8ZQqJ@ZzSd%ZU7aM?n;n|T|H@O;8`8i zgF?2TzPChLqt&WbDWrh2XNr2Yx2S4Vi@hD7Y*+V}BO3s#LJ!~-UDHI}#DSe})Tj=R z!{TGm=UdrA?ZXF!|Ob`Lwr>;^<;#@`iJ!jyGg- z0?vZqVk7;Q4dvQb4pt#TXR-%byIAISqn;RgigY$T43z(VTk@+NXuX&b&oEVx?Ew-U zRC$69ofg&1fw)5Yyl9{W@B7!qc$7s41HpBdy&FW2FL*gYt;4nq4-BmSR7m)| z{-td#=XgTBXho-E+Z7%T)Dl@2%2AwBPGgVxYVO_JXtf#svAHAC1a>*2pZA#O=v}2m zA@=e}>ZqNSeJh92X2of?y7C+nk`Rl11;QFcG@O{*C43XMcPedY+^MVjC!wxG6zddVZZ(;N?nO}KPb(d+j9f_DbanR+(a-}U3qX5a85 zZTI7hmVJFL>yLvGsrB~1$vnEn3aBk#@*3E>EnS7bq$%cH1z=1w9Z*O;#sUuFuQ3;$ zX)k<1$_vEiB46B6+fdXYWWV?a1W@Y(fvlymBQgNOCu#V)wcU>CDKj=0i}EwjEF4tr zih_!W1WfzmZpqAtJlPl>J#j|feZ^7b5iM7nPZ?490bsY_R%8hFUshXLs`_W2v!9}R zaUur~tnU#&B7Iw+@8R_!0iT)oLXOYdSAE}__&mJnDgg7W9JWKgl-i(}qnO&xK)4`6 z2jdU}!ss6#FPq$Q_$zJrC10qUS)c4C)^%C5;^!yjT(alBblN4qer&4sqVYsb4bD)H z<5}ufJ3R8`B8B6+S=?=>n#uO_efATpJ)F%=ppuh!kE9b^zkhwcuSKoe9Ej&Q26Qg! z{l&7INF(w!SAjJoConFW$d<_N!*ZI@TW zJFq2g2~@6(1;8^P$@hez5uge|jqU#?p0cCETyQ4QLFgrpMgJT zJ0Rx6c0ZCnbS!Yv5>ukJ<*+caTyHV4sGLKi6ke z9F+YAN7p=ex%bQ`xwC-tB3_`-J?t2V>0>#g!+0I|MD||@t=_!d{RNtj@j=mkmDj>8 z+ZFNs^kp+!?-$mCx^$2-9`PuWNAZ`OGg%c2XpDX(B%|v+cn1g#{}AVkra;{mdws@` z{{)qs&X&I3WjOJPW3Fj(N%47YoU&N54J>t;WFX10b%zR~!}e2kXc^IqIrM!WP39WF z9s{DyX4qZ!+xgaw3(3V>;6csGnoh1ghI&T4FlAcxO?15h_j0L5;G%3sbg4972+}Z< zLtkJeC(YzjFd2|h?%3^#r)y{60T>w73?M*R<%c{qEP!Y6e@#kx5O%}DGA1D+$)Y$;PWgLrgZjJ<@{g(O-oQ!)I;KOipG zf?mv{DAI_mj*v~0M%t4T{cQ}Glyw8vWfXb}pEc*Gi>(5$nS|hR2oK~y2io<{ zjYy==_k@gNn399)wIW1EsS!RpvVdni2?eiDnAG@ud6yz5EMe#>(-_vj-Iy+a(@nD> zMbAq#bB7Yi?)UMWIHQ6lnX3nY&X?LTX@R%PT5r3HZzUKQ#-UXZQ7zXFs7zU9M+Uc= zbNW0uui!flTycQ+CLNm^TL=_I6 z9V(!&nlx1^&}>QPbb#@$6Bm-rNb7ws$?@JwQQ^pTv7x|;QxQ1->FReF$<_&O1-_5> z{$D)Xd#^tnPN4crqpm;BwS9~7HbvbCq%Pap@B)bfq2ad}dQ3x$=D@5Ie`qQtvkd;N zIlYWZhcz#|<@`p*iry!DA@e|n^hU)wx3~hCdtpw%)O*bGUh(o-uqIzAkoI#j^MPgl z4(E@7;UA=Ig@bFyX&gM%WsO9h_fDJZNzg^@5zmx~iM1W;e;$FBeZGG9xy}_T$cE7> zk{x6$wW{0qm(BR2KFO1W#~i=oRS@V!egsmLRF5d!A1yQui6dK`@AZ%|J!|)ETosfzSdX+;H>oo3RJz4?+&=fN#8stp;cBU{OS)K*kj zUV8JRM*^+7Y^8_9S6a;iSB?RuVkCBTb$O4{*EkSyszTVWFgR=C003 z#VfSnX9!%TPIs3iTu3YrSN|FC13t<*17CQ3S7qjn(AO>x`2de#+{?r=%snZys8dyo zTf$p8D?MLWY1oY|!^zEKeED?FGOLTZfx%9m{XE72zyDB_reUY=JI*<__i@us4GZs; z5IHj!Tp???5S`B2UHW~{6aX80df4fCiJBV)^2weJpR%>{zPVcV$mPn)yv%`c?fe$`TrNb39`zaY~g4s?+}2P(WyKp-P3Y_4uf92{dZ|I?XEb9-MDr2STd(Fn%HA ztt07w!MCMc8~Z(zS;9mpv@by?ht-P(`B_Uok!4tL2e&n+qB(3n=isOYEZstg@1QRc zk3{xFm?hyI*s;2)O!5nt*|QoI4_IF^(dzmx==CM5q#8ERM+NE}9L~5Y{7ssb^|PU% znC&=``JRsGvw0S43^{=D5)BwWy$lkE*%+e$MNKn$i9H3yl5}9D^{2Neu!Bg*m-cY& zb`Y9tbTh|fhDgd&$0lV*Y3&`qx_PfBHhLK2S1HM{nxV!7pZS9)U`IkCzw>uW0gMWV z{%8Hlbe~wWK#i#uZ(=m=);)g!ODG#5570EOXZ=JXuLegE42WRn9ICTq1*s(7<|?i1szX01czUW=5{Pg4 z-#iti#q=nGnXFftU6L-<*iyeQ{Vv%fVbBn(lgSD}CY6+0%6A%@OUJ!91)jJsA&mV9 zvEHZ=U%Y~L5wi9Q-TBB?#5T5(Z-*wIYqx_CiDjvrOJZmD&)-{|@fRH6m^(dc}EyUm`$ znAOZ1aK;f~L!ZutqLa2!18-5i#Z)g9ZHXlnLni~Y^fiBn)2%_B4Fek*_<2Iwb0XkvWbzs1d}roD8rc`~NWd=}QKMFlnvoA|LQ# zAC53Uf@ZDiEc3&N+E{Z$fpcL_iVo7^wfZIxB9rQBUVR?-)=<3{5q6mM{l1&8j^*d- zU1im{;@{w)27rnDX(Sa$DZ zg<#Yo@Iu6h9#vRT(08xTuL;m|~$}F-i$A zmy80nA$;F0qGB$2@9;>_1LStgDwp8waOrH}NHmpvvY(LnA92aXh37`tu35*g~P;sXsm7vr7^3UmWWW}AA%{*VNXKFxs}Ef zIpt`gR#U}4y3IbqHxoBMntT`&u1PGy>p+D7IH4h{as`;&r!&3&>8PZgX6nk86<3zA zhVRq;RE*eRNz15i7y9W8iunT0P(g=M9Kv|EW!!P`H}S-sfiADAAF^x(xq2NE5jf0G zvX$x`A7DcUob1nb&{k)k_}WAisWQD259(3%yyx zr19Ki{-uo2b!#kj z4d(3GP!9I8sNFfe?g}40A3?w`0zjEcK1?!@mC9jiHZA+T(Rq@mu zC}pV-SMpEtsZemam{s2~u|v58lXblwLi9 zVqLi7|Fh}74wTS>Su5a-zL21rrhdIH0+Lrata}UgxKJmj2U6`pThujrB~dGlSk2fr z=kvB)+wNdd1z63)8!BU7pIoF)pp^JT5eT>ONontNo+Qu%LupBC&TUl~ltp6HrC{yg zZ|0HJ%o^3K*{t{w91@ZxOWEP9y&C9&87CaXy$Www!3*rg{lCjF8thv^ae4gidQ0gm zMb+IXT0n}z^!1-=eu=Kkj(%A?oBD4m9VjL`D%mHR5B~2b*AU-L8L9YEh)N`YHTvt`1^meZg2?jx8cD( z*wSy<|7PQGu;7^(b2bXwzJ)3OByal=Q8dyfzebIRXL_#xHJz!^+2xaNauw*z4{Mx#voQ<%cdh zdjf3wom;5P*eC6}8U5Ot9K7oq&#%xXU~^(^l;?npnwb}>QlSa&@-?5DNa5s5Mi(}d znI$=J$y+iXA-|Bne%A^>ni})sA`g@H4I6=7GwuPFzYdc&db}}mVlTR7TTajX5}YAH zZwnJXGcQ*3;H&CC^D$W)de73jg>W3Jbx40>|Je$}*lgC;N}far74vK$t)i{PcH%^W zZyVz$s$bVp;cefufj^4Y_`f)!@`Rf|=ug5m=cHIBhsEy^L3&0tm+Ql85jb0_t?`4U zTC`0GP+As;1@qNGsFn2AW{KIj3%y;oXP+(##2TB4OsG#GFybFYIc5(hB}ID7gJWxC zHQJFeCRWO1)qBBX(qvVt3(%P6+4#o%`?N;^RmK>UgyJK#)h8*~f}#GQw6{b6hUe$+ z@bHMzhV=L9%ysuNg;!LDp~L=|FDH1G3l&8VV*Z#M<$^;JPY-7(rh@GR!3=0EOW%Oa zzmjt4RP?>aL~!4SBk_Nu?7g8c)&g=<{Ao>~Sd$f0;fOsGgcRa`W-W)WCd(KE>`q|k zCc`EmzHBxV8ce-^_SR?$v>mkx*rTuJ^}X6 zNuRL2287+t7;d|3#Ro+wxzfOetdgw{N?f-x`EBxFgnKr)a$I-h#xi(BUV-4P22lx1 zsl+C#dn>co>tEbHZ`9BCSRQE~pO{}um@m?H@2|6|>qUd{W$XdR3dxKJ%od;`8A&E@ z!D>SAbmaNI76=H&%nMYEvaT9V7)brSbhaJ;@ekGp1G|WWx0I39OTIq&Wp;-H64ZbZ zR1Lyq`GHA}1%1)SriX@6w@%3Akwh1$I!&IZN!%+;Y(Dssq`FA_NZ5)MvTjt zgLIx8`?*u9R1TZDVoHy*H3`z&(=Y2DPCvqnjXv&>MYLEcDpIZ|psi!?xHCtGmolD# zwd{zKI7_-JuNE%RIU62vm1!vXoWPGv%v)nnp>ig{q@0V!^5E+1X@2cnomnR+RpUJQ z31NqC3UKBO>gEH1t+Gz3T>?Gc~07x?Cv>r2LOMTeu;;RtUv z>M4SH(3iuz->?SxT&L|FVv||L#I+*h;htoGGi8VQpCkSM<(~h-!z~fTw2n%2OwDCc zBkYIS$bJ~d0UyI-p>6{F+D0EVV-ty#Dl2)Wpc_K7y2JM-`)d780{WN~^vZ~#51H*< zCRZ|SrwdR@53&Vt^U(lDA=!u0JX$ef@*A_?Su=ZS?$~NS?0Wld;o=ErdjpQ~KP8oq z#n|^Wgi#;H1qUebBcvP_YKbsoMRdYbKo>vOZsazl5OBFrW?+tXVznuz0MSwU5-SL6 zW;M$?C^6PV0%Yy zT75A)W}=6>w+6&`iu+j?72@0_GNJGYhI}BVa#1SsNsLHxAcD^lsUH75T6z~iN)@zL zM@=HpEkEU{K)m1Z&uCOBxe)&mWzrv$zlB$aep@6J;q8F#vWqr0f=)VfFi98$CnyuG zJD&pYlv!x-eqTD}@p>FW+Fexi8Mi%uE0tXl0fpejk3peZk_22!GTigJ^vfg5v(ks; zN5F5Ls~N$xVN2WyEzRzd=_xL>X}O*$9YsQeU%P)pL;ijT=-lnj zmrk#5_nW)XNj(l`*qan+dzYVOx8B$4EpP!_iIz5s3t?2+J!T4{=z82A=?XTwh;N0qQk3wvkZ%+5(K&q5S@W(1F1FePFZ9i#f z$9(U@{NSz-8h`%h#w+irzl=$RHlf6U{2DufhsSnaHFyO8dgdN;(2UP4abG&`rO3-~ z6Q;5`{%WDMTx%(7VH!>2plv6SW4`q$crfBzoo6+h>3sGeqJ`9JaDeU^o;Qh6BNH?f zaIq~3OAy9cqz0nV!}}d}%Q@YcBZWr2OQ+shG#cCUMfE$bc>_y1;Jyf?Zg45+fFz=#PC5P^#JAXPk#hM6QN+sx>+4c)~ z8XL*|D{wyWOD4EPF$YUTf7A1|NVCPB6cVQz=vY5g;=&04C_W2V4P@PkP~ee}3TsVh zKA7J4F-Y^Vmv#^|X?&B+HllaPD}(pXNN7R9NcP0Kb-T2YMc}U?p>_SA*$Ut9lGrPJ zY+09ipYYd4dS;>66pLsX=05C-EH|Mg-~W^xH*ey!%G!v!JI1{Gz35Q8qk~p$6h;$@ za!)D(L0f)NZ-;Io2<)ROg}G%awEs0<5W^F<%O`!uh*tD?>w`X7sq92c>R17B@wsdk z%5gkp3Xp6pM0meSZS2VUDlXP*SUhEg3?b{gYF!_#l@Z#QQLp_=F89lu{Ozlh0ocid z!x)-*g7ExTA0iwcL*gVL!pQ-Ti>z@wxv&H(bK`@T zI;c)Wp8bIkQ@1C1s{FT=J*KZs|0aHJ?M+#_Rfbd;@8GCV57y*iX$r7o_5{uN?`IIk z_6n<>~i_!9KkC^HlJ&*VC=bD}#1Ji-{m|q#%TM zdnlB8(U_7N_9Ee2@t1t$ifOVMETtOiG#)$QTAJCLesd_tbRgSN&{*MW=m1#uk1)bA~8%T2Jiw>fdQ~)Q?Je+fmDLUUC3A%{)jevT{R##_H=z>gH|mF}=4k#5VK) zFQqM0%ONlMz$ApJfa&}E!8%I5y^&A=7ioW!K(9-o?nA6Nu~8ZY z5m8x9uwk57Pe}Nqu_pHeW2}hjLx@$6)!?;Re#;CBr813u4eUJ?ogiRiB7}iC(mOq!Y~?!*_N4qN@zQ~eKnazm3|%&SxILU+~9c@lBRI++7UU3 z3Brblc1^{$dvd}b_%376llkcz<_xL5(hRrZYL}kHFN1gJV@y{uLO%*=foYBJwGcXO zw>)LQdL<1#v!3N#9z;JxoZkh&ww)0#_Q#AV=X3rUkdevJ6#{k zuNMQAK|$S=_~2)*E15Dg5I#w|ym(7(aV;ft6n-Dz!s?UkLbqGK$=d^>V={=ilO<5r zt$menjD{a_nBU;MXx7T#5BHesHl3+9II4-^)))7etMCcAwpl6%FL{qgjsK^JA$br+ z4N{^%Dv64cWjTSip%$rNAP0hG{eJivFAp=mYnzoUJ$qWZ9MtQ$bTz#W=J~q&e&WwE0Ox{ z$7=Y|5|$bVGgF-&s86`p0e`+TYK)F-$CPvheg!M8h%~%{yNx*_1fc%$hHKRpIbQnG z^fcLXhuNJT;toeR4BFzn9$7ozHTQtP!RMF}Y*O!E4joU~jF#-*`y`C$Vm~my_Bh!* zl?cD-X(tY;m9jG{J3D@~LZQ-M+4I96oz#%2{!-P-TW$Az{1J%5lbfrIqAn75$3kPg z@gaY$Z=LKd)fVIrY^lHx%+*Zi@>YX%t(q#xe_w9>!72wW8LI|l?T8s>(QI*Q^7gP| zs1q}i$xfy>?u&`I9{Ii~W^iW#UOrj?W46sVGKmo_a@gt>qv0yST3z! zv2fY-93D-?M=?lZj5MCeOuN8wz{mE&!vZGRbH8uhYhp{dWVlxoaLG~(F&)JwA(;BR~cQ|jw3>S+u6jq&X+OxD!2q=Rw3k}JCMT*plIlw z$y{#(vG1NQyRSNSNpunZvdZ%Tk?1{d_vKU?xR8 zMGZi_O4_wa3-)b41r@hA4sJflLAK0PIjz%gEmkpNNz`EnhyWWgWDS;Drc8`1x_WR-~(Dd75 z0=JEJ+FQ?f@5IcG@YJ505o7e1bY2N#lU_RxodR=H|DM4P`v4EN`w~*B-!la=;s=@@ zf3eSeXEgOC{rmbG;EMgHY0oxv&TV2R)?VD|jH$I-Kq<-^i#5Z6Jn7sezpTLtl^UYm zw6iG_{7eKLzlu->g8?Yr(}nJ4v(u(f>d5V-nol!Ym07DQNx4XnZmAy6cLC(BB@Z=2 ztP|JVK1yzBR)#w~;V4F>m=!aGJbfet^YQccWAp3T?-IT0`% zDk1hy8ng)kjVTpnTQ(rCJuY;9y|5Q}BUm?WCN~SiM!asClsaZ^nZ`y0_@y^AMwU<` zx=eNT`!R;RT>5|}Zc=sTZ}1DZFFSCHLAAn;Lma z3Rkj=jlYU+6b5#Q+LB{3y5-jccJrgFKY-K&?#5X0WH^{_O=`fpFiL`y| zMG`Hq+L9;H9Z^d+Y}<-sn2W&4(s7O%;jJgCoL7D!Y8vmIQdpw}RaSsay1-n4PK|`K}Xgy6?k@y^A z;&8#^pSmQ#fbWj@w%|-wfYfGEG2b8*MCzxT=LA_ca0OjD%rOpwu9pr3uhhFM@wE2s zpSnF1pH@PT6B{~Y@bQ3U@)}9sRDFr&+<*ppw8Da#R|Dkap2d z5R~uj?gGLpNd^^Q zEWO^*l~ivHIdnigA_iw_U#k`C?*&wf%)D+HQhF^sXIxjR4LqJeK$8%i4scDlt%RIE z;+HgeCJW^IH$An_|5A&w|23RoI+Y#p>ALH)lCffdTewLlOeE?4qgjX34cn!-E>=Y! ztl-#Z*`+FWK&TS-zgKTTjbY&jTgr+wG@u!aR&+}alTYv1U{r`x&dzeJZ=(e_QPtQ0 zp)QoOO>hRnLJo_Gyxx{8%V1heJVfSxBU4Ka1w!9lg=rijDV$3o2>4vziGclj>Rpsb zZoaC}><&9lA4ywO+Qtfhp65jRoDn}rtA~D1yS0`uw2Y;jo{iscuOf+tXsx~j{Rxq~ zkjT6Y@2tk>w5SP63PsQiX>Rz~`~5gs{LWEjzXn`eW6ow75@DhxoiAtPLIcAfG%&j}D^t-}xODaD8gmRTzCwdH^7mod15#5)z(2vky`ihj_cv90@slkH+3Zek_A9gVbNP5TI z9}dX6?8ZY$cI37N{Degkf>-1cv(LoXqupfnR`$;Zg2rQ>2k#gRIYk&YKpS)`%uz@P z9h}Ed3d6ubTJ9ym$9*GUBgnJg`|0IoD%SH$YWo!Jk|VgRusswvZI0aVGQi8*j5|*7 z_T0m@R^}bpDwH*nD^W<3r`u6@dl0f(z|BWeVl8vo?Bb1fix7UXFC6!O-F!cQGoYRA zF(&PUTIoS~gocGhjA8sez6z~Ef9DM`2puM!SghsJBUv13v^s$wm)~z_eK|oqvhl&I?gZL*Z24w@ zM;V@hZ13W)2@#IvNI`eHIEYBaqWJ?&i6~p~CCzo(uuKy=i;_5oudZbRVJadHi`8G;r?31LF(D#Iyxvf~ zE!|F36kqXP2_*%)U#=5Cx#=%gVKd;0gL!shR{@CFI|KD61KS8l2a_3u;Vx?IuJiBq zKKBC9;ffH00nLKkly<9KY8BxS&hm@$S zA5P{l;1oA1_G|+lJmc{OvYhp$5SmgBLX;ocEc>4<5L9!Pvd!T@UYM{#AL8lQV)@<`RX_BdE>FU24$4h@6Fynpu!CGU1%U?q8 zGS@sJVBXDf8xxe!^%*GT0};4nV|{@}aNl;)Sv}KX*9gpCqhS}qP%#`J9`b%*WTtWZ z=Q&N0Tw;21>zTs5fClDg>|N58Xi=1ue!`Xu;PBqD{~=gNZ z`8jyvu~4x*kRN>0CVUpXBh_RLoK56%-o|(2D$TgA{E$DMZY_&d8JIS&VEcJO`4XZ- z(AU!O+=V@IM)fE_IY1|NJ#HBEh<|cpsh|7sa-$>>6!cEnaE%g1s-*wzV%GR51~+b< zziCJ&<0?Ni6fd*m3#NYekR{E7fO~Z1zM$JRJI6AXMmLYwsAszHeif<7CkjEiQ57xe zj%_V2hsi@?Zi<~q`$LdG3P}xx;qspY8`@al_zshTrq%vHJh4vD_l^BXw}^)$dFwHD z+6j=92_i)5aN8DM%rczW2GemT^6om^8<49YID*X(Y&{B$Mk@l_p99=Bc+%WR zQuE(ZDQ_g7B0O`!wvkXSL}QEP0H=963d0(lCy}4qx{hB`kR?tDJ?t88;`X`lAHdo< z27#k*c*Ig#pvD@5Od!y1$V}AWyA-5cJYix@f7vACR=RoTz4N7e2oWinwc=<}hui&3 zW#i7ogR-YErYx+)O&%Hs2ILM!cY#spNEY)@ze4%zj?9yS^~I z&=A0+Dkb6S2Z>5^Ku%sNK*Dcr`Th1grcts>2<*=L>-`TP)6<5uj*fa`)U%dgI0KVx z-0qyUUlah_Y|wpAl&~~zyWU1zeb?+bcs^ei#VBm3_=Aa~TPLX|ED&1i7IrVls6q`$ zs#(0!Z?X}yxq|GMjiCiAe){?c-tcxnWtrX{UFD0i^1Sq;ZptU3pZ&MQ%RP|H@&YMx zT5BRBVP?ccJ0J8ca`rl)*WFYcWC5l?-V+4}W!7@~@xEHUJ!}B@RX4X@d1Am5cS|0* zkm;&kNmK>Iy&EmCExdPyAGkY8t=mZ1;U+^@!;mCn($ntT*gZOr=@;-V!?cXu@nde%894`JVy}WFU_m| z&Y)f)2?=}0HPiPRSmdv!MK>LS&B*9=HE2!l={$zG05o-ylC0|ve@~K>dgTX%>A-a=d#Z1j8F!w_!OgFD4s74k$2@Ge zu~6@)y9D}(r+&@VhrR5?C!%rFh}hP4*6038)$zc)8h{Ps30FzGyUwl1#KPHWRmtAg zt51##O=?LPmZlAga=JL#Dder(U+M- z`Tl`x<4P)(0z>rds+%roDo*#1MDKw??$!y4`9{g^u z=L{ES;ZqC^gm){#`#14(gUUW;aI{zda*S}S2>6elBqFm$FiBzACH2WfQ4D>9{w$_) znqLY5obD_2Qxuj{x-2>x6p5Sg^vIfpH9ecmgM9-QPWp!!M+cz}RpVZ`%gsP>-wgy+ zwRAUkEcVCL+!C(ZQEEA5KXfQ|h`La$6!7ULfEMdZ0b+%FAWnlL;K+SP+Zgfu`rFyS z^pn7}i(djJ`{9k!!_hW^OjRGSEPk$BfdzMhaNVJ4(H_$n?@bufw83&!{QBF-Fv$MR z1IR>_Iuy_V$QW!st%l$jbpM~Day>qe#sU9gFw3QpHzxpiARhqPiQFD|mt1oy02lK@ z3f{dol$bio*uy>FDI=r5i-1N$z&b9`EA}Xs*@Do!o<{BklP>aJqP!NUX*A>@L&=K~ zwi~hp8Q~U*`t%oABHv#s{zx0B+&@t7{Sif!<2Gm0BW6320GtdO%c^5Odzwm!8J=OvjfXCX0 z5pIRVA!TF-L|uRHw?~DZs~M-X zmcOzYikX+p@c*mn+~b-4{y6TM+ZV&iCD|zVYf8y&%Pp6%ZjzBGLT<}lG;=3mQCO}s zQcSs&`z7VJgxn&Rxg@zWF=6~Z+xPq1e|wzEVtT^Ww*<|^!#pXAk_kevF@n`+zcoWd-775)BuEipjfGE%sc}s>P zS_KOAxa_|~9;@(2mU@(Fm+1#S{b(nfF-#X}WAz$nhN7`QB4z-r2?>9;{ea^G1$+%3tbngh=ULSB z?rwqf0zSQ^ztLlCo-*-IvyQKYBo0{&kQj>?RLJT4oXX6^#EUmIym9N)(+fW;ZeHQ6=nc68O_*VPIC1J zGoR-?MaoL=*V|H%{)$YPVVCw1GLM{)^ikb`)qOw3E&Vj&-1L>G=gvN%et`COgHPu6KsZziXQoOZ*3<`^WrQW}{-Q?;;A9*YY zp^nw`t9<{OJ~1SlpFmf2ko$i{eUn3>S5MG{0L+4e=R`0N&EHOo3rGV6u{ zeb9jdaw#gO3oQg4m|X1?!V+1p$b|{If8JUTe4-ikw2S;ebhi4MB4E`&IIBuQPAe;# zVc8W$CY^`)^wQC?2uhI!SdpoN?gGEtM(j6JV7U?rXOU1zCp}9{vad6O9QtP!HD%=5 z>gHtbD(fvPs9mG5ovrfnBE3-0w}}=m;}~d>j&)n=dN5dOXB<*!7-9wn!>iXO3!3@u z@fXdL5H8lO28%i-z7!Jr&tz+)-_IH4F2%6^9lr$^wdb8#mnq}u)g%79!LY{LTC9yW zO4(_&+2clSj_oB5%Xx>ZEt8w7TquQk2mgKs$ndPxddNbedNOtB*O{udWnQg7+Bw~v zlkBm&8RIWo&5m-Tx+!-DAFt~DXiqqEWuGae%*58+R_5f2rhoPi)D8iCP#Cn6oNDQn zlB+KB#0o;xG$x`)6uATbR0bD1cL}mxT|FBw7bgoMA8tWuH7iPU<}Wkqtb2^jsrCe2 zJO0kW8?vsXH)#2tvi(0_U?GBCs@_km%>?2k*cOFN4XQ`$kTpvU;GNkIzW5O_u(@Kh zd8I#2#}OJ7Jn6JDOiO1KJlIU~b;^A41!vd4tR0y8 ztfE5cQ}D8LVTOz;NZO^12lM_1R=H>BQdys&b;CR3FZV2rIeN*2Pr_cNSw@d~2HrkW z>9dDzi7OfGaIA^Gt~{U{t7ZhetJ_rG84s=dL|Wb}G9nDMQ$lNlLUtc7-6Hv-^&ZM1 zGXqRBffY2)`GMow_H{04r5iKw5G@CV?^_f1%V6(3JWo=Vs@Lj{RoHRu(|8oOq3E{- z!}N_05UwAiR&$3n&LzrSxn@>qC+{4W9UD8!F_I7k?#YN6mZ*2acg`2S4E+4&pQD|y zfO344(zrk7pn9Pyl=_Fm;2^O}@SS?oiahSnKGo>dVabbO`UQZ_a3|)2%ZtzWe(gdO z(t@W1zXsKYE6HsAiiX(4zTKGVndUjaAlHGW#w)2u9mdBl6N5i;@mx;5d0<``%z5B3 zT{XDf(ddU&9ylM2SJ4B?8 zH!34P>{=E6yT2a6M1;-9a6MUNQj&4h7C4r){4A+jTJ+1e5Z*DuRcSu_z)i?My&bUJ zWXk3*n%0@>tL{w!*rOE#S_0>&cW=V4cdcGj)ww<_Z3g}xmd78RCu7Yv17f-9HGvwl zm7R<4MATT%x%IZV36MU=o6WhAf-=fjUkxXnUE~!B<2A01IFwz7^Lis(+gmb+U8-|EK>npA$;7JNwzqxN5zZE?AZt$ zo`!;SG0Ht#3;Mn1TU<-y9heS}#M2e3|COfJYmEF1Om=F_5@t46sV1JT&FxYse9GpX5iZ^ zMq;L=`I;CAqYiLh@_;q?ji6$n76bJDOUZADXUC?MS?ajgl&^}>jRVYloaGJNGyJy| zV-5}afC{rA;Wd^qdj#7u9f_(fwTom_RRw{xw4Lu#SmdALfoUX!v$9k?m50J#>7RJ_ z4a(?1Tp+wBYD3g#Z`<=K`(hmS2%Q@uH5CVE(ynL@+kaf2b(CyFkVo|?|uuQ z?vM|r+yjFNue7rB`w@&mNI;qFxw5$?M16oHi7cmG0lppNbDuXb5c2GXP%MN?<$#sXGQx(WQ1U5LrE;UA@p+%j5U!rzp3k-#SS5;PY%+%FX90w>2*< z^uH)Z0;sGHe_qGi-z4t8?ASHQz+8QG*#eM; z6hV8`9`{Ak@Q4Du&6?sMqWRXhXTNW8I4gcvnzn4d;4SB_?8zW@g?kS@}=4EO7AEdh#czE17t zyJx)kpkUu)HNwfs(&fIVlKhOcda|`Yg1cw;$399Pu8udJoCb2AYfSW&SS>_RweQ8n zY^Gd33k^{@+$3Dkz312r9;HNzh%_M9RZBnL{gTB?)t1bd(z?7~AE>GF{kXSk)~ew2 z2dL`ctmXQNgx-NuCif|A$eyj2vq#usd;~L=Br~B%mDJ6PO`SXu>B6MCM^p)P3{y-) z_xl#Op8Us+jrgGI!+PZ{11(=(6l<_Z#GX45CN(+ti0|SLgy%EF+JWIQ^we*j_*Dw) zUq9{~Y2U+@xv@3aDk~e6my9B>ZO{z&h&A=AlCVM01FijbT_Z@L4SKJpr8Q*pf`jugc7N3#^@6*==G;UZXXDmpfb;_w&JyxuuGPg4}RiLSlTq+W?N5$h+p zU_fd)7K3~Lps!yV-3M@DL`UEu2W|6iUl-r&&`|L?Wmlf}Wb0bR@}KzShV8C^Q|BS* z{?Rk@#b)nGiEe#Dt&CjuXaOky@xR%N$KpWVMIzxPtBtETmhz zDrC<6q7GXJh|q0+x+c1PAWs~%kL4$wrhZLmhNb7KI^0&cjTiY!o0fIMEW9l`@qTeI z5B>MRbF9wdUFptGp@;m|v`RCdOAaG-&M*I91*Oe_e6V|No9)VJ=N%*@R9+Camd=@k z><3J&DSj_DDQ|URQnp)H)V=juN_zI7+>s_DdI9j@i4z^_bNEi^bo=XDgcAogTQM)z zf47i#$D;>HgU~-mUuXRNlrAyIi|Ik-dzVu3$T}u7+_N?LPG8niQaDC^#QC_RE@Fhq cG@e6_)Dknr&zmdLOu)y~$kMP(|4zjJ0H#dz!T|38ih$(D+;3@T0wlCqmgDyJi+j*4t!sU*u_>}Cwfc5IcqkY$oek}X@d z88Sw)hZ$jveK58$hB51JI-PTW?(_NHpTB<|k1_A6}&A`+=p;b5TmYjcfifg5|jIPKlR34(g2`_fL$B?_uTZK2#M-R4=GJ z3@&~Ue~OXM8IFjy?47S34-U8iy5I3a(;#)h{|L;EOIa%!fEVEIqiw;u+NWCNj@$~> z(hg5I{NCSVM5m%W7b#IL6*dPMB2PU zu$a-&0@p(lSr!ldXqeDiiglq-8bWNj<-F{{&%ZwTeEmX9*Z~cNdor50?&kl9}&)fmQ#mXs*;(^FRVeNFkzXg>a=MR6OXB zZ(X`?BW(9x;K10D=4G(^-lEa44Zf&v-L=1R&)Qnx1bKzl_ZCZ$Z|Q`2kL%TJIvSr#}EP2Fk}9v@7*9b)i7TlKbUctnfjks81uJ(9M(}+ z{qvF_Z!>j!>uahO(7?N@`r4sH&=(2Hv@6{QJere+}pFnW^6o3i3DB(FqL= z)ehCuh6Z}-=o%Rr>6|{JbLNZ|{|YTwxL=T4n3f;x$j_VneV>bWVIF~A{y|<)Kh+=i zb#sRX2brm>|9H^9|9*bXyJ24c^CUmmU(@1GQ0K=P9bN6yI{&^mf2iq?qsG^~!tVOq zzUbx47Z3j#Km*+~rhi`l|D5@sC;rDs`~MlKd-~izhW^K?|2_03>~5e1)R+IvAmIO~ z=C9}e=gGeYn(F+R`hSSxr=0&h$`>?nkEzbTl?L2%^M$3ofWUcyD;Ldggb6M;i&y*F z-6l6a9giG*953J{X1(vJfSS0_6`9j&FTefz{rv8|)qB)m-2~Fj6kFa-ywf~W;q%+| zffFTvn0p-%5jyc&CRjC5Mlx^Ti+(GySLZJ*d<)Eogg!MudgqaVq}!ouuq~tQ+{n<+ zJVlQ@BQl|PhvZ$}%%y#(0uwGhJTD_AWUeYE9HS~D{Qu8R3sEd4cFaM_!9g1Fh+2y_ zU>m+ZH`;&t*7~rY%1SN1-*4Hl_i!*7&kr&LqWMGrl%K_GELIdrG1 zjZ(sJkMbk4IiH_NjO4AXU$E>FblIvrxu8Ta40cgkyC3DElyo;`(sFg|(An(g<@k<{ z@hWx+cu6{IH!l|%&I`D!aFl=&OmITdb~5@%-qfj` zdt`uXi&2a}__#{`$>su1e+Shd*b$1xWJkZ#6hVj^DTF{PJh|S+=i5a>bdj$$ztkxlfABUP~+yh!>1y7 z-5k4BkeOv5&#Va)$ZHN~OJAJRt=@PpxCniP8h<%XY?miUT=6v%|1>gEG-sg*qSr1| zPZ{71hA+M^Kc|Vqn|oT*T)~{XDedjG?uYp@PwkVK0R3iWx51TrHm6TO{g|p=6QC*W z^j9(zh-~hlHC2?Nj@R?Pa5@-G_S_)l)ity9K;HEyhnV#|1)z7+TgL4sgWm^EofV?@ z^dx`DNAm z*-K;}#2O>~MAYROZAm30X$gCNbTKbMoH~PKqyFvFwsMT8hl+Na`png!&)` zjxQ`Fy;lKjs4-GR9Ek(i1l{`F1mr!pon=zUsi>1_W>EHc<#2hPO|!ptK^GYeL>fwO z@NEWACLVK-K6LLQTb*31rG+jz-KWW>iRAIqhojuv zL~{~?x1S-;6g9@(IKTElB5<{~8zuijK0)3AVJ5y{QcJqgEQJK8L|m9kcr4$n`DDlJ zkZGraT%j`3V?dGA(2UZ8J~Xp3hPe-tl!8;5nKq8dB~fHwUM{k0O>@EoUFSvPcF{En zq$#uOAloIfHHSUe@qF7Bh>Y($e7cJa^2GW#_mKP6C%*h?0r?vfUpS(_>@_?!&5=iW zHeHS?8}0*@uQ`pumFXn}2d9gWxX(vAnQ{Iq4A?)|QaNb>0U%ICca1*Om+jhSdIWerS z6fIXFl8=|xhg@74k=gu%Tc!w+!ag?;HnDDcJQ(;mJ#0DObhEUaczD&}TqY0|f-4y= zs-;tDSoM4(2Z&-RtzgdIrJAWox>1aI6l34dWQzV7Kd5O6WpYj+nR0*&IFNIwtn@FZ zZlkMh1L}ESIi9PSddtbE7(7d44e(+qnkwCE{ixnw6&`hDf~N;r<88LukYG8_D=An= zWa$*Mq`7_zJyfHwMep%Ktn>`AUWG<6-5tYZ|8*(vmlh#?UlEeLeGbjj2`scyjlF*) zQ)g)Fn4>rIFyjQa!tK0MDHs3(m*MjgH9v5%4y1Lk&V{b=FJ}?Rnt(1#X^=Q`(j*e*PJ5S zzDe)g8`ipt>ePC5BRUVzva2F``Us;*;8(ou|a-D-*_$4 z9#el+|LoQqV0iZm(-y>tocJD*Hp`w-)=OMlY~npH&sJ?Snh7S+E`O*<&Lsa%SNk)p zHLZ?Bq=of{sT6F(Kg$>N><}y}r>mUzwp*PlQCZ&LcCq@l^GeByX zFXERCuA*yJ?#aDo5YtT4x_;Q|EK`re+-2jw+lIBlJ(6DUP$y3%H(Nz5R}az8o!QN~ z@P~ZW{6`T)%R_(JM{d=9;GaQij>a z3)VZHya(@ud=AnJ7xHpGm&7idl`=hO9S!-Fbl8uJd4` zdj}_Y=&kArE1B4rdN`#8>Jg0YM7mdD`&)%B^8N@M_SB=(*_UMU_Xzm3Zdxek3Eb)z zzV1jTx4Wgd%7;O&Z7#s~waUji%oiLCpD!kjYXmSdLLCtE50!71plKt&a&A+=oJkZ- zXAR?o{PD^9mQcJMHj%*r|A-$QEYad^QawM_lAY~&a}by-TO1csC&7YzH4 z&#S7m+MsH`hFz6%r_vQzLkz7If&}yV!52@*+dcN#$CztoRVRA1Y<#vLnGUDRiR$h~ z1|Gq;+;esWF?y18^by5&!6_xqpn$u}@VpXQe_j36c{fDRNZv`0rK=_BDAB0RVyGF# zkx+|EAwcto0PDi(KXuVuj&!lS|Kz7i{tPaQ43(_2Q)-pA zOT#7*2^J7A6d02D=&ENW zXA+UNeW0Fa&*9qUK3_8o7>wEdF#;cC6kn+op_=VoSHlwgjHo65CX&U51;GOwnW}wzNJLvmIqcw$ZbfWOfC1@+$c z5C_DDQ7H2dx&rL?A)4dkR-)8}h@Q`B>fID7Tetyw-PuHqMisVMUCxF!RId2hF&f_3`SpBQg}d zx9NF6Ef1<*`PRK9*i4t386PIhx8h<(qzbjz^_V@w#+ydta~|PB;tKmh%weZo8As+X zf=TkSiuIjIE|HY2hf<2^8U5v9-Gd@~~{Gkep&T-pvr2Stor)yiWNpoYTF{PuLh5A80PV((%$8r_3 z&EwR;#aC-p*v;fA2PlLqYLFAcu}r@T0n ztl2|uo+)7<-IRDLgWDB(X5$939ODA{Vh*T02oy6)y3}7xz!>b321QThM+PfJDEvFw@fapwp9&cE+V18~{5H%USVV z+US+#ySO1{FbDfFDlna|pl@|i!{xUCydkLSS#fR`nU8*^nz5`Sf2WE+40SjW z99^jGw*DBEm0q9G_5vG#OjjvLKaKZ<^T6HDljM_2PfWh*2Aj2fNWPQeYSH&Fsbta( zww#+po4j~yExB#Pa6>I6sg-0;IXCikA<`%cgA~m>bB{cd|zR&8w;tRLw#VJ+fmY~<-Fc+D6S06d!HOajxy~Q{vVdM-)Zyp6c_SZI;$rb9@uUEi6ItQs_nPnXv?g0&NdU zVM#g0!+XXX2)ia-9I#2PDn)@74wQp#7WvvbXx1O^ynLnG+8Yw>f552|qnjG;+P9HxOBX`(<36me( zHDSARh6U}F0fqX~c5?b4tKgHoDN|~Mu{7^J3S;Vfn!={#mS_cgBYe&Q2zngq-BY~6 zbVEC)3vVfdi*wZ^gjuI_#z+>^D+5$}pV6~Lo4L$_v9ra=W7V&<2Ex9j-2NBc=k|Vy zP-^KI>!rL{FjMBF!b10@5?==tlW}A;oZ25=@J+uAs=SGP+67NJz|K`R zA^5DjjK>GCNh$Z!d^K~v(3-PIhOCoV9CH2L92(Rxw8cVJ5jTTgL;6;S^Y|RJ21MXJ zLp|g338o2}M~isyC%g0~&cd&m8YrZ*_(=%&TX!H&*YzWw3^hZUmYbrm-(VGi+Uu9r zTOk3bxbs7+WJph3Y9)G;va<$%(ESNmd9&NV1ykR1(_^a`l-~k8=D4RUqkO0Ti1yRb z1%db4d(DhFj&<^gN)u>|ZoIC^W&Faf%&2w2_#p)tKErL@p$zJW=%63+o)M`9hF7S~ z)}b{IMa*Q`K3ht6G0iOsch>aqnPFnp&ykww**Q;{UF8R|gn3ilUv>;~CHV7VW>BC& zBtPP9aEm5DAGW!g+kjV@EX+7^o@-l%ZFJA)>+nzEOggca`gPVDUX;Hnc z%<2st=6vovG<9Bo>I2sQzFsfWRQ&pmr{WeD%f}iB2ij!0{Vvv9@r*H_fLGN~_fLD_ zPNm@GMWSWE8stKNzPnks+Pm4-c~Jz#Ch)eXvim7rbU#1*7w}Vr8pd5I3a>=yh?fER za=MA8=>8gDWf>{B&>}a7o(j`uJkI%ao1RpOrWov2HOKXCIr1lka)Ckkln`gbVk^^h9!y%AtZ+K& z--Qw1&W+A-(k}u)!5cb+mJx-u!b9Ma^a(nFX>FEWguG|oL@!=ZDg~#kX*w)3uZdRT ztLf4zIhz?5a?u_xQEukU9J_w7aJ4%Bt0%#5;c+b_5Vh_ierHB_@@9C*+(gC^5la*rp zC>hj?ncH@rK(+zE-PXH*LIGAhk2LTd9=M?2{qt?tF`asi*wkX~Wwa`mN zpmRNg4b7l){cdgGMtaVBf(fnzKGJ`rtf&moytQNt;k}SRNlM6#oKnk_c!4jHiqL>| zT@WOS6o_6GD1aCo8})w*Fi~%5yB|g!J_OaqDd9DDLzb%(bw22>DoNs>g#_UPx!A(0 z=hPHghZSsps9vlt0uN3ur$C7={YmN6q3Wm}$jsuAVLy23+7JFopm`cb?!Fqxl_cyX zfqq)?nt$9oln$L~4k!*G@iX6{)m<(8m|D;0Kaf>EZ*H#NCUFw_a#5hbGUm=r5UH6L z*BId6^Y&6=t4-H+g9D@faR?=FszVjd5v4T4WZX0f9~cf}{t@E7kWOARO&rJ^afqeH zzqYw!A^w|8y{T}XffNEO(r>vYlw#f|M9Gmnt;(avwL#QGxk3p&(!LJ9r6vP2u`ZRs zm1-vA<8FTa>o`LXv@-GmOtNPxxyMY<>sF7FKLK^iNKD2_CV`1B;2XlyV)-qUF=GSA z4@pyO3GT`rg)2X_owcS&^5lmiEeH)8XZs5Q^__t1EBuc-=}A7P<71XUa>eiS{L%ny ze>Rz0xbX`J*_wxD*iBqUY~pHG_pD+OY5WS1ozY2STzBeo_@+@KgIAee#&0`UMr(iS$do$ zFoHJX0Dwr*zT=q-570~HfQG*q_>9A~a`~T?zBFHck&Gg|_KX?+g3r06*G?Kj0(q4i z5e|Lw0T(h^<>N@cD|1=pThABEo9UdhXzqiOZM_907WYWz8i+aC*};+4P1Hgs^2v$g zT6RTY8wpstL-&3^kw7^X!Wn4PH3umEPRWrst4T4CH>OM#f=VOEsR-hQSBL|ebCU5A z#aj9rS1FG?hj9rOs&u{Q28Sn)Y(5(`z&*ELnwe=zY%7!Y5C#MZrIazBbjc;7e`hfq z6Y$LHGLoSKXK|)&gkET|*}BSHJc6$%hx&FfwqxcJXutiN5XV!ZGk-0A^LL@DQGFY* zON!4VR~^lFooc#;*WX6%x;)`2?b*8lrq}Z(8d%W2j=Mvdk&}J8I1(YwzR#_U$E;nw z2$e#3cX9UDce?eC;_atrvzTXxUrQ-Ui0wR3GTxAbDrtF8>=3qKtmzD$;%YN9TwXGF zJO&}FsrEQUZJEC7P@d{o(Iw#&poH#0vscPU!9wDMl)PWAW^8)&*-Rin3#imfGcQHn zGf^R?RgF?xLX_}nku|aM8SJ5e{8E!}wSoEo-QMKX!&r9@?dtB-wx-fCatLBxZOwt6 zWX+i)>|cgE|EzEQg|~koR6e>6q3Q7mC>kMkadj`|V)dXw8aF=f z5lh|Xo%Tqswos0-Mvr}Kg05CF)`i8~*Ehu<{}6OuJkfRP+ROszoieE{fKBayf@U@c zp=OI%xpd?C5s%fHmB+pbtA;@x*A0o^&!`0ToBOK2zN~xJLNr~YdFCku5dFaDUZ*MJ zaCzYIc2_5+(SGG`e`4~?ZIrx9Ie-(kI9(Z}wODRj&)p=<{BYG7&ssnYr>JBN@XQKY z0OuYQ=9f=824`@ zp#6K!89;)EJ*Apg8z_u7?3_`m{0bw;WU`jtDb8G&ttlNt!P9(q=Jwjc?LrpLxpww)=fn49R2F+$Le98b&=cot6O6U z31?P6d9>NrZa(=h4KT*NOOunaiYvZ~*$GPUU+WMZTdNfo(kp4e`K;CTwsgj4Nb`y> zqrGDG?X}zSqa2r<8L5p z21t)j7pIEJso_NoD_>YCYbCg&o?8nFKhBlVk`NNx^i%5J7b^*fDTKgAJJR|;eqU!; zqLADja&GYakunUIS%_giD-Wt0540Nd86gq_I#xK^&KO(Ei%0)kGq1&1Hrh6@MR)22 z`yp#S2f7-vHX6K{EgilfZmAMHWRBu}mY=LO84UZr zP=CglHGfkxR9`v*Htc{uE3H+DSNGFayMM&p%{?zm&h^9T@JIOe(ejLA&oAv_ufH>= zoE?xi=`T)UC zKxR><9CKH zz0yNcwiIRN)zd@BjBz;|L+%y6tno)id?t(IfBmmrQcjWp*mQlf4FNW#zAi_sP!fpn zF0J(u758cIC)0gv8K$&|<0qVr3rq^QUwdoa8uzW7KMBHEQE%zyp)XkI#11O+Dh_dv<;Do5Q3{b+1DLzZ6vt zpm3N0DoQu3p_R?&B%}MFK=z?>NBw;l_lo2-$=yhoqz>#g5KK7)BDSa}9+O5ZF&Ye` zudN?Zi8Sk%OHk;-VJ$|Hg@(&7O{5zDIuHK4;l*5x22n}DW4_zo;AsGUK4AWv(B~8NbJG)ZKMlQCLNrz`yAapAVEbs5^OQWf*N3+)M$b0pvY=cf) z{@14VGE#W*E$O}zz1kK~U3C+=`EBoH-MW3)8PPMzJogN{nIi@oQf9`F;3;#R>njnH zqP)uqk2~xg5Ca)}wI#)oC0`}VWt}@7_<1!iMMAX}SLJW+ON`P@=}ZP(IC*|VW~JN0 z5qclgUn%akS_L-beku&tB|KeOfn++tH?I>j(wZ!{Uv8~gJuV4dS)B->X%Q8nO~qq9 zycf<1KF|_0qiKa0n%`U+*-vc?EJLsMu9kxuiXvxNp`nf0QxSK@%!$6~M%81SL}j9~ zrc}QQd}W2WCB3pFg;?{Iq|$q!W^ZN`e|GdKmeIVyvgJPB!)lL?wAFz0NGHrd)XrR{ zNt`vbE&`DrF;v)4m=0`U^(O#_YIxc;i!|`(zr&&EzL-1{=*lW=JixNfD0f)ullG&s z4U&GYOw{I6-q-R0bC;datoi8lHvJ^(y+`8=bw-XvW3X@K6D!3CY?#khWZMG}zH#cj zyS%+by^MTk)}?80NBJb;rN?qPm7Cd_Mxr5Rr}iidOwE|~FIq_nD*`o%Gb-DL^YP_K zW#UyMzcDpp*J|X2?a7dgTXV~8QY(hs;Q`AKou+v!V&-;tot6LifM_sgXIDb7G;m;X z@9jP`&z$&un>1jyB0JU~-@Jui?J_viUNe{GA+_ok&V9lcSt-2U4;1d$QAuGd6V14j z^Q)7J^HD^C_TluOGaorE1mX;Wh+L66fVJ~>DimJWIRC=CA+%{md1Xx>I_f_WE+p;C zTVp**hu`5@k2uy$`7;-bLP<0;l3sT2N*!qev1kQZ0`hkHLe~&$ktR6S3F)Zy*hbdG zHk#-OVtp@Gl=93CxE#6`NouUKHQD#q3XI9JM=h&njg1zeKA^Wf+0xcY6Imt}3R9Sy zeUg`tyg3efc-^RdmL0Qg3AzY+smT;^A=LtuIh52ZH+dnerMk1?7r2bU zp8EsCu}KXwoSgURf<7Dmzu*oRPRPgCt5)ONLWZ_{$5tyxF)g#5@m@KSM{P$E8KrVI z3n5BQMel>}c4bHo>MOpNSCrLZ#k4gl_o5GXaJKHWRrka%;Vxqh@{u=8tO@y$R4dTW zq*J?F&_GdjlE?1jjZ=qOy^8YiW~}dhE|s#C=Z5QmC6t*b{rtrzwLCkBW?sZz)c<)}z*UECl-?{UflDbEuus8L!s< z73HZ^TNuf_ZW)Y(w=F?{J1MZbHTs>luUFd^QsktU^11I;hyb1$7}NcH3|+$!Z(raG zbfOLw^lQRe!P*0)i>$cNpE)k>{A4K<;T_s0X%N(*1iQLz0ot@6USEODKB|pD5Q7Q9 zBxWgT)cnOxm0*4ZV^$ON9i-fXMNv~cH+2E5k&XtvKGP}__Ig_*7_nqajGUn5uhx$F zQN%fM@-8EJ@QqFc+YZAVUIlQc01YK&4G-EI0JN-*AZa9{jfHHzR2GV15u5W~q$Alg zjV$6?6lYM3x1q!P)=bM=TZWNn`P<9++|8nG-uI*?(<|qRC(D}xFZt~rfv`piTTEN9 zSq*Y&>R+}tiz>|igb)Ca(ZT9@CZYZ5Wn0MX6U(g}Ileiw8n!SUECoCcWiw83*SU$op7q-d9Yw*72M&me zfuc+6nXtB4VpEGosQy9a3(oF|&p>snkNOJrE^GIOIZq1{Qk%N48S6fq82T|OhP^_%wCN^7AO!$IieY|N^tN(~w z0v)MQz&Q>M{|!8}Llf7RScRcjn|CnWT2#@A5ufN^Ew;W-e&TV~NV2qUPhb`4*U804 z<#iwv_(+=y-Wg79NBGWP^o92sQ$9rL-O1N+xmPtQhhkhr#e(9_qi0Oeg(hoFG$t@d zZT8V7tw_el$CO?SULi1Jq~R}7$!C{$-N+YdjHWW^ydI3W~a2lfen9|N*re6>S=1DJ%a zE+f07pCnwP&F;FRgh8@9kh%Zj=3sVmxCWc-gDA;AepvU+6%n+BV0fG2M1O{F{{G2M zp^bG^_P+>x=&8($ps+AJ7HiGIaP`v+Q&Ke1peFpp!*H|p80qCsqa1-f1{X}5TXM+< zl0e{7k26yb%sQ_{*mb1=YhI0qMv+6e6g5nf*5?f{ z^&J^SM@R0s@x`qGWKT(N8*U5<1unkH41sVrG8<`qAdCl05PqV}$KkZ*e0b-v%uR4a z#g$g2BY)%PEqS54gM#~xYsPO!oZr1-$CY>zw3l~j79HsBK>PMI!O+3Ie}^MeI!2*u z%8PnnD}zN>lt6qmuB!WaRz^avwp?1Pr9zC!IDJq6w~#b*UZ><<)k7n<^2*JD#%s~$ z3K_c^jT8tGzMqUfdgk$p-Ams`UW93N7_ErzjDN}H21xw0htf;Yq?fddf?9W;O9}{o z=DpT5E7oiZ-W_G|(6|$bY>0lGU@hpNzxOD^^!DVS$m0m%joy2^E~w-iPlDxCm&A-1 zHc2N&*y%DpPWBk)2Ryk`?9mRsM*>YK*b_X#<8E&wH9o%;1pBD6cRI&N+^qvS8m4(M zr^|=YDf_P0K}i#=ito|u!lKhJSmvx&TQxBTRrpud!$od!nvdaT#jNE|e_hrT{z?Y2 z4}|=Z5I^`CIS_FA@_R)8DVXPXRqkw?yxWJCoT>i#`A$*zz!9u{@-1#@niC^pXxzHF z?%I7VwR*WiNQSXE%zh+QU~@qx=+xz$dZotilOxZ=re!GE83E7sJl7pY+wpAcR+e~j z?Jn`h_i(M}9X5)+SMT$Z+)Yatud{QyYi1w!ktTSWf7GxmXwWl%X|uvmY+W(jPa|qw z6j_1nMv+#L^LnU73@C^jlY6Z+-4ic(G)I>-*RrjM*d63L$oYd`5S0FACt zYsnCP4ELYHgseC3^ATG=i#=YH`wjGDy}l3OcS&R;7qRu7IBW+Y&e)~;yiy(7C6Dt7 zsW5Lv`KkJKZ^mxLf@%a#qPrf}9GS*TZyfoHiMB+YpX|*)3})!gUXwa`{R8%Q7pE(z zCi~Up_?GIhz}9;Dn>Q+2;fXas56%O#z?AJ>OP{jszM3jZ?LU(wac}wcmnoaXi~+E~ zeJX4^7#4F$_ieBsBfNU`C>H|J|WI$rK?Z?&YpXF%75&&B$1r)>RAhUrvvJ}BH(FSWT3{I znZxC)7`xAs4`F>~btLR0_-M=Xh{@0Lw%;sqkhfQ~@qO{aiU8Bj>%9H#qh7Ipt=RQB zE>tmc*S#-sSJ_FPA2EOIg3?sox^GyGheLfhWeZsA@4X?LfU2J(jm+Vzl@g;ispcQd zt=L(-8NHiVPY-{39rToWS|hFSAZ*{l`^i2vg^_W1KjFDjqQJ-dlmTNLD%MoHAvn^+ zcwWPG!Sm~zm7G=(f--uM^M327FkirL?3P!72rXM@=Q8FuWo7=6g0<#SliB3eneHv% z?29oI_+N(XKPkJuDB#HL+JzJnG$$L-Ga*so;Og)*g8QO+&JjLjRR^3PzP!fEMDApo zWt00P+4;iUL7}6L01xlv)SlhHn(~gv7M)<|$eISMlaX99CpV?8l)6UKFk&+E=I`)m!CVHaw5{T%2@_W3rRPqb6H2e))H0h@~8J7k8ql z6rQqP_6X%R~F-O@y$feCubjVjCUvk+VNf$Rho1L)Rr?&GX*!DSt;ldEu}Vt-RWbClA^6LPv8Y zV^hTxceBMm-$I58f@LdoTTF=`{Lv$-+-|p;wfp_Z{O7sc#mjUgA9NC+%=NojaIa&? z%V)Uqxx*mN-&ox-sU_PJ_<;o}kCG(WcVmJc`g|?*qS%V3sHImk1~MTON|vE8>Ni8q z8=$d)RN}K_$HKIndU>CD15Km;(%+t{&3TK9|9a8gL1R)~!B)&|-;B74xTdv~)O2aj zh)zjM`5uozhu8>gy(`Vzvuw#BRHP-PXpf>eh`r<8kem*DANhb~kX=-*czPB!YH%0x zhL7X_q}R=9Iw^OKOj)|rS2g}%*tRO@Qj(cF7ldE?DkqcUK8cqI%R`O!`K^yQxgh98^_?W12|lMf$RxVMWs z!xM|~JNd{u{x$YzYNFQu7nJ{EPyEk-zAd{>a*L`e15u=YVhs@sSJ&ZndqyRg(-nEBEu47u<=VUe z;v!ly6i?_8!7Ug~cFhk@+>n#(2?+_w?g9b*K1o$Lyn zZG({Td0N^JYCFz6(bT=-T%8dpTRT!|^aP)Gb+@9s6)IO-!Avyy{rM419W=Jz5}M-6 zUqVI7Z7PpGNxr+QrAIKVe7rGOldYueo~tM{e<{8H zFm&%uv6=rufp43*UC~yx;S4qJ#MKa$CzCT)YSXz_^FE~`UfPlxG88mk=i`gygm)<; zH2u6&JRZ>0e_0|T+al^xgQjdi7EN)IM?#bYLNUml#hGU)v|voc0$1+#+P_N zA178X>lo-?(Q1Aw+^f^udUVd=LC%@K+2=n*?y4rE@ag@mi`2=`cvx2~;;EoY3d+g8 z*4yfosiVq7!%0TnPW9kED3lzZ5IY}|Y3`vWcfCDiw%W6f+qA}joZBWzzf zDn}eEz8?XX4Vw3Gg7tTlEi*l0yGQ@cu2bpAawm{XGm%Ve=2#P;i1x z@_FnAzOC7*Ts2h`VW>0=L9KPxp_~4`?78vU*=IS8-_4ty+e4i&*|*jMaC+^tdde?i z+RqgH#$2?`3Nyb?>`dF6Jk^rkCL(Kl39u~WsvLy7Z3!^f1BAfyPI6STX3|V5O%`8^ z*1G-og0s7-fSs=p@6u%Xx_u&ueejzO?-_ZcD|`hzRVb4zRn{G|nH#pS1?nW<+szg8 zc%7D1rJ=v^N)LtZRYn!Rnz`X|{kQe&x>p{F+qWEa6>h)udu&?(rEEdZ>yeUD3Z?0b z<%g(|{@a5){TmlWLgxQ2?EE`y{%IN&U&LA_zZ0#%^8=W& z75=@_JYne8H7`MYk0e_-Y}`Nv@!#jKt~QA})8c#*l%K%++CDcn|IWiewxTtsRB(67 zN&f2h8HqK`kb}HB+xxorw1+zLEZ?|X& zg}^%@?MmHtGPn>NZmR7%Io|BQd%<;?ckEj#)|7bxMWKz+2Sw%iO8#!RYOH%Eq{K62 zVdkFbOoYtIA{EM^lbfAM$YF2PZxgyN#t$lsn)0fA zguh4Ny$*0zEzMfuQB*1EB<*iX@sF>qwFs%H^5VL_B9EeSk$xj4rlMhS?VPXxvnDhR>FTGc} z)_tXG>h&TkOjLJaV4z6FW9d8La`Macf5f@JckFKnPnP=IOEGFp@kOk;&kIu+#^Mp4 z-}`*ct~lSMx%2&2R|!@rT}KU`5FE}Cr@)hNsDdU%5O ztg7vqk<9YYWQ#VW2&gXwAMogq*3AqF+8oXy!ACUDq&}8Y^)e6_+<#Qu{ddhN;k-xG zw4@{P)`IdQ4G3j;PptGgqkaO@*HEyPy^yFju`5TE{_pa_KVz<};HQ(O-w#H0#qhgH z#Th(z#jzLmfNsjUB!mOp=og2MqoIP; zDK&$kRWaz7uAx@}f_p>lDpKFzJ8<@{Cmt`zlFy`U=>0_?|EXihGJMP}P5IrH(SEMHu%J@j#fbQ(@<*yQ6cW&L&1>1$G zEFhq60es|Iuf^%c$?j20DLQt(Py4r?nBGe@ekcopLwZ*pjbH7*B4?pOA>?Yy0A{>; zrali*)l&%lH~vq{gXdgn^_m+Byb~1Yykp>Tk@OWgN5V~MhF%th;5@rAbG!{`cnEAI z?l2zmJO3*TZI?|g=Qd7?T1gqsTvVN&{v{4<4rgxA1~1)u;z zF_GS62+3#xu~{X!{<>R~zo7O%a<8h`DJ7weyU12;UR+=AG$zm$PIbGg^2m@L=+5yP zn6g6IGd;9R^Fz9MIS)jnJ47p=`<5RKIDmT^yhqMQh@JbA*&q{SqXda@f0&s3y6>oy zsz=-?!($tI?L#_-4?!~>03rdiEqx88ivQgh{KZ~RDL>2>eSQ7 z^w1>TK&Z^*8AiZ~cUt_+eA1|DZ*d+S@;M>6V5+ae|M?LhbC`AKuu?^_L52=|WS2?h zAj2TM;wE=5_%i>0YC3$>kbP^0H}Ghgi49 z9&mgi${Whuq^^4nE2gwpetpg0{E_qq_RWka<9~#zpF5`kLeh~V*+hQBqQ80L8M2=k z)rf(uHO!=g(2b1gqR>ni=G#>(2=g1fQ@_t@;-o?R;MZ_(fQ=UE-Ujyjv?jma;kW(G z-UL1SIzC6T)a3iwere@!8S+gQ<4NpuIOCf-jvlLf-19?hdscNqyHds@pu3V>kwS!n zULDq04Ww0?5y?KmtZ`U8bW3|D&ISiX1 zZj7Os=uos97N|rW_UFD}Xjq$KV`kHAW+R`L2Dp=^)8GfCy4120c%LgP+uj_&iRrUz z+LFHCe;hg(xB&L{W9TCu-HP%5{g{b6DS+xOBW9)e*)zBQ_#~s5O1$!Wv|;iR zbZ@B~zYVQ^HO1*BR0%jD8|^kQVwh#>0sCD}8El!AY|o)9mY3A~dctaN0(EnW z=zqsRJ<&2<#u5@G#nU)w_OKj*=q4*Pis~l93e$NPaoF*srvjF80}YQ~oJYzT-$o25 zCilV_x^6*A-LXwR+6q?5(z*=AHv3|u{zXr1&gvO0u-ulA-Wq?2!qSRunE3B>cf{l`9?v+2X9>;xB1+=+- z)K*b^1=0%Xh%WvFvq&a(4utx+z)sgrxRs`V3UjZ>79D&38^=#%k8Ye_r?5a}m}OI?V5srMGoW^_YssU=+o0=WgowyR&Dh>rh(TGv%&2 zFC!V)z`oei0RE!t&<^W!NpCP0{L7{v-OrPW%N0Xwf4g#f-2PznkRfw*M8?wDLEd$* zQU6RA%32lc&hN}+Z+XfuQ1$F6erSfsQ~1D4wNCi>)?D8T>^j=up{CMO<~95pbmUjb zFH`)gD#erEOpl_3He@#YtQibSKp1*N377T3j8C3G+V@D=S$XuNXF~_jZ=^e{uaPun zM!VgY&j9fkI^b3*aehS5^tPoyfz%DXv^`oVa>mzeY-RS?J`_DC`OR8F{&5 zD%U%*;yxBc8{%xVahqP=2zqZ#b|eVCeFc}Zhl4U*_mrLkU0$}0Irp&Cj>s-Qc(bk? zk$C)QpjOOsnTC&PcLtd+!g0PI{91En&1sXg{G6$nw<|z~NB=_@?Khsai})Z%i80(0 z9~JBVb^giDy&4|@UF2Q%3s;n#{vT~kJ2UeCj2HOW7Ji=-K?&fma^SCX0Ha+4iLErz z7PqOEJ%}K0D(NdG>D+F!1gEU$)GVaI&A#H|4#A@Nm;4f`X`~Am+H=tOi+$_GQ+EP2 zH0m|~i2LRLW9+S?qTIhPU>X!DK^leLTM008U#dQC<%!HQ94CHM7ne6 z?(Xhxm|>WCAMf}6-nG8pb?>|0|7We|nNOUv&))l-hd+oKlcsK6;Pkw&w-=xHR5`Az>yZYz#xd~?cVjYHYFS?t4vu9lgG?ikie6(B1 z!0C%y4z=4G4c^A+lf{E8G$Gon{kRiU74Jhyb)>J1?$y^aMDRn7JV+6Tyl6hvILg*o z9#{yJeQl%@X@NFc{rP&g7vBAHv#**aC11k%b4&uu`v@Bf>g*^XNtv;_<|e!$idDAb z`{{~IH^IR-a7bF)ywPiW=)^ydP3={Zi?fp!B z2LtX^A6u#zb2|NN`!gO%-$AeVbi1WP%>Am>gd3rQ-FU?BT+X$|FUSh8QAZ7rWRcG? zc6{x_XREs#carZhAecYdh~+61evJBESy)oUK2djs#+@5a1jD2wCLL~6}x;=*?K+F-!Si2z!5x-hmJ-e#Dy5QB0C4?@{cha z6PujWP=qwnI6?njiiEyjdxwSHlhl-OE0+$bZ7FQ?o7h_}WmtP{uT6^aqjaM?&WH2M zvLQli^Hj^y_rS?}MPfql3NMKtO!i1t>fPn3VY(I!evjT>4OZP@O&v|6`sr-P-G?L% z+7Z7nBmK6dYl3uR&-$`flD>MZgKZmg?CFx^H8(L=)^+h4@xpsInB*!#=$G3r0ah|e zK4PNU89v~VCD~Q8;?1os9kKi7f3kGtUH?DsiMm0b1KF~+qMI4V_kh4{Xa?W;HF(BA zjyYJ|zY)cY0vtzlJScG+tdLKjQ-SEtf*{Jw3WU%{YugU|8SD$WG{uyi3ciw3 zXct>oeHe9SyLKIV;8p|pB)M*&B9HL-f=8V1)`Kvv1mzv)tid1(;?eK%OFBeMgi_}P zv&6AnA>p|M=AY?fPPwjpjy@Kr)Rr-tN>z@5L+B6j*mskmH)zduP z);19vhp59&V^o>N#H?b!I_XRB$xsid+nNjz!@phcjJ}Zdu!_($G4BpZph1(sI;tF~ z$Tv9y`HqPiU7X)v-L=)&y(ozoH#_`f`SNj7X5g*k(Rlo)U)5sCL3Ue_dfMvzq~_$_ zDS{Su9n(AN#xBvxxD+hS>;!UP!MAikJxyysf=#ra5DF6~=4e~3ifDnP7|;*+;>aNML+WS*~@s1EP&Z{1ouDMjII7} z@ac5gU-kQTi%qWV+g~^3qgu(oR3*`_;fYbZB26ZpD4GG{DwUjL6GREB=48E3hwNE-aO2w@O()UT@L84${O5m8&fQRUGBl{2s*VQq!oY zG80&pYAbQtT?!(D!2mD)FUQs!fanWL=C8KI+?$iX-^Bv=3Fcrcx4i*F>#>D)2f61G z{Jh!DDZDytW1PUkRtUkvT8gF=L)@B9;S10#lln=&7+SNzH+eWp+`U86lS#tcT%OTO zSPnn=c+R?ke^n|!va?8G-VkTh=J~muRR2ek-1bNRlYPJp{tOP)nb#-r@DV1DIiH*y zbWOU~GY`c2;f_Pyer4FrTCDZ~{Lt(>tsCStF<&)Fldi>Ly}O)9N`g<3ST7j&dXV~6 z)uagc5!9}YQfb$Uz}@Ai#olmj?8md?JkNE^MoY-ltB_-3w&h8zFjZc`G(Y+3UNbXs zdfHVbMm}Gy7qSq>EAo&9&!MDI2`;Vh{+eNAE)(yZ{_0yJ?cD=8vr)5Q{O7%+gpid% z$044?c<-{P84shoW}OuUi>iWrmB^@WJ_f1*8$K7~(>+^|%+%Yhu$vJ|#@+k)hKjGdR3Wv0BCJF{YU-!x$h36xQ@VDE9}E&*Z{E%qMXlezu6XMS>0 zoEhY>C|qeme7+vq%=-c*SI>!!3SbQzxv;_st^q`a1L&~!_a}(3V#XJ}Fm47ci zXFnhAI5y^|)uT5WcJ}pN-;5}osHL3Gr`zASl7nTf3LIxrI$RgKE@_VFi_xz<7G&;? zMw;qRHqO`zAA0AQQ5pTWoqH;ss951h4|+aP8FLhHaIfgYx^Xt*Zr36rHQo(+!d0~E zdrX5-^5_%l+*?15l|zWk1K}3Bvw)naa+@@C?Ki&AeA)$FZv&RYjR*6Ma&lyg{K`>w zY`Oz=tn_Yjc>hA9sGzSzp^ojlHBdS1%9$4qtA7!zi;Dj zgU}04+*pJTPbVyj(J;RPorHZ5ua&fO>icP?1J;kAW6O_mZI!TsMkMOph!CJH&5ZpsuDK{qJGR7qnYfwP3ba{hMI1VzJuP#)I=mK` z;|~>+9LXdW<-&g}i~srW)jX_D9dx-xUbQmEPIJzkZj(kH^R*U}6j;zttBMyk%L5Ml zLm`(vd35X%%XJZ9{?el%7(jbMy#|y$CkM&O6o%wvl7Z}vc*o>;YH4#HblcQ9aVmR^;YFn}cUO{qpiAS2O7@8RK{&H++FA(?g4JMQOxeF+!i zfaen~b=JcpH&PpO54KaaxBwsm%Y=I=|m@BOJF8N_}yY!o1+Xs%DM239Q^%l|iIusH$Ib5Be5e9TRIS**y@ z{?b+Ml6W^NnQLj8OfKXwW@_3yeU?<5kOl4Zwz2>2f;x2adAg8~IPIAF$+252wh5_E ziO>ZrR<*d4MG5QH+Qo^7_tF%Rd-lb7LG7((aX)I++tFWjOJMVA+r4WESdcS5-Pb-z zp3iaX2useUm04)e7sFXuZ`l1+Of6!uWeI)Wi*?$9y;_$i2%)d5cCk0hg|GFZe6OLi zaC{xU|1NL=Zr({;VM%fjAwCbVNb2)ZOherY7kksAMjy8^+kmrn`D#&-al1^t|>G#U)2`?n5oE%QJ4L*v1hgOp3N3s83kyq%NrA%B-xw z$FmXLUvOE`UZ`nu(o{#`l3B!eW6ZMklI1`nlv*enqDE)I6~)0!RrXUd>D`ykPva6U z6sq~;y?$x6zRRRLR-np=}y?nmN^1@uhRlC4;Bl^5( z@JWpU^2r8|xcjO+T2t10y&lrd5)udNou=!J3{#*@o)pqns}_*|-FPL$36n{KxFf{f zcdHI=M-6=VQVl8xaj5Vr_A0Q&=H)~B=ZGogk5sxkEhUD8+=U8eVTmjO3o7IE6Rd=d z5?N`@B?lrm<-}TH3qae z=P5CUh~7is;p-cYI4&iE2W z%B;yKlUxhT@nJpj`cMJ%)%Gg*oShvn%m37|J%8f6r2naeQhW-BvslobGh|4wXh32Q zm;=4SU0!bNtf0I&W zxA>u7+5|_ztN`s@Salu?kpp?P+EPb=#p;}dV3F?zt9V7pp{>u!Q@MZE7AYwFs*r1N znMBW)nDycC4tOHSFj=x^+h!e8AWamW-x9!$sL~D>2!TZHc3??$AV)jX;{tDug)MJl zcO4v*I|$75Rq6WdZVgKO>jOYMyX)}NBS+VsPNqX*Oq{j$>Y|(M4M9M7Rsp3|M49&e z$18MMMXC%JC0F`>{kVA0lXn(oR~co@e6>5X!NAZxg=WVJWXI5}bl-noobRkyou3Zb zoXiozwG~%IjLG^4$MrEWP>sYCN5=dew9)RhC_xQZsrgVjwOjMlesY8|Z$MZr-B4}p zp(zWY^VCr@h)FT!IAYi~cW^ma5s-y{?q= z(2_wwrLgq|$3h2JFnyG0O+N3XRX`O|X^D!GSH^J+P>|l5R>_k*!pEallOc58FvrC@KA>X(}iaf81PUA5nJ!RrO@b$gj z0_}!Z@B^~nQ@o}lRLyi;I21J6W;8)2nn7fQQh%hMx8szsE%k6M6Z7+`#*!`Z=kQM| z|I14M#TNj2PjWxBpR3Bc`E@@$k9D$2rOJG?NeFLI6N|GuvsxizVV)8-Tp|H9zud7K zwrx%K?Vk_6@jl`r)7I9{1+YiQtEr7VIpq!O|Av8ovk~ozre%mj^xERdNAfoy?7xH> za-Sa#q!R8c9AaT>=_NvTc!Gt=WDd-tZg4eInZ`Bw(93ZCVm*-EHSpH|6#D*Bsere> zq`?q8MnLDi6R>#m(jUZ05ezF{P>2(}i)b>{fx(YEjvGkrXqP%m>X?Xxwszx9GQ=0C z=O67-{)_e$O4Q|LpSb!6A>d3H0EJzWd`y|=*dkP?EE}Y}J!2q2s5bYM9P2F9!b69a zn6_pww#-3rX_ElYge5Ox_qwg^<8HEm{XedPin3`Lpd@)gPaJKVFV|6&-t$kNE~krD zmn;F6fcFvi(QWT0)2U^(`G!=Fj<*Ro<-H?qTNeNeNFd_qWuKXk_d8~bj`K+Eznjqh zJ64$vlSlU!c0kEQF=;Q;Yg)(eNq&^${CV2~j89aW4O>tadj$0cXwsX$Y$jR4SjEnu zqs*7qcYM$6OGZ`4Vq0;U2y4!4=l{7a^OKT%ng-8dMFA>4EGzz+UVH-{ah?f~jfP%X zsJiR~gTofnlELh?W!?H8ZUhI!d^~ z`RiZ4q~-s7v%*9D7+)9GQ=t26`;?%-VqIW$pwcDJ07TnF=R6q*Y35H80(-oAJ1yZ< zmg&aDK+Zq4V#h5(Ck_hjJdpVfD-uf$N6@Yg3TfYLQ2!5m_@6Z^jwZ6>#LVADH_&6^ zdwVDCy)szQF9mCFEh*EnJ5WatX=0sS3Ax|h+DU+{D8HT7aLNj31bch%PNzOF;jqz2 z1vPFJ48BUhMGToqU#;mdkIh$xKmV7%*M_*y=9~^!Eh?!VD=3PRPMlp6%f4Sy7;wbk=D9>0~XBLhbK`8*2K0-W4YzJ22*xcu@$nwFU@r*g9GQ z@$pp_$+gRVxXD;wd^#QO7Mo=(wT|BM3#v}^V-_Ow;R1}HvGPOM1ji2C(SamHs4d6x zMqzom3(O9v;a1-rqFW5?uENX-!w* zOC0&11n^&84S8~X3B08yhGjTm60~)2{qP&KE`A7?FO0@boQQT*kH|$sossy5$vMgs zUuAe)@hmmE=Eyk~%dh`Uif3}(;Ch04!+H0=Sgals+2Ld6O;8NXa*-JABPK_Q;;^S4 zPn>=$rwhe$<%P>{rpr|4`H_N76G!`TcJFR-gbl+tSUYgj&nLJz{}~1hll$QobwJm9 z!`tBNYo@mjRk<-B(f!;u6MX+~b9GMg@)|1yfuBA+S72TEJ|&42hHDlv^kq`td`L6j z#3z()(!;lZtP>bX@-KTz;$LwkVE926@d?BDysN@Zv z1>>*BGqxRVKTvd0k`7e8EcV12m|p>$NxgNW*`oSmDF^J8GRUBW+yY!xLS=N6$p(qrPXiaOxSr{AD~lf39i-#$#Y zY>dsC$#+=RKsi!aEvSu3-fr5@X|-u8smQ}Yu3QLQg!l#bE$5|A)HQ+IoQM2G-NE_q zWb+?Gp8lWvT7WRj^G%jMOJrfdCHE4?N-1}D7353{ zY_vd0l5cqqjePZ0eXWQICEBB{+UIdlYe+P+dhLyNBC6)jb^VICyu>6uXmhv{1GoFyDMFxaI zKnblKw$Ec-t|yF*S^I3Hmw&NonT%a!$Evx*r#e%S{B4^h;G^cOV-2k11}I zMb3R?D^>?8|H#qz%x%+>{rvUke?Cc{6is=q4qJ{Kxt>#4D`CkNtTERm2HY*$U2PEb6)ERaE-MfeK8BMTu+aK3f4oYoz_IXu$d$j#G{CS7 ztO4O?Xlq@J4XUm%cTt|!FeKfr<*n029-f+v#Tj55gU~>zmxjszDl{yxLE_{I96s;I zt4O?<&}{=ci?pwHNB_l@D2lh%(?_huYx2hgS`r4#_0=*uwa3R4o_h*nWezCoYuw{% zvz#Ylxn(ZwGjxlfk;>G$e2dT0%==dZiI=<$#U7*dj2VA(Zsobnu~0hL{OazL%}z$r z8Q+O24{7(5iNE}4)HwdLkUH|wE{^|3>PtbqkZ|IUF~p^ z)MKDgeQ_d#6cQ>{5<}^0gz#XvfJ|81ksv3y$ei1xHz%0zl<2%wt4ZDjrHqZa1RJeoVGRUv}}{>1cLEg^w`0d4-N(*qMbT^su2DuAT z4*Qp*rex}hI22A_NJlX|T}M{R4)M5yl7fXolCdLhN_Mg3l>_b6_sK`T zNHGrvEwEHq>nPgL#7srs?q z)FzWQAtBavuLD~xG8QEQ4(`Y5a64`856nqJA@F>>yZy-z7n8{yNxX~q)o-Yo|3Bc% zpXAB1j&VCR!x3Dp64WGe$!(7$q^zcpXY|>d>dJCnrIBv2!dsm8i;`k&d(Z)x z^4eC!3muJ5So^Fc2c|D`{`3GZ9fUUZ8egQ>{^>`bw}!a7{UNDr^ZAdtpeLAv0m${< z0UUFw_g>HN*gvr`H{JKu<#(EfjM+c-q1wqC=gZ2fn{$mtgWx~zsrl1%O#Q*Y#gB2I z{cvG_dNpn5cj%TlvHzy*;g^FJVgdb_sLAC&V{9N7B`^<1=(Tiheo^DcPd?8GVISJq zYvEVn;^(8+ml(9%`=XEI7~|?ZW0!8%x^Vtlr=5C$6x*%4Y&blu>*c?m1!fWzChwHV zeJz6CfZS*dkL>3MQl;xgwV_++2Z{9mmrFKOWp(K z5Z_7*ej`VX$SPmrWG9bz+!uN3kX;>}js5!TgyZ1AWCGe-?g|=BYNlD*Y@dfzHTF1$ zb-prc0VAN00OeaTFZy;K(t%i3C++9}W{c6rCL;@12h_ghL;z{t=~8(KXcKqw*emJ0 z2>03qd}IqZ2bUT~ThrF*<2S&d1v^J+49c}m6>@^g^L+p`3NIcoABQ?_?*qz(W5RY- zlG*je@?H0**ZhfrWE&?`h-DBu^8k=@OfOex;Fjvm)mJOl?>Ez~f98p$o&PSp&Ne}| zAN=Vh|04ti_~M%XS`BzWCTWS7J5G5Oou`@OeB7>mI3S0q@BT1{@CSWxFPf>a)w={? zR*T$=yw^byMwQ;)f12LKD(z1v%p1R4@1~?Wf{$7`f;aYXx{9VR8k?(%EFyFAw2NeTb*DK%|pM0V5 zkUL=?Tr@rFXn_F3EH1R+s8czNy2omyxYm~x&yB?SR~6|OS8TlEQLz`mj6H1Fo7KJpEQ0jL4<-~jzeP4kGsxz5f_fomD{5* z%oV1>d~YdC+glNnOBEr99ESbUs)G^D9r|MAysWVuUmmv|R}k;XKU2JO=fxhUvVz77 zUS$eAjlHI}wiac4su(#_|9~KW7I#)w?k5jSJ0v~ zx??||WhrYL5SkRcswhX@TrLXm6|@O;G8X*w+R9{An`2v7GTkkx;!Y2^{lzLr>2%|| z7K&cx>aFP)*Y3B!>^B*xh@5jJgVDvsFh6#$Id40gqh7TVu_X}nxdD(Im#m<9e; z6Y$)gNWVR+>7E~Nc=97el@t2WIU4nC!xb5Oj)+?;NE;=Vg6^kM=NDp&N&)1mdqjS! zkzDCoz0*rsx3EeWol|c6QF^3_zH#x+NOt)Ue@gfRxfJ-=wpDFhJIVWO3-8C(vY;^D z9shP_Y;d;iT=lapE(QW)ul;iy9|&S?fHg8k(yC9tY7pXe{czq^@{yn%Vr^7(0Z_W` z|CqKs$_!!F;<327q);t)2Q>A3P(=X~xY=Tm$jYG0Pm{7MR^PTjY~Cz{X~%uRGq0>9hJ(M;Y@ zJv=-~wscsYsg&)ouXu@j*+!`yT=_2P8hH9jl+1|}e0zi2m$Kc!VX5hQo>-thAxAsx z9SZ+ih*^=wJ7lbmQ$*zO{oc5uxHuu7g3V*CD&yu znH(PBJ)glxd)ULJ>x~Yx_6)T<vU9><1JY9@o7y$&1VZHF=xoKl7-LqN=XRs-I0R_ZIZze|EG zw1XZ+Uc3jky}olz(_>V#s`{|;ntwTr-OxG&v@o+@J{sk4KCVZ$={feZb6!U~GHSZz zE@5GE5ggqEv|Wume7dLW#QV%#?#_jAMwIhvnr7@n`v7pI?c{5ICR?YbqEHfF7CXd4A)jR>Gb3e{-sxRz+ZplT{ZG#9F@j= z7zBQ~A?7vyQcvPyV{2l+r#O+MAM-^0UR!^k zamVwDC_DVDiQ1^p$h{{*67tTHh!*E1w!bDceLbf^yrLyJ02^3(55fj-A5)lY(vNW= zALI*1jo(yYA{h*dc)ly9RZ&tJU3Lm53S3{0Hz87M{Mh`$n}Xmvkc`wN|M&9|`J0Pb z$A)Jf@25haq9%>K+yu|mCJXAE1!rCa$3W-Z=8Y;z_VjU~Kq`}FBqt{gBxI(gjZo?4 zX`|ijW%QUIe{En^rmU%F?b_HUOF5Md4d<&Q)tMPiRl7l+Uy_EakCv*>Uh31bU;K!D zPMR)Za!kSgsFqS&<~~RB1^SwhgTei?Dl2L1K&rMo6w|N$*x@^7MzqxThOd-9t;~Ax zj=FUt>V!w`1OjbUy0h zNE;8-F&Bpo8*PnJung6J<12sdr-Kcb3!D56<*#QDZ3e+v{zbWMK}s1f^C#{z3fs>x zt{1`bOO351xB;X01FlWV58JgiBR>*79?Kj|xhhblbFDvQ1nHPgnwE53Alv+5Jt*BUdK~?`tkj8`8o3jNG|gl+1$H~Q>3bN~ zp`JEGa))dYX@T-*$8H5(C($sS-0Ef@!v=vX_uNjpbzV7tkYG_fiBF*6`U=^zJ-+)z zm%&?fIQ$M`vfd_(42UHkYHeWbR=}=jpcsbVI!*vW~M?~>zTNn z{*F$NrJ$|p*w5wWX}=gbm$e5T>)-Ue0*{9U%luAx7>Jbt-^oG&T<8myCd6yB!s|w)lGqZq2-S$lgA}JIE-e(a7nhJO zQn$aqn9azQ>DG5#c1W6;6M$MiiCv65TWEw|t*AS6L?kZ9ejQPJMVPKmC41Vp)i&+7 zx?a*59CBe(*6DL;Pmuyy5wQJm+Aq%eNP0hfdsvl_Yk)w|!aV?z+09HvW9VcosAWO7 zB0(=f`!ZKs;()sVC?)bE=7tPOfgEf#rs;dq^~0+F;`SuT z6!5&V?mjF=+y<}3#EyZVnDa>!hAU@b=SZ`r#uu7qC;2Q@`BJ znf|?a=?Lp#!B*85GvGyUSJ={?LRum#V69Nv;v$t#5f-zZ_`36(l@xBB<|!qa4!oh6 z#3J!d65vUQ4J`9_9rzu4yt?>!rkk_MCFy=5wa_1U0Bjv2#ZUd~sKy z(Vxc1_RvA14DxhTEkw)Y^8SqYciRl)m7Z!aJIBy}HeTtXR3#YsjhDTg$tWu|_Vhyx z*9#*E{d+Yzo1IC2(ho7L2kUC|bii?hVq=3pv@Nwhup8eoJ;Zvv56_5s5|ML#s4a;v z*d%03H~#yjEpBJ6?@yKRjax4w&cY8G>-0`+)U7~m=?iTrd8%R;u`Gp^ZkIJE@jbzx zb;j$^>Y(u&8N3F}ep`v0b!PpON4xdt^-0^@$aO|@G*MqZCpznfu1ZMk6|;|82}gi1 zpI~ zT$WcsYd|B)4q#F_Tf$;3VzXIk8?z&S54Nk;d}l7>g*@6%*S(S&Y4 zXh&N`X4<_{B$_qw{88c~%!y$NEqrdb~}a;qr9TyaMMuX9QN{mKF&w`b(*tD6H*Unmd$b!?<|_vR z;>c71u#NustD?-q9I-J5k_?Y+&*F=?+Xe`FoW7hL%!j4(d$O_#^%-tM% zF6_0`gkEqKJ&%43yYSA;&0RsWbunUCH$&QT%VV@JG$)y=gKOnb- zy89RyalUJ%N(DdA%AG3t?!rjv67Ucf-Jm9XS@Pked-xWyIza}27CFj3CNu|RvE?v0 zY{iu48W$~_r@`nR68C!0YuT9v%nif;w*V*4e#mwkq};GNGc~^1B5>1&d~_% z`)35t$WYNcTw1v=xMaFoLKs=kn<#s8cCN(>;A!{e>~*Vh>NdY?6~=jhd21|x89>qJ zRSY*fR30bimme)Y?a(m2^N?rvXj`bOHiktQyr;M46T|BX#Zo7!^?-BtIYkHh4Lik= z%a=4un6F2SANsa6bi$syIb?-{M81zWD^9#Jeyom&7ua&%^B}_uelj^i&S7 z!6khi0S4%FX$?z{Lc}&>y39IPGU!DbkL4{bPUnJ3$3=e2qOyS*);Y6~eM+!ZpJkm@ z1zFp~Ysb_h?iSuu#cRvZDqgcdg4dkpDULsR?K{jmUG5d93NBYHH31nJlP$Ub<@1J0 zf)F0F3zqCrlahgTzGcrF_4v!AHy3K)kB1GHsJ;C8)>869X-1t#!G3??i*Q+-Dfm=5 zxmgdb=5*KB?t^dp46|FMsShbZCnb>ME>czF4nCJ(AVeSX&1M#*v2Oy$YL-G=`NOcm^!;9I5OzFMI)VaVcb$a^t9dQVd z(0&@?(c)Xr%LGe3Voh(6Id0R+d~Q5dk)l{&YJji)V8YNfvbfG?cOv9h*C1mxS6F8U z#YaS`3b=(9bx?O$cJIDtvV9!=v-`NXQXqTyjk}tVPDdF9d*)cULc?(fgSCZPZ#YRj z6g`~v9-^M3@H)mP-9){cc6584{Awj!;E8Zwuh|;Y>K9jCUi*ggN;4CDh)2(()FA*- zQ%8){`06Txe;GFeaiz3lD3h*ky9o>enW|QW778u3S z@b1+na4-n+;cG{Z)-?Py6?;Uj5vcp8HrJd(ha$B|rx(9w(iptY<6m0}{Gs~hh*JA; zaHH@8szAja@~_WN?!_x|vw`$vrNVS_w8-O?SO#F$d_P^eRaJS5L|Id*-~M1#+%W+@ z5G$MgeRcrCe^e!P$tmu;a~-x$t-eGYE9+D{PDRE(Ut}48ltu*~kQLkM*}S_-;|<=TJU$Or z6rpTO=0l;cL0{}HY`fkZwX{uADknK-lq*Is)3wa#KYMeJLf3Nfw1P0pAS(6)r+iLC z%H20Yp$6%LX}9g_yMD(5aP$NqZu+=e(Bx!tD0y0=oap<^yx2=RKl-jr+iL2ARtaUt zf4$@>`t|$d*d?MLJS%+Y7WY!&zSYQKAgPpK-ZEm-rvay;kV}tL7zSt0}+xc}G@4DgIbnor= zdki>#T|-`aqArJTM(X+9I!V#-WP2ui^AhRhcFxku@Q;T|vBvQ*S`9&j1&qsIM z`7wHUIV)4-h(-?%EqM^^KKoIpYNm&$X1GXG%@zC@oLsg%S9x7W2V2;Kc%rn#cS~mH zvbh-9F33r%SE$4bC+{DWFmG1F9xW_GnB77coJVUMy5CN;yZ92|dzXbQQlHgJSBFSD z4I;9V9wdowz4ACsw4YbMG(3PXPLGmYB9*NxrUx~lYa7vW*2>x*{oSg+x)ha`-s_rL0WL|q}0 z*{o(ou$DjkkZ`azZqI%Y;TBhQ`03ZUG3D`=D{o*XcV~EGHAOrPrQ>3wMX5L;X-?UI z6uV}`_C+9}9Bm)a!4kD7ogD8045q=ZbVKNbvqEv%?rQ7huqC5KMP$S#Q z>!-^qG;W#$>>?g#_|Yfji2{yCVcHN6&-QzI_^P-!k{45z_iI1l-pXCJ$<4mNUTy@5 zy;GigTZZTAAJJ25HO+X>yjx`h&48a|+5UeamlGcVN9UHz2%1#tVK^AZenqehKeeO8h5iP8CN1Lm@S2O^{&02 zMZCy3wZ(iL|%cCFYzHoqEJ)A^DTNS=s9rI{6 zGkTMKZ;rEK!VQW-#M?sO2wd*9dCXUy?-D@r=$#PVh28GSb<%`#`}A&&Ee$tl)q|+h znYs#HO?z%alZtzBt|8Yxv}m&*R!46=HluR`uWtA9?aq`>^~*{fxOuFpRMl*|Lfks{ z;kmP=3k1&{Gg*()mHd2&M41dmC(8J8$pw)z>5m4w4`-{=KP!dSgQM-HT}<>EKAxAK zjBuD+KgtB4{tb&oU^b}Fs*B^1vkDR?VC1ixy?S9fWj)fDco@P9#N$t=Pmq7aM^2i+SE& zX69$x-Ig71{Jh@uzU#?3S>q#|;YN!n8=4hV{!j4bfsaS2VOX|c&D*Ks<>g-fuVuL% z0Bh3IPJiNN)TXiCV70m#bv99G#Df^yR0a9`up|CI#AJeZdm3IZ`mF_R#B$-WrKdF} zYvOw?D?vS#A;g(NQXo*>Uc#ju!s8Y4ovG?)eDPlGIOjn!$k#;j2^TjVOLs$ zM>OeZ_b*m}7)ycegz)BvhG}Apz!?<~_%w!8Q-V^)-p4gZo+{lX3hP_L+SM>f(t5(n z7+3;$VBm~Ay@FA63);PaFClyt#P%^tM)Tbb9fu1CPsST1frj0oo#U?#HYkV))IP`lkjyR4JqLUgw40UO<9 zdKTge6#E`1x05HjTslH8Pi*=Criwtfv0m7`t@!BSi|&BUwQfpOuK1$sZed+vDG$e- zO)gW*-H>_rS+O7{DaJ!Np^qV6r~P4OuIhZ)wKo@OHtO*`BO9|0b?uRilDyvhFC%l| zGlP~L5p+ER^Z?agGRn&gPFVjKkylzY&4k)Wdo`obz&q`^lZVjapZ9W3=5_c(0r{~O zPq99Ic5xU`4u8{XJ%cITj*a~)*~XD2TULVO?~+0&yptU*iiqxc_<92Ll|q&4c<&e zzz3z?6;eX@46NtHD7Rnc7p+}BznW#5dy9?;wsD2H7O8s4arB+C!7<0yPS1JkG;Uw) z@Z^A5Ut4WwkDS$uowAE>mKBY($su;?1Y248M!BNiHRelKxSbv$w6={L($E^l>I^uT zdw9sR?Oi#s4|XF)69=1Y5l|llR+`-aL6v=>)c?%_I6(xhS33{Za%$3tkPtfy3W_m& zR)vV4Fz8aVJsL3aMIee{SXr}WR{f6~eOacH#B77NWH0A-|4>bG*>E$bry5D3kTHhI zZr)H*CMosume$WZ(!E9rTCnD@Q+a1W*(JZg)-j~?;oiEIygtH=9f)VZ;x$&MiIWwD0EPzJ^`w>M^xY@>U7XemC4QWt$;e{ZPkVG z15Kmp54qau%LJs1YXE`IDIt9ThsvKMKH=LpgW~Y9cIRY0U7)j0*-r)61r2*OMhUy^ ze0@qd85QA+3|!p>m~5UOgwf)UWX@F{Kt|cIG7Y7MLDp&H?Nsw;G2SK7{T>x;UC%R=Whkmh1a?| z({lKx4!geS`i!VNmQ=3cq#vSbe@J{=$#OwX4C~VamPkMPwJiS^QHo+Xz6*^nv2Cs- zmMB2A=Fe+(V()k}{#ey|B9OTg0(&2a?B>_#lQ->Xa$eXb7qI@Ylovn$@{;si0B2jO z5p_W*m$mFT`Ca|8d^JZQYlQCf#|HW&`Fi&dI(G+7_a-N8YCA?9zgOHr=7#oiBSNxp zyIZfuXvQQdeEfvOA47#>ch%JaBPSTgC6h_0!q6su$HsNh&Ii6zvszCvBR1_v1~itn zh9na2kvRb}#N%(Eut`UiFZbAhqm=EzVt2Uk!9sDR+!R6h@qzSO^hBX5ESWO%=2A|= zyOKHQ*FlVEK%Q{H_xi0;p`;IupCieHp=`BZ0N{H}m^OjpYzct4sWfTGFj9;vEvVA3) zy65fG!D^HTMw*iiI&1H7BqG7A1%%j6+D-$o^9skIylq@xMA-S)i&_eTU(%$Kutg}Q zA^q8L&hF?Yvz+u>+xY%zKMk9izLAG1_#h}sty=fWMJ zCrJ&riUIBRy&NuaV9T!#F1gS^XGmBl@*+LVM8Z5#hY8}!6&NVAv)$=~AYxspoAf7UI z$Q(HjQ7CS+{-9h_3S#WxGVB0t1!xl@KDf3CFyS_^!souyfso%@#$2lG4bzFG3Amb+ zwBakp38=K!Gw?0uskhRT-kSyU36qO1oo;nJ52U;gQ*Rzcb=Jn;^o2V31XrI=8qcSC zp7|CY9yvQ4CFtRjB};>+e#Yf{J&ph20)~0}j?@+Q-G1-HzaHVOSEMwpMIK5NmNU`PA8^fy0c~sWk=PZ}+cN#u4a`=R5co2{yAy z`%kGG`aU;JnU2+wKRX0gd)->4@4`g$s*>C*`)hpFAup3znfe5rhTdS&uB<=c7*=Mc zKsh=)sc%8zIOMw*U*m0tv5Cm)({9+RP)_GnD=4OK^0yi1W>w z(pWxn-H-*;ka(V7pdR$R`g2C7Nz$G*z2WXbO|{Nc#BqX;2ifxoOU)$ktkI0E)A#k| zTobosyW+y#sdob6wVR(<(+$WD2ap`g-PC5Qoe5GN@J^C5lRSyzA*+KQtrw(OqOrMy z&Wq>-`tAr%k<|ufI?A{gGHzazPeqsK$>!3b*s(FdUw;9{k)Q>LH(l28x2rYgU!OVF zCqspV@Ac~}G?taY!MnU;;EUJ#`d?kJ?z!W~plWjo8lovScWP}%-%C!1~K z`sSjbw8+4Us@>^WWyXF|FfWWRbLSBEAksgSW;PYf8cQ!=fzDk+!`Segk?yNmKcSdaDJskOP zSmN%lM_&M&*RTwzEeT;HF zmA~@LcOx7VSAr zkC#H7smMFg;{;mHj0ijB#`mFIg&?|}7dOuIjrzLBQ|MeH8|5tXFVXaDt!d@lPnm6~ zzT--Y^{Fb!oZCKm=Uamnn67l56+2D*%cOM6WB@ca$V<=@W}AcSvX#O2E1mK_LSm!r z|5EdR6r5@zQTtlhqNBMdJfwS*xQbP`?(akLQeM82*FJ_FJDH}A+Dosp8WwCoQkwWM zc0Qk+`LVleiZ-*msLL4pb(;4H%k<^1beW!|byHpZeKt}uLSU^@?Uk-gk7;bV= zHPWT7dmc^L94>=c;VP7R8NWkAw*E`6)?2qHgDhqX^hEIN*8juTS;kelcI#e3x)hOa z1e6Zx?ha|`mXPj7QW_+sr9?WUySp2amaa+1gmbgrwa?z~KJPl8=8rEtb3S*CdyM~e z4Q0l-=Mz7q2EI!nn(|CNc@r|Fxr38=)+V|XuR<+ADxdbE>vaHCL4#W6SsVx8IsWx+ z{{7a{d_TdFOJ7OtGm~YGtjj6&zNV4)@wzgJ2~zZ1cnQ92q+yP;e3#xZ|^x9 z%ad<{$a$~4N+W#Wv+GXZ1ye-$G~qiSTz#lC6CHscSnb zJE9?Ix}Dv;O(r*O%SOca;y*u94a?v9f@yfj{NY{3(tWzEey57og{zL>LDzjggGy<o*mX79sn0bAmB{=!KA|lMO|% z-fh`qeHT6M4qI6zi)LMOpv8Ja>Mqq3xbYcR>!ir1KTiMV=3{U`$nbw0v@8?~rHYFq zFVFWi{JN#qx2ZfPdvsJx!xmZW2rX$(*yBX05d9(cDBmdq{hne{h4_m=)PjB1Dr_>* z4*W^^hccA-1qhJvO%n9$d{S3qL`Lra-s1k=PyYT;eGC6)2E4NnCZt>c7>zEe3%rBmZ zFJ6?3#EvH`T(n8x>rjl6&VjJ{UEsD_#;xNn*JW?2N({>**7d`$O?^={NL`cgzaRLo zwD^xte*0P5U`c^7O@oOvr6LKupcuFN+;W9(#_)Zjz*T6k z^Q{TlKunOThLjQ(wgo}Z^PpXLa`bT1E==1YMIEn+JO>E(n0N+3NmQsJkHd7C*OgOD zuRr7TQOaks(}4)?9R{gi=t@Wk76~6)TmqVD1?4A)tK1OwR5h$` zvJNsywLWDN|MX8CkW{Oo+V5`(8?_}Z4j$Z|``-vdky$*+sJSuvI?4TDAs_tU?O%>z zZ9c;-%>J6~$7gRs)*=McI&rKLWkYO9+ti2sgOL=yazc^7n`cVhdk5D2FH~me*DKp!D0{*Q{*$5@e~cS#K(yE9_4mhxPCtUA+7aMuA3S(t z_Fr+&U#nip8LKV0+weF<&}7sI(U?YbqyO-gs*g@^Iy!mCI!p%%btc?xY7Ae$F=tMy z==rlGVsuh_h)H-6@kS9^b7kx5t8^@U7HR)C(KtC+pAfkT^56veZ$8k{Q0empQaH!; zJSkm>EfkGZGmjlrYjx@f5LhU>_+tJ@vHkmd`s*)MarkmWS>p$@g^{zu;tNiP?iozd zJIkVI>Wikc7+j=3$s2_+YJyB-ogrhn8EwIt#;MTioqdD5 z7dcUDIScpClDvpCmH&OP4XBG{jSb$-Di?lK#io<2h`C;a|HmuRtm^b0u}huI)3H zr;I@ay7vS-N!~8J`+Epk0j&G6RHoB_R+dMs3T5mw-(p03;^X)lACrGtbw7fiaM%d| z?$ALEEV?fNuUrIQEIt$xJ?ICwvSLL+yMnW6O_K)VGV}|b)nWOL%q~T1< z&3TYl$fFg)L-_myfg}0At7uU_R^nsq0-eghm^P|XVvf8oP1fgMn9@_4;_?5!F8=Sa zRzvp>tMtXqg)$I=jVVb!(*R9Wj&;FUIPK|kDym3jMV;%4SSFu=#B)R{fwl|3n5NlTR}v(lTLrG=iGhe~o{I)jX9`a_ZRxR8H0cNE1d<-9Yg$j2n?TkUeje#_O` z7dx->Y4gJ`iht^QNj#@Ye$KoX@5dbsv|p-NM}9j0n1s&dw@m7oDZyl*ZKY){1~vaj zFxWnXXyT2<;ZxkStzZBa+SG66Q;kymr*9toxQ)@>@}z<{$e#&~#|JE`z<%SbeAAa$zUm8d%-9MzosRGT8Ym`2@;_B6F(wu#hWM!)l1Mj&~gA@ ztdYC$rYPJo0kspzC7#l&YZ?ZjSoxMVIBc4$%X#uTaS5<3PPk)zSh``J&6Qq%PW0BQ zbc+LY(0+cMSaC%Ox^;xqf%~^=N>t({XU>oF0=_mz@Jek!8q7WrNB9xT=KYhLGnj|s zb@{tdtKLcV?P^Jl<%$Adg~|l|L{Ul2`?%8YeKK?<8-NzoRNlR}@_4gwA9;=}J3koc zkxd^BWw<5Po!bih5#S{9Lk>SBBO)0;k2c#7@5159_whGL7!!StSQ-RAZza&rwESaC z69Z{WGd%|1wL0P8fY#KchpQnWb->cN|DeT_@)8gN4d9pZ+I*{=cqfbeBR;snc`uhj z{63T8~gwts7S|m$VcYx5J$Rm0^<4gG?tB?E`BbC%^_Jc%F@C&#S&n zNRo9`?&ztOqF}aPbGsautm ztM&QAl3MC|K#FJma)vzr4PZ5(Mc6f;{<&W{s4m5?mB6$jm(~QzqT3#4bplqyLzS@M zkFb!Hy9L|r#R2w+_Wsan%Jl#pQv)cNx1AX2gf=*FPXimF?x4AsAFh#Us>>#VJueS6 z>Oq;M7M_}^>A2Vv5R)<++CATbgtE=w)j&YQ(*?}AG!0Xp_g|MIfV815aIyjnUvZn! zlh%iOc23JF<3m6n(HDJXuP@cT=08kK)L7_B&~P~&T?%&yQ#&^|s0psMaxHG$h*Qn1 z%g=B#GU2K`d6}AYphw8MLC)#0QC_UDY;=Afat#JlR}+K|4f_upPG&qr%)8{=@JKk4 zc!OHMQ8($%#M^-)T8)YZhL!8JRIdU;LJr(g+$&;I0sM`DcY>9s`LlY2q$acat!Cc> zaAyIZdzo{bS+6Jv9pgV6gJUGY(Ke2J`^!Ajdt-zvCp?w=l>ZV8d?P3s(De!4Y#SW) z%?+0{?7p$NIdfe~W>I|QG5EZlj?d2Eq5Ll3;+Vqoq#HNC&`9E`#K2BL=F2P1#EL@w znf5sJg?su0E>JO;nw_Y z_v<(-2fYuIR6tH#x^7Z|7oi>`zsdS@Ft_VZdiK2a|G@8S!KxIgmjs459uKi8deV#+ z2n${TB=z*r_hB#TSkvT0VkKJe-cGk6ZO5kh+~tAd)Q5T3j)#8tY{_-`8uw3lldzY) z9p61gM%w4)*qqG4`_ACSK#!M*h`#|Cxt2+&e``K@nh>Uy55a3R>?AoEEk^$Z z=KfBmJd=4P<8zC|wu6Sfk#I5Xb%~by8@J05UX^wFaHda4FFdGm-sn#?{Az)o9D==2 zbLy+Es;7Mu=;2yW3MlY9UWaLWBpl1TZ#QVFtpK26dmw>+_VsmB&@x4-TCy_ki8w#H z$n;z^H%oE4?+q$XH(pHQ3S828d5e4f)YMlc$=VH2X+JZaK1hX%J!~_JQ98#+#Auet((mGc*ZL)bp_DwI3JPUCNdIqC1-5decl69Abn3&TrxqL`mI(Nb)JJ4FyNP@jzigEoSTpk=)-=%E>P$$5R-USS96rwQvG9noZw z)5uxs3`Tc#{XIW8oXj%W487pRfIFBojz(_6+vnXcZ`QW&##tJdB*lj?TOJz;+#Rn3 zhU}~Z+}4eU7!z7NFP{Y%e$*8}}=#IHL}omm2kx;{7K9^uNWQmGcV* zktppvTa33Y+9h-wZs*VNjykjiO`580b4JOC)KZHIAe zHz7>v7AR#*>H0z=ZOWRzHa?b6x`K1b$U@$$iBNLLu`RxJJyf)5^Yj1h&e3+{?ohvI zQJ_oA=FjoljS|rLy{45KnqP10_~OeHQ#?-7zj&FZcsS&-9M{g9H6EiyxW5r`EoRcV zOVswL`&j!CZ)Cm0jXL3v&c-er#(LP;7g=BT5pnW)%4q8^cZ?f9T9pL6N2ecju7jKs z3;6UsQFewyW;?cJvoC%$jjnQiG;@6~)@M3IgZh*7!#W)&%#!7?ay) z*+IEt)=fOKR_CBF)ZBn&RxJBIzJRF7J;b2Ur6T@-&!lk{?2drsSyJ$ES?=xr!O?P! z)c2dA4BE)N53CkJkmOM?@>&!F9=Tszm_ToU<>s^F_cSg(2QsWA3{rH50TqFU#1su` z{*-gJW-;dp{O<6=<*K9%w`68I|M4>oeec(JEd}W=9^bZezbK$d42?_HqEniJ{-~EZ z(5e9Ybmrmk2*S%^tEHPh=C48im@Ew(!0EPE?-{1RRgC9u;rMBtBjWjDfjo zdITtic0ST=hhMEqoxuvvb&X+neYiV~1_Nn~9E7XuBt4f28}O9m!>_t6opc`Oj<{F= z#n?UE-`XB4kobBvD*3d*AYRQQWQGKw5xvs>IL+*qvoq+v<&5s-xnJRlbdJd%NpP@P zV649e3Klum4>5}@ZtA>F+Z#Ys<$P2xNb{PG`qLNVx$@pm!uxCs&$sBZ_{j`i6T#y-na6|!rT7EaEF=P6ANmTsxEtE+xCmn&xaAGckS)bNIQ@!A&4?S&-KP zi_888$xOCFwW5GecRy$KuoH1t*n>1X+9N1-SAck_MVgthI@9b3IyUwxuu zoBKfNZSsbpp(m0Mn?sDcY;NzT^p~Fdl$&=SG-_>M(UUtUX{|S&&e<>uvA36fx1z#3ZU9+tb(BiR-3TMVymQ=jlFC$TG+H z7@EXhE|B5a{T9ZC-g&}((A67~)_)=QZW)qrTI>To*Qhj#+tY$mZzydy1x5Q;Ra!et ztuWsR-dYB3pp?VhenKB5f5DtIZJt!TR_iS4G6<~cFY4) z11YBmC|rj-dHNA&4jy#7@4WaAzMwmVusjfk>4y53Nd808Q3f{VYIaDCjL)R|1<-I( z+uP4E$#0cck5a#2x$0)S1iB;S)YL5xgg=eyb@|unTRdCuSMWQ21~kV?*B>hKqiV_Y zLxV65$0?)v_`cJgd4AQxOkRPlbr2N~{MmV3j*Q$HuPj&}m+$_kQXYXce?8?+ky z@r9@Hl#;rAVWctrNj{L*8i=aV1K!H*H>|mp9%uik4`}~-K%pAd;>p8dr$?B^+ zuqA8gx#J|VKVmT12gOI0lYeL*j8AKP=ZW`g=8PO`4;$Wg#MUQ?q;g2!k{(VC4)w)G zKlOGiGQI)<JEw4i_DtP8+hMkoXvgyf*Z2{_sMQ`8)QJW+0~y^G(+R_rLhxJyT1G z5{HM;TXR$HsF<$W`bZwNj=8q5Lg0HgZg?-*5a}zOa%ldPm@x6BAs_RC!`Zmxb<&~s z63~qkqM>-d4zf7rdg>-nZQ)p)WOekYyYOwzBav;22aM^FPmQAF!@Fx8y#7NV7^xF< zmo|}pnS;#mejj&%#qC%$TfV5cvNBWQ{c^1<&1{*z?~zpM11uIE19WX)pr8NH$Hw&|1+X|%*2?kIz1sQ7@j=c&04EfWfru@_($7$T5Sn3$V6%$gm2+Mt0bg-wFF~j zHn{570=>}wB-i4I(Sm0`6akKpXQi%30>ArwmI;~Y5PRXO{AC=Q!2rBCqVnaM3U|B( z;U;8sp}$?z?3#>CDp||r_Q16cm=bWOX*6}&)GpQdxUbIte)4gR#k*tl_?B^dOwg=} zL*LwJ^pHR+rf`Y5AVKDgv&Hz4ThwC4|iAuP4XM9M(bIW2il{D&AybF)0;40mXd zadg-9vFLH{GUJaTcHD{;!S-SzCMg||og#{Uh{AG$Erugrv zJEAj?Y+3b`^<<*BzR5ESQ7d@qcDswBu}dL5KY5iDp~%9$NrN^7Zn}#PEBd59WCLa6 z?DTJt4BA*B@T*>(eH}`{SN3KZ(}2!o6jPHcEVBxCW(x9*<} z)scJ9f-R|ueXe3#yd?U1r29gVm?*|TI74bCQ)nY$VE?|x()oj1ig_Xq(>*p9 zsk>g!{PATd<@?+5#Cc)C;4Gxh?Dsz{+f#=2nLhxjRXZ2W$Pi1!q|R0($1#zylil%( zTu`ZbF2+Z;t{rg&P zJg+usnzfd-sww(t|GKDr1#eESU8y~iyOjV;NCqjwTh>B~g#d+xoD_Dt;&Ly$mc#N+ zjC&7X_ANH0k;%iQGR)Zztv(N^jl+Tuci~Tu5O+7yfY0x!d|DJ2U)^up=AQ+ww7NKn zDm5P}6zf7*R_rr`hrVsRf4SKpaMV!bI%6KU;lN)-YyJ>roxEcGi8b@>kl6bB9z$6B z-zgt|o{pUQii!v)`sy_DQ3j*AQ|ai_r*F2ngRdXA33q)%n=o}fsp!~jY*4u2qoP%M zzu$6O`t&3r%8UH6YeJTZ_)zyy5Nej@vd}NS19VdHZAk}OOVt+G4;d*A%;h>-vM%cv z8;x6C1w3{iAl9FfVVbW0q=vY;XUOA?a1qXcvtuQ93*5{-5!MVCoJz{s2@9yY8sRXs zGMrg(PNO>5WR7Uj;Lo=0Me%gLWO%g}to5VW|9Zz6<@#YFSK9EJ`b1~Te|gyY2(d5J z$){2m4()yU2GPK|S5n4_^|Sj;OsNYd`+e(K08{;VLNg?cUo@^N9NHkp|oK)jCsHXC|Wzs&32~>vGwG_ zOOT?^5eThU2smvv%*BhhfccNVk+|&fNo8<**O|QDeu_(KXkCS1xJxHNnF`m z{@jQffv+Nm<@Hb~$c$@{BZk0qA(2;p1BuXJ_G=2gC-z(hO-_3*rgmKAgqMJUB~0Vd zZPcThp_*ElRL&t$gse$rpSpm2(9JyUY;Y34L^585QQ>-zPm#m?#l5H@i~ z^qH;?C*od$GNFuB$J7oI1&xBSh{_4QvXqY--Dz7^Uf#xHO&|l+YfB}I@xJC4 zCs#FySW!n^KO`$-?bQQiujsX9(J(17-Jh1gO!TpVTQBo`EOyt!UsM}D`Y!WRQcE0* z%y8>J>;&0Z`(0KnG>y;b3>Ac11As)S#L$_?D{57TRWT|OVO(lzZ)Lp(7H z4l4DxuKPjEXUyh%%X-cfymQUO`b zWCX1$=Ly#z6=C`?tEeycP1^_E3lwNR4|jAE!IYvNh9hI^VPbHVTIs#OS6-o`v0hoo zAGW{$dP}!I`3{Y!$&C%Rx7s8!6o;~abPC9zXyrnfB=R*$1R{G-fz^&6p2V=`VS8w& zFd}iB+EbN<6w~qM+i4io5@=uwzeRG-NL-l3q}f#p{*l>qwwBuY?zAl3Y{aMPA$MfE z_083sXn_Isw8Y+R@=?`@K5KT*i+zLd#~n-=fT>4RkI;#rOmy3C^_3OmY5dta-~PN_ zE-oO%)R=V-pC z@A`}lrQIrfbY(%4)u0`9^`X*VC|B}?>#ttA2*8Qr#jyYtr+WYC4#1#KW`Bp-+rLk<*yMsWyD^oM>o&&< z^{lBZ)bw)K_*9ACym4AQe37bLd@nL;x!Wh+t+&qS>E_eHv^Dcww(o^qyOz?7Irc&8 zYH;e^M0>_Pr;E^U*E^9}88bn5iIP?yc|o!`hw>u6W$5b+t|=1vp|t)x`QWm#qe7>X z#uktadz}*4oe?5NM)@woicECc_f>y4;bJQntWRc=Bt8dg6wLg>S{}U%ZQp$mDak}% zRy3jhd7mD2V3HC~Hm3=n+7Faz+JXKYB5`&!Q+B2&iOEZz}BP%CF@|R0neTW4E42rTSOPFPti3#~1l{oRKB%49uJGs>X z!$cOilY>Sb*M1~=eO2Q?y@x$8AEO+I4V!X>iXDIQI{p1mjx#1`<4w{*q{z}rRjxs_+N8i!$yLyyJVo;kvtxDD zw9NY!yJMT7@fear5Zy#Q^ir$%S(}#Ym~hD_O4JY{iT;{J8^W82&La>PqmG0a32#pq zxw?&;sBrV$4=EU7)2>~IS|q#eYe|k07)msh)_qHTkdd`SmK2z;ix}Upx!+@!d)xZ~ zlxoaH3E*yl3K+duj>eq-4oHpgcH8aNPpgMU3?}PLK1eUrxv{4KGO;K$?hRT zlDuH zR>(3ala^z`SPh9^j}UqvY|UmJt(se|iW$B{S18pPLD=ciT55F7EFUl72=ZEwoMP9F zut_v8ClzYSCzuE7w*?kBzh|pJx`480yeZc2I-+K4(cuM^7^TU~Eyd68bYNKfV5EYCZ*r*{~yb>_wzYkuFzQ{o&2U!B*z2Rq(v+8@d;jxxqPg-05Hw)?+v}vo`v`s3dAY^AW8yiQVus=nRgfLku}!;Uxy2;4k5(I;#qQi>c#i!# zf9HOtUh}(4Hv*Ms$Ua3su1$IA;lk6q>czsllN?dfooDb36X^rgPmZ)omm`eMaZig! zrbCR%PQWFM>-!VWj7$w(=b)p`na>LlfmAmQb9~HE4(MrmSrmsm>WWsZ10sRD z@nboBc3SAyeplaba7Kj$txwZ96J1UkPdC2gs07{nTKFiwnrij6WSfG4y9v?K;R@-E zaA);erCY8l8*5h2u=1-pS!@q3eYDd0+E5oVzpsCjHhQ1OmjspdFT6F0CmXfYb^A`>^fE5uYiu}#=rGkxmZvfFp14UHd9$hP zxj)l!=zsM*K+fwWV+N8z=uK-NULNmO+G%GJ2JkBm0wM@#GS*({1s^@R95{< zx#|<$rY=7mCXRyz=i{hm|JBpBuWH?B7-{hoH+>Pvdg?KIGY4U5Rn(d&!s%ETFU$%y zC`LL#6?W$`dYj~8+3yI{Ga(2hZ0c_NjmP~<^^U$MInkWI-BkJp??M9Bz&>z#rAxwW z+`z_EMt^RUi#y=_JNZ?|hq#Nn>@p1sI2pa~RVSd>(~N*)mhEyEBtCQx zPgN2xz8|0%)7z!7n({8PVbQ={?uIk95M6i~Zlpd_^--L^rCxT|1Xwi>te*Nwb+viwSQq&yug&Umfz8)cApzAH7aj0@cX-Ll&|VsWBq zcF9mwUSgi**u<5{~Fz2V{!aLFsmfy?YfMYvD0Wla?QsE+68*5Qh94@lpChB8qhvvYWKX@Y~3l+S|*k$Zyz!> z_%Ca_ROIfeSzKW|SxDSctZNA;Wez;Zn41IP>+zJL-tl{W=JW2?6aE4IUKl1+k{)BA z3RlG^_*^1f5EhTK>5Sv!0@d9dn`3}(y4I<+sx#Jy@@bAo-BAG*({|!?p$ZMxEi=!c z2%e}{wh)tC_pZT<__t&4UA2mJEUxP?EoXF?xX4(7jg6#{&U=}@+q;)?dH0NkefH>^ zr41Rl`ZZImRn<9EX6>+MY{LAuGfQh?Mx+X!N;VWbIQC?suL}~!_G5%TNGE=;iRIMq z+0X=iMZX%N_w`ACMM0nnChNJ{^BF}!f;0bB$6^>Dmq;y= zVDD4;lSWm&tJQi`h(mgVplh7gDE^CgK5*QkGYtX){d1!AhF*)$*_!VMF0F+%vLO>w zh%%NDlq! zH6!%ehb?S8n&fH#Qp|Wku%$J`_N2g_QA9&4L%#lO(-Xl=kB*+QC${sv?FgN4aJQi^ zUvLrHf6fP7&6sl{pFg#h6Nj_{nWU zH7lad$;cd_ds0oguVsv0S?^KT3!VWd5c$W2HXaDc|wlv;ZyZx_r?y5u{puk3_DP%F<${;ekc7`;9tnSAKfA0ghv1}2x(x5onw16#l9u)2_$_L8BG+~ ztUogw91Zz-5%+mhA_5O5ajf}tBz2Vz2k-2B_ak1*6ZJ$Z9N<5&rK;1q1_t|=}| zw9e>kGSTRI$|mUA4i}SrbWOy0V4hUn)lz00O>XP859WYFq_i&w zU*+cKOy=ct$`+9=NiTaT!SHsvM6Is%xstgG2dplHMr&h;iE(ELRF;6-jSOfUb*|%I z%j02RaOO6|0!q`lJ;Y`pPx9k8xeNa60a?6Fr0<3(uZnxf^Hn+1_7S*6MfL~L{c6iKk9SpW z(A0~fn&(&ss-G-Q_9rr;lT%{8zUOIpCz*fxvwZ-~A429=(X+*X|4OgZmbp*%oUGM? zE%ik&O&QhR}ynPjMmXlF?AReus%6AS6Ny%fSNfe6Vrxj-UB(*X_Q z&+k~UJK))5rnO#>bq(N4@757ok@W092 zM=8S(df1c;SFMBPryqSM751HpEd+wA1I^<&#e$pDkkW7t=7HZ zs~eEO#G`zHZIK3x^qNYcR4y#*iI3b&ySRN9B!44) zA&dH7UI0BM^`3K}?z4Z0sbyPBlna=b@x}yD!Q%231-${t@fvh6g|i&0Lp^m&jA5lNnHRwsG=(-^;@^h4Jsxa9=rk*(>^)>yh4 zpQ{xOZEyDB`vSh~>cG)%sja_-3jT^)%*TaD|5K>2f}>2$9keFS#IwSr>f1z5S-Kql z=&boK#mK*ZsA0fZuT;*pqAmjGy(B2f;xgnFe3oe!sxvo|6gDn-E<3q{Q;b{MCH*t}jTnRLnUNxL zL`AmZ*Pa~JbTUO*k_}ZdOp={13f!owOb(V*tZUKdWI1pd6Eo2f|D+J8kk`g+g*SuXPS(Pd|@BM-No6>l=9(xfWbf_*7M{pt#2^WG$;SBQsF@hUj4nrt66}T*n|+*=&#@b zqNgYO5Kv%^L(aD4?Ki@R4gXi4;opt+zoZOl6nI8)<0PnS^f6@Q77-q61JRLQCn`0? zf@CK!W<#?4@z35wu?28NJ>8U*XaW+e?k_i=3I7`W@-Zw;aIa2}3bXD~*M4jFh^mYK z82Tamk!DB|^#}j;0#=e*(1jd;pya=@|5hv0Tk?kV6U{bWrrTrVN_l|`p~K~G&)UYV z3<1>Kmb$p2(gunt@3qgw>-+Dzd0h`*IO({|zvA8`c)KtO9O*~62M1OBI`a)XgH2W2 zZ!@DZ-I-fv zFlo4%$6@0y3Bo(Ng_X3Br90zt?XpST(gluT*^&ZwSXsu#Z`!9Tj!TfA1o0BZAATpe za?_t#d^QDAqAIlOw1ZIn;l3 z*i2aZ1=4V;jKtK|kGd!!ghy@XK+A}k@1Vq}xv%kP?*-?uxH-pd>Ydyz=@PQ32N_n( z38QfGGYZmg6rm0$6p}ech(f=RuhzoU{#0Q%PlRBc$ObEY%=4Kz#>Op+rk3SjvX0Q*saTb+~*V{(Zo zP=(^WY)2&RnwP!<8FOb4S`$qGpg)l_Y&BARlGVXKATTbKvUQ%n8~nVJZ6PG^iE2D)PTw;0^s+2fei2nE@Uck3RRRdHL_P{|cukf_abaGDT}=VlD{V|4=fiEiTj4K>*;Xq z4eqMhA;>K`{Ir^tNqr?miOQg@B>86q+7AryOCNhasH&%kn7w}S13RSC4+gGtJg0Z~ zFe?D#B*$wBbkShgI&3I1|6coT0a!7bJbOQP0D2#DE-}=eAu;TYt-85_rrg0-fKLV} zZUg0zTncbQ;@$mD*_316kook)N7H4z=78l8ApIygRiwUryhBvVm&xO-fU3PGXka;J zTinP?%8-??Tg_28Q${%YOyd$)_XwH)0yeTQ29ixD>rd}YF|@5K>Int!agMsa5}Q>s zv+6W^{3MfYo?rM%8$|)MI=E-d19F))^JaVBr$EPgZ9mrCZzI4%k^Z<`qZ3#aCs(h| zoA>gZnLd}L*{1x+1MKsJ5i)>6#)k8J-@V z1xs^S1@c>&z6xf}QC2>U2QH90FP(42$q}tB4;|3^an;MoE}CDnqNNxV^zO96YD0dQ z2ikKV--ts2Y}R-SCcQ2w%w3xwC<&%!hZm`s=cGlcu0~h&{}ojD2ZaiWFSeo%-uLtr z9L@z}BKSry^zc!I2<;OI#n4nX;ej&=OW~>0cM5Lgn09Gl9v9Z{M7rCF#k*?T;h%K0 zb|q*2^^aI7m4J7r4TzN5r6_+nP09}z)4e1TypN3O$})0?mBmed88se|jkFqr$7v&1 z`62-SDT8(mlIdN5q69;apa(M*3kS>l6lrpv%U@M+f~Lm?XB@z-81(0s_0Q9;l_i#d z_}m`Q#Woo(w?I44P$dpG%R?7Mm*|_7yzyL=%y@wT)h>?MwXYsn516~*xez4CJes6NOqe6+KeoKm#^EH5xpy9fH)J) zfAL}L{G_*vSfci($I03-Tn`@tynkFlq1tbXY85Q^bwJzB#$<5WgZ?&Utwd3&puX3= zdo~VCw}qK{SZ+qSw~BM+`)0mZMBM^8Nk@dAEMq+_7u}q`5%8M_a+MiJ z0F*@Df;LD?V5&HQo&)HW^9wT2`R$WlNd*2gC|Gjzt4f6YH=oN%J2HLmchK9l{F81` z(&o)S;R}K7%$xZTq<3@ZF~TkHd0LqaTrQWqp>MA$di`fHA0TTiT`OXG-fQ=;o@UW2 zV_Ek&>YC(zOwDH#Hp|}Hb%0@j$DaVD9rZO5a}@q9J$8!;!t4^@3+iAOWjFd~QD4L% z(Dwn7b9))R99R4*QD>l}@;0{g&+E$u^bw!AlG*ws?NT$SQp;WNVt%55bz_S=Ez@^gOdaPPj@tk( zIk{Ae95MnnZs62cqKBOusr1C52>(lQLk#Xz`Hb_&!0~tEm6#~&XAIH!9kNKW*6qLK zHZh7s1)tuKG0HR})4ml{ZE;c(`ESL|zrr9T8+c&oyl_yrjv|lx3%Y|%k-B=HRt*X-Jwh8vzJH+bL-L3$COS#mmZDP7#p_Zls*C~D^7U?X<`E=1%r_EkOHNo|$gv&TIx8_`^Jp+d~3= z_mcWf==xl%Wtcli;hr7=3>Vphfa4c+dGA5}`Alll$&Irn>qOpQiqFP1v@KwKx!S_G z)ql15eh`jfsm(v2DOPEiFrSV`Z-oUx9Is)4=2OZGu#Gmjh7xT6pZlNs`OL5ImhccF z)C0yzJlRj!5HpTt`C3rltX(MCR+)KT-N?djnR3Z>zJ4&GApykZn?<=Lj1crZ6XzDg|sD{Dyf^7r^@YEKp zUN6N^U(zEDrygMeI^0T@CuVHUadc_je9ux=D1wla5#fq=Ui_U=?UKXqAjPNz)7@x3 zh`|j7j3}fK_;_E`|F&sNUxV>(>=m-<-a_`IdU5s3w%&BI?yb4ttsLau(e*11${64@ zOfpk}`jDPlTD`iCb3w8IU^{m~JGiMjKuBqdi zx8;OM>eWM0$}5~Xk65`5=9f-wDw*StQm~LwAS&3Ht5p7JnP9i#Ut;{pQuE0k*pnE3 zs|j}gNffnr4)pk#~^cI{?%RkhSJwfcznrmO0uSqt>TRmtqMp~Wvt z<*hzCxhki5NVeQLPqxDs{*DNZBMi11S9BbXU#!KoTgWr5dU719i$CR)-`>?TpMQ4j z%zW4*lu0OxArZ+-E>Au`bX>hmvWaT_F%Y;AqHp9)#cE0FqMXe6hkr8lZMElj=P~Xb zF+}5%3fsnVdLJ|MmUFx0+s&2g;SSaE|3lYXM@7|k|HG1kq#z|NGjyYLgLH$mG$=So zBOQWt4lUi?9nv*`fCy4bBOwSw$N&TL9`5_e_xt?*aC8P2(4@BOJhWQn9l0OEX6 zb)cV(w7+2pPfi=TW5*$qBX*>A6S(_zXGnSj4gl-*JzUCEbIUCfY~7{X0{~s-buPu^ z*Dns$-*3%S(x5#r2EvKzK|?T>+{aQFJk(9dZ^r55K

StxB+Tz6sg<&${RIw?@DkTaI1i;AcV5%kPhQa&YIV~y;ir#Zq9F<;2wdl9grPdyD-2WlGj%Qb)q9_{x3nm=kzPgtBHq3Prdi~--* zWZuhk8a6%+A(Cu>f(YIj_Pw8)>kW3I&V7Mn`e19(t{?>$Bn0fgQv}^yvv+zgTD7HGr1lntVq?}0ErCN zYdz`!-9k}~4hhNi_kWuJRGq1h1V;+vM8EB~e|q}n+eC-3K!`lv;5ERAu0FU~cO&|o zbMp!rC@`>3?|+tvl>oXtDde*r9zIknAHd*vQSo|N81RFopng~KHpS5xoBK8VV0GY# zXN8g+2~`p0`grSxxVaaUZ&5}zV)rEGW5|0$WBRJgYRiER>gNh(F)rciW?{RkGw-2%mU zBJ#W;&XLbg-;4cjBiKY%1JwtZ%^{cTwk1l`{WU$Q0<}rf78Qqk!_6o8pq_CMwcyfg zCe?tGc2`$S8Z<=&NP6v&ayY2-c=M(T%1|L`bsAOV+mg&j!%QSAdU+LxFj~< zEBWtgrM+A%QrD458e#T1wiVO=DkyM+L07~F3y3o5HAr=Lr5O8bf1xoR0|5DUTZnom z>tMt0fE%;s0jiPMOyZ;Ogn&iY0+W{7E$iDQ_cv|>?3fS7;mT@H9h&M5^{5`Ef-z4;Z2YNkWH`7Z-3-OF5lPT z5CZff%PCu@)>$sYi}1L>3+FG-DHfht4JuvNYZ9v^_y9%#gLBm3AAnWKW#z0E*BgI% zdLiMxOCR{l=p=X9`Z=ynqNNEs2MJvO;)|D1XNY4iLj?2smmXB+o+Bv?p5(0MOknN- zsrLx2k1)jMe!@1icXj@2UWhz5qf5yIH-Rw{3x3JU37ecP*TrayBIhw9xkXr~esrnf z7{2=Pj+E=Q?he3gcj9n=ozw>#n! zTZwtE#xT#uLFK~%9hkPh3KBj^`urp}vl5=cD`z>k7C1ELKO3)wFup?j^n!#( zcs-36Jp7zrHd9h**tsZL^5x@ulZ*uVo?sTS(Y4HgD-cEHPgT#|kRq1V6PC^6* zi7l-{nOb;LxC6IC;C=M(Lwe9)op;ah#C*{g09KNug#I+ZL zm)Mwc-*41Of+_p)JW%aIYV>6jSt6pUoViCS{teU91G$&I?u5B@9hMaWijbIGfzoz3hyPv()A z^S-~^=l7FishtwvSY-;3DG4U5S>#Z>Og2W(wVrN&ii5~tQjXm+bDyjkjW-m*NMvO& z+_WmcBr`CzMc94a5xAI^i!b6?ancu*hGY_h4PMn1-+yA++k$(!vBzaS64yM%W)je{ z(vjhaEBhyciLjLSv52dUn%T|#{>zR*H*Bi{F6CmCYNf5$b2!Vw@{q(}W;Lbi8e;Qp zK)z%%XJe(}Uun#3(!i>Aw7l5DHY;%(rd6TFp5I%6q5stRB2eW!IbR{_2FrEm0alHx zhrY#2h9TCb*FTf%o-#kMBlO}OIiGJM!n3hjpFL8mIr-+hldyAu+Y-&0_3=u)-oeVJ zhkHG(rZjZ%NQ%DVt*><1LY-0KU&Tq#1crinK)X|en4jKuDJhE1F`LPy zcQv}}*6gHp_q!WU9jY=*U~}uP6zOGuqV*llQ-2KIwq_?)wX#!Da%tFj?e!cUPggJK zX_G`kXZum#PoUHgpL5UDL&c^YR49o349%+vtV!#w!i}9klfFNC`nmEzqv~VoU80$$zuo_ z5c^>eT}JrbnQ(N__8>?{gywMZmG46LJ3<8zrQ>S%OG((O z6%(Wr?FO(em^ue!B3VXgE^C(IgojLmttOp$JjU6*i}c?=(MnvoEe{tSK5SoXzy9re zc(Z=9#vtF~?_Bni!n`PTbEhN?7I@e^e|QR`U3<6UJx(}leIEH5aeM(OCU}=L5_8F) zvkCXYKT}v(FM#Z5*(x?JrYIxdR(RLv-pEE^f(4rXOfb#M3!~!(~qbXxt{g zcoIIqO$X?%-*c?`x)440j#hjPFwZeld3wGt>({)lx>!X?&)C+_;eIUrG$T?6&A0od zVb|*SY1k6hW#ls67ttn~r&HEpJ$Wj6;wpJm$=JeQaH8)U#-B2e_u`$fuz$ClnU9;o z8IkRLC51!FzQrr~Wb?pAtzXl4vLC|KPjyr;xUxhUIn&RYX+4b^&okgLkw4_l$rZzW zSiV0>$b%$Uv8~34|fe%Nm9H4zVP)Qhj*DcYoaV_k+MnY=XzH zk6K&ei$S`zRa$EkL@ft2&e9wv;Nn|gxvOA*0`dsH-=>;+Z`)e=?)_DKYZEgtle7JK zA+Cy_Wq|B#*>Ku;8v#|_EY8W&{KiH0(I)wsB@|bUq#)<%d%h*&q++i7#t+^T`ZtWX z;MxE##vKjuoS`%)GtV^|PLi6)I@Rw}A!|K0&d$J*I~A`8Bj`Jgtv)X9I4br&G<-+Q zFdB(^@EqwVd@^c1fX5?FAlLYEj{vGl7iOxTbi>QITf42;S6;_NwmYpDW{R{SB)1uQuCvbdGWlO1tB93n%}KIV1uZaX+ADek*lwU>sl4nw;l0!8bA_ z@Hj_+eu8WnLbg%ER~ewB@X~vG+b(D8k%wz3hYwH zWx7vP*)pMNTw4s=7`qgdue1hKm-rkW<&r%TmWJ; zB5gMQz+K7}L|Lc(R)n7JUf#e5TAQ+QZm^^KtmdZ?K3)1Bm$E>t4+L1Hibi*Me&V51 z^NjhCzaAY&KROMpqRiXiyI@E3SB)W=dc?yZPIq+Ldx|B^U*wZmM+A13?hygqo1 zZMb`n0ZlM8Li*IPm1uLM8o5~uifabNM%uy;j$W15Miie)A&;wRi5KdvS?al*t?c2m zx~jxu<$o2hWhbPiNT5i@sfS7z)ZdCHAthdm9-hK0Bt+KbsJ6h?ai zUc0+QqK>!6{UNitkO7v|RDdHNfWJZEZ6iN3A_$l8WJ~;@D5mZXQhsoJR?UF%;5V2k zHyGsD%XIvTc-JkT#pE%jBY|Ed*1>4c))NkizAhvak?m>jKHJ8d6!sKHDp%kF-Xxid z`%^rK_WD4|j-E#~#~Wlk5?5L(;hYxBBl=sgVZQV2kB)U1i|#v?hoWcGA#%$tEW@2z zwS%3KT#mWyM(4nPmjZrbyK9s5K*W^8oT&TZm2_98XUmUzXkj>=55w(lHhCIOYn_{Z0S(SIX+Wbo0AuLw@zo+Hhos5|ke(K}^6F>Cr5V5HWZ~!*g^jp)i-5pl5S`oDFA1xxH(ZOo z_cqx{TV6b3TdXt9m1BsORYm1c=z|w6e!D3%zZO^JN-W5Izfm&Qe>F9p%6V$GlLnC{ zZ~uNP3Deg`=_{?O0Cb;Qv^x}e1MA6wYWtKKFAs^Ew4ZD;9WtMEpw8%dK^=PESQy1fXl ze!DgcNsZfgcd{|u7|HGf#BNa~4}Dt}yBS4)Rk>|!{`?(TN&lMwQth>0Ub^YF>ZH}0 z1|0TvgiLpjWCXPN>(?>9ze8N_{_~)@XW}|@e~&xv9@IDvaVyCPyG-7_97{IAu#pd( zu7f>IU*xc%v?{<{Qqyq^^e|BFBvzv&@KTm=`D{FHSUz{dSLNJ9TC+3)jRTlvl8o>N zHISgq&QVk<2tOw`8I@5B>XS)6lY3m{_e8Nj$WsTKekKhy+7PKkPQ|S*iYwXhCdMmF z^cu?3jhNxJ=rvi76*6l+rFj|zR!t4#hr1XuDF!%1hDrLH#l4a|(`!uuTGGx#$Jkd| zTWc)TH`hcFr7lDf@rG|8;bR{A?sld|E{fl_i2rYYBxxK!G!HU{^^B&T!je${>L@OavYTQ%>iwVk{a z%5mTAP-w8U@+RA4R3A8&g@sFkgb0TAivdqv9v6R6{c4^vdf!}d-q5I0pGy@~H?-c1 zK{N_417r80S(C9R$Ft-dJtm6$zD(!;XOJ|0sn6lD&3p~f5}M1+)hEbR)|Q0LD1(?b z6&2x@4;l^QyVd!RMf%UR3dec$3aR?NOK{_8qANIfQW`36 z5-dUW=)FI4t!kf?<5u!s#9UKO188*Z5pA_#-ehr+eAUMs=`Wr*h~3wI3pyqp%BbVl z*3_tv_kFNRY}>`(vuC2wF{Ku5Swn%mqBr5P8kvG~gugemRnstF3@v_KpjB@txsm3KhX5r@RDeMpF0g{F@%S1)~ z>s|cg%^(|z8G7{&st{?y2KC%ImgW`P+p*HCiAYgB2dyT)VQDBP(MYoCs2qFwn`a?jDr#lwuwvY49x1ht zqT)&>oUc{mF-Y}Fb0uMxx`IB#T)1R-SB4X11`s_Ugss>5hoOIhGLwQI2`Of@)qBfY z_xa;>xs3swo?nTwf+tjahDYd+PP&UI^- z|9VA+CODjdoIl_1qLRH5g&DM^>okZpO~f~>uU=Q07o!KX=n>~u##SxuVM-U6Uk%R9 zf>kp+xUV8w?R8&q&b}C~82X7&KUpiq2zWC{L8YiQT&AiW0!uE9)xFBMdD-#!k>l3W z*9Z{qv0{OANAjprj?ST>`f1|V5BO=2q5UNwUw&&$Aq4vq>7;n0H3Gf&R_k10e+DI__NQ8K!t|6W#q{gek{lIB#!2hwS>bl^O};{L>b&Bfs~;^%C1 z`ci&4-6Z6LbAuD?#Y{ag&qu1HH@o)YqcTlq-YNx?5nX01Q%H@5racQw`k}D zZNeoXiFyg@%7@Mi{IfmA=qe*xUjC|XUk9(N;8I8-U|sRcO? znhnMgZJ%GM&I0&Xt+W!8+?4ii zg^H#v>wj!EXUy9<9AXs7vTRf&*%bR^alBz#HQC_QuJmyP46F$&4%i<&N`}6uig&~@ zx=Kh4JlCEnh1(9J8a{CIC)?}f@q}R~>h%jdvgwYnQJk30?#)ZDF^+(BvPl*}tovVW zFmXJ~zD|KVPn64j0&WY-lKy>e{p&BjE8MM!8N$qH;k8zfu@|vD(IhD9-p8TSb_@?T z*!EcX^0s8FjCF!ZWX(vW!zLomijy*M^317{4WD^8JUwBNXf~LWlKlJcGpxipW;J;^ zBtlF`T>;n`xqO^(h^~KC$o`@tl-5&8;w6hoAT&G{us9WhG^&3E`I7ze8`@IvKHn~B zI`3 z>LTDHk$()A0rtToZwr&R?a#e_Ro{A&EsnYl2~6%10|CxotLy-C4>9D~*LOzOW&!Q* zc2`kzb-=LP?og9|Je7;Oru%BldB2>6W$}59f&B{m)2@SJts4E16v(XPLI3nI%FHkl zkfpScXk0)OoAu zzLlJTeYj|neI23p(e#!22L1O$B1ou6u!uVn-Ulo-;N{9Z8Mml=P9I8et&Vc0n>@vofo#jxJJkT3x1?x z4h!FBm!Y%+SsC+ERWm@^NK#|7M545dRV<{XIz*3)L^;P$}ITup<0~m@CWQ%4$ zWH+dR#Bb3GA0JKwiss|BkP8zOFfO++p#!v*6|YaLw+kmaf!p5 zu3s@@!D?|&uF>P-{3s$aL4!~-6tai~K=*_VFGvZ$`5t$D#2rSo~RuzAA_CrMk z&^OEK1H-lK8(->dre8T8bc%6iTqv@de0p2vKM2kT;K)IzNw8(#7W~WV_v3j4(SZ4; zn*JkpX|D=+C|Gh*Hhu%<^-Rq!*z;ftG{K-ZkKTB-Ueh-+I63689udk zE$cW$v;34=8a6}59u5xFAPU4kB>$<2(!McU?r?znfo_iYJTIGh z;8(5Fyhgc8pV)zhq38|Duu!9c9Nh7ZbhpNg3x-eg|18=N229f50a&Ol)gl)`p<{|4 zAH6{7y_KMynQjc{@jx`-nm5!tz(vHlAGXxPiV>NTko;(_OIacjCC^!w)$$bty9^(( zJWc_(3VXkzW=JXxc$vdl<)x?9-2Uz~i^(Xfavm<|yezs%jyk~dW<2h$@?XPw^cW3H ztp(bfE*?Mp(2`i*>y2A&eE2396^gqWcK)rBu4T!gvdzk}b^6nF<9bc!?Hk-MG>nyq zJ|uIhR@=hD$;#z*g#nmIGXb5BYNUKLEWzDBu*y3^2_=?gw5z;zU-`gI*Wu znBKx_nSwvgjDZ|pXLH!7tHK;D5$>D`P0}WN&#fIUTqplGTqyK}+L5;8lb=cYH=*;} zIjOxCtrCaN5pv?w0@y!ZwAfSG4X&Z4;>`)4{Sg_05_~mlXPVdz3@jt|s5}a&S~PW9 z^~_(I3?I4o$v6Gvb!pw&2#?&SO{b{HD3yGGw~|U+*75Qe@8-GhKvUXV+-(P;rXbqw zsMvQA!GV{@*Q!E8F7AYwN6-F04yFESGAr^NObfq^cku<&9$6rpX!9N{w} zagK@0vpL?&bwbN;om0mASYjY;l^0b|L8A&&Z`F<<-3<0;vNuU$;l-zR;pVxYIFYv- z=MP)`nFL3+>;)VrIR0H#B}4{o{M?8MtZ!!q#^eYHJkTf3*oXmJ88ZS+MVqu#uV31T zkQpuUAsYpcx=O=nJ_d}A-*hzZzWJuKnP73+8GYwhYwMVqfjB6|OyR7b)!&_EZ~>z< zluoaW2By(l~95&i8=MZX(^DgPKs-0(A_G4=IlTnpG?Z^(6l3&J+<#*7P^` z|4Ux^1h@CvE0!EKNl61RU4HS)EnT|;k6#%5z%)5|z_ihB*{~s)i_!ORjvtWdvsgi| z+)O{@IMn_SuGl771+;ZodB znY0;qdsVvns_*1G4>r}&RQz=qmhNykmdjk9k1rj)0hwPyDoeh0_eu$~f#Y>Zf4{xd z2ixe|xXOouLR3(?ZR_{aHkIT*C!2m|O|9b3y_4W4tkAOL;Qx)HZ z9ITitR;1SX9@`DE=NuiQFCGr5N<{Z;PMR!cu->=U!hd9a{F>zul&zu|qOm`1AZhma z(JhF+QsJyG&42$Sp8Mnsrcm`*$>HnKKR`>yUco3EXt1aDYxu@czIl&)Wn}A9@CIKxlR(sYPTf?b2G-(_y^M!}oCgaNKJNCdhPI7$ zb=BmpXDd=+;&rFwdAp`xn=kl6ETCpf+CSN>=_BuS4@5uu~~>1Avc>WlEZG3H+_4= z10FKYn%VX+DxU5Iv6|yDk~oo{tg5`?=XyfaGCPwm(dU6;iwrnfVO{y5nc zKt2=9L`J$cQx7^t@ms>H^vq@ao$dM>+Q7jn#DVxee&A-;z0iTEnyA>8*i|*v1Mx5) zN2gXtT!B?T2F{2|GKV2Y0y3euZ~LUiR1w47w)IU~{Vz zn{RcmHd7<%TCxj&CmMwAcQ)ZRhDoKFIR^jeP&B6D9}?&;U%_!Z!a@@6{>F#CXC@6l?I-u z|1zD+UUAyOIutl#O#zx~xZ?-8`5KNEn$d4{mPcWCY!K6TKc0CG-`S4baqu1PYXY!( zyjHnR@iD-LT0%=cTyDh+xlhy-!px$nft3guSgQ*6IoDC$woQYAMgZYQ(&iX*+AORx zVEqF{IKY#`B&+~M_Gge!iJ!VJcx{HYECsXpB*+I}T?BIBKh~?0<7~V%ed-9MJuo~C zifj3hCp+LMTP-&kze=OJk%2SNSiS|zJiWXpMpLiScyoY{Gih7qW+Ptvgbs(TqWhP2 z%XZ$?Tg9-bObGSQSpU_IvALB|eQ-h@t9g%$xVY=9^1&3G%mpgKtWGWa)0eX65*sQJ z3d=^QtcN$B%|7eX9>n0ZSUUg2Fae)Dnar{1R4kvdbyUS_Q2lvpsZ2!J&H1w3$58zG z!mDP$oUc96II`aBTW!8;qd&%uoZdp4QQ4P13td31aljy zM3e7EKie$@8T@-n!9W_k5fvC2ONL_C;8sCad*!-VDLU>qRFY5)tsbYV5{}uhe#yhf zDn<`{?_e8n=He~m4Hg`5ZPCYif!*53MCBJg#NiCdV=kHV4 zSTV?vxLtylueA^UdIBiL1iOy=-GW1&FzPD<3Oh2RT&T-8qn#yE+DXaIq-?g zL9w@<;j=_~uIP6Q&okn$wvNYu7ED-jZlm4#XU41L2T*{V(L1;^nN2+@ae-cWnoK8o~@;Et_e_>^Tge!Td{=b6C&w|bhcm*As(YDB9N*C#A#vLL6DL@OP$J- zls2!a_nyirtFJ5TWbn`OFIQt@Oqg<7u5#gfWY7LcaOtf$5Se`9MO`Q6TMgKKQ#|hG z&7p^1Xqp3!L1owj>SqKmTE%#g-+t|TId2L50pXJnf`}RPWjQk>J_CeL-qv$ZDek=c zH?&hR>DNQOTFf~;7Rs^Y<#W6sswRKypS_B5Kdfu<&NVvb^xcpm8|QT^bR#1?aNCxO z^@|lDc}|{s0p2=$6SfUqQI(+(A-BfJtp6AY**p)TS2Z2%PSB?!UY3O+(&**SI5bLX z=p~>7J6@o_E@(zj zEv&4jk9oWZ-_=wFf0PBB6hx%$8mU=+NHek80H~$kh zEhhYJ5kGRn~pmt_>x9c@5_ult#QuVbHC~v?>O;u4>0-x>h`mYXA)Lyb52xGU1 zVFXhF{W-jS-UJ0Ra9omca;}&inPcigi{#9>STG%DyZbs4Uv7M7c#*7K3#h`YSxCpf zJN~p9UHoTJ+}raAt>rTz4T3pbp*flYj3zEc6EW0YHn1sve}8axLpM$Q+@@zK(UE_* zH%X%b#2r3O@r6ixrdATRkw!@aRclD@#yViDpXERk3&{`VWwnf%Daj3@>*mgal|$t9 zmk9kwRpKQf_6bdl?ip$`z*M#8q;?D4@4)VA-Y}6R_MO{5y#U_*MUP#hkH5!3-6jvI zZ36Xvdp3`v5v%s>V;UZ{}teusN2?wZ=eY>H}Cxi@lNUmJ$ zwtdk!RoI=M4MVCi-i;Zc_%7>P9Aieb-G8FD@G;LN3CXbs?tidFE<^rN8;XAD?f-z+ABs!5 zkwi-etG}_#{s_$Nb{Yw7gb#n1!4FYyZDyv=5oB!E;(IjK_>T(or1%{KUW;g>Gd5J^cB?^inBQ~l z6Q9h^DWu|2`Aze9BbwlB3=NmjKmHy$Ax|HA0Za#sG+mgig^ReZB%RT*Vpm0=1^mX|i9B7mYP6F0dA z1(0ASkf6^FCDDmr=Evh*pA{<7WWv>_XcEMO5=LsSlsFj*G0{Vc2zl73 zGl%8qe!NGd5f&qL|K8;Pe~b^{7d%FfN};0QtKZmuKiCsh|K$ZTGp0Q0kG>)@`)sQJ z9*&g#*p~=XtQ%HY7;Ky}EFCiv9gJc1%pV&=aJrC)YHM9)S2_Z>Y!GEj9lX{K_~@6U@OLv(x0K- zSy92g|K67yj7AAcQ^G}ur%1@q(Iv@Y>-0;5{mvORCG5onKMS2KSe}Oh-R(osljr6# z$kL~$-dry`+(&bt4s1W1lySITNW5Oh3H=r1%H7IITmwK787YvkUQTlbh<&2KrW(Tej+CPAhH;Wk|GAU@u|ewM z81vFoDO0`l0n#xK#m$*^X|ABhP)`#D^U~nlla1GlGHH0S6e5@Uxd$xgWdkFGp&BLP z{>N^>0V0xKAIz_iB-6>9{8TnOcqW_+_BtUk<+ogF08Xb0=b9pE9uc&A7>(%V7XQt7 zO#>D>?IQZu^Z8ZEwnjpr+YrgO`}}bo5F?eIkHpcY)%X^B5euJA2=oWux(%w}hRcMk z-~ztEc&h-@dV7rb&L9M{27cf%cmCOnZb_qfCy!N~V8=P4Fr4 zzNyNC!HT{7XeThiT45pFQq^&f`DUOn))D!J?y-r@wz{U&4+$>ke%z7pyUG2XLGtJE zs=9N_JsowC#`$F|nLL+G1*4(g9lvsVo7uOHrYiDwI{wd=stbFlg;X#93stXquQ}A_ zK#J}eznq~P=sYw{AT_-CU+|q z7z^$$UlfDJ_djO};cbq@^O-;{b{F7hDK`96nUDr$#PPsIzf+GoVGl|Y9773LUa*S( z{ME{KEEyLAyUX$>#$_U%X8;%y=KLEf6)7X+EyTJzwfyaHv!ngBZ zPkryO#@}IEbyDuTVuGH$z1&`ByI)>DG5Z%@_3!7eH3MjvG7XTK_*-Dj3@TJj4MI!^ z6%&jwDWd$bG-g;v$^^pFlI0b7h)`y!w|7J&TnYSI$ctC~gBgVwYfG?9q*VbECMX&l z8aB5KbBHxeEhJTf6U=R`=H$MXn1qKB2t5Fn2p2O^fW|Utak^n!avhk4;+XrvpSK+} zs2(1V0PbR8Kvdnt9arxmyM9&rxBPy=xI5M6+Lt?bT{&PB(htya$pE2Hurm7lkA6+FBfDk_=VO9C_nMde5VDw%tN)Eipm-sAXLxoJHM+8rO3&t z^EadHe)>RU;(!^TLLU&AI>P~2c+n;*ZwX%I{$P9S?Ik+t0fmv-p*=)EMpoG46(}RdPwVub0OQ!t1Wq<&KRkr0HEe8j|CX z%*)zO)z8KS)wc4s7S`>%_P`{}Wac*M{N*v)N?lu_P54ZES-8^BQZ;5gCVE_QrJ16 z`u*b`sw)+eV2&aPheO%HasYO^i-;L>^&CVfVLv-nCk8yM!9SmD^jDS$KenLj@;uiru$zM zy7O+oun8KsIe$@sg#m#oiw{D!7{jxUjse$(aNuWh*t-)y9I%k!N}FX(OOr=|Mz-ks zZee@_N3+C6H05mR3W>fN@h*J;!ZP(9V!u+`ax+$Fa_%R<{58(iw|(TEfk5ax!~LGJSS&p91**r};|5BZ3^H=iEr2o+jhidPRX@|}w0cK1{vX_} z;J>+B6ECvhLTI*c;jLEQL%-Mw?9fr5FXfUNAuqC5(Jf za2_YF$2p@(fVWgPgFdg4;%2@)dH4Rr2R>F2ncRcn{e7ku^|}+w)6YUv>qUSrE7hIY z?MuOPa`(pA_hk36iml@%hUG_MsdFpG%CyZ5&1=ngiVPHN9&Dn3RaS>7e+MK34-EX> z|6N+|CJUY{R`yN|l>mlvI0$gO@eKQt%%+=kFsDN=c<`gn1_lLi#h1XO^ndjX$y%5| zHleLiwKPQaWxZX5d0copMtgN3YSabQ=^6YPvv~kvA}ZgO9(BUhdB!%WPcPWU za8=I4gsSh5?R^XfXFepr93aLZVdNPQwj_BRgdG#ZoG-a$b3dndDD`aZtJw?Gm%;r~ z0D&BxR0M#f=mXBX8@}NM5fB8WDTX>|e5il)?NC{)s@|ZMPJ~cL4R2T_QxL_M5zXDf zkmaHfUgx~t-*#@XJpe8aIo$hNy1a-Bc>TW5eg`Pls@D%&?<;|5-hJl-Tv>iMxPcVn zXzY>Zf$G&(N_gA4Y@_12UK`5G!|;Q~AN_zbjB+Z-ixa>{S>K8%UnWOq2%vE0Dqav= z|6b;Q+as)e@FXn@w~swa&@nFJsBdK*h^lNTiqiLbv{=XSKdzHHA7a+uh}Jq_d7Kpt zt`6?+%6zbsY12F6aam1dD&oq6+_`k$wepEZCB3~F}{iIoY~B3W`ClxBaM zBou?pIrJbNh3BH9Nxj>AUX>iQ=C;O!_Oy~wL!$W;m==^)Esi`ETnGFn9uP@-^?2}@ zbqNEi%_qv*ot}rzGs-N3d|3`Rh@Sn5H;ozmX0E0r?4#l$tgb+s1OYJWjShcKXT?Fi zpyRR;$ub_3LQ`^y*o>!q3t9ELy`*qG+nHB-D<;zd*b)T0(E$@?46&*AhflKd`@TUn zvL|6vSr`9<-dR`jkl_~00J=3J<{4^ACWhB~HIYT@7K}gln^55MU>*|CqRr5NJ} z(S<^0!Vc_Ci8OEPBk90`8siD(egNSv?BV+6C5ZfK+9 z$oF6?Wy_<1h*=YhL(AM4Gm|Z;u{(}u!%b^})QY*w`vm>x(Z?MVo6GBNL>KpbV}sI(zitWGKSM+p2>z$ zHNsK4N+MxutEDD;E3)=2?Q{vBGc|gj$U}hbFX;z(S-E(OL_Tl%6*Uz7mwX{$)EaKJ z|Lwoem|Jfg2mqN(=3I-#b-nB&AC6wgXv>6dg)AK>Yca^&`!a%M?qT(2Dfw7$*uhxg z>g=Hy$8QRAxTND;qWaSNT1lkzM!BQf`x6i>Ba11f05Pg#V0A|o>OSCe#_5LMTSycS zQ6#@!n6aqO-$ECBYPhwn8vw<8@WGslOuGUn|{Zt?>_WYv^90i&_?BM8^5Qf~` zK|E4fdgm$asRIV{z<`_k^_2K zR>1cmt|xM;5g;dm7sT?_KCF6|ghBynypbCXF%)V6iS(U%53M6c>tL*fAZD+>>%UM52zpV%%u|76kaU(e?o zCi?a-r^gNl%iPIM#6;uVpQqMS>=$Rimg`$C06##Yzkjvc+^AOs__kV5;%Oq~q2?HZ zoTcQ!F=Y6>XQZL}NKe#|ZMfwAt`>Bn7n_1lwp;isckF6rvjY?5LmAkGqSbK~P>`0&=@Wpz1Ced$(P_)BpPFn8nO8#qR?C}U~ zlJs(~^r(LeCiNjn)9lioWheVx+_^u>A70~5CGKUhJAbr&JgXaD%V(3p5gCWXY2Ql8 z6~Do%Hz5||-u#=%U3W#yu&8ykHJJ~^v%qn=&wrCc2{^; zJAr|H02zmV!<4~X#oWXsxyjSW%A8gsaAu`Z2BmNuW|47qjB9a zAwMRpTZh7Q{NK-99x+mBzn6)Fa)77Pbg$a)DK8QO^jFW~Xu>YYZ4&>k&DA`@Ds7MD zX5j9n1p`k+p8{ygSu=kaz49=&Po))Y5?VQ4KdPQ>Wsd>qn3-3=p@hNNQ!6PN_XE@cL z;m*CjK26z{qlgcgXAsCYxx~S4#E-O#!P9XzUiR@>DRDu<1+~p}kt+&M}f;pV5tw(0h&q z_}=>s^p$q;B)rXY@4-ySp}`X9gI&Fdv|-9hg#~$qTgxYZ%>;i5qrdEwq@h1@Bt+fFqJtG6DBYF<+L3FMtF z=L4Ft%c}*Oq}YMGNI&|->nPZXd=`bdDctLOQE-Fpco@)PGSSs7%`R+@*RtEZn5eOF{^z6*NaCsL0N6!JX5-|d7l;m;Gg`H7I{C?AZ@Q8(Uo(rMhJ;d> zF-z@mbnhbg)^ljSyw5uLtGytp#V8e-GF1inaWRm*OO|wM&bodf_z?Xo)UVA zH}UfN@4H&DIZLEDf8BJc$+NbsTFZ8K1Dr@w6~-!Db6m#vNEKtnJTk?(L6#nNKPZZr zVhnzL@Mm}j<%<)O#qe%`%81e`>b-1k5Rd4mH97H1^Z2+$#BNDX-{L?km->b%&vW-w zQRn`L08qEKy$Nk=Ji@GHh~*HfQ3_^jIz}i0Hi89Q#5s!zZjZ~E-V-J^k=du-Acc(Q zQ&Gax)g`wgFX&2;wGlGRpSWDH-10d%GKD$2vZ0II^NrP*ls7Ergni_lk>#|}f3N`~ zR0@`{o>#M;rV|+Z*AmT0G3exzIOv-m?#oA8ABr|JIl*oEW4G{A5SIyd)#{wTq4Gnm zOxNY8*}$iF!SiwkwZfkD>!0s9kS3C&PeNvYL1tpg-h>sPrVW7y>tA#?z~qDxu1l7^cM_{-;I>l#%MRBjIz_KnIB~8t7*!>bk^1o z1}Am~PJ7r90*6VX_4Y)m+Xg}8h9&?S>FyH$1*O@&XiH>9LpAFqcMcYNm{A#4zuS9^TY`%v`?7ihJW_dY*C4)H<6vLL@d%cNo1855)s?RVVCSDT(b=Yrgv zKLQ17b%z+hG;ONwnQ|*U3$$G`SjixYE5Im%|K+1OlymH)M84z`Xp|ydd8|AF&=4yS z9U3rS2z35y4K8Q;iE_Qf-!U4Idwt2W?jyVD{BW0MUF1x3qYON)uQLiUCP~y(C#r9*0z`iTh+ijGa zjpYD%D!4A?h`%V)ZeYhLqodlqwO17+8tH>mr={h*{%`B3&l#>S!b#2?Vc%>WroWa!I)RoO#Y62kol=|QdP|zaWLB_LPh4b?bY#cr?(r3=>iJj11%+1uBJgD zTE~Q<2%(7LMu&M}I*C`Rr4`@F;xWa>ucrm7C9^d=pQp>6S=vp9*9L*HTjeqpG)P4d$E^~0-F9=S8!Hpz63OWB z{41{$wRsb4rInb7-g_*ea~q(mEjp0usIy(`xSTsO z{tM#M2)x4gQxFqH?x8ftG@w`H;1SNWg0#Z8_^YiTHuH*g?*xgzl#!ismuDLz2I*S@ zoBCth9dG@dRU>~83}9k7Aca)Gz2ONtc%Li5P9Y5R^qv@$9>)fl8qaBk-#ETu80qqf ziwoB$ko;SEHIsM;;GZdMNr`>x6~Ot^QWv8m_XXz%FKN8btO)DHg(<`zO387 z-LXcfT>J?IW38_=ueSREYt6TlF{#GV?O+~+QvjQ&V_%QU30dYZgPjN$aX;4R<6T%% z_z{`#-8qc_x^EW#Pk^()C>FUQYz4GR#%hnyd;X$}@t-lNy?^a1A+P2*DhcR?Bg!=+9mv^ZWs3r8qv~y%e7j& zRM2BjgIWC+qmtC}&*(inK9{MnN6Wf}R^VFMyw)U95J0=Kv0@HEczsD@)j6P za0y}9(&LGG1)kF%z`^%@&A1N%k-$N<*2iM$i${BCNJQjnWF}LA*Ue60H>Fo?7bTUB z#O)<{nDIpXzei(PdW45YV>N-oY{q_M)3;Qz%aVoyA}_YLO1ZigY!TMM2{lY??++BB zMS~ofJ4De0riZDRXCA${O?HKlz9_MRLA(qqRe8~QY6j7Vy)su@$JK8M+T{@&?5(iV zsuInlS6hl_@TrsV^BmhVRgE%t=Mz|_pFwc1dMSZl1Ya4OjuqRVN?7&j=~L%H#|StX zLRGo)0sT2k=W-Pc$W0zD3Bb|&ZJ~5%PjQTB>MZA7&Nu+$aI)FXxA)>0`Bm^FW!l#9 z)zX>KYL<-tyDqS1$nXdhhA6odQmu?aFjSF^L1C~+Pp5F1T$Kn^zwXIJa!7i0+v!tg z>dJ40j(ZZP-H9WQp~S`sZGl~=%qHsX9SLN^56f3rML1TC2RGGwEfDu0kfY8?zO4AI zNCJ?WQk}k6EV1QsAHxoWs!WF}t#HY)i}35-dPMNyy%1TwYgqP`v!r{H*$W~vXN8Rb z9&F|Lv#2T)sumZn7vcpU_|cZT@;KP*SA)-k68c)ci#F#g%bO^owr#q(@or)NQsWaj zQh`&&bkFXkb0Z0RZ>;`6rULaJYT@FzS?e89;US4}Aq$ev;KT_#DBjmEqZO<3d=$Z; z7bC6Dvz)AGH)iAs%(E~iC7u?2f4}_C`Lh+y=Wrnt_-9}XKs zvJE;FEN*Fe3E8ykWc!gVXj=OUzlF(xfU++vw;LU zZjIz5flKK&+LKOUnP4EkGe0xb>Jq-K4t3u0`n8;D`Ap``c2*|~wfHFt2(eTA@SO@u ze-LBrJ>7s1P3Ef(fs4-Khc0)nDl6{Jxqdp1v&qRI0`*uUizsxYF_nThr{Dni3}mr< zD?v7Bq0Kp^iDB{Z7*m6@P@KMC&ugFV~R$9zJKWXI;#K9l-$(}WbUeN zUR473mpa#5X4=zOc4-ljH+)8`K->ByJgXzvrDp#^}Avis+%*Pd-=zC-lqECLmQVgUJ%BY!qk;XGtFvMwFBvV_x4Prp? zM`Whi&kq+ShI^(>5*I86(GbFj*em^!ixl|wa;U0*Lg^tk!%sddHQgR!&k=TPtz6Eb zdlK)Q@3hWAXEdXejq6uKzp{=UgP#y!-AYbWo0&lgx)r#0g#W(zEDv&nm_SdW%4G{C zS>5SCw069jV$iD2s@kgNY@Bvi&C^{Y9`c9NwRkMb6ZRs~EMxKo3nUQ|37Xh*MJ?M_ zi|^mGKdN0A!&=89zmW8D(9(sAe=J}{;VT#h*4pRN#RWdumJz? zlfAe3$B`+)##qMV7z=C!gEv2{0jQvQG@f5Yo}dDd@5cj3uSt=0Z)a)5F9|A*FMUae zjqDdj3`}8MDCUMmwrf46VF!%WAl}p1((B)baYhYP7OerrO?=4dgvH#)XuV46A2d1x zGO?cFS}03lA?L^^9s!N3yKS8ot^z0+PKU51=%2!{*j0@^Rsi=|0xmnasVUg2$!AR`;=-t0~+N$WZB4YhfZG?NV+~ ze=;zGDf1B*o@Kf+M#e)}=3Lul#pF^qR;g|~XRt&!SCX|eX-S6++FwupBp>9;R!w3H z!ke6Lf8baBPWVTXbC4;aXa8qcYuD_H)i1LR%bi;3|LBUs`+R9-WRP4wF0-eK>PKnv zi!LiZ28B9wfuK+s$W3%C$O#>KP9-Wyk@`H+LP~Ykw7{#)FXn#iQKa?wtReks6eQ$A0*Y#C*fT?_D3Fh|MV8;@f z=b;F7NhQYy{?)`SRJdPsA5W3CEjIZ0gvERbo_l!3KGUZY6$@6u7G(0Fy8w@;rAP5) z+A&ZB&t9X++=20a&LH6!b-vNrtD^xh9b+z^VFNZjBwNIov{(GZ(YAK<@RdN-kfuYH z*T;)`*G1K~sf~!4WHn}}=)U^)XtCKM9_0w$Ed^^1k#FAi;SNhyWN0N(kA8-xK6Y&# zXW?@u!~h043;q_Q^1?!|Z5Vuyt0q!i`RX;FGa{s?(;AC$&LuSBD0E428aXmj*lnrD z3xF2wp?s0U72~TDiVvR^%a_ebiZ6*pJd68x0!pw1p*5 z=HcWk6&9i`rF9+ml=As1rpBjFnFcCGMBH-%VI|!$(8Sm8PFO8h%~<8Khp`kd5P2P; zvPxZF-n6KuvAT9IeaU6lxgH}8X1O`0Q`8YA_&V5Xyq%~w6Ix8rUeB{CH_qYv6dDgF zikZBgC4|*$G@~AK2c1yCOW{(}E7J@qP1O=jx<%Hck5taZ9Qq?Kg9J9E zER2>*kx--w1Jx-8cF|gF&?WXYz<4SdQNgLZ|2vKURnQNMN`9oKm46}hN;x=;kCxa+ zjQ*4P-Lql$x?(G6^Ix7vGw&8FvAYJ2T8qaM0VuoP3JZc@Mzp~GY-c9^3Z~FfWPepe z%-y?^DpucDNUHPPjCFK9T~j#eVpKyym%z~T(c-gWcvj@H;G7duhDG#T0$xvmuGO7j zyN^`l%j4eHMU1X62DB*JOyPa%OiQx!S41`BHdAj4*zSJYo5NVvFG*?UTt>ABJjRGB z8lJG&lZr44aV??j{CtZn7<& z(2!R(F}btXrKoLr9TD7b;{8Xn8O|lTrW!!F$QuufEV;CBRyJtppb<~HzBkB|5ptli zpmmJ$krJWHtBix^J3ONx3wM$dSb?j3*|#)5eH3{Zm8%b+K{DsQs2r zGsa=76OR9$b*3;8XfdU!{!G{EFfQpAY2#thK3AXN*ju1@0!TSh-}ZTg4&UDiT^=>Y zhz-S0Sq_a6ozRBEWW$+Ssa}`)vDnMe;~Q(AZIyY{QTsSp`Ph4&8MBdbC6k^Yg*SnP z5;Zbnt;-t=NuCppf&4jhr?iV*x$EB(8aU6bsj1rTXI?Gq<|0q)<;3pY{rJ16@xzx1 zMT_D(KBh=k!!O;;VHM_sSX|W=G`ByE5;;E%=oOc$i|K!Z zzMvuY%?rjurFqV1d9(3sArFy0R8Vc4jTzsVNcd(v{w~jJ-Q1pI^)V`Ow43sFN+j5y0;?W!I|km_H)9GRVirz-fRi zh=K4Z(v|be8iJ#aLF&_&x(6|B=H6$gWws_~kr@B=F8+rsIFDNNnbtH^&A#vdgn~(& zcyWnD(P((%ilfr_zsT#4r5g2*l6x*5h)x0d6R)JSDN$pjrb))x}^ z`6TlTmosY|q>)D7(^JL?2JfF=qV4t>B8uzXDvTy#8E7mr~U`{_U{?ve}AFFfbVX8Nuu{*P49e45~e@_H(pl)q(D5Y=!kd~;$nC5 zY3MYvjD6T1(v~L5X6PIiKO%;Vec}1(sQ5%b#_i}a(?8wQyZpbb8~;gMuPo8ST0 zx~w0n)e?L~a8`JtKu@e6!=mrqzwf^rC;K8Rh|IE|VWT>Q+9@@BHbpT1JSv;=#fj)R zyq31is0mG_g6^6RyQ}PeC{fYlepOydGiqE^uy=zG@%+!^I31kN$y!fw#0SQVNgPBd zRYAUjd@-NeT?~;(XkP5IuI+I;cyD1rRM%inm#(G$IzcTMt9#!E#hVBPnw!yp^}*-K zXedY|_>x@Tn6#p~sa(T9 zoFoT_28Z}Buugfk>>UgIUT{8wtS+LfrCnU_S20X!zN(_XEatI&Znm5`)=fA_M2G#K zE{;1oDxrkh^RJ8JULV#&5K$BL9G3f)+O%%USF_c-9m=wCTs8A75@kwxFPog+IySpw9Fz$bMnT6wiSG*%?PA(L_I1;?mYiK%m zY*rW!jT;TYe>I;?i3sOurgY=#Pm>E*O|9FfXSKL&#^CQf#lhEx=;se?n}Je0XQkW_ zMcn`4Zy1x#%06GZ`O{i7_Mp%<;Gf58C!3ZaQc@AGQuSuC;H!Axv`>+ec8K5usrzh1eTk8% zUOa0>{k7o70Y~BHUqpjv?9E|j(}|}&_L6+XPcEf{0HQN~wg2KR+JAR(zd`#`d$0B5 z{@bul$^AEu{0X_lEggSuKSWewXj`Xfc`%dddV2AF)3s#ANZH>B_bV?%uKhO{y z@`9|JQc03AoZ|Q$I%c(ZoaYLGIJzYAys@M+_p;)tlIgD^alT_B#l<|t2yd5DITwFM zpa1UaVS#s;mOWp}JCvXKIoX=c{2FrHrl#9m6?m`cfunROAu6-wL;RNMAe;0_W(SjU zD4wNsYy!?|r+iexDK*701NreXb**!XR!6{l*t+VCn*`qDr>~Ek*tTCaa=7{M7oimB zAFK+JJE%>+v_IVJDP955a07cQbRKvb*p+E=nEyY|yZ((dT(imzYpgoq_?Q>`PPhL_ z7OC9B6Dd!JFFUPuXhez~0ek5Mdw4xG-mlGrZeH6S`x5HPqQ6n7>6WsUoy)}+4LK@<=VBFHBrBu(Fu%H%z8M(EcY+`#i9kS=*U5^F;n~5gpy#nF@XzlY2iFtW zO=!LL4NauZdC7I|8)R?jPY|C?TR;I>k~w`~$t;j)i(3Iq>ZN3HrR?XwKUMZ}S~>X> zblo)mmg=Akj)T8>WlGzg{PkaL`@imlsodtPgt{)#Zd+QI61yR9k^$d2C>F^#p@iUQ zPGmnK6!~?`u*E!S?mW*l!_~C5=lR!0FV|dC9**XopoYGd6ubA#&um?ekUe%T7qO6LILY|G^OXY_IMuv!pu-R z$mXt|K&WX(UAtH6USDIG_+6P06eJSXj!CBeS+-sF32M-7et*`KSd6ncN)b**q-~Qd zu#@*{9#pR^jP~E@Tq0@bh&1yvW-q z(bgrdbeglY{#&sYx)JlIEme1|^L#g!=jjq0MURP4qORL7fvfp2oxi18JfgD78dJY8 ziC?vfxfsQV`X!_5o3Is9HbnipV4p)b{Y4~ zkb2IYrSX0yb~h31-G>0|2f21}Z;nrgDmMOb!DxX9^RmI|PZ74`8BYXJLr~hVjswfg zy4hLpuu)QsZw4|WD`7!9c!d926$x;?j4ij9bCq(aY~1FtA!rp58Q$DwGNHv#tXsH|X$Z5t}`hkVB@( zi>s>JtJYd4QmbhbP1=Pl^Lej_1)!oSwL?VFJ<|2OcC|j%noW1xox$`Il@QN|G*h49 zx=NwJ5b2a;AKnp_zI{k1ym2;@lAd@7n2UWFzzS43QTs|Yi=)er$zNJsap5IOCCb|n&Zg!!4M+A3-d2b-!xS8O&Z%%tV! z>rGO^Kfk`VL$jb>H;pEP3BTSx0+kJb%$|W<>0I|@YRdmaRoC}wg7ngWVxY) zZPa)f{Y6(TW8A#{Dz*bdkPH4Ze##qOt#OcTPno$X#k;ymrL(Zb{JG(6)2VtUgsY?_ z{=Gzkm^tl|p67MG``Wh;hX9OqgxnH3wHH^9|CHfQOgz%;n_#43SG2yS@u87wS%*^cfm^*n!q zs0hDP4E5t|Y;rtyaIXthfnl&-<1>84c?)G5+fF=Tt~V})_1&>H^Xq7@1D}w*?9!`) zw&O60K(pbosP0fdAUs|Ka#?bTL*bkEw?TZvV$R#6{Xh%$2gu>dOinbN7ck}%w4Ce# zOkQ{k9s&ylSC9xc^W}th&cZsh9)BI=&LUiaE=d9cX8Iw(W@j$RE)GJ@hW>V+PtOid zSvYBPz(cMUY8OCbu(|83dEvtR%c9~<(^N~|E1*-G`?&;20@b`ByWd=c#ZL3S+}Yjg zHWLbe8*3MzThansc(fk>e9r(U=zF-Dx9_w>@vC76_>ZeFyVp(AWzFO}Rgy^IhM+O} zGXYOnkmtss>=giwW~Qz|v!|n?h|nL(q3J)yx7S;l4Pk6=S`*q07hSg;Z};t^zD}5; zDN{tvWr-nLY}^72)bZ^1#Jug)1L$CLHtK0w0Z~-Qa+#E7SF?L?wOzP!BnO~v)6^~y zW$xI@p{R5DauecLih&h^9QTi4L_j6Z!NlB#ZQt!SVjWQ3&8#D8gn%UJY20hzu6^p{B z7tnr&*miy9o7OF86*$$&%C^!I?~K#eY*V$v>g`3iy5tY!`}^gYrk*ZLQUy;dw5sBSE{wJ zJ@#bH{+A`(Oe=8Ex0JAL5$ZVDzDiKi%l8%@RIfbgNxk-*9-bxbv5Anf`Qs~mJNuPp z!5jWBlymzk1|qg&`Ag+>Z9B4#hhFRaL3tCgypNM@8n824gux7g{@ zbdf5fdj=tTjTQCBtDOP!PR;6w`%a4++AX(Z#+k{tjVhH%&(1|9-FhX~eZC?iC@Say zH}InNNgD@}i8n$UoCs1=XZTBsK>t`&Xt?fy!R3Es0bI($iMR+Zqp~L#tj1@UwXL;N z5`Oe>WriFd7v>-N(fsQ)`ib5Ltj8sMBm7tNy~WG%?oxvtzFS{WC^SjGpy z+e@ZEI}6D>vbeN<^XXbe8v?6>RgeAvmYv9E(&D}N@Vn+04)u-qH}`}f3`lCu__iRm z^cT01KZpv-BIVuhkD5=+u2Nf0tz#~&>r(8yoy+&?LuZT+`e^rVWfNieL$2(aO- zyErt<^xzMR}yGzG=8XcQZ zgrq475R`CBX?S+zKf~BL%5I?;x*@TJn;AIN6ed?Tkq$=$T7(D z59Dnemn}*%*zX}N<1w0fY_*fjy}x6xI_Efq-=zfGT+5NOn#NpSI+?@h3ZY_ZE?IRM z3PoM-g6r1uyl3P|*sVRsG%7LPY>m!%Gj#O<22BzoL=*8C5q4K5MFhVt8C4P3N4u2j z+MGdNP({nzCD=i2zv(CHj~fHe-Occbl{hFqCLaHpeg=75qt=!Eo2FD*)?=H40UvTD zpT7)z@k}A^3W$~*z*bsA;{Dz4Z~=Yo-H#TyT<{qVTQ=;plJ~rE`jmHZVb^M;etp9amANBr$h*fndf^{} zM@)~D6XlAY=nqHihqbMM9A`WBa_!WQvY}fIChoCvQZOp2dD9^$*D6Zjl?x7iIY zqJ{67XTL0hSDh6;=$5LOlvni|fT#acjBkhh*vMUOSz&RlXAlr?agjkF=0pR>Xw4`^ zj0w*DmdfHu-atQs&J!d4LE^e6i^FOjMVHCr?}AMyPfbdH*EPsQKPy_7uASpIu2vq_$Ei)MAk;k6`+48OwYh$ zsQGjttfmN2Lx~cP;eENv3XpaTpW>9xcY1TPHyyLct$}Br+vwTnYl*%A34S{hlN8p4 zrVm{3s_a3}>KE{{FkZ7%Cxh4A@F~B=v~pnQ2zc)qUymDS>d?jEq*=G#6+p`vHr8|d zxK~7(kU`U{zGZkiY9BumiD-g`DL>^^z-!?J^F|)YNY4ecw!#RJ>vbdM&g9P-7A6Ij zpgOXoCzKam_lHw-Jf%xT%x|{m_XmT_=GHG6eske>om!5X2(s(S$tR6u&_I0$ zxYHX1$x@&iUkDcly*}kma0;4w>?(<)@U92B-Z=j}odZ#iC7wPK?K8bNCRl zYW`_Z^kyAk%we1_wyR1R;`H7~*jrYUx4s(akzHIaFO{%0?SO)N;m>k60P`(?aT;J4 zfaJDv4*PZlV{jtwdJ^CUL$TE}%gSf;VJ-3<5o{dz{YnyjU3t{G+jU^HV%T*+owZ7D}pyk#$Mt z`|$C?RY7URMFxgyg9gF7_kxTt#!hqB0Cr19FrL}$ON+rH^nK-5dhb~YUH{b!^i+Rp zpp-Uk*$LLF^B>mFvPvseAQM4K<1^!wmoyQ~`rgurv(~89{jv{sRpV~x6S1k}bbVsS zAP03WiAv;%ql~!2+jQt@BMV2GzvYiltzMTig(VM;2MzfooNt`8+v9X+6&KZwL+CyE zGHlkq{hdCc3s)fPf9g{anErGqb>yoNx$XG_w^Hk8dE>N9Zga!unkD73b8Vk93Bm_| z5yzwIe?~k8V+_$Z9^&99>qhf+SHDyL5=EVSDJn6sB*>Mei2Et`ZAIh2uLl2dD$TM$ z%X)Y!en)VZh$Z>m%a8dkkp{c&8ue9(z8xJR95*xsF$jf9_&iJ%k#+~2hyuDoBSi7~ zWV|&bC}=>GkBUN49rY!w10Nf~nu0haxkH?c(oz%|&ITSD&4^_HRUtlN=~<)K4tVN1 zh=KLAQ_I!6aJ6~DA0F#Cj$#GpamxjpZVcR3iUyrYcl>12502P5&)f9<@Ha)5uHGNu zawSmH0!$2hX!mD6XL9<1>&XEBoJm+UDDK3?kP1si_$5Mk-k`o%NTC`}n{~hIprg z%Aigz1UrU=zP#1360RQ4Fb*H{4kP;TUPEOQ6RzfF$zj6r-KU5{8$pCMc;?~#f#oAg zeu^mn%Y(#P0pe+eP8=erJ>|Rl$LGss{&=1YsQW9Lo(y znwO~t7g#OepE2BvUYr50;lU=>6r8U;-pl??pHb%3~eQ~nAHL*Z=7|Tr)*2OGuHihX91#B zy**#vQ0*hw10!b;$eZTuB_bTdakuIx;_2zoLWiHDxg#1hW%tTNAHZYomKEn^LTVN zmHd(v(X|B8_)DF|QMft@a$T#ibrfV7>Y?cBT5J=?EoWkMrVW{lVMb77z4eFj3!(C3mHde`ONK2gm%%i7p< zdxkR5BUpa2+0p-z3bZyV>GjQ6YMmsCS|ZI?bS46-K6LbkI{@e z8yq9Jxzi8tk;vaTaBoBqtOI|oI!EXi)HLR;2%I?9gE+Bz6eT;eSmH2_t_m%L<;us+lXGVDI}9IX_kbKs!xgnatC%#F(DI>ZbS@OZ6JTEAWoj zhh*GheYc(^A+r6F>K;S5HVmR>40~Q3zSWm^+e~Kok_9;*dwXX6YqEykJv{nP1y$Li zzcS^KWz9@GDK}A#y8&@;-2@%_z|K{8WVp#bvH^=y^ZK`U8ap`eA@;5efysAQ?Aq#b z0vot;hb$`;=;W3IYq@+qhU>pKz7v<%Q6*M1;eVP%u7ujADXhJ8h6m!8fEo$ETb=D( zGOTQfk$$$~snE3J-{#+af$)W&^h6iu$;D@&s;DtwoNMr}iMhU%5i6_-I8;9E< zF%qo{&~pKJTba&ba84A8eA*1f(9j_fMtSFrdCFoqR?8er$V4&nQOo|!w zSaRE=68QT&FB-;JW^uB)jZ$RJ_@?hk3nRqq{yHe#Xe;ySbbdQ+(i$#xlwQr{DHaYx znoa1t3g1Bm*s+XXwasbg)6I62D+g^Wlq~7;jZFJ%(S(y=K2_y<<+X9SGTt{4o$CiS zsAoz)wEMr}a!}Y3rYvCQQx%crpK{tP!?oJ4DJfH^x37B$58#>@>NTms>;^LP-Ke~AN|#%G?B`Metyn|_+)zZ zA$dtW6M59CW+tIjtCqZE#6an73w8OcV9(zbX=@RCmVvT8sFwL7MYU0iOwQ^NKPJk4 zd?NOCyAobN;DFm*o%%tntg~_M-h=*3s4wIe8_Cru+NQ&KTFsofnzHBRtnfh`XRcyq z#~9TOwUqal1jm=PQAC!$PN9_}elH%)e?R(snAg?JNl~Yhl`M6tSk?rZn~!U$Z(|~ zHy8NAWWg>Zw@@D!h<+OZ<^1)bOeK! z@;HNDUHe#GghNMlQ!N|^mRP{kPMc0_5GCZnQuB_rqCpL!6CnlWs7pH(pZoy*}VBL^nn{0&Efp%g}RrNCk0DIRhsMH>m%7=|LPu8LJ1Ceguk z+|h#mNV~ifI-!9?0Gl26Qnvn8l0Q4HTAkMLF~}cJM<}L$ZTZICmX0UPOVveZ-EEj3 zGQ`ml9nc`ID;%5~FI9qFY~Zi%%Tn069)5FY{rw$&mX?r3OPo#cMF`P~PEhfin@yMM+=Whj^J zeh9(*3`hLNBdx;iz(2hegETC*o{$8NdGGDf=x+4ag7B+jB_Xk;rM1T{cm*~)yLs>> zE=;;kh}R1IS1vqo1%}UjxSmB5GwX&+2cw9fAueMFQoeCnA;dH=&|LgVCAiuZ7HoX; zbgksPThY4XU;EQCu08=OVh7Aq4Zg=e+0>Bu2SzbfMU{aj32C2LN{p_+=lB3)CJIK@ z$pRG1jGTn-*Uan&AyAa?8@O;n(3DhL!KEJ%Q>gh}wbUX^vcCCpmbplw0a*upDuZ0S zZL~%A9Y~eCA`1QT6oaVO%WRMMho7PPz%<+gx0_kj0Ngh!R%_Qv0-gG!>L!!11ZCCX z>B0>S!9k`fn<9wXgxAl5t3U4`acK3n*OtHFAUr+D<3)KGpHfA(D~+|S^rQ>Yr~aOe z<3M;0B&tuy0p+ES8+FgTpb<;H(@K@Sgy0W9Ztp60wZTfRC72g>hH3lm$S*AQDJl*7 zF`TrV!EsO7dGo~TaS)#M3em|l2DWb!13EqzLeR4?&N{AV*nye;sDAIEPp`xznOmXd zbMWoUL?R|}wd4Mjd^vuK+dXBl)rdzuz%f=7oMey;9s>+f_IVi8IT~)@>4Uvq|I*T( z&H&+=Ml%v$WsIQjC!yNF+;z_<_mVt&P>s%fGX4j0TI{*rrR)SliU&ztQD?%e-reZo zBG$lDxz`E0WUHO%>6&gFe5;DYRx3v6qld^l6QO4x3XIm&-YhQ%MM4KL^v)d@Wk&?i zd$E3VZb-@NBLzpjg-c@0g@&jZjs~JBDIL3tw~+Z@>vd!kl|X}b{30_^1Xnr`QC<77 zB8uPmzxzL|e^LSNa*k{UAQZBZpaS9w;0TxX1ud&F@P<7Q zuErG5JyKKAs1AB7;zZB!^B#P$Fg=e~g2u#mumiQ+9Qu@@t4}DX+~H1qjc=Cy8=6-_ ze1hZ5)KTkQ@vvw}ko%fe(bRfe@uYnHnTExgAeyzqI}Mp=}v3c=bcu6xlAj@I|2DjkDS_A!Z!`$m&4-z`~|Q+eQzRh zINvCrC-2dEPsP&Uw8P=>aos3z3E8=_Y;52nK6)ck=Ccb@e&Xi%4(AV_mw(=lGCt1U z)wT5h{=tv`+e7zAkYDJ7}k5sRy*&U2Mmt5T08$tj=zn zU8(1P{9}A>ro0OC?^&BELJXzg&PxwYs2OiW&B&tkNP>PONniJ}KYs=fm00r&M-i@b z@j!T8cMO+>g5|Ai?k%AWG!pCo3~cvkxMm>I#E-xp(6pxySR&4TCz%Q1*IsV-Ma?bx zAlBO!ouynKQt=gA7}hK3rL4jBd;MWK<7ShbS{4qi+I(WFHpKNMVb6=OYP*M1uQ@F= zPz9U@Uf*@E1;p!dv#h@7wxa*$`8#hFLYHvUXk06H%74_vI&T>E%N%<#o&)1nD~#~0 z9@yxLrH5->&tF?$Znl4a1heeAN{A!2TSD1+^?8o}r0M!Z=}w|}qKi&TKm(h7Tem=jidZr3nc zQqeP;KWgL=ilnMrU!`T19iVKuOHtiVpPHx=sllWow^X%dTM8(^%4yR;N)H>w+Pg~u zlb*tNqpI##!a}UO)_65jntKf(aI68!AxUZ1hl9CMO6`5rHYSy+epFmLXVq&+!icq$ z^lF%I=@Dh!i<^4n_=poLvu2%Ay4jyCc|A}Zu7;J_5ob!M14^;Dn7O#zj(A?*YGAr* ztAg(OOi0hV8AEPWB@Fwr78H{AkULFtzEkzMRZ};}&uJ}LwVN6tAf`3)LPK`ZjEb`>IuCcUy#DQ;@R zF$QX7bR-eZv>p@8RGyvg`pWBWpXCNt+lNw$S`i)I# zSs$rA6wM8N0tsibdd|fUcSjnp0XXXTnVX#7%y|^JsrTcj(II-zH)m`077tnF0`iXhHVej%6)I<$_lBO0vrK;}CU{m}%+-VYUDH0EmT*^+Jvgo}o`M-3yL1qEQz{>nV?w8)cf)>jS^cLqp75r{ z;ogws15Bb#T@)Fe=4*Q})oW`KoGe7F)?*351JmQNK$_O4BU%p2(#-mV!DH~cg4v_p zohYO&rxJC?uAOQIizNSO79sj3mP;wmW3970);Ns!t5Dol?;fh)aOW&fjKjH}}6 z7P*4-D!uE29!ur=wM#7l6jw2eXmB-;uTY$yrs>B_Py$d&c*2cXn!Vj=la~16sU7*u#YWte$ zb?ZCdwrGaICO&oAwTFwceHwR}c`9@h0hG_@wUs2sR0aL)Wh~UG+?w56-73B`+RbOi z$%KjVSRJmrPy3IYrkn#DDo;QD6_l57+o@_W^v)YIKgq?D8&PRGCOG^MDv3;Al4G)p z-b`P)jM#-2cu&5&VSZB!^$8M?pL53u_9Il%+?07vCQ7T!A;U$Z9pX~xb9}-myc0-d zyBbFM=IIZcQ!zJU(ypM_d>vb?tWeJzc2;8W=?=Z%Fye-?<=WW9k{G;J0C+?01&68;I!;g^KGe9 zL<&JBx4vD?R>tkEi~~a|i26IuUJCWfNELLg6xb^kj!?ma+7Q9v9X{exuN@vqe$WMo znQ#a=q5aZ^U9KyD=-r~}yS0>`gJ=bfhe7b0W+Cc4z3mP7^s9oJUo^;ON!-Jn~-7?3whbE@5dUeTgha{XL9 zvX-w}GgU%##_TR+DRB76i#x~u-W|Mf#K57xf?V>ClDqG(97c*fU6P7uns@wLQUI+| zNUr1x5o@`-(J@BOE!C;D1D}1egyi~4j2xQ{BMWf?=9_H}xigS4^STq)1C-%>G8b~>^ zS|{Nvp@5|5x!TTDk-J~YcVwVV4?P}h0X4alk#ls-RC#;v$P?O89$vCm@8aL&-ZF11xP|=TOQcI8GYU6L{wk@tyEG4LGK4m4tHjz zbK~f||1W=uN}8YZI2`9diPJMLVLV^pj^3yA=7+RU{N=8ZkIebunKR~QDWUcOw z16>ZU%l~RgUyGI{Z(flMJ_>`3E5v)o7@ZWiV6vIdU#3*0H|x$)*;tBffTsSI&Gf)w z-NN#Pe=)74pbwW7~nuQwOmJ291Cwrx10#cA<|Q-Nr!p8Jy9&6c07 zQ=er#_iG9kY|^9-?O3ha0ch!XN;Ij>p`K2%waJEPg?qR=V~pOolS^yKX4zEAW9qxb zEUXl9=Ia#etNe1M|8Io-N=CWg_z|1PDc>Tvg)62Dd01_NP`SffL-&+vqW_W?K~9@G6rR8TRC_gXUa1wr=AU;M3i^A73i!xa-*@#stfCW4jI*NgBua$JL4S*&*l`9*iRD2gdzQtt>OBh0Q5@!PetCZ9K> zexM7IO{C{T#US56_eoZjjUqzBhtT7hkv;By_KD&#NgEcc_}Y3UppNwz{crzu6Vt)W z!zmaR*b}=skggl4{o3{g!{Wp69TMny=EO@(*PVe!e21`{rTfou`RE4lA*NDHuYgvH zq{K15fj_Z>AZYTum$Fc=xE>^%EucfdG~P*aH>Km2*3LUHY35bG7H-;%Xcd_oW9DE> z#%F=xcLvlMVwI9+PRT{vBgPGY@|=(!H@bf1-rJWM75sqt)u=9{^A(Qdby~mO9aq+L zvtejHtXrb?efV}dah$4gRJr#xtDG;LPKxEJ2$flgd z(<9rSp}~U6*2Xqqf61VOH<{u48h2+j2IIFTM;pUKGPBRdz=-f1pnwcCeX~49U%$=+ zbA|N6iJ#xju*9i~whKGrD1n7apd-UliKg+h$@Je;?;Tx+@ZY}e!ZpZ53UVhzYW4+* zt{#%MtL=y^&sb`Kg8L6~Wit$GDxv9lQ=$Lcqvn$LS8F#*0C=$SRSvJgugk_Kch9T^ zGM_!0^H5#X4a9IR{TGPbd0}Gy#B%Y+(uaG&=tos` zyx_i{KK%7q4cgIt1q?YKipeh$=*vo*j?KO(<4B7vy2jQDD5zZz46^o_@FpRq?2Sse za1Gjno+9rC(P2jFQqA;|;5(ledTnfKXZ9)Xqs(R6wp--)^g1qQBcN-uK*>6G?ho^* zpEbK7y(x608vP`(#vN3K2Ch3oIQ5;)QPpvb~0_e=@@OvC3O6U~$b4%>m)> zp#L9zZx$)W83&|He+m?_7dkFFvBxsxh!~AY@`|iH5h9M6tY8mM-k7oDQ;*q(2;?wrC@bv% zgi9mWEz1S-j+Bwj9;>>w)Gv@#^RvYu`v&GYo@1lEfr$I2`b@m&lYZZO0#R|5KpReE z-j63sQ+LEyx;hy$p?6%HiCd#L;l%Uv`OEDRqrFAIoV2vf%C=@?gE_S(A?UV_qWwNc zL?zJLgK%?*nxy7EY@ggVYcl`{R$Q9Jo-mEv)_q8s7EwFz+DuPO!^cTmoeH=)6O(wG z1<**{3*6WBMD1ix;rZssWJzRw>7)rFH1lufy;1#o*Q%^m%7wHDE=7>eOl65!Zr@G^ zt_gvC*&OC`yg@w_enO;v1Q#3x0!V@-ADvbPE0tvs$IDnMdTQqwtnRR|K5eQq1j$Hr zg9w3Cvvg?*`aE6ORR<%d#>#(|p2e#z&*3M2pH zKIB|t4V~CVj^`vXZ`S0~RDjAHzwe>2=!~9>o&<()Vr;L3VQ{g9_r=B7d@>qkL1r`2 zI12{LiyH4=GGvc}dPs~Hp&o#8AW_v8+X^kn^yvOf#lI(;VWZssfMT5Yli0>EX!IJ6 zY-qj*jZT*qNAVlXp?-$SB1ZMzLglvCWoifCEEWV449pK0ROP>Vp-U(89cE2zebo5! z=PN>9dcLS71_Lh6D*$ZGhlLaC4?e(70>tFH>k(WBWLXF-nMQA)@XwD%5_S#WfZ2Fs zb9wTwg0$sv$Y&x01RF$UnWtm$%Ei+)-|HeC26PDiS)gEvem$atEKeC3^*+idawW5?;(@fmywF>)!I+)+@laeG}tr?=OXm~MCIA7s? z61ZUc^BY<+kQ9LA(u-0n;>B3e3Y%BTJ(ubc4VD9$s$tO^m?#Hi#FAI5Ew2UhD*{e6 zL5MP-X>BEX^ z-r`Tp!^miGu$h_rV3;C$y(v%;aP{4S;Pz@m)r+2I}T0(BFsw zzL4x_qie8ucDdy{Mh?aK~9xZN@46 zQJj`ve^LHwJJV%&p=h8yKw|HMS;qAM@n-_okr8Vr@NsIaaQv?3!V=IN)g32Z1uV#Q zbpHiAd6Egk9%MsuHTiTN7F!c&k29Y1?{YjTXn%<6qtl-ZUsN%OaP=`>XN2Wf3-cPi z?v$__M;L0mzxN16C8?g))3wO5o2xGN%{X|D?RW^V4O=RZkVw1{aQDX&(!~C|+{BV@ zau97#=Yrc*xPBVPD}r||pWCPyDSZ=y0IDiZu>#X1>8C`4qEWqXo|*Xxw^oZdL|rBy_YLAUDxwv_||f-t{B9$v8y3FY3~Hw1T_u zR(RgeZzg*Uh`DLfMUkc?Yp+4gTs!1*Ezy;QbTsdtP@nIqo!$ZpW@~uR^Ow#ym(poQ ze@vA+a(#3KWZaJkWvwy6_>!*mE7LV)=MpT}Y%}HG`FoMJF2w2-DmT3iEEDe5qsLTUO%4o7l*4Uq@J>R-&*s>VZ(M=QJ6uDoab)O{OMJ_!>A zYo|iK13?B~mO-++OCjTtW`A1e6Q@o>?f|<~qt33H=VXw}iBH07MTps)0?YORKL3=5 z(>s{ki{h1O?aR*_s?L68B!VV`cX ziiR#)RH|7p^g|UADjoJ!5Wm=F<#oCv-`?xgAKdMIIb6SieDw2@7$VQ0_RFpanHMHe zdK7GU2%f(7@N3((_aW{vRg9P*I3{`JQDC$akD<;hy&qb%t+hu)FYxMCN7y52CG+DkK+qLSFzqkVaaqmJ_Y zS)1yo)NQdMmsw}A({EIkCjJee^68%njp)UL35ADMw!#Kuj2M**$jY}s4cehBfvn)k zp$FSkxo7o0C-z8Giz<%%tY%9KYEkF zfge0Q?GS3OGjK_aOtEvpr+;}I_*JBnJRDJIV9`!J002a7HVF_J?B~Y(aPUP*y--Mjfi2jq zkqJvy9`JAQbHabWoZ#W85 z;~XQ+NZgr!h>~LQ1jV4H8Iwr5p@_$1B1D-=#~z0I?TYfDgZ*X;RfK#9m~UC6k6eVU0MxB1BuQx%II(Qf15<<7$Y# z1p4%b6|6E&3*7c)>g7uQCmlD%{Z3(MOe~9_TB5CbtA_z(hoSj6G=7D-dHkx`Q@4)D zHno^m7-dQ(NwomzCo$kjr>Z9U;F{9Hq@ublGm z0tBFbvQ#JkJHHFcP=$?>A?vY6$DwE79Qafjx`VZQ{z4GEZTfgaM!0>pZQ! zc(!i`-5tDrZ%C4dAR96@jin}iGz(?=rdwOjJ_`8lNgTIadg*Gh(U)^G2+U*djL$ux zkxy!UeivTjd(rM-I|ry0VJ=1vVM3r}J1^#d?%hh@FUWbBP&)E&o0LtwkfHDWZ} zOB)hh-x2n+mtOnw4^SMZn8zTMpK!N$N;D})p1hgg(-0W3?xoj1Js*Lsz{3}=YT)-$ zDJBEnU!~*akitG=e@ruc#oiQO!t^Y@9ZxuAn%lJTrLk4%XRj}ExtCHlee@sMoKoCE zT@Y4$;B0k%ZC6SYX#L1dNIIKc*-~4ZZ6;ZSjNwL~sh(MDyi#V^cyjp$ zVW67nS}zbdtS!s#K(0f*bQg0M+uGR+oS*~!?x2Uc!D%|4>6tfl+K}h-_@~eg(YoJy zL-SP}`*y%Wq^>?0lg3G>gHLvV5TkI#H*7(t5whvcjJxAPp}zuiDTaxNd)LB57~n%P zyJwSI#q~r6i#8TCa0MoOB}DccRT;MrydM1GDi&9&WK@eM^l>;s5PVrOG!$}%lha5o z@*Q021dUwY2?F#SD4yxR#?(xZvfl4Nl~Zt7Ob+ZCHsXvO_2edWi@g6>l0Ne}DLg3L ztyX(Rgw=(!kZtaNu2{KRvDILcYkRgk>-1JiwafQCcUtq+dYOjh>r6@7Zb3Sevqk7~ zVPB$`ElPu*#dVn)i$Z6{pw*jJ4695wHOieC_GpA2c1(NAPI{U0Pqj`>MW}Ah#;#Xu z=zl7X!8DE~7X2EV;(>~VP+DNnL!{4PT>uN(VsQee$j%e7vLP|=91!L(hJ&0E?+NyF z>O2ca`nKJCd$NR91_~>!cja;ESHdpKovysXKjufRV=?Cj?;ZJ-jYdJ4G?}_d@Hv{7 zLeZVjw?PB7-p(nB%GPwcD%_Nxdq^={TlErfH;u0nF}R=()K`f>yYF&vNEJgYQQUT7=L)5(5+q&R~omS&n>Q!m6wlKfVV!r$!fBljWo#a^?d z0wo^yxSJ{`7LmK?+qql5>ZU2;;FOdUJEk4<^Gpa z{m`!TcA4`~2_eo`gTE8`S|+89@@8Euae9ML6G(||Q%si>6IheWA#ttrve(7jqRHCk zU+S{?OJ4OW1b_*+Qg*MVqsBCYNzxa3%qzt($rXe97pq`;t@Z-Mi^MPo?Td3H{;fQA z@lQKEt??&8`!kBa>94Kp4z`&73tsV#4X%$AaM9EUpKJurM@`z+2gl)$$03qnka;JS z?e4o2;I62))(#ojG1dsR4)sT>zvnAgh^`8LpBZFrbY;tdXmbt9ybqJLR?#$m-h4`s zZTa0WQfC_vJnsHb9(d?ud41z9HUpxPZEw($vAI<6+1v_Ims+SZ?d1Mc!U+}&HoZAT zx2s*KpUJRZplyC|d;R-M;?q)jXp=kzJsWjPSA)`#QiKg+PB!kO?S$1tGGgbr3*s_X zU!^D}vbYn6qB2Vs`dy}oWkD&u>{#KMt9m^ezKCU*l7J9xC#joE%_u(%CDN~i5}rSCKlpQ;Xgn^ijvHfivDo-Dsl;v1S4 z?3TX@x{aytEML8pejyDub@c=o8x2t`nK9@u+3_H*r4yyoE=k{0WO&BLY5dPH>!|NQ zGgEU!f&IRCedueSAPpI1g6gN{Njr=(hg|i)$42(dU5P@#! zAYbJzcKuaOC!ZCZVAY*rN0R4LM@h~5O(sTf)f#=zU)oD2d26;y2nzogx$462E2g0A zS2vTMn%Cmz)b`a{Cz&j3mXRlwH_K6wT(tL9S%3a_tgKHkhs++jnX05Q;T*|G8A}OFYHVB9LuuT z31QltGF&wBe^S9YAjvxP&Bt!eJ{^5PI3YsaMCgu}ht|lKCpF%E9h@C^yL&Xz-3p4~ zafjlwE`}E7mOud7 zJbYx1vX&(m&gA>s+ej>VWbVM7JEQEl2CjN5dO7+{QmH09$S@w4e!6!o`R5HNV2Mxp z%wDMzc#tuljHR@vP~*wmOdcQ6dfLaJ@N=)GQBqTgG?8N%Pz89Jujjp>a_$U6Z}Ie8 z_aW}3lss`m&NufAjplKlC@c)g#nPL9%w8;&fdEm96g^SF*+xKp3hAqifqtrLmxx|l zJ!!`mKS{wq0fXBJlCmyavGu#FA(Rvo5Pjcffq%`^w8zi~V-od_k)eM5L0n!EbKz}& z^7Km}CQu*mwegq1pTm8rek~Ny)6ZBbsMSND+gg|pbe1kspgN`s`lr?*V|-NkV>I+T zQqzq3GrPS}>Haf|lOp_RbkT-2UtF660`insO*!Fn*BMptGSkN8`K zzbk7`g}w$R2N23XMg0&2#b=5|!73c@q}P!Dko$>h@c>e z8N!kwy|$jw&CLDpbFw0jGz2b@UsQ<$+EyW&!gGt}D#GUY(X=Y4{No;Dv}zcH-`;<|5)}t&j>*>s;-=g%3%GzN%@@C>h1y729>~P zJ#)S3LsY>MF;Bt(rm_s@=%z?b0tbvS7&iG2=tWJFZ3YwBQ!nLF(~>|ZrURF<5oWMY z4En1l;a4_qo=LiK8W2WM=A0x>bV!VF<%DjqXgBb2_Uq8!clO}NA+#Y-oCHDBhOn+6 zB4D!P;bkGN64cf+2wOSil_;0wAD|UKBfN1K<#m49blvo`MAMdo1_IQPy7z&bX>We~ zyq+rXW1A=Q1g>W35f}g59DDUTu&_A%WLoE6b#Ob)O?sB?2X(9E=6|f|HzxY%>c5~l zI@LO{`7NJ@H*sPT4$9tgz4^65H<>}$3u_$Yn4E1wDT42>u#MZ)p2ShM7>L)NEf(El zOS|p1xxX*nTPf%OQD&I4ucY0^3FD+S&8$HR)N#nicfvK@$lYD&d7SQ~$PY9nd5|GK zilyimVuD^?Cceehp**^mCqB>V)~@py*rba(aKBIXR0u|Oab?w>EBQG=}vj>;P&nhYPI$6-M2i5HT z;fwbXGOCNbc-}9?k_1)Te2OMU!2WuWB#6f+IM%zYq?fxY#o4_5wjNgw;Y=p1j7 z`0|qkVNZjfJt9Sw-PxSa#O!xcSqFHA)oD!0L_lfG#gSZ}>29`-#Iv#)`s#ZXeG$*2 z649jVp@Jc-2pcmKwIrbrgPJlt*?N0vAGNpXhb`FW%daGJo?su>RLKk^FC6nUm20P0 z6i7s6&YC4RD6-gy?#pvr`0}epbFhvGiWm1u#4EGEgbZxZQ?(@fzgY7w=pMRPl=COJlmbU<+>uMdF|tf9;GSV#vZ#IisTO7|)ul@~$~#qJh_L-?v3 z2O~|yZ5$Gc$uWvfdi{~s_fe~z4@c!mH-76CHhz|MI{wFh*t|mZJcrqe#a>F6ZG)`L zdGoiX?gOYtplIlhPVJeV3Fv_O)el^zU^4vOr8US(Fc2Tz^Ez%VTamt@ZOWj%F0Y+w z42a$s{cccMc=u$--8s$l;tc*)s(wQ9{;KN;)G`J5a^C9G_Pyz+a42`$p$E%Hj@kO- zUvVOP$4$o_-%Xn?gvC~o>K`TreL~+p@CUy@;Mkudt}9ScT>^~9I{XNdOwf}%5fJ*p z@jqMuS<^1rk~5?Ge>=M$5D%OuTjSB>zVjYqcX#@5t&#>Uz|Y7ex#n!KS8`JQ6+Ihg zJUkFr$|`S@9omqa{||hICH`UdeQ1MUIYUA8{wkR|ZEp!opk=)6-7kzId>Hor1b?uo zxNtcg;YptG&H|wk3nh0d6~IP%p22VZ$#!hSkRV_>{Tg;ZITWC92|Ob6Mt^|o;rBzD zZf^)2Z^xVhun3$tH@$e&;=cfPv#_7h=3u1l}T9XVimziSQf-D*jD$7+#9w#U$>Xyt)0m31wPOP+qDJD@~1hPqZr{%kR`#4MSRJ3IuMQvKw%JU-V+Z zq4H@V$IKhrXB`L-cU$){uTyut)aZ-jXq?jeyY&|$7+7PES3m#80ahrGZD9-IVH6fm z$KrqRF_)X`_oy19XmIBoUVp7q2^-6$Ye2}> zjj53OjoSh7knuvD`g|2D6yNmg*+3Z-H(=&Z?UI+oyieG=yu|VBaRCMVb<*t26l~Sh zdr4UF^Y?Hn1>c~AS?!ngX2aC(D%zAoK#|P~AEW&VMk5tRz!IWC#GcBXLJr$nqHDwpZ~Lf1n~J=-)E5PwiPA!U9|H)|>G3^-=TDs>c+viAKI#FItiWd)Ey+ z#?~p-br`0A#8{dm;;9GdZQY0T>U@q}W&YR=F$SqpM#DQls%rAT;by9ej zamZY2+5e7Y*1Lk8bAe`7ip=uwsVonYql*4-0xU0o^F7#^8YGUX4z?f>JF`P9&w*c+ zi?{p$0F~kBJS;HH&%>{4$gtCycz(#L3kaI}zm*BZ@AmUzAt^-sh~C83`1Y^!+3$}2 zL>^z{kzaTr=tKsqUrT9zwry^bVO;wFG1DjX6dIOVw%5MbgUK1ptAWt@TGub?F$jR` zD@skiVbt;bInU$E7{7*^4ZLdg#bF)j%-DIOV4PB)*{aX<9vE)aLu82`54MMr$d)|R zp#4#0_H=UDN;F#cjLEL62UF67Z^4b>JX-!5Feb*-z`eEOT-kC_58XN^nrBv$JAbzC z1!M+ww~5LY0FIeiSq_AomRaqSZ_I#-zspu(0H4bE5$}^%#5e{MdpvB>F}Pz=BEz@< zj+vkal+9mtHgf>9)GpW+YHH>f*$n?x@ED#LhB}g8$o%FwSHDqLr)@6xvyC;acv*X~ zvBY8qX_~{)IpFRjV`iHMcLEEQOlEx7Y8a-ydt5gEE0gXoA?2tw%10|8hAQmP&}S`d zhw7tn+F`zrz&+pntKFW}AjIT%sGawTsR8=$Z5Dn1R!khpz{QrYv~wh1XiW!rKw;&B zLlR{!gb|WgXrBLt>=CfZ+Fb*mPs$rir@y3Ja%l^3FnMCAHfrP)E||CkZrXbwbC8Cz zuz?`MMeR}ben<`ZX_t%OEpJjRxj>^5_&Q^A)kNZ#Np17$=a*Z>NF6r|90V|N*KO@; zVAMSG4}tCzQk)CyhYLsMqDF&=hQa-8FBW7u{Xp!g(Z79Uy;tFU!k2p`7qcxyMxj4EP@^d z(<-5olUQ>ipo0Nq=m*;Qg!;u3I{)QCg~-1lT>d^m%2Y@HY_tR#P(Zr9a@!+!`Kpxi z@$w&?gNicAUGxghA~GN6J{dbZl&yfiQ}qV4r6a_pW@t6tb@{>QLD5}Q0zgDZ_mIwR zETUv*ePKl6Wl-+Uc%S`N*i~aN!>`7rt=S-88G0L7i;b(RZ@7eS8GBIF%FS(_QI!kdMIt!m8^UVO5-Mg0Wc z#>WTo6izDU>3$h0vBt!Hc5jI2CIp1;rccHNZfDq5O;b(MiX8cW(4(&y{X(Qaj-Ws1 zmaNPY#kO=(`2C9K9ZLq1etU*p0Wb0pSA8>kI2kE|51-sqL_{3~fJJV9)0**Rw)QrGb^9BR$HSi1fBNLj9#ln#<@7ikluB6o0lrfubC2a)s{TgsG0m- zV@1nX4r;f8I4~^VMVTwHkS@0{-O#&&42lDg#G)ee1Ix)%RvI zV(Be-ib7$I>9W5z!t@)PyjObq`8umTRsk7WKdZxV1{mh8$gM)g$;-O!Lg|g+>@Vx%q=>=w zJ0{;q=lFpfC!IaBQteX|R@rm}6E<%b#X6xDKlo8Nl06$3aFy@6cw9XMn>QT2Orj$CLDVTIT7i zfV>)B>eH{8B{#V$n-}>)Hhq_#FQ|UKBP~~hgOHrSFQJDh4Q1jh-AN~IP`q(~t8Y6! z?s}NEXuu>>rR>XhVd?*FC8{EzCO?*E(JDXtaVLeV6M(>?UcSzcXk&w<_~1#Y!bC2s z(wpzL0zAZPsstIopEhk^i1-c?{fpD*0mVqAUIT_fOW=~KdaR=FfU?mSFJNO5upBQ9 zOW2A&{PbWAt5`0SOi`zWzEX^@SYsO^6Ru`4euB;Mt393X8SZ2HHpzOQ9|f^la~bp& zrI_ve>Ny>9SIu4G*j;W`KM=prA`)@jOMQ4O6vS1-FO0$J_w!sVrcZkrQD3!wsji8A z$T1tWGxa&P$~8NyIemsTN%$J~^!KhPkzSVl*b8&j8_kl8t%u?4bJDtGtKBuj-cgBT z%Ok0Hfoa_WBb_Mch{xTH=~zpm6+A3hvfnM;JNm)DN7Wh z&{eT*3rvRm{M58y`=vlHx`>Hr5t>2|1=VPwIk%!PH@1UgGDRpXfsX@1RFa!|++2kX z1DtAauWGMaj4=S!4{kIp<-TXtU!3ic$S4E|ZmOjkaADg`Y4}w%UVP`|A zL69D3iLVU&?b~w{+o>hz!{}o@*X%7NfbMU!-W*l)g;R%1Raka_wi_!|S8k zZNRvM-ybtA6!+Gz0Unt}_05&~v=#0q@z~}-+q_mr`TZyPByz#a#gP`>pB8r73|r^9 zAfKt1k|N-nji?qh@?MD%aj$8&ax%X7Mh#yXj#>mC)M@W~LAx@J>rX+zP1A`v6%j6& z8nBDbQuw?Hu<;J$`pMFQr^o*}BH`NlIdvyLK;jO_oc`7iJKH61=xx1b z=pP(px%WX%&S^S&;z)1%s_3yX#BR=gZCC;)w97gLbLM1C`?0|oF==bYYBj}l>6ny_o#Y_rX#NRy!oZflH@ZVXr5U$gl6-vsB|z+RNRmD`$bMxFAAD0h@1T+iE4LXc?}# z#1}*W-8A_&!W0&nm~`>E93aXFxu#L3jgGH-S030>#as>6JvgY)IYl5}>RT8VNRtgg z^PWp>PlUVeQx^9|H3tmiwQL?K7bngJ@}g7=aCWXJAx+v0{J_EV`!V4#;q4ZJ{G)&+ zJ41A@wTW>G+vf~3!*`=`-!?~U=ck*}%8$XoTq=}6_0@;I-(>R*~91E>1NZ-1utf@X3X znsY4;OWeWhwNaqU+lDrY&bU%be7BHz@okG;902Mw%J&!#%4P~XRm6UiY|bEl z#(gB6m;J1bQAyHji?wAZmuE=^3YOHNDKNhnsqWAm7={RwU)#oQ=3 z>RE-h2FMFg#JCc-(4Z<6{s#dp*U^J<3b48Z7i#Yg;tHie-uFBXrM*PJle_Jd{hxy_ zW-E`hu1bz4&3+~P__}hgWd7g}YL?t#FGx-D(9a}9?tQf%!Kgs?bkeIf^; z(F$f3XsWnxV%;!99&I46$m~;ZD3hcZ#~Ynl`j`b`L!dA|t2sS&E{9XZ-Y?*GU7_-M zHqg){Z;{feCMx!PADQ48>mpajRvIBiEDD7@+({7T{dtc=5UU?S&gpiGmL0yjx`Sv< z*G@}8gMlB5$xp?`sevtxVE;&)$^~=S~vXMfS!8P?PAw0w5|VZ zT0Z}pwGD455Tuq1sU$mi9#Hx3)L#t zl`R@~eB%Q{p=)8xbvs4JiEX@%FSdyhXayH5VRE}q=V~gZ+ni{FZ<*03&yxr28jidQ zi&FJ`lv1qsmSob>vEddf?WMXdwnKb`W6_g=%^%Ff)A`(>0ad6At2&?h2ZNevr}DxN zW(j?tIcfh%Roir3d5)Rxw86B=X^E7KPUyT)t_%)36{EaS_&ulta!|a4&L0y5`mTZS z&UGG6sTF63omQOGr~xi`oqS6n*=ao28iZ+{9gY8p4&e`dkyF$Bgx<4H7LPXvCL)^o zw~Di;Nn@ILBuEoqQ8A2}ps1LS{$;Hh%8`oas#8A-_PJW?G-W;+MxICFUn}UpyF6Y4 z)2s~1wBCK8hg_b~wATf99;)=0Xb6k)@J?*D4)vmo%Vz2BUp{eS2gf`cg?Mw)qo(8y zQ+UR|01TPt@gwR$+Z_tWr90wx3||ZtqD%wxwi& zDX$vYpRYX@Wc*i4;=cbX;pN^Sb=6id^S=cosJ_TznC$&zp4W9roETZb6<$28%xH{9 zjQPRuH3sW#Ec&ZAKMQ>pFLl4sIkFquov2(;?CXEidVT)@5&p&H86trz7AGWMNl-J>-5E6gWKk7-eddC2 zN5Up}^5H&~mSV59xnqZmX0pjQq9H7*4F60PhG5x{%3{xslXI2Y>iv=`V1ffDkPyF9 zkl+!-vbViQJG>)NqFYzA7sp2JO1fXx4U3if6Y46ml=E^o^6Du8i^(DVre^xO*ayUu z){aWfQRz3o9Y_cQAB1QnZ7AZfw!f4&GnwZ(gekGU+ZlazL9aCtCLe7bBmbJnHSW=^ zNos$Ss$ZSt<{L2jxjNQB&5(jS*>tpnWo&`f&B$%tEZF+j8&Xj`WH{YtZeXP;jF)U>(g)P8Wa;^T5>z{{ z;HAyaya=h{E~B*`Fp`{{*8ptAFmDv-&&NG26S)(tkjCix($|80Fj+=?_1-ww`EhHfS$zKNjY#o`(AI5d;;w z?kaNJZz3Y#`)y%Lu06?4NQ)PyaAmUV{3oGe7zkYoDxwRXus=ytt$LI6+A&DGHd$An z1{#BVq9kXD`jt2|%_pqE{>+hfyZP-SoksyT74BBZe*146gV2++H$x*%V4+2my5`M4 zH}T<;Tk3nW%;sA)f18`0RL}GH{Um09W|Nk>zqsLkQPNyR|IejHI)o-;YHM(Quh__!;y$`GU zbp2lZ`OOIXw5@8z^A)wRM1c@5{j96)0kN`?6ooR+pZiwPTy<4TCZJ$u^5t<0DEPjX zi1_oh&zUoh`DBDv47z0^UA)hiCd$k|r=_Rk{x|=(w`f^Fb*t|A#KqJgLD#oW2#pS3 z;MED-g~)EZ{WzSvEs~wgcv7bCs?N+$Iy$teZh2p_QFpqYqNUaQV3{|!BU|IsnI&k@ zX6e+6NRnee?astWL~#2^YGs2%738xXsvNEw>0=Y;)7hbGr~-sCkX1{IAoM1xlT$hb zJD*i#JZH!jxG(fqF)GI+QSPjSH`oprZo=$qU}iso)iC-?dl@#W>} zJ^DSK?~IXcRiTKHvcR`3+PwmleTHCm2`XNeg9F;L*EF7PI4HZ_S8jzHgF!*&KFdZi ztT+@#QjkJrA~ZJXGom=|3c(X(e$fQ(t5uAr_%3tkJ|XI4d&G#$QYeH3X#7tJB7?n2 zX{wt@> z!jRDuI%HisAEO5@(z_gUQHZaGyinRBK$k_X=p0g|ZTzQ-_;8Z{`zuxxX}9M5D_l$c z&iI`ae@`63uIa*t0vj6v1^T8zujLaKy?aPBniazR_!ZqoFut%t=x#?CIzmfb0Eh`9 zFi1O-^8MfCI1fsf)caVvVt7PvL_tvTZ&KtE(Wl*%Hna)-2+TsZgF3#!zLI)}d@Bz*9>Dt5ANmu^+Vfl~$iPc3q zFNPPmXoJIJpMR{r!!u}rhKjnY5`u%x&p@C}767=FCuUd8n6|S>&InY8&UDD*5uVEq zkY*bn0vT7-_58CA%E6t@{FJtnP<({_J2JY zhicb1VSops*_)rE|IcazwA z&0pQtViXWFxn-n#8 zh4x(&#oY=@xrWL-KS_UrY;&A60U8|v;??R23z6aLryL+xB%l6+&q+7BOqwDzJZ*Qg z(gBuZ^S_>&hn@k)blXJJ`SWk^h)j&)laiH-Y#jOSRH8?y)8e zdo*+cUL2LVG;^g7320PataMDWy(pcCkd6u*g4s|6{7}o7ATuRYAB!LaeDB)5veg{L z|DR7W8OUx3@OGH*FIfGV_oVth{K|%sZkgZ#WeG=0c}qy7DEd(*FGhzAcUqNg8|2i5Nd(eoAb=oF(LS z&NjMS+Nf>t_QhoY8PT6w=hNy7I>l_4>Th3nXOC;%zv1WI;lGbXo4KJfn{j!us}SJ9 zuZK<>>v1#`KoX4cln#k7QWk}Q;9ozA@*@f!2*JE7vwiE0&qdr^D&lGHm)~mU?gfsH zT{u>G5$;K%am+)eNQ1|Q=ug-~@of(%k6TIUb7fCD3^s;EmHh|Q@ow9`-rpMambc0O z{D1#IWcwOMUa-qVTW&?$#LoWZ*S14u$Gpc7y?VL!&%cxs9F*35a}9J-T8tup zk@AyD3u7WUa&HVb#nqX7C$Fr~j^q5l-t)uTcV$9~A8(^AZkvFloxwAXTi0ZXEWOlR zv*dGy(pbOpbmYkR{`lHXBfTw9I)e1~(GGLeMBp}kFM6)SU!r8i|N9_-Ul^x^fP4G- z{sj-MYGB!bWzlbz!7h_+c54o2$I83MbIJ`x@UfWE!b}sLj#igPt6ela z75MMv8E7G7JH1l`tHbDl+gX%Dk(kM1G865?1Gjqq7bCwew>YE!&uRPj8KjDBZvnq_ z-$m%w3|_W37UPy)VwW2%h>c@y9s9-h=$`l*=5aCoj`uD|%>Qc_d7;N=@?m?<2A7yt zm#=M|W9J#R|Mg<{*KcJcY)HFrNxRoS?uyL%o^fgsH(YwZW12($A%Mc6TN0l-iT&1A zZ%nmcTJfYbf3=>%FcX{h?m`H%HvF!4b;sXtptkn<@bAW@p8u=r|9&}4U@RKLzg6<= zxd_1=V3bla=OWE!lPd?u=Q6*>yi{*F&fI=y=$dm!C8i^#GvZ=UqW6yaWuC%LVrqRy zMDEV5HjAs&&(E8l!-=PD|MNQj_W^mjYKU@F5ojyD^1Nl(2mh|(o^=)+nc4bzl2e8o ztzJ(ScG4ogpda;!tHn|x=nm_?=}o=4=%*h_r`cIolEz>A?=S1En8f~%N2UJ$j`J>7 z-vFL_Jt>+V{-w_mgLhQ+Gv<*d?JM5TSn07y(23_isw{pY(GGiTFNO(K3DkSI>*P*I zXspz>OtYc~Wbuvt#~1HE-Y^}KGE-j3iw&*2p$zy|%S~J}wrx4&8N6Caps>>ZHSa9G z|F9VT=ZsUMK(2ZLrS;dGd}NG#WT#)(vRxwl5-eQMjAdz)Mxq}@RHj*ss{1C+4bvSZ z{c^$cE4TIN5CTmqR_XuY!bt^+)fU*Lz?sPZ$KIPpHF>UW!y*b!5EYe42^NJ~m4Hlw zkbt(LMTo6ckSR*bV=Wv|Id0YpPWJNd@W38=1^tA#K?pcs?3?!NSsII+G z-Ax^xWd+n4!-X`?r$RIS9A`LeKMWS`EdN6MX9lS|*&prP$jHa{)2AG)iWL58?|@2m zA+_a`bNQp#@=oO}C5?NGz2iAf{+1guY6JJ8X%k5avK)=poE(XACYE20IAK)PhdT6? zn0c|u_9Uox8jnsroAY3uH0d$A>&8wK);51gxr0wyY`ETlxtfWt>U2NVR&+i)yi4HG zo6XV4{^yrWIXylm(cC8wlB=D12eW*p`e|j@SzAchG;{sJ zWIT`ZOB^J+xRfz8bUO@i$I2ARiZsHW{%6JgVVxSf1Q4njvl)bLMmb#gKEVdRtV*=l zy6NNQz?@A@nCKG+aYep{9}^PnZktrSXqbUln_{);z3Fc9lF%bkk+%o_e!y3cQ3Y6# zhKQQDcR;}dcSdS`sm0#yE4G$O?+*~J#y4PVjYQ?GpC;nzCbt3@X0lA`NN{z33&|FP z{%8|iqisrK&K(sZQ@{GH6#=wjAgrp(N~xMfG2y51 zT&ap~VD^S#Cflz|{NNs|?l|OZ$hVtfu^knoyJ+hT(!+0esVL(t^+n20hsb0`#`{XL zSjXT$_~5_G)Ge4iaSS*kzj0JR&H_6I9^BIfe*e&N&v`=^-M?hfh!!r_rn5Y@c9>#h zca=5uaV;fZ?sf%De!z&XPrre~%?PGaNsHB2LgBRiQE3(+50T4%#xA5>8t9uD&dR%1 zd~M72skR)fMyHmXmkV?V&C*$O{9A{X-*1N}EojF>!yUUa{TI?26YTVEv#Scel03II z^tC6=dbhs9S09&NM@=qE(ktK{k0yQ!X8c8QeXPkrM`Herw%mf`-=1TidBgb8CxssE zJH^zhes7B=k7wOHZHznZ>q0*3>%zROfiIOD?!T+r@)bDp{rc-66-!4Y*(=8&d_o^X zux?pGIuTMCdVD;IF7$DE1EXe{oj96gt{!4p%&u0N*y@o}a4)?~Y;VgNYm|uGduuZY z7J#jyEsuPq0Pf#Bash;V2u{-xPFt!nQQer9zCc=uVTty_NRAmGpVUs%UrTk%f> zHnoPHEM}KKP_~xkKb;D6*+vj5FcjtFS3cEulkJb-*abY}C1};)eR9ZhYsi6H3rOf- zas+p7t}wlb)0+;K$5=)>d`L)zom8Coe2lUjnJH z9U{Fxgzn}-`tdw{HKlul{x|YaD?Si|1OgcdxVQC<{r@ut+x|=aZ~w;&V2#aQW3$)T>@~T_nq1_6_PJ|vku|x@_|1njU*ikG-bHUejZ*>9N=J z*lT+1wF{4H7arFxJg!}MT)Xi2ziyh1uU&XtyYRSn;c@N45Iy4K1e*jjdy4H(EwZ^p#q+ zYIkH|e??6H5Ty-m1{FRz51V(%CAoKz=>c6=WV&j_B~*;}^K?mpL=@D-uZMaa8y1Itf6{FRrH8Y_vM_5A_9as}m81Y8N7Wv~;teT<+l z*ND`ZxmT2)p>lXx&SWT7XeGT+n2o+cn)^9yYxG&$Opeq3q^1K+dvDl|J8iS%=ibGk zE%{lYlI-}@k--Wf@~5wgFnvn#J>&~UsLzzy0G)L?j0<=VP~p{7BbAIr?vqzOdUKT4 zN=`G)oxjwrHQrnoYhwE!Z@}e^G@8m)n*Q_9LqR#4(`q`btH{++J?XQh`Cm!-dSdM(UbR0yD)CjlZICyC`{B()8@O6G7*fF@E3Tibn;|3Kz)a$o;6V zxYl zX$_wiiPndcE31w#V@9M)>=4LyL=t%UT=3Gdu0X8J&7T%&JHM>`PtQC(r3!PM!B9?q zU=Ky%XNWJn$~u=^^1x6fJFxiC1$@zD=Y3dZDgG>8_mtgaUE}ir+_!zB{_|C;+pZqn zAe&CSu&r?byK*(>&hg2KldCp@ZnT`903T92&>63TPXytbcrn_XcT^y`)t-TaF&}s>yiGxlU!MLxB7rIiQJS$%-+y}J>;#D zp9cwqdp~xXbb_~9*hw=J?5?9r(`P3Wv;%LP?vPf)zj|JEB_T}b(!P%Q?wXmPBr}@A zVsA*3@``eq@;jx_rN9t36@3=((J1+Jr>Hs049W>7(=q!kL$>9(;H4)I?EDq}n$`So zV?x%(_rGfTHl-&pFoRx~dpm9Y52%WHzBi68Z@iWpO-}i`3F{*ZAq+h`?<7e#JLhaG z70+Z_iw=#x3$ix!u>uR2#@Wjx=hZP1Bg zw)P%ghFA-W+22({r3VYG^VNZrv9KFg{!4NEpMN&)u&;XQYbq#r_r!K&4k_%tAZiaB z*`Z7PTM16Wf#u7QldX1j2Bv|NK3SSq3HC{`nLfjqp_PLyz3uOROaJ{%^@cWgDDbl( zyzdP#MhMqSj#_AjW*krnCrCyTEs$SHn{SjNj!Il#q__|z9?qZOHkv5CB&~?ILpms0 zOGdf{d@xv&9WLu*(0obm=>h~f=cjl_EV-bz*?ls$9QM9|WxVyib$QV-4e{r>)c6Wm zQ(G2xMG$zPP%7z=OgE{bly)XwrslkkOZ`ew_|d*TsjIoWV%D##n{hp2*|eh6J+mS! zs|U+8Uluebc+w_1BntO3nTG(pJ&a+s$)Z;#t~o31q&gLUx$61E3|1(LBxqqwsCCDs z#0jdWPjF2qJs;k=o?P#fYhg$7NHh!H6u*N%_iongpFX^{>x0}Qjz}V}dzl}VL@hYW zKUGVztCQC|`bEESl4N5s(QWZ6Mi9dC&sc7Nx2!$h8iy^ryK9br`}o_+F#Dg=+#Xh# zdOoR!Q3_a}%!~*;1#nAZ{5b9jz3x*E7n}N*Q1zC`UM zvE$?iAMUp+ZK|=-Dw~s9s4bgJ!s>HiFCYMr*swP{3?*COb{!_|l_dSceix$>mwWnR zBywM3%SHY%zv~n1Rx+-`I$M%mA;}gE2&$sB9yJzZT*oe6l&y6EHZ%M(4gb8$@fX_t`l$}%-vO9L^IqquW0PpM8FVA5Vq$( z2&D2~O^*8iLe(gQ@B=l&J3sAESUYN(q{0j~oQ7hO`l%J-tH#ES@etU@%wT@FrN5hh zk}d6OUx&~N6jA^gD4i`T9SQx)OZZO7e3pt^Hp^1Om+M88*kqu+K2sES!rZ>5ravFG zH;;sO*;c6=B(Ilim2lds!-8XolfgNWA0hr&ORLfD%!-r)CmYb$x)i_gu36iC2-@4n z*~&l@H$9cT%a(kA5u%SnF>I&Ixmh>M#uATDH!&xx%-iHpZ~}^3D(B)>YJ;=>nU`=< zLnK9-3j-Foz;YnR5cIX1y?i?2NYN(<;b{X!4R)@(^XCm0 zKhu_aU;xSJ?130tcWU~*v$oF*$5`iYDoffWyfP4xZRY+YRdu`f1ImQ{hUGfd@NKBh zbTb0%>^2B_>e%xW6Q3juISZ5xFr>f=1#C`7bs->SE?>c`sv4r@DvG?B3o3q4#GwF( zfovYWiYPAJ*j+a35m6F@o<&X1g}(-_q?j({r3o`7*=9^ri+5RBW*oCtH&hW=We-0x zrE=D}1@k)k8m;O4Z;nvd4|9S>O=yR;0i0YHm^3xv8B`qSpCo)_L9~;Z$V|9=7DC*P z#L4?z89*XJ6puKbEBGfk5FJ>LkWD}_y!lW(2iE-;XA8bePXN<-?&zFcc@cbz5W>RE zEy!B9;%M`VGWVDkbc>#~40=bOplINv6Suk=ME~GYAk7QTHy)JLc}y5s!sH16Ye)=P7j>!kbDFmm{PI94{V<0k@Lb zPrmYk;~NwYSn8Axj3*+*X<#ntf-GAoD-`CT`Wt6acX$UoQ&cX23&Itfm~z0dig$hf z6-d8Ol@@s&ZD9q^Sh+|F%{Ge~c7v+inszF8)TfS2-h-R*=APWT8MfT%fLj`J@MUPW zkQnwRF^-(=jeGs4M$B$6cGw`~Vk#bxGNfZ%`E~4#FOHnKM3juFxlJ*-EpFZbG*d+B zDA_zimX7?>v;DsvxG{Q4S!xK;2Z(4`4ZqC71OB)ld6Z%nmv$;m$(e&Zyj?!yEiehx zLhA%-84z@Iq0^Q}`##U5ky7n2Ca0I9y1U03bAmaUKr6{M$#$&K=Jh~NyX~#cGy2k9 z7CX*HU#4|f>|`cHx9AaqC*AyWc*nHR@)-|?gr-T6eSwihouTjD71a)NAWlaedbZa6zq&lhIhI0Sbi@l1P>KKn zdNdW`!~ob7a0O)HJtHMmoEPjo@5-`R?-oz9)&$l0kP9af%f}o01`7K_C%e0QN!Aca zW-9;HCwgQFb;yPhZ(R}9(TtJ2{u0_Ot&Ea1>@dstX;WAA_Vy^jFe+-_jR&Ow(!&D| zI~D$+`rPg2#dYi8f1mx{;TP++Yu6~V!edeY@Ubuig)pb^ouUt;33IDbf?eV2~2%v`cpM z15`fah{ABu4EZO;hvKCuoNN*bCfsR7T&9ARdf(UI%k42#cHSsBVv}K4Q9_;@-tLnU zT~NxK<9wRIi>c(Y;F|vNy>TJV%RGk*MYZiGf=$7aKfM#L9p|bAK0UTGNs+jG<`$_F z-22y_ttK_gIeTrjSRJpB7aUN_8zvvZ!u7&U6!Ue?Bv?x0mqz16jZk2LfD^#Luw;g` zE0WBQ55;nZ8JEFnQT?6giadcFSVYJOV6P^~1^IZyo0G5p+e~;ddIL_*L4kq+3eYdP z0#ZR=dHNUvETTw0F{Dxoh&EpUYxH!~WG~KSZP|obmUkjflI$X^q#~2I)cCUL9s-uu zltC2_*;uI#Hfkn!_{*|xN`r#j1jFPI)e|4IL;_cq629yts`UNrxf@BeQy+vX7pOZ<1+Aa1_~7S z;(=ulF99iXz9T5R!zLL?SxBH=EC3B;fx%#=WG4*!kWHfWLIL!BJR<0Nbvhu<#xs0m z4rj79;>(F3hb=}1%RUM9C4uE}IdfuacyY8_IoA^b)EB{`-y0dAbHukCr>7RlNzu_B);!hdj!5M^}_ZFjF zk3Ps=ZLm(RFosVBIP=$q6ReX}LWp55ddv)kpFb-nyH%n{bOc#)%ZjN@Z_Lo(=q3Hh z#+EdT1->cmjj48if#{x?MZUiPnEM##nA!>>->8doc0I6Vz_ZteCFSA8AZKQAPxbd^_tv zs6W9GaT1nd6bb z^>>CFwY4R4_?z zoN!MH;Yq5W9m*^6>?hyHR6((ovg-mJ56eAJN^hji#3_s<(}r2~hRs%$Bu7t%0wt<9 z1{ezn&bknJ_4NfB!w$bJ8f6S1!MD^su;}=PQ@!`I-Qo=f zMAT`0#NuMM+gm^Jz=ohFNP(HD7>46t`D7do-AXadPh`l{X~6(}TnTH5QNbu-maJW| zfKvV3&?eE*khjcN(sU> zc~FA>@*@0qvp_1z4)38j{@`j5tN?c0CoXF8u4KxReE%g5HJAK;V$9jZpS3n)d+h@{XTZEh<+|7H=~)FfCI$qLUiMSTB#~o)J!A6vpwAQ*3{pZ?&*9H_95A7 z*hE{}68!|a-w0X_MMs#s7~asAY&_9swqrdrA^UsVn1H-r8|2sTAz$xSKB}IJYg+U_ zX(@OYv1An;A>?}`#oqzAunxS1sW1sG$@G6jf^AhQt^U|aN`Y_6Z%+)AnNfS;h$u64 zTr5MRGu%tk(pY-t1vF*y`1L$1uq2b_$a!Vqmpm`2o$Xnm6s7!vtOVY97>{@lNzh!) z%xm?7h}#!#(D{mIvD`sK3}Ab3O7p39_!WRxJ4z`Qvm}vsJEZA$pXJJz5HW79PNIwk zJ;9%j==YJ{y@ib^@SFX*?;UYAvhMq7(5XaC(z#D~+++W`8BwpM&H=)M_Ql>E1Q`DV zx{GAoP^jEZ)AVW7o8r&bi?f-!J&DiMk3=OPHVUSYHlu=V1rDFHOCB{uyc6)4<-A5E zZEwUk&DpVr9y;MbrN%lA!P zSEG-Ms3}|&<)gZ0$nheoh&`Q#nM7Tt%TUpS!j&*srR($%2)fnSl)`71-a(SUdKAU~4IsMfSOJWc1-jvzY9#R*@0mxQ&UzqYgBf3-0G=*^r< zB_Z6DP4-EU9V+9&?9+(}uC@s8DOH3#UWNxK2+2%4;c5cWZSQurETKt3n6dw zkuImJqUe1I^zlSxRzk^r_+(-2YDoOyg>N;q&cpWNQc0tD4}2xCAnuL!=q#qNxMPT# z3-j_|E=f@mxIa;ta$n>f;N`r9g)4_>xq^y3o`E6br>io#(3m{;6hoHsE@*OLX_-@t z)eI(s#}gaFJB(%Z(yFRp)0r>vh+|=k_BcVs7=>yd*^3wcIQ3*~ z@0s|Uc^iK}1%=WKOIu~X?fZ^(V2O=7-0`SvAS(#?C~&a3m7JWxo;5OifLZFv$WJRn zuIMABCb}`nc3yJ72c6ftC{e1?sj4>7sq$=_a#S7WW3z4BK-Ax6dbyYxmagS%reonJ z)jtViqPMrc{6e+_SIeQ7#Ygg=a79PxQn#i{r1RQ-*g0`ID>O2tdQVO0ocxqfbeB8+ z>d?Om12+o%yvhU6H67Xjc;-d5NO&gTVOjs)32B-Mx z-h^8+m1kogY=qe}?@pEPa6J`&E!)dROoe1an_1zK3FTd8Nsgzj_Cb}+Esr%AYTIN_ zY4ES4uEPx;Y~A|U8O}aLGVKjM67rMbfGtyj$U@}Eqb#vNg zNLVE;mH6D)(7%^s5c68MZuV2L_3HnZAUyEJ+0`kPK#2Sm+)dZPB9KF+F=!u)b)#}& z{Ep0sCE5THL7A4NO1WPcky%UPeVBOhzIF*bWPL=tczbBpvp5>PklRxtS=?!lRn}r;)~~Lh>uPF61FsRYht!8X~1KCd++2a&Q^?tt`ls$qyp&S@pEt zkwBncl!HvROVzSPwi|f_vlN>|#JuQC(~IS$u}CQoAj*|UMxhUIC@gIyW%}LObJlv9 zKK!`coAK9gh(F0oOgxu z_cgbh|H%z$9#+ZKYRJF?|2pSS%`Yd3m4G*kK}4W|1^KBT-e3Rt!|0s|F3Rbl?QKgJnQVz(s}_tY4|BpZUPk) za)XY=2(k9vw(Vo>>;SVnJo6_x^<_P$8k?=#EnC>i3 z#^dv=i9~T`w2@O47lmhH8hWxpFOYVQdC<*Me~!>y+Zpo+ z+nUk(@_yC1cHZzOaS>AGF(}A{cc|0FHM}eIiPlh8cLe-%M-e7Cbj_ed_CeO%$|s!}e{x$t(iJ%Y@?$`((x)0@gTI#f<#c2(<@5GPok`!mv(}$DWmvEHM*o+7`cirYXj%x z{#+Cq9FeY+V(ymRB3z5Dm6`I$74Fg6W2OhkBf5VXULMEvYB#a`V7CbQY(PHZwh@zTrXhW zK49|gCcSUZ;AW+N^0ERXD%W``L76&g8D(s z2lhv*NR=ZvYdOyoT9MCrz7aDNt%d79I`;3Qb1)e!7LoEl*jgUUfY$Hpcdxz!+Q-k9 z^8b_!OOMXom`3SDTtz1&};=+74lBsy?(H!4tdC!3r$0r6|vA8%jOK5n*!Ht9*Jze

uysmcH!{L#4=c%LSmV`x^uWnflV)7PvCdea_b!1*(#_Q*DxQH#1_7dQUOi%+flt1 zqoQ8Fn}0J&?=nYV7kAvoM~>E8bhofWK>1JGh736z8;QY*a68aqb%ps&BO1!?xZRBT zS&L7F>FNiRl+|XXygWZS3Sb>~eUY}{RcJPDFei@ueQWUEX(c4uVzY0j1P_V5n6)G| zYl~fXU?Mpch9dP^YbG1o+`wg_-Z9Xp8z4r$retQ}OvuBfiK(_3&o^3C&@Sy6vl(+b(h530JqfvGT(f@6 zXKza1#&mlQUig4uvsh1`JN$inqgGF$e891^G6Uu?@EIBAx8m!iZ%N>D{bLD%5rDa2 zxSU+njg&Swu4)^4DUC0ex*Pf$B7fg)&+Cv)o%Xt1?f7o_di%GQ#c)uxHNzjh381u~ zK{ghM_2yPT>u}Ip*8_RY9I9a1DVrkf3XDVCwOz>e$TnvjF z`8o3{V@t-Bm)5%i{HAuHa=)t`*lIW|n}cHiJV7m!tRH0I+Cerdgnv3J%gAOQU zGB#goK#ABtCvQwk)XR@q6Wr9WKE&eItfk*SEX*!s@m4vOBT2ars$ntV|u`5zFwa5M0y>!7sAbjj1f1FEF1w!@`VHzB`?%F`Oj^FzW8st4jVqZTx(6lwn3_o$)K!6Oofyo)UVfgXG~)|UQyarg;0<`7r_mDl9`Z2Uz*V+wK@aR*pN5WKV%uR11KFzXqU#N~ zq7m)e4vIMygE*7vfBZUZ7YETQ2M;^!&Kn5j5J|YoE+DV$$C^2v2bpbRg2|Xs&e&ha zT_2Ts%N$~1AtMD&fdlFW+~9vcAv?=G;06=LDlB_Ju9a?vTd&Mm+5*E~xZn{W)H|~n z4zqB%pvre`$jPe`;xmK8_gzjvBvdpw_Mlci(>lPJL;S8}w4AY>7|9#U|0CpJGp@K) zgtr3FSvuge9OGmr4H-&fCE|=J{(Kpx9jyrYB^V~KA4NGafIHyQX zUVQ~TAzM|WR~!U+`~5;hiWJlD_?sX5Dslh*%~BvgXS;f&AjJ3kMwG( z5q#+R-v1-<*Ttsx%0i`b=f`Ni9>VKN9;}}!a7v|}!`l!F@^WL&O8-0@6Tohr1N=we zAmOomhzB4wa%c#68M3%lr}3=q&Joj?`k*fk%CQ4~z1V$C+OB_Hvi`8bb#w1b$7%#Is*HiN))XPKWh09KL6r zV6JfZow}qK@(5;L4}4{kDM-b}AU*Q6SYkM+iE_LQ5iN2QZ*E~BZ{*kN?XuPcYw=V{ z*4KM=V8u5IaLFkvwJq0;hu~6IPfx*I!pm{p-v!@0556-dJ&M`E`x~Mwg8`xk9!%%N z!^2~7HJEnz=&sYRbp<))`|*)Lpz%Y8v}RTg>Sk`yG$)(fhq^+4VxJ^>n-M!Yy3a5k zYNI?iLd&{`0`6JAUTzik{THuq35l3bvm7uAs2rjt`JU% z^o#Mr;r4{KP>JAjUT-$fRk@NsWhH%~;_KhTW4-@-`2GY$zfHz%eADR~CDu8<&bd@Z ziJkO9>>Nb!`uQc-OGz9kJi|LSXGQ3_=1_^CtoGi?Pm)rx z6jr*8a=0Ml)|~kKTQcPAk|>OkQHQcy3jx8x)H2=C#c%ipE}RQbvu9&8AQvCj$dC>3 zbhgm7T8HqFOIu8_Nw zaXDxq?t^@c(+HSnE@h^ohe5qs@5q=Haom+cQV}UTpoY^gxc$>7Kr8R9z_Z~I6-qnw z!_j)4{UG-@4pg+INCGsVBb0|gkm26Jp`poVipjK~YH zpf_#S2}t$AA;A3qG-r0Yt8}ptPf1O!43I2HmBP0Prd-y^0{V!FD;)Ohb#?0{G548o z&%2((I8uX(Lbd?tNY~lf=`8$^(zgbX2Z1B=yJ!jQZ$#rTY(~xRjDKK)EU!En6)`jw~VBHvA6EEhU|4Dd|-T$ZB8@Q|bG{ zk{ehZuh}-$z%ta`^iN3NYn|X@ag$ILxhWvk1NB)A-7x0rYo`y|Yv3Bay?o`UsMM>~ zm(M-GHg(iR9XL0KEUaer%kv9D2`JVpHps3}?#1XwwJ3Djk8H7!6F=xse`7H3jQawM z!3P*tmY#{3K_5>PZfpzoVTC5O zN@!+T*c5p9?PU7j>Tih-pohS8?`NqS-Fm-?-ewA)jf97t73lbr8x%Moif}kaW~Uk- z$ks|xAcuvE1KO9@U-1gdd2nOPegYdAFcXM(G2md-%W)Qn4z$JkoYPi4bqg6@em-mj z(No1WjTbJ)+6_^JIRzIg=YG|EhY1tfeNnUOp*Ds2LDQ$3n=EnJBn>m+D!vg8%VABp z-Xyx|Fbz+j8$pylh8@<i_?&uZ-Rr8r~dJR`@P|?Yh!nZ;P1rL|c(G z2!h#I>HBBR(BTB2$UHDF#_C!fEdqp~%0-3R1Qi+`ff0~J`M|^#cBme_L?bmY5!C_9 zDIu7n?-kDlrZz;ADXn8HwghB`aE3GsVjw?*QB9m3DWbRbHM^&~5{{OLu0gW(|k?<9zn z@d5opkJ`xuCbuS{lqeK#15oXSAbn`prL{K1y1lU?HMJO+h+uf*$k15s($>@W{yJ?s zM5F9U1vCwArHNoW8ykCop(_tl4VnlZ7wRrb`20xo%S)E<7FCR!SqF{R*Dt*r<-{5e zb^ytt9MwIoC{J~4#~2pzAm}rKsMBt;rTJd1NkmFq=oMnvB?kKJb-67-PDM3B86M_M zBaxZ{d?)R>|K{RB2lu2998Y@pRqOfIVZGmJx2|?CWM0XUn(* zun%eRFzq@#P|=sjx$$B>7B9EG_(KZ|z$@wJKrS0j zi%U6d$^3~}mEku*5te+=XP&Sr0A@sn?fg{1Et4`Pxw&y?3c-}$nRuEDC8=A`Lbl#@DyOe4je$HS-PH#gbOBc{?j7$6tzQt0BmmIb_DYLa(dRo&Xq6-YWkHc- z;)hv)k$G+ZIyMFUgIiXOmS#f$DyziHSzGuymI)tJm{H60Z)|;jA6tR$_#06_RUSa~ zNJOPz`>u+)S9)F4zl#~FNwiPRCHsYy1p}46Keb~FKSpn_Hh_LCwt73L1!P>m*Vhr8 zbA+*VVorO>JtQS4IpOMd1I<00lrMD3r3sLCf2h&M7;7a%9#`gKf!j*UJ((q_l@feq zLmB@$b#Cg#yLL)^y56E?&l6jGQMYs0Z2qY|@SkBt-X{*M$a-qO1NyeTRnt2CPlE1a zee$}xx^y9^sNBz=fp>P&MkYtuag>xAc6N63o0uAUUfE0AU!wxfNJGf>^sh4lW`9pe zr##PS%}<)lWWkI=nWRhGeE%Y`^Kp_oSBO*c8!voaK1PR4I6;X`+W!uwx?w z%%C|Jj6>yfN=h7?#?Il@89>kqc->|`d&UQ-sNSQ!SjJJh;!JDZG6&T5-*6;g$gzHD7yo7*oyZy~CA?pVro^jMTJBle7nzAuf(|A|%`MKwXTC%xF)Zeq=Jk37Ixg{jA#xl&D6&<4O@*~6^H*^<$ z!V%Kz{Ns^Q?vLVxO(0fjTvKxL=ch+sDE{C|X&|^%Y}um^Fd}0I*3?)>+QM32))o}7 zbcny^D6w|4U`$#)CC4GBpc0ZdEIR!rWs1x>Y-UI#97U{aUmiZ3M zMyNW@{)%EXf@Z39Z@$;zE(U8t+u%CgQQ(~C9SDnkr3Z|BGHM#+{nn#ig%ejP(%xLZ z4E!%6HSyWzn1qL7&LNMeNjJIDP*%CgP>?nW3( znPI^YND26*I$Ztt^8^>xdad{Xy&ONS-8|2gVt9pN^IG%iwk({FUiEEXxh9BGl_te? zXYyJ6Pe%C`xH;sryf0$RQ4DmhX^NQEt|R+ItD6@E{DJP3=L-8+nf%aZz8+?lqp=j&u8j(i?nSC$3-gTANe^F-^RhgkuZiHRpV=iu)nApoIL;d zglzh|{)yVJg}|)p>RPTALZu;c%2-N26OuS30x_?D!-CG{-?%3sHg;5LrPiR{t1Vj$ zw})(PvKhwa=p#Y&RR>q<$sZ7Jq0ULxn;ENy9h`5GDVWC&)0d?RDf*IE%Fn(?ys$^5 zn!@yqSFJT}Vnzz5t9RW+9s0pw0UfzK(ddF_tPbm}(P|yBclI~ed!CN1I7~hY+mSjt znz4%fSBnPpe9g7z@IES|jEtyh0s$a};a)%DJEx~x>4_j(S?~WnnU9Twv&;*g1NsJ} zz>kq5le6?Ly?BPL1M3l_cjZ7Yqc!x%EUt@o;@?k)9guYINcRA8;@5S2)5V2RTxt{Y ziG^TXIB$yG?;;m-bDc|1PcQ?I!r>S3D4%D3e9`=I<-09bYRMz9nKTD|GL6eJ47Dv= zNEy#uyV$)NWm;-~v|Jy2rT@9Ho~M$4Z6B9i%4mBn=vM@0nRcY4*WBnwSZhHfR13`6 zWxpy*TDGkI)I^t@?X*{l=h!+;$&ESi173sP0+e=0zNsGe7x>b@Ue0ziHn)t@## zRhtz6XL%$+JUr@spN2S$Y7`R?2>I7gjO)!cg_kdDN_&CgJSVGCp`p<$yQoqBX5;ka ztiP@8(=je~@VF|EppHK!N0m~?36~w*5Z2jk7Kd{F&@Ashr2x_L%fjD6;w*>MC`Q}p zE|`d`EM_1T29@ZVzmPLi;W<7#k*6hTz5(`l%S*O}RlwDHxi}MMn_$p)WfdWEc|}{R zF0Mf|)3KF3aC1~u=wZAEW8Oy@Z_Z0TW08JM-hQ0pYQ62v{LDzT$;~HGV92A*k1F;T zMG*U~J)2nBfd=a`YBz8AxU<2+d(9p6jB;bcVJCucb&V}81FA2z6Vbcaa$OCTOXlqV zVi4|oK?>yXtR-L|;!(RMg zDd!w51x>=@B0f2Jw~hIzehTWN%B1uS$tmzM;WD@IGiC!o{P+uN2Bl0c1|?-LyxYg|a@D?UOWbD_%q=0`N3hyM&jk^PHox7C&VoPUTy$fpD< znhNEe%EfWw>Yb%_V#SMFb=lTmrpIrxB}z8 zML}J9l6#YGoAXzJKz|y_&Lm{qjLCHS0cqxCBPgG}pvp6m=X{KD4Zj;jD2^3U(XdC} zEC;Uhqi1mr|9Fb;SR()!jyTzUP0;bmK|l)h6mKCp7iB4^5Ewa+=ERskK7Mu+rTpTP z4w?#OXTzuZ98Du7$L2QDIqrz)XtR`7%Y>7gk!M}c%R-W6vX#_YEF-7E1)y&R z$wDdFSxU8}+c3}MCkgIHU+}!;g zPwTrkGooR{#imsjJJGt2%ItV#fh`}h=XW*J@KLYTwMh7MRK3D;2hD-z=q$ErdXz;w zS*Y_7xP24yeKFFg2IhUVm5Nfdr>{l&n0a);fO*2$2xNNlDBQ(WU%Z0kVoG*AW7XC^ z4~&z6#rX`qlZ=G>_SKRCmoVqWRFc#04&DNL>rT^G=TVQ=o_mdJ_jOVh?M~XGcUdYZ zx6WF*FbzvV3_-HmQ_9O`;cQWV zygcjcxWceO-o)ePYc%smI3j*u5LK`_po0aGH$Z-@+8PVhFo0R^qz-a!7Y_(V`?#8= zC)fOzpoFlLY`UDr{z)VxrK(AKG%2f z=H8$5+UqGWyJGPGUJQ@Be=$5u7F_m8rR98;OTpe(B3`KiiLW;c%luv`;gQ2&OF$%B zTGF~d%b*5NVN0Y;ULAh>?6+rf!e&ap%;dB|?f-ppOyw&x1|-H@8|PV)8dvQ{Q)4i2>t z0q;|VRu0{L3BHUCm|a|IcR0ADkPSL&Dj|Y;h%lPZena{Q<7y$_A~&y(EqXiV!xHlA zja@`WdK0rx5QE^pwvpG13Z9><&6N`?@>1b0rk|bj;*q2*?`m9~j1|Vxl0!E0XGMK) za$$uw@LXW+hDWBwVFmYUt)V9sR;$KEZy1(Sd8zK4_4bSk#A~7qt!3-O+f2s-1 zdPyx)ebpB1!a0{_37VVTD;9|p3ABWJrPSKqERiD;rof8Rz6eOyF*-o!lzFOii?j++l^)cWRXM{eiH-(tkXfpdR)%S3Fj~ z#wlkdF#*ar%Q>KB?O+!xb~k;;A+k?-I;pPQANQk`S!Vy~S{?>7>D_6HAKk?Md-a$0 z>B3iiOy*~uxL0FG6(Sxxxv34lY~Jkhs?hi%T zmE0W2*4&LOf2oAa)|-od_b3Hf_g3FfAk3X87(b*y4)_D}+6>Hv79jx-5Y^aADS>(L z{63{_rJbcEdKOMrI05s6LO>oG`;JcU*+TlkdLs-@-O+Yz~4KtS<%}ZfoK> z*{~ur$oQbtw(!fL)WFoRD(EF$@cH29pCcGfhz-Lqn4WV8$XD!YRJ8Y9i4=W?G)D$n z)7Usi+KrTBKmvPz_|a29aSzSDk9sp+M z?O`xZ_6(0r43D)1!Z);7oT99C)5Apqr-(FzOVz@^Kck{Nq3&i!kNNBoQrtL=| z$yN5dUOqeyr=8&X(L* z!t`f}w34n~WzwAX`3uUW53G?ce{3~}Z+#HTXdDAVbeo#weQB2CawphN?PAPv`*vVHZ#5RJ#gL#sWYEj z`|en@PYju35KF6q4rYkvNDK`yhDkG~wx%d`kTFz2Q#ma?RbON)aX;hEJ>c(ew-r01u!xfPlMKiZF9$V!>`jg!DutG9YSCir@9CsFm<=b%6w-h4g7 z-q)XyS6i0YS3zH`;OWTuOU&&>ve4yhm+3q?A()nv;x}Kw`c6|($d4<-_pq;4z5Rn< z*SVoLcDQYZm4Egih|}Lw#pW-=W?h)Ac@BE`vwmA1grJ(*+ee8mA+QoLyhqs{isseH z9cw=L=>w8m%l%Bf=db=bC4h^NGk=%==ElfZYA{_8GWD^uozyV?Mp=xIfArlREj(IO znDK;{x1!iI;TmZAzIRrqLzu(J)sQL#HY zN*tWueH0y$syN{Oy0uLiQ&I$Ma+pR`Alnmjizh@GLq?|On7*ro4v`j@^)$OwLa9&I z7gXD4XBAPfstk zP+=I|pmyS0{^r0&)M%54*sNDsmF#;<7M{X5u%qEBk}1h^o~-{&6-gbQ!QnMt0(Y9# z1!Q=J(d}gP`16>F_<8oDHs*R!cVV(`V0e?RCDel!(hfQY70AJ-zOzZ|RGbS!;kaQ9UQo^4y8-Jx-V(vZ*kqA5}wrkDK@U>eZQCBW2*H-+!Ge z-Kl&onFfE0Dzu)kB>hW z`teJ?-sj`f958(|oF7crzFRpDH}yWZG-K6>N3Ucr?lJm#rUbvRFtJbYK3$NSv@H6# zx)+@u%UEZ&@Jq#({-D8EMqv9raYaj}VhF&WudI2kmo7T+$rw&Hqe?{o%*OeMjU8#S zWB6cLjzr3$(~UwKbq{fAS(j@jjE$c2WZtEeSdPXapB=GN#I{dl9vxF zq~UBUD1X`Igk#Y|@6W^o2>+he<}Q-v>}BWNA1@9RLOG9f!fJCdAQ~YhB@HE4QLqe{ zg#`2qlSY6~cN6OB6qSPD_?&o{ZSm#`>G^Nb3C5ZUuTBHO)RFdu&bxflERuGv5`ba z2ntfK&lIPS1y!a8;1|5!80zk2NGV)t8)t34D#n>aGvhNo*ay}_g}$HVWQWy}nRQKK zGh0D3me9E6rX>V0)J_DiMO_yjpEcvvZ%G8-Vxn(Tu^SGKzx@FF5xq?kQ`cC?YAztU zC!Eg8>!?V;@H%bF+fl`);B?xYHORxF*+ru6 z#*xKZ{X)LAk;$0|($?cjgm^oC^CUANtJbUVubIc4{ETW?S!T~3r)}~0dL|FrP^SQL)pt!o0 z)MMlu?y?UiRf`GGE!Dx62(I=P+aH&XehyUVI8`(|WWOWKchd+ayjco19IO4sop7z< zywA4$SSPrQ0!R4OYj&qO%5FDbEkE9|h2t8>!Jej|{0_DyK!>7zPHiSl$!=+uop04C zDV-+Ek0^)G1Uz5zwIjfU)YaUc3NfuHaBXYTyFcLP2z~Jl677cz(l)b$Z{}*DD}sDk zIO2bZoWxAGYb@XX+z6LX6_oXSL;H~U?e^Rkc^b*>@Tz!?;%_sn4)b^D%Jh~!q#0RW zq3j#0IE2N&!s$}`+UkcJmR_}Bm><&&e31ToIWCfDD1L2S zym~J(1nFJC#Yrfi$JRFSip2Y$;(xXxg|;F8!8clAvsBC$wGK1%(W2LQG~Zd%@g@4X zNIBnICQsHF>k7=WYsitbWRry?f`2w=-yQGWmOPtnVj)JHXL_CrOdb6;doy=|{<9bN zaes9|&FlM?~Ee44P|<(Z__`@~$kII9f@EenD6M zmgq}Pb;fhEKk3m-IUsXvJv-&|dQVIEFpk9zq`k41faL>^_HYb~3F_W{FiP%sdt_G8 z(1aKKx{1CPL*n`TEpu_tYSXnDuIXb~TOx@#M_}XV$~ntQUj&6F&fP|9(d~=NU@-9HE<*Hp%DH~wcH&2u|)kQm-*Y5T|u1a zw+H7kD1yI7P)Av-Zn_v|LZZ!GO-;IFh(s^df*bcQrpTYa_`un7d%6!SpKaDo*C04G zH6VOH``aHQMJu6S;i^WSLi7x*5&cqX<&eUzEA5I{*`oIoxJ#J^lF5%3? zhHF|IitHD>9i-lYZqA@SL|Y;$#$kqWxh~EfOk?Lh>IDFcK7p#}Nm*4|HQf^tWEfBE z(D%+rU(q+;@^+jgPPz98n>RqNW3CSo#X6Ph?FyP~wVRdNwdvuH^8J(KPT?y#PVOkI z2CmEEa1tBJLSh5Fg}2(cw(IEsUwdB}7UlOXDlpR0DV@?NDBV&cAT1qAiS*FjAYB3? z(%mQ$1JX4}gGdcMv~>4Q-hxeR!7$@pju8nLM_lHhS!k)IfDLTCw}{jQsA_M#DW% z|8TM89dQ3~c%bSlQi*1lNL&rBOjpUBg2$;OhPevZ5gGH(;QJ(2k-g7`T9Lc@6tik1;Ko`^Qsvq>ywKOX#y%pY?9Q#2IFwNP`k9Br&F)Ri~L1=v1D!X z(4WL!J&+|C5%4rOOC||6v6 zoMqui=OI0WVZEorl@Vcdj+S7uJhwQts{vO`=tSUHGr?mI-7) z>P{CAYyRkY@C9BL2UldQ$H0M$7_OM<*htmk{|Za)MWm!*g1(Z@%)a>7k@|8*4r zB@%(lR5I;G5^qPPAf4o<2B3UrvO@#4U3J}hlGm%#cjEF{1XuUY`^7)19A@3_+%d#FI??!8bXT|Mt7z=AQ`-?SGQ+R=;Ro#5=W^E^sS2IC^gvGa3IXQ1-4Ia=W{e6#Halrdk#3eU5BQp7NSzm z7@His^YDqJ7>H5O$cJ(AN$LU+-e})0PL-AEL%5t)+pnbc08+Y^GV{`Kav=s*knsm| zX`sofYNfA@R$IcsSeplxIzKU_ zzyMULJ(-*ei&9HZgxy>*SxNt@8~zAuj_ADse>E;~I4NQgb)39}8l^K+l`fq)+Nh2n_c>1MdpG1;;q>*|$)L?SwyAM~^N zJGDTav%OCo#j)OwPi`@KhqhL<4Wxo3f}#6_tu?hV5RmiI#gRh}kwh*KDv-jw7fv5d z3EGhc)b=Zc|x&doDAYD{DH0 z`r7aIN}3468M+~f_Gga4nY;9BG{^1d@-QW2LcWa>4 zG}wHnvVs3(jCILpxSh-tH@>&Nazel#g+T$ww)}k=^KbD_IZ_t*mv$jW?L{c9r@`(g z5yDw83$+a;K`O%MJC?=xbLGCbY2g!AyD^1sP~8RQR4H?$c-kGM?)k8BJMPKXiV*K) zsfvHY2=U;s4G|Ic$Q1*G4xn!Ks06(s+01f0^Zp<6ju=vFHX!-+bH3 zFmJ7_RGt5z%?>QduaCA*8o7P1+ZjfF!YSs@c&sVdQ;6r4=cY?x<{nYb7ymAyYMBQhMZ>2Y z@mf+t=4!##o4_nIo-kxZ2&t#{(QBV|W`<@6F!50FEt5XAv+6mi3iP?({oRJ#jLG!{ zJx;46HO}m*4XOd-+4$hsfIDP&AC(VFb`ab1e*>Rm`O)x@{L%UJzRj2jI!R^Suk4Vt zzbS?;+LELK5oOd_mos3OF(k51qK0ZPt$%oILHy2XpIxF5uzy0!{!$dngoXM-gR^9q zZ%-LA-arpl=!m76%CGp0YWaUoIcTF{rt=pub(Yj%DcS$@mEGT@-A< zuRIjGWeZ{T%jLvwbT^3oMv;GEBxa#VTK!LQ4H*&AogI||<7VSb8vB);n3f*St9RL# zaiOq&wQ!sARoKrkGBOfz5GT%yFG)!EdyyG6BSXSX_RFG@`%aI_;kL%}~8(*&u8X$%P z-YgLOj8^m)_Bg0t9)+#d68&13JfI*Ye+P8(;o9K!b7KCbz$2Oz%{|Z`raGIkFP&WB z=NM;LIbmv!tG_>?lZh>!Qsg%yqu@eW3gkVBS+F|Fe|p8fc6me_+jLYh(w zE4usf%?M<)C%|ca>#?+(4kwDw6S>{jY%y7(je;k55)eAA1f^D&-pb$Xfi#|8KXHab zvs5<#FL^}l>Z3qY2IQ`=y0SW>f`LE`v>OgPu}drmoTRS{vfyber*ZyAlaSLcCv7~3 zIjEBH4San?q)Y`uBGAWO1ICQq(+_Mhrc`*xDu8v>O$5~{N+Df?;ds{rr4*hj%VwO; zwX;HL5=1j9NUW3MVd9VKaX6Pcc4}{$gkE$_2=S(F=}Se1fi){Wdd`NMS~2UDP_#_{ zVemhh-(+J4b!1qidu|kW6#^`cSB*^^3~)O9xFc543?}>X(sh(rX`4i`Ewd9_U&KUM* zU93r3>^JY+2?9lgX!o|(=}js2EUVZCQ6_GN!DSw+jb?n6gUa=J;+SVQwjoZMeCU$MK#Hcc0@!_ zx(-$QXQ;IP2WD|wza84-;Bf?k<^*+ZJ7eE-4U3&7VXwPZOr>i09i2{3H#ya1clFqO zRSm1;=znZbkY)LL(ZLl-UKyxL+9nBHEOx{+5ka{z3r~04V|xjpPK(^fK7z=UD434NS( zX<#bWH|A=UVLYStMGOff^y3UX!2G#AfLYO7H7C3HT_h&=+JJXAyi@>3-7tJ{<1SG? z=0uW!*`|K^ThXsXpG}l@?=3HJvT(51q_3wFEtwh`g!b30vX39XQ;XK^u52|wZB)YN zg;uq^z=_*3a(qPwG7lfaXnUIC$NS^kSML2@Tp~rRg@F4>`dbBXGlD#Q7(O2qg}F%q z(ORE%K$bFyv04K^>$Fd8wX?x9%oQ;zhi7-I>0^#K*eaOXekV$;kNlx>>g%=wU=r#Y zdElcs?Cq0TQ!JkJh`nA`PXfj>%eyCMH}qW!uiTIDZ5Kh5db~*{xCp|+3bvltZEl3Fp|M^Q~QYNJaw z67BkFwL4mP3+>6pSaj18?)$dTnL=%g?@kwd*1MZYS-JRVKh;B(WIo3!iiao^C?fm< z%zQqIp0r+E`{j5+Q35H3q#q7uW4ffvnRHY!eV#d7_DKs^J~JkPE_j}WJnsyD8mE+d zk@LvLT$FJQ!f@IA@<~Cnw-oJsgM8*x3*NeQB;51jWqmM^4tO8NKz6YDy#z=RY{MmJ zAWl$94`Jr9OYAtrX4iisfg^y033c#&SxT6cN5HdS?0KYP0XRZ((k=!1VmNj3ADjTC zEHW8WZ7v1zcPAPsz6td4b?r}~jsZ8lSd+v4&gVaVxSVdD942@0t0VX}ocR1Q=bP0} z%MD7#@`(vzp3Y;0GBQ;MQ`kn^i%?`cP{L->3`(`M`%Qf+)cOynf`u^9qhhh$!ipZM zwy$=93}Gzoj|@nI>U@`%>}GaRd+v%0jdyUEATRwbQ-Ju+K)KlaBoG$JCLaTqiD7J; z5$i)1i^Nz{-J0b2i%nGo0wbIdOVFrpcbI|^lbH&?5P!Mg=@PtkqmDp3<=QQXn@f~hO){*GYQID-+D9ZZT@$3B1g*xDnuT|YawilOIv zN|I?>>kb@=7}>lJ=PmU!dhhh@c$t=~EvKU(F(vM`1;h7k0fNFfTm3vj%e&hR&rMK{ zl8m;IiYZFH>mYg@Gg>+K%U(KDUn8gJUZZ;JW?;dC;SXJ z0qlqxHGsX*r!ms9?5@Etwa#Lv^G#b%>hmg8>i*8KRQX&E3Z>^am1zptExZ-LQai&M z$8fE9S{wwFUFkY}2V?zlnGkOrmm zze)H?^61OWi|Zort^_Un(2NoML;=e79Hpmgw}U_`Bh>)TCM`wck=h)eABHA#f+)&i zeQlG@ys!@UT(!!WY4A@FT^nh&wIZ%Rrj8RXfp{d8DxqwDIvvn+TqyS;4$KMt4~h3Y zQY#Iybg)Si!$QYzgw-2viXcGIU%yj)e~gJ8(!POTiR*r=7J5zAQPFWRSZMgawM|k7 zb)SlR?X&BB^E%nV_>%V}gb5;f+d#6qzLoI~o$USBK*`c#Q>zm@AV9@6uA%%>Eos4H zEn5o2>i$MCLdI1^n+MJRK))!UtLUzX&eua4&88Wflw*!BKj=3LRBsN>eg{OMTx^x8 zmSLY74J+7Bil4()&;2h~@blf-<`sB&x4$nNUJMZ9zYR={2tYN0(3Va)>k5rl^pa=# z>ApI0-WOI$VK}eJ^`A9|pA@!o%JK#Oa@D#$4IUpu*9K1h+#?oUocMcfMt52k@Kzc={YYU064Joi6$nKSznWJm0Fvjpu_t+~T zGJ9P=s9)1|cEsiL2Uj?u=!74gGkh8fg{FPyl}WfVVgq*aq)=TrDN)Pv74&3v5{uKBbkjUM*o zA*JcX`O=vMg-v^^oiI_0$cl|)cFDDus5Q<4fF$`aP(A3&osU5w#eIoNQ@UBH?RnH1<>QHDP1 zTG`drVaKR1M`K9G3w1v{7U2CGa?l614m4_C93n||G#VxWBR*i!Ep@WxeWubxw=Du6 zi~Em@FS+9!u`mog!Cw%nRtja8>=}=p{8pzL$X(;G!qJ6UqET=Flzy#TeoTYuV4;S7 zB>_>DsXm-fD-+>uo&Tp8DHrHIiy#)Z8pK{yt;Qi}n}{2)>%(kbIfnTug8X$>il%~O zy!?6LD>P}rNYT>c)T{Fvx}=!ddN3B59ZmvK4D6n82e*Xi*Nm0BoyOUNTZ$E3Tlj~)o-!rh zRf)#8wTUTv%dvdlrh?@DoW9@;;h1LUQu{&(KH*&u;V|)AWiR&RM`ZA0y8W5rm{a?E zS@N?%YToA^zT&b=vyCs}@DZ0y;&Zhr=>~iXbizw3$t$UK#90KKkMXaA#2s7Du!g@j zGk2>$qFkVFxbGABnSJPoI>kP{R=RchEDeW1-9iHp4{F-?r{Q*-soP9io4RUpg@jxP zuYC&Ay{*Y*3gzR_lfOeT-`_s`y^*;UY=*ytCxW$Vq;~LH+QJ(YB@LwAr{7H<#g&vU zV$fwKm9?egv`na-*rU4PAAIBHnh|mOx);DT)Tf-}tG2Z((*`2yUGQ5Ec~@^W$KmNU ziT+5;{aU}`YYYQ~(#YM+BF!*(j~iXDxbxlAzxa@VEigR+;K*gw`(__(zP4s+^tjnC zT}7*hp4mEx`w%z?fu4b1{OQ+ z)ut(u&)Cyk;j4-a3dYSEpI>i=Em5Q(;Llm(jr~<6c{XKdSD=`VKx~&E`f*#yg0=z?^hb@n>4IAKb`xX%jU%C|5UqSb&5#5+ zC`O?D)f8q{J8{4!fhyqr5H3OIaapOx=sH}%JXiMK^>*x+Uy91w$U1u{=BcZvA9Bv% zdU$*B0aN*F#Y+ACP7$d03Aml_(@L|v*Rn4=xXVft-<@|%h$fyVLKgxo`7ibty;XdZ zU$^0p!eG@dwe?B@WFI^|FA^gkYGLn1YOppwU>_w@*`y}}f7g5@@(k;)Qq#+cgtAd$ zk;cL=;YiLfZ7S%)Z*x;cIrgi=@D<0mvi_Wvkdk+s>i=YQ@H|bRf5qM}vJVy`6IGN>yX>a-)LuEaT2S(#a>UYl#-%IHQerr%$ z>IjQ_;q-dEh#fq63!Zs1yCQbo^{m)-Fg#Zjr;BYb`bqk@EUR(xqi4bpM zXFuU@dL|n8Ug<+TX`=x-orOhAvcGw3&Uwy5*Vh&gf^8de=+#Am3WJp_^53MCR>9dj zLi>!B%*EqBZJx&tAB7mxnaq8SzI{EdM2>?-Z_i>)v{JP3%mb!A5F9a8L`e0OwYN}4 zgzvx$V!SjgZCNiJDPos(f8De6!k_;%oHwt>Mx2fH-amd0x_fm8xM4O6$Pr0ZtsE?zZ%XOlh)=&Snzp={&q%u-X!(fE z&$T0TraKB`fe$Y55aAh=qgB(>Gem^iyeDnLwFdtw=|yn}T>O>Xe5~KaYhmf0!cQ4< z^m`bWwf`diZw!lM=Zfi4FpXaeobr3K^ON=?Y-xhsfUiu}8GLiKtw#OD_UfWv3#c-B zU7?$CWD|pK_ZbZhXvkC09130;^4^~%0QdsM0r|7f0uimRGfmQEcj)x7syP)I%ZyOM zCi)bwh2>20kdaUP{1OUu*BfCZQ1?wb@h|_RhgF~dXC7R7>#5$wUvfzdIUXbSea?Zg zG2`dcC;w4zNcS(2#XqL1(gqiR+uPk}B+hdn%`6VgnA}AStsO&SoJ?fczpHn5P=#on z(X{z{r_Ck1g^rL?Dxwf|pawougMr>fb4uA_OvgsFGq(>F{BqI&f9)bo3P+g0%IFT` z!^IE`+Ta3>Wb!9Mt3q1BfJP!kSBN(=X;O}OerbLg=Sg?|lGG{Gd$wG8C2BnpLF zWNM{0c}5lXCm(r(09e&TsM?0lD?-YAf&X5p*ifY%i^Kv};aZZjaSz+!U0qQj3P}+N zqp{bvu5x~Qo+p4ln*&vQfEgQ_He?@FsEi?Be@~Nfc4QRmOVt1=cN{hZ4~eu`lF@=dzCnj&h>{ji1Sjmvj3@i0Mb>kTmq_ zu>`aMTmSwU z-9JP1Sz8Fo)P2sqxF3cD#77LEV6R~qp+N=BaAn6gk!bzDB1z=JhQ>*rn2R0Qv_1y1 z<}U`qwEu$hMx)nFB&m6+#Tp}PlfmD+r&ki7 z7YQos=6e3T;7Kd>r0s0VLb+}zZH&t6?kET9m{j}>-O%P)7GM~d1P#?vq~t2(Zw?-z zf<*L?u)O{q7QWcAsn2ed$;a8f2BucMZ`k4@Zig-$%d}B~v2m3h^E-*iIl(m41-Mp- zQ+AGyf||iapTtEio@SmC?vA86J_}$Jm@E`-U_VEi<%d{0hm%WW-P5%r=^|zY?z~@nJ;bhG2@8;WRePa5O@tL{S5xjhKMbvityfT z7Hqi~;2)h_NM9JxZKthWW6bnDkfQco*ZOUh&e>qq#0d03K_%x^>y{hu-acmqsAHo~ z@)7#L*B4G^Oh_*kAk~5tLOl{l6uZ;P>CQonL1>)Ec46%70M@42_x0bHr7yYE{c z=~TKAKpj9sKT-lV5A(0d0C3FoNna_s)AExnD_V#EnQlaan+CwO2JAua0?*g#TeasX zKgV8s-(9T;9s57Yn}4r0)80;u#66E3>8d!RmqLJ)D6szcBl*ChmPvLzkc)0!)?LlnSd~jvvUR4Rk7y%>}3-mR{#uH z0XL{t{C#xSef4YnU=tIQGrHqkrcYq9Ko2G4oJ?c?1D)Hld*sWD1Dv>0fvS>0X_FZa;Q=5hF=WTmG^ zx%c+0Op_bOj1!Odiw)Xk{v%CLq-5$;TH=5(U4o<%z<(U3o@RmF=Dn0e+Rd|x8-2Td zKY=ShkD%Y<2>lNnWGhA_4rlPYnuC*ttGTIaYcmp@68l@ubHjmd#If@AVzS`!eniq{ zA~vVk_D<|SsKSt_QURL<2%Bl3_g*Wn-%))l?PNZ&%FtKSv1Y5aU(7UO>aH2U>!f6W z4(Y3m_jbGWb=#ZwxGs5O*4YyS$lsZ?^x5B{jJulR$)Q>eLia-2VLCBtj4>8cF(ior zQbm}^Sz|ZL%K~BkP+d$I_F<*m#p}Boagnt>Y0?~|`S!y7g?3g9@m@x(FGgI~Y*A0DfB5OWu!ClR zZgQGb7q*cP0`Z8Zz8$T3?5|7GoJj&TGrR`t@U|{On?#ada|k zbeT3e<<0CA!~)=CzkCQ-l&z7RF*{UHrCK(Ox%T#Us=>k;eiI>nu%8##bE*a3LG(^5 zLtKt;JRE#9ST_;vzuf^9q6MgsH0EuY-D#+Sb2%38LQ^Ex~M7-7G`NadodU7 zl}9*e$w}&HMBsiR|0@$oq#@t9qOdt@Pf?MX4@8~SxMdO*`a{;76MX(~7Xm6G+3BoMTY+F{=Q1Z-Afq)MdLOjhAp`WC` zu>0p{ReVy?z)JwXacG(1NA}{1L#oe;%z=$7L> zb)?Nksx_HF=o|jmKQFw*?&AE@v7wtHGWU;0+)&c;ck%@kk?@R|)mD{r$flF&-du$zXcp3sS7;C0Tz#7tsF22f(_b04l7`v zB6sBB(b?0m7g5#y#Z`y8jMdvn|*!PFH$V;CkC(@&J}N>GkgT`ZhM`b5Uaj({%x`Gz>K6}lB6LTMRy}@ z?BNVa=6tM;_na?d0t}#&4orOby6jO!%(nXirQp1$Dy{?ECDXy0^`2dGyidg?$o2r9 z|2(|$kGMp5oY7S?0oFu{6;+P)B5jT?j%xR(0c)SRvP&>>^4};(u?nwL#$Rc!Ex-!7 zzwNBW^~8$f%i7V;otLi{`AjSP!-84R*qqzrlQf~*8BQnjJDg`;=N0y%GQBT>Y5e<8 zUcf%qPSm}Rc{=VB9$=dRlZr)>>Xrl^+TcJKo2ER9DUaHgbkq6UupYQUw-J|_IgfK4 z7Kc<3NWCyH2x#(p!tm2ORcgs<$=5WJGZz@En82iu6_!|}e_BNtB_DHcguei~n& z0bidgH7Uej#_5(^eDg6h-C>HhUi@4!+IA-Yv$r2d);(NuOcI>JgV9lsi>rm0Bn7;J z?xgf|*Y;`=5U>j~lw#f7NBQlGWJQB{;K8W-N?WVH!!4f!UmvAJC_L(`y!g${vj$5$ z+qoA0%I2L@-PVhl;zxLf2#2f3_dJG~n7S3x?zMVz#Ww$xoR>IX`JDD8`c2-_FPRJc z3|b<3H9}yWF{14}1;(lNHtC5u#QULN%vMDqGP`;aQP+A(-SGCM z1l=Sas2YhI#-BnfFG(%2sJeu*vGvYPKHQh1v)g>Q5EQ&X>ZVq2SfBzD_LNzamJEXS^i7_kW<1{(`IPH^LP_?c6ze}KZ}37q#=F2sK&{l zre^emKq{16K=odQ5$S#fqlblQNC>AZh2>UNkv03i=CMw#-$C-NEg9#NWuljOhN}T0 zG3`AGl-YtT4uTR2hmcp?%i_IuL@wF?_yd!dVh~Kv86mtM(|@-9=i^Vn*(Rmw{eNh) z|I!V3K}rBPhfL#!#{M5*|Je;_$u^W?sE5mO-ZT5ZwBMusPN)VOv}AJ$K`x^I8!FJU z3cy|E(Z84Pe|;f7CP2ns;AIZezjp&#*vQpj-0q{V{&kQ@{DHP05=adtHVwnS4iZVf z6cm69je7InkORUrXUdLGA5`1G{O_m*{d!0O={Nd;T)6*AtrR^KF!i=!G7A4TNF@Dg zJlXLEySd5#)(r`08j}rS$xk98^Y2iB!Tb+P|A(djd)@tiw@T0b!DufCNfO-r9sobE M+-uoN8PlNu1q)V%<^TWy diff --git a/docs/Tutorials/MathComponent/refresh b/docs/Tutorials/MathComponent/refresh deleted file mode 100755 index 5e58a08eb1..0000000000 --- a/docs/Tutorials/MathComponent/refresh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e - -(cd md; ./refresh) -(cd json; ./refresh) diff --git a/docs/Tutorials/README.md b/docs/Tutorials/README.md index a20aec3854..fa400b78cf 100644 --- a/docs/Tutorials/README.md +++ b/docs/Tutorials/README.md @@ -9,18 +9,18 @@ users to learn F´ and walk through the most basic steps in developing an F´ ap ## Tutorials Index 1. [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´ -2. [LedBlinker](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/README.md): F´ and Embedded Hardware -3. [MathComponent](MathComponent/Tutorial.md) +2. [LedBlinker](https://fprime-community.github.io/fprime-workshop-led-blinker/): F´ and Embedded Hardware +3. [MathComponent](https://fprime-community.github.io/fprime-tutorial-math-component/): Custom Ports and Types 4. [Cross-Compilation Tutorial](CrossCompilation/Tutorial.md) + ## [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´ -The HelloWorld tutorial walks a new users through creating a new project, designing their first F´ component, and testing that +The HelloWorld tutorial walks new users through creating a new project, designing their first F´ component, and testing that component through an F´ deployment. - -## [LedBlinker](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/README.md): F´ and Embedded Hardware +## [LedBlinker](https://fprime-community.github.io/fprime-workshop-led-blinker/): F´ and Embedded Hardware LedBlinker walks users through developing an F´ project intended for running on embedded hardware. It covers manager components, hardware drivers, and cross-compilation with the goal of blinking an LED on ARM hardware. Events, Telemetry, Commands, and Parameters are covered as well. @@ -35,6 +35,7 @@ LedBlinker walks users through developing an F´ project intended for running on 7. [Unit-Testing](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/docs/unit-testing.md) 8. [System Testing](https://github.com/fprime-community/fprime-workshop-led-blinker/blob/main/docs/system-testing.md) -## [MathComponent](MathComponent/Tutorial.md): Custom Ports and Types -MathComponent tutorial walks users through constructing a full F´ application including custom Ports, and Enumeration data types. Events, Telemetry, Commands, and Parameters are covered as well. Unit-Testing is also covered. +## [MathComponent](https://fprime-community.github.io/fprime-tutorial-math-component/): Custom Ports and Types + +The MathComponent tutorial walks users through constructing a full F´ application including custom Ports, and Enumeration data types. Events, Telemetry, Commands, and Parameters are covered as well. Unit-Testing is also covered. From f91f5c2f49985299d083bc651f95a9022d139f28 Mon Sep 17 00:00:00 2001 From: Rob Bocchino Date: Tue, 18 Jul 2023 09:52:54 -0700 Subject: [PATCH 09/12] Fix bug in Fw::Buffer (#2136) * Fix bug in Fw::Buffer * Revise null pointer check in Serializable.cpp --- Fw/Buffer/Buffer.cpp | 8 ++++++-- Fw/Types/Serializable.cpp | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Fw/Buffer/Buffer.cpp b/Fw/Buffer/Buffer.cpp index 010c2dc963..d244279c97 100644 --- a/Fw/Buffer/Buffer.cpp +++ b/Fw/Buffer/Buffer.cpp @@ -28,11 +28,15 @@ Buffer::Buffer(): Serializable(), {} Buffer::Buffer(const Buffer& src) : Serializable(), - m_serialize_repr(src.m_bufferData, src.m_size), + m_serialize_repr(), m_bufferData(src.m_bufferData), m_size(src.m_size), m_context(src.m_context) -{} +{ + if(src.m_bufferData != nullptr){ + this->m_serialize_repr.setExtBuffer(src.m_bufferData, src.m_size); + } +} Buffer::Buffer(U8* data, U32 size, U32 context) : Serializable(), m_serialize_repr(), diff --git a/Fw/Types/Serializable.cpp b/Fw/Types/Serializable.cpp index ec67ea618d..940db4b508 100644 --- a/Fw/Types/Serializable.cpp +++ b/Fw/Types/Serializable.cpp @@ -707,7 +707,7 @@ namespace Fw { } void ExternalSerializeBuffer::setExtBuffer(U8* buffPtr, NATIVE_UINT_TYPE size) { - FW_ASSERT(buffPtr); + FW_ASSERT(buffPtr != nullptr); this->m_buff = buffPtr; this->m_buffSize = size; } From ed89684432bf1ecb9213f1ab7b8cd3d25e016438 Mon Sep 17 00:00:00 2001 From: Rob Bocchino Date: Tue, 18 Jul 2023 09:53:05 -0700 Subject: [PATCH 10/12] Update STest (#2137) * Update STest * Code cleanup * Revise Pick.hpp --- STest/STest/Pick/Pick.cpp | 4 +- STest/STest/Pick/Pick.hpp | 12 ++- STest/STest/Pick/Pick_default.cpp | 4 +- STest/STest/Pick/Pick_spin.cpp | 85 ------------------- STest/STest/Pick/Pick_spin.hpp | 48 ----------- STest/STest/Random/Random.cpp | 22 ++--- .../Scenario/ConditionalIteratedScenario.hpp | 2 +- STest/STest/Scenario/ConditionalScenario.hpp | 2 +- STest/STest/Scenario/InterleavedScenario.hpp | 23 +++-- STest/STest/Scenario/IteratedScenario.hpp | 6 +- .../Scenario/RandomlyBoundedScenario.hpp | 2 +- STest/STest/Scenario/RepeatedRuleScenario.hpp | 2 +- STest/STest/Scenario/RepeatedScenario.hpp | 2 +- STest/STest/Scenario/RuleScenario.hpp | 4 +- STest/STest/Scenario/Scenario.hpp | 4 +- STest/STest/Scenario/SelectedScenario.hpp | 23 +++-- STest/STest/Scenario/SequenceScenario.hpp | 2 +- STest/STest/types/basic_types.h | 8 +- 18 files changed, 64 insertions(+), 191 deletions(-) delete mode 100644 STest/STest/Pick/Pick_spin.cpp delete mode 100644 STest/STest/Pick/Pick_spin.hpp diff --git a/STest/STest/Pick/Pick.cpp b/STest/STest/Pick/Pick.cpp index 178d77cbff..0edef7a0e9 100644 --- a/STest/STest/Pick/Pick.cpp +++ b/STest/STest/Pick/Pick.cpp @@ -1,10 +1,10 @@ // ====================================================================== // \title Pick.cpp -// \author AUTO-GENERATED: DO NOT EDIT +// \author bocchino // \brief Pick implementation // // \copyright -// Copyright (C) 2019 California Institute of Technology. +// Copyright (C) 2022 California Institute of Technology. // ALL RIGHTS RESERVED. United States Government Sponsorship // acknowledged. // ====================================================================== diff --git a/STest/STest/Pick/Pick.hpp b/STest/STest/Pick/Pick.hpp index 0ad15a3521..ca8a8e161a 100644 --- a/STest/STest/Pick/Pick.hpp +++ b/STest/STest/Pick/Pick.hpp @@ -1,10 +1,10 @@ // ====================================================================== // \title Pick.hpp -// \author AUTO-GENERATED: DO NOT EDIT +// \author bocchino // \brief Pick interface // // \copyright -// Copyright (C) 2019 California Institute of Technology. +// Copyright (C) 2022 California Institute of Technology. // ALL RIGHTS RESERVED. United States Government Sponsorship // acknowledged. // ====================================================================== @@ -12,12 +12,10 @@ #ifndef STEST_PICK_HPP #define STEST_PICK_HPP -#include -#include -#include - #include "STest/types/basic_types.h" - +#ifdef STEST_MODE_spin +#include "STest/Pick/Pick_spin.hpp" +#endif namespace STest { diff --git a/STest/STest/Pick/Pick_default.cpp b/STest/STest/Pick/Pick_default.cpp index 8763c12d58..e723bad3fd 100644 --- a/STest/STest/Pick/Pick_default.cpp +++ b/STest/STest/Pick/Pick_default.cpp @@ -1,10 +1,10 @@ // ====================================================================== // \title Pick_default.cpp -// \author AUTO-GENERATED: DO NOT EDIT +// \author bocchino // \brief Pick_default implementation // // \copyright -// Copyright (C) 2019 California Institute of Technology. +// Copyright (C) 2022 California Institute of Technology. // ALL RIGHTS RESERVED. United States Government Sponsorship // acknowledged. // ====================================================================== diff --git a/STest/STest/Pick/Pick_spin.cpp b/STest/STest/Pick/Pick_spin.cpp deleted file mode 100644 index 29d883b878..0000000000 --- a/STest/STest/Pick/Pick_spin.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// ====================================================================== -// \title Pick_spin.cpp -// \author AUTO-GENERATED: DO NOT EDIT -// \brief Pick_spin implementation -// -// \copyright -// Copyright (C) 2019 California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// ====================================================================== - -#include "STest/Pick/Pick.hpp" -#include "STest/Pick/Pick_spin.hpp" -#include "STest/Random/Random.hpp" - -namespace STest { - - namespace Pick { - - namespace Spin { - - namespace { - - //! The array of random seeds - U32 seeds[NUM_RANDOM_SEEDS]; - - //! The array of random values - U32 values[NUM_RANDOM_VALUES]; - - //! Whether this module is initialized - bool initialized = false; - - } - - //! Pick a random U32 - U32 pickRandU32() { - assert(initialized); - const U32 randU32 = STest::Random::lowerUpper(0, 0xFFFFFFFFU); - return values[randU32 % NUM_RANDOM_VALUES]; - } - - //! Pick a double value in the interval [0, 1] - double inUnitInterval() { - const U32 randU32 = pickRandU32(); - const F64 ratio = static_cast(randU32) / 0xFFFFFFFFU; - return ratio; - } - - void init() { - for (U32 i = 0; i < NUM_RANDOM_SEEDS; ++i) { - seeds[i] = STest::Random::lowerUpper(0, 0xFFFFFFFFU); - } - for (U32 i = 0; i < NUM_RANDOM_VALUES; ++i) { - values[i] = STest::Random::lowerUpper(0, 0xFFFFFFFFU); - } - initialized = true; - } - - void initRule(const U32 seedIndex) { - assert(initialized); - assert(seedIndex < NUM_RANDOM_SEEDS); - const U32 seed = seeds[seedIndex]; - STest::Random::SeedValue::set(seed); - } - - } - - U32 startLength(const U32 start, const U32 length) { - assert(length > 0); - const F64 randFloat = Spin::inUnitInterval() * length; - const U32 offset = static_cast(randFloat); - return start + offset; - } - - U32 lowerUpper(const U32 lower, const U32 upper) { - assert(lower <= upper); - const F64 length = static_cast(upper) - lower + 1; - const F64 randFloat = Spin::inUnitInterval() * length; - const U32 offset = static_cast(randFloat); - return lower + offset; - } - - } - -} diff --git a/STest/STest/Pick/Pick_spin.hpp b/STest/STest/Pick/Pick_spin.hpp deleted file mode 100644 index b567656272..0000000000 --- a/STest/STest/Pick/Pick_spin.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// ====================================================================== -// \title Pick_spin.hpp -// \author AUTO-GENERATED: DO NOT EDIT -// \brief Pick_spin interface -// -// \copyright -// Copyright (C) 2019 California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// ====================================================================== - -#ifndef STEST_PICK_SPIN_HPP -#define STEST_PICK_SPIN_HPP - -#include -#include -#include - -#include "STest/types/basic_types.h" - -namespace STest { - - namespace Pick { - - namespace Spin { - - enum Constants { - //! The number of random seeds to use - NUM_RANDOM_SEEDS = 5, - //! The number of random values to use - NUM_RANDOM_VALUES = 5 - }; - - //! Initialize the module - void init(); - - //! Initialize a rule - void initRule( - const U32 seedIndex //!< The index into the random seeds - ); - - } - - } - -} - -#endif diff --git a/STest/STest/Random/Random.cpp b/STest/STest/Random/Random.cpp index 7d993f863e..130afff108 100644 --- a/STest/STest/Random/Random.cpp +++ b/STest/STest/Random/Random.cpp @@ -35,15 +35,12 @@ namespace STest { const char *const fileName, U32& value ) { - bool result = true; + bool result = false; FILE *fp = fopen(fileName, "r"); if (fp != nullptr) { result = (fscanf(fp, "%" PRIu32, &value) == 1); (void) fclose(fp); } - else { - result = false; - } return result; } @@ -55,7 +52,7 @@ namespace STest { const char *const fileName, const U32 seedValue ) { - bool result = true; + bool result = false; FILE *fp = fopen(fileName, "a"); if (fp != nullptr) { int status = fprintf( @@ -66,9 +63,6 @@ namespace STest { result = (status > 0); (void) fclose(fp); } - else { - result = false; - } return result; } @@ -79,11 +73,11 @@ namespace STest { const bool seedValueOK = SeedValue::getFromFile("seed", seedValue); if (seedValueOK) { - printf("[STest::Random] Read seed %" PRIu32 " from file\n", seedValue); + (void) printf("[STest::Random] Read seed %" PRIu32 " from file\n", seedValue); } else { seedValue = SeedValue::getFromTime(); - printf("[STest::Random] Generated seed %" PRIu32 " from system time\n", seedValue); + (void) printf("[STest::Random] Generated seed %" PRIu32 " from system time\n", seedValue); } (void) SeedValue::appendToFile("seed-history", seedValue); SeedValue::set(seedValue); @@ -94,9 +88,7 @@ namespace STest { const U32 length ) { assert(length > 0); - const F64 randFloat = inUnitInterval() * length; - const U32 offset = static_cast(randFloat); - return start + offset; + return lowerUpper(start, start + length - 1); } U32 lowerUpper( @@ -107,7 +99,9 @@ namespace STest { const F64 length = static_cast(upper) - lower + 1; const F64 randFloat = inUnitInterval() * length; const U32 offset = static_cast(randFloat); - return lower + offset; + const U32 result = lower + offset; + // Handle boundary case where inUnitInterval returns 1.0 + return (result <= upper) ? result : result - 1; } double inUnitInterval() { diff --git a/STest/STest/Scenario/ConditionalIteratedScenario.hpp b/STest/STest/Scenario/ConditionalIteratedScenario.hpp index 1be1bf1e57..e50ec7d193 100644 --- a/STest/STest/Scenario/ConditionalIteratedScenario.hpp +++ b/STest/STest/Scenario/ConditionalIteratedScenario.hpp @@ -57,7 +57,7 @@ namespace STest { } //! The virtual implementation of nextScenario required by IteratedScenario - //! \return The next scenario, assuming isDone() is false, or NULL if none + //! \return The next scenario, assuming isDone() is false, or nullptr if none Scenario* nextScenario_IteratedScenario( State& state //!< The system state ) { diff --git a/STest/STest/Scenario/ConditionalScenario.hpp b/STest/STest/Scenario/ConditionalScenario.hpp index 2cb4cd9935..5693297f0e 100644 --- a/STest/STest/Scenario/ConditionalScenario.hpp +++ b/STest/STest/Scenario/ConditionalScenario.hpp @@ -56,7 +56,7 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { diff --git a/STest/STest/Scenario/InterleavedScenario.hpp b/STest/STest/Scenario/InterleavedScenario.hpp index 634f6ee727..0d96da10f0 100644 --- a/STest/STest/Scenario/InterleavedScenario.hpp +++ b/STest/STest/Scenario/InterleavedScenario.hpp @@ -38,15 +38,20 @@ namespace STest { const U32 size //!< The size of the array ) : Scenario(name), - scenarioArray(new ScenarioArray(scenarios, size)) + scenarioArray(new ScenarioArray(scenarios, size)), + seen(new bool[size]) { } //! Destroy an InterleavedScenario object ~InterleavedScenario() { - assert(this->scenarioArray != nullptr); - delete this->scenarioArray; + if (this->scenarioArray != nullptr) { + delete this->scenarioArray; + } + if (this->seen != nullptr) { + delete[] this->seen; + } } protected: @@ -62,14 +67,13 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { assert(this->scenarioArray != nullptr); Rule* rule = nullptr; - bool seen[this->scenarioArray->size]; - memset(seen, 0, this->scenarioArray->size * sizeof(bool)); + memset(this->seen, 0, this->scenarioArray->size * sizeof(bool)); U32 numSeen = 0; Scenario* *const scenarios = this->scenarioArray->getScenarios(); @@ -79,14 +83,14 @@ namespace STest { assert(numIterations < maxIterations); ++numIterations; const U32 i = this->scenarioArray->getRandomIndex(); - if (seen[i]) { + if (this->seen[i]) { continue; } rule = scenarios[i]->nextRule(state); if (rule != nullptr) { break; } - seen[i] = true; + this->seen[i] = true; ++numSeen; } return rule; @@ -118,6 +122,9 @@ namespace STest { //! The scenarios to interleave ScenarioArray* scenarioArray; + //! An array to store the scenarios seen + bool* seen; + }; } diff --git a/STest/STest/Scenario/IteratedScenario.hpp b/STest/STest/Scenario/IteratedScenario.hpp index b13489d564..78235094a5 100644 --- a/STest/STest/Scenario/IteratedScenario.hpp +++ b/STest/STest/Scenario/IteratedScenario.hpp @@ -51,7 +51,7 @@ namespace STest { // ---------------------------------------------------------------------- //! Return the next scenario to run - //! \return The next scenario, assuming isDone() is false, or NULL if none + //! \return The next scenario, assuming isDone() is false, or nullptr if none Scenario* nextScenario( State& state //!< The system state ) { @@ -78,7 +78,7 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { @@ -118,7 +118,7 @@ namespace STest { virtual void reset_IteratedScenario() = 0; //! The virtual implementation of nextScenario required by IteratedScenario - //! \return The next scenario, assuming isDone() is false, or NULL if none + //! \return The next scenario, assuming isDone() is false, or nullptr if none virtual Scenario* nextScenario_IteratedScenario( State& state //!< The system state ) = 0; diff --git a/STest/STest/Scenario/RandomlyBoundedScenario.hpp b/STest/STest/Scenario/RandomlyBoundedScenario.hpp index c73077728f..513809f191 100644 --- a/STest/STest/Scenario/RandomlyBoundedScenario.hpp +++ b/STest/STest/Scenario/RandomlyBoundedScenario.hpp @@ -73,7 +73,7 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { diff --git a/STest/STest/Scenario/RepeatedRuleScenario.hpp b/STest/STest/Scenario/RepeatedRuleScenario.hpp index a2cbc9fb88..7ebd3404ff 100644 --- a/STest/STest/Scenario/RepeatedRuleScenario.hpp +++ b/STest/STest/Scenario/RepeatedRuleScenario.hpp @@ -49,7 +49,7 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { diff --git a/STest/STest/Scenario/RepeatedScenario.hpp b/STest/STest/Scenario/RepeatedScenario.hpp index a714a0d9fa..dce19c1d24 100644 --- a/STest/STest/Scenario/RepeatedScenario.hpp +++ b/STest/STest/Scenario/RepeatedScenario.hpp @@ -52,7 +52,7 @@ namespace STest { } //! The virtual implementation of nextScenario required by IteratedScenario - //! \return The next scenario, assuming isDone() is false, or NULL if none + //! \return The next scenario, assuming isDone() is false, or nullptr if none Scenario* nextScenario_IteratedScenario( State& state //!< The system state ) { diff --git a/STest/STest/Scenario/RuleScenario.hpp b/STest/STest/Scenario/RuleScenario.hpp index e404c6d705..4b5c2ccc7b 100644 --- a/STest/STest/Scenario/RuleScenario.hpp +++ b/STest/STest/Scenario/RuleScenario.hpp @@ -50,12 +50,12 @@ namespace STest { } //! the virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { Rule *rule = nullptr; - if (this->rule.precondition(state)) { + if (!this->isDone() && this->rule.precondition(state)) { rule = &this->rule; this->done = true; } diff --git a/STest/STest/Scenario/Scenario.hpp b/STest/STest/Scenario/Scenario.hpp index c2ab8e72c6..73883237a7 100644 --- a/STest/STest/Scenario/Scenario.hpp +++ b/STest/STest/Scenario/Scenario.hpp @@ -67,7 +67,7 @@ namespace STest { } //! Return the next rule to apply - //! \return The next rule, or NULL if none + //! \return The next rule, or nullptr if none Rule* nextRule( State& state //!< The system state ) { @@ -94,7 +94,7 @@ namespace STest { virtual void reset_Scenario() = 0; //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none virtual Rule* nextRule_Scenario( State& state //!< The system state ) = 0; diff --git a/STest/STest/Scenario/SelectedScenario.hpp b/STest/STest/Scenario/SelectedScenario.hpp index 69c20aa9c2..c8f38ad916 100644 --- a/STest/STest/Scenario/SelectedScenario.hpp +++ b/STest/STest/Scenario/SelectedScenario.hpp @@ -40,15 +40,20 @@ namespace STest { ) : Scenario(name), scenarioArray(new ScenarioArray(scenarios, size)), - selectedScenario(nullptr) + selectedScenario(nullptr), + seen(new bool[size]) { } //! Destroy a SelectedScenario object virtual ~SelectedScenario() { - assert(this->scenarioArray != nullptr); - delete this->scenarioArray; + if (this->scenarioArray != nullptr) { + delete this->scenarioArray; + } + if (this->seen != nullptr) { + delete[] this->seen; + } } public: @@ -65,7 +70,7 @@ namespace STest { } //! The virtual implementation of nextRule required by Scenario - //! \return The next rule, assuming isDone() is false, or NULL if none + //! \return The next rule, assuming isDone() is false, or nullptr if none Rule* nextRule_Scenario( State& state //!< The system state ) { @@ -114,8 +119,7 @@ namespace STest { ) { Rule* rule = nullptr; const U32 size = this->scenarioArray->size; - bool seen[size]; - memset(seen, 0, sizeof(seen)); + memset(this->seen, 0, size * sizeof(bool)); U32 numSeen = 0; assert(this->scenarioArray != nullptr); Scenario **const scenarios = @@ -123,7 +127,7 @@ namespace STest { assert(scenarios != nullptr); while (numSeen < size) { const U32 i = this->scenarioArray->getRandomIndex(); - if (seen[i]) { + if (this->seen[i]) { continue; } Scenario *const scenario = scenarios[i]; @@ -133,7 +137,7 @@ namespace STest { this->selectedScenario = scenario; break; } - seen[i] = true; + this->seen[i] = true; ++numSeen; } return rule; @@ -151,6 +155,9 @@ namespace STest { //! The selected scenario Scenario* selectedScenario; + //! An array to store the scenarios seen + bool* seen; + }; } diff --git a/STest/STest/Scenario/SequenceScenario.hpp b/STest/STest/Scenario/SequenceScenario.hpp index 4410ce8c83..15dc729ad0 100644 --- a/STest/STest/Scenario/SequenceScenario.hpp +++ b/STest/STest/Scenario/SequenceScenario.hpp @@ -63,7 +63,7 @@ namespace STest { } //! The virtual implementation of nextScenario required by IteratedScenario - //! \return The next scenario, assuming isDone() is false, or NULL if none + //! \return The next scenario, assuming isDone() is false, or nullptr if none Scenario* nextScenario_IteratedScenario( State& state //!< The system state ) { diff --git a/STest/STest/types/basic_types.h b/STest/STest/types/basic_types.h index 907175f1bf..0b939ff1f7 100644 --- a/STest/STest/types/basic_types.h +++ b/STest/STest/types/basic_types.h @@ -1,16 +1,16 @@ // ====================================================================== // \title basic_types.h // \author bocchino -// \brief Basic types +// \brief STest basic types // // \copyright // Copyright (C) 2017 California Institute of Technology. // ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. +// acknowledged. // ====================================================================== -#ifndef BASIC_TYPES_H -#define BASIC_TYPES_H +#ifndef STEST_BASIC_TYPES_H +#define STEST_BASIC_TYPES_H #ifdef __cplusplus extern "C" { From 6e799d83dc73175584495144d2231d1f6a3dff82 Mon Sep 17 00:00:00 2001 From: Rob Bocchino Date: Tue, 18 Jul 2023 09:53:16 -0700 Subject: [PATCH 11/12] Clarify valid buffers (#2138) * Clarify valid buffers * Revise Buffer SDD --- Fw/Buffer/Buffer.cpp | 4 ++++ Fw/Buffer/Buffer.hpp | 4 ++++ Fw/Buffer/docs/sdd.md | 26 ++++++++++++++++++-------- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Fw/Buffer/Buffer.cpp b/Fw/Buffer/Buffer.cpp index d244279c97..65ff479bfb 100644 --- a/Fw/Buffer/Buffer.cpp +++ b/Fw/Buffer/Buffer.cpp @@ -61,6 +61,10 @@ bool Buffer::operator==(const Buffer& src) const { return (this->m_bufferData == src.m_bufferData) && (this->m_size == src.m_size) && (this->m_context == src.m_context); } +bool Buffer::isValid() const { + return (this->m_bufferData != nullptr) && (this->m_size > 0); +} + U8* Buffer::getData() const { return this->m_bufferData; } diff --git a/Fw/Buffer/Buffer.hpp b/Fw/Buffer/Buffer.hpp index 50d021216b..f24e7971a5 100644 --- a/Fw/Buffer/Buffer.hpp +++ b/Fw/Buffer/Buffer.hpp @@ -118,6 +118,10 @@ class Buffer : public Fw::Serializable { // Accessor functions // ---------------------------------------------------------------------- + //! Returns true if the buffer is valid (data pointer != nullptr and size > 0) + //! + bool isValid() const; + //! Returns wrapped data pointer //! U8* getData() const; diff --git a/Fw/Buffer/docs/sdd.md b/Fw/Buffer/docs/sdd.md index 4dba905ea1..dddc1394a6 100644 --- a/Fw/Buffer/docs/sdd.md +++ b/Fw/Buffer/docs/sdd.md @@ -6,7 +6,7 @@ This module provides the following elements: * A type `Fw::Buffer` representing a wrapper around a variable-size buffer. This allows for passing a reference to the -allocated memory around without a copy. Typically the memory is allocated in a buffer manager or similar component but +allocated memory around without a copy. Typically the memory is allocated in a buffer manager or similar component but this is not required. * A port `Fw::BufferGet` for requesting a buffer of type `Fw::Buffer` from a [`BufferManager`](../../../Svc/BufferManager/docs/sdd.md) and similar components. @@ -30,8 +30,14 @@ Name | Type | Accessors | Purpose `m_context` | `U32` | `getContext()`/`setContext()` | Context of buffer's origin. Used to track buffers created by [`BufferManager`](../../../Svc/BufferManager/docs/sdd.md) `m_serialize_repr` | `Fw::ExternalSerializeBuffer` | `getSerializeRepr()` | Interface for serialization to internal buffer -If the size of the data is set to 0, the pointer returned by `getData()` and the serialization interface returned by -`getSerializeRepr()` are considered invalid and should not be used. +A value _B_ of type `Fw::Buffer` is **valid** if `m_bufferData != nullptr` and +`m_size > 0`; otherwise it is **invalid**. +The interface function `isValid` reports whether a buffer is valid. +Calling this function on a buffer _B_ returns `true` if _B_ is valid, otherwise `false`. + +If a buffer _B_ is invalid, then the pointer returned by _B_ `.getData()` and the +serialization interface returned by +_B_ `.getSerializeRepr()` are considered invalid and should not be used. The `getSerializeRepr()` function may be used to interact with the wrapped data buffer by serializing types to and from the data region. @@ -40,7 +46,7 @@ the data region. ### 2.2 The Port Fw::BufferGet As shown in the following diagram, `Fw::BufferGet` has one argument `size` of type `U32`. It returns a value of type -`Fw::Buffer`. The returned `Fw::Buffer`'s size must be checked for validity before using. +`Fw::Buffer`. The returned `Fw::Buffer` must be checked for validity before using. ![`Fw::BufferGet` Diagram](img/BufferGetBDD.jpg "Fw::BufferGet Port") @@ -53,16 +59,20 @@ As shown in the following diagram, `Fw::BufferSend` has one argument `fwBuffer` ## 3 Usage Notes Components allocating `Fw::Buffer` objects may use the `m_context` field at their discretion. This field is typically -used to track the origin of the buffer for eventual allocation. When a component fails to allocate memory, it must set -the `m_size` field to zero to indicate that the buffer is invalid. +used to track the origin of the buffer for eventual allocation. + +When a component fails to allocate memory, it must set +the `m_bufferData` field to `nullptr` and/or set the `m_size` field to zero to indicate that the buffer is invalid. -Receivers of `Fw::Buffer` objects are expected to check the `m_size` field before using the buffer. +A receiver of an `Fw::Buffer` object _B_ must check that _B_ is valid before accessing the +data stored in _B_. +To check validity, you can call the interface function `isValid()`. ### Serializing and Deserializing with `Fw::Buffer` Users can obtain a SerializeBuffer, `sb`, by calling `getSerializeRepr()`. This serialize buffer is backed by the memory of the `Fw::Buffer` and is initially empty. Users can serialize and deserialize through `sb` to copy to/from the backed -memory. +memory. The state of `sb` persists as long as the current `Fw::Buffer` object exists as it is stored as a member. However, all `Fw::Buffer` constructors initialize `sb` to an empty state including the `Fw::Buffer` copy constructor. Thus, if an From 92c9f0712358ee34789c69a4cdc013f17748ad86 Mon Sep 17 00:00:00 2001 From: Thomas Boyer Chammard <49786685+thomas-bc@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:56:20 -0700 Subject: [PATCH 12/12] Reference cross-compilation in tutorial index (#2155) --- .../CrossCompilationSetup/CrossCompilationSetupTutorial.md | 2 ++ .../CrossCompilationSetup/CrossCompilationTutorial.md | 4 +++- docs/Tutorials/CrossCompilationSetup/README.md | 7 +++++++ docs/Tutorials/README.md | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 docs/Tutorials/CrossCompilationSetup/README.md diff --git a/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md b/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md index e6e047c315..2fbf66efaf 100644 --- a/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md +++ b/docs/Tutorials/CrossCompilationSetup/CrossCompilationSetupTutorial.md @@ -57,3 +57,5 @@ Next, ensure that the ARM toolchains were installed properly. To test, run the f Any output other than "file/command not found" is good. > Note: macOS users must run these commands from within the Docker container described in [Appendix I](./appendix-1.md). + +**Next:** [Compiling for ARM](./CrossCompilationTutorial.md) diff --git a/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md b/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md index 92c4dfeb75..493fece2e7 100644 --- a/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md +++ b/docs/Tutorials/CrossCompilationSetup/CrossCompilationTutorial.md @@ -49,4 +49,6 @@ fprime-util build aarch64-linux fprime-util generate arm-hf-linux fprime-util build arm-hf-linux ``` -> Note: macOS users must run these commands from within the Docker container described in [Appendix I](./appendix-1.md). \ No newline at end of file +> Note: macOS users must run these commands from within the Docker container described in [Appendix I](./appendix-1.md). + +**Next:** [Running on ARM Linux](./ArmLinuxTutorial.md) \ No newline at end of file diff --git a/docs/Tutorials/CrossCompilationSetup/README.md b/docs/Tutorials/CrossCompilationSetup/README.md new file mode 100644 index 0000000000..e69a760190 --- /dev/null +++ b/docs/Tutorials/CrossCompilationSetup/README.md @@ -0,0 +1,7 @@ +# F´ Cross-Compilation Tutorial + +## Table of Contents + +1. [Cross-Compilation Setup](./CrossCompilationSetupTutorial.md) +2. [Compiling for ARM](./CrossCompilationTutorial.md) +3. [Running on ARM Linux](./ArmLinuxTutorial.md) diff --git a/docs/Tutorials/README.md b/docs/Tutorials/README.md index fa400b78cf..8413c51860 100644 --- a/docs/Tutorials/README.md +++ b/docs/Tutorials/README.md @@ -11,7 +11,7 @@ users to learn F´ and walk through the most basic steps in developing an F´ ap 1. [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´ 2. [LedBlinker](https://fprime-community.github.io/fprime-workshop-led-blinker/): F´ and Embedded Hardware 3. [MathComponent](https://fprime-community.github.io/fprime-tutorial-math-component/): Custom Ports and Types -4. [Cross-Compilation Tutorial](CrossCompilation/Tutorial.md) +4. [Cross-Compilation Tutorial](CrossCompilationSetup/README.md) ## [HelloWorld](https://fprime-community.github.io/fprime-tutorial-hello-world/): An Introduction to F´

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 KspOFV&Hn~Sfc29A literal 0 HcmV?d00001 From 3cd2083634b57a923820e1028f88d8e1bc662cfd Mon Sep 17 00:00:00 2001 From: Thomas Boyer Chammard <49786685+thomas-bc@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:52:40 -0700 Subject: [PATCH 08/12] Migrate MathComponent tutorial to fprime-community (#2119) * Migrate MathComponent tutorial to fprime-community * update spelling --- .github/actions/spelling/expect.txt | 2 +- docs/Tutorials/MathComponent/CMakeLists.txt | 57 - .../MathComponent/MathPorts/CMakeLists.txt | 5 - .../MathComponent/MathPorts/MathPorts.fpp | 15 - .../MathComponent/MathReceiver/CMakeLists.txt | 15 - .../MathReceiver/MathReceiver.cpp | 151 -- .../MathReceiver/MathReceiver.fpp | 106 - .../MathReceiver/MathReceiver.hpp | 90 - .../MathReceiver/test/ut/Tester.cpp | 242 -- .../MathReceiver/test/ut/Tester.hpp | 136 - .../MathReceiver/test/ut/main.cpp | 37 - .../MathComponent/MathSender/CMakeLists.txt | 15 - .../MathComponent/MathSender/MathSender.cpp | 81 - .../MathComponent/MathSender/MathSender.fpp | 90 - .../MathComponent/MathSender/MathSender.hpp | 80 - .../MathSender/test/ut/Tester.cpp | 177 -- .../MathSender/test/ut/Tester.hpp | 115 - .../MathComponent/MathSender/test/ut/main.cpp | 37 - .../MathComponent/MathTypes/CMakeLists.txt | 5 - .../MathComponent/MathTypes/MathTypes.fpp | 11 - docs/Tutorials/MathComponent/README.md | 3 + .../Tutorials/MathComponent/Top/instances.fpp | 369 --- docs/Tutorials/MathComponent/Top/topology.fpp | 160 -- docs/Tutorials/MathComponent/Tutorial.md | 2320 ----------------- docs/Tutorials/MathComponent/json/refresh | 3 - docs/Tutorials/MathComponent/json/top.json | 76 - docs/Tutorials/MathComponent/json/top.txt | 13 - docs/Tutorials/MathComponent/md/.gitignore | 3 - docs/Tutorials/MathComponent/md/Tutorial.md | 1983 -------------- docs/Tutorials/MathComponent/md/bad-refs.awk | 15 - docs/Tutorials/MathComponent/md/check-refs | 28 - docs/Tutorials/MathComponent/md/defined-tags | 8 - docs/Tutorials/MathComponent/md/include.awk | 15 - docs/Tutorials/MathComponent/md/refresh | 18 - docs/Tutorials/MathComponent/md/sections.awk | 34 - docs/Tutorials/MathComponent/md/tags.awk | 37 - docs/Tutorials/MathComponent/md/toc.awk | 31 - .../Tutorials/MathComponent/md/undefined-tags | 9 - docs/Tutorials/MathComponent/md/used-tags | 11 - docs/Tutorials/MathComponent/png/top.png | Bin 67631 -> 0 bytes docs/Tutorials/MathComponent/refresh | 4 - docs/Tutorials/README.md | 15 +- 42 files changed, 12 insertions(+), 6610 deletions(-) delete mode 100644 docs/Tutorials/MathComponent/CMakeLists.txt delete mode 100644 docs/Tutorials/MathComponent/MathPorts/CMakeLists.txt delete mode 100644 docs/Tutorials/MathComponent/MathPorts/MathPorts.fpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/CMakeLists.txt delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/MathReceiver.cpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/MathReceiver.fpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/MathReceiver.hpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.cpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.hpp delete mode 100644 docs/Tutorials/MathComponent/MathReceiver/test/ut/main.cpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/CMakeLists.txt delete mode 100644 docs/Tutorials/MathComponent/MathSender/MathSender.cpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/MathSender.fpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/MathSender.hpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/test/ut/Tester.cpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/test/ut/Tester.hpp delete mode 100644 docs/Tutorials/MathComponent/MathSender/test/ut/main.cpp delete mode 100644 docs/Tutorials/MathComponent/MathTypes/CMakeLists.txt delete mode 100644 docs/Tutorials/MathComponent/MathTypes/MathTypes.fpp create mode 100644 docs/Tutorials/MathComponent/README.md delete mode 100644 docs/Tutorials/MathComponent/Top/instances.fpp delete mode 100644 docs/Tutorials/MathComponent/Top/topology.fpp delete mode 100644 docs/Tutorials/MathComponent/Tutorial.md delete mode 100755 docs/Tutorials/MathComponent/json/refresh delete mode 100644 docs/Tutorials/MathComponent/json/top.json delete mode 100644 docs/Tutorials/MathComponent/json/top.txt delete mode 100644 docs/Tutorials/MathComponent/md/.gitignore delete mode 100644 docs/Tutorials/MathComponent/md/Tutorial.md delete mode 100644 docs/Tutorials/MathComponent/md/bad-refs.awk delete mode 100755 docs/Tutorials/MathComponent/md/check-refs delete mode 100755 docs/Tutorials/MathComponent/md/defined-tags delete mode 100644 docs/Tutorials/MathComponent/md/include.awk delete mode 100755 docs/Tutorials/MathComponent/md/refresh delete mode 100644 docs/Tutorials/MathComponent/md/sections.awk delete mode 100644 docs/Tutorials/MathComponent/md/tags.awk delete mode 100644 docs/Tutorials/MathComponent/md/toc.awk delete mode 100755 docs/Tutorials/MathComponent/md/undefined-tags delete mode 100755 docs/Tutorials/MathComponent/md/used-tags delete mode 100644 docs/Tutorials/MathComponent/png/top.png delete mode 100755 docs/Tutorials/MathComponent/refresh diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 2d27bda491..49dcb2d512 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -523,7 +523,7 @@ FPCONFIG fpconfighpp FPGA fpi -fpl +FPL fpp fppi FPport diff --git a/docs/Tutorials/MathComponent/CMakeLists.txt b/docs/Tutorials/MathComponent/CMakeLists.txt deleted file mode 100644 index b9d97bec5b..0000000000 --- a/docs/Tutorials/MathComponent/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -#### -# 'Ref' Deployment: -# -# This sets up the build for the 'Ref' Application, including the custom reference -# components. In addition, it imports FPrime.cmake, which includes the core F Prime -# components. -# -# This file has several sections. -# -# 1. Header Section: define basic properties of the build -# 2. F prime core: includes all F prime core components, and build-system properties -# 3. Local subdirectories: contains all deployment specific directory additions -#### - -## -# Section 1: Basic Project Setup -# -# This contains the basic project information. Specifically, a cmake version and -# project definition. -## -cmake_minimum_required(VERSION 3.13) -cmake_policy(SET CMP0048 NEW) -project(Ref VERSION 1.0.0 LANGUAGES C CXX) - -## -# Section 2: F prime Core -# -# This includes all of the F prime core components, and imports the make-system. F prime core -# components will be placed in the F-Prime binary subdirectory to keep them from -# colliding with deployment specific items. -## -include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FPrime.cmake") -# NOTE: register custom targets between these two lines -include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FPrime-Code.cmake") -## -# Section 3: Components and Topology -# -# This section includes deployment specific directories. This allows use of non- -# core components in the topology, which is also added here. -## -# Add component subdirectories -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathPorts/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathReceiver/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathSender/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathTypes/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PingReceiver/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/RecvBuffApp/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SendBuffApp/") -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SignalGen/") - -# Add Topology subdirectory -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Top/") - -set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/Top/Main.cpp") -set(MOD_DEPS ${PROJECT_NAME}/Top) - -register_fprime_deployment() diff --git a/docs/Tutorials/MathComponent/MathPorts/CMakeLists.txt b/docs/Tutorials/MathComponent/MathPorts/CMakeLists.txt deleted file mode 100644 index 36f86a667d..0000000000 --- a/docs/Tutorials/MathComponent/MathPorts/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathPorts.fpp" -) - -register_fprime_module() diff --git a/docs/Tutorials/MathComponent/MathPorts/MathPorts.fpp b/docs/Tutorials/MathComponent/MathPorts/MathPorts.fpp deleted file mode 100644 index 0197e7b651..0000000000 --- a/docs/Tutorials/MathComponent/MathPorts/MathPorts.fpp +++ /dev/null @@ -1,15 +0,0 @@ -module Ref { - - @ Port for requesting an operation on two numbers - port MathOp( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) - - @ Port for returning the result of a math operation - port MathResult( - result: F32 @< the result of the operation - ) - -} diff --git a/docs/Tutorials/MathComponent/MathReceiver/CMakeLists.txt b/docs/Tutorials/MathComponent/MathReceiver/CMakeLists.txt deleted file mode 100644 index c1126f9b71..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Register the standard build -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.cpp" - "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.fpp" -) -register_fprime_module() - -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathReceiver.fpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/main.cpp" -) -set(UT_MOD_DEPS STest) -register_fprime_ut() diff --git a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.cpp b/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.cpp deleted file mode 100644 index 50340b3b5e..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// ====================================================================== -// \title MathReceiver.cpp -// \author tcanham, bocchino -// \brief cpp file for MathReceiver component implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#include "Fw/Types/Assert.hpp" -#include -#include "Ref/MathReceiver/MathReceiver.hpp" - -namespace Ref { - - // ---------------------------------------------------------------------- - // Construction, initialization, and destruction - // ---------------------------------------------------------------------- - - MathReceiver :: - MathReceiver( - const char *const compName - ) : - MathReceiverComponentBase(compName) - { - - } - - void MathReceiver :: - init( - const NATIVE_INT_TYPE queueDepth, - const NATIVE_INT_TYPE instance - ) - { - MathReceiverComponentBase::init(queueDepth, instance); - } - - MathReceiver :: - ~MathReceiver() - { - - } - - // ---------------------------------------------------------------------- - // Handler implementations for user-defined typed input ports - // ---------------------------------------------------------------------- - - void MathReceiver :: - mathOpIn_handler( - const NATIVE_INT_TYPE portNum, - F32 val1, - const MathOp& op, - F32 val2 - ) - { - - // Get the initial result - F32 res = 0.0; - switch (op.e) { - case MathOp::ADD: - res = val1 + val2; - break; - case MathOp::SUB: - res = val1 - val2; - break; - case MathOp::MUL: - res = val1 * val2; - break; - case MathOp::DIV: - res = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - - // Get the factor value - Fw::ParamValid valid; - F32 factor = paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - - // Multiply result by factor - res *= factor; - - // Emit telemetry and events - this->log_ACTIVITY_HI_OPERATION_PERFORMED(op); - this->tlmWrite_OPERATION(op); - - // Emit result - this->mathResultOut_out(0, res); - - } - - void MathReceiver :: - schedIn_handler( - const NATIVE_INT_TYPE portNum, - NATIVE_UINT_TYPE context - ) - { - U32 numMsgs = this->m_queue.getNumMsgs(); - for (U32 i = 0; i < numMsgs; ++i) { - (void) this->doDispatch(); - } - - } - - // ---------------------------------------------------------------------- - // Command handler implementations - // ---------------------------------------------------------------------- - - void MathReceiver :: - CLEAR_EVENT_THROTTLE_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq - ) - { - // clear throttle - this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear(); - // send event that throttle is cleared - this->log_ACTIVITY_HI_THROTTLE_CLEARED(); - // reply with completion status - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); - } - - void MathReceiver :: - parameterUpdated(FwPrmIdType id) - { - switch (id) { - case PARAMID_FACTOR: { - Fw::ParamValid valid; - F32 val = this->paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - this->log_ACTIVITY_HI_FACTOR_UPDATED(val); - break; - } - default: - FW_ASSERT(0, id); - break; - } - } - -} // end namespace Ref diff --git a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.fpp b/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.fpp deleted file mode 100644 index 603bfd7c88..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.fpp +++ /dev/null @@ -1,106 +0,0 @@ -module Ref { - - @ Component for receiving and performing a math operation - queued component MathReceiver { - - # ---------------------------------------------------------------------- - # General ports - # ---------------------------------------------------------------------- - - @ Port for receiving the math operation - async input port mathOpIn: MathOp - - @ Port for returning the math result - output port mathResultOut: MathResult - - @ The rate group scheduler input - sync input port schedIn: Svc.Sched - - # ---------------------------------------------------------------------- - # Special ports - # ---------------------------------------------------------------------- - - @ Command receive - command recv port cmdIn - - @ Command registration - command reg port cmdRegOut - - @ Command response - command resp port cmdResponseOut - - @ Event - event port eventOut - - @ Parameter get - param get port prmGetOut - - @ Parameter set - param set port prmSetOut - - @ Telemetry - telemetry port tlmOut - - @ Text event - text event port textEventOut - - @ Time get - time get port timeGetOut - - # ---------------------------------------------------------------------- - # Parameters - # ---------------------------------------------------------------------- - - @ The multiplier in the math operation - param FACTOR: F32 default 1.0 id 0 \ - set opcode 10 \ - save opcode 11 - - # ---------------------------------------------------------------------- - # Events - # ---------------------------------------------------------------------- - - @ Factor updated - event FACTOR_UPDATED( - val: F32 @< The factor value - ) \ - severity activity high \ - id 0 \ - format "Factor updated to {f}" \ - throttle 3 - - @ Math operation performed - event OPERATION_PERFORMED( - val: MathOp @< The operation - ) \ - severity activity high \ - id 1 \ - format "{} operation performed" - - @ Event throttle cleared - event THROTTLE_CLEARED \ - severity activity high \ - id 2 \ - format "Event throttle cleared" - - # ---------------------------------------------------------------------- - # Commands - # ---------------------------------------------------------------------- - - @ Clear the event throttle - async command CLEAR_EVENT_THROTTLE \ - opcode 0 - - # ---------------------------------------------------------------------- - # Telemetry - # ---------------------------------------------------------------------- - - @ The operation - telemetry OPERATION: MathOp id 0 - - @ Multiplication factor - telemetry FACTOR: F32 id 1 - - } - -} diff --git a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.hpp b/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.hpp deleted file mode 100644 index c085650274..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/MathReceiver.hpp +++ /dev/null @@ -1,90 +0,0 @@ -// ====================================================================== -// \title MathReceiverImpl.hpp -// \author tcanham, bocchino -// \brief hpp file for MathReceiver component implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#ifndef MathReceiver_HPP -#define MathReceiver_HPP - -#include "Ref/MathReceiver/MathReceiverComponentAc.hpp" - -namespace Ref { - - class MathReceiver : - public MathReceiverComponentBase - { - - public: - - // ---------------------------------------------------------------------- - // Construction, initialization, and destruction - // ---------------------------------------------------------------------- - - //! Construct object MathReceiver - //! - MathReceiver( - const char *const compName //!< The component name - ); - - //! Initialize object MathReceiver - //! - void init( - const NATIVE_INT_TYPE queueDepth, //!< The queue depth - const NATIVE_INT_TYPE instance = 0 //!< The instance number - ); - - //! Destroy object MathReceiver - //! - ~MathReceiver(); - - PRIVATE: - - // ---------------------------------------------------------------------- - // Handler implementations for user-defined typed input ports - // ---------------------------------------------------------------------- - - //! Handler implementation for mathOpIn - //! - void mathOpIn_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - F32 val1, - const MathOp& op, //!< operation argument - F32 val2 - ); - - //! Handler implementation for SchedIn - //! - void schedIn_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - NATIVE_UINT_TYPE context //!< The call order - ); - - PRIVATE: - - // ---------------------------------------------------------------------- - // Command handler implementations - // ---------------------------------------------------------------------- - - //! Implementation for CLEAR_EVENT_THROTTLE command handler - //! Clear the event throttle - void CLEAR_EVENT_THROTTLE_cmdHandler( - const FwOpcodeType opCode, //!< The opcode - const U32 cmdSeq //!< The command sequence number - ); - - void parameterUpdated( - FwPrmIdType id //!< The parameter ID - ); - - }; - -} // end namespace Ref - -#endif diff --git a/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.cpp b/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.cpp deleted file mode 100644 index 1f3de5edb3..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// ====================================================================== -// \title MathReceiver.hpp -// \author tcanham, bocchino -// \brief cpp file for MathReceiver test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#include "STest/Pick/Pick.hpp" -#include "Tester.hpp" - -namespace Ref { - - // ---------------------------------------------------------------------- - // Construction and destruction - // ---------------------------------------------------------------------- - - Tester :: - Tester() : - MathReceiverGTestBase("Tester", MAX_HISTORY_SIZE), - component("MathReceiver") - { - this->initComponents(); - this->connectPorts(); - } - - Tester :: - ~Tester() - { - - } - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - void Tester :: - testAdd() - { - // Set the factor parameter by command - const F32 factor = pickF32Value(); - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - // Do the add operation - this->doMathOp(MathOp::ADD, factor); - } - - void Tester :: - testSub() - { - // Set the factor parameter by loading parameters - const F32 factor = pickF32Value(); - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - this->component.loadParameters(); - // Do the operation - this->doMathOp(MathOp::SUB, factor); - } - - void Tester :: - testMul() - { - // Set the factor parameter by command - const F32 factor = 3.0; - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - // Do the add operation - this->doMathOp(MathOp::MUL, factor); - } - - void Tester :: - testDiv() - { - // Set the factor parameter by loading parameters - const F32 factor = 3.0; - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - this->component.loadParameters(); - // Do the operation - this->doMathOp(MathOp::DIV, factor); - } - - void Tester :: - testThrottle() - { - - // send the number of commands required to throttle the event - // Use the autocoded value so the unit test passes if the - // throttle value is changed - const F32 factor = pickF32Value(); - for ( - U16 cycle = 0; - cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE; - cycle++ - ) { - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - } - - // Event should now be throttled - this->setFactor(factor, ThrottleState::THROTTLED); - - // send the command to clear the throttle - const U32 instance = STest::Pick::any(); - const U32 cmdSeq = STest::Pick::any(); - this->sendCmd_CLEAR_EVENT_THROTTLE(instance, cmdSeq); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - // verify clear event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1); - - // Throttling should be cleared - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - - } - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - void Tester :: - from_mathResultOut_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) - { - this->pushFromPortEntry_mathResultOut(result); - } - - // ---------------------------------------------------------------------- - // Helper methods - // ---------------------------------------------------------------------- - - F32 Tester :: - pickF32Value() - { - const F32 m = 10e6; - return m * (1.0 - 2 * STest::Pick::inUnitInterval()); - } - - void Tester :: - setFactor( - F32 factor, - ThrottleState throttleState - ) - { - // clear history - this->clearHistory(); - // set the parameter - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - const U32 instance = STest::Pick::any(); - const U32 cmdSeq = STest::Pick::any(); - this->paramSend_FACTOR(instance, cmdSeq); - if (throttleState == ThrottleState::NOT_THROTTLED) { - // verify the parameter update notification event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED(0, factor); - } - else { - ASSERT_EVENTS_SIZE(0); - } - } - - F32 Tester :: - computeResult( - F32 val1, - MathOp op, - F32 val2, - F32 factor - ) - { - F32 result = 0; - switch (op.e) { - case MathOp::ADD: - result = val1 + val2; - break; - case MathOp::SUB: - result = val1 - val2; - break; - case MathOp::MUL: - result = val1 * val2; - break; - case MathOp::DIV: - result = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - result *= factor; - return result; - } - - void Tester :: - doMathOp(MathOp op, F32 factor) - { - - // pick values - const F32 val1 = pickF32Value(); - const F32 val2 = pickF32Value(); - - // clear history - this->clearHistory(); - - // invoke operation port with add operation - this->invoke_to_mathOpIn(0, val1, op, val2); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - - // verify the result of the operation was returned - - // check that there was one port invocation - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // check that the port we expected was invoked - ASSERT_from_mathResultOut_SIZE(1); - // check that the component performed the operation correctly - const F32 result = computeResult(val1, op, val2, factor); - ASSERT_from_mathResultOut(0, result); - - // verify events - - // check that there was one event - ASSERT_EVENTS_SIZE(1); - // check that it was the op event - ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1); - // check that the event has the correct argument - ASSERT_EVENTS_OPERATION_PERFORMED(0, op); - - // verify telemetry - - // check that one channel was written - ASSERT_TLM_SIZE(1); - // check that it was the op channel - ASSERT_TLM_OPERATION_SIZE(1); - // check for the correct value of the channel - ASSERT_TLM_OPERATION(0, op); - - } -} // end namespace Ref diff --git a/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.hpp b/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.hpp deleted file mode 100644 index 04b70f36b3..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/test/ut/Tester.hpp +++ /dev/null @@ -1,136 +0,0 @@ -// ====================================================================== -// \title MathReceiver/test/ut/Tester.hpp -// \author tcanham, bocchino -// \brief hpp file for MathReceiver test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#ifndef TESTER_HPP -#define TESTER_HPP - -#include "GTestBase.hpp" -#include "Ref/MathReceiver/MathReceiver.hpp" - -namespace Ref { - - class Tester : - public MathReceiverGTestBase - { - public: - // Maximum size of histories storing events, telemetry, and port outputs - static const NATIVE_INT_TYPE MAX_HISTORY_SIZE = 10; - // Instance ID supplied to the component instance under test - static const NATIVE_INT_TYPE TEST_INSTANCE_ID = 0; - // Queue depth supplied to component instance under test - static const NATIVE_INT_TYPE TEST_INSTANCE_QUEUE_DEPTH = 10; - - private: - - // ---------------------------------------------------------------------- - // Types - // ---------------------------------------------------------------------- - - enum class ThrottleState { - THROTTLED, - NOT_THROTTLED - }; - - public: - - // ---------------------------------------------------------------------- - // Construction and destruction - // ---------------------------------------------------------------------- - - //! Construct object Tester - //! - Tester(); - - //! Destroy object Tester - //! - ~Tester(); - - public: - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - //! To do - //! - void testAdd(); - void testSub(); - void testMul(); - void testDiv(); - void testThrottle(); - - private: - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - //! Handler for from_mathResultOut - //! - void from_mathResultOut_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - F32 result //!< the result of the operation - ); - - private: - - // ---------------------------------------------------------------------- - // Helper methods - // ---------------------------------------------------------------------- - - //! Pick an F32 value - static F32 pickF32Value(); - - //! Set the factor parameter - void setFactor( - F32 factor, //!< The parameter value - ThrottleState throttleState //!< The throttle state - ); - - //! Compute a result - F32 computeResult( - F32 val1, - MathOp op, - F32 val2, - F32 factor - ); - - //! Do a math operation - //! Factor parameter must be set - void doMathOp( - MathOp op, - F32 factor - ); - - //! Connect ports - //! - void connectPorts(); - - //! Initialize components - //! - void initComponents(); - - private: - - // ---------------------------------------------------------------------- - // Variables - // ---------------------------------------------------------------------- - - //! The component under test - //! - MathReceiver component; - - }; - -} // end namespace Ref - -#endif diff --git a/docs/Tutorials/MathComponent/MathReceiver/test/ut/main.cpp b/docs/Tutorials/MathComponent/MathReceiver/test/ut/main.cpp deleted file mode 100644 index 8b840a94fa..0000000000 --- a/docs/Tutorials/MathComponent/MathReceiver/test/ut/main.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// ---------------------------------------------------------------------- -// Main.cpp -// ---------------------------------------------------------------------- - -#include "STest/Random/Random.hpp" -#include "Tester.hpp" - -TEST(Nominal, Add) { - Ref::Tester tester; - tester.testAdd(); -} - -TEST(Nominal, Sub) { - Ref::Tester tester; - tester.testSub(); -} - -TEST(Nominal, Mul) { - Ref::Tester tester; - tester.testMul(); -} - -TEST(Nominal, Div) { - Ref::Tester tester; - tester.testDiv(); -} - -TEST(Nominal, Throttle) { - Ref::Tester tester; - tester.testThrottle(); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - STest::Random::seed(); - return RUN_ALL_TESTS(); -} diff --git a/docs/Tutorials/MathComponent/MathSender/CMakeLists.txt b/docs/Tutorials/MathComponent/MathSender/CMakeLists.txt deleted file mode 100644 index 5f3ff699f8..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Register the standard build -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.cpp" - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" -) -register_fprime_module() - -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/main.cpp" -) -set(UT_MOD_DEPS STest) -register_fprime_ut() diff --git a/docs/Tutorials/MathComponent/MathSender/MathSender.cpp b/docs/Tutorials/MathComponent/MathSender/MathSender.cpp deleted file mode 100644 index ad839ed6d2..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/MathSender.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// ====================================================================== -// \title MathSender.cpp -// \author tcanham, bocchino -// \brief cpp file for MathSender component implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - - -#include -#include - -namespace Ref { - - // ---------------------------------------------------------------------- - // Construction, initialization, and destruction - // ---------------------------------------------------------------------- - - MathSender :: - MathSender( - const char *const compName - ) : MathSenderComponentBase(compName) - { - - } - - void MathSender :: - init( - const NATIVE_INT_TYPE queueDepth, - const NATIVE_INT_TYPE instance - ) - { - MathSenderComponentBase::init(queueDepth, instance); - } - - MathSender :: - ~MathSender() - { - - } - - // ---------------------------------------------------------------------- - // Handler implementations for user-defined typed input ports - // ---------------------------------------------------------------------- - - void MathSender :: - mathResultIn_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) - { - this->tlmWrite_RESULT(result); - this->log_ACTIVITY_HI_RESULT(result); - } - - // ---------------------------------------------------------------------- - // Command handler implementations - // ---------------------------------------------------------------------- - - void MathSender :: - DO_MATH_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq, - F32 val1, - MathOp op, - F32 val2 - ) - { - this->tlmWrite_VAL1(val1); - this->tlmWrite_OP(op); - this->tlmWrite_VAL2(val2); - this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2); - this->mathOpOut_out(0, val1, op, val2); - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); - } - -} // end namespace Ref diff --git a/docs/Tutorials/MathComponent/MathSender/MathSender.fpp b/docs/Tutorials/MathComponent/MathSender/MathSender.fpp deleted file mode 100644 index 869f69f4c1..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/MathSender.fpp +++ /dev/null @@ -1,90 +0,0 @@ -module Ref { - - @ Component for sending a math operation - active component MathSender { - - # ---------------------------------------------------------------------- - # General ports - # ---------------------------------------------------------------------- - - @ Port for sending the operation request - output port mathOpOut: MathOp - - @ Port for receiving the result - async input port mathResultIn: MathResult - - # ---------------------------------------------------------------------- - # Special ports - # ---------------------------------------------------------------------- - - @ Command receive port - command recv port cmdIn - - @ Command registration port - command reg port cmdRegOut - - @ Command response port - command resp port cmdResponseOut - - @ Event port - event port eventOut - - @ Telemetry port - telemetry port tlmOut - - @ Text event port - text event port textEventOut - - @ Time get port - time get port timeGetOut - - # ---------------------------------------------------------------------- - # Commands - # ---------------------------------------------------------------------- - - @ Do a math operation - async command DO_MATH( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) - - # ---------------------------------------------------------------------- - # Events - # ---------------------------------------------------------------------- - - @ Math command received - event COMMAND_RECV( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) \ - severity activity low \ - format "Math command received: {f} {} {f}" - - @ Received math result - event RESULT( - result: F32 @< The math result - ) \ - severity activity high \ - format "Math result is {f}" - - # ---------------------------------------------------------------------- - # Telemetry - # ---------------------------------------------------------------------- - - @ The first value - telemetry VAL1: F32 - - @ The operation - telemetry OP: MathOp - - @ The second value - telemetry VAL2: F32 - - @ The result - telemetry RESULT: F32 - - } - -} diff --git a/docs/Tutorials/MathComponent/MathSender/MathSender.hpp b/docs/Tutorials/MathComponent/MathSender/MathSender.hpp deleted file mode 100644 index 874818e0b5..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/MathSender.hpp +++ /dev/null @@ -1,80 +0,0 @@ -// ====================================================================== -// \title MathSenderImpl.hpp -// \author tcanham, bocchino -// \brief hpp file for MathSender component implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#ifndef Ref_MathSender_HPP -#define Ref_MathSender_HPP - -#include "Ref/MathSender/MathSenderComponentAc.hpp" - -namespace Ref { - - class MathSender : - public MathSenderComponentBase - { - - public: - - // ---------------------------------------------------------------------- - // Construction, initialization, and destruction - // ---------------------------------------------------------------------- - - //! Construct object MathSender - //! - MathSender( - const char *const compName //!< The component name - ); - - //! Initialize object MathSender - //! - void init( - const NATIVE_INT_TYPE queueDepth, //!< The queue depth - const NATIVE_INT_TYPE instance = 0 //!< The instance number - ); - - //! Destroy object MathSender - //! - ~MathSender(); - - PRIVATE: - - // ---------------------------------------------------------------------- - // Handler implementations for user-defined typed input ports - // ---------------------------------------------------------------------- - - //! Handler implementation for mathResultIn - //! - void mathResultIn_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - F32 result //!< the result of the operation - ); - - PRIVATE: - - // ---------------------------------------------------------------------- - // Command handler implementations - // ---------------------------------------------------------------------- - - //! Implementation for DO_MATH command handler - //! Do a math operation - void DO_MATH_cmdHandler( - const FwOpcodeType opCode, //!< The opcode - const U32 cmdSeq, //!< The command sequence number - F32 val1, //!< The first value - MathOp operation, //!< The operation to perform - F32 val2 //!< The second value - ); - - }; - -} // end namespace Ref - -#endif diff --git a/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.cpp b/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.cpp deleted file mode 100644 index be15213201..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// ====================================================================== -// \title MathSender.hpp -// \author tcanham, bocchino -// \brief cpp file for MathSender test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#include "Tester.hpp" -#include "STest/Pick/Pick.hpp" - -#define INSTANCE 0 -#define MAX_HISTORY_SIZE 10 -#define QUEUE_DEPTH 10 - -namespace Ref { - - // ---------------------------------------------------------------------- - // Construction and destruction - // ---------------------------------------------------------------------- - - Tester :: - Tester() : - MathSenderGTestBase("Tester", MAX_HISTORY_SIZE), - component("MathSender") - { - this->initComponents(); - this->connectPorts(); - } - - Tester :: - ~Tester() - { - - } - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - F32 Tester :: - pickF32Value() - { - const F32 m = 10e6; - return m * (1.0 - 2 * STest::Pick::inUnitInterval()); - } - - void Tester :: - testAddCommand() - { - this->testDoMath(MathOp::ADD); - } - - void Tester :: - testSubCommand() - { - this->testDoMath(MathOp::SUB); - } - - void Tester :: - testMulCommand() - { - this->testDoMath(MathOp::MUL); - } - - void Tester :: - testDivCommand() - { - this->testDoMath(MathOp::DIV); - } - - void Tester :: - testResult() - { - // Generate an expected result - const F32 result = pickF32Value(); - // reset all telemetry and port history - this->clearHistory(); - // call result port with result - this->invoke_to_mathResultIn(0, result); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - // verify one telemetry value was written - ASSERT_TLM_SIZE(1); - // verify the desired telemetry channel was sent once - ASSERT_TLM_RESULT_SIZE(1); - // verify the values of the telemetry channel - ASSERT_TLM_RESULT(0, result); - // verify one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_RESULT_SIZE(1); - // verify the expect value of the event - ASSERT_EVENTS_RESULT(0, result); - } - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - void Tester :: - from_mathOpOut_handler( - const NATIVE_INT_TYPE portNum, - F32 val1, - const MathOp& op, - F32 val2 - ) - { - this->pushFromPortEntry_mathOpOut(val1, op, val2); - } - - // ---------------------------------------------------------------------- - // Helper methods - // ---------------------------------------------------------------------- - - void Tester :: - testDoMath(MathOp op) - { - - // Pick values - - const F32 val1 = pickF32Value(); - const F32 val2 = pickF32Value(); - - // Send the command - - // pick a command sequence number - const U32 cmdSeq = STest::Pick::any(); - // send DO_MATH command - this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - - // Verify command receipt and response - - // verify command response was sent - ASSERT_CMD_RESPONSE_SIZE(1); - // verify the command response was correct as expected - ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK); - - // Verify operation request on mathOpOut - - // verify that one output port was invoked overall - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // verify that the math operation port was invoked once - ASSERT_from_mathOpOut_SIZE(1); - // verify the arguments of the operation port - ASSERT_from_mathOpOut(0, val1, op, val2); - - // Verify telemetry - - // verify that 3 channels were written - ASSERT_TLM_SIZE(3); - // verify that the desired telemetry values were sent once - ASSERT_TLM_VAL1_SIZE(1); - ASSERT_TLM_VAL2_SIZE(1); - ASSERT_TLM_OP_SIZE(1); - // verify that the correct telemetry values were sent - ASSERT_TLM_VAL1(0, val1); - ASSERT_TLM_VAL2(0, val2); - ASSERT_TLM_OP(0, op); - - // Verify event reports - - // verify that one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_COMMAND_RECV_SIZE(1); - // verify the correct event arguments were sent - ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2); - - } -} // end namespace Ref diff --git a/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.hpp b/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.hpp deleted file mode 100644 index 93bf2fd724..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/test/ut/Tester.hpp +++ /dev/null @@ -1,115 +0,0 @@ -// ====================================================================== -// \title MathSender/test/ut/Tester.hpp -// \author tcanham, bocchino -// \brief hpp file for MathSender test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// -// ====================================================================== - -#ifndef TESTER_HPP -#define TESTER_HPP - -#include "GTestBase.hpp" -#include "Ref/MathSender/MathSender.hpp" - -namespace Ref { - - class Tester : - public MathSenderGTestBase - { - - // ---------------------------------------------------------------------- - // Construction and destruction - // ---------------------------------------------------------------------- - - public: - // Maximum size of histories storing events, telemetry, and port outputs - static const NATIVE_INT_TYPE MAX_HISTORY_SIZE = 10; - // Instance ID supplied to the component instance under test - static const NATIVE_INT_TYPE TEST_INSTANCE_ID = 0; - // Queue depth supplied to component instance under test - static const NATIVE_INT_TYPE TEST_INSTANCE_QUEUE_DEPTH = 10; - - //! Construct object Tester - //! - Tester(); - - //! Destroy object Tester - //! - ~Tester(); - - public: - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - //! Test an ADD command - void testAddCommand(); - - //! Test a SUB command - void testSubCommand(); - - //! Test a MUL command - void testMulCommand(); - - //! Test a DIV command - void testDivCommand(); - - //! Test receipt of a result - void testResult(); - - private: - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - //! Handler for from_mathOut - //! - void from_mathOpOut_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - F32 val1, //!< First operand - const MathOp& op, //!< operation - F32 val2 //!< Second operand - ); - - private: - - // ---------------------------------------------------------------------- - // Helper methods - // ---------------------------------------------------------------------- - - //! Pick a random value - static F32 pickF32Value(); - - //! Test a DO_MATH command - void testDoMath(MathOp op); - - //! Connect ports - //! - void connectPorts(); - - //! Initialize components - //! - void initComponents(); - - private: - - // ---------------------------------------------------------------------- - // Variables - // ---------------------------------------------------------------------- - - //! The component under test - //! - MathSender component; - - }; - -} // end namespace Ref - -#endif diff --git a/docs/Tutorials/MathComponent/MathSender/test/ut/main.cpp b/docs/Tutorials/MathComponent/MathSender/test/ut/main.cpp deleted file mode 100644 index 7fe0415d25..0000000000 --- a/docs/Tutorials/MathComponent/MathSender/test/ut/main.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// ---------------------------------------------------------------------- -// Main.cpp -// ---------------------------------------------------------------------- - -#include "STest/Random/Random.hpp" -#include "Tester.hpp" - -TEST(Nominal, AddCommand) { - Ref::Tester tester; - tester.testAddCommand(); -} - -TEST(Nominal, SubCommand) { - Ref::Tester tester; - tester.testSubCommand(); -} - -TEST(Nominal, MulCommand) { - Ref::Tester tester; - tester.testMulCommand(); -} - -TEST(Nominal, DivCommand) { - Ref::Tester tester; - tester.testDivCommand(); -} - -TEST(Nominal, Result) { - Ref::Tester tester; - tester.testResult(); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - STest::Random::seed(); - return RUN_ALL_TESTS(); -} diff --git a/docs/Tutorials/MathComponent/MathTypes/CMakeLists.txt b/docs/Tutorials/MathComponent/MathTypes/CMakeLists.txt deleted file mode 100644 index 2fc3269205..0000000000 --- a/docs/Tutorials/MathComponent/MathTypes/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp" -) - -register_fprime_module() diff --git a/docs/Tutorials/MathComponent/MathTypes/MathTypes.fpp b/docs/Tutorials/MathComponent/MathTypes/MathTypes.fpp deleted file mode 100644 index 32085c868a..0000000000 --- a/docs/Tutorials/MathComponent/MathTypes/MathTypes.fpp +++ /dev/null @@ -1,11 +0,0 @@ -module Ref { - - @ A math operation - enum MathOp { - ADD @< Addition - SUB @< Subtraction - MUL @< Multiplication - DIV @< Division - } - -} diff --git a/docs/Tutorials/MathComponent/README.md b/docs/Tutorials/MathComponent/README.md new file mode 100644 index 0000000000..9abda83483 --- /dev/null +++ b/docs/Tutorials/MathComponent/README.md @@ -0,0 +1,3 @@ +## Welcome + +The MathComponent tutorial has been reworked and moved to [fprime-community/fprime-tutorial-math-component](https://fprime-community.github.io/fprime-tutorial-math-component/) \ No newline at end of file diff --git a/docs/Tutorials/MathComponent/Top/instances.fpp b/docs/Tutorials/MathComponent/Top/instances.fpp deleted file mode 100644 index efd36a3ceb..0000000000 --- a/docs/Tutorials/MathComponent/Top/instances.fpp +++ /dev/null @@ -1,369 +0,0 @@ -module Ref { - - # ---------------------------------------------------------------------- - # Defaults - # ---------------------------------------------------------------------- - - module Default { - - constant queueSize = 10 - - constant stackSize = 16 * 1024 - - } - - # ---------------------------------------------------------------------- - # Active component instances - # ---------------------------------------------------------------------- - - instance blockDrv: Drv.BlockDriver base id 0x0100 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 140 \ - { - - phase Fpp.ToCpp.Phases.instances """ - // Declared in RefTopologyDefs.cpp - """ - - } - - instance rateGroup1Comp: Svc.ActiveRateGroup base id 0x0200 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 120 \ - { - - phase Fpp.ToCpp.Phases.configObjects """ - NATIVE_INT_TYPE context[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - rateGroup1Comp.configure( - ConfigObjects::rateGroup1Comp::context, - FW_NUM_ARRAY_ELEMENTS(ConfigObjects::rateGroup1Comp::context) - ); - """ - - } - - instance rateGroup2Comp: Svc.ActiveRateGroup base id 0x0300 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 119 \ - { - - phase Fpp.ToCpp.Phases.configObjects """ - NATIVE_INT_TYPE context[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - rateGroup2Comp.configure( - ConfigObjects::rateGroup2Comp::context, - FW_NUM_ARRAY_ELEMENTS(ConfigObjects::rateGroup2Comp::context) - ); - """ - - } - - instance rateGroup3Comp: Svc.ActiveRateGroup base id 0x0400 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 118 \ - { - - phase Fpp.ToCpp.Phases.configObjects """ - NATIVE_INT_TYPE context[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - rateGroup3Comp.configure( - ConfigObjects::rateGroup3Comp::context, - FW_NUM_ARRAY_ELEMENTS(ConfigObjects::rateGroup3Comp::context) - ); - """ - - } - - instance cmdDisp: Svc.CommandDispatcher base id 0x0500 \ - queue size 20 \ - stack size Default.stackSize \ - priority 101 - - instance cmdSeq: Svc.CmdSequencer base id 0x0600 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 100 \ - { - - phase Fpp.ToCpp.Phases.configConstants """ - enum { - BUFFER_SIZE = 5*1024 - }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - cmdSeq.allocateBuffer( - 0, - Allocation::mallocator, - ConfigConstants::cmdSeq::BUFFER_SIZE - ); - """ - - phase Fpp.ToCpp.Phases.tearDownComponents """ - cmdSeq.deallocateBuffer(Allocation::mallocator); - """ - - } - - instance fileDownlink: Svc.FileDownlink base id 0x0700 \ - queue size 30 \ - stack size Default.stackSize \ - priority 100 \ - { - - phase Fpp.ToCpp.Phases.configConstants """ - enum { - TIMEOUT = 1000, - COOLDOWN = 1000, - CYCLE_TIME = 1000, - FILE_QUEUE_DEPTH = 10 - }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - fileDownlink.configure( - ConfigConstants::fileDownlink::TIMEOUT, - ConfigConstants::fileDownlink::COOLDOWN, - ConfigConstants::fileDownlink::CYCLE_TIME, - ConfigConstants::fileDownlink::FILE_QUEUE_DEPTH - ); - """ - - } - - instance fileManager: Svc.FileManager base id 0x0800 \ - queue size 30 \ - stack size Default.stackSize \ - priority 100 - - instance fileUplink: Svc.FileUplink base id 0x0900 \ - queue size 30 \ - stack size Default.stackSize \ - priority 100 - - instance pingRcvr: Ref.PingReceiver base id 0x0A00 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 100 - - instance eventLogger: Svc.ActiveLogger base id 0x0B00 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 98 - - instance chanTlm: Svc.TlmChan base id 0x0C00 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 97 - - instance prmDb: Svc.PrmDb base id 0x0D00 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 96 \ - { - - phase Fpp.ToCpp.Phases.instances """ - Svc::PrmDb prmDb(FW_OPTIONAL_NAME("prmDb"), "PrmDb.dat"); - """ - - phase Fpp.ToCpp.Phases.readParameters """ - prmDb.readParamFile(); - """ - - } - - instance mathSender: Ref.MathSender base id 0xE00 \ - queue size Default.queueSize \ - stack size Default.stackSize \ - priority 100 - - # ---------------------------------------------------------------------- - # Queued component instances - # ---------------------------------------------------------------------- - - instance $health: Svc.Health base id 0x2000 \ - queue size 25 \ - { - - phase Fpp.ToCpp.Phases.configConstants """ - enum { - WATCHDOG_CODE = 0x123 - }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - health.setPingEntries( - ConfigObjects::health::pingEntries, - FW_NUM_ARRAY_ELEMENTS(ConfigObjects::health::pingEntries), - ConfigConstants::health::WATCHDOG_CODE - ); - """ - - } - - instance SG1: Ref.SignalGen base id 0x2100 \ - queue size Default.queueSize - - instance SG2: Ref.SignalGen base id 0x2200 \ - queue size Default.queueSize - - instance SG3: Ref.SignalGen base id 0x2300 \ - queue size Default.queueSize - - instance SG4: Ref.SignalGen base id 0x2400 \ - queue size Default.queueSize - - instance SG5: Ref.SignalGen base id 0x2500 \ - queue size Default.queueSize - - instance sendBuffComp: Ref.SendBuff base id 0x2600 \ - queue size Default.queueSize - - instance mathReceiver: Ref.MathReceiver base id 0x2700 \ - queue size Default.queueSize - - # ---------------------------------------------------------------------- - # Passive component instances - # ---------------------------------------------------------------------- - - @ Communications driver. May be swapped with other comm drivers like UART - @ Note: Here we have TCP reliable uplink and UDP (low latency) downlink - instance comm: Drv.ByteStreamDriverModel base id 0x4000 \ - at "../../Drv/TcpClient/TcpClient.hpp" \ - { - - phase Fpp.ToCpp.Phases.instances """ - Drv::TcpClient comm(FW_OPTIONAL_NAME("comm")); - """ - - phase Fpp.ToCpp.Phases.configConstants """ - enum { - PRIORITY = 100, - STACK_SIZE = Default::stackSize - }; - """ - - phase Fpp.ToCpp.Phases.startTasks """ - // Initialize socket server if and only if there is a valid specification - if (state.hostName != nullptr && state.portNumber != 0) { - Os::TaskString name("ReceiveTask"); - // Uplink is configured for receive so a socket task is started - comm.configure(state.hostName, state.portNumber); - comm.startSocketTask( - name, - ConfigConstants::comm::PRIORITY, - ConfigConstants::comm::STACK_SIZE - ); - } - """ - - phase Fpp.ToCpp.Phases.freeThreads """ - comm.stopSocketTask(); - (void) comm.joinSocketTask(nullptr); - """ - - } - - instance downlink: Svc.Framer base id 0x4100 { - - phase Fpp.ToCpp.Phases.configObjects """ - Svc::FprimeFraming framing; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - downlink.setup(ConfigObjects::downlink::framing); - """ - - } - - instance fatalAdapter: Svc.AssertFatalAdapter base id 0x4200 - - instance fatalHandler: Svc.FatalHandler base id 0x4300 - - instance fileUplinkBufferManager: Svc.BufferManager base id 0x4400 { - - phase Fpp.ToCpp.Phases.configConstants """ - enum { - STORE_SIZE = 3000, - QUEUE_SIZE = 30, - MGR_ID = 200 - }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - Svc::BufferManager::BufferBins upBuffMgrBins; - memset(&upBuffMgrBins, 0, sizeof(upBuffMgrBins)); - { - using namespace ConfigConstants::fileUplinkBufferManager; - upBuffMgrBins.bins[0].bufferSize = STORE_SIZE; - upBuffMgrBins.bins[0].numBuffers = QUEUE_SIZE; - fileUplinkBufferManager.setup( - MGR_ID, - 0, - Allocation::mallocator, - upBuffMgrBins - ); - } - """ - - phase Fpp.ToCpp.Phases.tearDownComponents """ - fileUplinkBufferManager.cleanup(); - """ - - } - - instance linuxTime: Svc.Time base id 0x4500 \ - at "../../Svc/LinuxTime/LinuxTime.hpp" \ - { - - phase Fpp.ToCpp.Phases.instances """ - Svc::LinuxTime linuxTime(FW_OPTIONAL_NAME("linuxTime")); - """ - - } - - instance rateGroupDriverComp: Svc.RateGroupDriver base id 0x4600 { - - phase Fpp.ToCpp.Phases.configObjects """ - NATIVE_INT_TYPE rgDivs[Svc::RateGroupDriver::DIVIDER_SIZE] = { 1, 2, 4 }; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - rateGroupDriverComp.configure( - ConfigObjects::rateGroupDriverComp::rgDivs, - FW_NUM_ARRAY_ELEMENTS(ConfigObjects::rateGroupDriverComp::rgDivs) - ); - """ - - } - - instance recvBuffComp: Ref.RecvBuff base id 0x4700 - - instance staticMemory: Svc.StaticMemory base id 0x4800 - - instance textLogger: Svc.PassiveTextLogger base id 0x4900 - - instance uplink: Svc.Deframer base id 0x4A00 { - - phase Fpp.ToCpp.Phases.configObjects """ - Svc::FprimeDeframing deframing; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - uplink.setup(ConfigObjects::uplink::deframing); - """ - - } - -} diff --git a/docs/Tutorials/MathComponent/Top/topology.fpp b/docs/Tutorials/MathComponent/Top/topology.fpp deleted file mode 100644 index c6ab2274aa..0000000000 --- a/docs/Tutorials/MathComponent/Top/topology.fpp +++ /dev/null @@ -1,160 +0,0 @@ -module Ref { - - # ---------------------------------------------------------------------- - # Symbolic constants for port numbers - # ---------------------------------------------------------------------- - - enum Ports_RateGroups { - rateGroup1 - rateGroup2 - rateGroup3 - } - - enum Ports_StaticMemory { - downlink - uplink - } - - topology Ref { - - # ---------------------------------------------------------------------- - # Instances used in the topology - # ---------------------------------------------------------------------- - - instance $health - instance SG1 - instance SG2 - instance SG3 - instance SG4 - instance SG5 - instance blockDrv - instance chanTlm - instance cmdDisp - instance cmdSeq - instance comm - instance downlink - instance eventLogger - instance fatalAdapter - instance fatalHandler - instance fileDownlink - instance fileManager - instance fileUplink - instance fileUplinkBufferManager - instance linuxTime - instance mathReceiver - instance mathSender - instance pingRcvr - instance prmDb - instance rateGroup1Comp - instance rateGroup2Comp - instance rateGroup3Comp - instance rateGroupDriverComp - instance recvBuffComp - instance sendBuffComp - instance staticMemory - instance textLogger - instance uplink - - # ---------------------------------------------------------------------- - # Pattern graph specifiers - # ---------------------------------------------------------------------- - - command connections instance cmdDisp - - event connections instance eventLogger - - param connections instance prmDb - - telemetry connections instance chanTlm - - text event connections instance textLogger - - time connections instance linuxTime - - health connections instance $health - - # ---------------------------------------------------------------------- - # Direct graph specifiers - # ---------------------------------------------------------------------- - - connections Downlink { - - chanTlm.PktSend -> downlink.comIn - eventLogger.PktSend -> downlink.comIn - fileDownlink.bufferSendOut -> downlink.bufferIn - - downlink.framedAllocate -> staticMemory.bufferAllocate[Ports_StaticMemory.downlink] - downlink.framedOut -> comm.send - downlink.bufferDeallocate -> fileDownlink.bufferReturn - - comm.deallocate -> staticMemory.bufferDeallocate[Ports_StaticMemory.downlink] - - } - - connections FaultProtection { - eventLogger.FatalAnnounce -> fatalHandler.FatalReceive - } - - connections RateGroups { - - # Block driver - blockDrv.CycleOut -> rateGroupDriverComp.CycleIn - - # Rate group 1 - rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup1] -> rateGroup1Comp.CycleIn - rateGroup1Comp.RateGroupMemberOut[0] -> SG1.schedIn - rateGroup1Comp.RateGroupMemberOut[1] -> SG2.schedIn - rateGroup1Comp.RateGroupMemberOut[2] -> chanTlm.Run - rateGroup1Comp.RateGroupMemberOut[3] -> fileDownlink.Run - rateGroup1Comp.RateGroupMemberOut[4] -> mathReceiver.schedIn - - # Rate group 2 - rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup2] -> rateGroup2Comp.CycleIn - rateGroup2Comp.RateGroupMemberOut[0] -> cmdSeq.schedIn - rateGroup2Comp.RateGroupMemberOut[1] -> sendBuffComp.SchedIn - rateGroup2Comp.RateGroupMemberOut[2] -> SG3.schedIn - rateGroup2Comp.RateGroupMemberOut[3] -> SG4.schedIn - - # Rate group 3 - rateGroupDriverComp.CycleOut[Ports_RateGroups.rateGroup3] -> rateGroup3Comp.CycleIn - rateGroup3Comp.RateGroupMemberOut[0] -> $health.Run - rateGroup3Comp.RateGroupMemberOut[1] -> SG5.schedIn - rateGroup3Comp.RateGroupMemberOut[2] -> blockDrv.Sched - rateGroup3Comp.RateGroupMemberOut[3] -> fileUplinkBufferManager.schedIn - - } - - connections Ref { - sendBuffComp.Data -> blockDrv.BufferIn - blockDrv.BufferOut -> recvBuffComp.Data - } - - connections Sequencer { - cmdSeq.comCmdOut -> cmdDisp.seqCmdBuff - cmdDisp.seqCmdStatus -> cmdSeq.cmdResponseIn - } - - connections Uplink { - - comm.allocate -> staticMemory.bufferAllocate[Ports_StaticMemory.uplink] - comm.$recv -> uplink.framedIn - uplink.framedDeallocate -> staticMemory.bufferDeallocate[Ports_StaticMemory.uplink] - - uplink.comOut -> cmdDisp.seqCmdBuff - cmdDisp.seqCmdStatus -> uplink.cmdResponseIn - - uplink.bufferAllocate -> fileUplinkBufferManager.bufferGetCallee - uplink.bufferOut -> fileUplink.bufferSendIn - uplink.bufferDeallocate -> fileUplinkBufferManager.bufferSendIn - fileUplink.bufferSendOut -> fileUplinkBufferManager.bufferSendIn - - } - - connections Math { - mathSender.mathOpOut -> mathReceiver.mathOpIn - mathReceiver.mathResultOut -> mathSender.mathResultIn - } - - } - -} diff --git a/docs/Tutorials/MathComponent/Tutorial.md b/docs/Tutorials/MathComponent/Tutorial.md deleted file mode 100644 index f254ed2fce..0000000000 --- a/docs/Tutorials/MathComponent/Tutorial.md +++ /dev/null @@ -1,2320 +0,0 @@ -# F' Math Component Tutorial - -## Table of Contents - -* 1. Introduction -* 2. The MathOp Type - * 2.1. Construct the FPP Model - * 2.2. Add the Model to the Project - * 2.3. Build the Model - * 2.4. Reference Implementation -* 3. The MathOp and MathResult Ports - * 3.1. Construct the FPP Model - * 3.2. Add the Model to the Project - * 3.3. Build the Model - * 3.4. Reference Implementation -* 4. The MathSender Component - * 4.1. Construct the FPP Model - * 4.2. Add the Model to the Project - * 4.3. Build the Stub Implementation - * 4.4. Complete the Implementation - * 4.5. Write and Run Unit Tests - * 4.5.1. Set Up the Unit Test Environment - * 4.5.2. Write and Run One Test - * 4.5.3. Write and Run More Tests - * 4.5.4. Exercise: Random Testing - * 4.6. Reference Implementation -* 5. The MathReceiver Component - * 5.1. Construct the FPP Model - * 5.2. Add the Model to the Project - * 5.3. Build the Stub Implementation - * 5.4. Complete the Implementation - * 5.5. Write and Run Unit Tests - * 5.5.1. Set up the Unit Test Environment - * 5.5.2. Add Helper Code - * 5.5.3. Write and Run Tests - * 5.6. Reference Implementation - * 5.7. Exercises - * 5.7.1. Adding Telemetry - * 5.7.2. Error Handling -* 6. Updating the Ref Deployment - * 6.1. Defining the Component Instances - * 6.2. Updating the Topology - * 6.3. Building the Ref Deployment - * 6.4. Visualizing the Ref Topology - * 6.5. Reference Implementation -* 7. Running the Ref Deployment - * 7.1. Sending a Command - * 7.2. Checking Events - * 7.3. Checking Telemetry - * 7.4. Setting Parameters - * 7.5. Saving Parameters - * 7.6. GDS Logs -* 8. Conclusion - - -## 1. Introduction - -This tutorial shows how to develop, test, and deploy a simple topology -consisting of two components: - -1. `MathSender`: A component that receives commands and forwards work to - `MathReceiver`. - -1. `MathReceiver`: A component that carries out arithmetic operations and - returns the results to `MathSender`. - -See the diagram below. - - -![A simple topology for arithmetic computation](png/top.png) - -**What is covered:** The tutorial covers the following concepts: - -1. Using the [FPP modeling language](https://fprime-community.github.io/fpp) to - specify the types and ports used by the components. - -1. Using the F Prime build system to build the types and ports. - -1. Developing the `MathSender` component: Specifying the component, building - the component, completing the C++ component implementation, and writing - component unit tests. - -1. Developing the `MathReceiver` component. - -1. Adding the new components and connections to the F Prime `Ref` application. - -1. Using the F Prime Ground Data System (GDS) to run the updated `Ref` - application. - -**Prerequisites:** This tutorial assumes the following: - -1. Basic knowledge of Unix: How to navigate in a shell and execute programs. - -1. Basic knowledge of git: How to create a branch. - -1. Basic knowledge of C++, including class declarations, inheritance, -and virtual functions. - -If you have not yet installed F Prime on your system, do so now. -Follow the installation guide at `INSTALL.md` -in the [F Prime git repository](https://github.com/nasa/fprime). -You may also wish to work through the Getting Started tutorial at -`docs/GettingStarted/Tutorial.md`. - -**Version control:** -Working on this tutorial will modify some files under version control -in the F Prime git repository. -Therefore it is a good idea to do this work on a new branch. -For example: - -```bash -git checkout -b math-tutorial -``` - -If you wish, you can save your work by committing to this branch. - - -## 2. The MathOp Type - -In F Prime, a **type definition** defines a kind of data that you can pass -between components or use in commands and telemetry. - -For this tutorial, we need one type definition. -It defines an enumeration called `MathOp`, which -represents a mathematical operation. - -We will add the specification for the `MathOp` type to the -`Ref` topology. -We will do this in three stages: - -1. Construct the FPP model. - -1. Add the model to the project. - -1. Build the model. - - -### 2.1. Construct the FPP Model - -**Create the MathTypes directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository and run `mkdir MathTypes`. -This step creates a new directory `Ref/MathTypes`. -This directory will contain our new type. - -**Create the FPP model file:** -Now go into the directory `Ref/MathTypes`. -In that directory, create a file `MathTypes.fpp` with the following contents: - -```fpp -module Ref { - - @ A math operation - enum MathOp { - ADD @< Addition - SUB @< Subtraction - MUL @< Multiplication - DIV @< Division - } - -} -``` - -You can do this by typing, or by copy-paste. - -This file defines an enumeration or **enum** with enumerated constants `ADD`, -`SUB`, `MUL`, and `DIV`. -These four constants represent the operations of addition, subtraction, -multiplication, and division. -The enum also defines a type `MathOp`; the enumerated constants are the values -of this type. -For more information on enums, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Enums). - -The enum `MathTypes` resides in an FPP module `Ref`. - -An FPP module is like a C++ namespace: it encloses several definitions, each of -which is qualified with the name of the module. -For more information on FPP modules, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Modules). - -The text following a symbol `@` or `@<` is called an **annotation**. -These annotations are carried through the parsing and become comments in the -generated code. -For more information, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Writing-Comments-and-Annotations). - - - -### 2.2. Add the Model to the Project - -**Create Ref/MathTypes/CMakeLists.txt:** -Create a file `Ref/MathTypes/CMakeLists.txt` with the following contents: - -```cmake -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp" -) - -register_fprime_module() -``` - -This code will tell the build system how to build the FPP model. - -**Update Ref/CMakeLists.txt:** -Now we need to add the new directory to the `Ref` project. -To do that, open the file `Ref/CMakeLists.txt`. -This file should already exist; it was put there by the developers -of the `Ref` topology. -In this file, you should see several lines starting with `add_fprime_subdirectory`. -Immediately after the last of those lines, add the following new line: - -```cmake -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathTypes/") -``` - - - -### 2.3. Build the Model - -**Run the build:** -Do the following: - -1. Go to the directory `Ref/MathTypes`. - -1. If you have not already run `fprime-util generate`, then do so now. - -1. Run the command `fprime-util build`. - -The output should indicate that the model built without any errors. -If not, try to identify and correct what is wrong, -either by deciphering the error output, or by going over the steps again. -If you get stuck, you can look at the -reference implementation. - -**Inspect the generated code:** -Now go to the directory `Ref/build-fprime-automatic-native/Ref/MathTypes` -(you may want to use `pushd`, or do this in a separate shell, -so you don't lose your current working directory). -The directory `build-fprime-automatic-native` is where all the -generated code lives for the "automatic native" build of the `Ref` -project. -Within that directory is a directory tree that mirrors the project -structure. -In particular, `Ref/build-fprime-automatic-native/Ref/MathTypes` -contains the generated code for `Ref/MathTypes`. - -Run `ls`. -You should see something like this: - -```bash -CMakeFiles MathOpEnumAc.cpp MathOpEnumAi.xml.prev cmake_install.cmake -Makefile MathOpEnumAc.hpp autocoder -``` - -The files `MathOpEnumAc.hpp` and -`MathOpEnumAc.cpp` are the auto-generated C++ files -corresponding to the `MathOp` enum. -You may wish to study the file `MathOpEnumAc.hpp`. -This file gives the interface to the C++ class `Ref::MathOp`. -All enum types have a similar auto-generated class -interface. - - - -### 2.4. Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathTypes`. -To build this implementation from a clean repository, -do the following: - -1. Go to the `Ref` directory. - -1. Run `cp -R ../docs/Tutorials/MathComponent/MathTypes .` - -1. Update `Ref/CMakeLists.txt` as stated above. - -1. Follow the steps for building the model. - -If you have modified the repo, revise the steps accordingly. -For example, switch git branches, use `git stash` to stash -your changes, or move `MathTypes` to another directory such -as `MathTypes-saved`. - - - -## 3. The MathOp and MathResult Ports - -A **port** is the endpoint of a connection between -two components. -A **port definition** is like a function signature; -it defines the type of the data carried on a port. - -For this tutorial, we need two port definitions: - -* `MathOp` for sending an arithmetic operation request from -`MathSender` to `MathReceiver`. - -* `MathResult` for sending the result of an arithmetic -operation from `MathReceiver` to `MathSender`. - -We follow the same three steps as in the previous section. - - -### 3.1. Construct the FPP Model - -**Create the MathPorts directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository and run `mkdir MathPorts`. -This directory will contain our new ports. - -**Create the FPP model file:** -Now go into the directory `Ref/MathPorts`. -Create a file `MathPorts.fpp` with the following contents: - -```fpp -module Ref { - - @ Port for requesting an operation on two numbers - port MathOp( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) - - @ Port for returning the result of a math operation - port MathResult( - result: F32 @< the result of the operation - ) - -} -``` - -This file defines the ports `MathOp` and `MathResult`. -`MathOp` has three formal parameters: a first operand, an -operation, and a second operand. -The operands have type `F32`, which represents a 32-bit -floating-point number. -The operation has type `MathOp`, which is the enum type -we defined in the previous section. -`MathResult` has a single formal parameter, the value of type `F32` -returned as the result of the operation. - -For more information about port definitions, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Ports). - - -### 3.2. Add the Model to the Project - -Add add the model -`Ref/MathPorts/MathPorts.fpp` to the `Ref` project. -Carry out the steps in the -previous section, after -substituting `MathPorts` for `MathTypes`. - - -### 3.3. Build the Model - -Carry out the steps in the -previous section, -in directory `MathPorts` instead of `MathTypes`. -The generated code will go in -`Ref/build-fprime-automatic-native/Ref/MathPorts`. -For port definitions, the names of the auto-generated C++ -files end in `PortAc.hpp` and `PortAc.cpp`. -You can look at this code if you wish. -However, the auto-generated C++ port files are used -by the autocoded component implementations (described below); -you won't ever program directly against their interfaces. - - -### 3.4. Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathPorts`. -To build this implementation, follow the steps -described for `MathTypes`. - - - -## 4. The MathSender Component - -Now we can build and test the `MathSender` component. -There are five steps: - -1. Construct the FPP model. -1. Add the model to the project. -1. Build the stub implementation. -1. Complete the implementation. -1. Write and run unit tests. - - -### 4.1. Construct the FPP Model - -**Create the MathSender directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository. -Run `mkdir MathSender` to create a directory for the new component. - -**Create the FPP model file:** -Now go into the directory `Ref/MathSender`. -Create a file `MathSender.fpp` with the following contents: - -```fpp -module Ref { - - @ Component for sending a math operation - active component MathSender { - - # ---------------------------------------------------------------------- - # General ports - # ---------------------------------------------------------------------- - - @ Port for sending the operation request - output port mathOpOut: MathOp - - @ Port for receiving the result - async input port mathResultIn: MathResult - - # ---------------------------------------------------------------------- - # Special ports - # ---------------------------------------------------------------------- - - @ Command receive port - command recv port cmdIn - - @ Command registration port - command reg port cmdRegOut - - @ Command response port - command resp port cmdResponseOut - - @ Event port - event port eventOut - - @ Telemetry port - telemetry port tlmOut - - @ Text event port - text event port textEventOut - - @ Time get port - time get port timeGetOut - - # ---------------------------------------------------------------------- - # Commands - # ---------------------------------------------------------------------- - - @ Do a math operation - async command DO_MATH( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) - - # ---------------------------------------------------------------------- - # Events - # ---------------------------------------------------------------------- - - @ Math command received - event COMMAND_RECV( - val1: F32 @< The first operand - op: MathOp @< The operation - val2: F32 @< The second operand - ) \ - severity activity low \ - format "Math command received: {f} {} {f}" - - @ Received math result - event RESULT( - result: F32 @< The math result - ) \ - severity activity high \ - format "Math result is {f}" - - # ---------------------------------------------------------------------- - # Telemetry - # ---------------------------------------------------------------------- - - @ The first value - telemetry VAL1: F32 - - @ The operation - telemetry OP: MathOp - - @ The second value - telemetry VAL2: F32 - - @ The result - telemetry RESULT: F32 - - } - -} -``` - -This code defines a component `Ref.MathSender`. -The component is **active**, which means it has its -own thread. - -Inside the definition of the `MathSender` component are -several specifiers. -We have divided the specifiers into five groups: - -1. **General ports:** These are user-defined ports for -application-specific functions. -There are two general ports: an output port `mathOpOut` -of type `MathOp` and an input port `mathResultIn` of -type `MathResult`. -Notice that these port specifiers use the ports that -we defined above. -The input port is **asynchronous**. -This means that invoking the port (i.e., sending -data on the port) puts a message on a queue. -The handler runs later, on the thread of this component. - -1. **Special ports:** These are ports that have a special -meaning in F Prime. -There are ports for registering commands with the dispatcher, -receiving commands, sending command responses, emitting -event reports, emitting telemetry, and getting the time. - -1. **Commands:** These are commands sent from the ground -or from a sequencer and dispatched to this component. -There is one command `DO_MATH` for doing a math operation. -The command is asynchronous. -This means that when the command arrives, it goes on a queue -and its handler is later run on the thread of this component. - -1. **Events:** These are event reports that this component -can emit. -There are two event reports, one for receiving a command -and one for receiving a result. - -1. **Telemetry:** These are **channels** that define telemetry -points that the this component can emit. -There are four telemetry channels: three for the arguments -to the last command received and one for the last -result received. - -For more information on defining components, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components). - - - -### 4.2. Add the Model to the Project - -**Create Ref/MathSender/CMakeLists.txt:** -Create a file `Ref/MathSender/CMakeLists.txt` with the following contents: - -```cmake -# Register the standard build -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.cpp" - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" -) -register_fprime_module() -``` - -This code will tell the build system how to build the FPP model -and component implementation. - -**Update Ref/CMakeLists.txt:** -Add `Ref/MathSender` to `Ref/CMakeLists.txt`, as we did -for `Ref/MathTypes`. - - - -### 4.3. Build the Stub Implementation - -**Run the build:** -Go into the directory `Ref/MathSender`. -Run the following commands: - -```bash -touch MathSender.cpp -fprime-util impl -``` - -The first command creates an empty file `MathSender.cpp`. -The build rules we wrote in the previous section expect -this file to be there. -After the second command, the build system should -run for a bit. -At the end there should be two new files -in the directory: -`MathSender.cpp-template` and -`MathSender.hpp-template`. - -Run the following commands: - -```bash -mv MathSender.cpp-template MathSender.cpp -mv MathSender.hpp-template MathSender.hpp -``` - -These commands produce a template, or stub implementation, -of the `MathSender` implementation class. -You will fill in this implementation class below. - -Now run the command `fprime-util build --jobs 4`. -The model and the stub implementation should build. -The option `--jobs 4` says to use four cores for the build. -This should make the build go faster. -You can use any number after `--jobs`, up to the number -of cores available on your system. - -**Inspect the generated code:** -The generated code resides in the directory -`Ref/fprime-build-automatic-native-ut/Ref/MathSender`. -You may wish to look over the file `MathSenderComponentAc.hpp` -to get an idea of the interface to the auto-generated -base class `MathSenderComponentBase`. -The `MathSender` implementation class is a derived class -of this base class. - - -### 4.4. Complete the Implementation - -Now we can complete the stub implementation. -In an editor, open the file `MathSender.cpp`. - -**Fill in the DO_MATH command handler:** -You should see a stub handler for the `DO_MATH` -command that looks like this: - -```c++ -void MathSender :: - DO_MATH_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq, - F32 val1, - MathOp op, - F32 val2 - ) -{ - // TODO - this->cmdResponse_out(opCode,cmdSeq,Fw::CmdResponse::OK); -} -``` - -The handler `DO_MATH_handler` is called when the `MathSender` -component receives a `DO_MATH` command. -This handler overrides the corresponding pure virtual -function in the auto-generated base class. -Fill in the handler so that it looks like this: - -```c++ -void MathSender :: - DO_MATH_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq, - F32 val1, - MathOp op, - F32 val2 - ) -{ - this->tlmWrite_VAL1(val1); - this->tlmWrite_OP(op); - this->tlmWrite_VAL2(val2); - this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2); - this->mathOpOut_out(0, val1, op, val2); - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} -``` - -The first two arguments to the handler function provide -the command opcode and the command sequence number -(a unique identifier generated by the command dispatcher). -The remaining arguments are supplied when the command is sent, -for example, from the F Prime ground data system (GDS). -The implementation code does the following: - -1. Emit telemetry and events. - -1. Invoke the `mathOpOut` port to request that `MathReceiver` -perform the operation. - -1. Send a command response indicating success. -The command response goes out on the special port -`cmdResponseOut`. - -In F Prime, every execution of a command handler must end by -sending a command response. -The proper behavior of other framework components (e.g., command -dispatcher, command sequencer) depends upon adherence to this rule. - -**Check the build:** -Run `fprime-util build` again to make sure that everything still builds. - -**Fill in the mathResultIn handler:** -You should see a stub handler for the `mathResultIn` -port that looks like this: - -```c++ -void MathSender :: - mathResultIn_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) -{ - // TODO -} -``` - -The handler `mathResultIn_handler` is called when the `MathReceiver` -component code returns a result by invoking the `mathResultIn` port. -Again the handler overrides the corresponding pure virtual -function in the auto-generated base class. -Fill in the handler so that it looks like this: - -```c++ -void MathSender :: - mathResultIn_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) -{ - this->tlmWrite_RESULT(result); - this->log_ACTIVITY_HI_RESULT(result); -} -``` - -The implementation code emits the result on the `RESULT` -telemetry channel and as a `RESULT` event report. - -**Check the build:** -Run `fprime-util build`. - - - -### 4.5. Write and Run Unit Tests - -**Unit tests** are an important part of FSW development. -At the component level, unit tests typically invoke input ports, send commands, -and check for expected values on output ports (including telemetry and event -ports). - -We will carry out the unit testing for the `MathSender` component -in three steps: - -1. Set up the unit test environment - -1. Write and run one unit test - -1. Write and run additional unit tests - - - -#### 4.5.1. Set Up the Unit Test Environment - -**Create the stub Tester class:** -In the directory `Ref/MathSender`, run `mkdir -p test/ut`. -This will create the directory where -the unit tests will reside. - -**Update Ref/MathSender/CMakeLists.txt:** -Go back to the directory `Ref/MathSender`. -Add the following lines to `CMakeLists.txt`: - -```cmake -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" -) -set(UT_AUTO_HELPERS ON) -register_fprime_ut() -``` - -**Generate the unit test stub:** -We will now generate a stub implementation of the unit tests. -This stub contains all the boilerplate necessary to write and -run unit tests against the `MathSender` component. -In a later step, we will fill in the stub with tests. - -1. If you have not yet run `fprime-util generate --ut`, - then do so now. This step generates the CMake build cache for the unit - tests. - -1. Run the command `fprime-util impl --ut`. - It should generate files `Tester.cpp`, `Tester.hpp`, and `TestMain.cpp`. - -1. Move these files to the `test/ut` directory: - - ```bash - mv Tester.* TestMain.cpp test/ut - ``` - -**Update Ref/MathSender/CMakeLists.txt:** -Open `MathSender/CMakeLists.txt` and update the definition of -`UT_SOURCE_FILES` by adding your new test files: - -```cmake -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestMain.cpp" -) -set(UT_AUTO_HELPERS ON) -register_fprime_ut() -``` - -**Run the build:** -Now we can check that the unit test build is working. -Run `fprime-util build --ut`. -Everything should build without errors. - -**Inspect the generated code:** -The unit test build generates some code to support unit testing. -The code is located at `Ref/build-fprime-automatic-native-ut/Ref/MathSender`. -This directory contains two auto-generated classes: - -1. `MathSenderGTestBase`: This is the direct base -class of `Tester`. -It provides a test interface implemented with Google Test -macros. - -1. `MathSenderTesterBase`: This is the direct base -class of `MathSenderGTestBase`. -It provides basic features such as histories of port -invocations. -It is not specific to Google Test, so you can -use this class without Google Test if desired. - -You can look at the header files for these generated classes -to see what operations they provide. -In the next sections we will provide some example uses -of these operations. - - -#### 4.5.2. Write and Run One Test - -Now we will write a unit test that exercises the -`DO_MATH` command. -We will do this in three phases: - -1. In the `Tester` class, add a helper function for sending the command and -checking the responses. -That way multiple tests can reuse the same code. - -1. In the `Tester` class, write a test function that -calls the helper to run a test. - -1. In the `main` function, write a Google Test macro -that invokes the test function. - -1. Run the test. - -**Add a helper function:** -Go into the directory `Ref/MathSender/test/ut`. -In the file `Tester.hpp`, add the following lines -to the section entitled "Helper methods": - -```c++ -//! Test a DO_MATH command -void testDoMath(MathOp op); -``` - -In the file `Tester.cpp`, add the corresponding -function body: - -```c++ -void Tester :: - testDoMath(MathOp op) -{ - - // Pick values - - const F32 val1 = 2.0; - const F32 val2 = 3.0; - - // Send the command - - // pick a command sequence number - const U32 cmdSeq = 10; - // send DO_MATH command - this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - - // Verify command receipt and response - - // verify command response was sent - ASSERT_CMD_RESPONSE_SIZE(1); - // verify the command response was correct as expected - ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK); - - // Verify operation request on mathOpOut - - // verify that one output port was invoked overall - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // verify that the math operation port was invoked once - ASSERT_from_mathOpOut_SIZE(1); - // verify the arguments of the operation port - ASSERT_from_mathOpOut(0, val1, op, val2); - - // Verify telemetry - - // verify that 3 channels were written - ASSERT_TLM_SIZE(3); - // verify that the desired telemetry values were sent once - ASSERT_TLM_VAL1_SIZE(1); - ASSERT_TLM_VAL2_SIZE(1); - ASSERT_TLM_OP_SIZE(1); - // verify that the correct telemetry values were sent - ASSERT_TLM_VAL1(0, val1); - ASSERT_TLM_VAL2(0, val2); - ASSERT_TLM_OP(0, op); - - // Verify event reports - - // verify that one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_COMMAND_RECV_SIZE(1); - // verify the correct event arguments were sent - ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2); - -} -``` - -This function is parameterized over different -operations. -It is divided into five sections: sending the command, -checking the command response, checking the output on -`mathOpOut`, checking telemetry, and checking events. -The comments explain what is happening in each section. -For further information about the F Prime unit test -interface, see the F Prime User's Guide. - -Notice that after sending the command to the component, we call -the function `doDispatch` on the component. -We do this in order to simulate the behavior of the active -component in a unit test environment. -In a flight configuration, the component has its own thread, -and the thread blocks on the `doDispatch` call until another -thread puts a message on the queue. -In a unit test context, there is only one thread, so the pattern -is to place work on the queue and then call `doDispatch` on -the same thread. - -There are a couple of pitfalls to watch out for with this pattern: - -1. If you put work on the queue and forget to call `doDispatch`, -the work won't get dispatched. -Likely this will cause a unit test failure. - -1. If you call `doDispatch` without putting work on the queue, -the unit test will block until you kill the process (e.g., -with control-C). - -**Write a test function:** -Next we will write a test function that calls -`testDoMath` to test an `ADD` operation. -In `Tester.hpp`, add the following line in the -section entitled "Tests": - -```c++ -//! Test an ADD command -void testAddCommand(); -``` - -In `Tester.cpp`, add the corresponding function -body: - -```c++ -void Tester :: - testAddCommand() -{ - this->testDoMath(MathOp::ADD); -} -``` - -This function calls `testDoMath` to test an `ADD` command. - -**Write a test macro:** -Add the following code to the file `TestMain.cpp`, -before the definition of the `main` function: - -```c++ -TEST(Nominal, AddCommand) { - Ref::Tester tester; - tester.testAddCommand(); -} -``` - -The `TEST` macro is an instruction to Google Test to run a test. -`Nominal` is the name of a test suite. -We put this test in the `Nominal` suite because it addresses -nominal (expected) behavior. -`AddCommand` is the name of the test. -Inside the body of the macro, the first line declares a new -object `tester` of type `Tester`. -We typically declare a new object for each unit test, so that -each test starts in a fresh state. -The second line invokes the function `testAddCommand` -that we wrote in the previous section. - -**Run the test:** -Go back to directory `Ref/MathSender`. -Run the command `fprime-util check`. -The build system should compile and run the unit -tests. -You should see output indicating that the test ran -and passed. - -As an exercise, try the following: - -1. Change the behavior of the component -so that it does something incorrect. -For example, try adding one to a telemetry -value before emitting it. - -1. Rerun the test and observe what happens. - - -#### 4.5.3. Write and Run More Tests - -**Add more command tests:** -Try to follow the pattern given in the previous -section to add three more tests, one each -for operations `SUB`, `MUL`, and `DIV`. -Most of the work should be done in the helper -that we already wrote. -Each new test requires just a short test function -and a short test macro. - -Run the tests to make sure everything compiles and -the tests pass. - -**Add a result test:** -Add a test for exercising the scenario in which the `MathReceiver` -component sends a result back to `MathSender`. - -1. Add the following function signature in the "Tests" section of `Tester.hpp`: - - ```c++ - //! Test receipt of a result - void testResult(); - ``` - -1. Add the corresponding function body in `Tester.cpp`: - - ```c++ - void Tester :: - testResult() - { - // Generate an expected result - const F32 result = 10.0; - // reset all telemetry and port history - this->clearHistory(); - // call result port with result - this->invoke_to_mathResultIn(0, result); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - // verify one telemetry value was written - ASSERT_TLM_SIZE(1); - // verify the desired telemetry channel was sent once - ASSERT_TLM_RESULT_SIZE(1); - // verify the values of the telemetry channel - ASSERT_TLM_RESULT(0, result); - // verify one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_RESULT_SIZE(1); - // verify the expect value of the event - ASSERT_EVENTS_RESULT(0, result); - } - ``` - - This code is similar to the helper function in the previous section. - The main difference is that it invokes a port directly - (the `mathResultIn` port) instead of sending a command. - -1. Add the following test macro to `TestMain.cpp`: - - ```c++ - TEST(Nominal, Result) { - Ref::Tester tester; - tester.testResult(); - } - ``` - -1. Run the tests. -Again you can try altering something in the component code -to see what effect it has on the test output. - - - -#### 4.5.4. Exercise: Random Testing - -F Prime provides a module called `STest` -that provides helper classes and functions for writing -unit tests. -As an exercise, use the interface provided by -`STest/STest/Pick.hpp` to pick random values to use in the -tests instead of using hard-coded values such as 2.0, 3.0, -and 10. - -**Modifying the code:** You will need to do the following: - -1. Add `#include "STest/Pick/Pick.hpp"` to `Tester.cpp`. - -1. Add the following - line to `Ref/MathSender/CMakeLists.txt`, before `register_fprime_ut`: - - ```cmake - set(UT_MOD_DEPS STest) - ``` - - This line tells the build system to make the unit test build - depend on the `STest` build module. - -1. Add `#include "STest/Random/Random.hpp"` to `TestMain.cpp`. - -1. Add the following line to the `main` function of `TestMain.cpp`, - just before the return statement: - - ```c++ - STest::Random::seed(); - ``` - - This line seeds the random number generator used by STest. - -**Running the tests:** -Recompile and rerun the tests. -Now go to -`Ref/build-fprime-automatic-native-ut/Ref/MathSender` and inspect the -file `seed-history`. -This file is a log of random seed values. -Each line represents the seed used in the corresponding run. - -**Fixing the random seed:** -Sometimes you may want to run a test with a particular seed value, -e.g., for replay debugging. -To do this, put the seed value into a file `seed` in the same -directory as `seed-history`. -If the file `seed` exists, then STest will use the seed it contains instead -of generating a new seed. - -Try the following: - -1. Copy the last value _S_ of `seed-history` into `seed`. - -1. In `Ref/MathSender`, re-run the unit tests a few times. - -1. Inspect `Ref/build-fprime-automatic-native-ut/Ref/MathSender/seed-history`. -You should see that the value _S_ was used in the runs you just did -(corresponding to the last few entries in `seed-history`). - - -### 4.6. Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathSender`. - - -## 5. The MathReceiver Component - -Now we will build and test the `MathReceiver` component. -We will use the same five steps as for the -`MathSender` component. - - -### 5.1. Construct the FPP Model - -**Create the MathReceiver directory:** -Create the directory `Ref/MathReceiver`. - -**Create the FPP model file:** -In directory `Ref/MathReceiver`, create a file -`MathReceiver.fpp` with the following contents: - -```fpp -module Ref { - - @ Component for receiving and performing a math operation - queued component MathReceiver { - - # ---------------------------------------------------------------------- - # General ports - # ---------------------------------------------------------------------- - - @ Port for receiving the math operation - async input port mathOpIn: MathOp - - @ Port for returning the math result - output port mathResultOut: MathResult - - @ The rate group scheduler input - sync input port schedIn: Svc.Sched - - # ---------------------------------------------------------------------- - # Special ports - # ---------------------------------------------------------------------- - - @ Command receive - command recv port cmdIn - - @ Command registration - command reg port cmdRegOut - - @ Command response - command resp port cmdResponseOut - - @ Event - event port eventOut - - @ Parameter get - param get port prmGetOut - - @ Parameter set - param set port prmSetOut - - @ Telemetry - telemetry port tlmOut - - @ Text event - text event port textEventOut - - @ Time get - time get port timeGetOut - - # ---------------------------------------------------------------------- - # Parameters - # ---------------------------------------------------------------------- - - @ The multiplier in the math operation - param FACTOR: F32 default 1.0 id 0 \ - set opcode 10 \ - save opcode 11 - - # ---------------------------------------------------------------------- - # Events - # ---------------------------------------------------------------------- - - @ Factor updated - event FACTOR_UPDATED( - val: F32 @< The factor value - ) \ - severity activity high \ - id 0 \ - format "Factor updated to {f}" \ - throttle 3 - - @ Math operation performed - event OPERATION_PERFORMED( - val: MathOp @< The operation - ) \ - severity activity high \ - id 1 \ - format "{} operation performed" - - @ Event throttle cleared - event THROTTLE_CLEARED \ - severity activity high \ - id 2 \ - format "Event throttle cleared" - - # ---------------------------------------------------------------------- - # Commands - # ---------------------------------------------------------------------- - - @ Clear the event throttle - async command CLEAR_EVENT_THROTTLE \ - opcode 0 - - # ---------------------------------------------------------------------- - # Telemetry - # ---------------------------------------------------------------------- - - @ The operation - telemetry OPERATION: MathOp id 0 - - @ Multiplication factor - telemetry FACTOR: F32 id 1 - - } - -} -``` - -This code defines a component `Ref.MathReceiver`. -The component is **queued**, which means it has a queue -but no thread. -Work occurs when the thread of another component invokes -the `schedIn` port of this component. - -We have divided the specifiers of this component into six groups: - -1. **General ports:** There are three ports: -an input port `mathOpIn` for receiving a math operation, -an output port `mathResultOut` for sending a math result, and -an input port `schedIn` for receiving invocations from the scheduler. -`mathOpIn` is asynchronous. -That means invocations of `mathOpIn` put messages on a queue. -`schedIn` is synchronous. -That means invocations of `schedIn` immediately call the -handler function to do work. - -1. **Special ports:** -As before, there are special ports for commands, events, telemetry, -and time. -There are also special ports for getting and setting parameters. -We will explain the function of these ports below. - -1. **Parameters:** There is one **parameter**. -A parameter is a constant that is configurable by command. -In this case there is one parameter `FACTOR`. -It has the default value 1.0 until its value is changed by command. -When doing math, the `MathReceiver` component performs the requested -operation and then multiplies by this factor. -For example, if the arguments of the `mathOpIn` port -are _v1_, `ADD`, and _v2_, and the factor is _f_, -then the result sent on `mathResultOut` is -_(v1 + v2) f_. - -1. **Events:** There are three event reports: - - 1. `FACTOR_UPDATED`: Emitted when the `FACTOR` parameter - is updated by command. - This event is **throttled** to a limit of three. - That means that after the event is emitted three times - it will not be emitted any more, until the throttling - is cleared by command (see below). - - 1. `OPERATION_PERFORMED`: Emitted when this component - performs a math operation. - - 1. `THROTTLE_CLEARED`: Emitted when the event throttling - is cleared. - -1. **Commands:** There is one command for clearing -the event throttle. - -1. **Telemetry:** -There two telemetry channels: one for reporting -the last operation received and one for reporting -the factor parameter. - -For the parameters, events, commands, and telemetry, we chose -to put in all the opcodes and identifiers explicitly. -These can also be left implicit, as in the `MathSender` -component example. -For more information, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components). - - -### 5.2. Add the Model to the Project - -Follow the steps given for the -`MathSender` component. - - -### 5.3. Build the Stub Implementation - -Follow the same steps as for the -`MathSender` component. - - -### 5.4. Complete the Implementation - -**Fill in the mathOpIn handler:** -In `MathReceiver.cpp`, complete the implementation of -`mathOpIn_handler` so that it looks like this: - -```cpp -void MathReceiver :: - mathOpIn_handler( - const NATIVE_INT_TYPE portNum, - F32 val1, - const MathOp& op, - F32 val2 - ) -{ - - // Get the initial result - F32 res = 0.0; - switch (op.e) { - case MathOp::ADD: - res = val1 + val2; - break; - case MathOp::SUB: - res = val1 - val2; - break; - case MathOp::MUL: - res = val1 * val2; - break; - case MathOp::DIV: - res = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - - // Get the factor value - Fw::ParamValid valid; - F32 factor = paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - - // Multiply result by factor - res *= factor; - - // Emit telemetry and events - this->log_ACTIVITY_HI_OPERATION_PERFORMED(op); - this->tlmWrite_OPERATION(op); - - // Emit result - this->mathResultOut_out(0, res); - -} -``` - -This code does the following: - -1. Compute an initial result based on the input values and -the requested operation. - -1. Get the value of the factor parameter. -Check that the value is a valid value from the parameter -database or a default parameter value. - -1. Multiply the initial result by the factor to generate -the final result. - -1. Emit telemetry and events. - -1. Emit the result. - -Note that in step 1, `op` is an enum (a C++ class type), and `op.e` -is the corresponding numeric value (an integer type). -Note also that in the `default` case we deliberately fail -an assertion. -This is a standard pattern for exhaustive case checking. -We should never hit the assertion. -If we do, then a bug has occurred: we missed a case. - -**Fill in the schedIn handler:** -In `MathReceiver.cpp`, complete the implementation of -`schedIn_handler` so that it looks like this: - -```c++ -void MathReceiver :: - schedIn_handler( - const NATIVE_INT_TYPE portNum, - NATIVE_UINT_TYPE context - ) -{ - U32 numMsgs = this->m_queue.getNumMsgs(); - for (U32 i = 0; i < numMsgs; ++i) { - (void) this->doDispatch(); - } -} -``` - -This code dispatches all the messages on the queue. -Note that for a queued component, we have to do this -dispatch explicitly in the `schedIn` handler. -For an active component, the framework auto-generates -the dispatch code. - -**Fill in the CLEAR_EVENT_THROTTLE command handler:** -In `MathReceiver.cpp`, complete the implementation of -`CLEAR_EVENT_THROTTLE_cmdHandler` so that it looks like this: - -```c++ -void MathReceiver :: - CLEAR_EVENT_THROTTLE_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq - ) -{ - // clear throttle - this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear(); - // send event that throttle is cleared - this->log_ACTIVITY_HI_THROTTLE_CLEARED(); - // reply with completion status - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} -``` - -The call to `log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear` clears -the throttling of the `FACTOR_UPDATED` event. -The next two lines send a notification event and send -a command response. - -**Add a parameterUpdated function:** -Add the following function to `MathReceiver.cpp`. -You will need to add the corresponding function header -to `MathReceiver.hpp`. - -```c++ -void MathReceiver :: - parameterUpdated(FwPrmIdType id) -{ - switch (id) { - case PARAMID_FACTOR: { - Fw::ParamValid valid; - F32 val = this->paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - this->log_ACTIVITY_HI_FACTOR_UPDATED(val); - break; - } - default: - FW_ASSERT(0, id); - break; - } -} -``` - -This code implements an optional function that, if present, -is called when a parameter is updated by command. -The parameter identifier is passed in as the `id` argument -of the function. -Here we do the following: - -1. If the parameter identifier is `PARAMID_FACTOR` (the parameter -identifier corresponding to the `FACTOR` parameter), -then get the parameter value and emit an event report. - -1. Otherwise fail an assertion. -This code should never run, because there are no other -parameters. - - -### 5.5. Write and Run Unit Tests - - -#### 5.5.1. Set up the Unit Test Environment - -1. Follow the steps given for the -`MathSender` component. - -1. Follow the steps given under **Modifying the code** -for the -random testing exercise, -so that you can use STest to pick random values. - - -#### 5.5.2. Add Helper Code - -**Add a ThrottleState enum class:** -Add the following code to the beginning of the -`Tester` class in `Tester.hpp`: - -```c++ -private: - - // ---------------------------------------------------------------------- - // Types - // ---------------------------------------------------------------------- - - enum class ThrottleState { - THROTTLED, - NOT_THROTTLED - }; -``` - -This code defines a C++ enum class for recording whether an -event is throttled. - -**Add helper functions:** -Add each of the functions described below to the -"Helper methods" section of `Tester.cpp`. -For each function, you must add -the corresponding function prototype to `Tester.hpp`. -After adding each function, compile the unit tests -to make sure that everything still compiles. -Fix any errors that occur. - -Add a `pickF32Value` function. - -```c++ -F32 Tester :: - pickF32Value() -{ - const F32 m = 10e6; - return m * (1.0 - 2 * STest::Pick::inUnitInterval()); -} -``` - -This function picks a random `F32` value in the range -_[ -10^6, 10^6 ]_. - -Add a `setFactor` function. - -```c++ -void Tester :: - setFactor( - F32 factor, - ThrottleState throttleState - ) -{ - // clear history - this->clearHistory(); - // set the parameter - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - const U32 instance = STest::Pick::any(); - const U32 cmdSeq = STest::Pick::any(); - this->paramSend_FACTOR(instance, cmdSeq); - if (throttleState == ThrottleState::NOT_THROTTLED) { - // verify the parameter update notification event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED(0, factor); - } - else { - ASSERT_EVENTS_SIZE(0); - } -} -``` - -This function does the following: - -1. Clear the test history. - -1. Send a command to the component to set the `FACTOR` parameter -to the value `factor`. - -1. If `throttleState` is `NOT_THROTTLED`, then check -that the event was emitted. -Otherwise check that the event was throttled (not emitted). - -Add a function `computeResult` to `Tester.cpp`. - -```c++ -F32 Tester :: - computeResult( - F32 val1, - MathOp op, - F32 val2, - F32 factor - ) -{ - F32 result = 0; - switch (op.e) { - case MathOp::ADD: - result = val1 + val2; - break; - case MathOp::SUB: - result = val1 - val2; - break; - case MathOp::MUL: - result = val1 * val2; - break; - case MathOp::DIV: - result = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - result *= factor; - return result; -} -``` - -This function carries out the math computation of the -math component. -By running this function and comparing, we can -check the output of the component. - -Add a `doMathOp` function to `Tester.cpp`. - -```c++ -void Tester :: - doMathOp( - MathOp op, - F32 factor - ) -{ - - // pick values - const F32 val1 = pickF32Value(); - const F32 val2 = pickF32Value(); - - // clear history - this->clearHistory(); - - // invoke operation port with add operation - this->invoke_to_mathOpIn(0, val1, op, val2); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - - // verify the result of the operation was returned - - // check that there was one port invocation - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // check that the port we expected was invoked - ASSERT_from_mathResultOut_SIZE(1); - // check that the component performed the operation correctly - const F32 result = computeResult(val1, op, val2, factor); - ASSERT_from_mathResultOut(0, result); - - // verify events - - // check that there was one event - ASSERT_EVENTS_SIZE(1); - // check that it was the op event - ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1); - // check that the event has the correct argument - ASSERT_EVENTS_OPERATION_PERFORMED(0, op); - - // verify telemetry - - // check that one channel was written - ASSERT_TLM_SIZE(1); - // check that it was the op channel - ASSERT_TLM_OPERATION_SIZE(1); - // check for the correct value of the channel - ASSERT_TLM_OPERATION(0, op); - -} -``` - -This function is similar to the `doMath` helper function that -we wrote for the `MathSender` component. -Notice that the method for invoking a port is different. -Since the component is queued, we don't call `doDispatch` -directly. -Instead we invoke `schedIn`. - - -#### 5.5.3. Write and Run Tests - -For each of the tests described below, you must add the -corresponding function prototype to `Tester.hpp` -and the corresponding test macro to `TestMain.cpp`. -If you can't remember how to do it, look back at the -`MathSender` examples. -After writing each test, run all the tests and make sure -that they pass. - -**Write an ADD test:** -Add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testAdd() -{ - // Set the factor parameter by command - const F32 factor = pickF32Value(); - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - // Do the add operation - this->doMathOp(MathOp::ADD, factor); -} -``` - -This function calls the `setFactor` helper function -to set the factor parameter. -Then it calls the `doMathOp` function to -do a math operation. - -**Write a SUB test:** -Add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testSub() -{ - // Set the factor parameter by loading parameters - const F32 factor = pickF32Value(); - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - this->component.loadParameters(); - // Do the operation - this->doMathOp(MathOp::SUB, factor); -} -``` - -This test is similar to `testAdd`, but it shows -another way to set a parameter. -`testAdd` showed how to set a parameter by command. -You can also set a parameter by initialization, as follows: - -1. Call the `paramSet` function as shown. -This function sets the parameter value in -the part of the test harness that mimics the behavior of the -parameter database component. - -1. Call the `loadParameters` function as shown. -In flight, the function `loadParameters` is typically called at the -start of FSW to load the parameters from the database; -here it loads the parameters from the test harness. -There is no command to update a parameter, so `parameterUpdated` -is not called, and no event is emitted. - -As before, after setting the parameter we call `doMathOp` -to do the operation. - -**Write a MUL test:** -This test is the same as the ADD test, except that it -uses MUL instead of add. - -**Write a DIV test:** -This test is the same as the SUB test, except that it -uses DIV instead of SUB. - -**Write a throttle test:** -Add the following constant definition to the top of the `Tester.cpp` file: - -```C++ -#define CMD_SEQ 42 -``` - -Then add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testThrottle() -{ - - // send the number of commands required to throttle the event - // Use the autocoded value so the unit test passes if the - // throttle value is changed - const F32 factor = pickF32Value(); - for ( - U16 cycle = 0; - cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE; - cycle++ - ) { - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - } - - // Event should now be throttled - this->setFactor(factor, ThrottleState::THROTTLED); - - // send the command to clear the throttle - this->sendCmd_CLEAR_EVENT_THROTTLE(TEST_INSTANCE_ID, CMD_SEQ); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - // verify clear event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1); - - // Throttling should be cleared - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - -} -``` - -This test first loops over the throttle count, which is stored -for us in the constant `EVENTID_FACTOR_UPDATED_THROTTLE` -of the `MathReceiver` component base class. -On each iteration, it calls `setFactor`. -At the end of this loop, the `FACTOR_UPDATED` event should be -throttled. - -Next the test calls `setFactor` with a second argument of -`ThrottleState::THROTTLED`. -This code checks that the event is throttled. - -Next the test sends the command `CLEAR_EVENT_THROTTLE`, -checks for the corresponding notification event, -and checks that the throttling is cleared. - - -### 5.6. Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathReceiver`. - - -### 5.7. Exercises - - -#### 5.7.1. Adding Telemetry - -Add a telemetry channel that records the number of math -operations performed. - -1. Add the channel to the FPP model. - -1. In the component implementation class, add a member -variable `numMathOps` of type `U32`. -Initialize the variable to zero in the class constructor. - -1. Revise the `mathOpIn` handler so that it increments -`numMathOps` and emits the updated value as telemetry. - -1. Revise the unit tests to cover the new behavior. - - -#### 5.7.2. Error Handling - -Think about what will happen if the floating-point -math operation performed by `MathReceiver` causes an error. -For example, suppose that `mathOpIn` is invoked with `op = DIV` -and `val2 = 0.0`. -What will happen? -As currently designed and implemented, the `MathReceiver` -component will perform the requested operation. -On some systems the result will be `INF` (floating-point infinity). -In this case, the result will be sent back to `MathSender` -and reported in the usual way. -On other systems, the hardware could issue a floating-point exception. - -Suppose you wanted to handle the case of division by zero -explicitly. -How would you change the design? -Here are some questions to think about: - -1. How would you check for division by zero? -Note that `val2 = 0.0` is not the only case in which a division -by zero error can occur. -It can also occur for very small values of `val2`. - -1. Should the error be caught in `MathSender` or `MathReceiver`? - -1. Suppose the design says that `MathSender` catches the error, -and so never sends requests to `MathReceiver` to divide by zero. -What if anything should `MathReceiver` do if it receives -a divide by zero request? -Carry out the operation normally? -Emit a warning? -Fail a FSW assertion? - -1. If the error is caught by `MathReceiver`, does the -interface between the components have to change? -If so, how? -What should `MathSender` do if `MathReceiver` -reports an error instead of a valid result? - -Revise the MathSender and MathReceiver components to implement your -ideas. -Add unit tests covering the new behavior. - - -## 6. Updating the Ref Deployment - -The next step in the tutorial is to define instances of the -`MathSender` and `MathReceiver` components and add them -to the `Ref` topology. - - -### 6.1. Defining the Component Instances - -Go to the directory `Ref/Top` and open the file `instances.fpp`. -This file defines the instances used in the topology for the -`Ref` application. -Update this file as described below. - -**Define the mathSender instance:** -At the end of the section entitled "Active component instances," -add the following lines: - -```fpp -instance mathSender: Ref.MathSender base id 0xE00 \ - queue size Default.QUEUE_SIZE \ - stack size Default.STACK_SIZE \ - priority 100 -``` - -This code defines an instance `mathSender` of component -`MathSender`. -It has **base identifier** 0xE00. -FPP adds the base identifier to each the relative identifier -defined in the component to compute the corresponding -identifier for the instance. -For example, component `MathSender` has a telemetry channel -`MathOp` with identifier 1, so instance `mathSender` -has a command `MathOp` with identifier 0xE01. - -The following lines define the queue size, stack size, -and thread priority for the active component. -Here we give `mathSender` the default queue size -and stack size and a priority of 100. - -**Define the mathReceiver instance:** -At the end of the section "Queued component instances," -add the following lines: - -```fpp -instance mathReceiver: Ref.MathReceiver base id 0x2700 \ - queue size Default.QUEUE_SIZE -``` - -This code defines an instance `mathReceiver` of -component `MathReceiver`. -It has base identifier 0x2700 and the default queue size. - -**More information:** -For more information on defining component instances, -see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Component-Instances). - - -### 6.2. Updating the Topology - -Go to the directory `Ref/Top` and open the file `topology.fpp`. -This file defines the topology for the `Ref` application. -Update this file as described below. - -**Add the new instances:** -You should see a list of instances, each of which begins -with the keyword `instance`. -After the line `instance linuxTime`, add the following -lines: - -```fpp -instance mathSender -instance mathReceiver -``` - -These lines add the `mathSender` and `mathReceiver` -instances to the topology. - -**Packetize the telemetry channels:** -Open the file `RefPackets.xml`. At the bottom, right before ``, add the following lines: -``` - - - - - - - - - - - -``` - -These lines describe the packet definitions for the `mathSender` and `mathReceiver` telemetry channels. - -**Check for unconnected ports:** -Run the following commands in the `Ref/Top` directory: - -```bash -fprime-util fpp-check -u unconnected.txt -cat unconnected.txt -``` - -You should see a list of ports -that are unconnected in the `Ref` topology. -Those ports will include the ports for the new instances -`mathSender` and `mathReceiver`. - -**Connect mathReceiver to rate group 1:** -Find the line that starts `connections RateGroups`. -This is the beginning of the definition of the `RateGroups` -connection graph. -After the last entry for the `rateGroup1Comp` (rate group 1) add -the following line: - -```fpp -rateGroup1Comp.RateGroupMemberOut -> mathReceiver.schedIn -``` - -This line adds the connection that drives the `schedIn` -port of the `mathReceiver` component instance. - -**Re-run the check for unconnected ports:** -You should see that `mathReceiver.schedIn` is now connected -(it no longer appears in the list). - -**Add the Math connections:** -Find the Uplink connections that begin with the line -`connections Uplink`. -After the block of that definition, add the following -lines: - -```fpp -connections Math { - mathSender.mathOpOut -> mathReceiver.mathOpIn - mathReceiver.mathResultOut -> mathSender.mathResultIn -} -``` - -These lines add the connections between the `mathSender` -and `mathReceiver` instances. - -**Re-run the check for unconnected ports:** -When this capability exists, you will be able to see -that the `mathSender` and `mathReceiver` ports are connected. - -**More information:** -For more information on defining topologies, -see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Topologies). - - -### 6.3. Building the Ref Deployment - -Go to the `Ref` directory. -Run `fprime-util build --jobs 4`. -The updated deployment should build without errors. -The generated files are located at -`Ref/build-fprime-automatic-native/Ref/Top`. - - -### 6.4. Visualizing the Ref Topology - -Now we will see how to create a visualization (graphical rendering) -of the Ref topology. - -**Generate the layout:** -For this step, we will use the F Prime Layout (FPL) tool. -If FPL is not installed on your system, then install it now: -clone [this repository](https://github.com/fprime-community/fprime-layout) -and follow the instructions. - -In directory `Ref/Top`, run the following commands in an sh-compatible -shell such as bash. -If you are using a different shell, you can run `sh` -to enter the `sh` shell, run these commands, and enter -`exit` when done. -Or you can stay in your preferred shell and adjust these commands -appropriately. - -```bash -cp ../build-fprime-automatic-native/Ref/Top/RefTopologyAppAi.xml . -mkdir visual -cd visual -fpl-extract-xml < ../RefTopologyAppAi.xml -mkdir Ref -for file in `ls *.xml` -do -echo "laying out $file" -base=`basename $file .xml` -fpl-convert-xml $file | fpl-layout > Ref/$base.json -done -``` - -This step extracts the connection graphs from the topology XML and -converts each one to a JSON layout file. - -**Render the layout:** -For this step, we will use the F Prime Visualizer (FPV) tool. -If FPV is not installed on your system, then install it now: -clone [this repository](https://github.com/fprime-community/fprime-visual) -and follow the instructions. - -In directory `Ref/Top`, run the following commands in an sh-compatible -shell. -Replace `[path to fpv root]` with the path to the -root of the FPV repo on your system. - -```bash -echo DATA_FOLDER=Ref/ > .fpv-env -nodemon [path to fpv root]/server/index.js ./.fpv-env -``` - -You should see the FPV server application start up on the -console. - -Now open a browser and navigate to `http://localhost:3000`. -You should see a Topology menu at the top of the window -and a rendering of the Command topology below. -Select Math from the topology menu. -You should see a rendering of the Math topology. -It should look similar to the -topology diagram shown above. - -You can use the menu to view other topology graphs. -When you are done, close the browser window and -type control-C in the console to shut down the FPV server. - - -### 6.5. Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/Top`. -To build this implementation, copy the files -`instances.fpp` and `topology.fpp` from -that directory to `Ref/Top`. - - -## 7. Running the Ref Deployment - -Now we will use the F Prime Ground Data System (GDS) to run the Ref deployment. -Go to the `Ref` directory and run `fprime-gds`. -You should see some activity on the console. -The system is starting the Ref deployment executable, starting the GDS, -and connecting them over the local network on your machine. -After several seconds, a browser window should appear. - - -### 7.1. Sending a Command - -At the top of the window are several buttons, each of which corresponds to -a GDS view. -Select the Commanding button (this is the view that is selected -when you first start the GDS). -In the Mnemonic menu, start typing `mathSender.DO_MATH` in the text box. -As you type, the GDS will filter the menu selections. -When only one choice remains, stop typing and press return. -You should see three boxes appear: - -1. A text box for entering `val1`. - -1. A menu for entering `op`. - -1. A text box for entering `val2`. - -Fill in the arguments corresponding to the operation `1 + 2`. -You can use the tab key to move between the boxes. -When you have done this, click the Send Command button. -You should see a table entry at the bottom of the window -indicating that the command was sent. - - -### 7.2. Checking Events - -Now click on the Events button at the top of the window. -The view changes to the Events tab. -You should see events indicating that the command you sent was -dispatched, received, and completed. -You should also see events indicating that `mathReceiver` -performed an `ADD` operation and `mathSender` -received a result of 3.0. - - -### 7.3. Checking Telemetry - -Click on the Channels button at the top of the window. -You should see a table of telemetry channels. -Each row corresponds to the latest value of a telemetry -channel received by the GDS. -You should see the channels corresponding to the input -values, the operation, and the result. - - -### 7.4. Setting Parameters - -Go back to the Commanding tab. -Select the command `mathReceiver.FACTOR_PRM_SET`. -This is an auto-generated command for setting the -parameter `FACTOR`. -Type the value 2.0 in the `val` box and click Send Command. -Check the events to see that the command was dispatched -and executed. -You should also see the events sent by the code -that you implemented. - -In the Commanding tab, issue the command `1 + 2` again. -Check the Events tab. -Because the factor is now 2.0, you should see a result -value of 6.0. - - -### 7.5. Saving Parameters - -When you set a parameter by command, the new parameter -value resides in the component that receives the command. -At this point, if you stop and restart FSW, the parameter -will return to its original value (the value before you -sent the command). - -At some point you may wish to update parameters more permanently. -You can do this by saving them to non-volatile storage. -For the Ref application, "non-volatile storage" means the -file system on your machine. - -To save the parameter `mathReceiver.FACTOR` to non-volatile storage, -do the following: - -1. Send the command `mathReceiver.FACTOR_PRM_SAVE`. -This command saves the parameter value to the **parameter database**, -which is a standard F Prime component for storing system parameters. - -1. Send the command `prmDb.PRM_SAVE_FILE`. -This command saves the parameter values in the parameter database -to non-volatile storage. - -Note that saving parameters is a two-step process. -The first step copies a single parameter from a component -to the database. -The second step saves all parameters in the database -to the disk. -If you do only the first step, the parameter will not be -saved to the disk. - - -### 7.6. GDS Logs - -As it runs, the GDS writes a log into a subdirectory of `Ref/logs`. -The subdirectory is stamped with the current date. -Go into the directory for the run you just performed. -(If the GDS is still running, you will have to do this in a -different shell.) -You should see the following logs, among others: - -* `Ref.log`: FSW console output. - -* `command.log`: Commands sent. - -* `event.log`: Event reports received. - -* `channel.log`: Telemetry points received. - -You can also view these logs via the GDS browser interface. -Click the Logs tab to go the Logs view. -Select the log you wish to inspect from the drop-down menu. -By default, there is no log selected. - - -## 8. Conclusion - -The Math Component tutorial has shown us how to create simple types, ports and -components for our application using the FPP modeling language. We have learned -how to use `fprime-util` to generate implementation stubs, the build cache, and -unit tests. We learned how to define our topology and use tools provided by -F´ to check and visualize the topology. Lastly, we learned how to use the -ground system to interact with our deployment. - -The user is now directed back to the [Tutorials](../README.md) for further -reading or to the [Cross-Compilation Tutorial](../CrossCompilation/Tutorial.md) -for instructions on how to cross-compile the Ref application completed in this -tutorial for the Raspberry Pi. diff --git a/docs/Tutorials/MathComponent/json/refresh b/docs/Tutorials/MathComponent/json/refresh deleted file mode 100755 index c39ab771af..0000000000 --- a/docs/Tutorials/MathComponent/json/refresh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -fpl-layout < top.txt > top.json diff --git a/docs/Tutorials/MathComponent/json/top.json b/docs/Tutorials/MathComponent/json/top.json deleted file mode 100644 index dccf0d9750..0000000000 --- a/docs/Tutorials/MathComponent/json/top.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "mathSender", - "inputPorts" : [ - { - "name" : "mathResultIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "mathOpOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "mathReceiver", - "inputPorts" : [ - { - "name" : "mathOpIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "mathResultOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 0, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/docs/Tutorials/MathComponent/json/top.txt b/docs/Tutorials/MathComponent/json/top.txt deleted file mode 100644 index 69d31efece..0000000000 --- a/docs/Tutorials/MathComponent/json/top.txt +++ /dev/null @@ -1,13 +0,0 @@ -mathSender -mathOpOut -0 -mathReceiver -mathOpIn -0 - -mathReceiver -mathResultOut -0 -mathSender -mathResultIn -0 diff --git a/docs/Tutorials/MathComponent/md/.gitignore b/docs/Tutorials/MathComponent/md/.gitignore deleted file mode 100644 index 5e2df058f8..0000000000 --- a/docs/Tutorials/MathComponent/md/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -defined-tags.txt -toc.md -used-tags.txt diff --git a/docs/Tutorials/MathComponent/md/Tutorial.md b/docs/Tutorials/MathComponent/md/Tutorial.md deleted file mode 100644 index ee15a6d52e..0000000000 --- a/docs/Tutorials/MathComponent/md/Tutorial.md +++ /dev/null @@ -1,1983 +0,0 @@ -# F' Math Component Tutorial - -!include toc.md - -## Introduction - -This tutorial shows how to develop, test, and deploy a simple topology -consisting of two components: - -1. `MathSender`: A component that receives commands and forwards work to - `MathReceiver`. - -1. `MathReceiver`: A component that carries out arithmetic operations and - returns the results to `MathSender`. - -See the diagram below. - - -![A simple topology for arithmetic computation](png/top.png) - -**What is covered:** The tutorial covers the following concepts: - -1. Using the [FPP modeling language](https://fprime-community.github.io/fpp) to - specify the types and ports used by the components. - -1. Using the F Prime build system to build the types and ports. - -1. Developing the `MathSender` component: Specifying the component, building - the component, completing the C++ component implementation, and writing - component unit tests. - -1. Developing the `MathReceiver` component. - -1. Adding the new components and connections to the F Prime `Ref` application. - -1. Using the F Prime Ground Data System (GDS) to run the updated `Ref` - application. - -**Prerequisites:** This tutorial assumes the following: - -1. Basic knowledge of Unix: How to navigate in a shell and execute programs. - -1. Basic knowledge of git: How to create a branch. - -1. Basic knowledge of C++, including class declarations, inheritance, -and virtual functions. - -If you have not yet installed F Prime on your system, do so now. -Follow the installation guide at `INSTALL.md` -in the [F Prime git repository](https://github.com/nasa/fprime). -You may also wish to work through the Getting Started tutorial at -`docs/GettingStarted/Tutorial.md`. - -**Version control:** -Working on this tutorial will modify some files under version control -in the F Prime git repository. -Therefore it is a good idea to do this work on a new branch. -For example: - -```bash -git checkout -b math-tutorial -``` - -If you wish, you can save your work by committing to this branch. - -## The MathOp Type - -In F Prime, a **type definition** defines a kind of data that you can pass -between components or use in commands and telemetry. - -For this tutorial, we need one type definition. -It defines an enumeration called `MathOp`, which -represents a mathematical operation. - -We will add the specification for the `MathOp` type to the -`Ref` topology. -We will do this in three stages: - -1. Construct the FPP model. - -1. Add the model to the project. - -1. Build the model. - -### Construct the FPP Model - -**Create the MathTypes directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository and run `mkdir MathTypes`. -This step creates a new directory `Ref/MathTypes`. -This directory will contain our new type. - -**Create the FPP model file:** -Now go into the directory `Ref/MathTypes`. -In that directory, create a file `MathTypes.fpp` with the following contents: - -```fpp -!include ../MathTypes/MathTypes.fpp -``` - -You can do this by typing, or by copy-paste. - -This file defines an enumeration or **enum** with enumerated constants `ADD`, -`SUB`, `MUL`, and `DIV`. -These four constants represent the operations of addition, subtraction, -multiplication, and division. -The enum also defines a type `MathOp`; the enumerated constants are the values -of this type. -For more information on enums, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Enums). - -The enum `MathTypes` resides in an FPP module `Ref`. - -An FPP module is like a C++ namespace: it encloses several definitions, each of -which is qualified with the name of the module. -For more information on FPP modules, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Modules). - -The text following a symbol `@` or `@<` is called an **annotation**. -These annotations are carried through the parsing and become comments in the -generated code. -For more information, see [_The FPP User's -Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Writing-Comments-and-Annotations). - - -### Add the Model to the Project - -**Create Ref/MathTypes/CMakeLists.txt:** -Create a file `Ref/MathTypes/CMakeLists.txt` with the following contents: - -```cmake -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp" -) - -register_fprime_module() -``` - -This code will tell the build system how to build the FPP model. - -**Update Ref/CMakeLists.txt:** -Now we need to add the new directory to the `Ref` project. -To do that, open the file `Ref/CMakeLists.txt`. -This file should already exist; it was put there by the developers -of the `Ref` topology. -In this file, you should see several lines starting with `add_fprime_subdirectory`. -Immediately after the last of those lines, add the following new line: - -```cmake -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathTypes/") -``` - - -### Build the Model - -**Run the build:** -Do the following: - -1. Go to the directory `Ref/MathTypes`. - -1. If you have not already run `fprime-util generate`, then do so now. - -1. Run the command `fprime-util build`. - -The output should indicate that the model built without any errors. -If not, try to identify and correct what is wrong, -either by deciphering the error output, or by going over the steps again. -If you get stuck, you can look at the -reference implementation. - -**Inspect the generated code:** -Now go to the directory `Ref/build-fprime-automatic-native/Ref/MathTypes` -(you may want to use `pushd`, or do this in a separate shell, -so you don't lose your current working directory). -The directory `build-fprime-automatic-native` is where all the -generated code lives for the "automatic native" build of the `Ref` -project. -Within that directory is a directory tree that mirrors the project -structure. -In particular, `Ref/build-fprime-automatic-native/Ref/MathTypes` -contains the generated code for `Ref/MathTypes`. - -Run `ls`. -You should see something like this: - -```bash -CMakeFiles MathOpEnumAc.cpp MathOpEnumAi.xml.prev cmake_install.cmake -Makefile MathOpEnumAc.hpp autocoder -``` - -The files `MathOpEnumAc.hpp` and -`MathOpEnumAc.cpp` are the auto-generated C++ files -corresponding to the `MathOp` enum. -You may wish to study the file `MathOpEnumAc.hpp`. -This file gives the interface to the C++ class `Ref::MathOp`. -All enum types have a similar auto-generated class -interface. - - -### Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathTypes`. -To build this implementation from a clean repository, -do the following: - -1. Go to the `Ref` directory. - -1. Run `cp -R ../docs/Tutorials/MathComponent/MathTypes .` - -1. Update `Ref/CMakeLists.txt` as stated above. - -1. Follow the steps for building the model. - -If you have modified the repo, revise the steps accordingly. -For example, switch git branches, use `git stash` to stash -your changes, or move `MathTypes` to another directory such -as `MathTypes-saved`. - - -## The MathOp and MathResult Ports - -A **port** is the endpoint of a connection between -two components. -A **port definition** is like a function signature; -it defines the type of the data carried on a port. - -For this tutorial, we need two port definitions: - -* `MathOp` for sending an arithmetic operation request from -`MathSender` to `MathReceiver`. - -* `MathResult` for sending the result of an arithmetic -operation from `MathReceiver` to `MathSender`. - -We follow the same three steps as in the previous section. - -### Construct the FPP Model - -**Create the MathPorts directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository and run `mkdir MathPorts`. -This directory will contain our new ports. - -**Create the FPP model file:** -Now go into the directory `Ref/MathPorts`. -Create a file `MathPorts.fpp` with the following contents: - -```fpp -!include ../MathPorts/MathPorts.fpp -``` - -This file defines the ports `MathOp` and `MathResult`. -`MathOp` has three formal parameters: a first operand, an -operation, and a second operand. -The operands have type `F32`, which represents a 32-bit -floating-point number. -The operation has type `MathOp`, which is the enum type -we defined in the previous section. -`MathResult` has a single formal parameter, the value of type `F32` -returned as the result of the operation. - -For more information about port definitions, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Ports). - -### Add the Model to the Project - -Add add the model -`Ref/MathPorts/MathPorts.fpp` to the `Ref` project. -Carry out the steps in the -previous section, after -substituting `MathPorts` for `MathTypes`. - -### Build the Model - -Carry out the steps in the -previous section, -in directory `MathPorts` instead of `MathTypes`. -The generated code will go in -`Ref/build-fprime-automatic-native/Ref/MathPorts`. -For port definitions, the names of the auto-generated C++ -files end in `PortAc.hpp` and `PortAc.cpp`. -You can look at this code if you wish. -However, the auto-generated C++ port files are used -by the autocoded component implementations (described below); -you won't ever program directly against their interfaces. - -### Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathPorts`. -To build this implementation, follow the steps -described for `MathTypes`. - - -## The MathSender Component - -Now we can build and test the `MathSender` component. -There are five steps: - -1. Construct the FPP model. -1. Add the model to the project. -1. Build the stub implementation. -1. Complete the implementation. -1. Write and run unit tests. - -### Construct the FPP Model - -**Create the MathSender directory:** -Go to the directory `Ref` at the top-level of the -F Prime repository. -Run `mkdir MathSender` to create a directory for the new component. - -**Create the FPP model file:** -Now go into the directory `Ref/MathSender`. -Create a file `MathSender.fpp` with the following contents: - -```fpp -!include ../MathSender/MathSender.fpp -``` - -This code defines a component `Ref.MathSender`. -The component is **active**, which means it has its -own thread. - -Inside the definition of the `MathSender` component are -several specifiers. -We have divided the specifiers into five groups: - -1. **General ports:** These are user-defined ports for -application-specific functions. -There are two general ports: an output port `mathOpOut` -of type `MathOp` and an input port `mathResultIn` of -type `MathResult`. -Notice that these port specifiers use the ports that -we defined above. -The input port is **asynchronous**. -This means that invoking the port (i.e., sending -data on the port) puts a message on a queue. -The handler runs later, on the thread of this component. - -1. **Special ports:** These are ports that have a special -meaning in F Prime. -There are ports for registering commands with the dispatcher, -receiving commands, sending command responses, emitting -event reports, emitting telemetry, and getting the time. - -1. **Commands:** These are commands sent from the ground -or from a sequencer and dispatched to this component. -There is one command `DO_MATH` for doing a math operation. -The command is asynchronous. -This means that when the command arrives, it goes on a queue -and its handler is later run on the thread of this component. - -1. **Events:** These are event reports that this component -can emit. -There are two event reports, one for receiving a command -and one for receiving a result. - -1. **Telemetry:** These are **channels** that define telemetry -points that the this component can emit. -There are four telemetry channels: three for the arguments -to the last command received and one for the last -result received. - -For more information on defining components, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components). - - -### Add the Model to the Project - -**Create Ref/MathSender/CMakeLists.txt:** -Create a file `Ref/MathSender/CMakeLists.txt` with the following contents: - -```cmake -# Register the standard build -set(SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.cpp" - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" -) -register_fprime_module() -``` - -This code will tell the build system how to build the FPP model -and component implementation. - -**Update Ref/CMakeLists.txt:** -Add `Ref/MathSender` to `Ref/CMakeLists.txt`, as we did -for `Ref/MathTypes`. - - -### Build the Stub Implementation - -**Run the build:** -Go into the directory `Ref/MathSender`. -Run the following commands: - -```bash -touch MathSender.cpp -fprime-util impl -``` - -The first command creates an empty file `MathSender.cpp`. -The build rules we wrote in the previous section expect -this file to be there. -After the second command, the build system should -run for a bit. -At the end there should be two new files -in the directory: -`MathSender.cpp-template` and -`MathSender.hpp-template`. - -Run the following commands: - -```bash -mv MathSender.cpp-template MathSender.cpp -mv MathSender.hpp-template MathSender.hpp -``` - -These commands produce a template, or stub implementation, -of the `MathSender` implementation class. -You will fill in this implementation class below. - -Now run the command `fprime-util build --jobs 4`. -The model and the stub implementation should build. -The option `--jobs 4` says to use four cores for the build. -This should make the build go faster. -You can use any number after `--jobs`, up to the number -of cores available on your system. - -**Inspect the generated code:** -The generated code resides in the directory -`Ref/fprime-build-automatic-native-ut/Ref/MathSender`. -You may wish to look over the file `MathSenderComponentAc.hpp` -to get an idea of the interface to the auto-generated -base class `MathSenderComponentBase`. -The `MathSender` implementation class is a derived class -of this base class. - -### Complete the Implementation - -Now we can complete the stub implementation. -In an editor, open the file `MathSender.cpp`. - -**Fill in the DO_MATH command handler:** -You should see a stub handler for the `DO_MATH` -command that looks like this: - -```c++ -void MathSender :: - DO_MATH_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq, - F32 val1, - MathOp op, - F32 val2 - ) -{ - // TODO - this->cmdResponse_out(opCode,cmdSeq,Fw::CmdResponse::OK); -} -``` - -The handler `DO_MATH_handler` is called when the `MathSender` -component receives a `DO_MATH` command. -This handler overrides the corresponding pure virtual -function in the auto-generated base class. -Fill in the handler so that it looks like this: - -```c++ -void MathSender :: - DO_MATH_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq, - F32 val1, - MathOp op, - F32 val2 - ) -{ - this->tlmWrite_VAL1(val1); - this->tlmWrite_OP(op); - this->tlmWrite_VAL2(val2); - this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2); - this->mathOpOut_out(0, val1, op, val2); - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} -``` - -The first two arguments to the handler function provide -the command opcode and the command sequence number -(a unique identifier generated by the command dispatcher). -The remaining arguments are supplied when the command is sent, -for example, from the F Prime ground data system (GDS). -The implementation code does the following: - -1. Emit telemetry and events. - -1. Invoke the `mathOpOut` port to request that `MathReceiver` -perform the operation. - -1. Send a command response indicating success. -The command response goes out on the special port -`cmdResponseOut`. - -In F Prime, every execution of a command handler must end by -sending a command response. -The proper behavior of other framework components (e.g., command -dispatcher, command sequencer) depends upon adherence to this rule. - -**Check the build:** -Run `fprime-util build` again to make sure that everything still builds. - -**Fill in the mathResultIn handler:** -You should see a stub handler for the `mathResultIn` -port that looks like this: - -```c++ -void MathSender :: - mathResultIn_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) -{ - // TODO -} -``` - -The handler `mathResultIn_handler` is called when the `MathReceiver` -component code returns a result by invoking the `mathResultIn` port. -Again the handler overrides the corresponding pure virtual -function in the auto-generated base class. -Fill in the handler so that it looks like this: - -```c++ -void MathSender :: - mathResultIn_handler( - const NATIVE_INT_TYPE portNum, - F32 result - ) -{ - this->tlmWrite_RESULT(result); - this->log_ACTIVITY_HI_RESULT(result); -} -``` - -The implementation code emits the result on the `RESULT` -telemetry channel and as a `RESULT` event report. - -**Check the build:** -Run `fprime-util build`. - - -### Write and Run Unit Tests - -**Unit tests** are an important part of FSW development. -At the component level, unit tests typically invoke input ports, send commands, -and check for expected values on output ports (including telemetry and event -ports). - -We will carry out the unit testing for the `MathSender` component -in three steps: - -1. Set up the unit test environment - -1. Write and run one unit test - -1. Write and run additional unit tests - - -#### Set Up the Unit Test Environment - -**Create the stub Tester class:** -In the directory `Ref/MathSender`, run `mkdir -p test/ut`. -This will create the directory where -the unit tests will reside. - -**Update Ref/MathSender/CMakeLists.txt:** -Go back to the directory `Ref/MathSender`. -Add the following lines to `CMakeLists.txt`: - -```cmake -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" -) -register_fprime_ut() -``` - -**Generate the unit test stub:** -We will now generate a stub implementation of the unit tests. -This stub contains all the boilerplate necessary to write and -run unit tests against the `MathSender` component. -In a later step, we will fill in the stub with tests. - -1. If you have not yet run `fprime-util generate --ut`, - then do so now. This step generates the CMake build cache for the unit - tests. - -1. Run the command `fprime-util impl --ut`. - It should generate files `Tester.cpp`, `Tester.hpp`, and `TestMain.cpp`. - -1. Move these files to the `test/ut` directory: - - ```bash - mv Tester.* TestMain.cpp test/ut - ``` - -**Update Ref/MathSender/CMakeLists.txt:** -Open `MathSender/CMakeLists.txt` and update the definition of -`UT_SOURCE_FILES` by adding your new test files: - -```cmake -# Register the unit test build -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut/main.cpp" -) -register_fprime_ut() -``` - -**Run the build:** -Now we can check that the unit test build is working. -Run `fprime-util build --ut`. -Everything should build without errors. - -**Inspect the generated code:** -The unit test build generates some code to support unit testing. -The code is located at `Ref/build-fprime-automatic-native-ut/Ref/MathSender`. -This directory contains two auto-generated classes: - -1. `MathSenderGTestBase`: This is the direct base -class of `Tester`. -It provides a test interface implemented with Google Test -macros. - -1. `MathSenderTesterBase`: This is the direct base -class of `MathSenderGTestBase`. -It provides basic features such as histories of port -invocations. -It is not specific to Google Test, so you can -use this class without Google Test if desired. - -You can look at the header files for these generated classes -to see what operations they provide. -In the next sections we will provide some example uses -of these operations. - -#### Write and Run One Test - -Now we will write a unit test that exercises the -`DO_MATH` command. -We will do this in three phases: - -1. In the `Tester` class, add a helper function for sending the command and -checking the responses. -That way multiple tests can reuse the same code. - -1. In the `Tester` class, write a test function that -calls the helper to run a test. - -1. In the `main` function, write a Google Test macro -that invokes the test function. - -1. Run the test. - -**Add a helper function:** -Go into the directory `Ref/MathSender/test/ut`. -In the file `Tester.hpp`, add the following lines -to the section entitled "Helper methods": - -```c++ -//! Test a DO_MATH command -void testDoMath(MathOp op); -``` - -In the file `Tester.cpp`, add the corresponding -function body: - -```c++ -void Tester :: - testDoMath(MathOp op) -{ - - // Pick values - - const F32 val1 = 2.0; - const F32 val2 = 3.0; - - // Send the command - - // pick a command sequence number - const U32 cmdSeq = 10; - // send DO_MATH command - this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - - // Verify command receipt and response - - // verify command response was sent - ASSERT_CMD_RESPONSE_SIZE(1); - // verify the command response was correct as expected - ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK); - - // Verify operation request on mathOpOut - - // verify that one output port was invoked overall - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // verify that the math operation port was invoked once - ASSERT_from_mathOpOut_SIZE(1); - // verify the arguments of the operation port - ASSERT_from_mathOpOut(0, val1, op, val2); - - // Verify telemetry - - // verify that 3 channels were written - ASSERT_TLM_SIZE(3); - // verify that the desired telemetry values were sent once - ASSERT_TLM_VAL1_SIZE(1); - ASSERT_TLM_VAL2_SIZE(1); - ASSERT_TLM_OP_SIZE(1); - // verify that the correct telemetry values were sent - ASSERT_TLM_VAL1(0, val1); - ASSERT_TLM_VAL2(0, val2); - ASSERT_TLM_OP(0, op); - - // Verify event reports - - // verify that one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_COMMAND_RECV_SIZE(1); - // verify the correct event arguments were sent - ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2); - -} -``` - -This function is parameterized over different -operations. -It is divided into five sections: sending the command, -checking the command response, checking the output on -`mathOpOut`, checking telemetry, and checking events. -The comments explain what is happening in each section. -For further information about the F Prime unit test -interface, see the F Prime User's Guide. - -Notice that after sending the command to the component, we call -the function `doDispatch` on the component. -We do this in order to simulate the behavior of the active -component in a unit test environment. -In a flight configuration, the component has its own thread, -and the thread blocks on the `doDispatch` call until another -thread puts a message on the queue. -In a unit test context, there is only one thread, so the pattern -is to place work on the queue and then call `doDispatch` on -the same thread. - -There are a couple of pitfalls to watch out for with this pattern: - -1. If you put work on the queue and forget to call `doDispatch`, -the work won't get dispatched. -Likely this will cause a unit test failure. - -1. If you call `doDispatch` without putting work on the queue, -the unit test will block until you kill the process (e.g., -with control-C). - -**Write a test function:** -Next we will write a test function that calls -`testDoMath` to test an `ADD` operation. -In `Tester.hpp`, add the following line in the -section entitled "Tests": - -```c++ -//! Test an ADD command -void testAddCommand(); -``` - -In `Tester.cpp`, add the corresponding function -body: - -```c++ -void Tester :: - testAddCommand() -{ - this->testDoMath(MathOp::ADD); -} -``` - -This function calls `testDoMath` to test an `ADD` command. - -**Write a test macro:** -Add the following code to the file `main.cpp`, -before the definition of the `main` function: - -```c++ -TEST(Nominal, AddCommand) { - Ref::Tester tester; - tester.testAddCommand(); -} -``` - -The `TEST` macro is an instruction to Google Test to run a test. -`Nominal` is the name of a test suite. -We put this test in the `Nominal` suite because it addresses -nominal (expected) behavior. -`AddCommand` is the name of the test. -Inside the body of the macro, the first line declares a new -object `tester` of type `Tester`. -We typically declare a new object for each unit test, so that -each test starts in a fresh state. -The second line invokes the function `testAddCommand` -that we wrote in the previous section. - -**Run the test:** -Go back to directory `Ref/MathSender`. -Run the command `fprime-util check`. -The build system should compile and run the unit -tests. -You should see output indicating that the test ran -and passed. - -As an exercise, try the following: - -1. Change the behavior of the component -so that it does something incorrect. -For example, try adding one to a telemetry -value before emitting it. - -1. Rerun the test and observe what happens. - -#### Write and Run More Tests - -**Add more command tests:** -Try to follow the pattern given in the previous -section to add three more tests, one each -for operations `SUB`, `MUL`, and `DIV`. -Most of the work should be done in the helper -that we already wrote. -Each new test requires just a short test function -and a short test macro. - -Run the tests to make sure everything compiles and -the tests pass. - -**Add a result test:** -Add a test for exercising the scenario in which the `MathReceiver` -component sends a result back to `MathSender`. - -1. Add the following function signature in the "Tests" section of `Tester.hpp`: - - ```c++ - //! Test receipt of a result - void testResult(); - ``` - -1. Add the corresponding function body in `Tester.cpp`: - - ```c++ - void Tester :: - testResult() - { - // Generate an expected result - const F32 result = 10.0; - // reset all telemetry and port history - this->clearHistory(); - // call result port with result - this->invoke_to_mathResultIn(0, result); - // retrieve the message from the message queue and dispatch the command to the handler - this->component.doDispatch(); - // verify one telemetry value was written - ASSERT_TLM_SIZE(1); - // verify the desired telemetry channel was sent once - ASSERT_TLM_RESULT_SIZE(1); - // verify the values of the telemetry channel - ASSERT_TLM_RESULT(0, result); - // verify one event was sent - ASSERT_EVENTS_SIZE(1); - // verify the expected event was sent once - ASSERT_EVENTS_RESULT_SIZE(1); - // verify the expect value of the event - ASSERT_EVENTS_RESULT(0, result); - } - ``` - - This code is similar to the helper function in the previous section. - The main difference is that it invokes a port directly - (the `mathResultIn` port) instead of sending a command. - -1. Add the following test macro to `main.cpp`: - - ```c++ - TEST(Nominal, Result) { - Ref::Tester tester; - tester.testResult(); - } - ``` - -1. Run the tests. -Again you can try altering something in the component code -to see what effect it has on the test output. - - -#### Exercise: Random Testing - -F Prime provides a module called `STest` -that provides helper classes and functions for writing -unit tests. -As an exercise, use the interface provided by -`STest/STest/Pick.hpp` to pick random values to use in the -tests instead of using hard-coded values such as 2.0, 3.0, -and 10. - -**Modifying the code:** You will need to do the following: - -1. Add `#include "STest/Pick/Pick.hpp"` to `Tester.cpp`. - -1. Add the following - line to `Ref/MathSender/CMakeLists.txt`, before `register_fprime_ut`: - - ```cmake - set(UT_MOD_DEPS STest) - ``` - - This line tells the build system to make the unit test build - depend on the `STest` build module. - -1. Add `#include "STest/Random/Random.hpp"` to `main.cpp`. - -1. Add the following line to the `main` function of `main.cpp`, - just before the return statement: - - ```c++ - STest::Random::seed(); - ``` - - This line seeds the random number generator used by STest. - -**Running the tests:** -Recompile and rerun the tests. -Now go to -`Ref/build-fprime-automatic-native-ut/Ref/MathSender` and inspect the -file `seed-history`. -This file is a log of random seed values. -Each line represents the seed used in the corresponding run. - -**Fixing the random seed:** -Sometimes you may want to run a test with a particular seed value, -e.g., for replay debugging. -To do this, put the seed value into a file `seed` in the same -directory as `seed-history`. -If the file `seed` exists, then STest will use the seed it contains instead -of generating a new seed. - -Try the following: - -1. Copy the last value _S_ of `seed-history` into `seed`. - -1. In `Ref/MathSender`, re-run the unit tests a few times. - -1. Inspect `Ref/build-fprime-automatic-native-ut/Ref/MathSender/seed-history`. -You should see that the value _S_ was used in the runs you just did -(corresponding to the last few entries in `seed-history`). - -### Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathSender`. - -## The MathReceiver Component - -Now we will build and test the `MathReceiver` component. -We will use the same five steps as for the -`MathSender` component. - -### Construct the FPP Model - -**Create the MathReceiver directory:** -Create the directory `Ref/MathReceiver`. - -**Create the FPP model file:** -In directory `Ref/MathReceiver`, create a file -`MathReceiver.fpp` with the following contents: - -```fpp -!include ../MathReceiver/MathReceiver.fpp -``` - -This code defines a component `Ref.MathReceiver`. -The component is **queued**, which means it has a queue -but no thread. -Work occurs when the thread of another component invokes -the `schedIn` port of this component. - -We have divided the specifiers of this component into six groups: - -1. **General ports:** There are three ports: -an input port `mathOpIn` for receiving a math operation, -an output port `mathResultOut` for sending a math result, and -an input port `schedIn` for receiving invocations from the scheduler. -`mathOpIn` is asynchronous. -That means invocations of `mathOpIn` put messages on a queue. -`schedIn` is synchronous. -That means invocations of `schedIn` immediately call the -handler function to do work. - -1. **Special ports:** -As before, there are special ports for commands, events, telemetry, -and time. -There are also special ports for getting and setting parameters. -We will explain the function of these ports below. - -1. **Parameters:** There is one **parameter**. -A parameter is a constant that is configurable by command. -In this case there is one parameter `FACTOR`. -It has the default value 1.0 until its value is changed by command. -When doing math, the `MathReceiver` component performs the requested -operation and then multiplies by this factor. -For example, if the arguments of the `mathOpIn` port -are _v1_, `ADD`, and _v2_, and the factor is _f_, -then the result sent on `mathResultOut` is -_(v1 + v2) f_. - -1. **Events:** There are three event reports: - - 1. `FACTOR_UPDATED`: Emitted when the `FACTOR` parameter - is updated by command. - This event is **throttled** to a limit of three. - That means that after the event is emitted three times - it will not be emitted any more, until the throttling - is cleared by command (see below). - - 1. `OPERATION_PERFORMED`: Emitted when this component - performs a math operation. - - 1. `THROTTLE_CLEARED`: Emitted when the event throttling - is cleared. - -1. **Commands:** There is one command for clearing -the event throttle. - -1. **Telemetry:** -There two telemetry channels: one for reporting -the last operation received and one for reporting -the factor parameter. - -For the parameters, events, commands, and telemetry, we chose -to put in all the opcodes and identifiers explicitly. -These can also be left implicit, as in the `MathSender` -component example. -For more information, see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components). - -### Add the Model to the Project - -Follow the steps given for the -`MathSender` component. - -### Build the Stub Implementation - -Follow the same steps as for the -`MathSender` component. - -### Complete the Implementation - -**Fill in the mathOpIn handler:** -In `MathReceiver.cpp`, complete the implementation of -`mathOpIn_handler` so that it looks like this: - -```cpp -void MathReceiver :: - mathOpIn_handler( - const NATIVE_INT_TYPE portNum, - F32 val1, - const MathOp& op, - F32 val2 - ) -{ - - // Get the initial result - F32 res = 0.0; - switch (op.e) { - case MathOp::ADD: - res = val1 + val2; - break; - case MathOp::SUB: - res = val1 - val2; - break; - case MathOp::MUL: - res = val1 * val2; - break; - case MathOp::DIV: - res = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - - // Get the factor value - Fw::ParamValid valid; - F32 factor = paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - - // Multiply result by factor - res *= factor; - - // Emit telemetry and events - this->log_ACTIVITY_HI_OPERATION_PERFORMED(op); - this->tlmWrite_OPERATION(op); - - // Emit result - this->mathResultOut_out(0, res); - -} -``` - -This code does the following: - -1. Compute an initial result based on the input values and -the requested operation. - -1. Get the value of the factor parameter. -Check that the value is a valid value from the parameter -database or a default parameter value. - -1. Multiply the initial result by the factor to generate -the final result. - -1. Emit telemetry and events. - -1. Emit the result. - -Note that in step 1, `op` is an enum (a C++ class type), and `op.e` -is the corresponding numeric value (an integer type). -Note also that in the `default` case we deliberately fail -an assertion. -This is a standard pattern for exhaustive case checking. -We should never hit the assertion. -If we do, then a bug has occurred: we missed a case. - -**Fill in the schedIn handler:** -In `MathReceiver.cpp`, complete the implementation of -`schedIn_handler` so that it looks like this: - -```c++ -void MathReceiver :: - schedIn_handler( - const NATIVE_INT_TYPE portNum, - NATIVE_UINT_TYPE context - ) -{ - U32 numMsgs = this->m_queue.getNumMsgs(); - for (U32 i = 0; i < numMsgs; ++i) { - (void) this->doDispatch(); - } -} -``` - -This code dispatches all the messages on the queue. -Note that for a queued component, we have to do this -dispatch explicitly in the `schedIn` handler. -For an active component, the framework auto-generates -the dispatch code. - -**Fill in the CLEAR_EVENT_THROTTLE command handler:** -In `MathReceiver.cpp`, complete the implementation of -`CLEAR_EVENT_THROTTLE_cmdHandler` so that it looks like this: - -```c++ -void MathReceiver :: - CLEAR_EVENT_THROTTLE_cmdHandler( - const FwOpcodeType opCode, - const U32 cmdSeq - ) -{ - // clear throttle - this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear(); - // send event that throttle is cleared - this->log_ACTIVITY_HI_THROTTLE_CLEARED(); - // reply with completion status - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} -``` - -The call to `log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear` clears -the throttling of the `FACTOR_UPDATED` event. -The next two lines send a notification event and send -a command response. - -**Add a parameterUpdated function:** -Add the following function to `MathReceiver.cpp`. -You will need to add the corresponding function header -to `MathReceiver.hpp`. - -```c++ -void MathReceiver :: - parameterUpdated(FwPrmIdType id) -{ - switch (id) { - case PARAMID_FACTOR: { - Fw::ParamValid valid; - F32 val = this->paramGet_FACTOR(valid); - FW_ASSERT( - valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT, - valid.e - ); - this->log_ACTIVITY_HI_FACTOR_UPDATED(val); - break; - } - default: - FW_ASSERT(0, id); - break; - } -} -``` - -This code implements an optional function that, if present, -is called when a parameter is updated by command. -The parameter identifier is passed in as the `id` argument -of the function. -Here we do the following: - -1. If the parameter identifier is `PARAMID_FACTOR` (the parameter -identifier corresponding to the `FACTOR` parameter), -then get the parameter value and emit an event report. - -1. Otherwise fail an assertion. -This code should never run, because there are no other -parameters. - -### Write and Run Unit Tests - -#### Set up the Unit Test Environment - -1. Follow the steps given for the -`MathSender` component. - -1. Follow the steps given under **Modifying the code** -for the -random testing exercise, -so that you can use STest to pick random values. - -#### Add Helper Code - -**Add a ThrottleState enum class:** -Add the following code to the beginning of the -`Tester` class in `Tester.hpp`: - -```c++ -private: - - // ---------------------------------------------------------------------- - // Types - // ---------------------------------------------------------------------- - - enum class ThrottleState { - THROTTLED, - NOT_THROTTLED - }; -``` - -This code defines a C++ enum class for recording whether an -event is throttled. - -**Add helper functions:** -Add each of the functions described below to the -"Helper methods" section of `Tester.cpp`. -For each function, you must add -the corresponding function prototype to `Tester.hpp`. -After adding each function, compile the unit tests -to make sure that everything still compiles. -Fix any errors that occur. - -Add a `pickF32Value` function. - -```c++ -F32 Tester :: - pickF32Value() -{ - const F32 m = 10e6; - return m * (1.0 - 2 * STest::Pick::inUnitInterval()); -} -``` - -This function picks a random `F32` value in the range -_[ -10^6, 10^6 ]_. - -Add a `setFactor` function. - -```c++ -void Tester :: - setFactor( - F32 factor, - ThrottleState throttleState - ) -{ - // clear history - this->clearHistory(); - // set the parameter - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - const U32 instance = STest::Pick::any(); - const U32 cmdSeq = STest::Pick::any(); - this->paramSend_FACTOR(instance, cmdSeq); - if (throttleState == ThrottleState::NOT_THROTTLED) { - // verify the parameter update notification event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1); - ASSERT_EVENTS_FACTOR_UPDATED(0, factor); - } - else { - ASSERT_EVENTS_SIZE(0); - } -} -``` - -This function does the following: - -1. Clear the test history. - -1. Send a command to the component to set the `FACTOR` parameter -to the value `factor`. - -1. If `throttleState` is `NOT_THROTTLED`, then check -that the event was emitted. -Otherwise check that the event was throttled (not emitted). - -Add a function `computeResult` to `Tester.cpp`. - -```c++ -F32 Tester :: - computeResult( - F32 val1, - MathOp op, - F32 val2, - F32 factor - ) -{ - F32 result = 0; - switch (op.e) { - case MathOp::ADD: - result = val1 + val2; - break; - case MathOp::SUB: - result = val1 - val2; - break; - case MathOp::MUL: - result = val1 * val2; - break; - case MathOp::DIV: - result = val1 / val2; - break; - default: - FW_ASSERT(0, op.e); - break; - } - result *= factor; - return result; -} -``` - -This function carries out the math computation of the -math component. -By running this function and comparing, we can -check the output of the component. - -Add a `doMathOp` function to `Tester.cpp`. - -```c++ -void Tester :: - doMathOp( - MathOp op, - F32 factor - ) -{ - - // pick values - const F32 val1 = pickF32Value(); - const F32 val2 = pickF32Value(); - - // clear history - this->clearHistory(); - - // invoke operation port with add operation - this->invoke_to_mathOpIn(0, val1, op, val2); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - - // verify the result of the operation was returned - - // check that there was one port invocation - ASSERT_FROM_PORT_HISTORY_SIZE(1); - // check that the port we expected was invoked - ASSERT_from_mathResultOut_SIZE(1); - // check that the component performed the operation correctly - const F32 result = computeResult(val1, op, val2, factor); - ASSERT_from_mathResultOut(0, result); - - // verify events - - // check that there was one event - ASSERT_EVENTS_SIZE(1); - // check that it was the op event - ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1); - // check that the event has the correct argument - ASSERT_EVENTS_OPERATION_PERFORMED(0, op); - - // verify telemetry - - // check that one channel was written - ASSERT_TLM_SIZE(1); - // check that it was the op channel - ASSERT_TLM_OPERATION_SIZE(1); - // check for the correct value of the channel - ASSERT_TLM_OPERATION(0, op); - -} -``` - -This function is similar to the `doMath` helper function that -we wrote for the `MathSender` component. -Notice that the method for invoking a port is different. -Since the component is queued, we don't call `doDispatch` -directly. -Instead we invoke `schedIn`. - -#### Write and Run Tests - -For each of the tests described below, you must add the -corresponding function prototype to `Tester.hpp` -and the corresponding test macro to `main.cpp`. -If you can't remember how to do it, look back at the -`MathSender` examples. -After writing each test, run all the tests and make sure -that they pass. - -**Write an ADD test:** -Add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testAdd() -{ - // Set the factor parameter by command - const F32 factor = pickF32Value(); - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - // Do the add operation - this->doMathOp(MathOp::ADD, factor); -} -``` - -This function calls the `setFactor` helper function -to set the factor parameter. -Then it calls the `doMathOp` function to -do a math operation. - -**Write a SUB test:** -Add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testSub() -{ - // Set the factor parameter by loading parameters - const F32 factor = pickF32Value(); - this->paramSet_FACTOR(factor, Fw::ParamValid::VALID); - this->component.loadParameters(); - // Do the operation - this->doMathOp(MathOp::SUB, factor); -} -``` - -This test is similar to `testAdd`, but it shows -another way to set a parameter. -`testAdd` showed how to set a parameter by command. -You can also set a parameter by initialization, as follows: - -1. Call the `paramSet` function as shown. -This function sets the parameter value in -the part of the test harness that mimics the behavior of the -parameter database component. - -1. Call the `loadParameters` function as shown. -In flight, the function `loadParameters` is typically called at the -start of FSW to load the parameters from the database; -here it loads the parameters from the test harness. -There is no command to update a parameter, so `parameterUpdated` -is not called, and no event is emitted. - -As before, after setting the parameter we call `doMathOp` -to do the operation. - -**Write a MUL test:** -This test is the same as the ADD test, except that it -uses MUL instead of add. - -**Write a DIV test:** -This test is the same as the SUB test, except that it -uses DIV instead of SUB. - -**Write a throttle test:** -Add the following constant definition to the top of the `Tester.cpp` file: - -```C++ -#define CMD_SEQ 42 -``` - -Then add the following function to the "Tests" section of `Tester.cpp`: - -```c++ -void Tester :: - testThrottle() -{ - - // send the number of commands required to throttle the event - // Use the autocoded value so the unit test passes if the - // throttle value is changed - const F32 factor = pickF32Value(); - for ( - U16 cycle = 0; - cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE; - cycle++ - ) { - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - } - - // Event should now be throttled - this->setFactor(factor, ThrottleState::THROTTLED); - - // send the command to clear the throttle - this->sendCmd_CLEAR_EVENT_THROTTLE(INSTANCE, CMD_SEQ); - // invoke scheduler port to dispatch message - const U32 context = STest::Pick::any(); - this->invoke_to_schedIn(0, context); - // verify clear event was sent - ASSERT_EVENTS_SIZE(1); - ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1); - - // Throttling should be cleared - this->setFactor(factor, ThrottleState::NOT_THROTTLED); - -} -``` - -This test first loops over the throttle count, which is stored -for us in the constant `EVENTID_FACTOR_UPDATED_THROTTLE` -of the `MathReceiver` component base class. -On each iteration, it calls `setFactor`. -At the end of this loop, the `FACTOR_UPDATED` event should be -throttled. - -Next the test calls `setFactor` with a second argument of -`ThrottleState::THROTTLED`. -This code checks that the event is throttled. - -Next the test sends the command `CLEAR_EVENT_THROTTLE`, -checks for the corresponding notification event, -and checks that the throttling is cleared. - -### Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/MathReceiver`. - -### Exercises - -#### Adding Telemetry - -Add a telemetry channel that records the number of math -operations performed. - -1. Add the channel to the FPP model. - -1. In the component implementation class, add a member -variable `numMathOps` of type `U32`. -Initialize the variable to zero in the class constructor. - -1. Revise the `mathOpIn` handler so that it increments -`numMathOps` and emits the updated value as telemetry. - -1. Revise the unit tests to cover the new behavior. - -#### Error Handling - -Think about what will happen if the floating-point -math operation performed by `MathReceiver` causes an error. -For example, suppose that `mathOpIn` is invoked with `op = DIV` -and `val2 = 0.0`. -What will happen? -As currently designed and implemented, the `MathReceiver` -component will perform the requested operation. -On some systems the result will be `INF` (floating-point infinity). -In this case, the result will be sent back to `MathSender` -and reported in the usual way. -On other systems, the hardware could issue a floating-point exception. - -Suppose you wanted to handle the case of division by zero -explicitly. -How would you change the design? -Here are some questions to think about: - -1. How would you check for division by zero? -Note that `val2 = 0.0` is not the only case in which a division -by zero error can occur. -It can also occur for very small values of `val2`. - -1. Should the error be caught in `MathSender` or `MathReceiver`? - -1. Suppose the design says that `MathSender` catches the error, -and so never sends requests to `MathReceiver` to divide by zero. -What if anything should `MathReceiver` do if it receives -a divide by zero request? -Carry out the operation normally? -Emit a warning? -Fail a FSW assertion? - -1. If the error is caught by `MathReceiver`, does the -interface between the components have to change? -If so, how? -What should `MathSender` do if `MathReceiver` -reports an error instead of a valid result? - -Revise the MathSender and MathReceiver components to implement your -ideas. -Add unit tests covering the new behavior. - -## Updating the Ref Deployment - -The next step in the tutorial is to define instances of the -`MathSender` and `MathReceiver` components and add them -to the `Ref` topology. - -### Defining the Component Instances - -Go to the directory `Ref/Top` and open the file `instances.fpp`. -This file defines the instances used in the topology for the -`Ref` application. -Update this file as described below. - -**Define the mathSender instance:** -At the end of the section entitled "Active component instances," -add the following lines: - -```fpp -instance mathSender: Ref.MathSender base id 0xE00 \ - queue size Default.QUEUE_SIZE \ - stack size Default.STACK_SIZE \ - priority 100 -``` - -This code defines an instance `mathSender` of component -`MathSender`. -It has **base identifier** 0xE00. -FPP adds the base identifier to each the relative identifier -defined in the component to compute the corresponding -identifier for the instance. -For example, component `MathSender` has a telemetry channel -`MathOp` with identifier 1, so instance `mathSender` -has a command `MathOp` with identifier 0xE01. - -The following lines define the queue size, stack size, -and thread priority for the active component. -Here we give `mathSender` the default queue size -and stack size and a priority of 100. - -**Define the mathReceiver instance:** -At the end of the section "Queued component instances," -add the following lines: - -```fpp -instance mathReceiver: Ref.MathReceiver base id 0x2700 \ - queue size Default.QUEUE_SIZE -``` - -This code defines an instance `mathReceiver` of -component `MathReceiver`. -It has base identifier 0x2700 and the default queue size. - -**More information:** -For more information on defining component instances, -see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Component-Instances). - -### Updating the Topology - -Go to the directory `Ref/Top` and open the file `topology.fpp`. -This file defines the topology for the `Ref` application. -Update this file as described below. - -**Add the new instances:** -You should see a list of instances, each of which begins -with the keyword `instance`. -After the line `instance linuxTime`, add the following -lines: - -```fpp -instance mathSender -instance mathReceiver -``` - -These lines add the `mathSender` and `mathReceiver` -instances to the topology. - -**Check for unconnected ports:** -Run the following commands in the `Ref/Top` directory: - -```bash -fprime-util fpp-check -u unconnected.txt -cat unconnected.txt -``` - -You should see a list of ports -that are unconnected in the `Ref` topology. -Those ports will include the ports for the new instances -`mathSender` and `mathReceiver`. - -**Connect mathReceiver to rate group 1:** -Find the line that starts `connections RateGroups`. -This is the beginning of the definition of the `RateGroups` -connection graph. -After the last entry for the `rateGroup1Comp` (rate group 1) add -the following line: - -```fpp -rateGroup1Comp.RateGroupMemberOut -> mathReceiver.schedIn -``` - -This line adds the connection that drives the `schedIn` -port of the `mathReceiver` component instance. - -**Re-run the check for unconnected ports:** -You should see that `mathReceiver.schedIn` is now connected -(it no longer appears in the list). - -**Add the Math connections:** -Find the Uplink connections that begin with the line -`connections Uplink`. -After the block of that definition, add the following -lines: - -```fpp -connections Math { - mathSender.mathOpOut -> mathReceiver.mathOpIn - mathReceiver.mathResultOut -> mathSender.mathResultIn -} -``` - -These lines add the connections between the `mathSender` -and `mathReceiver` instances. - -**Re-run the check for unconnected ports:** -When this capability exists, you will be able to see -that the `mathSender` and `mathReceiver` ports are connected. - -**More information:** -For more information on defining topologies, -see -[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Topologies). - -### Building the Ref Deployment - -Go to the `Ref` directory. -Run `fprime-util build --jobs 4`. -The updated deployment should build without errors. -The generated files are located at -`Ref/build-fprime-automatic-native/Ref/Top`. - -### Visualizing the Ref Topology - -Now we will see how to create a visualization (graphical rendering) -of the Ref topology. - -**Generate the layout:** -For this step, we will use the F Prime Layout (FPL) tool. -If FPL is not installed on your system, then install it now: -clone [this repository](https://github.com/fprime-community/fprime-layout) -and follow the instructions. - -In directory `Ref/Top`, run the following commands in an sh-compatible -shell such as bash. -If you are using a different shell, you can run `sh` -to enter the `sh` shell, run these commands, and enter -`exit` when done. -Or you can stay in your preferred shell and adjust these commands -appropriately. - -```bash -cp ../build-fprime-automatic-native/Ref/Top/RefTopologyAppAi.xml . -mkdir visual -cd visual -fpl-extract-xml < ../RefTopologyAppAi.xml -mkdir Ref -for file in `ls *.xml` -do -echo "laying out $file" -base=`basename $file .xml` -fpl-convert-xml $file | fpl-layout > Ref/$base.json -done -``` - -This step extracts the connection graphs from the topology XML and -converts each one to a JSON layout file. - -**Render the layout:** -For this step, we will use the F Prime Visualizer (FPV) tool. -If FPV is not installed on your system, then install it now: -clone [this repository](https://github.com/fprime-community/fprime-visual) -and follow the instructions. - -In directory `Ref/Top`, run the following commands in an sh-compatible -shell. -Replace `[path to fpv root]` with the path to the -root of the FPV repo on your system. - -```bash -echo DATA_FOLDER=Ref/ > .fpv-env -nodemon [path to fpv root]/server/index.js ./.fpv-env -``` - -You should see the FPV server application start up on the -console. - -Now open a browser and navigate to `http://localhost:3000`. -You should see a Topology menu at the top of the window -and a rendering of the Command topology below. -Select Math from the topology menu. -You should see a rendering of the Math topology. -It should look similar to the -topology diagram shown above. - -You can use the menu to view other topology graphs. -When you are done, close the browser window and -type control-C in the console to shut down the FPV server. - -### Reference Implementation - -A reference implementation for this section is available at -`docs/Tutorials/MathComponent/Top`. -To build this implementation, copy the files -`instances.fpp` and `topology.fpp` from -that directory to `Ref/Top`. - -## Running the Ref Deployment - -Now we will use the F Prime Ground Data System (GDS) to run the Ref deployment. -Go to the `Ref` directory and run `fprime-gds`. -You should see some activity on the console. -The system is starting the Ref deployment executable, starting the GDS, -and connecting them over the local network on your machine. -After several seconds, a browser window should appear. - -### Sending a Command - -At the top of the window are several buttons, each of which corresponds to -a GDS view. -Select the Commanding button (this is the view that is selected -when you first start the GDS). -In the Mnemonic menu, start typing `mathSender.DO_MATH` in the text box. -As you type, the GDS will filter the menu selections. -When only one choice remains, stop typing and press return. -You should see three boxes appear: - -1. A text box for entering `val1`. - -1. A menu for entering `op`. - -1. A text box for entering `val2`. - -Fill in the arguments corresponding to the operation `1 + 2`. -You can use the tab key to move between the boxes. -When you have done this, click the Send Command button. -You should see a table entry at the bottom of the window -indicating that the command was sent. - -### Checking Events - -Now click on the Events button at the top of the window. -The view changes to the Events tab. -You should see events indicating that the command you sent was -dispatched, received, and completed. -You should also see events indicating that `mathReceiver` -performed an `ADD` operation and `mathSender` -received a result of 3.0. - -### Checking Telemetry - -Click on the Channels button at the top of the window. -You should see a table of telemetry channels. -Each row corresponds to the latest value of a telemetry -channel received by the GDS. -You should see the channels corresponding to the input -values, the operation, and the result. - -### Setting Parameters - -Go back to the Commanding tab. -Select the command `mathReceiver.FACTOR_PRM_SET`. -This is an auto-generated command for setting the -parameter `FACTOR`. -Type the value 2.0 in the `val` box and click Send Command. -Check the events to see that the command was dispatched -and executed. -You should also see the events sent by the code -that you implemented. - -In the Commanding tab, issue the command `1 + 2` again. -Check the Events tab. -Because the factor is now 2.0, you should see a result -value of 6.0. - -### Saving Parameters - -When you set a parameter by command, the new parameter -value resides in the component that receives the command. -At this point, if you stop and restart FSW, the parameter -will return to its original value (the value before you -sent the command). - -At some point you may wish to update parameters more permanently. -You can do this by saving them to non-volatile storage. -For the Ref application, "non-volatile storage" means the -file system on your machine. - -To save the parameter `mathReceiver.FACTOR` to non-volatile storage, -do the following: - -1. Send the command `mathReceiver.FACTOR_PRM_SAVE`. -This command saves the parameter value to the **parameter database**, -which is a standard F Prime component for storing system parameters. - -1. Send the command `prmDb.PRM_SAVE_FILE`. -This command saves the parameter values in the parameter database -to non-volatile storage. - -Note that saving parameters is a two-step process. -The first step copies a single parameter from a component -to the database. -The second step saves all parameters in the database -to the disk. -If you do only the first step, the parameter will not be -saved to the disk. - -### GDS Logs - -As it runs, the GDS writes a log into a subdirectory of `Ref/logs`. -The subdirectory is stamped with the current date. -Go into the directory for the run you just performed. -(If the GDS is still running, you will have to do this in a -different shell.) -You should see the following logs, among others: - -* `Ref.log`: FSW console output. - -* `command.log`: Commands sent. - -* `event.log`: Event reports received. - -* `channel.log`: Telemetry points received. - -You can also view these logs via the GDS browser interface. -Click the Logs tab to go the Logs view. -Select the log you wish to inspect from the drop-down menu. -By default, there is no log selected. - -## Conclusion - -The Math Component tutorial has shown us how to create simple types, ports and -components for our application using the FPP modeling language. We have learned -how to use `fprime-util` to generate implementation stubs, the build cache, and -unit tests. We learned how to define our topology and use tools provided by -F´ to check and visualize the topology. Lastly, we learned how to use the -ground system to interact with our deployment. - -The user is now directed back to the [Tutorials](../README.md) for further -reading or to the [Cross-Compilation Tutorial](../CrossCompilation/Tutorial.md) -for instructions on how to cross-compile the Ref application completed in this -tutorial for the Raspberry Pi. diff --git a/docs/Tutorials/MathComponent/md/bad-refs.awk b/docs/Tutorials/MathComponent/md/bad-refs.awk deleted file mode 100644 index 62272e5b27..0000000000 --- a/docs/Tutorials/MathComponent/md/bad-refs.awk +++ /dev/null @@ -1,15 +0,0 @@ -# ---------------------------------------------------------------------- -# Check for refs that are not of the form -# #... (local ref) or http... (internet URL) -# The most common error here is to forget the # at the start of a local ref -# ---------------------------------------------------------------------- - -/&2 - exit 1 -fi - -./defined-tags < $1 > defined-tags.txt -./used-tags < $1 > used-tags.txt -undefined_tags=`./undefined-tags defined-tags.txt used-tags.txt` -if test -n "$undefined_tags" -then - echo "WARNING: References use undefined tags" 1>&2 - echo $undefined_tags | tr ' ' '\n' -fi - -bad_refs=`awk -f bad-refs.awk $1 | tr '\n' ',' | sed s'/,$//'` -if test -n "$bad_refs" -then - echo "WARNING: Malformed references" - echo $bad_refs | tr ',' '\n' -fi diff --git a/docs/Tutorials/MathComponent/md/defined-tags b/docs/Tutorials/MathComponent/md/defined-tags deleted file mode 100755 index 71ac652e42..0000000000 --- a/docs/Tutorials/MathComponent/md/defined-tags +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e - -awk '/^ toc.md - -# Generate the final Markdown file -cat Tutorial.md | \ - awk -f tags.awk | \ - awk -f sections.awk | \ - awk -f include.awk \ - > ../Tutorial.md - -# Check for broken refs in the final Markdown file -./check-refs ../Tutorial.md diff --git a/docs/Tutorials/MathComponent/md/sections.awk b/docs/Tutorials/MathComponent/md/sections.awk deleted file mode 100644 index 687c333599..0000000000 --- a/docs/Tutorials/MathComponent/md/sections.awk +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env awk -f - -# ---------------------------------------------------------------------- -# sections.awk -# Add hierarchical section numbers to Markdown headers -# ---------------------------------------------------------------------- - -BEGIN { - MAX_LEVELS = 10 - in_code_block = 0 -} - -$1 ~ "^```" { in_code_block = !in_code_block } - -/^##+ / && !in_code_block { - new_level = length($1) - 1 - if (new_level > MAX_LEVELS) { - print "sections.awk: too many levels (" new_level ")" > "/dev/stderr" - exit(1) - } - ++levels[new_level] - for (i = new_level + 1; i <= MAX_LEVELS; ++i) - levels[i] = 0 - printf("%s ", $1) - for (i = 1; i <= new_level; ++i) { - printf("%d.", levels[i]) - } - line = $0 - sub(/^#+ +/, "", line) - printf(" %s\n", line) - next -} - -{ print } diff --git a/docs/Tutorials/MathComponent/md/tags.awk b/docs/Tutorials/MathComponent/md/tags.awk deleted file mode 100644 index 018cf22d86..0000000000 --- a/docs/Tutorials/MathComponent/md/tags.awk +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env awk -f - -# ---------------------------------------------------------------------- -# tags.awk -# Insert anchor tags for Markdown sections -# ---------------------------------------------------------------------- - -BEGIN { - level = 0 - in_code_block = 0 -} - -$1 ~ /^```/ && in_code_block == 0 { in_code_block = 1; print; next } -$1 ~ /^```/ && in_code_block == 1 { in_code_block = 0; print; next } - -$1 ~ /^##+$/ && !in_code_block { - level = length($1) - level_tag = "" - for (i = 2; i <= NF; ++i) { - if (level_tag == "") - level_tag = $i - else - level_tag = level_tag "-" $i - } - gsub(/[,:]/, "", level_tag) - level_tags[level] = level_tag - tag = "" - for (i = 1; i <= level; ++i) { - if (tag == "") - tag = level_tags[i] - else - tag = tag "_" level_tags[i] - } - print "" -} - -{ print $0 } diff --git a/docs/Tutorials/MathComponent/md/toc.awk b/docs/Tutorials/MathComponent/md/toc.awk deleted file mode 100644 index 50f5e04f0e..0000000000 --- a/docs/Tutorials/MathComponent/md/toc.awk +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env awk -f - -# ---------------------------------------------------------------------- -# toc.awk -# Generate table of contents -# Must generate tags first -# ---------------------------------------------------------------------- - -BEGIN { - in_code_block = 0 - print "## Table of Contents" - print "" -} - -$1 ~ "^```" { in_code_block = !in_code_block } - -/^##+ / && !in_code_block { - indent_level = length($1) - 2 - tag = prev_line - sub(/^[^"]*"/, "", tag) - sub(/"[^"]*$/, "", tag) - header = $0 - sub(/^#* */, "", header) - for (i = 1; i <= indent_level; ++i) - printf(" ") - print "* " header "" -} - -{ - prev_line = $0 -} diff --git a/docs/Tutorials/MathComponent/md/undefined-tags b/docs/Tutorials/MathComponent/md/undefined-tags deleted file mode 100755 index e1a401eccf..0000000000 --- a/docs/Tutorials/MathComponent/md/undefined-tags +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -e - -if ! test $# -eq 2 -then - echo 'usage: undefined-tags defined-tags used-tags' 1>&2 - exit 1 -fi - -diff -u $1 $2 | grep '^+' | grep -v '++' | sed 's/^\+//' diff --git a/docs/Tutorials/MathComponent/md/used-tags b/docs/Tutorials/MathComponent/md/used-tags deleted file mode 100755 index 98cf79c0ef..0000000000 --- a/docs/Tutorials/MathComponent/md/used-tags +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e - -awk '/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∈nkhfB7ZEa%zW9xV_(ZuIS|d=qdH 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