Compare commits
No commits in common. "main" and "satrs-core-v0.1.0-alpha.1" have entirely different histories.
main
...
satrs-core
64
.github/workflows/ci.yml
vendored
@ -1,64 +0,0 @@
|
|||||||
name: ci
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
name: Check build
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- run: cargo check --release
|
|
||||||
|
|
||||||
test:
|
|
||||||
name: Run Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install nextest
|
|
||||||
uses: taiki-e/install-action@nextest
|
|
||||||
- run: cargo nextest run --all-features
|
|
||||||
- run: cargo test --doc --all-features
|
|
||||||
|
|
||||||
cross-check:
|
|
||||||
name: Check Cross-Compilation
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
target:
|
|
||||||
- armv7-unknown-linux-gnueabihf
|
|
||||||
- thumbv7em-none-eabihf
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
|
|
||||||
- run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
name: Check formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- run: cargo fmt --all -- --check
|
|
||||||
|
|
||||||
docs:
|
|
||||||
name: Check Documentation Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
name: Clippy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- run: cargo clippy -- -D warnings
|
|
7
.gitignore
vendored
@ -1,10 +1,5 @@
|
|||||||
target/
|
/target
|
||||||
|
|
||||||
output.log
|
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
output.log
|
|
||||||
|
|
||||||
output.log
|
|
||||||
|
|
||||||
/.idea/*
|
/.idea/*
|
||||||
!/.idea/runConfigurations
|
!/.idea/runConfigurations
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"satrs",
|
"satrs-core",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
"satrs-example",
|
"satrs-example",
|
||||||
"satrs-minisim",
|
|
||||||
"satrs-shared",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"embedded-examples/stm32f3-disco-rtic",
|
"satrs-example-stm32f3-disco",
|
||||||
"embedded-examples/stm32h7-rtic",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
76
README.md
@ -1,23 +1,11 @@
|
|||||||
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
|
<p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p>
|
||||||
|
|
||||||
[![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
|
||||||
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
|
|
||||||
[![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
|
|
||||||
[![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
|
|
||||||
|
|
||||||
sat-rs
|
sat-rs
|
||||||
=========
|
=========
|
||||||
|
|
||||||
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
|
This is the repository of the sat-rs framework. Its primary goal is to provide re-usable components
|
||||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||||
for the special requirements for these systems. You can find an overview of the project and the
|
for the special requirements for these systems.
|
||||||
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
|
||||||
at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
|
|
||||||
|
|
||||||
This is early-stage software. Important features are missing. New releases
|
|
||||||
with breaking changes are released regularly, with all changes documented inside respective
|
|
||||||
changelog files. You should only use this library if your are willing to work in this
|
|
||||||
environment.
|
|
||||||
|
|
||||||
A lot of the architecture and general design considerations are based on the
|
A lot of the architecture and general design considerations are based on the
|
||||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
||||||
@ -28,64 +16,24 @@ and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-
|
|||||||
|
|
||||||
This project currently contains following crates:
|
This project currently contains following crates:
|
||||||
|
|
||||||
* [`satrs-book`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-book):
|
* [`satrs-core`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-core):
|
||||||
Primary information resource in addition to the API documentation, hosted
|
Core components of sat-rs.
|
||||||
[here](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/). It can be useful to read
|
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-example):
|
||||||
this first before delving into the example application and the API documentation.
|
|
||||||
* [`satrs`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs):
|
|
||||||
Primary crate.
|
|
||||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
|
|
||||||
Example of a simple example on-board software using various sat-rs components which can be run
|
Example of a simple example on-board software using various sat-rs components which can be run
|
||||||
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
||||||
* [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim):
|
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-mib):
|
||||||
Mini-Simulator based on [asynchronix](https://github.com/asynchronics/asynchronix) which
|
|
||||||
simulates some physical devices for the `satrs-example` application device handlers.
|
|
||||||
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-mib):
|
|
||||||
Components to build a mission information base from the on-board software directly.
|
Components to build a mission information base from the on-board software directly.
|
||||||
* [`satrs-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32f3-disco-rtic):
|
* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco):
|
||||||
Example of a simple example using low-level sat-rs components on a bare-metal system
|
Example of a simple example on-board software using sat-rs components on a bare-metal system
|
||||||
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
with constrained resources.
|
||||||
framework on the STM32F3-Discovery device.
|
|
||||||
* [`satrs-stm32h-nucleo-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32h7-nucleo-rtic):
|
|
||||||
Example of a simple example using sat-rs components on a bare-metal system
|
|
||||||
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
|
||||||
framework on the STM32H743ZIT device.
|
|
||||||
|
|
||||||
Each project has its own `CHANGELOG.md`.
|
Each project has its own `CHANGELOG.md`.
|
||||||
|
|
||||||
# Related projects
|
# Related projects
|
||||||
|
|
||||||
In addition to the crates in this repository, the sat-rs project also maintains other libraries.
|
In addition to the crates in this repository, the sat-rs project also maintains other libraries.
|
||||||
|
|
||||||
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
|
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
|
||||||
packet protocol implementations. This repository is re-exported in the
|
packet protocol implementations. This repository is re-exported in the
|
||||||
[`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
|
[`satrs-core`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-core)
|
||||||
crate.
|
crate.
|
||||||
|
|
||||||
# Flight Heritage
|
|
||||||
|
|
||||||
There is an active and continuous effort to get early flight heritage for the sat-rs library.
|
|
||||||
Currently this library has the following flight heritage:
|
|
||||||
|
|
||||||
- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT)
|
|
||||||
which has also
|
|
||||||
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
|
||||||
The application is strongly based on the sat-rs example application. You can find the repository
|
|
||||||
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
|
||||||
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
|
|
||||||
alongside a Flight System Simulator in the context of a
|
|
||||||
[Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
|
|
||||||
at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
|
|
||||||
|
|
||||||
# Coverage
|
|
||||||
|
|
||||||
Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so
|
|
||||||
already, install the `llvm-tools-preview`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup component add llvm-tools-preview
|
|
||||||
cargo install grcov --locked
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, you can simply run `coverage.py` to test the `satrs` crate with coverage. You can
|
|
||||||
optionally supply the `--open` flag to open the coverage report in your webbrowser.
|
|
||||||
|
@ -14,15 +14,8 @@ RUN rustup install nightly && \
|
|||||||
rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
|
rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
|
||||||
rustup component add rustfmt clippy
|
rustup component add rustfmt clippy
|
||||||
|
|
||||||
WORKDIR "/tmp"
|
# RUN cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
||||||
# Install cargo-nextest
|
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.34/mdbook-v0.4.34-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
|
||||||
RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
|
|
||||||
# Install mdbook and mdbook-linkcheck
|
|
||||||
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
|
|
||||||
RUN curl -sSL https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && \
|
|
||||||
unzip mdbook-linkcheck.zip && \
|
|
||||||
chmod +x mdbook-linkcheck && \
|
|
||||||
cp mdbook-linkcheck /usr/local/bin
|
|
||||||
|
|
||||||
# SSH stuff to allow deployment to doc server
|
# SSH stuff to allow deployment to doc server
|
||||||
RUN adduser --uid 114 jenkins
|
RUN adduser --uid 114 jenkins
|
||||||
|
7
automation/Jenkinsfile
vendored
@ -32,8 +32,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Test') {
|
stage('Test') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo nextest r --all-features'
|
sh 'cargo test --all-features'
|
||||||
sh 'cargo test --doc --all-features'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check with all features') {
|
stage('Check with all features') {
|
||||||
@ -48,7 +47,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Check Cross Embedded Bare Metal') {
|
stage('Check Cross Embedded Bare Metal') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo check -p satrs --target thumbv7em-none-eabihf --no-default-features'
|
sh 'cargo check -p satrs-core --target thumbv7em-none-eabihf --no-default-features'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check Cross Embedded Linux') {
|
stage('Check Cross Embedded Linux') {
|
||||||
@ -68,7 +67,7 @@ pipeline {
|
|||||||
sh 'mdbook build'
|
sh 'mdbook build'
|
||||||
sshagent(credentials: ['documentation-buildfix']) {
|
sshagent(credentials: ['documentation-buildfix']) {
|
||||||
// Deploy to Apache webserver
|
// Deploy to Apache webserver
|
||||||
sh 'rsync -r --delete book/html/ buildfix@documentation.irs.uni-stuttgart.de:/projects/sat-rs/book/'
|
sh 'rsync -r --delete book/ buildfix@documentation.irs.uni-stuttgart.de:/projects/sat-rs'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
coverage.py
@ -1,65 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import webbrowser
|
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def generate_cov_report(open_report: bool, format: str, package: str):
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
os.environ["RUSTFLAGS"] = "-Cinstrument-coverage"
|
|
||||||
os.environ["LLVM_PROFILE_FILE"] = "target/coverage/%p-%m.profraw"
|
|
||||||
_LOGGER.info("Executing tests with coverage")
|
|
||||||
os.system(f"cargo test -p {package}")
|
|
||||||
|
|
||||||
out_path = "./target/debug/coverage"
|
|
||||||
if format == "lcov":
|
|
||||||
out_path = "./target/debug/lcov.info"
|
|
||||||
grcov_cmd = (
|
|
||||||
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
|
|
||||||
f"-o {out_path}"
|
|
||||||
)
|
|
||||||
print(f"Running: {grcov_cmd}")
|
|
||||||
os.system(grcov_cmd)
|
|
||||||
if format == "lcov":
|
|
||||||
lcov_cmd = (
|
|
||||||
"genhtml -o ./target/debug/coverage/ --show-details --highlight --ignore-errors source "
|
|
||||||
"--legend ./target/debug/lcov.info"
|
|
||||||
)
|
|
||||||
print(f"Running: {lcov_cmd}")
|
|
||||||
os.system(lcov_cmd)
|
|
||||||
if open_report:
|
|
||||||
coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
|
|
||||||
webbrowser.open_new_tab(coverage_report_path)
|
|
||||||
_LOGGER.info("Done")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Generate coverage report and optionally open it in a browser"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--open", action="store_true", help="Open the coverage report in a browser"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-p",
|
|
||||||
"--package",
|
|
||||||
choices=["satrs", "satrs-minisim", "satrs-example"],
|
|
||||||
default="satrs",
|
|
||||||
help="Choose project to generate coverage for",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--format",
|
|
||||||
choices=["html", "lcov"],
|
|
||||||
default="html",
|
|
||||||
help="Choose report format (html or lcov)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
generate_cov_report(args.open, args.format, args.package)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,37 +0,0 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
|
||||||
# uncomment ONE of these three option to make `cargo run` start a GDB session
|
|
||||||
# which option to pick depends on your system
|
|
||||||
# You can also replace openocd.gdb by jlink.gdb when using a J-Link.
|
|
||||||
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
|
||||||
# runner = "gdb-multiarch -q -x openocd.gdb"
|
|
||||||
# runner = "gdb -q -x openocd.gdb"
|
|
||||||
runner = "probe-rs run --chip STM32F303VCTx"
|
|
||||||
|
|
||||||
rustflags = [
|
|
||||||
"-C", "linker=flip-link",
|
|
||||||
# LLD (shipped with the Rust toolchain) is used as the default linker
|
|
||||||
"-C", "link-arg=-Tlink.x",
|
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
|
||||||
|
|
||||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
|
||||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
|
||||||
"-C", "link-arg=--nmagic",
|
|
||||||
|
|
||||||
# if you run into problems with LLD switch to the GNU linker by commenting out
|
|
||||||
# this line
|
|
||||||
# "-C", "linker=arm-none-eabi-ld",
|
|
||||||
|
|
||||||
# if you need to link to pre-compiled C libraries provided by a C toolchain
|
|
||||||
# use GCC as the linker by commenting out both lines above and then
|
|
||||||
# uncommenting the three lines below
|
|
||||||
# "-C", "linker=arm-none-eabi-gcc",
|
|
||||||
# "-C", "link-arg=-Wl,-Tlink.x",
|
|
||||||
# "-C", "link-arg=-nostartfiles",
|
|
||||||
]
|
|
||||||
|
|
||||||
[build]
|
|
||||||
# comment out the following line if you intend to run unit tests on host machine
|
|
||||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
|
||||||
|
|
||||||
[env]
|
|
||||||
DEFMT_LOG = "info"
|
|
@ -1,84 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "satrs-stm32f3-disco-rtic"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
default-run = "satrs-stm32f3-disco-rtic"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
defmt = "0.3"
|
|
||||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
|
||||||
embedded-hal = "0.2.7"
|
|
||||||
cortex-m-semihosting = "0.5.0"
|
|
||||||
enumset = "1"
|
|
||||||
heapless = "0.8"
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "1"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[dependencies.cobs]
|
|
||||||
git = "https://github.com/robamu/cobs.rs.git"
|
|
||||||
branch = "all_features"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.stm32f3xx-hal]
|
|
||||||
git = "https://github.com/robamu/stm32f3xx-hal"
|
|
||||||
version = "0.11.0-alpha.0"
|
|
||||||
features = ["stm32f303xc", "rt", "enumset"]
|
|
||||||
branch = "complete-dma-update"
|
|
||||||
# Can be used in workspace to develop and update HAL
|
|
||||||
# path = "../stm32f3xx-hal"
|
|
||||||
|
|
||||||
[dependencies.stm32f3-discovery]
|
|
||||||
git = "https://github.com/robamu/stm32f3-discovery"
|
|
||||||
version = "0.8.0-alpha.0"
|
|
||||||
branch = "complete-dma-update-hal"
|
|
||||||
# Can be used in workspace to develop and update BSP
|
|
||||||
# path = "../stm32f3-discovery"
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
# path = "satrs"
|
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
|
||||||
features = ["defmt"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
defmt-test = "0.3"
|
|
||||||
|
|
||||||
# cargo test
|
|
||||||
[profile.test]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
opt-level = "s" # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = "s" # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
# cargo test --release
|
|
||||||
[profile.bench]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = "s" # <-
|
|
||||||
overflow-checks = false # <-
|
|
@ -1,114 +0,0 @@
|
|||||||
sat-rs example for the STM32F3-Discovery board
|
|
||||||
=======
|
|
||||||
|
|
||||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
|
||||||
can be used on an embedded target.
|
|
||||||
It also shows how a relatively simple OBSW could be built when no standard runtime is available.
|
|
||||||
It uses [RTIC](https://rtic.rs/2/book/en/) as the concurrency framework and the
|
|
||||||
[defmt](https://defmt.ferrous-systems.com/) framework for logging.
|
|
||||||
|
|
||||||
The STM32F3-Discovery device was picked because it is a cheap Cortex-M4 based device which is also
|
|
||||||
used by the [Rust Embedded Book](https://docs.rust-embedded.org/book/intro/hardware.html) and the
|
|
||||||
[Rust Discovery](https://docs.rust-embedded.org/discovery/f3discovery/) book as an introduction
|
|
||||||
to embedded Rust.
|
|
||||||
|
|
||||||
## Pre-Requisites
|
|
||||||
|
|
||||||
Make sure the following tools are installed:
|
|
||||||
|
|
||||||
1. [`probe-rs`](https://probe.rs/): Application used to flash and debug the MCU.
|
|
||||||
2. Optional and recommended: [VS Code](https://code.visualstudio.com/) with
|
|
||||||
[probe-rs plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger)
|
|
||||||
for debugging.
|
|
||||||
|
|
||||||
## Preparing Rust and the repository
|
|
||||||
|
|
||||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
|
||||||
If you have not installed it yet, you can do so with
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup target add thumbv7em-none-eabihf
|
|
||||||
```
|
|
||||||
|
|
||||||
A default `.cargo` config file is provided for this project, but needs to be copied to have
|
|
||||||
the correct name. This is so that the config file can be updated or edited for custom needs
|
|
||||||
without being tracked by git.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp def_config.toml config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
The configuration file will also set the target so it does not always have to be specified with
|
|
||||||
the `--target` argument.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
After that, assuming that you have a `.cargo/config.toml` setting the correct build target,
|
|
||||||
you can simply build the application with
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flashing from the command line
|
|
||||||
|
|
||||||
You can flash the application from the command line using `probe-rs`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
probe-rs run --chip STM32F303VCTx
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debugging with VS Code
|
|
||||||
|
|
||||||
The STM32F3-Discovery comes with an on-board ST-Link so all that is required to flash and debug
|
|
||||||
the board is a Mini-USB cable. The code in this repository was debugged using [`probe-rs`](https://probe.rs/docs/tools/debuggerA)
|
|
||||||
and the VS Code [`probe-rs` plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger).
|
|
||||||
Make sure to install this plugin first.
|
|
||||||
|
|
||||||
Sample configuration files are provided inside the `vscode` folder.
|
|
||||||
Use `cp vscode .vscode -r` to use them for your project.
|
|
||||||
|
|
||||||
Some sample configuration files for VS Code were provided as well. You can simply use `Run` and `Debug`
|
|
||||||
to automatically rebuild and flash your application.
|
|
||||||
|
|
||||||
The `tasks.json` and `launch.json` files are generic and you can use them immediately by opening
|
|
||||||
the folder in VS code or adding it to a workspace.
|
|
||||||
|
|
||||||
## Commanding with Python
|
|
||||||
|
|
||||||
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
|
|
||||||
using COBS encoded PUS packets.
|
|
||||||
|
|
||||||
It is recommended to use a virtual environment to do this. To set up one in the command line,
|
|
||||||
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
|
|
||||||
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
|
|
||||||
on how to activate the environment and then use the following command to install the required
|
|
||||||
dependency:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
The packets are exchanged using a dedicated serial interface. You can use any generic USB-to-UART
|
|
||||||
converter device with the TX pin connected to the PA3 pin and the RX pin connected to the PA2 pin.
|
|
||||||
|
|
||||||
A default configuration file for the python application is provided and can be used by running
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp def_tmtc_conf.json tmtc_conf.json
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, you can for example send a ping to the MCU using the following command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./main.py -p /ping
|
|
||||||
```
|
|
||||||
|
|
||||||
You can configure the blinky frequency using
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./main.py -p /change_blink_freq
|
|
||||||
```
|
|
||||||
|
|
||||||
All these commands will package a PUS telecommand which will be sent to the MCU using the COBS
|
|
||||||
format as the packet framing format.
|
|
@ -1,51 +0,0 @@
|
|||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_semihosting::debug;
|
|
||||||
|
|
||||||
use defmt_brtt as _; // global logger
|
|
||||||
|
|
||||||
use stm32f3xx_hal as _; // memory layout
|
|
||||||
|
|
||||||
use panic_probe as _;
|
|
||||||
|
|
||||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
|
||||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
|
||||||
#[defmt::panic_handler]
|
|
||||||
fn panic() -> ! {
|
|
||||||
cortex_m::asm::udf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
|
||||||
/// with status code 0.
|
|
||||||
pub fn exit() -> ! {
|
|
||||||
loop {
|
|
||||||
debug::exit(debug::EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardfault handler.
|
|
||||||
///
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
|
||||||
/// with an error. This seems better than the default, which is to spin in a
|
|
||||||
/// loop.
|
|
||||||
#[cortex_m_rt::exception]
|
|
||||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
|
||||||
loop {
|
|
||||||
debug::exit(debug::EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
|
||||||
// once within a crate. the module can be in any file but there can only be at most
|
|
||||||
// one `#[tests]` module in this library crate
|
|
||||||
#[cfg(test)]
|
|
||||||
#[defmt_test::tests]
|
|
||||||
mod unit_tests {
|
|
||||||
use defmt::assert;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert!(true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,684 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
use satrs::pus::verification::{
|
|
||||||
FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
|
|
||||||
};
|
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
|
||||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
|
||||||
use satrs::spacepackets::ecss::EcssEnumU16;
|
|
||||||
use satrs::spacepackets::CcsdsPacket;
|
|
||||||
use satrs::spacepackets::{ByteConversionError, SpHeader};
|
|
||||||
// global logger + panicking-behavior + memory layout
|
|
||||||
use satrs_stm32f3_disco_rtic as _;
|
|
||||||
|
|
||||||
use rtic::app;
|
|
||||||
|
|
||||||
use heapless::{mpmc::Q8, Vec};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
|
|
||||||
use rtic_monotonics::systick::ExtU32;
|
|
||||||
use satrs::seq_count::SequenceCountProviderCore;
|
|
||||||
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
|
|
||||||
use stm32f3xx_hal::dma::dma1;
|
|
||||||
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
|
|
||||||
use stm32f3xx_hal::pac::USART2;
|
|
||||||
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
|
|
||||||
|
|
||||||
const UART_BAUD: u32 = 115200;
|
|
||||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
|
||||||
const TX_HANDLER_FREQ_MS: u32 = 20;
|
|
||||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
|
|
||||||
const MAX_TC_LEN: usize = 128;
|
|
||||||
const MAX_TM_LEN: usize = 128;
|
|
||||||
pub const PUS_APID: u16 = 0x02;
|
|
||||||
|
|
||||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
|
|
||||||
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
|
|
||||||
type InstantFugit = TimerInstantU32<1000>;
|
|
||||||
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
|
|
||||||
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
|
|
||||||
|
|
||||||
// This is the predictable maximum overhead of the COBS encoding scheme.
|
|
||||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
|
|
||||||
const COBS_TC_OVERHEAD: usize = (MAX_TC_LEN + 254 - 1) / 254;
|
|
||||||
const COBS_TM_OVERHEAD: usize = (MAX_TM_LEN + 254 - 1) / 254;
|
|
||||||
|
|
||||||
const TC_BUF_LEN: usize = MAX_TC_LEN + COBS_TC_OVERHEAD;
|
|
||||||
const TM_BUF_LEN: usize = MAX_TC_LEN + COBS_TM_OVERHEAD;
|
|
||||||
|
|
||||||
// This is a static buffer which should ONLY (!) be used as the TX DMA
|
|
||||||
// transfer buffer.
|
|
||||||
static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
|
|
||||||
// This is a static buffer which should ONLY (!) be used as the RX DMA
|
|
||||||
// transfer buffer.
|
|
||||||
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
|
|
||||||
|
|
||||||
type TmPacket = Vec<u8, MAX_TM_LEN>;
|
|
||||||
type TcPacket = Vec<u8, MAX_TC_LEN>;
|
|
||||||
|
|
||||||
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
|
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU16, Ordering};
|
|
||||||
|
|
||||||
pub struct SeqCountProviderAtomicRef {
|
|
||||||
atomic: AtomicU16,
|
|
||||||
ordering: Ordering,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeqCountProviderAtomicRef {
|
|
||||||
pub const fn new(ordering: Ordering) -> Self {
|
|
||||||
Self {
|
|
||||||
atomic: AtomicU16::new(0),
|
|
||||||
ordering,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
|
|
||||||
fn get(&self) -> u16 {
|
|
||||||
self.atomic.load(self.ordering)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn increment(&self) {
|
|
||||||
self.atomic.fetch_add(1, self.ordering);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_and_increment(&self) -> u16 {
|
|
||||||
self.atomic.fetch_add(1, self.ordering)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
|
|
||||||
SeqCountProviderAtomicRef::new(Ordering::Relaxed);
|
|
||||||
|
|
||||||
pub struct TxIdle {
|
|
||||||
tx: TxType,
|
|
||||||
dma_channel: dma1::C7,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
|
||||||
pub enum TmSendError {
|
|
||||||
ByteConversion(ByteConversionError),
|
|
||||||
Queue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ByteConversionError> for TmSendError {
|
|
||||||
fn from(value: ByteConversionError) -> Self {
|
|
||||||
Self::ByteConversion(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
|
|
||||||
if tm_creator.len_written() > MAX_TM_LEN {
|
|
||||||
return Err(ByteConversionError::ToSliceTooSmall {
|
|
||||||
expected: tm_creator.len_written(),
|
|
||||||
found: MAX_TM_LEN,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
let mut tm_vec = TmPacket::new();
|
|
||||||
tm_vec
|
|
||||||
.resize(tm_creator.len_written(), 0)
|
|
||||||
.expect("vec resize failed");
|
|
||||||
tm_creator.write_to_bytes(tm_vec.as_mut_slice())?;
|
|
||||||
defmt::info!(
|
|
||||||
"Sending TM[{},{}] with size {}",
|
|
||||||
tm_creator.service(),
|
|
||||||
tm_creator.subservice(),
|
|
||||||
tm_creator.len_written()
|
|
||||||
);
|
|
||||||
TM_REQUESTS
|
|
||||||
.enqueue(tm_vec)
|
|
||||||
.map_err(|_| TmSendError::Queue)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_tm_send_error(error: TmSendError) {
|
|
||||||
defmt::warn!("sending tm failed with error {}", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum UartTxState {
|
|
||||||
// Wrapped in an option because we need an owned type later.
|
|
||||||
Idle(Option<TxIdle>),
|
|
||||||
// Same as above
|
|
||||||
Transmitting(Option<TxDmaTransferType>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UartTxShared {
|
|
||||||
last_completed: Option<InstantFugit>,
|
|
||||||
state: UartTxState,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RequestWithToken {
|
|
||||||
token: VerificationToken<TcStateAccepted>,
|
|
||||||
request: Request,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
|
||||||
pub enum Request {
|
|
||||||
Ping,
|
|
||||||
ChangeBlinkFrequency(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
|
||||||
pub enum RequestError {
|
|
||||||
InvalidApid = 1,
|
|
||||||
InvalidService = 2,
|
|
||||||
InvalidSubservice = 3,
|
|
||||||
NotEnoughAppData = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_pus_tc_to_request(
|
|
||||||
tc: &PusTcReader,
|
|
||||||
verif_reporter: &mut VerificationReportCreator,
|
|
||||||
src_data_buf: &mut [u8],
|
|
||||||
timestamp: &[u8],
|
|
||||||
) -> Result<RequestWithToken, RequestError> {
|
|
||||||
defmt::info!(
|
|
||||||
"Found PUS TC [{},{}] with length {}",
|
|
||||||
tc.service(),
|
|
||||||
tc.subservice(),
|
|
||||||
tc.len_packed()
|
|
||||||
);
|
|
||||||
|
|
||||||
let token = verif_reporter.add_tc(tc);
|
|
||||||
if tc.apid() != PUS_APID {
|
|
||||||
defmt::warn!("Received tc with unknown APID {}", tc.apid());
|
|
||||||
let result = send_tm(
|
|
||||||
verif_reporter
|
|
||||||
.acceptance_failure(
|
|
||||||
src_data_buf,
|
|
||||||
token,
|
|
||||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
|
||||||
0,
|
|
||||||
FailParams::new(timestamp, &EcssEnumU16::new(0), &[]),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
if let Err(e) = result {
|
|
||||||
handle_tm_send_error(e);
|
|
||||||
}
|
|
||||||
return Err(RequestError::InvalidApid);
|
|
||||||
}
|
|
||||||
let (tm_creator, accepted_token) = verif_reporter
|
|
||||||
.acceptance_success(
|
|
||||||
src_data_buf,
|
|
||||||
token,
|
|
||||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
|
||||||
0,
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Err(e) = send_tm(tm_creator) {
|
|
||||||
handle_tm_send_error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.service() == 17 && tc.subservice() == 1 {
|
|
||||||
if tc.subservice() == 1 {
|
|
||||||
return Ok(RequestWithToken {
|
|
||||||
request: Request::Ping,
|
|
||||||
token: accepted_token,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Err(RequestError::InvalidSubservice);
|
|
||||||
}
|
|
||||||
} else if tc.service() == 8 {
|
|
||||||
if tc.subservice() == 1 {
|
|
||||||
if tc.user_data().len() < 4 {
|
|
||||||
return Err(RequestError::NotEnoughAppData);
|
|
||||||
}
|
|
||||||
let new_freq_ms = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
|
|
||||||
return Ok(RequestWithToken {
|
|
||||||
request: Request::ChangeBlinkFrequency(new_freq_ms),
|
|
||||||
token: accepted_token,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Err(RequestError::InvalidSubservice);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(RequestError::InvalidService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[app(device = stm32f3xx_hal::pac, peripherals = true)]
|
|
||||||
mod app {
|
|
||||||
use super::*;
|
|
||||||
use core::slice::Iter;
|
|
||||||
use rtic_monotonics::systick::Systick;
|
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
|
|
||||||
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use stm32f3_discovery::leds::Direction;
|
|
||||||
use stm32f3_discovery::leds::Leds;
|
|
||||||
use stm32f3xx_hal::prelude::*;
|
|
||||||
|
|
||||||
use stm32f3_discovery::switch_hal::OutputSwitch;
|
|
||||||
use stm32f3xx_hal::Switch;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {
|
|
||||||
blink_freq: MillisDurationU32,
|
|
||||||
tx_shared: UartTxShared,
|
|
||||||
rx_transfer: Option<RxDmaTransferType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
verif_reporter: VerificationReportCreator,
|
|
||||||
leds: Leds,
|
|
||||||
last_dir: Direction,
|
|
||||||
curr_dir: Iter<'static, Direction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(cx: init::Context) -> (Shared, Local) {
|
|
||||||
let mut rcc = cx.device.RCC.constrain();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
|
||||||
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
|
|
||||||
|
|
||||||
let mut flash = cx.device.FLASH.constrain();
|
|
||||||
let clocks = rcc
|
|
||||||
.cfgr
|
|
||||||
.use_hse(8.MHz())
|
|
||||||
.sysclk(8.MHz())
|
|
||||||
.pclk1(8.MHz())
|
|
||||||
.freeze(&mut flash.acr);
|
|
||||||
|
|
||||||
// Set up monotonic timer.
|
|
||||||
//let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
|
|
||||||
|
|
||||||
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
|
|
||||||
let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
|
|
||||||
|
|
||||||
let leds = Leds::new(
|
|
||||||
gpioe.pe8,
|
|
||||||
gpioe.pe9,
|
|
||||||
gpioe.pe10,
|
|
||||||
gpioe.pe11,
|
|
||||||
gpioe.pe12,
|
|
||||||
gpioe.pe13,
|
|
||||||
gpioe.pe14,
|
|
||||||
gpioe.pe15,
|
|
||||||
&mut gpioe.moder,
|
|
||||||
&mut gpioe.otyper,
|
|
||||||
);
|
|
||||||
let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
|
|
||||||
// USART2 pins
|
|
||||||
let mut pins = (
|
|
||||||
// TX pin: PA2
|
|
||||||
gpioa
|
|
||||||
.pa2
|
|
||||||
.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
|
|
||||||
// RX pin: PA3
|
|
||||||
gpioa
|
|
||||||
.pa3
|
|
||||||
.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
|
|
||||||
);
|
|
||||||
pins.1.internal_pull_up(&mut gpioa.pupdr, true);
|
|
||||||
let mut usart2 = Serial::new(
|
|
||||||
cx.device.USART2,
|
|
||||||
pins,
|
|
||||||
UART_BAUD.Bd(),
|
|
||||||
clocks,
|
|
||||||
&mut rcc.apb1,
|
|
||||||
);
|
|
||||||
usart2.configure_rx_interrupt(RxEvent::Idle, Switch::On);
|
|
||||||
// This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
|
|
||||||
usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Switch::On);
|
|
||||||
|
|
||||||
let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
|
|
||||||
let (mut tx_serial, mut rx_serial) = usart2.split();
|
|
||||||
|
|
||||||
// This interrupt is immediately triggered, clear it. It will only be reset
|
|
||||||
// by the hardware when data is received on RX (RXNE event)
|
|
||||||
rx_serial.clear_event(RxEvent::Idle);
|
|
||||||
// For some reason, this is also immediately triggered..
|
|
||||||
tx_serial.clear_event(TxEvent::TransmissionComplete);
|
|
||||||
let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
|
|
||||||
defmt::info!("Spawning tasks");
|
|
||||||
blink::spawn().unwrap();
|
|
||||||
serial_tx_handler::spawn().unwrap();
|
|
||||||
|
|
||||||
let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
|
|
||||||
|
|
||||||
(
|
|
||||||
Shared {
|
|
||||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
|
||||||
tx_shared: UartTxShared {
|
|
||||||
last_completed: None,
|
|
||||||
state: UartTxState::Idle(Some(TxIdle {
|
|
||||||
tx: tx_serial,
|
|
||||||
dma_channel: dma1.ch7,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
rx_transfer: Some(rx_transfer),
|
|
||||||
},
|
|
||||||
Local {
|
|
||||||
verif_reporter,
|
|
||||||
leds,
|
|
||||||
last_dir: Direction::North,
|
|
||||||
curr_dir: Direction::iter(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
|
|
||||||
async fn blink(mut cx: blink::Context) {
|
|
||||||
let blink::LocalResources {
|
|
||||||
leds,
|
|
||||||
curr_dir,
|
|
||||||
last_dir,
|
|
||||||
..
|
|
||||||
} = cx.local;
|
|
||||||
let mut toggle_leds = |dir: &Direction| {
|
|
||||||
let last_led = leds.for_direction(*last_dir);
|
|
||||||
last_led.off().ok();
|
|
||||||
let led = leds.for_direction(*dir);
|
|
||||||
led.on().ok();
|
|
||||||
*last_dir = *dir;
|
|
||||||
};
|
|
||||||
loop {
|
|
||||||
match curr_dir.next() {
|
|
||||||
Some(dir) => {
|
|
||||||
toggle_leds(dir);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
*curr_dir = Direction::iter();
|
|
||||||
toggle_leds(curr_dir.next().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
|
||||||
Systick::delay(current_blink_freq).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
shared = [tx_shared],
|
|
||||||
)]
|
|
||||||
async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
|
|
||||||
loop {
|
|
||||||
let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
|
|
||||||
if let UartTxState::Idle(_) = tx_shared.state {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
});
|
|
||||||
if is_idle {
|
|
||||||
let last_completed = cx.shared.tx_shared.lock(|shared| shared.last_completed);
|
|
||||||
if let Some(last_completed) = last_completed {
|
|
||||||
let elapsed_ms = (Systick::now() - last_completed).to_millis();
|
|
||||||
if elapsed_ms < MIN_DELAY_BETWEEN_TX_PACKETS_MS {
|
|
||||||
Systick::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for completion after 1 ms
|
|
||||||
Systick::delay(1.millis()).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(vec) = TM_REQUESTS.dequeue() {
|
|
||||||
cx.shared
|
|
||||||
.tx_shared
|
|
||||||
.lock(|tx_shared| match &mut tx_shared.state {
|
|
||||||
UartTxState::Idle(tx) => {
|
|
||||||
let encoded_len;
|
|
||||||
//debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
|
|
||||||
// Safety: We only copy the data into the TX DMA buffer in this task.
|
|
||||||
// If the DMA is active, another branch will be taken.
|
|
||||||
unsafe {
|
|
||||||
// 0 sentinel value as start marker
|
|
||||||
DMA_TX_BUF[0] = 0;
|
|
||||||
encoded_len =
|
|
||||||
cobs::encode(&vec[0..vec.len()], &mut DMA_TX_BUF[1..]);
|
|
||||||
// Should never panic, we accounted for the overhead.
|
|
||||||
// Write into transfer buffer directly, no need for intermediate
|
|
||||||
// encoding buffer.
|
|
||||||
// 0 end marker
|
|
||||||
DMA_TX_BUF[encoded_len + 1] = 0;
|
|
||||||
}
|
|
||||||
//debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
|
|
||||||
//debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
|
|
||||||
let tx_idle = tx.take().unwrap();
|
|
||||||
// Transfer completion and re-scheduling of new TX transfers will be done
|
|
||||||
// by the IRQ handler.
|
|
||||||
// SAFETY: The DMA is the exclusive writer to the DMA buffer now.
|
|
||||||
let transfer = tx_idle.tx.write_all(
|
|
||||||
unsafe { &DMA_TX_BUF[0..encoded_len + 2] },
|
|
||||||
tx_idle.dma_channel,
|
|
||||||
);
|
|
||||||
tx_shared.state = UartTxState::Transmitting(Some(transfer));
|
|
||||||
// The memory block is automatically returned to the pool when it is dropped.
|
|
||||||
}
|
|
||||||
UartTxState::Transmitting(_) => (),
|
|
||||||
});
|
|
||||||
// Check for completion after 1 ms
|
|
||||||
Systick::delay(1.millis()).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Nothing to do, and we are idle.
|
|
||||||
Systick::delay(TX_HANDLER_FREQ_MS.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
local = [
|
|
||||||
verif_reporter,
|
|
||||||
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
|
|
||||||
src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
|
|
||||||
timestamp: [u8; 7] = [0; 7],
|
|
||||||
],
|
|
||||||
shared = [blink_freq]
|
|
||||||
)]
|
|
||||||
async fn serial_rx_handler(
|
|
||||||
mut cx: serial_rx_handler::Context,
|
|
||||||
received_packet: Vec<u8, MAX_TC_LEN>,
|
|
||||||
) {
|
|
||||||
cx.local.timestamp[0] = P_FIELD_BASE;
|
|
||||||
defmt::info!("Received packet with {} bytes", received_packet.len());
|
|
||||||
let decode_buf = cx.local.decode_buf;
|
|
||||||
let packet = received_packet.as_slice();
|
|
||||||
let mut start_idx = None;
|
|
||||||
for (idx, byte) in packet.iter().enumerate() {
|
|
||||||
if *byte != 0 {
|
|
||||||
start_idx = Some(idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if start_idx.is_none() {
|
|
||||||
defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let start_idx = start_idx.unwrap();
|
|
||||||
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
|
|
||||||
Ok(len) => {
|
|
||||||
defmt::info!("Decoded packet length: {}", len);
|
|
||||||
let pus_tc = PusTcReader::new(decode_buf);
|
|
||||||
match pus_tc {
|
|
||||||
Ok((tc, _tc_len)) => {
|
|
||||||
match convert_pus_tc_to_request(
|
|
||||||
&tc,
|
|
||||||
cx.local.verif_reporter,
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
cx.local.timestamp,
|
|
||||||
) {
|
|
||||||
Ok(request_with_token) => {
|
|
||||||
let started_token = handle_start_verification(
|
|
||||||
request_with_token.token,
|
|
||||||
cx.local.verif_reporter,
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
cx.local.timestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
match request_with_token.request {
|
|
||||||
Request::Ping => {
|
|
||||||
handle_ping_request(cx.local.timestamp);
|
|
||||||
}
|
|
||||||
Request::ChangeBlinkFrequency(new_freq_ms) => {
|
|
||||||
defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
|
|
||||||
cx.shared.blink_freq.lock(|blink_freq| {
|
|
||||||
*blink_freq =
|
|
||||||
MillisDurationU32::from_ticks(new_freq_ms);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle_completion_verification(
|
|
||||||
started_token,
|
|
||||||
cx.local.verif_reporter,
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
cx.local.timestamp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// TODO: Error handling: Send verification failure based on request error.
|
|
||||||
defmt::warn!("request error {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("Error unpacking PUS TC: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
defmt::warn!("decoding error, can only process cobs encoded frames")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_ping_request(timestamp: &[u8]) {
|
|
||||||
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
|
|
||||||
let sp_header =
|
|
||||||
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
|
|
||||||
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
|
|
||||||
let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
|
|
||||||
let mut tm_packet = TmPacket::new();
|
|
||||||
tm_packet
|
|
||||||
.resize(ping_reply.len_written(), 0)
|
|
||||||
.expect("vec resize failed");
|
|
||||||
ping_reply.write_to_bytes(&mut tm_packet).unwrap();
|
|
||||||
if TM_REQUESTS.enqueue(tm_packet).is_err() {
|
|
||||||
defmt::warn!("TC queue full");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_start_verification(
|
|
||||||
accepted_token: VerificationToken<TcStateAccepted>,
|
|
||||||
verif_reporter: &mut VerificationReportCreator,
|
|
||||||
src_data_buf: &mut [u8],
|
|
||||||
timestamp: &[u8],
|
|
||||||
) -> VerificationToken<TcStateStarted> {
|
|
||||||
let (tm_creator, started_token) = verif_reporter
|
|
||||||
.start_success(
|
|
||||||
src_data_buf,
|
|
||||||
accepted_token,
|
|
||||||
SEQ_COUNT_PROVIDER.get(),
|
|
||||||
0,
|
|
||||||
×tamp,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let result = send_tm(tm_creator);
|
|
||||||
if let Err(e) = result {
|
|
||||||
handle_tm_send_error(e);
|
|
||||||
}
|
|
||||||
started_token
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_completion_verification(
|
|
||||||
started_token: VerificationToken<TcStateStarted>,
|
|
||||||
verif_reporter: &mut VerificationReportCreator,
|
|
||||||
src_data_buf: &mut [u8],
|
|
||||||
timestamp: &[u8],
|
|
||||||
) {
|
|
||||||
let result = send_tm(
|
|
||||||
verif_reporter
|
|
||||||
.completion_success(
|
|
||||||
src_data_buf,
|
|
||||||
started_token,
|
|
||||||
SEQ_COUNT_PROVIDER.get(),
|
|
||||||
0,
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
if let Err(e) = result {
|
|
||||||
handle_tm_send_error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
|
|
||||||
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
|
|
||||||
let mut tc_packet = TcPacket::new();
|
|
||||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
|
||||||
let rx_ref = rx_transfer.as_ref().unwrap();
|
|
||||||
if rx_ref.is_complete() {
|
|
||||||
let uart_rx_owned = rx_transfer.take().unwrap();
|
|
||||||
let (buf, c, rx) = uart_rx_owned.stop();
|
|
||||||
// The received data is transferred to another task now to avoid any processing overhead
|
|
||||||
// during the interrupt. There are multiple ways to do this, we use a stack allocaed vector here
|
|
||||||
// to do this.
|
|
||||||
tc_packet.resize(buf.len(), 0).expect("vec resize failed");
|
|
||||||
tc_packet.copy_from_slice(buf);
|
|
||||||
|
|
||||||
// Start the next transfer as soon as possible.
|
|
||||||
*rx_transfer = Some(rx.read_exact(buf, c));
|
|
||||||
|
|
||||||
// Send the vector to a regular task.
|
|
||||||
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler task failed");
|
|
||||||
// If this happens, there is a high chance that the maximum packet length was
|
|
||||||
// exceeded. Circular mode is not used here, so data might be missed.
|
|
||||||
defmt::warn!(
|
|
||||||
"rx transfer with maximum length {}, might miss data",
|
|
||||||
TC_BUF_LEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
|
|
||||||
fn serial_isr(mut cx: serial_isr::Context) {
|
|
||||||
cx.shared
|
|
||||||
.tx_shared
|
|
||||||
.lock(|tx_shared| match &mut tx_shared.state {
|
|
||||||
UartTxState::Idle(_) => (),
|
|
||||||
UartTxState::Transmitting(transfer) => {
|
|
||||||
let transfer_ref = transfer.as_ref().unwrap();
|
|
||||||
if transfer_ref.is_complete() {
|
|
||||||
let transfer = transfer.take().unwrap();
|
|
||||||
let (_, dma_channel, mut tx) = transfer.stop();
|
|
||||||
tx.clear_event(TxEvent::TransmissionComplete);
|
|
||||||
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
|
|
||||||
// We cache the last completed time to ensure that there is a minimum delay between consecutive
|
|
||||||
// transferred packets.
|
|
||||||
tx_shared.last_completed = Some(Systick::now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut tc_packet = TcPacket::new();
|
|
||||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
|
||||||
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
|
|
||||||
// Received a partial packet.
|
|
||||||
if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
|
|
||||||
let rx_transfer_owned = rx_transfer.take().unwrap();
|
|
||||||
let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
|
|
||||||
// The received data is transferred to another task now to avoid any processing overhead
|
|
||||||
// during the interrupt. There are multiple ways to do this, we use a stack
|
|
||||||
// allocated vector to do this.
|
|
||||||
tc_packet
|
|
||||||
.resize(rx_len as usize, 0)
|
|
||||||
.expect("vec resize failed");
|
|
||||||
tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
|
|
||||||
rx.clear_event(RxEvent::Idle);
|
|
||||||
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
|
|
||||||
*rx_transfer = Some(rx.read_exact(buf, ch));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"preLaunchTask": "${defaultBuildTask}",
|
|
||||||
"type": "probe-rs-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "probe-rs Debugging ",
|
|
||||||
"flashingConfig": {
|
|
||||||
"flashingEnabled": true
|
|
||||||
},
|
|
||||||
"chip": "STM32F303VCTx",
|
|
||||||
"coreConfigs": [
|
|
||||||
{
|
|
||||||
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32f3-disco-rtic",
|
|
||||||
"rttEnabled": true,
|
|
||||||
"svdFile": "STM32F303.svd"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
|
||||||
runner = "probe-rs run --chip STM32H743ZITx"
|
|
||||||
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
|
||||||
|
|
||||||
rustflags = [
|
|
||||||
"-C", "linker=flip-link",
|
|
||||||
"-C", "link-arg=-Tlink.x",
|
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
|
||||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
|
||||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
|
||||||
"-C", "link-arg=--nmagic",
|
|
||||||
# Can be useful for debugging.
|
|
||||||
# "-Clink-args=-Map=app.map"
|
|
||||||
]
|
|
||||||
|
|
||||||
[build]
|
|
||||||
# (`thumbv6m-*` is compatible with all ARM Cortex-M chips but using the right
|
|
||||||
# target improves performance)
|
|
||||||
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
|
||||||
# target = "thumbv7m-none-eabi" # Cortex-M3
|
|
||||||
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
|
|
||||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
|
||||||
|
|
||||||
[alias]
|
|
||||||
rb = "run --bin"
|
|
||||||
rrb = "run --release --bin"
|
|
||||||
|
|
||||||
[env]
|
|
||||||
DEFMT_LOG = "info"
|
|
@ -1,4 +0,0 @@
|
|||||||
/target
|
|
||||||
/.cargo/config*
|
|
||||||
/.vscode
|
|
||||||
/app.map
|
|
881
embedded-examples/stm32h7-nucleo-rtic/Cargo.lock
generated
@ -1,881 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic-polyfill"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bare-metal"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
|
|
||||||
dependencies = [
|
|
||||||
"rustc_version 0.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bare-metal"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitfield"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cast"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cobs"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cortex-m"
|
|
||||||
version = "0.7.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
|
|
||||||
dependencies = [
|
|
||||||
"bare-metal 0.2.5",
|
|
||||||
"bitfield",
|
|
||||||
"critical-section",
|
|
||||||
"embedded-hal 0.2.7",
|
|
||||||
"volatile-register",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cortex-m-rt"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2722f5b7d6ea8583cffa4d247044e280ccbb9fe501bed56552e2ba48b02d5f3d"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m-rt-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cortex-m-rt-macros"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cortex-m-semihosting"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc"
|
|
||||||
version = "3.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
|
||||||
dependencies = [
|
|
||||||
"crc-catalog",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc-catalog"
|
|
||||||
version = "2.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "critical-section"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"defmt-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt-brtt"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c2f0ac3635d0c89d12b8101fcb44a7625f5f030a1c0491124b74467eb5a58a78"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
"defmt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt-macros"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb"
|
|
||||||
dependencies = [
|
|
||||||
"defmt-parser",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt-parser"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt-test"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "290966e8c38f94b11884877242de876280d0eab934900e9642d58868e77c5df1"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m-rt",
|
|
||||||
"cortex-m-semihosting",
|
|
||||||
"defmt",
|
|
||||||
"defmt-test-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defmt-test-macros"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "delegate"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ee5df75c70b95bd3aacc8e2fd098797692fb1d54121019c4de481e42f04c8a1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive-new"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-alloc"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
"linked_list_allocator",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-dma"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446"
|
|
||||||
dependencies = [
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-hal"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
|
|
||||||
dependencies = [
|
|
||||||
"nb 0.1.3",
|
|
||||||
"void",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-hal"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
|
|
||||||
dependencies = [
|
|
||||||
"defmt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-hal-async"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
|
|
||||||
dependencies = [
|
|
||||||
"defmt",
|
|
||||||
"embedded-hal 1.0.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-hal-bus"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57b4e6ede84339ebdb418cd986e6320a34b017cdf99b5cc3efceec6450b06886"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
"defmt",
|
|
||||||
"embedded-hal 1.0.0",
|
|
||||||
"embedded-hal-async",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-storage"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fugit"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7"
|
|
||||||
dependencies = [
|
|
||||||
"gcd",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"pin-project-lite",
|
|
||||||
"pin-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gcd"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.14.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.7.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-polyfill",
|
|
||||||
"hash32 0.2.1",
|
|
||||||
"rustc_version 0.4.0",
|
|
||||||
"spin",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
|
||||||
dependencies = [
|
|
||||||
"defmt",
|
|
||||||
"hash32 0.3.1",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked_list_allocator"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "managed"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nb"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
|
||||||
dependencies = [
|
|
||||||
"nb 1.1.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nb"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_enum"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
|
||||||
dependencies = [
|
|
||||||
"num_enum_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_enum_derive"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "panic-probe"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m",
|
|
||||||
"defmt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "portable-atomic"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.82"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.36"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic"
|
|
||||||
version = "2.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c443db16326376bdd64377da268f6616d5f804aba8ce799bac7d1f7f244e9d51"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-polyfill",
|
|
||||||
"bare-metal 1.0.0",
|
|
||||||
"cortex-m",
|
|
||||||
"critical-section",
|
|
||||||
"rtic-core",
|
|
||||||
"rtic-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-common"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0786b50b81ef9d2a944a000f60405bb28bf30cd45da2d182f3fe636b2321f35c"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-core"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-macros"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-monotonics"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-polyfill",
|
|
||||||
"cfg-if",
|
|
||||||
"cortex-m",
|
|
||||||
"embedded-hal 1.0.0",
|
|
||||||
"fugit",
|
|
||||||
"rtic-time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-sync"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49b1200137ccb2bf272a1801fa6e27264535facd356cb2c1d5bc8e12aa211bad"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
"defmt",
|
|
||||||
"embedded-hal 1.0.0",
|
|
||||||
"embedded-hal-async",
|
|
||||||
"embedded-hal-bus",
|
|
||||||
"heapless 0.8.0",
|
|
||||||
"portable-atomic",
|
|
||||||
"rtic-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-time"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
"futures-util",
|
|
||||||
"rtic-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
|
||||||
dependencies = [
|
|
||||||
"semver 0.9.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
|
||||||
dependencies = [
|
|
||||||
"semver 1.0.23",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "satrs"
|
|
||||||
version = "0.2.1"
|
|
||||||
dependencies = [
|
|
||||||
"cobs",
|
|
||||||
"crc",
|
|
||||||
"defmt",
|
|
||||||
"delegate",
|
|
||||||
"derive-new",
|
|
||||||
"heapless 0.7.17",
|
|
||||||
"num-traits",
|
|
||||||
"num_enum",
|
|
||||||
"paste",
|
|
||||||
"satrs-shared",
|
|
||||||
"smallvec",
|
|
||||||
"spacepackets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "satrs-shared"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6042477018c2d43fffccaaa5099bc299a58485139b4d31c5b276889311e474f1"
|
|
||||||
dependencies = [
|
|
||||||
"spacepackets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "satrs-stm32h7-nucleo-rtic"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m",
|
|
||||||
"cortex-m-rt",
|
|
||||||
"cortex-m-semihosting",
|
|
||||||
"defmt",
|
|
||||||
"defmt-brtt",
|
|
||||||
"defmt-test",
|
|
||||||
"embedded-alloc",
|
|
||||||
"panic-probe",
|
|
||||||
"rtic",
|
|
||||||
"rtic-monotonics",
|
|
||||||
"rtic-sync",
|
|
||||||
"satrs",
|
|
||||||
"smoltcp",
|
|
||||||
"stm32h7xx-hal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
|
||||||
dependencies = [
|
|
||||||
"semver-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver-parser"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smallvec"
|
|
||||||
version = "1.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smoltcp"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"byteorder",
|
|
||||||
"cfg-if",
|
|
||||||
"defmt",
|
|
||||||
"heapless 0.8.0",
|
|
||||||
"managed",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spacepackets"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e85574d113a06312010c0ba51aadccd4ba2806231ebe9a49fc6473d0534d8696"
|
|
||||||
dependencies = [
|
|
||||||
"crc",
|
|
||||||
"defmt",
|
|
||||||
"delegate",
|
|
||||||
"num-traits",
|
|
||||||
"num_enum",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.9.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stable_deref_trait"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stm32h7"
|
|
||||||
version = "0.15.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "362f288cd8341e9209587b889c385f323e82fc237b60c272868965bb879bb9b1"
|
|
||||||
dependencies = [
|
|
||||||
"bare-metal 1.0.0",
|
|
||||||
"cortex-m",
|
|
||||||
"cortex-m-rt",
|
|
||||||
"vcell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stm32h7xx-hal"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3bd869329be25440b24e2b3583a1c016151b4a54bc36d96d82af7fcd9d010b98"
|
|
||||||
dependencies = [
|
|
||||||
"bare-metal 1.0.0",
|
|
||||||
"cast",
|
|
||||||
"cortex-m",
|
|
||||||
"embedded-dma",
|
|
||||||
"embedded-hal 0.2.7",
|
|
||||||
"embedded-storage",
|
|
||||||
"fugit",
|
|
||||||
"nb 1.1.0",
|
|
||||||
"paste",
|
|
||||||
"smoltcp",
|
|
||||||
"stm32h7",
|
|
||||||
"void",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.64"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.61"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.61"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcell"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "void"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "volatile-register"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
|
|
||||||
dependencies = [
|
|
||||||
"vcell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.7.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.7.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.64",
|
|
||||||
]
|
|
@ -1,85 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["Robin Mueller <robin.mueller.m@gmail.com>"]
|
|
||||||
name = "satrs-stm32h7-nucleo-rtic"
|
|
||||||
edition = "2021"
|
|
||||||
version = "0.1.0"
|
|
||||||
default-run = "satrs-stm32h7-nucleo-rtic"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
# needed for each integration test
|
|
||||||
[[test]]
|
|
||||||
name = "integration"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
defmt = "0.3"
|
|
||||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
|
||||||
cortex-m-semihosting = "0.5.0"
|
|
||||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
|
||||||
embedded-alloc = "0.5"
|
|
||||||
rtic-sync = { version = "1", features = ["defmt-03"] }
|
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
|
||||||
version = "0.11.0"
|
|
||||||
default-features = false
|
|
||||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "1"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
path = "../../satrs"
|
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
|
||||||
features = ["defmt", "heapless"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
defmt-test = "0.3"
|
|
||||||
|
|
||||||
# cargo build/run
|
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
opt-level = 's' # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo test
|
|
||||||
[profile.test]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
# cargo test --release
|
|
||||||
[profile.bench]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
@ -1,118 +0,0 @@
|
|||||||
sat-rs example for the STM32H73ZI-Nucleo board
|
|
||||||
=======
|
|
||||||
|
|
||||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
|
||||||
can be used on an embedded target.
|
|
||||||
It also shows how a relatively simple OBSW could be built when no standard runtime is available.
|
|
||||||
It uses [RTIC](https://rtic.rs/2/book/en/) as the concurrency framework and the
|
|
||||||
[defmt](https://defmt.ferrous-systems.com/) framework for logging.
|
|
||||||
|
|
||||||
The STM32H743ZIT device was picked because it is one of the more powerful Cortex-M based devices
|
|
||||||
available for STM with which also has a little bit more RAM available and also allows commanding
|
|
||||||
via TCP/IP.
|
|
||||||
|
|
||||||
## Pre-Requisites
|
|
||||||
|
|
||||||
Make sure the following tools are installed:
|
|
||||||
|
|
||||||
1. [`probe-rs`](https://probe.rs/): Application used to flash and debug the MCU.
|
|
||||||
2. Optional and recommended: [VS Code](https://code.visualstudio.com/) with
|
|
||||||
[probe-rs plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger)
|
|
||||||
for debugging.
|
|
||||||
|
|
||||||
## Preparing Rust and the repository
|
|
||||||
|
|
||||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
|
||||||
If you have not installed it yet, you can do so with
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup target add thumbv7em-none-eabihf
|
|
||||||
```
|
|
||||||
|
|
||||||
A default `.cargo` config file is provided for this project, but needs to be copied to have
|
|
||||||
the correct name. This is so that the config file can be updated or edited for custom needs
|
|
||||||
without being tracked by git.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp def_config.toml config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
The configuration file will also set the target so it does not always have to be specified with
|
|
||||||
the `--target` argument.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
After that, assuming that you have a `.cargo/config.toml` setting the correct build target,
|
|
||||||
you can simply build the application with
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flashing from the command line
|
|
||||||
|
|
||||||
You can flash the application from the command line using `probe-rs`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
probe-rs run --chip STM32H743ZITx
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debugging with VS Code
|
|
||||||
|
|
||||||
The STM32F3-Discovery comes with an on-board ST-Link so all that is required to flash and debug
|
|
||||||
the board is a Mini-USB cable. The code in this repository was debugged using [`probe-rs`](https://probe.rs/docs/tools/debuggerA)
|
|
||||||
and the VS Code [`probe-rs` plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger).
|
|
||||||
Make sure to install this plugin first.
|
|
||||||
|
|
||||||
Sample configuration files are provided inside the `vscode` folder.
|
|
||||||
Use `cp vscode .vscode -r` to use them for your project.
|
|
||||||
|
|
||||||
Some sample configuration files for VS Code were provided as well. You can simply use `Run` and `Debug`
|
|
||||||
to automatically rebuild and flash your application.
|
|
||||||
|
|
||||||
The `tasks.json` and `launch.json` files are generic and you can use them immediately by opening
|
|
||||||
the folder in VS code or adding it to a workspace.
|
|
||||||
|
|
||||||
## Commanding with Python
|
|
||||||
|
|
||||||
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
|
|
||||||
using COBS encoded PUS packets.
|
|
||||||
|
|
||||||
It is recommended to use a virtual environment to do this. To set up one in the command line,
|
|
||||||
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
|
|
||||||
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
|
|
||||||
on how to activate the environment and then use the following command to install the required
|
|
||||||
dependency:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
The packets are exchanged using a dedicated serial interface. You can use any generic USB-to-UART
|
|
||||||
converter device with the TX pin connected to the PA3 pin and the RX pin connected to the PA2 pin.
|
|
||||||
|
|
||||||
A default configuration file for the python application is provided and can be used by running
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp def_tmtc_conf.json tmtc_conf.json
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, you can for example send a ping to the MCU using the following command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./main.py -p /ping
|
|
||||||
```
|
|
||||||
|
|
||||||
You can configure the blinky frequency using
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./main.py -p /change_blink_freq
|
|
||||||
```
|
|
||||||
|
|
||||||
All these commands will package a PUS telecommand which will be sent to the MCU using the COBS
|
|
||||||
format as the packet framing format.
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [STM32H743ZI Ethernet link checker example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/ethernet-nucleo-h743zi2.rs)
|
|
||||||
- [smoltcp DHCP client](https://github.com/smoltcp-rs/smoltcp/blob/main/examples/dhcp_client.rs)
|
|
@ -1,119 +0,0 @@
|
|||||||
/* Taken from https://github.com/stm32-rs/stm32h7xx-hal/pull/299, adapted slightly to work with */
|
|
||||||
/* flip-link */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
/* This file is intended for parts in the STM32H743/743v/753/753v families (RM0433), */
|
|
||||||
/* with the exception of the STM32H742/742v parts which have a different RAM layout. */
|
|
||||||
/* - FLASH and RAM are mandatory memory sections. */
|
|
||||||
/* - The sum of all non-FLASH sections must add to 1060K total device RAM. */
|
|
||||||
/* - The FLASH section size must match your device, see table below. */
|
|
||||||
|
|
||||||
/* FLASH */
|
|
||||||
/* Flash is divided in two independent banks (except 750xB). */
|
|
||||||
/* Select the appropriate FLASH size for your device. */
|
|
||||||
/* - STM32H750xB 128K (only FLASH1) */
|
|
||||||
/* - STM32H750xB 1M (512K + 512K) */
|
|
||||||
/* - STM32H743xI/753xI 2M ( 1M + 1M) */
|
|
||||||
FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M
|
|
||||||
FLASH2 : ORIGIN = 0x08100000, LENGTH = 1M
|
|
||||||
|
|
||||||
/* Data TCM */
|
|
||||||
/* - Two contiguous 64KB RAMs. */
|
|
||||||
/* - Used for interrupt handlers, stacks and general RAM. */
|
|
||||||
/* - Zero wait-states. */
|
|
||||||
/* - The DTCM is taken as the origin of the base ram. (See below.) */
|
|
||||||
/* This is also where the interrupt table and such will live, */
|
|
||||||
/* which is required for deterministic performance. */
|
|
||||||
/* Need a region called RAM */
|
|
||||||
/* DTCM : ORIGIN = 0x20000000, LENGTH = 128K */
|
|
||||||
RAM : ORIGIN = 0x20000000, LENGTH = 128K
|
|
||||||
|
|
||||||
/* Instruction TCM */
|
|
||||||
/* - Used for latency-critical interrupt handlers etc. */
|
|
||||||
/* - Zero wait-states. */
|
|
||||||
ITCM : ORIGIN = 0x00000000, LENGTH = 64K
|
|
||||||
|
|
||||||
/* AXI SRAM */
|
|
||||||
/* - AXISRAM is in D1 and accessible by all system masters except BDMA. */
|
|
||||||
/* - Suitable for application data not stored in DTCM. */
|
|
||||||
/* - Zero wait-states. */
|
|
||||||
AXISRAM : ORIGIN = 0x24000000, LENGTH = 512K
|
|
||||||
|
|
||||||
/* AHB SRAM */
|
|
||||||
/* - SRAM1-3 are in D2 and accessible by all system masters except BDMA. */
|
|
||||||
/* Suitable for use as DMA buffers. */
|
|
||||||
/* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */
|
|
||||||
/* buffers, for storing application data in lower-power modes. */
|
|
||||||
/* - Zero wait-states. */
|
|
||||||
SRAM1 : ORIGIN = 0x30000000, LENGTH = 128K
|
|
||||||
SRAM2 : ORIGIN = 0x30020000, LENGTH = 128K
|
|
||||||
SRAM3 : ORIGIN = 0x30040000, LENGTH = 32K
|
|
||||||
SRAM4 : ORIGIN = 0x38000000, LENGTH = 64K
|
|
||||||
|
|
||||||
/* Backup SRAM */
|
|
||||||
BSRAM : ORIGIN = 0x38800000, LENGTH = 4K
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
/* Assign the memory regions defined above for use. */
|
|
||||||
/*
|
|
||||||
|
|
||||||
/* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */
|
|
||||||
/* These do not work with flip-link */
|
|
||||||
REGION_ALIAS(FLASH, FLASH1);
|
|
||||||
/* REGION_ALIAS(RAM, DTCM); */
|
|
||||||
|
|
||||||
/* The location of the stack can be overridden using the `_stack_start` symbol. */
|
|
||||||
/* - Set the stack location at the end of RAM, using all remaining space. */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* The location of the .text section can be overridden using the */
|
|
||||||
/* `_stext` symbol. By default it will place after .vector_table. */
|
|
||||||
/* _stext = ORIGIN(FLASH) + 0x40c; */
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
/* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */
|
|
||||||
/* - All other memories connect to a 32-bit wide bus -> align to 4 bytes. */
|
|
||||||
SECTIONS {
|
|
||||||
.flash2 (NOLOAD) : ALIGN(4) {
|
|
||||||
*(.flash2 .flash2.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > FLASH2
|
|
||||||
|
|
||||||
.itcm (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.itcm .itcm.*);
|
|
||||||
. = ALIGN(8);
|
|
||||||
} > ITCM
|
|
||||||
|
|
||||||
.axisram (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.axisram .axisram.*);
|
|
||||||
. = ALIGN(8);
|
|
||||||
} > AXISRAM
|
|
||||||
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM1
|
|
||||||
|
|
||||||
.sram2 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram2 .sram2.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM2
|
|
||||||
|
|
||||||
.sram3 (NOLOAD) : ALIGN(4) {
|
|
||||||
*(.sram3 .sram3.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM3
|
|
||||||
|
|
||||||
.sram4 (NOLOAD) : ALIGN(4) {
|
|
||||||
*(.sram4 .sram4.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM4
|
|
||||||
|
|
||||||
.bsram (NOLOAD) : ALIGN(4) {
|
|
||||||
*(.bsram .bsram.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > BSRAM
|
|
||||||
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"com_if": "udp",
|
|
||||||
"tcpip_udp_port": 7301
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
tmtccmd == 8.0.1
|
|
||||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
|
|
@ -1,55 +0,0 @@
|
|||||||
//! Blinks an LED
|
|
||||||
//!
|
|
||||||
//! This assumes that LD2 (blue) is connected to pb7 and LD3 (red) is connected
|
|
||||||
//! to pb14. This assumption is true for the nucleo-h743zi board.
|
|
||||||
|
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
use satrs_stm32h7_nucleo_rtic as _;
|
|
||||||
|
|
||||||
use stm32h7xx_hal::{block, prelude::*, timer::Timer};
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
defmt::println!("starting stm32h7 blinky example");
|
|
||||||
|
|
||||||
// Get access to the device specific peripherals from the peripheral access crate
|
|
||||||
let dp = stm32h7xx_hal::stm32::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Take ownership over the RCC devices and convert them into the corresponding HAL structs
|
|
||||||
let rcc = dp.RCC.constrain();
|
|
||||||
|
|
||||||
let pwr = dp.PWR.constrain();
|
|
||||||
let pwrcfg = pwr.freeze();
|
|
||||||
|
|
||||||
// Freeze the configuration of all the clocks in the system and
|
|
||||||
// retrieve the Core Clock Distribution and Reset (CCDR) object
|
|
||||||
let rcc = rcc.use_hse(8.MHz()).bypass_hse();
|
|
||||||
let ccdr = rcc.freeze(pwrcfg, &dp.SYSCFG);
|
|
||||||
|
|
||||||
// Acquire the GPIOB peripheral
|
|
||||||
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
|
||||||
|
|
||||||
// Configure gpio B pin 0 as a push-pull output.
|
|
||||||
let mut ld1 = gpiob.pb0.into_push_pull_output();
|
|
||||||
|
|
||||||
// Configure gpio B pin 7 as a push-pull output.
|
|
||||||
let mut ld2 = gpiob.pb7.into_push_pull_output();
|
|
||||||
|
|
||||||
// Configure gpio B pin 14 as a push-pull output.
|
|
||||||
let mut ld3 = gpiob.pb14.into_push_pull_output();
|
|
||||||
|
|
||||||
// Configure the timer to trigger an update every second
|
|
||||||
let mut timer = Timer::tim1(dp.TIM1, ccdr.peripheral.TIM1, &ccdr.clocks);
|
|
||||||
timer.start(1.Hz());
|
|
||||||
|
|
||||||
// Wait for the timer to trigger an update and change the state of the LED
|
|
||||||
loop {
|
|
||||||
ld1.toggle();
|
|
||||||
ld2.toggle();
|
|
||||||
ld3.toggle();
|
|
||||||
block!(timer.wait()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use satrs_stm32h7_nucleo_rtic as _; // global logger + panicking-behavior + memory layout
|
|
||||||
|
|
||||||
#[cortex_m_rt::entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
defmt::println!("Hello, world!");
|
|
||||||
|
|
||||||
satrs_stm32h7_nucleo_rtic::exit()
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_semihosting::debug;
|
|
||||||
|
|
||||||
use defmt_brtt as _; // global logger
|
|
||||||
|
|
||||||
// TODO(5) adjust HAL import
|
|
||||||
use stm32h7xx_hal as _; // memory layout
|
|
||||||
|
|
||||||
use panic_probe as _;
|
|
||||||
|
|
||||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
|
||||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
|
||||||
#[defmt::panic_handler]
|
|
||||||
fn panic() -> ! {
|
|
||||||
cortex_m::asm::udf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
|
||||||
/// with status code 0.
|
|
||||||
pub fn exit() -> ! {
|
|
||||||
loop {
|
|
||||||
debug::exit(debug::EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardfault handler.
|
|
||||||
///
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
|
||||||
/// with an error. This seems better than the default, which is to spin in a
|
|
||||||
/// loop.
|
|
||||||
#[cortex_m_rt::exception]
|
|
||||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
|
||||||
loop {
|
|
||||||
debug::exit(debug::EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
|
||||||
// once within a crate. the module can be in any file but there can only be at most
|
|
||||||
// one `#[tests]` module in this library crate
|
|
||||||
#[cfg(test)]
|
|
||||||
#[defmt_test::tests]
|
|
||||||
mod unit_tests {
|
|
||||||
use defmt::assert;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert!(true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,528 +0,0 @@
|
|||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use rtic::app;
|
|
||||||
use rtic_monotonics::systick::Systick;
|
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
|
||||||
use satrs::static_subpool;
|
|
||||||
// global logger + panicking-behavior + memory layout
|
|
||||||
use satrs_stm32h7_nucleo_rtic as _;
|
|
||||||
use smoltcp::socket::udp::UdpMetadata;
|
|
||||||
use smoltcp::socket::{dhcpv4, udp};
|
|
||||||
|
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use embedded_alloc::Heap;
|
|
||||||
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
|
|
||||||
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
|
|
||||||
use stm32h7xx_hal::ethernet;
|
|
||||||
|
|
||||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
|
||||||
const PORT: u16 = 7301;
|
|
||||||
|
|
||||||
const HEAP_SIZE: usize = 131_072;
|
|
||||||
|
|
||||||
const TC_SOURCE_CHANNEL_DEPTH: usize = 16;
|
|
||||||
pub type SharedPool = StaticHeaplessMemoryPool<3>;
|
|
||||||
pub type TcSourceChannel = rtic_sync::channel::Channel<PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
pub type TcSourceTx = rtic_sync::channel::Sender<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
|
|
||||||
#[global_allocator]
|
|
||||||
static HEAP: Heap = Heap::empty();
|
|
||||||
|
|
||||||
// We place the memory pool buffers inside the larger AXISRAM.
|
|
||||||
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
|
|
||||||
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
|
|
||||||
pub const SUBPOOL_MEDIUM_NUM_BLOCKS: u16 = 16;
|
|
||||||
pub const SUBPOOL_MEDIUM_BLOCK_SIZE: usize = 128;
|
|
||||||
pub const SUBPOOL_LARGE_NUM_BLOCKS: u16 = 8;
|
|
||||||
pub const SUBPOOL_LARGE_BLOCK_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
// This data will be held by Net through a mutable reference
|
|
||||||
pub struct NetStorageStatic<'a> {
|
|
||||||
socket_storage: [SocketStorage<'a>; 8],
|
|
||||||
}
|
|
||||||
// MaybeUninit allows us write code that is correct even if STORE is not
|
|
||||||
// initialised by the runtime
|
|
||||||
static mut STORE: MaybeUninit<NetStorageStatic> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
static mut UDP_RX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
|
|
||||||
static mut UDP_RX: [u8; 2048] = [0; 2048];
|
|
||||||
static mut UDP_TX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
|
|
||||||
static mut UDP_TX: [u8; 2048] = [0; 2048];
|
|
||||||
|
|
||||||
/// Locally administered MAC address
|
|
||||||
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
|
|
||||||
|
|
||||||
pub struct Net {
|
|
||||||
iface: Interface,
|
|
||||||
ethdev: ethernet::EthernetDMA<4, 4>,
|
|
||||||
dhcp_handle: SocketHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Net {
|
|
||||||
pub fn new(
|
|
||||||
sockets: &mut SocketSet<'static>,
|
|
||||||
mut ethdev: ethernet::EthernetDMA<4, 4>,
|
|
||||||
ethernet_addr: HardwareAddress,
|
|
||||||
) -> Self {
|
|
||||||
let config = Config::new(ethernet_addr);
|
|
||||||
let mut iface = Interface::new(
|
|
||||||
config,
|
|
||||||
&mut ethdev,
|
|
||||||
smoltcp::time::Instant::from_millis((Systick::now() - Systick::ZERO).to_millis()),
|
|
||||||
);
|
|
||||||
// Create sockets
|
|
||||||
let dhcp_socket = dhcpv4::Socket::new();
|
|
||||||
iface.update_ip_addrs(|addrs| {
|
|
||||||
let _ = addrs.push(IpCidr::new(IpAddress::v4(192, 168, 1, 99), 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
let dhcp_handle = sockets.add(dhcp_socket);
|
|
||||||
Net {
|
|
||||||
iface,
|
|
||||||
ethdev,
|
|
||||||
dhcp_handle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Polls on the ethernet interface. You should refer to the smoltcp
|
|
||||||
/// documentation for poll() to understand how to call poll efficiently
|
|
||||||
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
|
|
||||||
let uptime = Systick::now() - Systick::ZERO;
|
|
||||||
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
|
|
||||||
|
|
||||||
self.iface.poll(timestamp, &mut self.ethdev, sockets)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_dhcp<'a>(&mut self, sockets: &'a mut SocketSet) -> Option<dhcpv4::Event<'a>> {
|
|
||||||
let opt_event = sockets.get_mut::<dhcpv4::Socket>(self.dhcp_handle).poll();
|
|
||||||
if let Some(event) = &opt_event {
|
|
||||||
match event {
|
|
||||||
dhcpv4::Event::Deconfigured => {
|
|
||||||
defmt::info!("DHCP lost configuration");
|
|
||||||
self.iface.update_ip_addrs(|addrs| addrs.clear());
|
|
||||||
self.iface.routes_mut().remove_default_ipv4_route();
|
|
||||||
}
|
|
||||||
dhcpv4::Event::Configured(config) => {
|
|
||||||
defmt::info!("DHCP configuration acquired");
|
|
||||||
defmt::info!("IP address: {}", config.address);
|
|
||||||
self.iface.update_ip_addrs(|addrs| {
|
|
||||||
addrs.clear();
|
|
||||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(router) = config.router {
|
|
||||||
defmt::debug!("Default gateway: {}", router);
|
|
||||||
self.iface
|
|
||||||
.routes_mut()
|
|
||||||
.add_default_ipv4_route(router)
|
|
||||||
.unwrap();
|
|
||||||
} else {
|
|
||||||
defmt::debug!("Default gateway: None");
|
|
||||||
self.iface.routes_mut().remove_default_ipv4_route();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opt_event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UdpNet {
|
|
||||||
udp_handle: SocketHandle,
|
|
||||||
last_client: Option<UdpMetadata>,
|
|
||||||
tc_source_tx: TcSourceTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpNet {
|
|
||||||
pub fn new<'sockets>(sockets: &mut SocketSet<'sockets>, tc_source_tx: TcSourceTx) -> Self {
|
|
||||||
// SAFETY: The RX and TX buffers are passed here and not used anywhere else.
|
|
||||||
let udp_rx_buffer =
|
|
||||||
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_RX_META[..] }, unsafe {
|
|
||||||
&mut UDP_RX[..]
|
|
||||||
});
|
|
||||||
let udp_tx_buffer =
|
|
||||||
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_TX_META[..] }, unsafe {
|
|
||||||
&mut UDP_TX[..]
|
|
||||||
});
|
|
||||||
let udp_socket = smoltcp::socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
|
|
||||||
|
|
||||||
let udp_handle = sockets.add(udp_socket);
|
|
||||||
Self {
|
|
||||||
udp_handle,
|
|
||||||
last_client: None,
|
|
||||||
tc_source_tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll<'sockets>(
|
|
||||||
&mut self,
|
|
||||||
sockets: &'sockets mut SocketSet,
|
|
||||||
shared_pool: &mut SharedPool,
|
|
||||||
) {
|
|
||||||
let socket = sockets.get_mut::<udp::Socket>(self.udp_handle);
|
|
||||||
if !socket.is_open() {
|
|
||||||
if let Err(e) = socket.bind(PORT) {
|
|
||||||
defmt::warn!("binding UDP socket failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match socket.recv() {
|
|
||||||
Ok((data, client)) => {
|
|
||||||
match shared_pool.add(data) {
|
|
||||||
Ok(store_addr) => {
|
|
||||||
if let Err(e) = self.tc_source_tx.try_send(store_addr) {
|
|
||||||
defmt::warn!("TC source channel is full: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("could not add UDP packet to shared pool: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.last_client = Some(client);
|
|
||||||
// TODO: Implement packet wiretapping.
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
udp::RecvError::Exhausted => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
udp::RecvError::Truncated => {
|
|
||||||
defmt::warn!("UDP packet was truncacted");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[app(device = stm32h7xx_hal::stm32, peripherals = true)]
|
|
||||||
mod app {
|
|
||||||
use core::ptr::addr_of_mut;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use rtic_monotonics::systick::fugit::MillisDurationU32;
|
|
||||||
use rtic_monotonics::systick::Systick;
|
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
|
||||||
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
|
||||||
use stm32h7xx_hal::gpio::{Output, Pin};
|
|
||||||
use stm32h7xx_hal::prelude::*;
|
|
||||||
use stm32h7xx_hal::stm32::Interrupt;
|
|
||||||
|
|
||||||
struct BlinkyLeds {
|
|
||||||
led1: Pin<'B', 7, Output>,
|
|
||||||
led2: Pin<'B', 14, Output>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
leds: BlinkyLeds,
|
|
||||||
link_led: Pin<'B', 0, Output>,
|
|
||||||
net: Net,
|
|
||||||
udp: UdpNet,
|
|
||||||
tc_source_rx: TcSourceRx,
|
|
||||||
phy: ethernet::phy::LAN8742A<EthernetMAC>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {
|
|
||||||
blink_freq: MillisDurationU32,
|
|
||||||
eth_link_up: bool,
|
|
||||||
sockets: SocketSet<'static>,
|
|
||||||
shared_pool: SharedPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(mut cx: init::Context) -> (Shared, Local) {
|
|
||||||
defmt::println!("Starting sat-rs demo application for the STM32H743ZIT");
|
|
||||||
|
|
||||||
let pwr = cx.device.PWR.constrain();
|
|
||||||
let pwrcfg = pwr.freeze();
|
|
||||||
|
|
||||||
let rcc = cx.device.RCC.constrain();
|
|
||||||
// Try to keep the clock configuration similar to one used in STM examples:
|
|
||||||
// https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/NUCLEO-H743ZI/Examples/GPIO/GPIO_EXTI/Src/main.c
|
|
||||||
let ccdr = rcc
|
|
||||||
.sys_ck(400.MHz())
|
|
||||||
.hclk(200.MHz())
|
|
||||||
.use_hse(8.MHz())
|
|
||||||
.bypass_hse()
|
|
||||||
.pclk1(100.MHz())
|
|
||||||
.pclk2(100.MHz())
|
|
||||||
.pclk3(100.MHz())
|
|
||||||
.pclk4(100.MHz())
|
|
||||||
.freeze(pwrcfg, &cx.device.SYSCFG);
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
|
||||||
Systick::start(
|
|
||||||
cx.core.SYST,
|
|
||||||
ccdr.clocks.sys_ck().to_Hz(),
|
|
||||||
systick_mono_token,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
|
|
||||||
// good for.
|
|
||||||
cx.core.SCB.enable_icache();
|
|
||||||
cx.core.DWT.enable_cycle_counter();
|
|
||||||
|
|
||||||
let gpioa = cx.device.GPIOA.split(ccdr.peripheral.GPIOA);
|
|
||||||
let gpiob = cx.device.GPIOB.split(ccdr.peripheral.GPIOB);
|
|
||||||
let gpioc = cx.device.GPIOC.split(ccdr.peripheral.GPIOC);
|
|
||||||
let gpiog = cx.device.GPIOG.split(ccdr.peripheral.GPIOG);
|
|
||||||
|
|
||||||
let link_led = gpiob.pb0.into_push_pull_output();
|
|
||||||
let mut led1 = gpiob.pb7.into_push_pull_output();
|
|
||||||
let mut led2 = gpiob.pb14.into_push_pull_output();
|
|
||||||
|
|
||||||
// Criss-cross pattern looks cooler.
|
|
||||||
led1.set_high();
|
|
||||||
led2.set_low();
|
|
||||||
let leds = BlinkyLeds { led1, led2 };
|
|
||||||
|
|
||||||
let rmii_ref_clk = gpioa.pa1.into_alternate::<11>();
|
|
||||||
let rmii_mdio = gpioa.pa2.into_alternate::<11>();
|
|
||||||
let rmii_mdc = gpioc.pc1.into_alternate::<11>();
|
|
||||||
let rmii_crs_dv = gpioa.pa7.into_alternate::<11>();
|
|
||||||
let rmii_rxd0 = gpioc.pc4.into_alternate::<11>();
|
|
||||||
let rmii_rxd1 = gpioc.pc5.into_alternate::<11>();
|
|
||||||
let rmii_tx_en = gpiog.pg11.into_alternate::<11>();
|
|
||||||
let rmii_txd0 = gpiog.pg13.into_alternate::<11>();
|
|
||||||
let rmii_txd1 = gpiob.pb13.into_alternate::<11>();
|
|
||||||
|
|
||||||
let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS);
|
|
||||||
|
|
||||||
/// Ethernet descriptor rings are a global singleton
|
|
||||||
#[link_section = ".sram3.eth"]
|
|
||||||
static mut DES_RING: MaybeUninit<ethernet::DesRing<4, 4>> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
let (eth_dma, eth_mac) = ethernet::new(
|
|
||||||
cx.device.ETHERNET_MAC,
|
|
||||||
cx.device.ETHERNET_MTL,
|
|
||||||
cx.device.ETHERNET_DMA,
|
|
||||||
(
|
|
||||||
rmii_ref_clk,
|
|
||||||
rmii_mdio,
|
|
||||||
rmii_mdc,
|
|
||||||
rmii_crs_dv,
|
|
||||||
rmii_rxd0,
|
|
||||||
rmii_rxd1,
|
|
||||||
rmii_tx_en,
|
|
||||||
rmii_txd0,
|
|
||||||
rmii_txd1,
|
|
||||||
),
|
|
||||||
// SAFETY: We do not move the returned DMA struct across thread boundaries, so this
|
|
||||||
// should be safe according to the docs.
|
|
||||||
unsafe { DES_RING.assume_init_mut() },
|
|
||||||
mac_addr,
|
|
||||||
ccdr.peripheral.ETH1MAC,
|
|
||||||
&ccdr.clocks,
|
|
||||||
);
|
|
||||||
// Initialise ethernet PHY...
|
|
||||||
let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
|
|
||||||
lan8742a.phy_reset();
|
|
||||||
lan8742a.phy_init();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ethernet::enable_interrupt();
|
|
||||||
cx.core.NVIC.set_priority(Interrupt::ETH, 196); // Mid prio
|
|
||||||
cortex_m::peripheral::NVIC::unmask(Interrupt::ETH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafe: mutable reference to static storage, we only do this once
|
|
||||||
let store = unsafe {
|
|
||||||
let store_ptr = STORE.as_mut_ptr();
|
|
||||||
|
|
||||||
// Initialise the socket_storage field. Using `write` instead of
|
|
||||||
// assignment via `=` to not call `drop` on the old, uninitialised
|
|
||||||
// value
|
|
||||||
addr_of_mut!((*store_ptr).socket_storage).write([SocketStorage::EMPTY; 8]);
|
|
||||||
|
|
||||||
// Now that all fields are initialised we can safely use
|
|
||||||
// assume_init_mut to return a mutable reference to STORE
|
|
||||||
STORE.assume_init_mut()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (tc_source_tx, tc_source_rx) =
|
|
||||||
rtic_sync::make_channel!(PoolAddr, TC_SOURCE_CHANNEL_DEPTH);
|
|
||||||
|
|
||||||
let mut sockets = SocketSet::new(&mut store.socket_storage[..]);
|
|
||||||
let net = Net::new(&mut sockets, eth_dma, mac_addr.into());
|
|
||||||
let udp = UdpNet::new(&mut sockets, tc_source_tx);
|
|
||||||
|
|
||||||
let mut shared_pool: SharedPool = StaticHeaplessMemoryPool::new(true);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_SMALL,
|
|
||||||
SUBPOOL_SMALL_SIZES,
|
|
||||||
SUBPOOL_SMALL_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_SMALL_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_MEDIUM,
|
|
||||||
SUBPOOL_MEDIUM_SIZES,
|
|
||||||
SUBPOOL_MEDIUM_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_MEDIUM_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_LARGE,
|
|
||||||
SUBPOOL_LARGE_SIZES,
|
|
||||||
SUBPOOL_LARGE_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_LARGE_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
|
||||||
|
|
||||||
shared_pool
|
|
||||||
.grow(
|
|
||||||
unsafe { SUBPOOL_SMALL.assume_init_mut() },
|
|
||||||
unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
|
|
||||||
SUBPOOL_SMALL_NUM_BLOCKS,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
shared_pool
|
|
||||||
.grow(
|
|
||||||
unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
|
|
||||||
unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
|
|
||||||
SUBPOOL_MEDIUM_NUM_BLOCKS,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
shared_pool
|
|
||||||
.grow(
|
|
||||||
unsafe { SUBPOOL_LARGE.assume_init_mut() },
|
|
||||||
unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
|
|
||||||
SUBPOOL_LARGE_NUM_BLOCKS,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
|
|
||||||
// Set up global allocator. Use AXISRAM for the heap.
|
|
||||||
#[link_section = ".axisram"]
|
|
||||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
|
||||||
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
|
|
||||||
|
|
||||||
eth_link_check::spawn().expect("eth link check failed");
|
|
||||||
blinky::spawn().expect("spawning blink task failed");
|
|
||||||
udp_task::spawn().expect("spawning UDP task failed");
|
|
||||||
tc_source_task::spawn().expect("spawning TC source task failed");
|
|
||||||
|
|
||||||
(
|
|
||||||
Shared {
|
|
||||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
|
||||||
eth_link_up: false,
|
|
||||||
sockets,
|
|
||||||
shared_pool,
|
|
||||||
},
|
|
||||||
Local {
|
|
||||||
link_led,
|
|
||||||
leds,
|
|
||||||
net,
|
|
||||||
udp,
|
|
||||||
tc_source_rx,
|
|
||||||
phy: lan8742a,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(local = [leds], shared=[blink_freq])]
|
|
||||||
async fn blinky(mut cx: blinky::Context) {
|
|
||||||
let leds = cx.local.leds;
|
|
||||||
loop {
|
|
||||||
leds.led1.toggle();
|
|
||||||
leds.led2.toggle();
|
|
||||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
|
||||||
Systick::delay(current_blink_freq).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This task checks for the network link.
|
|
||||||
#[task(local=[link_led, phy], shared=[eth_link_up])]
|
|
||||||
async fn eth_link_check(mut cx: eth_link_check::Context) {
|
|
||||||
let phy = cx.local.phy;
|
|
||||||
let link_led = cx.local.link_led;
|
|
||||||
loop {
|
|
||||||
let link_was_up = cx.shared.eth_link_up.lock(|link_up| *link_up);
|
|
||||||
if phy.poll_link() {
|
|
||||||
if !link_was_up {
|
|
||||||
link_led.set_high();
|
|
||||||
cx.shared.eth_link_up.lock(|link_up| *link_up = true);
|
|
||||||
defmt::info!("Ethernet link up");
|
|
||||||
}
|
|
||||||
} else if link_was_up {
|
|
||||||
link_led.set_low();
|
|
||||||
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
|
|
||||||
defmt::info!("Ethernet link down");
|
|
||||||
}
|
|
||||||
Systick::delay(100.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds=ETH, local=[net], shared=[sockets])]
|
|
||||||
fn eth_isr(mut cx: eth_isr::Context) {
|
|
||||||
// SAFETY: We do not write the register mentioned inside the docs anywhere else.
|
|
||||||
unsafe {
|
|
||||||
ethernet::interrupt_handler();
|
|
||||||
}
|
|
||||||
// Check and process ETH frames and DHCP. UDP is checked in a different task.
|
|
||||||
cx.shared.sockets.lock(|sockets| {
|
|
||||||
cx.local.net.poll(sockets);
|
|
||||||
cx.local.net.poll_dhcp(sockets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This task routes UDP packets.
|
|
||||||
#[task(local=[udp], shared=[sockets, shared_pool])]
|
|
||||||
async fn udp_task(mut cx: udp_task::Context) {
|
|
||||||
loop {
|
|
||||||
cx.shared.sockets.lock(|sockets| {
|
|
||||||
cx.shared.shared_pool.lock(|pool| {
|
|
||||||
cx.local.udp.poll(sockets, pool);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
Systick::delay(40.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This task handles all the incoming telecommands.
|
|
||||||
#[task(local=[read_buf: [u8; 1024] = [0; 1024], tc_source_rx], shared=[shared_pool])]
|
|
||||||
async fn tc_source_task(mut cx: tc_source_task::Context) {
|
|
||||||
loop {
|
|
||||||
let recv_result = cx.local.tc_source_rx.recv().await;
|
|
||||||
match recv_result {
|
|
||||||
Ok(pool_addr) => {
|
|
||||||
cx.shared.shared_pool.lock(|pool| {
|
|
||||||
match pool.read(&pool_addr, cx.local.read_buf.as_mut()) {
|
|
||||||
Ok(packet_len) => {
|
|
||||||
defmt::info!("received {} bytes in the TC source task", packet_len);
|
|
||||||
match PusTcReader::new(&cx.local.read_buf[0..packet_len]) {
|
|
||||||
Ok((packet, _tc_len)) => {
|
|
||||||
// TODO: Handle packet here or dispatch to dedicated PUS
|
|
||||||
// handler? Dispatching could simplify some things and make
|
|
||||||
// the software more scalable..
|
|
||||||
defmt::info!("received PUS packet: {}", packet);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::info!("invalid TC format, not a PUS packet: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Err(e) = pool.delete(pool_addr) {
|
|
||||||
defmt::warn!("deleting TC data failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("TC packet read failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("TC source reception error: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
use stm32h7_testapp as _; // memory layout + panic handler
|
|
||||||
|
|
||||||
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
|
|
||||||
// feature)
|
|
||||||
#[defmt_test::tests]
|
|
||||||
mod tests {
|
|
||||||
use defmt::assert;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert!(true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
/settings.json
|
|
||||||
/.cortex-debug.*
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
|
||||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
|
||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
|
||||||
"recommendations": [
|
|
||||||
"rust-lang.rust",
|
|
||||||
"probe-rs.probe-rs-debugger"
|
|
||||||
],
|
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"preLaunchTask": "${defaultBuildTask}",
|
|
||||||
"type": "probe-rs-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "probe-rs Debugging ",
|
|
||||||
"flashingConfig": {
|
|
||||||
"flashingEnabled": true
|
|
||||||
},
|
|
||||||
"chip": "STM32H743ZITx",
|
|
||||||
"coreConfigs": [
|
|
||||||
{
|
|
||||||
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32h7-nucleo-rtic",
|
|
||||||
"rttEnabled": true,
|
|
||||||
"svdFile": "STM32H743.svd"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
// for the documentation about the tasks.json format
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "cargo build",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 116 KiB |
@ -1,260 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0" xml:space="preserve"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="360.0" width="479.0" x="771.3047672479152" y="458.0"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="237.5" y="178.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="177.64799999999997" width="200.75199999999973" x="1037.5527672479152" y="470.15200000000027"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.919921875" x="13.264464667588754" xml:space="preserve" y="8.302185845943427">Simulation<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.433926114471642" nodeRatioY="-0.45326608886143704" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="84.39999999999986" x="1068.8351781652768" y="508.2800000000002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.638671875" x="23.380664062499818" xml:space="preserve" y="8.015625">PCDU<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="550.4800000000001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.453125" x="13.973437499999818" xml:space="preserve" y="8.015625">Magnetometer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="594.9000000000001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="88.83203125" x="15.783984374999818" xml:space="preserve" y="8.015625">Magnetorquer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="783.4063563305535" y="545.2800000000002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="85.931640625" x="17.234179687499932" xml:space="preserve" y="8.015625">SimController<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n6">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="840.5407126611072" y="677.8000000000002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.05078125" x="7.674609374999932" xml:space="preserve" y="8.015625">UDP TC Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n7">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="1005.2814253222144" y="677.8000000000002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.111328125" x="11.644335937499932" xml:space="preserve" y="8.015625">UDP TM Sender<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n8">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="34.0" width="120.39999999999986" x="931.6174253222144" y="775.5920000000002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.740234375" x="40.82988281249993" xml:space="preserve" y="8.015625">Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<edge id="e0" source="n5" target="n3">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="-5.199999999999932"/>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e1" source="n5" target="n2">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="60.19999999999993" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1023.8695890826383" y="562.2800000000002"/>
|
|
||||||
<y:Point x="1023.8695890826383" y="525.2800000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e2" source="n5" target="n4">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="48.72964366944643" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1023.8695890826383" y="562.2800000000002"/>
|
|
||||||
<y:Point x="1023.8695890826383" y="611.9000000000001"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.955078125" x="12.686124396959713" xml:space="preserve" y="-22.50440429687478">schedule_event<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="13.519999999999978" distanceToCenter="true" position="left" ratio="0.11621274698385183" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e3" source="n6" target="n5">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="-5.329643669446341" ty="0.0">
|
|
||||||
<y:Point x="900.7407126611072" y="628.5400000000002"/>
|
|
||||||
<y:Point x="838.2767126611071" y="628.5400000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.923828125" x="-87.89792405764274" xml:space="preserve" y="-40.606550292968564">SimRequest<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="49.935999999999936" distanceToCenter="true" position="left" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e4" source="n4" target="n7">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="60.200000000000045" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1223.8814253222142" y="611.9000000000001"/>
|
|
||||||
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e5" source="n3" target="n7">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1223.8814253222142" y="567.4800000000001"/>
|
|
||||||
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e6" source="n2" target="n7">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="11.514125426161627" sy="-2.5781798912005343" tx="45.553752843062284" ty="0.0">
|
|
||||||
<y:Point x="1223.8814253222142" y="522.7018201087997"/>
|
|
||||||
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="60.4140625" x="-2.4087265765670054" xml:space="preserve" y="145.1356018470808">SimReply<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.97817989120062" distanceToCenter="true" position="right" ratio="0.679561684469248" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e7" source="n8" target="n6">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-25.212712661107275" sy="0.0" tx="-11.264000000000124" ty="0.0">
|
|
||||||
<y:Point x="966.6047126611071" y="731.8000000000002"/>
|
|
||||||
<y:Point x="889.4767126611071" y="731.8000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="119.751953125" x="-132.27600022951788" xml:space="preserve" y="-32.03587548828091">SimRequest in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="20.73181250000017" distanceToCenter="true" position="left" ratio="0.9386993050513424" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e8" source="n7" target="n8">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="29.18399999999997" sy="0.0" tx="24.28800000000001" ty="0.0">
|
|
||||||
<y:Point x="1094.6654253222143" y="731.8000000000002"/>
|
|
||||||
<y:Point x="1016.1054253222144" y="731.8000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="104.2421875" x="-62.15307370122309" xml:space="preserve" y="34.80927001953137">SimReply in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="23.81218750000005" distanceToCenter="true" position="left" ratio="0.12769857433808468" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e9" source="n5" target="n1">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="23.921741802203996" sy="-3.0501798912007416" tx="0.0" ty="-56.27417989120056">
|
|
||||||
<y:Point x="867.5280981327575" y="502.70182010879967"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.95703125" x="73.38950633588263" xml:space="preserve" y="-62.699758016200235">step<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="11.126187499999986" distanceToCenter="true" position="left" ratio="0.5889387894625147" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
Before Width: | Height: | Size: 98 KiB |
@ -1,650 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="157.79999999999998" width="452.0" x="959.3461887999997" y="585.7236400000005"/>
|
|
||||||
<y:Fill color2="#000000" hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="145.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="434.0" x="9.0" xml:space="preserve" y="6.399999999999977"><html>
|
|
||||||
<center>
|
|
||||||
<h4>ACS Mode Tree</h4>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
<table border="1">
|
|
||||||
<tr>
|
|
||||||
<th>Mode</th>
|
|
||||||
<th>MGMs</th>
|
|
||||||
<th>SUSs</th>
|
|
||||||
<th>STR</th>
|
|
||||||
<th>MGT</th>
|
|
||||||
<th>RWs</th>
|
|
||||||
<th>ACS CTRL</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>SAFE</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>OFF</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>SAFE</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>IDLE</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>IDLE</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</html><y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-1.1102230246251565E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="134.60000000000014" width="452.0" x="959.3461887999997" y="757.2703199999999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="120.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="428.0" x="12.0" xml:space="preserve" y="7.300000000000068"><html>
|
|
||||||
<center>
|
|
||||||
<h4>ACS IDLE Sequence</h4>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
<table border="1">
|
|
||||||
<tr>
|
|
||||||
<th>Step</th>
|
|
||||||
<th>MGMs</th>
|
|
||||||
<th>SUS</th>
|
|
||||||
<th>STR</th>
|
|
||||||
<th>MGT</th>
|
|
||||||
<th>RWs</th>
|
|
||||||
<th>ACS CTRL</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>1</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td>NORMAL</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>2</td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td>SAFE</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</html><y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="32.400000000000034" width="146.79999999999995" x="1128.4" y="313.94999999999993"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="113.94921875" x="16.42539062500009" xml:space="preserve" y="6.0515625000000455">ACS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="80.39999999999998" x="910.7200000000004" y="407.20000000000005"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="9.281054687499932" xml:space="preserve" y="4.03125">MGM
|
|
||||||
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="32.400000000000034" width="146.79999999999995" x="1133.6000000000001" y="243.3579999999999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.99609375" x="14.401953124999864" xml:space="preserve" y="6.051562499999989">Satellite System<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n5">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="72.40000000000009" x="1021.1200000000003" y="404.99560000000014"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="5.2810546875000455" xml:space="preserve" y="4.03125">SUS
|
|
||||||
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n6">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="67.59999999999991" x="1124.92" y="404.99560000000014"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.599609375" x="5.5001953124999545" xml:space="preserve" y="4.03125">STR
|
|
||||||
Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n7">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="67.59999999999991" x="1208.74" y="407.20000000000005"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.599609375" x="5.5001953124999545" xml:space="preserve" y="4.03125">MGT
|
|
||||||
Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n8">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="72.40000000000009" x="1292.56" y="407.20000000000005"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="5.2810546875000455" xml:space="preserve" y="4.03125">RW
|
|
||||||
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n9">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="67.59999999999991" x="1381.18" y="407.20000000000005"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="34.732421875" x="16.433789062499955" xml:space="preserve" y="4.03125">ACS
|
|
||||||
CTRL<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n10">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="463.3000000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.0156250000000568">MGM0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n11">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="491.32480000000066"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.015625">MGM1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n12">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="519.3496000000011"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.015625">MGM2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n13">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="463.3000000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.65234375" x="4.5338281249999" xml:space="preserve" y="1.015625">SUS0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n14">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="491.32480000000066"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="15.443359375" x="14.6383203124999" xml:space="preserve" y="1.015625">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n15">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="519.3496000000011"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="43.287109375" x="0.7164453124999" xml:space="preserve" y="1.015625">SUS12<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n16">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="50.0" x="1129.9800000000005" y="482.79120000000023"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.0390625" x="2.48046875" xml:space="preserve" y="4.03125">STR
|
|
||||||
Device<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n17">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="50.0" x="1216.4400000000005" y="482.7912000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.0390625" x="2.48046875" xml:space="preserve" y="4.031249999999943">MGT
|
|
||||||
Device<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n18">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="454.7884800000007"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n19">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="482.3769600000013"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n20">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="512.3769600000013"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n21">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="542.3769600000013"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n22">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="32.400000000000034" width="59.75999999999999" x="1305.2" y="313.94999999999993"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="17.3505859375" x="21.20470703125011" xml:space="preserve" y="6.051562499999989">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<edge id="e0" source="n4" target="n2">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-5.199999999999818" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e1" source="n2" target="n3">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="950.9200000000003" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e2" source="n2" target="n5">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="1057.3200000000004" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e3" source="n2" target="n6">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="1158.72" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e4" source="n2" target="n7">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="-11.29999999999859" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="1231.2400000000014" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e5" source="n2" target="n8">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="1328.76" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e6" source="n2" target="n9">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1200.63" y="379.7"/>
|
|
||||||
<y:Point x="1414.98" y="379.7"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e7" source="n3" target="n10">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-28.009798234586242" sy="0.0" tx="13.11999999999989" ty="0.0">
|
|
||||||
<y:Point x="922.9102017654141" y="473.3000000000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e8" source="n3" target="n11">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-28.009798234586242" sy="17.706497359551577" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="922.9102017654141" y="501.32480000000066"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e9" source="n3" target="n12">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-28.009798234586242" sy="12.053248679775777" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="922.9102017654141" y="529.3496000000011"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e10" source="n5" target="n13">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-24.18711646917177" sy="11.599696230174459" tx="-20.855199999999513" ty="-1.9751999999995178">
|
|
||||||
<y:Point x="1033.1328835308286" y="471.32480000000066"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e11" source="n5" target="n14">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-24.18711646917177" sy="0.0" tx="-20.855199999999513" ty="-3.024800000000596">
|
|
||||||
<y:Point x="1033.1328835308286" y="498.30000000000007"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e12" source="n6" target="n16">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="3.7399999999995543" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e13" source="n7" target="n17">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="1.0999999999994543" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e14" source="n8" target="n18">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-23.248152938343992" sy="14.026599270174415" tx="-13.026188800000227" ty="0.33167708069714763">
|
|
||||||
<y:Point x="1305.511847061656" y="465.1201570806978"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e15" source="n8" target="n19">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="0.0">
|
|
||||||
<y:Point x="1305.511847061656" y="492.3769600000013"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e16" source="n8" target="n20">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="0.0">
|
|
||||||
<y:Point x="1305.511847061656" y="522.3769600000013"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e17" source="n8" target="n21">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="-1.1368683772161603E-13">
|
|
||||||
<y:Point x="1305.511847061656" y="552.3769600000012"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e18" source="n4" target="n22">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-5.199999999999818" sy="16.200000000000045" tx="0.0" ty="0.0">
|
|
||||||
<y:Point x="1201.8000000000002" y="290.43031999999994"/>
|
|
||||||
<y:Point x="1335.08" y="290.43031999999994"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e19" source="n5" target="n15">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-24.18711646917177" sy="0.0" tx="-20.855199999999513" ty="0.0">
|
|
||||||
<y:Point x="1033.1328835308286" y="529.3496000000011"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,991 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0" xml:space="preserve"/>
|
|
||||||
<node id="n0" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="111.16238125000103" width="641.0000000000002" x="809.2454000000014" y="463.9111499999988"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="641.0000000000002" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="6" leftF="6.400000000000091" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 7</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n0:">
|
|
||||||
<node id="n0::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="77.16238125000098" width="598.8461000000009" x="835.7654000000015" y="482.9111499999988"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="297.42305000000044" y="36.58119062500049">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n1" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="54.0" width="157.5999999999999" x="830.6454000000015" y="503.13804374999916"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="157.5999999999999" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="8" rightF="7.67999999999995" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 5</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n0::n1:">
|
|
||||||
<node id="n0::n1::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="845.6454000000015" y="522.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n1::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="952.5654000000014" y="522.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n2" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="56.25" width="171.68000000000018" x="965.5654000000014" y="502.88804374999916"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.68000000000018" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="7" rightF="7.039999999999964" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="1565.2374000000002" y="273.72953125000004"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 8</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n0::n2:">
|
|
||||||
<node id="n0::n2::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="980.5654000000014" y="521.8880437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n2::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="1102.2054000000016" y="524.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n3" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="164.0" x="1271.2454000000016" y="509.13804374999916"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="164.0" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 8</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n0::n3:">
|
|
||||||
<node id="n0::n3::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="1286.2454000000016" y="524.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n3::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="1407.2454000000016" y="524.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n4" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="178.3169499999999" x="1107.9284500000017" y="509.13804374999916"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="178.3169499999999" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="5" leftF="5.120000000000118" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 9</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n0::n4:">
|
|
||||||
<node id="n0::n4::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="1128.0484500000018" y="524.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n0::n4::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="13.0" x="1258.2454000000016" y="524.1380437499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="415.0" width="739.300200000002" x="695.3113000000005" y="568.8694874999993"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="367.650100000001" y="205.5">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="290.08000000000004" width="195.36000000000013" x="1210.809400000001" y="618.4077874999996"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="195.36000000000013" x="13.645244799999773" xml:space="preserve" y="10.027339123239472">PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4301533333333345" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46543250440140804" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="10" leftF="10.240000000000236" right="5" rightF="5.119999999999891" top="26" topF="26.431249999999977"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2:">
|
|
||||||
<node id="n2::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="703.4877874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.39453125" x="10.802734375" xml:space="preserve" y="6.015625">PUS 3 Housekeeping<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="743.4877874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.529296875" x="33.2353515625" xml:space="preserve" y="6.015625">PUS 5 Events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="783.4877874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.9453125" x="31.52734375" xml:space="preserve" y="6.015625">PUS 8 Actions<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="863.4877874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.205078125" x="36.8974609375" xml:space="preserve" y="6.015625">PUS 17 Test<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="823.4877874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="116.8515625" x="16.57421875" xml:space="preserve" y="6.015625">PUS 11 Scheduling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="660.8077874999996"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.255859375" x="19.3720703125" xml:space="preserve" y="-0.96875">PUS 1 Verification
|
|
||||||
Reporter<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n3" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="105.13453125000012" width="155.4000000000001" x="711.4787000000007" y="632.7428312499998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="155.4000000000001" x="12.272399999999493" xml:space="preserve" y="3.249732314531343">Application
|
|
||||||
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="5" rightF="5.400000000000091" top="45" topF="45.13453125000012"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n3:">
|
|
||||||
<node id="n3::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="726.4787000000007" y="692.8773624999999"/>
|
|
||||||
<y:Fill color="#FFCC99" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="69.2216796875" x="25.38916015625" xml:space="preserve" y="4.8515625">ACS Task<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n4" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="227.60000000000014" width="310.3248000000002" x="882.3347000000007" y="682.1055312499999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="310.3248000000002" x="23.48672560479929" xml:space="preserve" y="6.686613155747068">TMTC Components<y:LabelModel><y:SmartNodeLabelModel distance="0.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.42431566666666864" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4706212075758038" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="5" bottomF="5.1200000000000045" left="0" leftF="0.0" right="5" rightF="5.324800000000096" top="26" topF="26.432000000000016"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 7</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n4:">
|
|
||||||
<node id="n4::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="1052.3347000000008" y="788.14553125"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.93359375" x="27.033203125" xml:space="preserve" y="6.015625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="902.3347000000007" y="859.58553125"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.42578125" x="23.787109375" xml:space="preserve" y="6.015625">UDP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="1047.3347000000006" y="859.58553125"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="70.111328125" x="24.9443359375" xml:space="preserve" y="6.015625">TCP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="902.3347000000007" y="788.14553125"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.001953125" x="27.4990234375" xml:space="preserve" y="6.015625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="897.3347000000007" y="723.5375312499999"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.904296875" x="18.0478515625" xml:space="preserve" y="6.015625">PUS Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="130.44000000000005" x="958.2147000000009" y="946.9855312500001"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.5302734375" x="42.95486328125003" xml:space="preserve" y="4.8515625">Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n6">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="127.03999999999996" x="1030.4707000000008" y="629.3055312499998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="8.558574218749982" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n7">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="120.0" x="896.8787000000008" y="621.6432750000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="17.91748046875" xml:space="preserve" y="1.703125">Shared
|
|
||||||
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n8" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="106.85641458333345" width="167.16000000000008" x="678.4854000000014" y="467.9111499999987"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="167.16000000000008" x="0.0" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 9</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n8:">
|
|
||||||
<node id="n8::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="76.8564145833335" width="137.16000000000008" x="693.4854000000014" y="482.9111499999987"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="59.875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="118.25" x="9.455000000000041" xml:space="preserve" y="8.490707291666752">satrs-example
|
|
||||||
Data Flow
|
|
||||||
Diagram<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<edge id="n0::n1::e0" source="n0::n1::n0" target="n0::n1::n1">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="4.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="48.5234375" x="22.12435476073938" xml:space="preserve" y="-29.312517773438344">TMTC<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="18.0" distanceToCenter="true" position="left" ratio="0.48378541003594444" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n4::e0" source="n4::n1" target="n4::n3">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999983203125566" y="-22.719979003906246">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
|
||||||
</y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n4::e1" source="n4::n2" target="n4::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="5.000000000000227" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="none"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n4::e2" source="n4::n0" target="n4::n1">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="27.85279999999989" ty="1.470468750000009">
|
|
||||||
<y:Point x="1112.3347000000008" y="829.09977125"/>
|
|
||||||
<y:Point x="990.1875000000006" y="829.09977125"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n4::e3" source="n4::n3" target="n4::n2">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="-16.954559999999674" ty="3.7001374999999825">
|
|
||||||
<y:Point x="962.3347000000007" y="839.58553125"/>
|
|
||||||
<y:Point x="1090.380140000001" y="839.58553125"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="none"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n4::e4" source="n4::n3" target="n4::n4">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="5.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e0" source="n2" target="n4::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="39.69774375000043" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e1" source="n2" target="n6">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-51.37930695586169" sy="-119.14225624999972" tx="60.74632526271894" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#993300" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e2" source="n3" target="n6">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-19.839999999999918" sy="0.0" tx="-38.399999999999864" ty="8.753668750000086">
|
|
||||||
<y:Point x="769.3387000000008" y="593.0482125000003"/>
|
|
||||||
<y:Point x="1055.5907000000009" y="593.0482125000003"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#993300" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n0::n2::e0" source="n0::n2::n1" target="n0::n2::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="-2.25" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#993300" type="line" width="4.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.171875" x="-76.64202462463709" xml:space="preserve" y="-31.1845177734383">Events<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="19.872000000000014" distanceToCenter="true" position="right" ratio="0.33295067336054324" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e3" source="n3" target="n2">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="52.53735479999898" sy="29.121903125000244" tx="0.1231654464985258" ty="139.1977687499998">
|
|
||||||
<y:Point x="841.7160547999997" y="918.67033125"/>
|
|
||||||
<y:Point x="1308.6125654464995" y="918.67033125"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#0000FF" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="none"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n0::n3::e0" source="n0::n3::n1" target="n0::n3::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="4.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.6328125" x="-91.81636757812339" xml:space="preserve" y="-48.84101777343835">Function
|
|
||||||
Interface<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="28.21599999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e4" source="n4::n4" target="n2">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="-0.049743750000288856" tx="-82.89626674268379" ty="-24.959999999999923"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e5" source="n3" target="n2::n5">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="7.053999999999746" sy="-52.567265625000005" tx="57.5920000000001" ty="0.0">
|
|
||||||
<y:Point x="796.2327000000005" y="581.6432750000002"/>
|
|
||||||
<y:Point x="1368.6414000000013" y="581.6432750000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999972949219227" y="-30.999879003906244">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
|
||||||
</y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e6" source="n2" target="n2::n5">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="47.144060319997834" ty="0.0">
|
|
||||||
<y:Point x="1415.2947000000008" y="763.4477874999995"/>
|
|
||||||
<y:Point x="1415.2947000000008" y="675.8077874999996"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e7" source="n6" target="n2::n1">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="39.02668799999979" sy="0.0" tx="0.0" ty="-8.276800000000094">
|
|
||||||
<y:Point x="1133.0173880000004" y="750.2109874999995"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#993300" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e8" source="n3" target="n7">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="-38.78246875000036" tx="0.0" ty="4.884353124999166"/>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e9" source="n2" target="n7">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-65.73714292000136" sy="-118.80000000000018" tx="35.35927123999659" ty="-20.0">
|
|
||||||
<y:Point x="1242.7522570799997" y="601.6432750000002"/>
|
|
||||||
<y:Point x="992.2379712399974" y="601.6432750000002"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e10" source="n2::n4" target="n4::n3">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="60.0" ty="0.0">
|
|
||||||
<y:Point x="1415.2947000000008" y="838.4877874999996"/>
|
|
||||||
<y:Point x="1415.2947000000008" y="926.9855312500001"/>
|
|
||||||
<y:Point x="1042.3347000000008" y="926.9855312500001"/>
|
|
||||||
<y:Point x="1042.3347000000008" y="803.14553125"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e11" source="n5" target="n4::n1">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-45.90000000000032" sy="0.0" tx="15.199999999999932" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e12" source="n5" target="n4::n2">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="46.63667953667937" sy="0.0" tx="-37.26332046332027" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999968403867797" y="-27.220816406249924">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
|
||||||
</y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e13" source="n3" target="n4::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="66.54799999999989" sy="0.0" tx="-35.74228584000252" ty="-3.0175312500000473">
|
|
||||||
<y:Point x="855.7267000000006" y="770.8415312499999"/>
|
|
||||||
<y:Point x="1076.5924141599983" y="770.8415312499999"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#008000" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e14" source="n4" target="n6">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="18.251857503227473" sy="-77.84525000000008" tx="-38.24174249677253" ty="3.238468750000152"/>
|
|
||||||
<y:LineStyle color="#993300" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="n0::n4::e0" source="n0::n4::n1" target="n0::n4::n0">
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#0000FF" type="line" width="4.0"/>
|
|
||||||
<y:Arrows source="standard" target="standard"/>
|
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="82.4609375" x="-88.20577865560745" xml:space="preserve" y="-48.84101777343835">Other
|
|
||||||
Messages<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="28.21599999999995" distanceToCenter="true" position="right" ratio="0.030113173151242907" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e15" source="n4::n4" target="n2::n5">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="0.0" sy="-7.129743750000216" tx="0.0" ty="-0.10225624999964111">
|
|
||||||
<y:Point x="1112.3347000000008" y="731.4077874999997"/>
|
|
||||||
<y:Point x="1112.3347000000008" y="675.7055312499999"/>
|
|
||||||
</y:Path>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
<edge id="e16" source="n4" target="n7">
|
|
||||||
<data key="d9"/>
|
|
||||||
<data key="d10">
|
|
||||||
<y:PolyLineEdge>
|
|
||||||
<y:Path sx="-80.61839999999995" sy="0.0" tx="0.0" ty="0.0"/>
|
|
||||||
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
|
|
||||||
<y:Arrows source="none" target="standard"/>
|
|
||||||
<y:BendStyle smoothed="false"/>
|
|
||||||
</y:PolyLineEdge>
|
|
||||||
</data>
|
|
||||||
</edge>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,954 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%âãÏÓ
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/Title ()
|
|
||||||
/Author ()
|
|
||||||
/Subject ()
|
|
||||||
/Keywords ()
|
|
||||||
/Creator (yExport 1.5)
|
|
||||||
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
|
|
||||||
/CreationDate (D:20240209121455+01'00')
|
|
||||||
/ModDate (D:20240209121455+01'00')
|
|
||||||
/Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/Type /Catalog
|
|
||||||
/Pages 3 0 R
|
|
||||||
/ViewerPreferences 4 0 R
|
|
||||||
/OpenAction [5 0 R /Fit]
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/FitWindow true
|
|
||||||
/CenterWindow false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Parent 3 0 R
|
|
||||||
/Type /Page
|
|
||||||
/Contents 6 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Length 7 0 R
|
|
||||||
/Filter [/ASCII85Decode /FlateDecode]
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GauF[]96a;XrZ`X=6'nI']0G-Xc?4G)r9+Z>EKNG2R4%q0^eCF&Ru6"F&ec4BQ"pOe/h16:(d[p&-0iM
|
|
||||||
R9opj8<89"/O*Y3#Ch."r:0gdGZOb<A#9#>pfj+Mq>To#iqpb<5Q:*ns8CKnJ,8':s.;OT9E+^)s8Q;e
|
|
||||||
r9++WDnl>Ls7O/,aM7WLDu]Utnc/F"n6>@gGouoFD;Jt3gAG(frg14qpF?3t)"tpuCMf1E/rJd]h<YjS
|
|
||||||
mGD7bARfQBp#S*OVrM^S]f.Y2^LOJIa,g8s^MJN%[*$]\r9NuN+9200PLa+)s7ij6s.&\UXn'$/;7*dX
|
|
||||||
+oEqIa/dm=(^TflZ.K$9a1qX&k6':pf*@mSC]/>HO']tADMLCP](!2h-Hu>T=S;^nf6/g>IE'n4kSO5A
|
|
||||||
XO^DO*WOBfrQ70n4lnhUAY&#A?`_`G?iTXcT<W6\<BY6QH;N:IIs27!`gZi^+8k-f6iT#N'49`8.PLb,
|
|
||||||
%!'##j^5"I9W$Fp/Ucb%D5^^spiE7n5CT[Me'(m#dlQm1`j3eN8Aa88pLgT5YP$SgY;j:Q[LA5Coc"nD
|
|
||||||
q7V!b1p47@B7BJdI("=pBYUG0onECUNA@J\[Mn6h[ElC5`Tkj"WSc-R`NL>jC,ho7!j@U:%Li><I:43P
|
|
||||||
qu;[dKKG1>RUUq3O3bJA*Xe0$4.":CI`4L&QS>f(@'?d&^#S<R)5<g=+Q6BhrFhSs@`t;NVtG%+Xk)@Q
|
|
||||||
0D(tlctf)aIN4l/96lfRcaF;Ef-9\sj-><nh3Zcr-quV+0@X@r^4nGUIC#5<pP!J6q)#kX3eQfQ;Uc\b
|
|
||||||
I]dR2M-h:Z+9'H'7cWm]cap1gq!./m07d&_:'8mNTMlZ%;(5!26>\_'?Do]]$S&j(ZhK5RE.3g7(".;t
|
|
||||||
#/5]GlR6f#HS(`7Qf:!'J:h3oc?I:HCff%HJc?MMSIFn%rLZLaA)5s6?iP5hC7Ri3hU):>1n:0YI4VS,
|
|
||||||
^A^^S./I]*OjfdZJ+!-k^O1#snoc00R)egK(U)B_TcU'+\WZ\'M"Jtig.VaccitDQRO*8m__2I6'SI7c
|
|
||||||
mGl=G?`#tlqu9Hn+Ke36_34FdlfI<`\J!K-I"q3Tb$?[umC3J/r]=0Z^V<\0r)0=Dg)m1U.oOZr\T[o<
|
|
||||||
\2d%WGHCXjDl%nSO3t;HOla'%s-kN]:-5($<mi@nA9Ll4GlQUs'K+I"Ir1H.c1f(.WJOu\]Y<IR=)(Vn
|
|
||||||
g3AT*8"J75?_VtXJT?2!cW[;T6eSBnA?+V\j(/jFiU=Q8H,\ENqOG=[=7=.*rV@J'O1r3j^Z4ieWp9To
|
|
||||||
cQh<(hVWOYrr-ndYs,EJY)Et[cRF6jnZi4i[pN=cE<".Co'obq>(-G19+i<@rZ6C4L\7nlh>&krCW[#g
|
|
||||||
fS?=7Ho,Fs/!Q`E3jHmFlH-ckO1Fe'g3V#7p&;AO)?R<QI<hM!aQh@*1G>j]I4[?1e6#a5lF+U:L[O8g
|
|
||||||
G+5mZ\BPT9^"H3uGb4U$qhNdpB2/&bk2SN:CRt3/Sn6.XAohk^IgC%qjXO!+qc[Ef8k#fLWVbBQO\Ykm
|
|
||||||
:bmog(8r8pcFV,BE7GL!jbjY5\,0?1(?puG@HE"P7fB+^g#"9tZ1Y0)ST`?E0L8(Y.'qbe9V`:\,Ti%k
|
|
||||||
o$8+3WYs@0=b_D1cCZ?A9<`.(!eOG&Z[+K:AMm:(j&nB3@#,Nc=(sEb9P55d::)'lrGXqpa's35If4^!
|
|
||||||
ZMo<docGqE0`H+&r+BE(WmSmd:L"Bj>r[Ic"'h-Uf:hkQB2%#g^.0JtMupmG@*#\UOO_UJj!r<LA[8Kb
|
|
||||||
;NgW7&n`d6h*I)1%O<OLpV8ta^7JL234'1\^Z2X-O<07+3i_r@=NBbc1tl:1R+=!ECrA?DF;/IONN!+?
|
|
||||||
\q6^OOc@s4JTAGsDC8_2s6poMa..J$06h^[[E^Z2>*7H-3%JS+M)YAJWrIg7N/np%k_EmEH(ic>W6`6k
|
|
||||||
0AN8g=Lt=:?RW7>RM#JuNdeskZ$tUF1]*oaVS9CFLU>We()Vs5f/3L8%Ht#.aMu93iu\5f^Vrr4epk_-
|
|
||||||
&p2G)A9&tgs,0K6*?R;9>ltG3ZhD._^U`p5%Kq79Umd9:EqD]RgE!Gco5b:Bh'q/nV*j985d2UkG]%]I
|
|
||||||
6+(l_=<EPYp44#tX:,#tC_sCh=a/N"'hJ0/6$g^>J#U\m`WlCs)^pPG/XX!R8Ze),"jX/]2tmIo:@)u%
|
|
||||||
1io4&,uDY]mD?K1V#aoU5iBM4p$_QRlH9Z`pK0F_rNGt,b0`q]2u%NY8gkWa1q^>DqV%Nl\<eFGJB^ja
|
|
||||||
ro(9*EElX><L03%`]1TKMuMc'm_"=`\m,2_VbR#cX)sIVD#eF$jeBpX5#Oc;nr.jhdT!^1Cb3kAKiLSF
|
|
||||||
D,YBPcF,A\<,>TP/L8$1m%r<Ii("WgC$2-PdX'9:*mJRZ"6[oXRWbcZ.k$u<<C1udoTsANb0eH.$KRC/
|
|
||||||
&EjYli%dg)s6BVo9pi5*;7]kE.hC`jSZe-0.%TZk)FNi2X^D)$Ied@,a8NFCj]pA]lsS#nNu]dVmoW`m
|
|
||||||
*WOA)m.5#[m&[@n[?;S<,PK\kruM+qqdn4CAWf3uo%UqZmJV(]2SQ14f:2osI[PVA)V]@ARIEh`YjoAT
|
|
||||||
U%6f!bP/TBo(A8sf6Lf2EM<o,lPk?-I^pN?hFDqcYh=K9QEt`HIg=9>[q,9A_YaV-c7s\lmoMX0,PCn/
|
|
||||||
gf*2"(&`EuhOd$X?]H1CH_#<JBAa,$4P/0pI/O7&C+E`a`#:\oLIGH`[WPl&NJ!#FQVI$;YVbrS]+%IX
|
|
||||||
X,Ges8t4@b"^_k2H6MmW^@8Bi:ENoTHpbkLaak3bZZ(ldm-!9r7Bm3Epl:PMX;Z;sqK]I560S!p^E/UJ
|
|
||||||
mB&)PC[9W<#:2`[@JiiUZt`ZVi\(tE%*CGdWGL#CBPYlN5/*`Rr0jIAn"(,(nE>kJ'5`i3QL2D:0,c[R
|
|
||||||
m,.<3%pLjZg%cUeNJGgeHr)S_d.c+Mrn*,N<"-0%^K.J-7a%g?@_ASd_RdY!P-(:(7U^Eq^O"K%K9GoK
|
|
||||||
Ar4UAbcI9TF1b*3)db_EhnS2@mp%^ld(uR<CQP33ot&RT3>hZq1IbV+(P]@0Bsk.mY$RZEDSn;(PPd7N
|
|
||||||
MIq%$B!e$Rr0Uj\7)uLsiS0uU4/0DAgc8#?^Jm:,oL8TIAb5+*`"ADM_e*VU'XW/5l@g^7Kq3_\L$bVA
|
|
||||||
LlR3T`K&Td$R^tNUqA)PjY6R#B[SLG?L0=o\\T^8P*N9GjB#'%`Wpj$HDdgY6l]FB9&[[m*<k\8g5Z'u
|
|
||||||
e:-VbXZ.terXS3Lf_r\$%^<BU%WA<dbMU'5BZ$hdjb/)aa#intI86qF3cqMs1V\6`:kqXXUS5&_]mI-Q
|
|
||||||
)4c&e%Lp7$5Yc6,m#gg2S%5PBD2tk.fN1cK2X#R/I##X%a-1jL$I5Yf[o';C$$r('AC9/GdRA*F2`UeI
|
|
||||||
1>#3Tr!-YsktuqgbmXChp##;*e`-@Kh(!e<ab$=C^_tcn*=POZ[goO6gJDM8B&-'sh*(UWkpk6pHs$G&
|
|
||||||
3c+BYfng\J%!>GZ]COSO(^68g^C,%/XP<46p\A%[oOIiDS?un`o79nT8DCt]=iYbj-ALuO[c],_s8HnU
|
|
||||||
m3>WY,4Kf(_535F]OB>T/^@"=SJEU>1$Z*5an_g[VY!rDm[K(0c)`)[8=;ZBJR#pF(Y0/sdZR'.618c)
|
|
||||||
VQ;E1VO;.jf_j&g]33r>%;)GSAJ&AMZ2^r48W+YHRBiko#o*@*)GU+u?bT!5e?72mW*QK0VAZL+6,AJn
|
|
||||||
?T5s[HC=G4';t!JFNa$seRf<F"D/Os_%jn)!hpM4nFjGdBl.V:W&B5_-<9Aa4`VQ9MRs(a&%`>.5lD>P
|
|
||||||
^hEIr;g`_h]159G?Xf&8Id&)bG&KDel;:A,'BSoWUb=.1L6]A.B_[asod4m`dCPu]4#Ukn6#4pf>`CL(
|
|
||||||
CN,+HN5q5g$Pb)Gf&/$eXgg.Pg)@^]m2)oM/($C7CX>0Q:+INt#3gp4n]^s]Y]E$1#6+TB]/@)2mA%OI
|
|
||||||
QEUQpeg`O7VB"1F]&plL?gkCB[D4HFq+J$uQT1Vgi0&4ro#M]F20dG#r%>2OnYULnF'#:G6;H6)^@:Ig
|
|
||||||
-gX4MJ&eSh`t.PG?i06qh$Y4;-t[>/j&c39DG2WZjF'\Vj1o._5aeqA76UY'KA_/>T'3K_+$s"0hD^Z/
|
|
||||||
ehrqpFEqB4)5Zr*AYTr'5I6r;3hgZ3f6HSE@_g$Z>_"oL`_Tj#Sfiamg2qC5r5>D0$qA"Q_.>$PhCrDb
|
|
||||||
"V]5u,-CQ=WlQ0'h2-Q3Vm9?KVebYl%lMqYF+1[GPY\R%-Nb>56tH33>`\h$47-DEAF>#Qj[C%JUYOWJ
|
|
||||||
,PJKUj&aI#]G0l'Dt=N]`t.eWrRg\?!6<<Wre5^CXR5hMTha)GEP7&r+E.)]&;aZ=r^8Y.H4/;6r.WXM
|
|
||||||
ih0X):HU>ps8=htguhK0`Y2t*0g,*`(]T(!S];5p1=<OR\OQV#I4b!l:X&\%A3k41;/:`mY8p\Y-iHcf
|
|
||||||
>CXpn)_>j]f:is@m7E>j=?h@0@SD]fceS$!SCajuoKe`!-M]ML]U:?Nbb&Ps$$!',.a?IR`g89^LQanE
|
|
||||||
`.&?m57!AggNsPBm0]n9T]r>baU_u:[Nn!ojhlUt..f:%OQolG%k.%Oh(ml`9/?GOLa[5W6(1hPBenrZ
|
|
||||||
YJS7C?h,1&$]ZFW&Tf:G+'fe;haO-[XJ35f@>NoUHmM6iL_S`JBjlt=r+b<2b*[hVZrmOn+S9?1CQnVl
|
|
||||||
p""W686;uEX<6T.<6N%UbZq*NZb$!,qlMfrDHI7*DJN:>F=2AB._h1W.rP,ss,`#qX@#WBBWQp?l"7rc
|
|
||||||
ndI@f%u*GU+YVjrLXtDPhm'@P4`c<Z=3nAdSHSP2*TEHngT8U"=nJ#ERj[16\4ShZ!?-i2YqGj??5Q(J
|
|
||||||
iP4X"hHBCVq##PUkE"C)ngO$Ohof&dMrQ(!qpqaV](X2dIm05S[YduDS]hcrCHpA,ej"B[I_i2UqqLu"
|
|
||||||
hq"Y'[OqSfofIHaM.<F'F$8cn,$M]8I2K0d4fDh*6(cAbOfrXP5G_fL6PO_d`0/@#o>);r'Fr5)G4r_+
|
|
||||||
:jQTeFXV0&qk-P3iFF7^42PeiTd:59A(tkiQQ+YE=[?83K42C'bhXrDMia-qrje%24`nF9'\P7c%<9X*
|
|
||||||
]9B:om0C\mH8'*,!T3j0l]f..#QDVbN7e^(r,`o@JJR*^[c7h8bhZU^f<]p$+3.O9W^_hCo5>+LmW&aV
|
|
||||||
f6U$R>q=tadQms@2A5s)6^GZds(d9n8$q@S,bC=TF:e3MZX&qO6[pZM\$NFRiKsKTlPEhhmcKo6+6$le
|
|
||||||
HHdVmqhIpKRqT'c6+Om>]M%]0TR<qYUM3^]_<G2<5XVornSj7[0j7+0UnVPQk'.5U%:sh+Ft<eK;s2Jg
|
|
||||||
W%9H6\>C>2PM6_=p!D].88fm&"WGDYemi^%OHaFF"r_C@]!O^C;Jgk)C!2i*rX<%mCtPn<P-3'B(fTa5
|
|
||||||
#,5\fINVPAl>"Y3kfI$&N6HIkf@5aY6^j..j\:AE@?Ne^o4$Z7.a2Rg&#LaVAq'M.(\XD9G;8tc?el]+
|
|
||||||
PC1U<n%S^#jXM:VX@poK9Cn<=)a$IpDXc-)/h"%:>&@HVk^Bg(EI(9PnXYi1--KU7>C)r<3HP:LBqZu;
|
|
||||||
V;e>:?\PDX:g6ZgFAC3)qN.+`Zr3Qi\#_j$]D9WB:M5flfeeaIUa*0[')ML0V+GTj%Tt8EkaeR9VrT#/
|
|
||||||
TQbtCG4RZkDN!1ugmpIfDJ\FtDJeJcGP_)pYdV@i,g)7lX-+-4GIHe];6+F(U/+BCCQplpR>8r:[OFES
|
|
||||||
8kk20p^<[sWLXQ3%le`F90C_gUKXoa>?ZB`4'Pm8Cj3</CX6`kN`u*Q>.CcBh:]rUNed(!]<e?(EmoD(
|
|
||||||
^,oQA-oH?[a]^L4E62iimam*Z/+dB7i0e^j^)Ym=-SOl-Cj3<7\V(qkRW<UA%[!+HiZIpU66jnK-0FWS
|
|
||||||
?fLKiku7_.B1ormgRYH^h=;/fc<U$K`Kk[*[Q@]V2=Aa\)=Gek_q^dpq<mV;s7bEp&GoNbQi>tQZ!;,$
|
|
||||||
M_e,\F2f(Fi&K2=qX=3ObRc;['mMprbg1Yni5e,MrHJ1cI;NejIlhqu1JX&GIgb^FJQ`!nHi8HRV"O>H
|
|
||||||
q_+.ps%ts`C!UV%N#h8MO%'J$NFJ;%OcNQ$`UI+fQ9Xrf32bA0b[0ph[+m8$k&%@#:X;PS>7o(s<`;0o
|
|
||||||
E8_U"4q45^#Fu\ti@[3a=r'sp=WRfZcN"OqN8Y'fB3;pU4c["`WSd2FMgg]tp?9H-P5S:@1jDI[*mD)Y
|
|
||||||
l!S$A^RQ)"LisRjSab0@V<ZH;hOOGS(`un^>Ha0`h4VY.%7);PH,;"TdFWKArfVMHjj`b9f(Lk@r#4b1
|
|
||||||
UjpCN2DZ8s8l_&;9rpSuJabG.WF'+YMKUT'KHlkCl`^#u*[q.>MCUl9k+c1XMB:fEb"PXDb&OYhY0C+Y
|
|
||||||
*YCEOM(oqI&gKiW2]XYf#V$uF8eer%CD8G$0ujop*5[TRPr'TQ3alSLW;\kZ-:d!2o?`m)G:]8:g6KcW
|
|
||||||
Otm[]?^9+c=2WCp1-*^BaQ*n5S5Jq][tm\kOQ:"4TR(0iPZ4eZNe<(!EWANTLN&2:rtYk<b&=O?3?Id#
|
|
||||||
?AW^nmB6dNn27MQN2@Oa"?Gu)Y8oG^Jp^D.,01!:O!'d:AIuT&Ps8r)$YLNWb1[dB-s[GGE__aA"Sn_U
|
|
||||||
&S*%ROFckg>E9%ME9*SZp[?`Za9PM%+J56:O!#OoY6a\:;t:4b6(Uk`eF6]nd]-QKShID\lD+VHRg3km
|
|
||||||
+DXFY6%7$2A<M5\FqWkKi,r3b,:\fTGJ(bq,q4Ih!3Id+[hRrmSc%&:4H'"(G,^%omZkQ]6B1e9`^cj[
|
|
||||||
,EMCt4L<Z9TR=F]BX"Qre/`?3h?VRh%H]5WrslAa_&]@$#Hsf@ToBTRS7%A">c!;1`MSL_1L$[97I:MT
|
|
||||||
oR*bK:%$)VLRftW#WWc6UKqJ;O=nZk3BJQahg)(]Z:1kpq^PU#?6>gIA<ABE&J'Fbb&#E"j2jOHW(r6Q
|
|
||||||
\QaVX_O$?mFJ>oC+i>]i`MjNN.'<uR1P593\M9r9)-dEsl^*HMJor8$mH<a@6g.C=+3r8dhT&.dTbXVS
|
|
||||||
2.:-$][jf>GdUSpr&HK9WqtMeJ$mEWfI3R;4*7Vh,4J$$<\6q?jc5\c+Qa!8DBe2>[4<i\H_BVOGgg]p
|
|
||||||
:!4Eged;?T*P7W<M0rC-Sl*\]2_A)Q>-;U/b,!RY-Za6RPYr`hEqgr.WhOesc];\66ADL\;+7O$rabc@
|
|
||||||
F6@g))/$KBD^P)RffXWPK3/'-GIci-r.<a3T.M;O(OXLJ(UUsA&JJLC.ggKld<skcDa(L,*om'^ICh,8
|
|
||||||
<8bk14hXf"=8,<-1u'U(Cr1RFV%0*bPr_rSF2-2/#p$R9(o>A5Ql-ZL]"CoGGP=_#*[g\S*RlaG'X<"]
|
|
||||||
=IWVG[R@=Kq<G],;X=S#;Q#;nrD!UJb:@_T\ta@!GuGAQ]"Cpd:!Vbg9')m20@gCSYEU`R;Yh]P;VA`P
|
|
||||||
n4K8?9_V/9qX?S;rhdjhFiIIbd`Te,]ReYqPnHeAWRU-CRdK.W5MRtnhTZ&(PVBaJglKf4Fa>bqYLLD=
|
|
||||||
]i(cSSgE;>OB:`:;u$17BSUr2`/4%WM,,)b15e<$]d,UA?E$HpU"Nk&qs=R(3oP?<r+3>5-:R[TroR>;
|
|
||||||
G7MI*oX#(o!pNkJ%^?:Zs4G!VliJorR<qZ8>FTfOphf1(fJ<pU[;37qq_D9?H#IL8UX*6_r)XR*V2Vr:
|
|
||||||
+QS>m\)b#b%.,m%Z-t2P,\E<L&*'qAZfC!NkO&/='/tp,:tD$fiGGoH>(Ym=g`&Ps,'&\!Nj?2j%rW?B
|
|
||||||
&'m"$kG=*JH&iK`[j9VE[Kh$$D($p#C--H`ZZUO5L31l<&(St0jkub=bgpE:o/&'^)iFEHrgR4*l.<J`
|
|
||||||
LS3(J(uZe`(4@LmOuHF]PSqFu)r+;3Scq$q/h'7OM0NKWhY]N$/,:.SDg=2okL.VE*r+8OQc0e;pk(+9
|
|
||||||
^4="EgHA.#*@Ic[d?5_NF!$4tK718]l"IPXG2\L[^>1j;T5)Fe[,`+LX[=s,Q+D_;+nqCK5=d:=4..^\
|
|
||||||
J\93^IXuX31ODEIPGL.8EO%>Y@INO5!GEJ/80EoP>%F9_>oH4:";5[$(@*GMXr)Ja3&pY+C$8luNmd&.
|
|
||||||
8'(E/NX+lnf/O#[9B4B;6B-%_GE$\?>9=brl)l4FS5"q_Q/DJDJbH3cZ8gGm:)6pKc_.oRC7Hhh0o_A3
|
|
||||||
AMT7/Prh^"P>gOU=H=XeHMVu$)KKq+W]9;c:*nLO#N+olAL1CPXnJT2LgB&2g<DJb>GB-bCe3TBrm3X!
|
|
||||||
^N@WIOu[bnV:&+k,Bpgj52;iOE5S)#N[&9Ya,0DT/rcVf<hs`"Es[gNM`d'aY;D8/,E6ca5$[-(0$lTM
|
|
||||||
f=Am]\@BPP09@)B8/b!c46=s0Ig3Z7Y4oM=!uaZb-_7Oe<rM-O-SO;52"hICIFgI.$Ih&b(t:OSI\FcL
|
|
||||||
EC0tZH;g3f,3<LSQP;G`#BT:8+7fCc7nZB5>(Ii0b-X4\m:cfG-)p4kTKsZ"%UR>M#skOhC`9.aMIq-,
|
|
||||||
,"J]l1]qg+%\g(&",3$P7ZQ&P:9Y4$UAdK>(:!>=fYT`D*%h#4R%qX71.UZ'FQI2,Y7Jt,3[f^UaQ'NM
|
|
||||||
i@K7gP9!%ECVFhrN[Kt=[!@a]Q"D0#BF.I\U@*Bji4Zg7-;-rLk6qX2S?.Vg/^[!I>(Nr&(.O>86/2do
|
|
||||||
+3#%EarcA'X5jdgNB;jCM`W/>FRrDV(YZ]fmS=t?,"LrAXEfnb7[]l#4P,^jFYL.9q&S/kPV62J6[b[\
|
|
||||||
6%A64BB8SBnY*4f<4<NPE\hg^F:T13.fI7F(HGY05,OBY;pGc!FIVuOZ!VG;jRDJ(\oG,-93H;teU?'b
|
|
||||||
AVLX,hH)Atg<&^mq-L7J[JRmDS+?E%TV7X<G[e+=CSDU3l6[HuFkc-sok;hSW"kHM<KpX?d\X?o"Wa>n
|
|
||||||
c0sSkRl1LtFO:m=YAU["p^e+P$Li2+4];\:"-5fG&\FA:O+-W/J&6Gr&1Lqh;Ub'D)V7flG/D^rJ;_md
|
|
||||||
<,`e(ho@8)044M=+_6tJMe0lDX*`3,nK"/i.Q@NQr3sRVTG2_1Z(ie--l/?Lnkr[<:V3,-hY+[$\n$^'
|
|
||||||
PNMN%BEkoc%"rUBBdrUdR9hH8lJr^f:$u`h%(Zq?ENS^.:KfKrIE%)*$gIR5Ioc<2#OYQ_oQF_o"WCG0
|
|
||||||
TZ$BC=PPI(:#;XNA]=2?dRWEE;IY,Ca#qY;M>nHo"<DO$6^I'X;OFpt?6!=[_@nFi?2X__Gq8]QOJY.U
|
|
||||||
jOkMXYJG4d.=^,-X:b6J]Bm`N`*?i!Mu=W`H&K`)OXsKfcP`gIHB`.RTB<m((K"PH,^]F8X!O<J4$DS[
|
|
||||||
(&9A:h-\:XoN.@DV`j$$m]U.S`7oE?R?siOo<o.oGa*At2pA_L.CZO`WopS>=I#"eEa!VOCif&-XQ7<u
|
|
||||||
LM,+_]CK$<2@:Z3GNS#OYHN3kgUp/W>iQ#WBj^UmL@eO_5;-Pnn`CqY>k_\BkhOE]9o)d4[bemi1/#0X
|
|
||||||
=$/W&5k4>7C'8Eee8]ai`G=sEMA,*T'P64+'G!,lEEZntID,<G19r3gYXjW,HdLhF2"*a%Q$NL_G4Zh4
|
|
||||||
\"-8iRf(LO29k`qd;,<Y;lQBb.6p#)"7=QZd`T3-IQG^>=sE\^3$,/Z)6rjjBA_Kkcua0W]E_/X3:e2u
|
|
||||||
+jVe:AFR)#*jeLTQ?^kQK=9Jlq5UqXB\ISUQHmk"9i2jE\L'P3c"</_0^h<VRR.J?<^^nkh'ORf=/Ijq
|
|
||||||
3j)@FiAd?"iB:lAdB;Iq[c_@i3eofi<^S6_s%mQX&,`eI]4Sso#GaSt#Hc$!:&PU9dZe$rd`Ge8i5hh1
|
|
||||||
&p091TIj>IjumZOmt'N;H/u*Vlbho<]a*V:H4S`j:)5X^o/P1CSK'ra6.9!n7aQkG".jK(SPZBX>U>Ui
|
|
||||||
f6@F=mm5DoONpb*OlCTQ.[K4^kK[ka*49;r:EA/R^Q)4DYW_((]a*$ooH$h=d!K`tQ[ecC_EXUMq,cJ?
|
|
||||||
BP6;E\_euS2fnIPR_gV0r.[umJ_8=-AGHWLP9+@2o(`/?>e0-j\e>_ELpf845Xkg)fmP`[L)O-"I'pje
|
|
||||||
B9*s4di@EDH5dqh'5((\=bAG<rke/KZsJk._g]R.h6"*Q.oNp(d]K>U>sf;N"C>=Jn]Qn4.GH`Y&C`Zi
|
|
||||||
G]kPqfcepRTGSjAG@8U8q=a?m@u]*kqIs:IMB&aI;oEW2qs)%dj#h]R)L!TQIXAHEHa&MF;@K&fAN%Uh
|
|
||||||
pYVCWPX)B[UXXP(J(a1Xd;H9fnuJnG=0l]<V>ddg]#S;2hCnY(J#=5%=os=`[XG:6o6:&0<Qo'XFj-Up
|
|
||||||
V1\V%^92iPZWb&llr=!r>rEaB[n`E6IMFZ7*YB]iS9iC$B!c'$%IYE"eib7sDVdV1Gd30bbrW7Ra-Qq^
|
|
||||||
)<QQgNO8<%#MHi=hC]qV\roLH8eu9(lm2at("Q*DEQ]lt7[o8sM?Aj>\fJ)p\r\J<UYU9@&D'^,B5oc]
|
|
||||||
7S9YdHg_Fg(ND9)SBr3jS!@cuWu\/r9=td-/D#kFaCVeLp.:[V[=G3sVC,t>Nh%mgF,_5aS*;9s$a\5A
|
|
||||||
^C%\&ce.Op(J!.QG>XgN?Y4r_AGssd<!T2S9\Tt-V6#sKoRZm!ii4OWetu6H9;]N;M%WU2^ht253,;qc
|
|
||||||
]O[K0GDWeun=nDlc0<Y)s5L0+Y?L(\Sa/30Y;`2"K/c5.G0"KSrR)Xml)N`]kYLf-gKf13'lnXUPdBQ_
|
|
||||||
*n]+$fNT0tKAR>8CQZ;G]MrG1Aupa+.:27G-Q6/KM*(HHRhE5&rFDk(>E64sZe&"`C)QtG6:#NCD:J0:
|
|
||||||
FMU"/)j"6s_N(D2W*$`OrIY"-PcRgD&A,rMTG09/KKN2nN,>>4NAEIs2uX_RV+0LM=@TG1P:JC\#O@hu
|
|
||||||
):Ei\32;\;VJug9Ri9<Z2%D>u%Y%'01S>%Zj4JWbf/R8\So(:@oQY$hNlEr:]g1KoB-u>2-N!M:_PM6a
|
|
||||||
+`EghR71@d!kH;oVT,n<nQ`BdFO[.,m%uVk3!8J,FUIse)TPU(7k$8429m&g7asD@hV:!6B[fPLEJA"p
|
|
||||||
)Mcs&%Vo#'%*14^NkL'kBq->pC)VjjUeY@u?/sRQ'2c??IkEnnNC19Rj%PXmBcKXm35j<+i,<<Lb].<d
|
|
||||||
Z+4nB`j,7l>iU@L6fX9C?/sT9"Kl`0(MOl$U#FEZUKQjPdMs0\a>FG^29q;S]1MsACE,qK2%F2X3b9jW
|
|
||||||
NSsPRPo=R4j&#/Lj+-"737Jep34F@1`Yl-H;MW70*0E)'r[kQ[*PS<J)fA*4]2NmFm;;1m[.3/X,!V5h
|
|
||||||
#1lB:Ok>M(j!pcHo]?#ONdc\1cME(sV8tbOFu%gJBE<\9nNKR5:#'W,XqfIa\jUk*M"hm<V,d3,6,,ku
|
|
||||||
q8>c&n$i+C'tQI!(#Dq@Fon/c:binCcej=cOQH"8L%^aV_A;b[pYCk.^PME+KRQKW_((tS2:O7$`BYN<
|
|
||||||
Kkr]kr]-US-C`d_]<%1t7,"qPn4/&J/F=SH6j?Lh]Q@l&JNM^0F<]fn.E*,KZ1A*&GfNguWAVb,2Y69N
|
|
||||||
K3r<6^?"O@eYL*bk7;o\?2GL;n(PUZXBnVoLIPX-?aY#q5pK9VLcihfUb=<oZk@S([Sj2#C2qSEq+WEZ
|
|
||||||
%V-f9^Dce(*#GH&^Mh?HQZurrLcIS5FU+JA(SKR;%0<p&QOcrq:.-4j]GWgh!4mGiOfas66pn?!nKc#Q
|
|
||||||
@C3Bf87AtRaQ_&c>Ra`2N*72+PW9Af>b^N!l`j26k,p7,G:c#Ee^%NAX=>5YKA;F584k5L!uap4c$&J5
|
|
||||||
+%'i4aY0cIQUKV4_MmhI\t2""%g(#%)9K0qKlLJ7429Q08or@,2sh23,J2h([B!tikdCk5c4b92d&OQ8
|
|
||||||
fU9?:o*>E*,[6P$Vj6bp<nQ:rT'':ejkhaDL)r5-ZcW1JEGYG#mFipfVaG7B*WhV&R6\T`nSG#TH.&iA
|
|
||||||
DKt3pCXW[-Q71/Y:-\G!qZLc_=IMPmlkZ%@@kCc]p8R1%ZVpo`)K=h6>WjYO\.^a!nK(QS>C.oZ?/V'i
|
|
||||||
_AR$R;V*de3MZa?&l4]USDu=B0CABrT\V6XjLFDZH")RL4(+3,r(W4/giI&>3fmn(n:i^VQKVbZ8[?AU
|
|
||||||
/^o+"0f9\lJ+tonP!tD=TVh]j)q;U:&ju#$#Q"1Te\C.brd-RYACK(SnI/r$VF,pLKm0iTo!do!A$>pY
|
|
||||||
][a@f`f1f#c-^CU`:Pc_eSVtb_V^BS0^HJ`eN]:C%/tb=nq'*>Du\b0N3bE<]^#/GnD[)un;H,47_'\,
|
|
||||||
-@0B4?cH;aoUf+#-G=T?iDeAQO_%(hm_Oi.ns:B_(#/-Df>(Iu?Y#5Y9149!XL`b+oRJ9(Xn$=;rM&j7
|
|
||||||
@C9EM'<to(hRd8d:V;#9hB_Cn]h.G>k^d2r^_<b"Ii4L*L:AH!Wsc09p4s:]p300MF+g#dK,@`Yh`fn@
|
|
||||||
XRR2tNZP_[ZAeWG&`A(HqLOp`@s:15A5sIM0;MenMnQB7YXNm8nU)?f/#M.l87Xg!bdo4ICrq-?mrDb%
|
|
||||||
NcKX"m^i!2!MR9047;Xk$`Li"I<9enP3?>Qm%nED]//uMmJU]f?$JN<oqPo-b4-4\++$K7U6Kc[0Dg((
|
|
||||||
CH"l^=#!)-&a*qFotBPW><]M'Y+>-o6(VhVDeU)DLFA;*/RN(@5?rf[2X3/%i9$4_83XL7`'+')3[K\%
|
|
||||||
LpAes^S"Q<Y@-qg6(r;\PC=KZpuh.i_7?33c)J>fIV2tn*gaHH1e4]+a+iJL2U!7a0K]`9dc*GtR^#6J
|
|
||||||
bjR5AmFdoL6TmLl=UIT?_98-!B\p@RCqjB7N5GnE+MhlYJis.5mi8a,*&X&P7<&[0/pq)+H=2I\bkQ%'
|
|
||||||
cr54Ol"9O$*`RI\33Bs`>CpD>IWl4t:8X@m;DL`UrD@>*"%.GFR9jeE^\QVQhO56d&qAU29c)r8c:!Lr
|
|
||||||
ksdP2/i8+CE:^V7A-a**^9$a$hM)\,:YfW*Xl/[0^;r_a?tPLG'MWLiVp"%cknM-cI-qq0hCD@kP:Mc5
|
|
||||||
,jhIo"RUA)g#?)ETpBUWWn:mB8_n"3>B\'JN#ZW[gIjKuik4?dC2TsbR)_g%BNVrdr;p2HAWCpY/'RE6
|
|
||||||
Zh:G1`sK=Whrbmmh4q;c#0csSZW/"dc(;:D"lAn1J7F?&E_V_&p;B*RI\g83js=+$]p5W4X&oB5cUE\$
|
|
||||||
0%4'/;2l?S3n*+f;TIQ,iiERG3@Oug[5T@Fi]@mMRJIET@=HQBBX5MSG9QfSC6Y0ZKIpnUNq=sO"VPRA
|
|
||||||
_BiK!^/[[t\*Gp&Kj815E<"YW"/H+WN'nW)G^+,k;G$I(J+-DBnWXd3qMs5I:W[!ldd#ul3/FVhq1W(^
|
|
||||||
f]PQ(T+J+Pif75]pre\FF11E/BIq9@)DOG<8[?**OuE&=Ba3QZ:WYD,!GO<)%m2$$(Ar4L0Z6bKrg^r'
|
|
||||||
)cF8<;e<R98O&sXe"qOT#>#`Uc$$tmG3KT0P"q(*?Y]FQ^QN55#aFK>ZG):S.G_;To.R.`2E'lAj_j&'
|
|
||||||
S&da6NIq%%+8U^1BQ40^@X#Dc,0.u+]*1]139p79eHd.<2!Vhcf^RMehJgt7J)Bd-`RU3oa/eKBh(<H!
|
|
||||||
ql'Ta>1%+/Gqtl7:.:Z>Z3%T8#`MhGoLiaZDJ:+p1AIk+$[Qp%cFL,6Bn_:*J3P2LAbF[p6qJ.+g9#PP
|
|
||||||
IHh#B4-_FFT#gdZN#.iVb;7T.m5c9_<DnZ:+pB(\rOOH7\Veq%"5B.t<p9sE24M)QVTOA?IEng4NO-*7
|
|
||||||
LggLP],,h+$,8U0=PiI77BL<JX"o">F\3AB<G_-.%rEY[s0C:6r+NRlM0saET]SB]\i=5`[gP?%dmqQ(
|
|
||||||
SIYQIWPfi<"Y]_8igi.M`9N0M?js-6_:B_'e1XU62o_c3<R>'J9X7UmnmWNu3P,_]Bk=aMgQSSPG8&DN
|
|
||||||
P\MMQ(I.:\\?2[=)`DQ]c>MZ:S'khk!!?e$l@jXK3W0H"<`!T#34k5igPD!<Wh/`4bd:oK</h3,2?&#D
|
|
||||||
dX6"3:"h1lFGL0])VQcRf'TbIg;c(GTZ/k(S8kZLP-GB^mq(nTOH2%WjTi3"[Tr81KjUh5WE3;$+I.+6
|
|
||||||
dCAd]S_QTl%HW]O(N.6n.W&qb:uJF'p^6f6:4Q*q]Uu7uQ0Uo8AX4r&*Q2;/$`%B[d$iR?%;!"6k=/0Y
|
|
||||||
3]"rP6sd1JQF+Ct(Ket+,BFpsJh3QH473J5`@f7,4%oG;rcL`*MgQ,Vd91Lr8T4#:frk%6`X?rbe%+[E
|
|
||||||
#WIfQFB<0sS"!AtS%IhZBX^O=&V5-p_nuBgb#G(igKi`?g=BSkjEE/h)"]Jom@QPcjFKcP_#*QG3QeD0
|
|
||||||
ZR't)`D1s<=,,pjFgQ-Hcu,h6P.)rXGKXW5\@Om]a2MD^"FhNh>/B*cF49>%SElpD_3?2?(&Ms]*1]lo
|
|
||||||
DXe?R$Egf0)&1APXp@aJMujg(":[<c'hD=T[qlJcnN`N&C-d=3C>o)n;XKTj^ZD&2E)S7q]]h\@1%<KO
|
|
||||||
X^@`bMC>=qre(jmY'G()oY:Y+<ja4?8a?W=R((THZWXXOZWD*<%NFYdjON<p?]'#L/D]_73A9OFRo;u1
|
|
||||||
RUi6hr3_*e,iJ!Dj)Y"grPNF2S>f[AYc%7:q2Nl3Ga6cGb%rMk=\<eXJSjHo/=3H'93f1j3jXpRCf'Fp
|
|
||||||
QA$e@Oo9%`6/e0N`_1!3>'l+_[*flhY_-TTG/K658eDRKN#Xri09qboCO=np4KB%Xc+$=?Gk)A*8m7f>
|
|
||||||
d5\Sd.Z7>`am[)iVfc)KD:h5p?Y^EIi+lB0*aGPq(T6>aL`TofRBU/kk%\Ao04aIr2;5,2=I%(f*jB/1
|
|
||||||
/i3!@i2(WnmBsmUZDlr"fbMbODCVj!V.4.@0?j.W<+t3jI/FJTJ+b)HVVdnAm[-mXg[DAB]J`o<on2]Q
|
|
||||||
?;b+;(/2H^3(&_lGj^KilqiqX*SQ&clVWrIe>0`0Xi)/lh,7Qjmo.B0r)S(8;-p<;j#\mT<IV!j2^)8D
|
|
||||||
1&LE;?MhER=6UKanuDAH;I63-9ZjEKGNb/OI?\b]8i_>L.Bc`p*65)STh.8C00-0[&BAsr.WoKUP!P@Z
|
|
||||||
90$Z6.49mIb&@:<)kW$OVNa/#.Hd$Gj`O1FN/Q\Ynu<GIjLAsV6Rf40CFDe:jk-BA2T6F.9c6QKD2Qs9
|
|
||||||
4jJFJq;)'h]jTK1)0&?<BUMcn^lf:V$c$>LBm$KU:1OkQYu?!%"g*MQ=PXb)<PFRZ11Fm+0,_#nHnHYG
|
|
||||||
hpu*QZ+'`/iRh^p[4*>:M))s:\;[X.`F&-DO0SQM1=F=YIqkC_X:s,g7dQ+.Q][SIrLl`<2B-4\Qnm)&
|
|
||||||
G9J:X/W4C(egsR7++0p6V4c@(*8@ksDqZ(sN&3sNk"S4b#W[PnEq9h!`)NP%1?-qsUij+W1=DVcFlVo?
|
|
||||||
oQNfH8-)6=1=GITgB"KL7[cGrP`[:GdGQOB&[r@-]8YPDVi5joHXK7W>q9&^^Vd>IU&H]i?5OH;^#e'.
|
|
||||||
B@6)Gma_j)NKasshO<[>CT$@"Q`Y/9ojBF3GP!@HK'u00q9esY,rK`QjPDFqm81A>g%I=0qO`+emQ7oM
|
|
||||||
TcQctd,0qYSo,'-Q`k0?VaL5;5UC\>Rf$RedaUEei5r.X9uRMR0>(K4%#]t57FuFSVjiPW]Bl,-^"r;/
|
|
||||||
UX7KW;<[9DV4@\!g26EDmd>L(O,0=c*"b<Ar8mI>/oT>t,t+^@DX,BTf\--@RL#\Yddm10n#Dl@0]T4,
|
|
||||||
TR(_/pIjtr0(HSO>S2DT4E+D@?$c<t<W?g]m8:=Up*apEGD,YHnRWfrl\"i<r,`p,?m`r65+80-ETYH7
|
|
||||||
>_i+f-ZpbCDCk?+faEJe,;6b:R>%_[()L+fVDeqPF=-fmrA@H"'lS";E`%+Th,LjGBY5E!$K@Etrr*p6
|
|
||||||
Ihbl:DX70jMY(HORX2"sa+X!G>AoMle6A^)H>?(?B>2ClXfked4j.Yipl%tLf_>=7<Q.XiY(/$Z(LI;=
|
|
||||||
S>/f3[MiKk9XIS/?Yisjl!lsF2oafV\tnf^Qc`-5]?$Vk=,/p,rBa`>b`JaJ4H%Fr8FRB)`Kp=OiK-Ra
|
|
||||||
hRjdK>Ai;)+KN-9ZY1F!WIDro$Z?h\dT8_#p#r8+.:-Q:+m5-#i9ekt4/qfD7efTElplN(_3^'B%]lte
|
|
||||||
j"/-rY,`6uToPJ<R5L`u;_%@&$F9Fp;ab+GZWim5n-N?\mJ.bY:s#2?WGuOTj(eSM0)Y0t0;68CJJ1%g
|
|
||||||
nWSbb0VakBD58Xn5c<!t(VRtZ.E-Ab8FLgrj"m0h^WfMPBY6IeX$s'!11eb"b9"8Ro_oY=!K#Q1WkE`?
|
|
||||||
]=tZ0.Gobn&nXYjcf2,5h]+jort`>j9>CAnFsA2dY:<jKWKS+NbW_5NH,Q\:,-@h(*C=sE`GA5S;+i%T
|
|
||||||
>9**OJsi5l'`@rj+V2?0WW!R:SD.6pEI5kSSo:%7n$r=M=jDM'hu8(jZ^k3fh6WW.4fd8mJU$0'bk#TT
|
|
||||||
=g"oun=/7?S>Yc040tHT=WO&9c:"6+9[#&#rj#lY#7mh@2]G4-#^XGE'DC%!2"0rh"4[$/h6S:"$dWEm
|
|
||||||
lcKo-^R4,!n$F.c4m)s][Oc'<;r]"DE[HYU!])Wf^D,eVY%KcdX3>TrO-o%Qrd'kd8*F+$^r*K+0]%$/
|
|
||||||
n!mMO56jW=i=ZhJp+0k7_44uq2@\df,P,Vo2DVRo#'6&lm5C?^BP4u6^6$/hYL:G%pgmqsC:?@?C,GH%
|
|
||||||
M*fF,?>hW9B=!'+T&gACQr=E=)pC6I[c\[&NP*Ki3LJj8biThf1>qpi#6>]P?CG"5^J)ssOYru=@YWAW
|
|
||||||
cBm]:h"-:=[<#6c[E*A8p+5BDHdBtgB7$cAe4)o$+cfQRm!RC#e'E%TKTeJUgYK3+GC#M-lHks\]PL!l
|
|
||||||
Z^gF=5O/tUd3I`6!I:!;@q7dWY-'S(H"$?:j_XC`**d_(WeT$Vb9*UDf\]@3V+`2V#.'ieoTSN#,%sGX
|
|
||||||
3SXEa-DO%s7NOlc8L"Hh2lKS'/B\pG*6dQQ=$dB=A>_Ar`,Z#IIIYNV:QF.cZE;PkBMPJKn184!ai*u!
|
|
||||||
o(a%o9fK8p11iN[H^6I'1Wu1<1!:I+3'>C;??!:!n[)F0R:cJ)ijZ8s8djCEIM2.Ceo/,RbLt/ERf''&
|
|
||||||
ea(`CR17X(jDSWjmiq#<C:>'U4:_\ACK\sDDpNI4ZZ?@-bLp4A0tlD6/tH.jp4eUt;A)^?M3d&1ZoT>E
|
|
||||||
:Is26.gap8P"6cf/$k`p8'H"NbV;2!k<M=O083pml]H;Y/8*>A1Wu1)K3?L/Ie!??FKH]DRIAsSXK_R=
|
|
||||||
lue@rnKc-q%#NlcM<dDABE0d1`q4!fbXfULkS$4A=EfflWdtUF=(H*U;>TP-[_):[I+l3aaQ;Z6;k@Z<
|
|
||||||
*t(3po>*(/+k>EN4joUqq>_u4b_HQ4BCA?$)eI1V@ZFZDK\KQ@2$J138_RP\PZ$A+"Z[mt?U#On,OsYT
|
|
||||||
ItO.?8ZP_'^6>*?=`PF%1U?ao$X):0$-s^9k8VR!Wr+/e0Vf(d,kbF*60M2+RP&moQQ#J*ClMo9k:G1b
|
|
||||||
]?oSp3KmPuf'rZ^cd3eQ74Dcu4:Xr`a.B"aUd%;nA7*R)9]DV>]'86]UT;'HE_-]1O@!E^/5K]U:%)1e
|
|
||||||
]$071"Y7bsp"qIc_XJfU4L+drd,0'F(m20B4dfHR[c6#H%6JRk3j/$C.2OV_*gH<$C`hPE*';=ZAfP$B
|
|
||||||
0t@@S]]#Q#Q#ZDfh,J6,*]#c&,mN$PMl"Kbfqb1E<Arr/V9;rN'>9/0.#sj/+^8ZH*,_Em91[SQ.#oku
|
|
||||||
&lE/d#BB)u?(l)"/T!5iV]'m#?=^^U:76YB;3PNeM3]XWG)jk\')%Wh8aUWL4[qrc(`e(Y)+d-+4gJ'7
|
|
||||||
of06W-ETf!UFb-*b#X'&W&SrEh]\#N\2qj'L0\fcXSm&4Gk)C*fSMal**X8"m%bXT^C1"5Cm@_O7(.uK
|
|
||||||
!Vn-:CDO6VbQm1);nl#OM*8.E-GO&>Pp/5t(nDn2p?M[e,4c1#N(<$^/QIVO^Z4arI)kBm8%(g`0>C90
|
|
||||||
QI0u9jELIkOZT8E[gpf<;1Irm<SBHFoB"Rn/ZjDV`ii-,ntC?P"4<HBgJ`#6Hf?'$f&?)TZ2=*S<R?Na
|
|
||||||
Z[b]nj2G?:5Kq0Dn`Pg'#PUHFMc$\+8cOVB)j-'+h3W%=?Q+(<LX.Asq=jeBc<P$u'uY`.\b2ggO6^`S
|
|
||||||
`f,9C#<joWDi@nW![7$+8EVdt?Dp!k9;tiA)Cb!&^@nF0dqi+#@cgcWE/AU(ZB&m6,b)CsAh;#X=j5/^
|
|
||||||
^:t7Pga*S?j%koiG:`TirY$7L^Ye1kj5u_Gk&iWM@@>a'[)^FMg/cnL(.6,\RFg71X^gs,[rlJW-E^6Y
|
|
||||||
^m*,UBp)b=q$L%_Ap*4k6dEWsDJ%]q3?Eg]Z?bR-9BLBO?12B\G[9g>ALt;^>KpV(D,QiX`m>KF=L<W=
|
|
||||||
p?cj0m,Uc,Hb8H`,MLn?fA3\m19]G07&N[[_d0_@NX5LN]^E5G9!KGF=?5b\lt]nGke)"N7:&`ciHa6r
|
|
||||||
4PK1+gPO:;B\;!Fk&n"RAGW++0UgR2jTl;u-c>15)!>N2,9LQT&imtS]^E3Vb@B=L@:"/Ac_/cQT%EA^
|
|
||||||
o"+WiAkfX$D^2%Ci:"LpDIeiNk&mH6h=J5"9qKtm#0Zm_\od(W,F><T?8o/o3qKqPG(90O^2%[mfFmOQ
|
|
||||||
r=Ul`4=/Z@QIej!;g-Q"j`%<_.UB?#qCVH#?T#=$R25lUTKEM30403#/7lN6/(QL_Z19\7nW(<^?P,s,
|
|
||||||
oD8.k@O\aPg_!0[NnB3UG:m]\^o^^2@jrT'AnC=bV3mgY'h^9,hhn617AKrdYn[+6YnXPaYnVRcYnZh$
|
|
||||||
YnZgh@O]q((cef@n;^TXIM&iL(>/M\=Gk5TN`aG3Y7XDFlka+[5^0T6g%Z.P_(S5;Zl?97cI/89b)3%F
|
|
||||||
5h?8dQ?)Ie&N`#5IE+P.5<L-3;8dS071ipgV&3NqOUF6dIunram2Y[h*qQV"a9Rd'LquBXldRT8f+4H@
|
|
||||||
Nmbe1'ST\6]69sEJ(<uGo,]J\W;%qHNu;94BE&-*/>_$d'h^P&h"c!PI0S0Ip3pPIDWgDQeW?LO7Hb+6
|
|
||||||
W#VXTZS$,6=O2.Bo&a;e)=ehg^*8k`bo^FEKef?(8te?rQ=>3\X]UJ6Q!:MX6loD\`HJ+_Z'<1m/Qr4*
|
|
||||||
rUs@9g5f.D+(Qmf@Wp+Y=Gj4">HsF7*[-gN'X8?\`)=grg5;_7oFX`!e2*qGbWo']R*:Rm8tq&</B.>=
|
|
||||||
>Hnn6&oPZdU`$f%*`sS\h*(dBZ'7Qb;Ck8LSER?b7npaBnF2!<.@FFm=A!mYe:-GVAPuBq_PfPok[NpQ
|
|
||||||
dBJ2Z(*\D[74`#S9X0gJ3E7VA:l5iXR4R`q\Ks9/R":6W0q3=-oR<!KBa7(1g-q@H?abF47Br(n?Vr@C
|
|
||||||
8F[;`g24f,bQGNn91b9fO%0Ys.:NmYYnZc<a&3JhfF^bZ>RamR?&W,]J+/b5.>+/CMC<pu3:;nl8D6cg
|
|
||||||
8`NO8PXp6,/!=?jPPQ8CW%@3lh"7fQ7@[5OP\e<:HsO8GD=*KZ+mGtP'P9R>.1;AJ/1".Slt2'e=_bMf
|
|
||||||
VoZ?[*Q?lX2grqKX>[MEDI71Vl2o<(_-\*V'X_]rj0otXK-l$&i616P!@%BegJs[a^F]:To@j_cIha@/
|
|
||||||
eY>CbZfCsJ7tY/`UqH$/ks3!IWPlCL8APOZrOtj*>C5["$0XOYK5b^Xo+pg)Y*e#EnWa&CD!IZ=9BALQ
|
|
||||||
j+E^XJClCFYo;ONIWrlijJX*h"EKu$+C71jRoa=_)SM"$eaA9K2#li,_g<Z)rW_f4j!n81\>lrFBm-p`
|
|
||||||
8[C39$C=0<L^J!F>nI+lGXYd6PFa`NZ_#k-"Wf'Y2htB'>,iL#BiDHu[=EOaj?.b3aea!(KHnd]NjZ]@
|
|
||||||
rm3<<7nC"6E;$K,)^1qG66UpO)ND7:qe[SNesEn)KN-su,Ga7MJ"<@NPr[_A4`Fm>mOHnBqFJ@2]s(eS
|
|
||||||
9)D:O/%)DO*jQi`R4Ygec[*ZNorj6?d%`ida6]0&'=XR[^/;MJIe6X`7.hTYhPQGa<Pr<ieOqdJ`g(I`
|
|
||||||
Da^X`<2*A]$DL$$qEeuX1cKm^'Cdg$B&h"s7a3K3eb=mX]m$FK5'GT0\@3T(+#q_uj(@E82_gho3,N]D
|
|
||||||
DTl4$mH$:]QY2c5h7^m$/s>MR'qm.H@@b3ibhat?\q>H'7/o1DOuNucj)j]'ZF]9Qc'phMgDds(IQA`-
|
|
||||||
n\1Qn?X++]Edu&GKQU]rAsRJFfkNHh7JNn)nbcdUCe`[L0+l6JQY*@[?$*=DDlW48X]2YK>(;H2_g7l"
|
|
||||||
5;U4qA8OE_j"95u21<ODL[52XMg!h/oik"hX8=5e@&",)U_B#?XS)k0dE,-R:KiZ'nV1q-:[0XiSjBDo
|
|
||||||
W*PnQY.7]>gGnAX%`J5W,ua?J9F[Z$ld-,ggjGH'd+]g'I5o7WHXGmNh`RId\^+`dKK*Ghg+U-L`WEgP
|
|
||||||
NK$.!`f-28>KPTe*X&Y2*\A\90KK[BZo,Q+EE,q2E!h("^<_0kADL^u`.l'/\g%6g6G6YCqeU%nBp9!p
|
|
||||||
;^.9fPSPbWA,PAr3+DmS2qVUdpO#db62j$j!-.F?_=0TcXk#H)qH&T(?A^?2Gq!4K]O:\^I%R>*TtdZt
|
|
||||||
NH4QB4OurZNmp\#k^M)%lg<h+E3/YsM*p@p<FOAGCmk7Mo\!3U-Dqn@9qfn5Ae?EXHg!-&/]UjP*l$3R
|
|
||||||
<p^B2;+>];H]N^FrXS>mfe,/V;S+k.`e-mSI%tc>:>`7EidQ)L26Zn;=R-COJuEd)SiUGjn0S0<ULa[f
|
|
||||||
&M3@<)I%gCQ)/YB.LsU/B5I@k^nc51*7-<fQ=a%0PeTu\C<aOqbG.ShXhWsP)QhU.Bq`N]*@b.S68$M!
|
|
||||||
"Y]`Kl"0)PiMW%./SB56r#M7i>FsL>1/9N=pOG'[g[b]>A0g\u2Wl`&aP6:l&7+s,=epibB)/MF1We^_
|
|
||||||
an+UEB]Y%3b*JXM;r,4)QQG/Thm)>V+9K6E7tCC,Af^u"m<Jgu-:QR6bR@6ja4f0\XHe[2DrqY=`cdb?
|
|
||||||
JCEkSGm'X7jq6D?bEJr0=W+%_p*b&"F\VSABUa7`pL+O)FBaCr@IG@_("QcV`a4UJF2a+UjGkY]ieRr:
|
|
||||||
Z&G-Q)m)2j\J:rIe?jpc4r:Cq]9;Q!d7+`,*DpQsBQTis]ja)?%39?7gTu9uN)F<V#T$.-9bF3;;Nt@N
|
|
||||||
A0\ePp#^O8871oN\(_"DKtFd-:JWB.(T!$JkZjk1AS&;q\(deP(39WB(\L?!QJVK;iO'DsQ7a1@^(XB/
|
|
||||||
Fk\&*U#OFgm6idV74H:".!:k`PYp]u9>3,2J<>s4aJ77F#@!YN?1IQsH)Y$;pVD^6DleT6+M^`kY+W8_
|
|
||||||
UoXds=GQ*L)P%R[i[*"p>WZ)TSs0UOPZkRbVSsVU9?:?g[i?hh]cK4GL]/I1YI0[Zbp^!C4epM4q`]p9
|
|
||||||
GO5\m-,]E!?P92Aiu&.MN_&YgMZgfTn^g4/s2;YMK.+-!h2Ea>E($O>pNPR:jZJlO:u\INQYaolh2U1u
|
|
||||||
2b#4-+nL*oGmp%,;/6*9qi0[iOJ:8"WhS!)7c=6)..oI3ms+MEc%sL$(O&R(Z1j/RC-/87;"rS[ctg=p
|
|
||||||
LOP4(-q4I^GB*A8EbO("eGQ>@eEE$he18m'_MD)/?4iI2n\4%/`AA1s%>rdWE#1s!j2@SKV3nL_R?ljO
|
|
||||||
[TcuK9f>(P.Fr>4bb^\lm6XmmX=!6_)RHVSRc!(=G<NZT^)QO'Tc$^Bi#%ONDh#S8V)Gt#V;CPbU=?W$
|
|
||||||
>S3g@Q"VU=D6)5ce.CYuQss]6PW"imXPgG@C3:aZ?EP>X^-##9cel4gLhkr&WAifZ:Pr')g,g%G*oq</
|
|
||||||
WPKYgV)CQV<*Z.rSd&P%90u[g4\DbcOXK9>.r;[eW`bc54#;sgW?$2S2p-l@T1(NKipWa1cP4)kQ9sm'
|
|
||||||
bqda=r^IlVX!mWp8h*FB.OdtAc)<%uc1P&#HBO&0G-iGc/RCo2HBP$7NLc;07B$O&"B"H5F4>)'NnUqB
|
|
||||||
o$Y[lP;GZFUXXI=bP5q"UjOmh]edih7joOYN)Hu?UX\$BEJm\/IO@ng<L2X_TD3a*k^T,tlhp,oDlnpM
|
|
||||||
K>1BMH;GdsV&kkGq!*PIGokH8CBo?-Ir,\l1?p4(M(dBgRoQ!RldX+8\hrunn<:0fHFl7E-DT(`g%6]#
|
|
||||||
7<X[bMGnmX9'rRaMuL&0]9"e=M]@K29gn2dSG9/7D3Jlt/tnrIQZhZ5H-(J&WBiC1W?DYo,?M$T(o=C/
|
|
||||||
Xa*/,')m%_*V95^EBKHaIH.32/?Bp@#,fl,%:AM+q#5sl\l!s8=eG4"rrPu(NVeXPc/U6QEnV5D.1Zhj
|
|
||||||
`sF?N%SqJm=#)5M*gER6+rm1DC$#Y\*Y`=H#JTJ9lMW*EH3DRON"]1-_s]5V%:6UP1f4Y?-I=:a66B9p
|
|
||||||
Sk/E@R3nS>C(&E*eZR>H=V::kXZ/VsW$d=l'eq:,cR/L]'3(N+Ykh(ki0$R5.+e/5hhdgfDf&,hh3,g]
|
|
||||||
Yaslc?!"#Mr/s8tEGR[%[5kh8dph-HfVYD]-`Arj7%Bq(ZnQJ5W9lTXg]VAu0m+.CVYn9&<s!AXj!nn^
|
|
||||||
WULs-kc/pWE9[RkoOfe-mLrQZ4k9oh@@X!*Chjf3`i\?B;1.XU:fomO!Fl7P'53Mf5i0DiO./Bj=8CQg
|
|
||||||
b312!FD5`7GatG<qTmV/R6):Wf#Y3_WdFI/24W7E[r>LQXiG.8UbUQ32eNP/f_gb!\7lVMFK$S$Xjt/F
|
|
||||||
pQo?%L:NrC6X)J^Y>/7aBuo=_:a%;jn$:ZdqI^k@WkID"mnlP=fQfh&Gfp0r?C!6&_e*9HJ(feI2fs('
|
|
||||||
B.<J1cK>2L5F<be4SJr9"2:'p):iM]'H5Fs\tjGSmddX>12J1BoRh]ebPbgkhX+A2aLXl-9ZQ4MJl!hC
|
|
||||||
s%qLd23r'tdGm=_XiJqL_s+r"l)L6?k_A%jI1%]2P$M+=\4[d8$bcsDBp(rcJ^SZk/+?q>[)D/cL\<4c
|
|
||||||
26VaS;GI&B?+n!o5Ah;rb`d4.*+F@Nct1W9<j`0-*lF]-(/_#%58]O[_&Q*+TJu$+(&*>X)X%F$bXkHF
|
|
||||||
m]iQTje^V*9*+<QQPl\YUmi$9<eV=1PRQ.sN3tLFq`;RFW;@95(i&FIDi_<M?h'!eI#XYZ(c.Pn'?/5&
|
|
||||||
B)<C`%$ES?;4jZuHu@9>bN$4o"Q+IXA?::$Liu=O`@ai%GgeAm:+,hDl[VL8C<rIQ`<j7.?0Na+eo.hW
|
|
||||||
Vts1/-hsk?pe1$%>NO*K=Pg]ArQY66NltenWVlAap((J;2OU`<!]+f.\87b($h#M,S$E:X*F[ENeNK28
|
|
||||||
Zsff/ddHE`aS%Y_m`-QLVingE$d<9C?=:cSo`F`F&U8fc"m0TJHuIZuU#,5IG@?NYZ1j0J9"XQa+F5O)
|
|
||||||
c!]jo]q]:&V:&:6'I*b3>HmlK.E?Vf;ou6."_RBt0/0Rpf*LC?B#?3,>dj1:j*LO/lo*LS,/\6fTEqfL
|
|
||||||
.Y4)G4#RProG4b"L0D6SCtbcN*G+im28-M75(aX>Z(gD2SSSVIS8:^_Ra'o#[2%GCA>mKUbePqSo0*))
|
|
||||||
irs&49eS2=p(B#4g\TU_"_RBo0/2,j^?J'kN%a&2m7'%"2O<?horVN+_u6Vo6f''3Q[:4Nq=>7t)-f6<
|
|
||||||
It0ZG"_QM3=1uC3aQ%Mr)>k)\I`m8CoMjO9[tDm@3Gl4*HReE!>Ei11@Ko.@kc()\*`RG8B46kF;b?&A
|
|
||||||
9#p(nS@0rIoMeTW@TN5&c:U5u=?8e6VU<&fVimg3(RQe(V!8-G+l12J0VS<R7gPdbn,i2,&,#:f`='oL
|
|
||||||
jhuKP_,6EGT>h-33hp?YRR$<[4>Rr"oPSKACl?0g^.B5\Znk.g%XqV1KpKMWa36;b.Z)t[akHAdc=5\)
|
|
||||||
VioqW:Z\Yrh>6U^;Pr,sQhW#sYfL&9U.aZ&ok*OJDmS>&C%@m6U_fC'21Y+]/=7BJF+h8,?1[MaH7D5L
|
|
||||||
8l):Sj?/6ib%^jWlCLW=ZsK.PjG5ph_UUf7p0tN$?3/$7nZ[9g*U+8IjpK`8\i3Z\`NN6.oMe_Kkc(B%
|
|
||||||
l!;gT.-U&p(optRol]5jGL[)FK9Di$lEKT@01EKX>Q=>o9U"Y$\B7al'9H4c8muup<WBctErLs"p/cYA
|
|
||||||
_XY?Sr@JLT,u.`O,Cg"Y=p0itfKG#A#`*m3X<0+*2H'F'YP(J6s2+<CC]5p"I'%+'NhH'!V=bAEY4_>)
|
|
||||||
U9m,@XphF^4N6'_r^#B5qEa5kqRG!+9!nle2R7UBrI_.dI@_7Kee]6RH:5:7Ue,^2O)m(Mp%9>=rR6P]
|
|
||||||
>5t7<BmktQ]%7kS@es;nfsUfFrR1Oe02c!RpqsFRI3R;>_U//BQclek61r\&S.04];SVIl9FEFJHIo4)
|
|
||||||
ZL/tIgSOR_mRLHQkAdHnEQ061^2WfC!=NJKntao]Xfq1al`1hi\)t*&m(EAHqIap;.NSHP/C'$RogK[1
|
|
||||||
rI_OorBSn?%Ge2[`S[/_Ad5C"W*<Nm1G2"r0E.!\;2m]2=&37'/AAMra:[+OfS%hrh2'MUUbK3*T[GIh
|
|
||||||
;sX#h+1;)6ldGWm#&&G?e0hqiRh8$RPMTC*G-*F.Y<FnnZb(lA1nOBOoF'"*1tlM']0bjs=0h1pbSNZ]
|
|
||||||
WU\M:=E/X;[;l_p>H*d6S5-oaj/pi%.+QTMB3-j"?Y<FQV&s>O''Ba;=&N0"X:)WlZZI)%fbNaUSR%A;
|
|
||||||
K\''/A_hSArG?PGUHV<9c2XG(o<Ts?;WHGq/4o,S&JV8]c`RiVfp1oNMX'hO4M/K!gi&Y0>3(U>.<J07
|
|
||||||
XK/ZX3O$m7)Q+8!H`Uq&4Ebne?7.on"TNDoAU92:rW?gNq%)3KG&*URZ*.3!?-->Qn/>pG'_M)sfc#4m
|
|
||||||
I$a+en&hsejB/!Z\bZ+qVmW`:?q,;gO52_?!57FX);1$+hadlg%\D%ALVQ]!<NU7g1"#2>5'1hFH:G?V
|
|
||||||
8f=I`.pCqjPt=$Xl;%.hClDQVaoa@C)`Yj$a7"!?Eub,V48+X"4=SAfT?N46gis/5*g>UPDPF;R?`lEF
|
|
||||||
cPa4D-G:++`@<[LMlc7'Vj0O?`7&.fNa<Rqq>Ja`'s&X+SQ0q0CSpE!gle9g8qq<BV,fe9GH!aNo\16h
|
|
||||||
9A#5cQX#e)h[)qckCf<[XW<u8>dBA-_*gpJ@\55YBAK(T^!=4RV*Jj=;=a6<l_8jUI@'Q64c4JQ2d3(_
|
|
||||||
LQldo(n?>D%OohAMUoq=.!u[D>$:=56>%fPX4=`NS_>[_Y>\FYm^qiH\&&e]qr4"ULh"%R9Ng)Cq-BT3
|
|
||||||
`A\7loS>5FNHh#eiZE3jMHqMa&hjC"cFVZZjEG=[%eY^l9,b9A#<@AqiAm]"aa<qsE_`]Uhdr^V1?*DE
|
|
||||||
C`7>=]Ztirh_Q0UU4*<,dp7Sa`rh)4WD=V4Yf/h_Z-FZ0[g=[gISP`8*b3<jX(Z\`Y,h6+Y17RIg%GWO
|
|
||||||
[D[;Jf5O3d11dO@p'nR8gJP%f`-eg"^D'J<h;CkP<BQ*ZM<eo)C,q6M7L$[7r<PG`J"$sJWdM>f'Wc]4
|
|
||||||
n")1E&/=PCpa.##e#BfC+7J9)W8#@NQW)^LWNpD"o,?%/fnf9IBn-&dMo&15(tVhNokNnLJtTV)GWY08
|
|
||||||
dIK.V;Sk$&.^,K44maC=<Ru]k!U;7F[6B%Bp_C2@pmo[XI.c]tn!Dl5"p_iqqWYNHB:8=ops^rm4;^;[
|
|
||||||
G:Wc'%[onOBC)m,QCj^fs"<s:KBJo%<-+&..^]M?1Lopk,Q:kXlSV1F2jRFP4iS_X5B,h:ju_6$cJ"!B
|
|
||||||
gP%X0P2tosDdGJu("Uq1_eY/$C6q[\'\Q..]mm2S\g;^qhgI31N:-]8SDQ!][(%smI)Gs_m?F3L)Y9pu
|
|
||||||
JM3J%fAO[A3[JR>^.;h,-[TeIE4"!@e"47&s(F,n50Vf/lK,q-4_(#+9#&2d*uALT2Dt!SL\X"O2RR&T
|
|
||||||
rB;Zq-5V)FS`>!uW@4R5UD_M!"/t'7Fmk2D=K/Y5:7*om)72`AG.qQepVqVUGp(hj^t3h2XK!4HF4?eE
|
|
||||||
6gQ^RDsXB)O^7:'g!j<.!,ZHmPK&+>PPS\b;D5IRqW^m<BjI*#h&"hB:#-W+D`0IcPW&1t^Q=,"?MVd"
|
|
||||||
6XFuL1FQH1D\0MSN\;))mIhJk02KB^U!'QU$&cl$]70"9)7HSO!PY#a-b*!c".-PR-,,4mPmK)c8q6-f
|
|
||||||
6][Ot&+umcjhj^HX-X>9Ilf[!If3/N1pu)7s6#.ckWQN@98`W'!hL<>^3ho!%iTj_"7*,)I)mu2jhh,e
|
|
||||||
e_>g9OO]%:TMhj4HMJ+Q7(dn\3Fto<+(ftEo>T"4D^O#:kTO:s6uBVbLmFLUCb_0F$d`q90`uatYd1E<
|
|
||||||
5-0fCN0Q%<EX)I'6dgZA*tDGMF5D`Pkonf9=WI?VG+(b8kuCUKaMuI>HMOe"cM.=YbD.BcMpZ+UW]9(U
|
|
||||||
d`[D;*+a6.\sN?&f)FV&,VR"3cnFL[@4jp[I$gmCasD3#IVQE9bc(7gbG@36VaKZ#C')*5WkFp*-H!7_
|
|
||||||
]'(.q'E%iigL'^GiA`[:OhjEO)*DC:Zi3$erP'JekYLR"5KGH<fm;[9(F:6V#'_Ruae\ci^"i/sMP8aT
|
|
||||||
\tR%^R:::diln9D%RP=Jd#0,3Rf^*c&-!r#:@35-AGU&M*(]bD7c=74at1*Da6]P,%ZV@'QASocr5.q^
|
|
||||||
>'K?3C5DK8C2hO3hFb+Poq\j"aH-I\&HYb`1WWoUArfnh:3e8_.do$$O1*09qJ5:5U#Bq1-g&1.+#\Se
|
|
||||||
)XechHC5;$SA"qs&`A*.GQZ9&=gH"Q]H-/Ue#rV;K0";9ph1-A9o_qgqI1<Dp;ST0(UA?fPb.U/QZgF=
|
|
||||||
eaCZT>CL]9rT66E6@W.IguGXjJ7#Q:DMb9&<ZApNO>1hM@],:Kd>u.C\B4SE8ILL'&8ADQ;UCE<[$PO2
|
|
||||||
G/rP9m&QKson`4I)(0[QT>+ro)=1N#05jAm,3.i3L#L&#biE6Ih`5Q\3]Z\adm@@Aj=+F%,l0hs_Hf2N
|
|
||||||
Mj_+#kX4t=\ac6LR3b@,oU74*>H/(4f1ikKP1kVUWLah*4el8F#-#egME#9pedm4&fs!Q%Nu[F+C?du,
|
|
||||||
BYNS@Q,.$ckG?!^35_6,5rpYN?>fRkkISuE-W2$BYDai#`TF/_*'@^K:)?E'c:=*/E?Fe8VP3'R1m#Dq
|
|
||||||
S(/Zte$H1elCK;&XhU/!dO9O]mp/.k[J5d(.JneGjo"(#PYBE$`(t%OPo?P@\od,QP?ILM9BPWmRsg)3
|
|
||||||
_[jFN='a`^K"mRd[&>WXG8W@,_j@+lR8rf:b-Kp7%fY,[%e)A=rFYeVH?KW;?E0_K3H6InlCcQ,%G,K$
|
|
||||||
8`j'6ONA)QZHjlPE@3sBGtfH'mE/o`$@7'`@S<(;XUCSec'>L)&(_ZC?Xe0V_X+!/gA9tChUtU#m<Ea!
|
|
||||||
*Z>r*]l?-?En,@SD>+P?Jtr:8j80Yd<KI**a/mV!6@I\fM<phS.^A0=AM[Z(1Oet[SE3W)*l#aBJQEBU
|
|
||||||
A"DaR9Wm_^G@q!GXt5ra$nX9'CB8^c'2*%SrF"3ljS89A+',,QG68YXYZ/Sho2R:=K`$kT?`'rmYKo#"
|
|
||||||
o;3BB!G3Dj/_!raR!Xpn)\fV2Y4lm.B4rs:Tr$KRE]qJ_4@:VC$R/1J8#KT-Nm0@5^dko<[o74HjQN\O
|
|
||||||
SoFr0<fRnqU"-g3,L;sp1*G!16e$T/bfpL5Z.16cQ9'KJJ[j:e1d%2Xlbb`9GqM&U0`+4Q'I0Bk$iUK^
|
|
||||||
Bu+.gZ$k?(61M:a2Q-=XSd7gtGM6dPc-2r_f.Ue/pnA2flH-qr?6#^,UV6M")1aAS?9Ol\Sm))<<nU:R
|
|
||||||
68&`<2?s&4r.H4&be!&1^YJKd.rG,4&\b!pMaj]^jsO]@#rBrZ\KPA4!m'[%"V3\?p$MF%_=PmUkE?mE
|
|
||||||
N1EG@1[e<2ihW8&J^5n8SRFI6lUL@dC"49$rHH?IMQ\3g!AcKUgZdWtC/J9/5CY?88'L]_T-N32'O,Ji
|
|
||||||
>;sO((+j%]C=."-GV%Wr\+#D\hr;JFX1(XTNb5MOI/O3b@EVcgC2'-2?uOeA$[=,-K2#Q1Z?m!#X`i<=
|
|
||||||
g6<pGq17^sVsgSaL4aY<]9ro<lDNP!pg3qOm^/#V(Z*Dp'm0!V>1aX6C2C7W9$jXpH_L`C^Y\,54)V.5
|
|
||||||
\>Y?F8r^<BpU?:ZHR)[N1$@TrVISd-epk_A+)>G[o]B]GljTJ9Xd!BbTl5Dec?\/<q!FE7I^7V&lee-u
|
|
||||||
)geYJ8i<N3VnTK<1$2'C_C^6)hY'&nd.Y):@2U>&Ebt.GDur4CfA,&77!^5q$,A,$oJc+IN5eKu/XO,@
|
|
||||||
%L8hdCg4N1`kr_BI0n[pGbc0=!1Qop$VqAH-4Y>DqkHU@fPCoCqm&HQPl50Ce9'Fe72?Q4J+K/K?TdGq
|
|
||||||
MXTfXU)T+FH7uFdK5[Z>,?FAYcQ%;GbPpF[,3*LSlgL8EEGXkh/QsGunD01j<aVK!dsEeKqr[?#]s<M-
|
|
||||||
$tc&tAHpB-au2H?I#G;TX:&6p?F7d)c/?!WU[<9LK%hd@<qi0CKi,0QrC1(=R<WqXgOk7WD&q4#.$Z/3
|
|
||||||
GqAcsDX6Fd_5EHU;&l.3H/1)6I)"'cETTZ(^aDVlqPrt]ri4<COEsj(4lfNYo,br@)C>:cM.aTT`S4==
|
|
||||||
haP5:\)D*d^#8:roC=(JX2CmV2e"L3o7>=h?-K6&L1&+uk).O'R4ccEhR*u$Zli^u"hH+!q"r(Z!Q'P'
|
|
||||||
nXHnZW-7g<dttMFq@M1^X%nsPkK4"S]IeG:gA8*Q3?GQW8/]qEbbTj!Slp0P3]487Y-r./Vp%DT7icNR
|
|
||||||
4)%ht^0,hdGaS.s-`i*CfW:!r8\0W(f+P'[>8'J&o]BZG=B,)+ON0cU.s2e,qlfh@C+-,ns,*_F1VIbI
|
|
||||||
Nkir`44gl0Y+m#bjS)7tV0qmsh^%jUr;ci2nb1R(+*OX>lqGX0o-SOkXdK)GB!c7=0hJWkpqA+%9(Z7!
|
|
||||||
?fNfn\NFiXYAe*Gf]`A62BUF3o4^k*osCRL1SV\]n+a>fp_UE9fV7+uD*;_bepk`,/Tjh;9>2r%V;+/Z
|
|
||||||
0'`ePdY^A]oq#mXkWWEg!9?u;Vi:d@'/FY!bK4u[g7;CDX"I&a\-Y<]'ncM*]&27Z!Em--r3C>A]4\Z.
|
|
||||||
_SYXOXGaThf/7,;?$HKks)I4.eFpdnpS($%:oW8Oi_A=6S)U:WIcAtGf7IA(^!So!*3G>U)+O%RpACZg
|
|
||||||
M@m@"COYK67i&D>HVq$uL=/98,HX\9\?B/nU53-EjV?dWdH/cYU.p8a-f7'ZDuECkd3O!7[^UV26'bS"
|
|
||||||
QX7;hS%d)PWJpEo7"TtWcGnUV%X;g&V;lg:<aui/0.JZ13gD'V,U90D/U,*S$M-He&2Od`;XGUNp?M'7
|
|
||||||
,4BDIa*%W7bC`gtD>=co@m#2<%Yl+bn)(DI7f)8'c!FnMPeN9BjDc7['B[/Z,Nhe`R$j)/rjktJlR8$P
|
|
||||||
C`6b^Q>P3TlpG9#"li8?j#OlakrG@po3T1=`g8o@X'rZAAs>M5K_g%M#;Mrbk?AH<rgr(No_$t^P6Wfd
|
|
||||||
ATp%m2<[\6H/QVJ1F?l*=rs40C*^MUl\BlXkN5fp'<i=V]TmXZ.R["PlC(EKr#OGeZ-9q-a)h'I]NC'n
|
|
||||||
n%]j\\[O(L]h\L'LgHuYY68;N[k6KmBI2A<_XO]a]&5MjRZoK$`:4o0_2YWcG!hIAnG-YV"?V^IRik6&
|
|
||||||
MZh'+0>+&bj'@?t!1RQ.PUVR#-P%9OWNe3sL]rYJ$B&p.e0s0+m\'C`.W<&X\RPb+,?sW8h;c*l/`>BU
|
|
||||||
2o\!KkM5b0%G#<+VXkUO8dBK+K6&Mk8\?,[>`CI4UPA@.E?B%^g<O+9Is8&h*Qj:/%9M_p\)?cD0;]1G
|
|
||||||
N1LZCdjIV2n6Og.MO"J8B[q=8^S?%"1&],IDCGCA\O9dhG]RS`g<.0P4)i[$r5RLp3a%M<o,R"dJL^UD
|
|
||||||
_M.Giig9JPM0Il9>11#"fBA7J"<OQb]K;C7oNkUu`EcR)D7Z3D8Il_OJ$X2^U.QQn]g"b>N$.ZIkJ\#i
|
|
||||||
J"+gGPOAlW-N*O&a@siEnVirbp11?D_aR8G9Ddb9S\H0CVG'1c,i5I]Gf+-4\41i<T/Qofo$iE%7D+[V
|
|
||||||
@5P7h#,ed7In0H]S1fi3fbVc:<GO%H=CYqo@T,:*ePGUK((Zkc*&B),V;pPF7cKq^:1U:_B/)laGSZUD
|
|
||||||
qC-\tqMtBpc?fd#f.odi#3AQV]T=<0fAgQbc1EqS(Sn5;*L+Ad.Lj+h>,Z-b:Oi7hnRc1`MdllS!uL$^
|
|
||||||
,$p(pVg5kEPdm[5]B<'&0'$10i(9I)O4$W1`@oX!BX^fub11.tW6hsa`ISs`8GPnh!NFo#'jk>8c:qNS
|
|
||||||
a87q\1>8^?;(DOXGL.Me*W/[lh#d97d7)qG`QtG1_>&9^b2gLhas\e(Zold(HrXaBqbSVKhA9`]f:s,O
|
|
||||||
>4n$FG)uCVWuY5"HScQgbiE!^e[\M/UJqrbIbOd=<tnF]j]qIDhI#8_SS]IY$e3o?3YZTYW)RFbjSJDB
|
|
||||||
mt9Uk?HQLSqj.pX=kWVX.hW1Q[ei^<,A6]j-q43l(tWTbRD,EOTOR#?)X$0e^=hCh`L#1VROI(?7ltMl
|
|
||||||
c?bAO,%j5*9%)VV&?S0A<EEtjm*T^t+]7uWhL;[]$KbR)>#\*P>_P\]aMP!*+VM,<=".\i``o%%>O^5S
|
|
||||||
D/?0.\iDFgQBa(:'dA#opE<4j2Y/LoE#[qSOr)mGDJ%PmN$\LUIGJB2GF["5:Q.$)'Kn7AQD`8mZI`f5
|
|
||||||
CQ!+Her.s<kY>=K-nX\hCG*PmR:agQ.VC"]K'$,,<aD?4lZLme/)b1oi#$HJlLb6lO#%Qsg_70WJ[fYH
|
|
||||||
LM34s4A-2*6g7+j<B:@^lsg,VTIKLJr@n.tA'Un%hu66NQY3rrC,Rg(MM2Ht7^"SX6ndiR`_V.(o)aVU
|
|
||||||
r6k14*;"TaPSaL03ZWFj#NN8gZ!RgM4gt9KA&!V"!jl=N$2iGZi-`94.l^]".mVdKrQ;s]?TBjSfWX3k
|
|
||||||
p"6Gf*;^K+r2oo)j-@>c&_;D"[0NH[l.W?!G4jLk0[@c<$7!F9Ve&*d.q`u".b/EQnSJ6=?bp"N1$AMh
|
|
||||||
Y6\jW,$]AZ_RT:iC=i+s^IjEKe<aFDa*n3db'PQ]fLlgtr7J1mm+/pm#kfbZraB%g4n^DC]]6KD@uqO!
|
|
||||||
^\97Q3-H=:3*5=L_A4E9YP#NOa"M)tR$</&2bT)lX?.cKA&%Tu5@nBF$Gf:%[4^!TY=hm-.RdL\]e7iT
|
|
||||||
Y=j].`r+A9Y=l:\lOmZ_YqB&0'Uc-C>jE#bY=l8df;2ZClVai\Xhjl&f[ltRmWF@Lj184eF'[dm'`/mE
|
|
||||||
\jY73:Y'N,/1DGbhs4/TYEt(+C0fhHq7:[oh`l>=Wr7q+<pf9B;o*t^a4n_2B"PEq:+'4=W*+9^n[J!I
|
|
||||||
g&6_K@L"L_mltn,$@b&u[j;FSs)-o87gk3rB8nXHDguQ,s6B-V`u&GH=5.DSEUr_AHjT+SP;&H]<VQGA
|
|
||||||
YD:mL^?_C4c>$$-c3_:].;#up"lf+]F't&8&Ds)BZG:M@HBMLPGmi'i\]?&RfT&NkrGr.a\po3Z+$J=J
|
|
||||||
-'&]UZS;asok9m"knea,YhLkss+3>Fp]k`%^]'YaYi@M2P*LZ+_L%A5$t_;\@;Y+eM9C%]Gr9fZ:cY7@
|
|
||||||
FR%G`?1ZKf'[ukh9nrAI51gb+q>-0"%.@-(UNE(K45ru]NfBe0AP^>WTQWEkhtlVr]&T$L8%ft@:k+$M
|
|
||||||
[PJhe@\g27^@[QOd!8QWW7:4c5Z+c?3K!!IS^loqihc9,JtITG7PR3adDiF$:*5g$JlK[)<V1P;E<N6I
|
|
||||||
EHYBIVA#9b?^d.8G,mWloi:MbH6abn$h#kqrNb0p:29OMJ!)Ph1XO0tHIV?%)"kB0rYUVaV&61\#s(Jd
|
|
||||||
TT[#]$DL$$qL[^m667m,//`.;T`#&+N3N:7=>J@m:q:,6g[uVNRFC8>a\91G]k""p2(t,M'9F3#be0<]
|
|
||||||
PULCh4I=9^ctg=*VK_RIOdS"il7sT[)&.hLn<]@W[d,]dY>Sub,>&.:lS?B*W"^JQb`E;e1SbXpL(pH-
|
|
||||||
4Qah!YhR!YO;VJGkilT"mX9M4mu+Y^\.@d#DTPe>MI)slHAnN)-r#l=$ImMSmWidFP#UuTV3o?3'JVT^
|
|
||||||
^EiQZ?0?'RQ(6/A;(C4*QPkfQV&61\#s(JdTT[#]$DL$$qL[_8!?4OZ[o%O*DMGnHqb"j(mo]6i/WKmY
|
|
||||||
R2!><*-P=p8/8D=_#!M%orf.]8%#9Z$JTu/<t"IVqKO'X6_>q(`c9$n?3aQif<RQ;\b*f^k6N4f/0l78
|
|
||||||
8g^70Y$KRhS@&f's-cA@B>StNdW8ZEm/jO5nD.4)B04j'#Bc<&NGp/W'=P6WqIMUd=m\8"n>h=k:4LZd
|
|
||||||
DPmon?_GkTN(0W\Bt(F#U-m8:"[Z2RQA[:$G-f[ehjtEAE3'pTm;gqOQHD5FADa)0.AD$?p8QX'^0DIK
|
|
||||||
gF.fR6>E8fOcCh12sbi?)dB_]Q9K?YbGq)M>UH=0=!4D"Z/n+3Qd*h'>J&en],Sl+8o"d?]L(5rN*g<G
|
|
||||||
5Vedp(9BIWnSl3X3!F[`=.IldN.S:@9)j/,\&nG<pl_2R4WJBSS](+&nptWFm(/X<a9<7.WLXP\.+9dc
|
|
||||||
Nd?<OjME]WB`Z98@Ycp?/$-A!D,tj$0p%DW&"gEBnR_Zidn<b_X'llLS2kA/fn%l+LY!U1+Br,'PK4@)
|
|
||||||
1qmobmIR!7l&&Iq$PU&p_QL)p_Ct\6OS:BL($)rhi&k)7(!-1MXPh@h%a/U_*k[%\md9b"])uR`A``;;
|
|
||||||
b?JRm4d*>m-\;Ie!!GS1e$Co5G,s#4T(R5\530`WBM4X*F^<.Bs5<:`FmH]bJo]7"82%*5@7)u,CerM4
|
|
||||||
cq<'I4MWP*=_S*e<g@[[O<GOW,j/[GL[1!XS22RY3p3S`<T'3P>pB8+-p;i7otn)U*"U\#gu98.qe7?`
|
|
||||||
;onA^\Z(>m?MMr4;O+)Y0:BG^VUUJrk?+;9hD+sl*09Fh8?K^ngUSeglNCBbNaXnq"0WX/gt,4EN@.eU
|
|
||||||
A$0,,3i0WpkgMTDiS<3+\4iP/c!+>\%::i-0".tmEL"o<JfYj?`OWm6Bhppi9UN2=&@_4OY\WN`UlN3p
|
|
||||||
Z@'U^moVM]*Hu%+1=U)Yh*P:@6^9Gg\YIHs?u*;R)FQ[6=7i7<j+#uhJa9j.?arENCad[T`Oc@ul=P!4
|
|
||||||
Fs,`u<f5:kgi&@P#NGOFg$TWS`nqobIUT.H5Q?a"1Zgma"0uPUXsgcU2Oq&(N)\DB*3#NZa-SC>s)D8c
|
|
||||||
hrqM5A(Ss(r&YBmC8H$S2gaTIcA1`168!ScW/Y1mXD5hFN?DBA_ceEh.O#6&\^#>V4.c3nH)l,$(G[m_
|
|
||||||
B!sNZD4HS@I^3Fq^M`'KQJI-nXtT@>Trn8AT.T+\\8)MpbO\nEJ^iSmQrO)sKD49\gJjlm^Ifu$O%@)7
|
|
||||||
ID9ZW6R)Hdf/9W8I#0r2@S!P+^G\2cX&tu5V'0T9"c<ltDa)?Og0/1[?J!O&eW.6phBV<_TX\d?]r2%4
|
|
||||||
\SNUZs3a`8kIo>2JVJ[):sRpm5f"V\(leQY,PCec<mXo>-1?spn>7ieDQ2'nla`Y'^<W#bi:l.(ofQ8q
|
|
||||||
@,sm;PfX:d*OKkL[l\Y3)t$rc`aa0(?.PepUlQ<IWc.X)=fWQnP>eK@@eoEnUG/4[F0CF(M=QrJm"H4c
|
|
||||||
A3M:&9#GN8#BmWj^OO:p?=s(XOlrO5B7m.hf@")=C.qng.q?M1/dt%\BkE;b#9-s\/E59ma[g.Hh3d_K
|
|
||||||
L&S>@[?$\P`"bGuQSHA>o/4Z$6u2`@>Z`"`<eNo$Ja%eWGQ=/7X[qAYLjMN_?VI!6dtYYIj:3t!*)&Yl
|
|
||||||
06jCSMnKkE9^S2/%C5;T$[psiU*,_s@G;q>mK+uL834h/-HFc?`%R5j/)I=?deG;0$$qXZe?&D2Lh"Wi
|
|
||||||
ZuVrUk9!HS(S]aG<5MqZT]!h]f'0fRcP'o6+44<%&_i(fi$Ve*p^ZbKE+`#[S?87aL/R^!8_3pq*-&'.
|
|
||||||
7B7H+nm7O5LBWOLd=4\>41nMY^HFZoa**6>*#/#\7P;gX-.Jj-G@XhsqW^?%54;A+o[:%[6l]EGVmT^;
|
|
||||||
%GB$p7@+G1G-&&97\Yq^Atbo%5G%dIs!bCnkV9^oNO">lI(YHL@1lGs^%,%^/adVFaAD!#(&>kMZ6sHO
|
|
||||||
>BtQBZaK+2d%j`K=jWHsb>%_0-ToRDpOn?'F[XPrhl!l&-M>Oh8N@Jh4XmCq@q[X:V_V-Ro):ZS_;^e%
|
|
||||||
4)KIce#_l9".$[qa=2A_pH`d)XrmR&/]X(>2R0hLOR7Ssf6.AHFJGqs7)\F8j1]M0N'MX`Z]-d_,MpV5
|
|
||||||
:85tjc+`i/4jrV7MW]%)dcXb9gK0R:P!t&LH`MC_?Y,?KmuaC'&7LU=J'!i<]g2mOeZ$J.3<a?tdbi`_
|
|
||||||
H7U->rLIoAiGG,LncE@MX!=1TT_jiMoL0013F$S'V7t"cqNCYKYs-[KiK"kdQT,5?-2&(!PHY0qD@[Sh
|
|
||||||
aR5?)_R>-DDVdWr;uYIYJ)`7#QP5`M`_K0O@*6ked=P\:g?P""[:;T^*Qh(LZ-l6!>3p]ATgllk4L<\a
|
|
||||||
b9:$lVs]OFgYZa`*=\Ug4$M]`M9n=Jj/^K"Q"L/.TCMI,pPc:rN\iqQ5fX.l1>s,n]f+^c1R*.TA(F>4
|
|
||||||
c^][4\n.us1mi@-lrchB52d>SPD4A#UG/Tir;I]qk&83V6UO("cW;U:P_g`4P*Uf]WKVf^:)qj(T`oK1
|
|
||||||
n1TAgU:%k)j.,#J2oSR!GS2sn`&4>JA30^phQR'Lndohe^md!9%*.M4#K_JSTR`X6Kq#%Gim*rKBjjS=
|
|
||||||
G#6+0T!aJ#@lOpV,%K&9j-VE@;S"9n$\-PrT9Ch08W?GM:HraUH8,4`5&/9X<Xs.Qk8:gupa(T^^N\ZK
|
|
||||||
NI>cH8"TYkdT?AVfrWSG.n@aC3?>!^(H@\I-bq/jEN&c)0?g)W6eb-#F7>h]HrE4QM_R$;RSuOPe84Xp
|
|
||||||
UIhI2d!mlBR[nS)#?R^M.?,,3`?B)G<BQrr#s#sFUNUkZE^;b^HemE(=)IU,_g=eBk5>_uVpT`>8*[i'
|
|
||||||
*TFc,'#3]GA<.f(-,mikDOJahgD,pW&[KWXXD55gAuA?hbA>$qka%X3i'h'XB)YG,AlI9U>@nP80\jC2
|
|
||||||
,<1r9#dX?,iTB\o!a=iZnR$1C$bC]]LZW*><L+$jW;C;\32U7F6h6OlhWpJq^+.<"4`UndoR0Qk/[NQT
|
|
||||||
*j,F`4fW=YPsSFGq*QW?Y;"VPopm<LJ`Gk<V@keMhAcBhYKFRWO0lXa^"V_AcKV]ZdU0cMH0KT-pCR2d
|
|
||||||
XH\k6Oe\SZE;+"A(OM/9"s*>9;+Wok$JtCY`OEhs9BR[ABZ:d^J)IO6*7NBH2YC4*(0[3bDFWtGG_d^n
|
|
||||||
Mt,GG=?:6WN$Bm)Fehih^UVLl"IACC[fe8[H\SB'E$QDZ0BAFLn/2aNq2Rhgq=!4G>6:k;(@Lfp]A@&$
|
|
||||||
CVTMu3gOU$LbHHN-DcRb]sTJ/Pao)oQ?h!t#@s.%Hfc\-,?p3jeq&)Y*qN#hVKdSEDD1M/=kqSgkJEAc
|
|
||||||
n..=,`I&F/BMaSJs7^K7<Tkme/`b5IBo=4,2A/)@/&s^b4k?pBPC'2?m)5=UED,`9fG.`=e9i:aX.-&!
|
|
||||||
;5qX/SqrfTRl'%!%bEAE@kZI+G"Q1lo<Zcsg`=@;hh15tX[rHVCOJ/15M,b/B1-N`=)p)mpDbV$7::?e
|
|
||||||
n]MM&c"<23=?J[H1:)c0+CC4C2V`&&UZA!?f'PXPq-GRQhe3f8/]8lf?^Z3:T):qnZ@02]Zd'aQV%q8a
|
|
||||||
ZjT5)*^a,QbpgBPmRJW3dLU\F=6YlbO)$BY^N2QQm$&Tl:^]JKG?+t)QDE"W.c0'd\,PE0`VdLH84CQO
|
|
||||||
-/^AG^TgGN^cGWNPgGMLn/(O<(!SBnLJ()2Cd[WY8,)A9^>K#uj0T<*GE[HOF8%;dBXeP=MM?.oW>J0(
|
|
||||||
dIA)MGk)p.V-s6#*D7k#DTek#HF@'.>-IDN!617/.b]\)1OGjOr8M.[cG;ul=^3GFQb(Zk(#cRg/eqkc
|
|
||||||
+VgX@bHhDUhkAeRJUN3>^@YdBZ@YS/s,NE`BGX-WJYAt-E!*&f1B`boiOR`A>C!_KXUr`]19)G7VdD0"
|
|
||||||
rd<XQc0WT41)ZK%$oNL#B6:\p0A+j(Y$@q;*.\mmd_IU:_HD<-5sIO4Ng!Mj@m7+Dm37M_^D<X_\?Wc\
|
|
||||||
iCY/=R6RaFOH6Rp9-$XH_/lpaEtmr]>&3IL'2Zu7m+E#c.Vm;C!fcC0HBn2(3#O4!Xb56B>QqrN=HVnl
|
|
||||||
eJ#HsSV<<prlr:Rb22/-9$+GmEIqshb0oM^B[J$ARD/?$nsQC@V30Ak\^7:NhqC+>SR15G5Cqe<4j]+$
|
|
||||||
<Z/V]HbDGdB)SM5YCSWHO(P;q<aM&[+2/*JK3OJ6pC&S*qL;6A8FN'ECNe$`afU`>XUifuKNpD\Y:7cd
|
|
||||||
E+H(%qSo5`G>bC)M5SJA]A^5irn+=*i+U8RqTF%2+43GhG=1DrlYU`TT@QW8+djcfFO8,6qdafBm:'_A
|
|
||||||
0/R1BGG4@c41R_JHrK4b[1UNi:T7//rCD_]Z9C+jlKY0(&acDmI,E]#0A2;n5t?[YA`+&gThIV)kX2-0
|
|
||||||
,d3J-MTb$8DNpI9TDR`ef#8A<-hBd)*0iDnkhfW+@*OC,bkerhas"IQeQTM`$6fe8pRCIadUc&Y-#fi!
|
|
||||||
,4b)_042J4#sE4A]JPGdb)4Q<QSakdK)"(T>jBrhnG.8Jci:"UoY7L.ojYj(XT53ts,[2s)48*a(I]g\
|
|
||||||
)h0r@79ImNj*:RlX8qqfp[,.65aaS0AJNLdQjrYSRu9>,`eF6]c%-i/nN8">XLP&(U@8@C4T!qhrM2(f
|
|
||||||
RQ:;?):oQ4B&LEEW5GgmOg+Dn0WNIj(0A4fI".bce;2>V5$%MNKU&1aI$qDP:(R_d)Njja-@97_UV?j_
|
|
||||||
:;`X:1GdDl(=].Z,"+Y"DPn5g4n,<U&)D"=gK/]fs3<Of<`=O_52.9X_,ss9.8Q@AqRcCCSEOnPHd.d*
|
|
||||||
nug\J1N($SO"qrTe_dHs.R"cYC8;`TI[ps/];LTeb-6MgJiPP\OS$eS[j_D(Qq6l3N"pZ%NBA*30C^(.
|
|
||||||
mT?3m*LLBW`*!U,O?I-RiW=,$A"H`"F$oY60LS*;B!JUCr'@Kk*@8mLb_3*!TV/c5Vrs>[9T@n8^,K&(
|
|
||||||
\FZmNqAn(\K_SU:Dt7SnJb'^seODa]Q8%Nq<O4<(-V2#1=Ia1iq:h[CD8V\I1Md7b`/14Dag[brb-?@'
|
|
||||||
M!HTW\-WRn,i<Kddu!8sn200'9kG%n/iD803h/*\;6!e/gZ*Kp..!/1_1kE?8F]%`mb8t;0cuZejbC1+
|
|
||||||
LHLHe1?rNt2RWZ9@N8>eb]4Jo)3U7#akSWBM=K:8ZCVorpHjM5OPI9Pn^o=-N!$6DTX8UD4f6kN`hfHP
|
|
||||||
CVX%TN3obKp`+`oEq,5%3X&;T?KVWT[qP&Q>)0R>qSANZiXcRlSO&>5jB%9_PEV)dh\?_ubclu&\U*3.
|
|
||||||
mk-O;kK::o/%7V"N@1Pn@D"Ok?0UuG]/2rc^1^eWh4tDlnJP5@\ELf^Ua]H3GlAC*AbWZPd(_BR)k=?^
|
|
||||||
"(@r8YBa@OhFrlT-Aum^(-Ef@1D!?iX^KZZ://"CM3;>1'2;`H&AHl.\gK9<)`G8Q<8[q6agDtF;!2-O
|
|
||||||
^l?$@^:jj4i%qbLA#j2hLDR*&`!M2mR4\_Z^$+jcMsJ+H^<i`N-naJf[s%WCouk1:ET@KR%YM5XlYg)c
|
|
||||||
$E`d,18h[.=aU+,6]5l;(qnsShS8fFO4N4CkDkjeGq,"f6PqE$[5QIq6iPPQ2CXPu]'RPDDgKEZ9Y4k!
|
|
||||||
.r7..2S:ckMi?d<mF%FB2bC53._VZHpa*)J#gp]D"!cAq18E3gbB9WU8d:dH7#qnGBnCQi6J9&cGrQhY
|
|
||||||
hrr!Ll/F4hG5/\-b8'iUpkE,m&qKuA7G3jhkppf#_ja?!1m1<gW21:nTD?R7^&44]G#iYLC7SJPedHfL
|
|
||||||
8%U&!``=22/+@\!bt:.?m1Wn4WQHS.S,ludUr1.h$Qh=r/+plYLU_4l(Su\tD`m1rB?!`bd_o],:i-4j
|
|
||||||
TA`+p0S_;")R0$/J.Ol^d(@[4q*m/[:U_lrE%a(XTndk)R7,]?if]%4&BETdru63h(PVJ.fUA#E]c0T3
|
|
||||||
[C/n^QUK<HU)/f)Ob/ul@kE`"=m'HR\_4o+lE#2B]Eu+M:NUP.@Rm&<b`a%2"UOCcK*U_q_@');>j3ib
|
|
||||||
Ph>adcJh)ZpHl@M<hU\in!e=>lD<M1K2QAOidACq.)ubCZb/-d)4od.3LK'(l$Whr8F#U]B-b,KFlt[V
|
|
||||||
!AcFC&'u5QZ2;2:Bs)ROQO8_:@pJYjNbk@]Pm#5$.CVsaA6DX>D3,G*6jT-$PS9f8![B@WTd8r3o*g/4
|
|
||||||
Tt2UuD3m;>&eEZ'rKXhD<F_S$/%8Bdn@bB5[a54]AES2UUEhFe@>d8OkopP6jYu\+`DZMD=[%91klN-=
|
|
||||||
HgM2F5rJ=3dXA\0eLcL>p=@XuL\>HC6co<kSM%43#1#p:XEgEX.BDok/)MkX.4:1"a%TLrDq_EO,4@7e
|
|
||||||
,Nt[tK4C$,Tg-"]1H'`@dmqi?)J2hlLmLZ]1#9i)e7iL?HDB9gXB&*V>[_9n8L6dc5rq_I)jH5igS$JP
|
|
||||||
R52=2%uDMm:T`-i.;f1=>U3>5$E;BhGsPn13Y<D._GcUa%8j2C]TF@!L$(X[(SgY)Sm8+_\WlH&@a,iN
|
|
||||||
f12d.BO8Msfh(o7Q>R]2htt9gSc*oUhS6hJr:>#1jo:;f/,u*_5:US9gG!CB38Sq[3\fq\rk`C>VUrTB
|
|
||||||
%<^qF(,<OkWW6Q"0?@m8%Aa?*rBHJ@kAS\."M:JHs/?rW^jYHj@BPndrti\WjnZ_$8c6n"jT<Ve`4E["
|
|
||||||
.[R[F,'cUPefFMYs*TQs9k?1?@PWH4fGUZ4Kte/dAm$%l!U2\#Dl*NtnC:e=4R_0Sa84YBs']_VIeMuf
|
|
||||||
SY(kA8#=YkK,YGXH,=s_ESU&D:QNYo1jicu;9ls/p0GVUG@&4U2mME=/.!"8?L6P_h]j\2n;@lZhFikM
|
|
||||||
4*DOmc4N81q*O13OuIc[p[>fUe"aoV"I<9IeoTcc9O3D?XDGfLfaYo^%)%ma+`RY1M`0%XfXkGkIQ*kL
|
|
||||||
D.JP]A1-PE\Kc;c@!f6bDnWlr)m^H%X'j7_@pseSp0QU6AACj2bmG'"1Ee_^^GLsRakr#)lbp-[pW5'#
|
|
||||||
V&mH/=1l:7p+.5IhTEbioX9SdGRr[>.`#n.mp4`/'TR:_Jh*qF)7Z.#&\[FuHi:3.%<>S*r!rDbQqhQ`
|
|
||||||
qq<Q=V4+(-^NC!WLgqH,CS<Bk!BWasNO?#eM&%Sh@MqtY^-GMHd%RpY<)1pr_*,cpX7]rj`E-p%'Baa>
|
|
||||||
hXp(2'b\3`eq'#UL)Y[RCg50K9jUHR,<-IJ31p+c<l,p.q2u`dfK"EM4Am.`Qt8c-5Btm'*'\\76l\Fq
|
|
||||||
j`R.9n6mAun,$8h]<?J.X-]A9hh0rNHsb1hSN-qnnW%u$Ln3S<]Op7"*^2#NQ!\Zon,)*ap%NDf7h`"m
|
|
||||||
]+f9Jl#8VR<-"!H"eF[Fho"uES`B,%'c]I%E"L'N_B(bYO6lG_3.>O::S&X"I0@U*`k"o\T<oW\nU:17
|
|
||||||
amm(9A;qSd40;7u4OHT9@<+J[koA%D*ffDbS`ABJk@sYaE'-W(XVkd'icnc`4WH\2at/QsN[;_1pmW7a
|
|
||||||
Xb69`q&[#5*m:f3M\e'[L!AFagLBg.AoY/nbKttp.Ug5A,@m<'MOmPlceoE5'(\<c]f8Hsm'%$3:-'$a
|
|
||||||
!8@O#8c9BM4_k')-!GEp6,-rEY4Gs(9DV^mJCJr,aSSUmkSDW#1",1P4`oU`SP&6G;"1@NKAqbKgq9j@
|
|
||||||
D"!+-6_)#bKt;2C0==r]cG",\_2;h6Eo1nZNBfImb<s$$ar"4(i`_30LL8R\W-T.n7hflSG082J/A(,\
|
|
||||||
*-A4$HUk!KKjV5!Tc-(7'H@2@UZ!kSUjVX)"2F,#@ZR%2&,Ln;9C$0ZrlJ1VP`8$Y6_3[pm7Ofu4lNRV
|
|
||||||
[qHV%,;-4heS#]g9<7mb-6FeqKCRO/ftftiAf>=Z$G9#Y+[FoG[RF/:?>W+m;MBJVA'$<"oni']A5p7H
|
|
||||||
;-bp>)fC>R+qC9b:nP"IEBn7`>1=lPFtcm],LW<ie&knC)f!<i3;Ac_B+*m55oM<2Q=C3&ONfm_A-0er
|
|
||||||
&gcrZ?fMNQnKh(=j'sWON,LHnS]3UtcaAbJE^o_;Huh;"cml`PnL8NVN0!.9(n[pt5q>;Q;-k(]T48th
|
|
||||||
m-`K8Z#NCVq##!tGVXKUod(1dp9ToLc-D]$#[B<:9U5N[1#a&jCXg^T--'Q"M1:qMd5aEg57QdIUaj0[
|
|
||||||
Z.A93dIYD"]jV*4*2h?>gGkeK&$a``kPpAL"o/"64;g.g\8sMFJ%^qmD%t[BksL>E`cI"]0==s/R!5Cp
|
|
||||||
FGfF.;e1+;"Bo,Y;"%.KEuEgSFa6QD/I@-3lU"+m3R7!Mi4/js-+t*M'G>*F>S.!\lB+_Hp@lX?N[@pI
|
|
||||||
r/fj:&fpH_dNOh:385,N&J[P<T)%@SG4'",/7W/Q`sTuA4k^>i`_Zt6OG\t/1+PgjO+&o1-6N2B54?s(
|
|
||||||
3'6tJVVN?_c+`c=<Uk`gU_*aKKb9?F?VVs$d<e0/mmEoel_L"ZMp,XAIuf?EBVMo^9;l34T,$h^ER)0\
|
|
||||||
JthPX9j_G+:C/mVgbJsA9B(Y%l]*k.ct/?P0Id^`1';9/R0gj!!jgH<Ijl=.mgTXs?^6&9*m++RJ&=$g
|
|
||||||
iMpPDg-4!YbP'U1n$TgpkH"P;>s0AmrC+_G>%[W-/2P!6VF)$A8>Rf)Ob+Bt*u(&QL?H&+p_%BQo]!1#
|
|
||||||
?5PP^BWhDYf*=Y:#`)mol'J]<o\SN0TFC0;ognC1.,M,[kuTBJP*Qu([Tn3lad]Z]m,YI+A`bDlcD":6
|
|
||||||
G2j[Rb]I@WU'!F%WJ(*U6,AJnUj2=%dmQ>Vim<W;Cj!PMEEE8Y:,_+eZjNn[G06b*ig'R`F%ZK]\ZUFU
|
|
||||||
nelT]Sji%+BLSat=opA<CFe^R<.^d>"-hTh&IM/RE/Y#:M;E79\TljjQ6cN)q,fPi-;1#9f<ePu@nb6r
|
|
||||||
(i__qP92R_lAVbYrolSU*a//McPV.5+!sJ$).bDQa8Btt/\iDA.B#dWn?*bY(<$c\gj[@iT]SEG+A-7j
|
|
||||||
E(M[QCaL^orlC^PaXTDWI%?t=IYN=SLi'63@Q:)T?`<3>=PFUYbs$(U$9+Ql`RBfeYP@r7K1L1Dn34+"
|
|
||||||
WQ4D-B%5hNep?l:NE4Pc$7UO`8s]t.9;@,m6*2K8omMq-q?RTYjjPlZEIl!=nb%ZL6hZV?+$<Kmi?4+s
|
|
||||||
?EML[6PLgOeU;+e'aC+L?fU2,aZnc58ng9?Ir9';GZ&N1cp_sB%TrU_'G$U.MY[AP;"C_10YVtCqT9c:
|
|
||||||
bKJlSb2A(X4H(=Ed(6_[(fad<,s=RE!aej"3hP4&P)=f6'l-\(+;J\>qUCt5W;,EfW%mF'P]*PBC.@(&
|
|
||||||
3]o#0MiUt<Tkn]YUgbM181GBI]o\-59a[3^fm_mQ$C3V"=e%,k'j9M:V.+Esh<O1;V'7E;\Q!*>-0N^F
|
|
||||||
5HilCBN;7e.bP60a@\9Z.b:gd5h@?sK2fMHQTSMM&a3diAkGMN(h(T`4WlkTlR\WW@[nM&-Q\Z,,mfkn
|
|
||||||
O\R;]asB+Z'!#nA;6-*tB\noWA[gsul.=1Jq`2!*8SWfhm%g"H98G>3rc;JhDt*-RE]-a&S>l!@rr/fT
|
|
||||||
kbmV$EMK0+3?MLiD+S)0>3!JXVe7YF`IlA1f;;1GUN_XQM/#Dj!XcDVg\$l=pm*tCh,:..XhZP']3ma*
|
|
||||||
[3E*%UfM')PQt[2We:%TX4Co0UASC&F8HFnE*rMmT:P7#D8T))C54TMgS2lA/kUH]PU1.r\d\+38]!qL
|
|
||||||
c0Fh^Pme*R<.ZaJ[3`/E]<J.+P08aH17]pOkoKaroJbZR'r*jmDLYiT2,XbLfhTG2]skMJ^5;X.-Fsoa
|
|
||||||
d-9Q%V4ombhf$PZ.\j>E(Tcrj4bS!>reIYce88:QpsdEEcoAX`nKn#c82]glpB1)]+`Z?\5uID1n/e0X
|
|
||||||
b]<L50\/,VgJGTIm<(=(1pf_SYE1k/)7g`.8X-KSYZKX9/AL+R/#@!ocr)_^a1aHh<a!sS\@-VUH'Nh-
|
|
||||||
B?a$5Mr-lL1m]anPYc/q&FVuq'W7XHNG(;4rj_(5^UqCm(B#Ebf/[3d..?0'4sA7.G<u@`Dh%a,a7Fe'
|
|
||||||
LJO9/np>K+TDnk?r7C6TQ:2id;DKM/SeqBLC]RPSI/Y/,o7,pD^;%F$Is$N"-bF9eTeKm')%2muq"rti
|
|
||||||
fOY"D7Ugp:Cn19\n)sM[GHC8$(0fEGSK@1r3URrV6d`gMJDm!-g?89[i2]Af^V;J>UoA"9eHiUF5fY3;
|
|
||||||
D[cO%HJ\-Sir/f34W]P9e'A^Qb\9:2i-5LHIc:e]S@K7cN9D;6^jQ,%HOnf%GPpYZC'u\0Oe77>n(59m
|
|
||||||
rnN!TS)WoW,M)Agb,5<*4&KDl1:4c(-*gWh<Cg^Z`46_dXouD@<q7:XRj!O;+r9d(]>"-12k\#e*U06:
|
|
||||||
]4liBGfFCp'-%_0D\NpU(c?825K@B^#1+,`=L.c@Z`uK!XtSP]B@P]P'!:HLRQh[o=rNNT\+M<J'_1HC
|
|
||||||
K2.DAI_Rl&'siFk"[?pm4Aja,\FY1[%N8a4qAWMd]l[N#"blPQa<?7"OF?O(a,,nj(Ete8G8k:Wn8*EX
|
|
||||||
-FK<@P>QgT0k(kSfigBF*4,08pqd,5hED6nk'6hR5!6.N'VdX[0X!$p(F,[f1bLIU,5ua;*u!7qg$1KT
|
|
||||||
ic>,W*JmhaC9=+#^L)dfNM:'dqI'1:T0_&"IEX?i!nDb')&EbieF]PmE6PhrB"W.r1um-,mk_[8Mk"db
|
|
||||||
*TqNoVT4Y!8*a&'B7+HK2</7sehc?bko2E2'Jh@h3-]-04`S]MSq"d8j7!YN)/%9C`g>QD1VP](SFNHO
|
|
||||||
kF?E],L(I-RU`_UoJt3Uf.:ZM`iH9Ybd_7]1OdZRAt35.g/0nKM?;(g')*CkC`eJLp)PsUnon"RT`Qn.
|
|
||||||
O\s!tYpmdVd<(%Sk@_M4V_j(o9llkgXghm8_Flr,XPI-JhN[QRrQ0sQ\8)`p=)PbPLo55M3SlSi"kULF
|
|
||||||
8Y,(a'^b%FbijOID2n4Oii)@)Y6\!,F?Ni>It/n^FZi_7C<k)@-jpU$`9gsFQRb\>Z8Tg]SN%Q3cdCW8
|
|
||||||
)I'mYQckI^2"9AqTOC."@crgST@/@A`)02-6'XK=Y/&T_DSE/HdZcd9(%2l_G7:pCZeIYgaK!CQiCu>7
|
|
||||||
rUJ)!=s2Xc.C:%/',=UI#`c&Bg8k8'K.Y8m6_3$L`\*_Tg[QRInGGA.kPsf_`W+P<cK=6E`Ou%3J&lsf
|
|
||||||
r#L"gqt':/TDq"I3IcP9rbA'8OlLU>Dr>N$7BZ5c'Cg9S?<=lq/pJ:\W>7k5!8/Zm.7p`o.dS(^fX_E0
|
|
||||||
^K:bZ=/;i-qEfkI,oZC+C-jt1fl/\0]GFeBpaV6ek`J^)NAnmj4M9nMNaL,AEFT-')sZdT75Ru>BjVNt
|
|
||||||
@/00@JO`"um96au-KR%t`U!adqNaomid:@AoR(T1HnW77),cM8F^!kPq*X4m76p0JZB:c3F^s!3\S&K.
|
|
||||||
*'qFWbABh\VW,<%:^frP$GZ.7k`i4/.hc?*JJrHa#-Np`f.ZC4nFpP025%-b7`VQ"(d[dQW=P'[EPr7:
|
|
||||||
dt3>odt,IXS"L)#=X2XLYM0)D1i6je(UNo/Un0=7h?q?JifX"$0g]<Q9`,Ma'CW&r,L.e.eV6)mplSUe
|
|
||||||
1(_Clr?_;tnIU2bNR)NeoQ8?_@sC^b`4RQ<\0L$qn7HrB6eUIl1KRmo/r.]GSq_)_3uim?`HmV`SmOIZ
|
|
||||||
O8($,h8FXr4jie""Q2b11rnmbcgbLm*&6G$Y;t_u@ZEO5^A/aq^Q9%NZf%=H_+8KN;tZ^rn!D;/UDT[Y
|
|
||||||
QmR:8(_s4JH;p2[[55Oe4gK_Vjc$DoL=A`GFm$lOcah'mN\;GM&i8ZXr>sMAUi83Ng6r%[2:2'R/Cd
|
|
||||||
H4Qed>0OfrW,mY8c<jI3H!E%<'LjVDg#5XC2\3,n1[d'a-3F%^<kD6n&aUU<n@nOK/I&:4A`Z=?=[QJ"
|
|
||||||
R.W"P8S-Cj@LmIuDeQBb2Hu-;MNX`R-!&mi5b/1^N!+g!W-mld\`X;q(*P7m"Q)J;iV+E\9BD!h8Ja!K
|
|
||||||
nlr+T#,sB'hlc)sGmNhGm*H1d;&6b*(oSD=XW>fSL.p`3?8+s..Cr'6?j//\D/gjT1D_3cGS`endee`r
|
|
||||||
W>BMT@Zg,!%j'W-Y)iCLc"AW*IO*2oFgq,&2>^dM6uU=T#E5594V2:rIaa"uYtQLY(p84ZQLud_'&qr<
|
|
||||||
>\t8*4/@<%H+%\SnfYWVDXgAq)-Km\26Hn<*5Mp<%YlB3Gs?Dec42\+FP'/AQ-S\!G>gCs8d?f+9]5:+
|
|
||||||
FfiZo>1A'chs2\1p%#CK;dW]-dr9EmI9IHKjO-1=Bu5&B48F:<8.=RkI``JFO\W$ScMYTs`lWm&qY0s,
|
|
||||||
K.FlNVr]6N+!f]+OM09D50>!S]Jh&%\Tp[FX)QJl@^cK1h_sm2bRscl,aW`d3,2(3+;qt,Ai0#3`dNh=
|
|
||||||
bGR*J>^.t^#G0NCRCl["@a.YYBK.M\8Fgb+NpZ-Q/"2guc\f_.F0s4pZPc`Br+4loSU?Fl)FI6-i&I[a
|
|
||||||
h\3Nip'MIB:>P7TPk`BWBc*(b,-X7)>%caq$rMgjlSD_0&pP23TdTQ;cp+k)MB&q3I0><^khudHOJ?9e
|
|
||||||
XeGVK)F<>A+O!?PXOt7UB_aE[.We!$`APAe&^9;aDC%a;,\QG0PHmJ9=\shtJ:8H5"kWW:L)fj;D_Ra]
|
|
||||||
6Ru**b3Jq6]RP+LA[_[Uj[GWNCVXRspJ7n5apVE30k5q\++4&9kb>J?X_k"j#3<*A^,V7?GVb*jjB4Q8
|
|
||||||
fbOY$h(_9<4`SlO8q6g\=(]Q14@ja%f<\?'5s/AT2(!'7)\]>FZ`YVb5(`)g)s>='3;_)a8LouVF&9T;
|
|
||||||
6W`Y<9T/e,T\qln.kK>V\,#V5D)(#lgr^K,)Y&D0;O7;@DT&J,C1kB*K4?8WMD>q9_>!]NK,p7'jmKsL
|
|
||||||
ZYOMJ2]a,d(XqG;m]PN^V;10<-C;?GLZ+D=/ai:;mOfgep/^QK.<cSlRY9I%\@^dAGDCl[En;d2e4]3j
|
|
||||||
G.G3uAJO=p@_i5ih6kAJ/pGq0r))lc&'lkTX\0"BXJMOi-:!^K?(DncjmLQSU>0&6>KU5A/pA=7m]K;H
|
|
||||||
Q?%1@$N%DGFUjbM9ADuh?Agk`H$h3Q(soj".cW^NCgNI?48D(bjK<P0jmIbNGcOU5S'0WqI0:m@=nfhq
|
|
||||||
mQr>LOh5CMG=q,1<lguW&X5rVY">1`<gFg#?SXK`aPA^Yg7-l'K=%<,FcLXUqrg8raPa5>jXu/e?1)U4
|
|
||||||
!Qs4]BW8W!h8<g2a^)O'E?sl<>VA9j]=V$/X/$]rmV]b&eh*Fb>FfeY=5(X)$RFeRf<Iu"?(@+NfVOBg
|
|
||||||
s,LCQnd;YFh;(V+b$F?+]&`:FkCR_uGO?p$FdpM1K.$EOlpkh4+a9@ImDrQ@V5YV^I7>W/pRkgSEq_;<
|
|
||||||
J$6=_Yrg"$R`Bf#</"Cu=PKJ-s+IlUn*f"<8$u$\WON`J;'qVge?Kn*`"OMS^Tb6=KlQ\Q$_QVP]m"k:
|
|
||||||
(l/7P%V.8"=r8kW`S"pkA<E[GhcH'$_PtW#3qZ(A^A'RDb$$cM17nct:toGa,i`s?[HT2tWa/>I:e]Z]
|
|
||||||
BeX*V&37._K2bnu0TthC0<o/LpJ.:6pfAj'XOSQZe**uYd4lPXSY)TmK+?&++&q7!RSsbH2M8_r]>_RD
|
|
||||||
\Zt/Y`@/7XoA)iRq5EjYD^8,:*\PGIWrLM\(gX[URh0%#(J`lNA<s\@[Knkbd3:%Vc9#WN/?.!i2f[f)
|
|
||||||
/H&I*:@qlgG2oVOAE>u652#i!]4`9\ZqasUq9U;(H[V/%^/>Y5dd8E31L>GFrF_>S[u[HrO/lFpN0?//
|
|
||||||
+LZl#/L/5)phkZIgVR^Yj='e@"3IlpiNtf.Q^L^sH>iL*@XU-*<el!c(ltZqb*VlC+4p;5:T;6=*3$ib
|
|
||||||
'1]eMUOs/t(A%\M=&4SIKm74'1I>PQ0(I-)RiHaXI(/WGphjTSSSMITPj^M".,Sn"OFeJ@42.Ls:/`-2
|
|
||||||
GW%$&h^pP\W:p1[I*?bl`_a50D,G)E5>I)7fA'I:EQ3FMnD,:?m;B&c<9%^\lpK?NEJ@6G/J*8^o4oNF
|
|
||||||
DD,gM,Ri)SfS,,DRBo+NY!a62A*iEk"d0%sHu/__,"E:*bNQPA$K!FK`cX%]Y+MS'/_TB2igNkPQ]QU(
|
|
||||||
JJEHl&:m1*/J.#I&RAYI%8hA)>dOFe;U9<m>00:eWrQEeR27K=]SLAq7@LVo<qDHrGW&3lHfSXuAnd+:
|
|
||||||
eW)JWn&T\\XO4/2kTTVVpYWB=NNQTi?12(pnDX"APiNa`/@JIJ+fZ`J@Sqg;]8^17V#`uVqg"L>>=<Cc
|
|
||||||
hG(cOT9o!^rMM4.SJC=%hQ:k1^e>7GbBF=f:.2Hgb*!d$h@M.Xj!(#R]fN?57$M%Ahs2\)ojZ0;Y?6;K
|
|
||||||
V0,2pBZ4*<]6-0Eml%,1(fM]:OVjd5b88,LG#:-Gc3ufQFe7&8*55XMnD,9tP4jWBaRZ9YaG5;u93Ea%
|
|
||||||
BjH`5R9HcEHJ(E<n8ob(a0!dF!5pf>C5S.LmR%72+$r(!+%!VS7@\)'<utj2I9eUoeCSLhB74Qsf6V(W
|
|
||||||
.")@dlpK4FDiLnd;Rki=a$JNkon4:En+=fG's.-FNtBcABohDH?&t<_NtS#r)jDe"]sUEe6hW:lepRgs
|
|
||||||
+g$UP5(Z/[6[80_36KhkHjCP.qTkBV][^N*#Yf\D]IM&`oL6G`YcC]M?`j.obC"Ueb#!RQ^PR`Rh?3Re
|
|
||||||
YPp%m2bO5A-D)JoWO[PrG72>,9)LI)BjG''L8eCRMIF3\Dh%K0MHeR#+Wst^nf\!+8e&5o`46m6+%"m#
|
|
||||||
OgV@&Sle1/GlA`,LCi>q>1`*YHjJrD32o$\KQoW7mLHB5D1X,Q`I#"I%j@lV0"T3_cp(u/$>$4!lbp#6
|
|
||||||
o+s2$^=o2D4F[-c\26d[Yrm&L4PJu]I,P".oFr[#iTkjW6$5guE]:5`/E;#*@EDeEs16usSFDZQ21:5J
|
|
||||||
(/_*j]7$Anj=HlS/nm6h,1D[deb<F6]<t8Zc5ptm/pF@##a2gM/KW$i)EYS,&T<ifeTX@JWY^i1lCfJ=
|
|
||||||
/Op-u5515_2/+*SGKZ:GQJ?$k[*.:253'j<:qpJ8j30''.Ci[VkN0=p=3H`<27QF:_UnSdqS02a._]EZ
|
|
||||||
D>Utn[m<C'h<I)7P!>Q9UI`e4*_Yo*L>8nK>q\X\Q<We.iY!i*`Z]&IXtqkpLlU'Ypgs<7cHLkqP.Q[b
|
|
||||||
G.B,`%_6r\?]h'841.[bq@)75:d/p/--]8D.,-^;!t+KJ:k-^HE_W9Qh0.j3,A`FrAW3b7h)_rfK\R:L
|
|
||||||
\Qdl#+t6@/TAkqVHN"q?ggrm]bMDG]\gq[1hf:Zq`&QuZ)<V;k_1NQ+e1+'_mP`ZoU2+gVLYQaLcQIF9
|
|
||||||
aReN>`r,d*,'D565=WDZ>CVNboilkdrVD9K,<$88rkA>BUi'ap-bg[WSC6$f\\Qs>PW?9o`)g18lH[06
|
|
||||||
ot8$5NoTi&Aa`X@QGOWj4?62o<IYH:hL;g&Si0\!INp&:iN.5YY8(OroCUJp(-OZ;Q:dC.,K<V&N9nmD
|
|
||||||
bIJ`O1iJ$]`a'ciZ(h+),t>XR`_f5pd4gTGa&p)mN9ADkG_&_'r$lTi0hsVsJHYXILlnoOT2l\8;%,&&
|
|
||||||
]Y/?BW.\B)ockl@6c>X9B<IN88?aOho=N)mXjSV]3L^o.S0iH5k_8G9YfA!VbG"stgsSH0mQ.K'Mcn\8
|
|
||||||
>3"9[Mh6F%p9sHsL7L]#!9-//H6K2qA),"Y?_Z8VhQ,D,S=@Ve4NdF^=b$k,[-4]TkhSua$K)e:bX^L"
|
|
||||||
C@kh5GhJ#F^4W99O^.J!h`*R&]Z^6@ZC>0O,75//l6nj2O>U.T+3Yn`[@WbT37b!J'2Pcdg4AO.AC!.L
|
|
||||||
BMrVU@B>G#oub]a>PXgOX&Eh6Xtit%?cqB2%pDQN<mEteBPk>sZ@GF"1ukp3pLYt>m+$m6)cF\RSWWI)
|
|
||||||
#@W!fB-EWdfu![/MfmF+8k&L1#11`!>isLIX:@$7+L?1p#N^4USc_&DRRN9Nj:,7=DrY]5jn)m6D_2i!
|
|
||||||
EV%7@Zhq`3]s1+([/*rTUe<fDSIB/5XG3,#_658q3,`/dN$chTc\)-n&B/+&RKhW=Gsa(qFoI[c)1$AG
|
|
||||||
?n9EHQp[`qs'=XVcLn8>Sp)Or'a,WLQ\qU[=%OLp)5j!?j.N9umt23L3`lTC;$MID4R-&a?XfVshC0fd
|
|
||||||
<Sgm"/Fmm-)t.6BY&D[/MC7tg$Z7"/#$X#$4ds!GRh13Y3*(^$"]CYL$YRJ52r,R:3,h6!hWouA1cgY(
|
|
||||||
>c./=;I;%r8FL4$RA-549pl+3Tf4[9[.9P!h=?X\bW&m!);Nj?!D_+K7<]6E8`atG]'mo]^Voq6HJcu1
|
|
||||||
-(o6>W-O>/i5HPoc23C#T(dE1\rjZc<b5lenRZ@omH>X36P5I,jg8(ZLjn#*2<)</1YK%Y##Yi&8*NLb
|
|
||||||
UTHUrp_?81M[.8phOoM8.OqDffe_"tqE?=1)r?AWO#s66;K7^P[)?gKgX00B$Fr0Y'q6t!pOcoXh:I*b
|
|
||||||
A>dRW+\Gr,%4^YDPOZi]&Z(]c2tCbEJoA$eeqO,Z+U01JX#P!F[;SYHp;iBjf.feU^XPgIhOoM8.OqDf
|
|
||||||
U>SaQ;B0HpZ7#iN*jNI.8`$_X-j7P=bY[IN-F;W+Ea8/ph+L7?,]R?l0XQQZ7<eH=AQXMh-s/$9i<n80
|
|
||||||
KtU"5JB\0fAPi`//.-rV4;3>62g3C"T7=o(U9IeP=BQ?"iEeR#EQX3SV097VM9)EA.->VrA\2Of-`nfh
|
|
||||||
2*o?Q6dL``CLDDb1F'T3X^VKW;rXBUO8DZG)LGj]>qXDkAu0ib^Q(tmLr&-mf>5\`NN8.bG3kTtKFW7#
|
|
||||||
bVrrG-hU=03EFft[19Zc-;iqhgWX;cgFZ_$WQa+Ydu>&c5.i;:k,ad8hJpcMSq"el,JSM'WB9[,IO.l3
|
|
||||||
nYUtC*lG!@cWHc=gfNC_/8Fip?:_\3)t1im4s!qA`=,LhG3J@BAj17/!jmWLSiu\#)-tL`!,lg:`bjaS
|
|
||||||
i5Rc7/\bJJ59XD4PV(&AbY!oYX=`hRZI]4]Tcq0Q0jpO&m$+!`G$ZTGG[G/2hk;091PJh,%WZi>n7(^H
|
|
||||||
`pDm%?$;!QQ$Uc'7%NQ?Fd]nf?F#(!8-cK-M[mm%'iZ*1-5?[hq^_LQh/%1l@NM7fG0+,_P/(VlWkRNR
|
|
||||||
NdFd/0U2K&&!)HJ`MF6EL1QH2QM#RfT9[K75$"^=/cAV8iLb/G/3-3qDo^)/m_<;VU;,=QcUtGd39Sm#
|
|
||||||
=K&*:<kt_Q'6[DGcjDJ0Fugm<-3]YIFGj'/,50.9be)/)St75PYd-S<*kB!ng&Qs'SPBVuMbEWLHQ9<A
|
|
||||||
0sX1a3]r'4l#fjKn`GL.GB,;jX*=17L1c@II-T_*Wa+kP,skbt,>1F4AVn4O.W!3Xm[:$CX00@-m@H&h
|
|
||||||
g/BH?gNeO_r=2>+lQ31Wm@??aVV&4CGZ6W.7!KD#ZU=poQAE>t&qOC:QN`*bHGX8=Z:'6>(XHp,=1ZF]
|
|
||||||
QXfLN`0iuY#bt"UobfT&/d6kpO<ro.5@06ke*:d=[2:[N`)>)Doone7G,!3;aPFdd]U<n7p*D">Jp,=2
|
|
||||||
a`b'ElFlO!:=f+[M_Z9'fgSQFSpRl!@$&GBna-;5lR?/04/mfP-M!:'A?>9Ei%FBB^LMcmn+7=orI+Q9
|
|
||||||
:N9u(@6/L[%UR/@<\9CJ^i!u#:1C8aB\oSCLepII$a*F;IgbR?<7]qUX:J@j('ruAk@!lnq(K!QNq[^B
|
|
||||||
\GWBJdG`q,emJ%J2L/.@j7<VA<X#Sa]'dl^+YYE1g4#SPqF.#SJ]'K8gS.ZUo$2/fhCl3&*5r2;.E24O
|
|
||||||
U7C)PjR;]D0'FtW&M?4:J%$oWp^"@'qh87:haldUa$*h18D-jclVM,ZP.E%c]PV@)g^qBg'f?qA<e3Co
|
|
||||||
>Lm)6_YWF)<LKs-B4:+8IL<+\7.c32'NWm]X`eMbig5q<K&XHYWme^t#JN!?,fK\2r=R]&;cuuc(Z)`%
|
|
||||||
,i!?ah,%RabQhKX;5>Y](A^>^gAReh>25FYFI_k9h%hWEHL.btn)Z4GGeUY/^6`3W%&l`\?X%jO0n5eH
|
|
||||||
_lA7?q0LHa%rc.s0uI%2^>aCRE^n]jR'OY,Dq>>r3-$cj^6OT*+,5koL"26@^2RsHa(`LU7/V'J,DpcV
|
|
||||||
X4P=sHmr;^Q*"(]Yd38_US47B_58OGQO*q=cal,ZC=ce(b8JK1;$VHh<EE<@Ytile`b:1=2C`Sm)+K[L
|
|
||||||
=27jF-CU0A3n;(6@@MthWgDk%YIN<KW$OYo&^ELG8^:21\LUpBPDMe#Dl`eGla:.ZEXJD;W!IN*Wj2i.
|
|
||||||
\&.b=+q:YD?7YU>>MWD%T?1'%,(@LAF%$9G6]hZ[33t(lSgJY^k-hNN[DkA9IAcm%AY68/F[_r!3,;^r
|
|
||||||
pD%$_q-'o1&J)G]nf`KS45K/<]It"Dq69kqNN2/iacr0^1c/A*6FKgnMt?-[KU!:!m3gtDP@jaF-JKBs
|
|
||||||
\Nj_[YI\h*Za/_@KtheP3C,SZf+@S;hf?)7n=,q-8TF=*fq&F?\d<Sp\%sS")l\L%C5<oTH\gu$X*9Y<
|
|
||||||
Gr(J=5Y1NRV\NbO-5.eYhO/%=]c3dT\[58XReD3O(S5dr<$C>fp]59c.:m1sFXa4^,/ng/P^Oi8k)I3D
|
|
||||||
2Ck?8D1e8L*%S'rg8sJ8G3c)=B$[q5S2n3>\RUpNk6)^i#H?QfRq"mn9!@Pg,!50:qEg4-*;DU7oV2k^
|
|
||||||
r=9l/V_+MIiI,-nK(QIa<6)/W:umXN`1+;Y#3G'i#7Wof&>%*%K.VuP+LCKd6Tpp"GeYru%V-`Ka)c^E
|
|
||||||
P[t(6E6IcJ,aE_0JYA*XA%^G:+QEoX_H\QRH-[u9V9tIC.:jW]:mejSs(/)JepBQtQ[]a:(o'U$<NZ83
|
|
||||||
hKt1l>.#UN^%h<a`e0\C)YIe%:2-&^dABj>-I*N>)HrF@l7dT/qfq,Q.1%rZfkPQGP;PpAjo<D174C##
|
|
||||||
OCoHa`07`7$K'V"_l*.Wl(g!?bqbb[Ta!f;')2o@06PiG&+.eBBZ,$hT=!1JQhkgkZ@&bLBJ#c^\&)(R
|
|
||||||
Ad#p%pajQUTT>)qf;)j$J[h.o$FdNHBftR3%[bJ.dO;Y]<sHYFJ0ZbG:HmGoF&(O'l"um0>B-Qjd^VRi
|
|
||||||
U./?4BZTjIGqQD5U@S5,ndhQD4JC1V5K)8mnVhd5(#\G*TpfNbg\>'CMNo&@68$XrSeS'\8E[&6[!#en
|
|
||||||
M=CZmH]LT5g-Ck"YGm:P'1^[9UV6QN9N/i1BB=u;0]();0:DN9@AVUJSZ$V3-KHObR:Fr5j'+%R0H^eM
|
|
||||||
#an%/>&dulhNUsgPHJ!LTMA,I;d[*cVol[hi3V,F'Xi1"(Z`:[@?GC!TlMIElu7qdV<W!#@?&92]3@J?
|
|
||||||
?:6iYE'<G>AJ_U@DUsi*3FEc?)&Z!aoa^ZP(qq6ZXL>`S57XD7$aDe,[;'tB]$>2)+R&L"iP?P8[hb;M
|
|
||||||
SLElPXP%/_/Y5<9;K2n%oZll%W3#FCaXoZrHg?cI1(#?$n=%sGa"$2=modOj-&pJND$Hj'FJqR@5#KE1
|
|
||||||
g%OA=XNVRXi,>2_e]XEW\M:M-T\]I[41'V$U<0)D#""];<g_!QaN@%qGR14)l_<s'!?S?t#E/8OgUa`n
|
|
||||||
_hhYL9]XZEX^D1SrR)%5>q8U5&TOp,$b;d2@2A#=C'B?J\lP3(jiB56qgN)O=S"Wp"YXA)eDbAb[DH%Q
|
|
||||||
pTnKX=uisA:hU$Qe-`390!-gnF-(GBCg%A!$9W$ET!9f2p#7B+4-.rZ.8$^MiN;4i$57DEXqdHoW"gBO
|
|
||||||
6]PUbn'DEJp]s\*]_EPuZiYl,O&hX%&t3!4k"A<U"RVA?PI@d?9\/?k<Z%>&`3fYf-Fm.oS$\hh(aq"Y
|
|
||||||
[)/U<4JD.[Q"ItO0aEd/GMo]kp&$oFkH?fg#5`]kn9Ld3Ds`"k44a$'m1kkh5,,j:l/R@S*cdUW]$sQ'
|
|
||||||
K11!'EG<QM0_`mNVYb_to%;:C^-%dGA"dS["0Z3AnoVbqadV)@lF"U_YqHngEa;^J?C]ca>W[=1Cu*.G
|
|
||||||
jN53",WiQd0hJ[W9:a@3_sOlc*P3TM3BU]'B+W+tgpH*/P-"%kB(6;nBHq&U\a@Qt.:Lbaj$]4ZCN.&P
|
|
||||||
P/M+6f8q8Yqeib'5Tr]=;j+4WCQ=Qg%JNF_2OegT9;DKk0C8o1[P+%9a'1L^@pG2``*KlG&u4Ej0:bHY
|
|
||||||
8KRg$V//ug[P&(3qf4(\'duY^r)o0L8UGhbrnd'o1Kh!6ASprGW#Y,=a[\lnZK>^!MDE5d>k0IFP),l#
|
|
||||||
P!&Te2)8mT:T`&.l#U9dn`Cagdkf:j@cX,/C]2WCZA2Z.S,$`$\`RhRZ_Q+>[^WR!,`[];qTT:fZR6,9
|
|
||||||
k&bUb5%lHfWGkCZ?(I0$32DB1rZ+e(/7OSF\[,hK1Rm"a5,R?8Lo0MYAi;,I+b;Q=ilO@=A$"6-gM0.t
|
|
||||||
oBPj>S"At-GV5s5Tl%d.ofu6tT)&8I;n_AY<9Gu7)j,cmDI<c@AL.Jed/1`Dhm#EGmkWamM@P5h=Vk7d
|
|
||||||
nb*PElZ**K]#.[Q=k/,7F]8hagr%*#)"j$YFr3,OM^UTkB9V013NZ5^aPMM"#1s8<VZlL6,KZi5BJVo[
|
|
||||||
mXqf_mlR\rg;.,+AHUn<bq-$S#Au\d%>6k_Sb;L)hpK,d+%P@n*%<fq:5@5FCo#MCW\^-pRQs0cG;RX^
|
|
||||||
+ZpTa&l0$u<Gt2`;g`4Gm9@2dKS5;T4ml"1M:!l5B'R*.6M?2`3qQb>Xhh,c"C+E-EGjZ12\d2YYgL)0
|
|
||||||
Tf6t#^(L@-kumtuI4G,E^=lShPWe;aSAkI?M;J3<Ehlu@lbn"boAPKU#IFAbA"ejQr2mQcm?:u!]Q)(T
|
|
||||||
cGJIi#IMg$4TCH?H+\-F7f$`0EITmehgqsi/BuoOqt&DY:ER&ZjaAC<[BfW+421Pneu(F.WgZ1X^"O=g
|
|
||||||
-p99eGVcARf$E\57hq&D?V8g)k*?W;*(ps[Dq-]4NpuD^\mk!pejQln<T.*OCf^dM?_=8N=I+3?W.OQf
|
|
||||||
/2]m*P_no=<i+?9"tS9i44<3*Cdl$6oh[/-rJu)38(q$[eQ7O+>*CRC[=@f2nid+0'UtA7M5ob$&r-E2
|
|
||||||
I[NUmJi9\b4Bk%<oGd@TeF/=KJP)9qgtTkMYLY-tX+a(s\p`$]79sCMK18#TRt"D,H@@W^]bYW&E=Rk=
|
|
||||||
THotH4QA`t`10PFnGAnYkrs\2fqV1:**92aS'hfp7eS')L+/aILZ()t%egqhO8c1<2>$uF-%?DbF_XER
|
|
||||||
ItA&<"9PV^-u/K!e9_da[f!]WS/hsd(ulg_Nppj"[FeYNXdVFQo,?3o<*?_emTDEN'k#l/.UM.?pl$SK
|
|
||||||
No0WQ0g>Vt8)iAposV\43I978FUWWQ[o:%Njn)9\JU?n&V:Du0psP1BYL]j8O`EhX=j0$/S^Xf$1kQ(A
|
|
||||||
Hd49ifY![&'oA3:M.;4@4sbLGRE-@&TDsTaH0W8J\=4b[==lGCoq;_mDDfYVep]0/<a<L2Fl)9Ul;u3"
|
|
||||||
Sj,1VN.19\d>?L&BoY:7OCI/-XaZAc>;E8qJ%"7pY=j9@^_b%-n\c):`]-3Gj54"^i6VBWXK!/=9:@a&
|
|
||||||
pcEG=XQSKG5QC1'Ab.R)$jM`j9(mH+*OmEGWE$q&?at+mKBL:I@-EC3q00K'<iA@b$ZEdcc2PFen#p_\
|
|
||||||
7t#VO)q]u5i7,%1U8-"\_]B=*'nEL]M&tL!n,&%!V"'GJn$**"ot_S*QTmDMr1_<R2f*D/Q!olRkb$C=
|
|
||||||
9-^LHZ=6N<P<NZf'k,1h5bq7G4'&gHg`NYfaB$)ANT@O4T?ZjL<R?M]*\liETa@NrH,@b;%GuZRdu,!R
|
|
||||||
hR%!rg8Y3(annJ@6I)($T4i1#gQBK:YLXt`&s:2VokjWqI=*2Ym@]IRlCQU((^H5ob=[)MS:@6l'qdE'
|
|
||||||
b)e%:B@`(aW8UFGZ-B>WkulXZG#3baqQlukC3PS'.dLR$r'Q;nRc94rcbVIec8F2d;#s9TnV&W=EC"/#
|
|
||||||
jr"12Sh9`C[q<iZ+\fq4`AIT`):Yu$*j4!!6FbtLp""VqO\+iR$1`.VXK)*)b(L_I]R_T>OA=:-Dj=R=
|
|
||||||
DJSs1?_=8XmM)$W1BD78X>]U'?(JQ1PGj2+l6("EZbnJsNSdd=gT<3.Gp@$Bi>BmC:Uqel;Ff'Jj.`&4
|
|
||||||
L'M[slmI:Bgo5H:5gJ]iR,I\XM!O"tbF3bKn16L/cg&2G7&3PS<Aqs5Kkj%3#\X!TI`]Li-@\VPm?TX"
|
|
||||||
)<"1`\[u;t3+>&NNX'hZ0^DXUU-'8ZSaoH;VcL[Nq[9s[JNm^`8PMcGojbPP'0_qsh`,g#N82L=]#[i4
|
|
||||||
Vqs">DRg`9`PBFpccUS[1jHJZn/;8QH@4_FT:UPMV#U2DHmgMO=d#+'YkGoUBK@9&<b-/$O*7j5J)Nb_
|
|
||||||
rME/?PM8gQW!^csS4!nH`ApY238V^T^Z3KqYUkbS,EQc-o"ctI%F\It1iX7QXWkZIb#i/>d0-dS*_?2T
|
|
||||||
4Lm!!>N6Q[mj4TP#0Q$)2V>THYG$I5dW3PlH!6-u0QWd_##<p@I!HK$_k)F"U5(<@KNgoQIp?)6k"R4#
|
|
||||||
Y%.)0L&<s`C$fh`a)8pP+0=FEolW"@&TVMNZWA4pANqZhVV;G3PWt1f@.^JHC6VaP)H"=PVD86,oa)7?
|
|
||||||
]1a(%+F*GK/9+SR$I5mM2UGp:Rkp9V.OF>jWf3\,d7%d,9-%9cNDYEZk"sa27(!YQ:L2;?hRf_`C9bnR
|
|
||||||
>#h0Im.SI^S6kQ4.VMp$9-)el-p':ik+sh$:!9VRNjEa)2mKG*EO01R4Y+Zu+&4cZ*mO'!90V[EQ[Gmk
|
|
||||||
>2?(5bbrUgAW=*3FAr!NBj3:snAWFFgO4Pk9>kghE\:/5(3W?h0eF""Z#iVaB:kBignnsrTosB/C_'OA
|
|
||||||
5F.+:Y,nNY`LIX:p2MXGS@6FnqS$[#"-GYsd#+pF\,Kjff\b?SL-P]uh%Hu2PPV,ISn7"$hYO?bB_K+P
|
|
||||||
,]$5BX^U7Nq%p1(e`ua%b#QhH.g'J8N"<>E:2:Y,Q3d(]?<SJTK9Zs'Zp:3dq*'BZXaos'.3#'Fct`Pr
|
|
||||||
#qb)MPSV>=]0'eY]\E]g`m9bDdD+$0fe!oF,0sq9JW_;/U]lYE+$Fg[3EeZrZp8#5JX]EWXB?hEAn9K*
|
|
||||||
GN8[WG8seZ\9;Xh?_mo_NmIj\AN@+_hFI!d,5p'oH/:S+:Hb15koj8LMUFk?id6ILYK&r3"V+:B]fBS9
|
|
||||||
>tGE;ISZ9\HF,q\N?Kkr\qCh)/1htq/YG+g(+;b?^U-6t=ppFsU71.jhIf&#/fRmXnsrVU#TC8iaLIf)
|
|
||||||
>/3V>6OOarQPOQ,>/5TVNYW1;U]p=R>q.EK9')Gn>:/\]"0So/[:H[3$^@`t8IH6!^!Hi?<,$oQO-O(9
|
|
||||||
gkX8ET7]"N`HQbu86T0&kn9)[%iUc`\nMAk]X;i!0"20bID"1F!c785>p4n#If0k'OQVPnm:tgtdoVIA
|
|
||||||
3gkP!4^-!Pm/%OZm4Z1Pb0%8`Y?u1h)Lq5,NeQ$L8O<%5VQVD>jY\NE'[O)G](4Rh7aMf!"B8J54\)^F
|
|
||||||
V4KQRi:?=3JK_:T7.GT2JrdP,*<tEplr=dXAQoC760"O_T=XkQVGk.4LWmcAVg6SKDc4g`I7G0SJGCbe
|
|
||||||
flI@D<hH$&Lbo5$C>GW-dX880c<XYcI:"h/YK[[M;gfYrLQCp$r"];u0t0ekCV.8dDQ_VO0/OA@htp2k
|
|
||||||
ELXF\nX%^h4HMst-I1];BX5GtDhS\A]8(r`YK7+rT6BsLD4`?$S8kYlQQfA:A.=R6A^"%f.u+UCcP3j3
|
|
||||||
QZCs21+F*RMa&^3G#Z]s&'n`K7#"s,R""+Kkr6%%_=LGVrQ)NmBf=>OG.0Z"=kVs=e>o_sR8%q;oqC0S
|
|
||||||
/eckU[RhVLn&X*&b!kWFA7jtH5;KRg*^?r?pETRaoHP;!=/C>Zj*kOVp#>A<euHBrYp]HY-JlphW86hL
|
|
||||||
r3AJ0fX2c-2\8.D,9PuqAB.CWc?F0:TD,o=fk5qdAF!)R4BCWDbLL`>^30<+)j.L#^1g,&Z?$<Ma:J94
|
|
||||||
bh]Q\kXTf$KAtTR-j)4&].#-\4$u,Nfbk\oBXp/\HFA"]^ci8V\f]TS6U*=^?b7E53P*0J])&Tfic`FO
|
|
||||||
9q=0pDmpnC1mLU\@#h/phdLZ9?IhsE5"f(JgppU:LC&7SRd4g%;>0qnA7pf@r]$jgNc/ibP?+9e1#\3,
|
|
||||||
c-I5u3-Jf6al5Z/EV3#rWO:o,9Oj6%C]0QVU.t,fo2@D_m11HK>5VO:8Q<HJq9?U9P0E[urO]D`s7nC:
|
|
||||||
?)-?(s70\>s48?N&Yc%259Z`69)K]S#lF5^fUr1dCAL/uO1f+<7c'\5E/j;Ki(^n"3:T,>lRoP>B,C%(
|
|
||||||
nl]"LYGb<73:fmoAh.ca,-Fj8\)[3s7H#6!M)WF]S\mC3'-2XLlBCWhO.,uiZd?@&2iihh*JSkDlFbrc
|
|
||||||
'%$87Ut8:E@YN%\r#5pP)iui$&7`^;RTdeCRu'WD:Yt3ip*O<p2k%,Te3.I6[WaUk7n.M)6Sfb4Q*\Lc
|
|
||||||
U?XY?$]g/RaNBfc8Gum8*MgICYYUppS-R4La#`s/mG=jIpQMN%Fah92DnO]9=tMkV&FQArn,r.&g9\X`
|
|
||||||
q6MT[.#'=CYQ`:Us+teSf`0$i:b$EqT-g1!L'71!.Vfk'X.pF(onV^2^g3DFGj:2>__W*&p9:?F@!1sA
|
|
||||||
iQSps`bA+=J2pq$XfDu%(tgC93G<5p2fg$6Ekq7I'"\2UAg59lH,GW3Qobkn>"P(?R0tM/Jf#h2Qs>Qs
|
|
||||||
#URg0:joI=(Y+0Uee*E-qPUI_=Z*D&iJ#+h'p[C+m?fs+,:l?ZpGf:=lQ2W/XEu,*Ts*$@/ku8DAQ=-!
|
|
||||||
(Z*b5#dKA;1'>-BY*JP^Hej1'/+'DG@l<5\b\1RXg]lB(kB(ToP\#T-L8t`E#0a>8PG9dA(Jj_22LD,@
|
|
||||||
P;s(4lSKhnaQAN?(?TP=-5<OO`f6"IW\QYrnJ'::)eO\jms+gP=(jm/8[6:,AWA2o$[NoU%Z\;dA"!;"
|
|
||||||
+jlo*j`FRn1;4:A)R\R&!T`MK!aYa/%C]d2;p6?s6U0b#[u4\a(e#"bJo&re0le*ITY^QGSS]?47j?,B
|
|
||||||
;/M^%2Gq*5i*Q#lNEMN@47U;t1+lL7,gWJA$%hO)Q-W4O,2NFlmWY(s'`ke*4hR+d-s`d$HKTV[1NmuF
|
|
||||||
HKN+d,eqTXWDaj[qH!$-ab`0G],fOLa5?BHV^a1f::9&#op,9247:W/hK\!-X)rr2\<"uQ^%.Mm9feH3
|
|
||||||
7k5Qlk_8S>bY(;WCBUTVN"SLadVkqohSPi59B<\H)'sTMl$ID>AKLLGH_OQtaN'iuTo7@7h`%_8`eCre
|
|
||||||
]!(P%]E$IR]K+CZ\W""u47ZH>C,#\`/<6)(!#d+<H`p19?@]D;.%NG_hI0-@VLo.qQ!MOt,Yg"?;o3CA
|
|
||||||
Nms_(p\dGs_m+C\SLtf,e>L5t27)KM=+.9B8XB`Aefthhmd8rGX;"Las2Z1b37c=^#+7V\l/Tn+bb'i.
|
|
||||||
`+W#mI5I;)->tI!B*F8b:j)R3p>/.]ne-USF]<iDp%p/%H"r%K9OC4b0P?\LD'8idCcVc^mN*;$;<*Z\
|
|
||||||
E3-]H:%OC/mSirQn(=K55hT+-FU#m0NZta#P15_Q"*H_2Wq5r!c]QVP?8FPjq']G0""c[*orPobf3qPf
|
|
||||||
54SI>I$'KcqpoWC6jgaTH=?4e`ZWT6fPHf[JVe.qPQWkH`'C>C`[>o%ni/;3.[B%>Db%G`gt$2)m>\_)
|
|
||||||
Rg"iB*mZCNfU@(=\Pc>%aFHF@9'kl)^m":*Ga`s7FuqLBQ7s3H*H?kl>$f8+*h2akl,e;J8%+FLl(chG
|
|
||||||
)+."G>Y.PSAf@4g5T^Tdgd"_JTAS'D>;HIgRE&VqJ3nR+Y&hohbUXUi\;Ao&a)2a;AW8ZcF8*K$RJ^4:
|
|
||||||
\l1-%F([KB+eXES.dr2Nr8S[(-`&XMQcqkn;k;oR4#5ZTf7#7U`@lLg>Pp+/D$7>4*fCY.Xj\T9,_B!U
|
|
||||||
]_mCD"D5gr87h+1r"tr`Wce1%5M*uR*/':N#opD16Cuq=32DH3rZ/Vb[g^jKDe)/,>qUOiQiEBJT#GhX
|
|
||||||
hp4mKQs;Sj`IhO-V*9sSB>NikGlnsBaAh;_=h1DakB,qC?^W0`e0K#RA(qX2A6)`\YOeQ>Ps":o'%a+t
|
|
||||||
cBCF1[8,m]jT(Wj>b:Umg*AEEqP)f,$jlZUOXHWH?6f5*R'hQK\/FuKqq.oIJg4BOdYGBc_3;5UkN(iS
|
|
||||||
A^<pu>-5tlre5X@qn1gu\9dgJoiXEj$ArcR`0Q&$Ip&/*616Csog<(t#!MF;1=XWcc$]I%UJ\']WaCVn
|
|
||||||
nQo'a?PNapURSu)n>P^_j4\mBNHmgPKi.XB?s)+)%6K%!0#-^)3,n_Jj*C[]44a0*)js3/\U1]fd\B;I
|
|
||||||
\`3lg@IF+n]%_5tq%o&41!ef3gq'LU<:4ULE=B25i3N.WrkrHfm/K/]7T`Mti/H$7#d,NV4,k*@o*`ST
|
|
||||||
ls`]gNT5<ahE*na10^!LbZpm&LW8!2lYF_*dqMT9Ang]>/Ma+Q@AoZJYAjg$'KNI/2q,EK=[tX]Df$nd
|
|
||||||
Kf=.IX>@SiI<"F1<T_$*`0j`#/X\*4;cWjoD.-)M[hl5)a0/\2/+&cP&9=bT^I6eFQ.*m<G-3IFTiW<T
|
|
||||||
WPigR:;P6"gnJ^g6s&;=M[rUILGF(mM=[iq;iN<1qegcPfu\Z(=\$1$:<0/ONu`_%pJJ%%3F*%n;DbWh
|
|
||||||
oEq&]LM,e0rgh0;BBt],TssN%Q9G5Qi>/o;Qg9?HZcRXJ6*Wq'#1B9]6l]DOV\OpF&Egj_AW?/8rKL@1
|
|
||||||
kX"[WI$tTFp8U;\EbMD3dcTddK68m3:H]P]fS"5r??]J84\Cjl8TmejaeA'gW@3XG:fs.&b]E*b**<aN
|
|
||||||
hSFGjpaW>6H&n<rdJM\Sm^gHZXDJ6)4&X+KiDR_R%ZGic)NpG>b<#8_^85su!b5fGggHclTs3m)rK:Jd
|
|
||||||
J+(rP5[S)S8+4h5obo[5pL51l^)`qtcLWQ.oFMHSo@WXQqPV^Z%0o3,.gd:IfF`OAQDaHL<)5`EXC
|
|
||||||
X\/CF<B#_sTWrBrmF8_m4Rps\<=%mt:/T(O>4K3cU4kPsBuOM%r)+.QhV0G)oMrE^UIRYu?fHg':B&Fk
|
|
||||||
!tp+PT``'p<+:oYJiA+mDMhrm8II0da."B+'pi=Ak^(jF'pL]\W(_i%muTdZcl%CRnTm0_FA!k8gT*Gj
|
|
||||||
qJ(dag3Y(OknJ@;3AWZnXn8=_0=``qpPqs0J7$j@<'>9_r>!msoLg8Thg=S&Y<%s$,H'Ho?Vl,]C1l`"
|
|
||||||
`P&WjB]2j3bT8,/RTfo""cq]tB^e"/Tt%<dMSF7Nisn4sEM45MF/no3";e,R_;bom_e:Wd&,Kk6`esLg
|
|
||||||
,Nu0]1fj7iOnVpZ4K`%Ka7\;^K>&fbWCK_"S%]Dj4-)%2U4%bu3QGcN2;43W\#]=k:e-2N13&#p)1aJ%
|
|
||||||
9"u(Ll+OokqPlMT4VAPm`LY&W06SDo[%5.qMc(hO%ZX%hAap2T=.N0,#dLR8rmH(^ml`eI_q+,qhBSrV
|
|
||||||
9j^eGEkWH1[3P:rE;U_91kD6Ja8Z!d1G]a.L&O!N9@goH/d2W(7:es,fm3I4["Xf$S8S3&):*01TCrW[
|
|
||||||
fgngRgg_(*G!E*_>fh'(?!NG&YFVuoe]R/<igc,Ag>7V#Kc.jqpo<8cM7*gU^fsu]_r(DZP?`l::G?Yk
|
|
||||||
'5i:CPLX,X@CEZ-qR^mrW.dc4;JB#q0M5aac2qHKOJuu?HtArp?14/g.Q9M5*J\dYJ45l24B'k[I]R!:
|
|
||||||
o&8_:WTfr)\)`BL$dPJ-X!$b&\^\!ifpQ@%H]q[iFsb(RDM(As^]"LtFn#pRZo?p4.lF`f9,lE[Y`$Rt
|
|
||||||
J*@QjNn1W6c$+:Nch4be5SL9P0&TGl4o&/I(Tq&MX`j<D](_2=dnmRD9qZ0.`\j?`p$=MOe7FFV!e2TC
|
|
||||||
l8nQ<XH8E21:_?E+ho@KK"2"7/\4M+9u0Jul!S9B`@4NG8Pgf=rRY0kZHaet..M\_H*4_1Ns=npqO41\
|
|
||||||
fp00NpQ]YBcAu!/>f1_4VbQG_mK-WC0VeI3glEk=h(<!Jrj(sS@Z+Q6RJ6J^>Nkn_s7'HG0ECCus*i/R
|
|
||||||
:pSoU6?PWMi69AR8qk%[pt+grZ^h'RM2kr9@&Co(RG'X8;#B$-b[u6'p2Bu#_>M&#Pk;H6@J-QEZ:m>A
|
|
||||||
T#koM=Xh<S_4+RXrY>H9M]"Wu>'?BCc5*mKq$Z<&a$&^0gA`%4_QBgS2rs)U5[9DcL(_b2MSA,Bk[XJ@
|
|
||||||
q%&Y4rckljeKFS30A9HKdmAFN[/[Jre8[>:o0t[A.m:/t$+nNkRV0E7HeX!R^hTg>p4!8eP\04)[W%X0
|
|
||||||
#QKj+PauMmIDhQh`T=j:Wd\UrA]JNbB@%gRAb"N5Vie19DV=Fs#_+ooVm*I2=sW_nI&(SYmrI.QHJNV1
|
|
||||||
=h'K^AfduL%KA[RrT'Tg:N]UM+9On+NVd>,i;^R]RmmEqFO=f__J#I4_drT>TYha/AGSL)W60B[3^ij0
|
|
||||||
K+=iIfK:DICK2(L)seF-L)/-],&bt5X@8Oa$Z<]!0>+tUB(u'rdm<GOD(qSHrR:fH2'$L4=nup6?%bT"
|
|
||||||
N/>fHN>d'M!umeU-\hXU8g;I=R#,t&?5PV#XuE#%_*Roni>;)TFG:qdSi$VRNVtZaNLjf^:KUn1L&j*U
|
|
||||||
c/4&uXDb9W&FDG_eQWh^55`>TWKO1=X'8T<oaPj>B@%8oK^th5n3p!"_J/h_aA_H4MDp?Wb!G`oV8(Pp
|
|
||||||
l-D>kiiAfMJ)gPC/O'JS<o/<%VuQd@q_Xg_EW(ok]8Tas<0k;j`6?B5Z=uO`WP!PgX3YsmBD9`F_c0T4
|
|
||||||
WA^51Nr,uN2ql,eJ)O6;oD/A9hGB-e0H"esEG"rn^/2nIV?"k*jmD+h]bP_tJ!bHJ[H#Sh!]sc6n;o0\
|
|
||||||
?TRTS$sI-To-M3bq!\rAXPc,;VgIPt)lMqVU)UuKMs]2RR30`/rulKgH^]ede4kG1ZR=<mQECW9\l/P7
|
|
||||||
?"l2!XjVgU`V1=uDB[F--[YrEO%ln!TVm&h&'eX\$Xoe4m`<ZM$-!E5'S612@7C"U(Z*GFl@0-]EbWTO
|
|
||||||
PAps!'0,5g+?[.#2h"n7,KV=Ms4CVoUrjHr'-Jn$U?ED]RG4fDL7P5gq`f^.*j*/6l?<N[7(W:Hr\JXD
|
|
||||||
dpqh5i0b26qF0P<<[jQ6rBEW(`u)fc1f&IX!L5;SCJtUj477O'LO/q?a7FeOCNjB\_)/K(C_nJS;l8q;
|
|
||||||
1Qm^k`1j7L(GH;$=ceSX7"u9!A@V25.q=obp1hYUV^7ih$b`-[1"P!T'q7b,l1L;u9o0`SAd?5UUJFp>
|
|
||||||
-/mRR+Y2o(&1Rkf"pZ!F)lW;?YDsM>rp3Cq<k.5@LK8qA':Nk_gWbJRGS\g[%9&&:fFtJ(bfQ9fXXHo-
|
|
||||||
EB_rP@h!RUBj&5P/h;RLH:"RJKhs3C0Q&lhb4\Jo29ABi@q\WbHW_Fnp)F_S,`73Mld-UC="=mm"#V4/
|
|
||||||
eW+9tZ&ZRXTbs=D.o_iH")<8:Ul2IW!T<2PCrLu=UpHbsg#-O?90;?uZ\[fg-IC*L7/HWuXVjV`L,)9U
|
|
||||||
i[^`r@irduM*7s3T[%:O(UeA2:/U*12?_n*b`<;6Ss[Pergk(MAh#D3Z8#\>E$`N\eAg2uH(l=jY_Zd-
|
|
||||||
m!iTZU#EA/diuaIp_'W"..c6f_MRa+>`mrgkWW5*H\gN3]@3u!i(CLt@E;(),T*.?V"]eh]JUobb)eW"
|
|
||||||
1/>4cU!e^l!q68F#?u$s5NI`n2pI<2)AkO^cEmh1B-(=T335#;\WR;4TT^mFp1\2Z?!5!@8#i[(G]93r
|
|
||||||
Y"@7Hp>4"OXZB_2#!Wj0mWj$dC!-u6k/BN`'etF#4^4Vi,EC<ipYqbO<qgC$$R-(co(,.",ohOIhf!Tl
|
|
||||||
3Bii:!&=hEF1bTi+l;^K#PVl^S&LLTAo1/j8\--Y>cXBi"A^4l_.[e`M4m$bfocSOY$*pOL&A31P%5?[
|
|
||||||
>N7bPdGBh=`N8]ai8<M"KsP^',XZs21H=-c?>Cm3Y.qhSXfoTK0$^o*]`h\l^MaMkDID7F_]3]N=eW*Z
|
|
||||||
p^jO9Xr5O7<E>nG01p0<:bXrTm-"hI?LhfaL]h1J9DA1"4HnK?OjbhQ`N!SkCM)@QYcTY@^pU'Zf-B>u
|
|
||||||
edPSBf@NYP@')?3h'TQCA/Y5!<]sRFa93GtNReo2&nSp&2c#I(X/+h?nV`.IWJH1UnhFZA6/6b?\<c`-
|
|
||||||
nE-:p=ceL!*J7P";Y^0.gfZ9T0EeF"BFf,h_>TssFP#-K2"5+XFHi%S/!FgKZg[j[oE>AX'r^9t%r)8_
|
|
||||||
eq.uIL([@\L-sd[C!!+qKoEb*I[4/\e/@ok36#;Sc^sp++.iLl-Vobpo)[O^bQm&raIX=J3E^0;S5iCJ
|
|
||||||
N9JoJG4AX]cdn>5ms/bRaI]Mu6Y`am]qiT`eV/(<];^OSCqkt\19cX5^f77Y+,#%h.MgSs4quuImhe2*
|
|
||||||
_O\3:B@=.<'ABrk:kK!_.]tEP0>[E51>8O>NNnlKeZ+mNAMKTJ"K:P;d6fE?(Po8*c54jSZY07kUTH^3
|
|
||||||
J)Xu61BF.Qr_+HsoA)0EPL"3+ea<UBAVI&sEH5SQg!n2]Xo#R)l_7,8Q9VRX\`I07kCAlu6+$P1e+0^.
|
|
||||||
[f"ARH!C8jh3NQ@hLq1Z;gBJ.^SRjih_KC.;5+uDV<-SA/Pk!_Xko@.F$@+qIh6H.a0bc4=OSjO-AspN
|
|
||||||
5?$7H*,55Wr%5P)Xe"c)rRJL7[VsEKN?69Xas(2!UqKO4#uH)7R[[?dA%2B(nAR!Z3b`BVnn/T(A,02/
|
|
||||||
bI%2\LU=#FNa9I8rVMIQ/$HkAb`TC0MWdr&h"#B;P(o:;,IdR]mOY0JBK[-*62a)0cU:WJq6rb[7E`I6
|
|
||||||
Vr_:[e+0]J$(;Yq?*``tBXNiCMIA[g\,:#D0teUgQN+cm`C%0[)[eJe/:T!dE6I.igp-c/=^/QbOH&[C
|
|
||||||
bq96:ociqHnu#H+bMOR7%Enbp?_HL"aGH/b/`/JR7c<`fPpmrLJ&cm7ou<#&O7o3+2`bi&,I9*eYh#?/
|
|
||||||
e/+S/m?u5O.qRA[]'5JA#2C/i]huZj0o`_@I89kBkem.iBH<<q\A(+n-/mJbI8ZYEP<Jf;f&tG*?aQP,
|
|
||||||
RkaE.=$N9GKGWJjRpFpF2,TS6e7GsfoEis:;W-+*2D,lp)u372`V0GRKliTOp"6USF"8Qk'D2]*fK-%l
|
|
||||||
43%n>pgp/sk#W1,I4&<>*"IG>R#QOd@.1hI,`hY=gSqB==6`j;]%r_-(YjX'%5Xt/%Oif%qqrRcBTLt_
|
|
||||||
:>i'B^B8Kqi_*Nt<c#AYN\7Z\)(7-+=@8]>D&9PP]Cmbh([-jKWRg?\D_b^pB()uQ_s`u*0coa&_$"Mr
|
|
||||||
qtp';J#j6_66EY-SS**=ZJ)nf7f0c@OTWRP0AK*L`[](Gi/o)JM>Q8IGVLbQV0;Y:\;Ec_01=7c#KNUg
|
|
||||||
0GK;Tg@,#N7'j/=mQm5*('Qi,ZVZh6K.r>h!11sSh&JQ`;g&R4#`d\mi0YR=R5s$o+ACI@mQm5ZrDRsL
|
|
||||||
;GpA)c0FkM.qi\ce_2TsfKBMHF+<EqKW0)e&&K<n-g3j8jOT&qpE2T^7*946l5g)K5'R!\GO-B(.A!CA
|
|
||||||
@<T+P5kj\o5nAS'f-4Rh[5KNu+d=[eEjtB*B,9YRZlZtO"-U[%#@S[#JDmhtg:!>hj;nHEoH6hRcK.?"
|
|
||||||
?BCq/B;X1R#@SDAQQCh5gpX!`_<Kr#KS/MTE(geZ*1UG`&>jW!mQn(P"kpl&GFGe#1V#2,/l6:"h=bP7
|
|
||||||
mO.JiZ#"B*&>m+eoH;BE<]F,Ki`P#h5^Y["'?<B+6't2Ej;rtW]T"se(148B!@H$"Qcuf4/qe7lhRaf'
|
|
||||||
9CCb)R)=-"=sYtNCbuj(dY%&IA&rMf"&GNp.K^VrP.h?&/#6oh!4@3Dh!qT8hG(>5f60;fIGDs=>TCRL
|
|
||||||
MYLj:pBYpch^6G`hO2.f]OmHg=L;EA<1#`rYcg1$T@!Q?=7+?Tj+(P.-AT:hAn^A#78oh,n('1efn2$q
|
|
||||||
$QX=5/aC(7-4[A1&oTRWCI!e8fa"%4)4EZP0tY4uPr7RRa0XVJbmN.CnZ>nj2SOllPWlh3>p"*79/;^k
|
|
||||||
ZeK3[N1eSHM=KQ]9!lSH@=6%>9]IY;MUe//NLT9U']U]]6"rH#]tEC#ebK&(55p79HlcUP)Em+!g[)=q
|
|
||||||
p%0mg,R&/e_J@*1PAb-:MgngC]akLEhY:XeC0]rWA\PB"R$XLY1T30Aqk_>Z_[>1_dM5"C$(S>4i^Trn
|
|
||||||
GC1gSkMNOl5K'&D/?p3W)PbA[me\+q2D08:780e:e6+5+/,8FkQ1@A+Q&nj><iC^--ZMf"Z^#le=Kj;5
|
|
||||||
[UN/IW3Xus?t?s/?XbZ,n"#]XqC4%6CX'65TFKH/UFS>Cf9I>TCY:<>M<ePIaYgG(OP9Rd?T*Op)bl=M
|
|
||||||
k?s[LkTe]'9'G:B8s"piZSU9IkoPg,`E=iaTQ=S$@/5\M7-/a`gtFFAX3kSP.$,hu(b)`LQUiP>n&8]^
|
|
||||||
.5P\_RC;i2J]4D!ZI`PBC=PlFbFjLE)cX[UK7`=7PJ3hPap>B+;S[0[-$e8NX`r`c,'s5/a7HJnqWX5f
|
|
||||||
`.>;u?5\#@*dGYaGhI?uOc:q/SptKF]Q0j0!BXaX4*JQcc6081n_tES[!(Up]6KMEA[6KtLA_P*qB3ns
|
|
||||||
T_Z7agX;*AY]dtT1j6g\:[V82kLpMP037Fq%Fs)C>Q<mJ27(]\H!6,uE4o5/dB$V$Jh&56II)b!Q1gml
|
|
||||||
E?OY)"(Oi[?cJ,YhHR0(0kMYR`7C]i#IaWSUJE>)]B]I(Npssq:fN`(p(Tg19IbldYTnVf%sTo])F-CD
|
|
||||||
Q?CZ^sm7r5-N,/B3U'TX9>Tb9FrN8WD%g\>ADXjq"&XQ7<87h!PYNYjlP).DheBA/L%&O60=#Tc'C
|
|
||||||
0oG%c4gFMBO%oZuQnQ*[E=gl-J^LYD-con^nm7d^>EK])(ZZ#ZA3@n7cTnM*i],sFRf>,rqXaMh*fkgL
|
|
||||||
iEt]oVY>lknGX&K]oA(c`s?>F\MIP'k???>NGud"6&a9S%:jpJ8m@KQ`UaN1AGR1'c"#W&d1`&_$=cHi
|
|
||||||
^FWc!IrFT>rCj$4JC\>dGXIC%G1L#pY8C&4h"Gf:4L'M0djU7sq!^:Va-.\,0eYZL01Be<De_bFZTPE1
|
|
||||||
#j58U0XKhq-(aasgWmRl$8-_cRj/o!mj)9!=e89QcLWQXpCkr8gj?C`K=iAnYKeE9je"HQ9HJjg>TDB+
|
|
||||||
.QSq;(q1TZFQ1CR:GGJ=*>$k;m;I6ih)\6"3*1t9Ab/fc5p(*`\OIIjc0I3B\'PPOm9V,8*-c3`7-_1*
|
|
||||||
=pO:LkXclSh_NK&QeCp5@37I!e#]X')Df;9Y'tCL[:0g+CVH68jBlDsl-;92s/34[aH@N3K'9pD;+H]N
|
|
||||||
#jE2>RKMWJ(RamOgj_-Ep>$]WA8V29dK57*"GYkpNdB(eC?)cTs.DkT$\(>pA'S2(YA\XlD7R\@1oa;T
|
|
||||||
jhk9,b7?Z5.;YGhaD4T70Zm\-A#3qX7d+I)JUDMLOh#\f):1W/Q;:[DlFJJe"4%<]SEC8"X;lW,NmA3h
|
|
||||||
VpGa:eW.rcBADk0Ksb_ue?2AZq*\B;<iKBJZ,l1&2rE]>.*8$b*SZ6olLogQl`Jj(-O8M#Ks,0h:YDeD
|
|
||||||
GQ,DOh"UDYpq,_Q$tWiNp'[+k9!h.0(EfZE0</uaSl5pCV=>#;hTONsEe%6%ed0L_$Y-S>mYih#0^1TO
|
|
||||||
)JN;&L`!=X'(%4..V?4,;Z1i`^9iY(CHo)Ycd0AdJNrSgpL$MSPSLH*Hf44QX<%0PCGm3c<K"70i8%\?
|
|
||||||
S\#`\g.#]AKnG5;2LE^&`49*R9t1_?p:qH2]M&<d`S,;_UTIKXUQW%*M,G1+-V0a2CCc+N1M[qt[24S*
|
|
||||||
?eU@&K*@.?NSPtP_`Kblk<;nOG*KBLCCe(AMRCJYpYP6`C%d+E7u'g`g7pB`Aa5^W:8,*\=F4Gr9%G'Y
|
|
||||||
]-_;4RC/#_m=DZ$=aT?Dl=+Ts+e#f/Vl$\AmY)Bk]3WY7$cC]<E6O^A#$?4+dYbHcV_W$$C#-Ea"tIcs
|
|
||||||
4Z_j];g$K@=aQCQK!bpR@b)S*>RP>._jkjp?S+9TGRsGZMm[WM,\n(/A[+.%+O/WF#<Id0LKrgTCCeOq
|
|
||||||
9%=XbB'h=@A^&lgEf&>$fi,?rZrfC""ID#X^Ln>]>WC&SBZFf`"27,ho)-maia_-SQ_4;/4`[hjn+`S>
|
|
||||||
-[rRPk;JR/=/uJ[qZZRKa*dk,qt[I7pIB/tP0GZG&A@2B="!kIEPg3QZ$GbrAM$g_%;I7PAtlqfXDl)"
|
|
||||||
\"d">84r]pREPjOX0S83Y3%UjL#"K889"1N9ANp[dC-UB`/"9SBa_[N`g!3MVH$M?)@KPj'CVA3r2)Au
|
|
||||||
.c2iR2ZInq4$P$3$hfq7ph93>3K/4[(tHASoOa,X[$9-D^=\k2nHZ[:SC!^</lX]!gj#,DO280#=%J:>
|
|
||||||
X&O!#M_%^\X'=AlHQB2L13GNc:XD"kq/4p8c[UpfI!:jsaQ5;)g@1tT9"q>HpfFp;-#sAWgQr[WJ@8Db
|
|
||||||
?eW!A[YK1=n^faBS2)c[djtC(;G<VG]Um/"b3<?HC]!`!Rl-m0iLThIYi:("m<&=ip2%`i?$,6LAGf>t
|
|
||||||
Zoeo`I)O9/Wak%E1V9Pl@E];kCZm(1#N0fdh!6$X43GnJU:E1=e3g!rd-W(=F">aN&^%L\@*)0Ya^&,r
|
|
||||||
2jRG:1Q6gCA^^P3-)(Sr0BhGuITRZP]Jpo"?_3J+Ql<tG"`(gEh1j_+Tlp1.gsE(+DP3MHXqp%l%@KO_
|
|
||||||
6SS88qFtkEFM>G8<Hlkl!uhG.WPjpBGsNNP+7SGtrE):"Rqcnh&F8Kr1#UBpPs[b8NrAl#7n&;ajOh,N
|
|
||||||
9Iq@\aPe0&o7+s8.dSUodsYp6&_7P5TP^mH_o;#<kC!klG>c>qeZjUG4DGYK9?k!*K9n`#;<'!Sd,HQl
|
|
||||||
[3@Ybodn[VNie*F=VH4^r1gLI6f`@IKbQSE2leX6,E.gOTi8N(4;Z`=/Q?uX><LXHSW/6g>M9RHrUl\u
|
|
||||||
4-l*4DiFJ5!T)c3\9jDA^G0eRqfR+a,<`.m=@Zg4e^Yj\)UWm0Oc":W1M/`?7dlC\*+eE:nm]g_"^.ND
|
|
||||||
ICP5[m?D]LCu>3!4)KOU7)q@:2c=3>NtJ?MZ&X?89/ioUL#I`>;uO`QDAPKC=@ZQj\mZUng5q>EMNp%o
|
|
||||||
OAUn^PFrHZoR<l@X<Z\iGmK7TVL:k;NeEgUShB0:(_p.?>2Fg3G]H](E^?lh[H!h0:>0qR%:9RI.LmG6
|
|
||||||
<A(d&(h4nd"VYHQ>GVh2,;%MGMXh?j@$HVf-/Ph/HaOfps6Reim,Xj':LPMd3WKhlXgcW9J%F&hI;1Q6
|
|
||||||
/VRP;=E</,/^#$"Ae1KLY,#!oe7gG*7^8rajZIFGq:'*"&ga9b4F8j``Ri6kK8F4%)tsX4>ie[L`.m/\
|
|
||||||
",3@qQ5B2`bI,IAXD,D:m83Uj/*4OW74HVWqpOIa'6I^G'\8D;5cTl?@DpnmF"<hDAQJhp(Z$;LZfp=(
|
|
||||||
(Pijqk=V<+,j,Y_3-HIbnAm>c(rU1<+Z/[44o!@cor[`J0q;&"f0/V8k2pchRsX#U'_nCdZHlchR>I!b
|
|
||||||
-)Cn,$_1:p7U7et^.5-)Ic`]``64Aag8iK'Wu:T#)2qcL`)GO<k/DE5GJ@hMjRlc]F1/9qSen>l.T#re
|
|
||||||
LLpgqbbZ`98moq/R@Up;>Og&+S1b_(LN.*PU$D1+QCb:mfbE,lq)4i_aF?oJaqC+2..?.4C?&9.g,-It
|
|
||||||
%a@33fh1&.jVDAoV&8:9)?,"`2X^&&R.7saRe+)Re1Z*I8RH3?;]^@2nbu2lau]slr<MdrUa?HQe"<]F
|
|
||||||
5Tbm*ZAk-L/`J=*XRGVJHg"o%Tr,o$JQfA?<iBq>#%O]b\<C_*M,t'qDX[AC;`Q3h%ai"War+5FDMecX
|
|
||||||
Noua;4lbuX[sW\S\68-HHu\RNH_c[L(9blEHo[t@XTF-uAbAKpR_/L2cK.F*^7T6eUD2:GHii%NjdBGM
|
|
||||||
WB=gKM6/mQK3qHH;s16`4;U>0)P]EmY"Gti`%Q*Zch-["jZ0qqBP>RLQoV61kNUYcWMenkq5SJPZ]sXk
|
|
||||||
`h1Cpi\-Y@e&][Q@?fqJ44/(-]P$>?m(VO#hQ`@/7!5<rS0D[Rcob0Y<h<6J5:b(P=\W`NnVm0so&Sqb
|
|
||||||
8o7lVad;a``P7WbpI^f]B?k3>:EqAcj4tceicUJ5?DgGA:'D6:nr/[>7k)d+fsakNfe0*`(Ii[&*/ldK
|
|
||||||
T:C;:j*iE&W<bB#5kmR>[[^Hc96h-TW`$HP1QufjK4CuF-RUlPH%nQZ/EJn^b:2OeCum[ba>MF:bFGCk
|
|
||||||
B6RO*$0-I&dAtBk=C=6.,O7BllL]IE&TkPWL[tB2Ros4.+[%\_ge0l?j[XBWB_MnX#s)+YQT*f3iq^(Z
|
|
||||||
T/M+KHQuNJ$6.2k27PZS=>_3NGbqi]%IWXA"C-$69$>%FOCA%>i;E(S847Cnpf5rkmr")kGJ"OXe$rhi
|
|
||||||
^"I==QT]u-/^a_rKJp@/09B6gq#+OL:*3T^cBV",N9EWRqJPqoo(:Ja]qm?b>+LAfGG=N33QA5J26D:5
|
|
||||||
Z0R`9lZWre-0ocO9ulq=i688EnXAadT6r<Dmi%EG;sOg.=E*#BeuuUm)a:SA%ZpW&[-F!fl@\9g40D`>
|
|
||||||
\\2M!dUCm('f>GH*i4rNfX#2S99eaFqAN+oJ+@<e1i-7MQYK0cC2OGZ+BJITD0W=$_6M3L3JQNM^rq!"
|
|
||||||
6PT=1*9?<@<m`"=r:p,;r."_nrm<t,YLaj'O!/$'VgNUdJFg%58,r2H^Y^@:ldu.;VtX=O~>
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
68517
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/Parent null
|
|
||||||
/Type /Pages
|
|
||||||
/MediaBox [0.0000 0.0000 803.00 551.00]
|
|
||||||
/Resources 8 0 R
|
|
||||||
/Kids [5 0 R]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
[/PDF /Text /ImageC]
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
/I true
|
|
||||||
/K false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
11 0 obj
|
|
||||||
<<
|
|
||||||
/Alpha1
|
|
||||||
<<
|
|
||||||
/ca 1.0000
|
|
||||||
/CA 1.0000
|
|
||||||
/BM /Normal
|
|
||||||
/AIS false
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/ProcSet 9 0 R
|
|
||||||
/ExtGState 11 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 12
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000315 00000 n
|
|
||||||
0000069260 00000 n
|
|
||||||
0000000445 00000 n
|
|
||||||
0000000521 00000 n
|
|
||||||
0000000609 00000 n
|
|
||||||
0000069236 00000 n
|
|
||||||
0000069714 00000 n
|
|
||||||
0000069430 00000 n
|
|
||||||
0000069469 00000 n
|
|
||||||
0000069571 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/Size 12
|
|
||||||
/Root 2 0 R
|
|
||||||
/Info 1 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
69787
|
|
||||||
%%EOF
|
|
@ -1,285 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0" xml:space="preserve"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="261.7582999999998" width="631.1152000000001" x="810.8848" y="142.00000000000003"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="296.09375" x="26.51035059931519" xml:space="preserve" y="9.42072533356739">satrs-example Component Structure<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4579944349315068" nodeRatioY="-0.46400983146067415" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="217.68500000000003" width="166.9147999999998" x="819.9999999999999" y="180.434265625"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="166.9147999999998" x="13.181757989188668" xml:space="preserve" y="6.728692946816665">Application
|
|
||||||
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="39" topF="38.90500000000003"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n1:">
|
|
||||||
<node id="n1::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="234.33926562500002"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="113.94921875" x="11.482790624999893" xml:space="preserve" y="4.851562500000028">ACS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="270.5687968750001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.884765625" x="12.515017187499893" xml:space="preserve" y="4.8515625">EPS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="306.84403125000006"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.923828125" x="11.995485937499893" xml:space="preserve" y="4.8515625">TCS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="136.9147999999998" x="834.9999999999999" y="343.119265625"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="81.259765625" x="27.827517187499893" xml:space="preserve" y="1.703125">Payload
|
|
||||||
Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n2" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="261.7582999999998" width="498.25116669136776" x="971.9147999999997" y="151.36096562500023"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="content" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="4.0" x="247.12558334568382" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="7" leftF="7.00529999999992" right="3" rightF="3.313607121059931" top="14" topF="14.073299999999762"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 4</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2:">
|
|
||||||
<node id="n2::n0" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="217.68500000000003" width="441.0158999999999" x="993.9200999999996" y="180.434265625"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="441.0158999999999" x="16.916359570308032" xml:space="preserve" y="5.969557999968089">Generic Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.46164229096885634" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4725770815629552" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="22" bottomF="22.006468749999954" left="0" leftF="0.0" right="0" rightF="0.0" top="28" topF="27.68500000000006"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2::n0:">
|
|
||||||
<node id="n2::n0::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1151.9280499999995" y="281.84403125000006"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.837890625" x="33.0810546875" xml:space="preserve" y="4.8515625">TM Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1151.9280499999995" y="330.5687968750001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.7119140625" x="9.64404296875" xml:space="preserve" y="4.8515625">TCP/IP Servers<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1294.9359999999995" y="281.84403125000006"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.1689453125" x="24.91552734375" xml:space="preserve" y="4.8515625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1008.9200999999996" y="223.11926562500005"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="7.53857421875" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1008.9200999999996" y="267.1160312500001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.7021484375" x="3.64892578125" xml:space="preserve" y="4.8515625">PUS Distribution<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="125.0" x="1151.9280499999995" y="223.11926562500005"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="20.41748046875" xml:space="preserve" y="1.7031249999999716">Shared
|
|
||||||
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n6">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="50.0" width="125.0" x="1008.9200999999996" y="311.1127968750001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.12890625" x="23.435546875" xml:space="preserve" y="6.703125">Satellite
|
|
||||||
Mode Tree<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n7">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1294.9359999999995" y="223.11926562500005"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="74.7861328125" x="25.10693359375" xml:space="preserve" y="4.8515625"> PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="57.265600000000006" width="631.1152" x="810.8847999999999" y="411.39428125"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="261.8125" x="166.89412267941418" xml:space="preserve" y="3.144146301369915">satrs-minisim
|
|
||||||
Simulator based on asynchronix<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="50.0" width="631.1152000000002" x="810.8847999999998" y="476.2958625000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="374.8359375" x="110.3824039294143" xml:space="preserve" y="0.12842465753431043">pytmtc
|
|
||||||
Command-line interface based TMTC handling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,312 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0" xml:space="preserve"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="372.44000000000005" width="681.2" x="808.8000000000001" y="142.0"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="296.09375" x="28.614190924657578" xml:space="preserve" y="13.404178370786525">satrs-example Component Structure<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4579944349315068" nodeRatioY="-0.46400983146067415" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="311.8450000000001" width="155.39999999999998" x="823.9300000000002" y="187.59499999999997"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="155.39999999999998" x="12.272399999999493" xml:space="preserve" y="9.639200000000272">Application
|
|
||||||
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="207" bottomF="206.71046874999996" left="0" leftF="0.0" right="5" rightF="5.399999999999977" top="45" topF="45.13453125000012"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n1:">
|
|
||||||
<node id="n1::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="838.9300000000002" y="247.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC99" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="69.2216796875" x="25.38916015625" xml:space="preserve" y="4.8515625">ACS Task<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n2" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="355.9183000000014" width="539.4029243565859" x="971.9147999999997" y="158.5217000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="content" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="4.0" x="267.7014621782929" y="0.0"/>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="1.4779288903810084E-12" left="0" leftF="1.1368683772161603E-13" right="0" rightF="0.0" top="14" topF="14.073299999999762"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 4</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2:">
|
|
||||||
<node id="n2::n0" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="311.84500000000014" width="490.5852000000002" x="986.9147999999998" y="187.59499999999997"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="490.5852000000002" x="18.817724356585472" xml:space="preserve" y="8.551700000000238">Generic Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.46164229096885634" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4725770815629552" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="57" bottomF="56.71046875000002" left="7" leftF="7.372800000000552" right="193" rightF="193.19119999999975" top="28" topF="27.68500000000006"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2::n0:">
|
|
||||||
<node id="n2::n0::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1144.3088000000002" y="284.00476562500006"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.255859375" x="24.3720703125" xml:space="preserve" y="4.8515625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1009.3300000000002" y="397.7295312500001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.830078125" x="20.5849609375" xml:space="preserve" y="4.8515625">UDP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="120.0" x="1144.3300000000002" y="335.7295312500001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="81.1298828125" x="19.43505859375" xml:space="preserve" y="4.8515625">TCP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1009.3300000000002" y="337.7295312500001"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.1689453125" x="24.91552734375" xml:space="preserve" y="4.8515625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1009.2876000000003" y="230.28000000000003"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="7.53857421875" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="125.0" x="1009.2876000000003" y="284.00476562500006"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.2216796875" x="13.88916015625" xml:space="preserve" y="4.8515625">PUS Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n0::n6">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="40.0" width="125.0" x="1144.3088000000002" y="230.28000000000003"/>
|
|
||||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="20.41748046875" xml:space="preserve" y="1.703125">Shared
|
|
||||||
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="284.0" width="180.0" x="1279.3300000000002" y="208.7295312500001"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="180.0" x="12.572399999999789" xml:space="preserve" y="9.817168750000121">PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4301533333333345" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46543250440140804" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="24" topF="24.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2::n1:">
|
|
||||||
<node id="n2::n1::n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="247.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.255859375" x="19.3720703125" xml:space="preserve" y="6.015625">PUS 1 Verification<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1::n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="287.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.39453125" x="10.802734375" xml:space="preserve" y="6.015625">PUS 3 Housekeeping<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1::n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="327.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.529296875" x="33.2353515625" xml:space="preserve" y="6.015625">PUS 5 Events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1::n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="367.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.9453125" x="31.52734375" xml:space="preserve" y="6.015625">PUS 8 Actions<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1::n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="447.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.205078125" x="36.8974609375" xml:space="preserve" y="6.015625">PUS 17 Test<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1::n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="407.7295312500001"/>
|
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="116.8515625" x="16.57421875" xml:space="preserve" y="6.015625">PUS 11 Scheduling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,835 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%âãÏÓ
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/Title ()
|
|
||||||
/Author ()
|
|
||||||
/Subject ()
|
|
||||||
/Keywords ()
|
|
||||||
/Creator (yExport 1.5)
|
|
||||||
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
|
|
||||||
/CreationDate (D:20240208115813+01'00')
|
|
||||||
/ModDate (D:20240208115813+01'00')
|
|
||||||
/Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/Type /Catalog
|
|
||||||
/Pages 3 0 R
|
|
||||||
/ViewerPreferences 4 0 R
|
|
||||||
/OpenAction [5 0 R /Fit]
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/FitWindow true
|
|
||||||
/CenterWindow false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Parent 3 0 R
|
|
||||||
/Type /Page
|
|
||||||
/Contents 6 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Length 7 0 R
|
|
||||||
/Filter [/ASCII85Decode /FlateDecode]
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GauF[afZr%OsN$$?ZCN;:U+hm2(CkA#-&YV>R3Uj"qa0Zrt!2qelkQ>N_hnSoNp?Fgi_i8SMXM?UQLkW
|
|
||||||
e+;HuHN!Fgq#t!&ci8Ckq"<6gqs44=YJ4\WB8(gBo-Zd&HGAJ&?[qt=s8;3_IbsZ<ro;?)qR?Kf^]*VM
|
|
||||||
I(YXBr:b^<oABT>J,1\ql0j-"j4!&gj+$$s5&'UKp.sCidLNY[7oB&2WpB]A_uDL$^\V;XRH0!YUA=Iu
|
|
||||||
%Z:8Umghk_1;<)ME\HfAn5&"L'8_D*5B/gc\%hpGs()lA%t"(7M#VbsVm$-fq+9l_qVV<VBmm2JkuL@&
|
|
||||||
[aTHYT>Eb#n(&Q(k<ErKs0ocBorhgHn8:?1G^liTn".;bW8DYY?OPZ(F0W2;3'V=#m<e$"eb]#BNb3K7
|
|
||||||
R\S9FIQYq7s71ehq_D-_UUD7D^Xj+(BBsa_VmUo5MDlhOANF#6T?Z9*Gb:/42h009W6Q>FT>n3tT5N]B
|
|
||||||
2'"Qe2]"sdc^+%2*)H38D+AqtIu!]8IcnkYjmKW<Q(P>El*q2#K`?4E:[?L)Y4eFZSX9A"(i^"/Zu5d+
|
|
||||||
[PqQ,<Bb2C-DRQ6kFXm.GM/!SG4^1DL\IK+n!t!"]$n8C8#3Uk*sc@^=6h[5%7D\>X`i-s!Z)e[qHeEq
|
|
||||||
6Td_IHQP0B9:X(Op2k::Mnce\8%[bB(epHns2=+T45Pm3POJG\"T#$9=s"MICEALZf2kM0N'8MZRt_tF
|
|
||||||
^ApFhhMohoGKcgZoK`Z%H%1=Ai:#h"\A6u*ZT9$$3ut^UH3;daFNKp5AS`X'4T)KMi=4XYP;M[BBmHc&
|
|
||||||
d=>1.FaH9C,u/+oON<ad/#Bgn59,Z.?'+`N+`kn*!Pci0KX$iD[6^gmNQ1'$cTY\:%:(J`@mJW_BQ[Il
|
|
||||||
ouiCd)m*GoX(ffIBUiCFR)H$iI0P#=[<aU50WGj0_1O8:fSd4NbeTeTmI*pIXPmgK:!W*li6)Tecj$Ir
|
|
||||||
n-2tkZ_edrf;sJpiku'`LU4CW!c[#NN]-[GiP!M8a/.O^IFR#)jHkLp-%qg!5<nB(dj[f0:8>h4^l[$;
|
|
||||||
nV5r*b6>+C@s4DSIDr^dmaW/@;N=Ul8::XNN]&WgN-=HOLO-:tec^:6A"_?n_+N'WOGa;@k,`91E`RLZ
|
|
||||||
LH[Ncfa0h,]#;S73Fb+=l&lpRhZ<+-]n[>'^jc+OILMp.dZ/h6pe`LWm]Ns6%O#6iZ^,9_>UMXUl`b/7
|
|
||||||
qj02lQV#h^neSNDZTT#)Y4`idH[W!p*fR#4HEQV0V.DN`G`\!Z&OY2@ib(TAl^A8e>@%7HAN$?u6_Z!0
|
|
||||||
NI@hB&&A[V&Q3PR?8%kr?)47'5tb4A\(,1'S"uiMO,\nTghqlEc,Zt#7Y2E$7bh>U];BmDADQX&^s0Ul
|
|
||||||
o0!&0[rLlQ&"f7ZI8Jh;FUsq&'%]s72l4MtZYWSB,55R?fam#iYE2"4+#l9/BiuOB@"n"(I7#AnF+oR)
|
|
||||||
gH!%p-$sF%Af6.R5!5=g2?#dFTDaL55<]%'Sc1i<k/2>&I5lj$?#.dEc\H7idL7QA>J;0V6;u"eHaL3r
|
|
||||||
Hp>'rdsWJqT[>f/Z'sok6dDa'UGcP9A3PBZBNBA%j.NA8!2#\CDJ)0'i@t91'0"D$L8QG+dBc;8gDk'6
|
|
||||||
^nibODG5ZkBL#1Pd:Q]?B$0Sm\hJ%[J]`45Fh*9AP?#%/)'R+jY=`!3jps7/>r.9!0.bHL_$RG[T)Teo
|
|
||||||
-TqD6Q$U^Em'l0XTLplu)8g9/ci:hJk<E7#aRao.bO^4\BBSc%*oqABci5,4rke1&+1.^4)rBm!8"TaV
|
|
||||||
[g(1Y6'pd7o^Co;5GQ?ie_p6fA'\gI8USnHq*-\lcSKD6K4k4Ik<C>q"$HU9i.DTio:PXJr221_O6?Oj
|
|
||||||
Q1iims/1WhLAd[Mo!6QIhO_KAr-84I]KQ9tDU73FKCB0UQ$%BO_XdUP]M!SM'=NOb^O<"cT"jr-K]fN[
|
|
||||||
_P=F9hk'ji;]Djp81Dmb5s*X6Ue;'@s$:LUk>)e`s1o`Ik^G?Nrs'k&NthL$5BMIk0@/PP$NuB&UB-,4
|
|
||||||
5Nriso-[i-q&;'Cji'?l".PjMiPi<+&]sW/32-$9r+uMYfCAcur^khfAM3>\om_]ra&pO8g8,$[`sh_o
|
|
||||||
o),=!i;#TBRaLE+I-VTXnoL;TmZQX3IT<I!*</-G;<R6?:X&!o2t!]\FFU(TdMiG`a,M6`AlI<7[b:()
|
|
||||||
5%oLk+?+(eK0;E-eohF6>klOt:9c-bqEC;4;/a_KbHna/L.8k(L0Zp)?p+>-aY$bR4?ftA5>TDNO1ta/
|
|
||||||
le9\"iosoqg##3n`-46f]=O$W)0/["1jKU1BS!Q72s$"+*tS@E"P3^pKb)GJ^&D/?ErNR0L>8qJ[lQj>
|
|
||||||
Hm_%(eb,TGSRYAA5O\!D"5W54I$NQc;8T8]m,4TC+WfJ(p/?/NG<a;b<2"em2&L%@G&;^4LCE4!2ubju
|
|
||||||
mT>J2j;_1XfaLgGr[#Y@A(_;K=Jq>8X3Kj1F,JH+R`m@d=0r`f`jaE2?-k"EKZto"Q[2?J^bVCnGu0*`
|
|
||||||
/!q?s%WWb;Sc5+;lr7E.>kW#&)nhagQTr0s?E;/<*e(e(Q-]%.qJ<\NmGTl`mI$KMGX;'tUQON<GQ=Q7
|
|
||||||
q>GWu%52>/=1.MS^AbbE(t")'AHZG>-&_]ZSL-2(7hBi))Se>\E+:H%G\_k:Il'D57M(73B)+*D+p`p8
|
|
||||||
4V7ho.eSEY47+36N;"tI#r90BC#+k6O!&6He!:Y50\dgYO4H6H@AP$'*/NH@?q(R]FE5N8Y"0Er&<=<a
|
|
||||||
)h,\`+;eub&HqQ<<Wp7Z;,.810]N-J1h]l-`oo5%d6C+F<DMk:CU==hkuNif(fhp/.,C],HOlnX6=>is
|
|
||||||
fIo$sYKqBIPgT4$r5.06lh$;`7UWp-Ds7jeTJRl!(\S,$@#lH**5YWkR'd&Kkk;gU1a)[*3u(\\?Eima
|
|
||||||
LaR_rjufaZCDL8-3=V2/KCMpD=L+!R?FWWsa&j3F#g<R+i=5b^.'8T!$5uk'0S85I!j9fFZm0JPZ[P=X
|
|
||||||
IB9-mN=tkNL=%;_,mYsA3I5Ku,j0IViQ]E?[s,sXEAq$F34fmCi1!C*K-n7+LNRd>MJ]q;@[PQfB10A=
|
|
||||||
/*aB4G92eWP`EE_[Z23@6fTr,^X/op*mD%:g>9DKLRr2ejP5K&f+kp(2n/]H2UAV_Gh@6M'o_!]W:Z`O
|
|
||||||
B:lSTCYi5c)2r_A!\MtcXg&q:gSma07p6V[^U_#-ID]L[&88p"&O,7FE6E1pN/s@d95]qM^*J'HnNd\*
|
|
||||||
I!HqhKb<D-;9g$9K1e8-0gdk5#N^)f0+UXZ[gr2q"1iEl;oeZhO65edmC[C?2?!Lf1VR7A5qCIE4Y)UK
|
|
||||||
Q\DD@rE(%<VAEQd`:Is36]KeQrdF8G:Wb*;nSA;5-2$3</.X)\ZWT'5a4FPP>QS\\m:s)(R03k@lGT,&
|
|
||||||
NW-^Z!C$N&G4u'J!C'&u&A8@E80smc+qH"0N$>YY[UWfrd#h^_[U8Ql>mGW0^!L&qL7s[>l]Nps\,qCG
|
|
||||||
38V$Icnr.aB00qW_ZBT[etRJofeD$mr9AB:;a$j4A:MKtE\qO5ZD@J+clm3ZWC.+g$IX78jpNt)&_m!k
|
|
||||||
r&T)T%!WjKg6<R3#2PH@GU2N>e@lngO,+<YLR*fBTLAW0$S5Pq[qH6;?S1g`]gkNbS.m>W2PTuC#LrD&
|
|
||||||
ko31W6\4.!6+:u!1A3[)23(/.(o_rj3h5r)n3GQ+:lmiOl0B%mh,9HVK[n([N".+Xn%tbeq"uMjo?SAd
|
|
||||||
4/E[3@'5$()q_d7ENFs*oiMgpL@r2[`.k[_IHI9+LsnKAe@,(:@/CidQYc'J2a1j.d(WQ!>Pi9O+u[A!
|
|
||||||
Qq:[=MW)A*&E\j0T(Wo(^F<4%rge.3`1)*A=:oK>WDJTr<3?t@NHOpC3jcft/?X0^l9G'=H,u4#iba%=
|
|
||||||
Yp'[r'ldVg&T@%j6_C_,[b'bQ]DkA;[<_XE[Z>U6Li$rgilFTE*%PJc'9QP$.7ch-07uW>7Ju9ik>CUX
|
|
||||||
ECK4R<lFt`fZ9t3er+gUL^f\Q&K?_?a(>+qp@DaACnk:o9X[]8U9B@^d:^a/PehQ5o@^+J,'Jtf?a1f(
|
|
||||||
pYTOmE(-75DLW[3D(#ojg#CB8kJ<IDWAA(L21lDBhAsi-E.0Y)CYe@,)"agaXjT!BT,"e8XOP'bK=NDL
|
|
||||||
=Os(gZApj;[L8A<P8"q*:LU>?oZM*HM>(oY+29jbdn;hZ\NKfo_UoRC>$i2I8aQdEb_/O#W+>3JHS6d\
|
|
||||||
>7sC?dT<J)@LHQJ]p[JM5]9c3PjodjNL>25[!nZl3igEN?]f7GCHn,*E35UHq-m<t4n`RS*g&TnHCK8$
|
|
||||||
(;iiOM;CBrD!$`BQPK+JpC?Z*]pC5akVi-J\@#._LMVa*VOc:hLf(JNWRE"P)FO/:Z]F.N&^E)UZFI].
|
|
||||||
^KlRA@Pj1K?]NS@=du[c\ptE9P)nNE?cUmO1p#]gm]ZhW"Vu9Z4+=`"E=D5j`nRg7hM=7ahP2.cK)H-o
|
|
||||||
@SYsg-FO#/rEGc5#q\.Np+WrKXgU7Yqs;B@rIpu\;j+NWCr/A4WpsDjK?lTn.Oq,^U>Sc'(WBZ%s70Np
|
|
||||||
UV6%fi0aU&moB?lda.(QcNTBZ_\7T1"N4Njb$+L`DqHLfPu`i,U3+V8i^%aZg$/+8(.j\h110o9p2%g5
|
|
||||||
Il5%uB9.kC^Vh*dQ9Xc_`eqM9kH.)toLK\/l0B$j$dtdUJ45P[6dKSO"hTKp^tS9R'`iRKid0SJeP92q
|
|
||||||
@TsiN[^<Y[(Y2e`XMl5Ef\t#*Dg,42fC\ch\6j*]6:_[gipc>Y6-d=PNYSiR<lhmd2op*(ooZV#R<Z_*
|
|
||||||
He@9^e[#J:.3d6#[+K$i]XG+*br`D52=8$)ikZ(IfsKmNZjIBF+g$i_@lu9m4>.]7'?9f`(LkG[*lL_P
|
|
||||||
N,K0m<SR:L=#OatP+R<q[>:8SCWqm$Y:>RLnJ-!\+oi,ln<@bf,S]8@53'c+5Pr7$^=D[:Yp'/YhTRZ%
|
|
||||||
hD7Wt9X@p+_3=@>CeTf0fs=DT`hBF`2h>HWf[4*m:em(tfi28`+oo%b"M.b>^imf_^Q#G+]X\15'l,:[
|
|
||||||
!!mAZ^Ca9;`'FDfru4uA@)GcJ"Q2SiNA-6+X?V:\HYZEu(*4nf,-<Ra.">ScSe$:/o[WdL?Coc$Si=gC
|
|
||||||
qm-g4^Z_paJnNI%6.C%.4T_9`_Z&f5M?)k@L]FADd(\eKFL+G(h7R5<@hA^h+X-o^&gIB<4>#UOf6l6[
|
|
||||||
HETY>s#SfagHc]aM`YZ\Yu5ccP2q%O*"_T/*<FgiapBeu<Plbg?LKbq8A)&c`=n%-#g'X`A%f!O_PcF@
|
|
||||||
A#7JSEZNII_5=3HZ>^^V[/*>`9@6&o%4WN`7hkS:^$,o^k<qAABD+Hm^V_jKPiQmJkaB0FR5rR`bT8T$
|
|
||||||
40'BDGS(eO;#Sg*1VHk]RD'$S9Ke!rV>eUnHZ8u0=6j@0jI&00LuTX?%j(;c*%cGJJYKotm[;$IiU6<N
|
|
||||||
o$$`+;$fAu"`O7O-r/+4T9$\HplbLo%slanq=TC=F:8!]8)]j>3[4X]nSFALV"Yr0_H5]'WCN-NHq?D4
|
|
||||||
kLL*#aF@on0e^6]ht)6G[Y.D:L^o1?F#<Zg7Z-p]3fnh^-1iT6GgR<Aa-e&p3kka>1-Sg8IpmU(J`2+u
|
|
||||||
U8KeuG[:RO=Rp[+:iR2@^k8G:g_4pY]"duR[8?\H?[7',HNB.r4<6ppSk^DS!lAS4)7/g`A`r(IT9u4#
|
|
||||||
A,'H>ibPoJm.Fra*3WlHb004<&%boc_2]@"2skKe5Q4C2-efL7@fBcZB_M\MCOS"u<O1O[aM<`q5/3JL
|
|
||||||
rAXEujFa8!>e`I9Sg`ZPpNKIKda:8TVl#+5fp]\QeI'!\_+,#ngg,E<9'[AV*n1pYo_ta%Kac0RA3=.j
|
|
||||||
M>=%>qG%WH4FPq@698jOR\0S_>Lq!3>9?Je,O5lbqCV$p-0ljf]cG$>A71HblBh'U9#s'WnpIhbIgFO?
|
|
||||||
cd[&Y-c\-Kf&3J5WmDuh(C^u*$P)\TjI:Y?;2+_fUQC$i$5m3f5nl]Ug.PJM+q!Q_FF&lg1gAYjla2sP
|
|
||||||
8XpOf+6rAb)37*Fh9_q6fnlZfO,s;?oF\JAXk5hE@f+N0m6`D4QW@oUXk5hE@XDh.p<nic6S$`S<idsV
|
|
||||||
=?j0%I;-[sdYd]b/(P65=?j0%rQpGp6S$^uX]Sq6Z+dRhHQ?:p^9jUTRQ=^-5."dGBuTeoMX<b]g9IWP
|
|
||||||
(#)A3[bnH$F8=?8LVPX]T)XV7`?3EU@\.TO`[M-_n4S*5@\+`'_Ilc<=pplnZu4-EId!"`i5:u[HbJo0
|
|
||||||
1]*iEPt_L8ef)m:[`S0\;[s<Vh`OUm!pPq:i%t@-Y(#E(+.rSO3@*gW&'1RB4'rjq5^j_YoELK]a0)6i
|
|
||||||
I.#(`rkM,\bF]^BoeU^1-u?<<Z(W`0cfX\:[3k)'N)c2JR/JP"jc1'BnL\9=c&pc'/oRC94j1L)NE2Xl
|
|
||||||
>sQmGlpIMm^h=?<.Rol+q7+H'BXj6"YsrVhdfl2>39o!#'=2BC!PREd)rFc&Gn5c03/Tp^BQs'`S%&Pd
|
|
||||||
lGM0`a<G5+(4NZ_27*%lhfa/'=)3'5-0R+BoKehYm="2Vr4AMSoD?[)$_20j,+eKFI_6!-jUB,m4OP<L
|
|
||||||
Q4s,%O0:>"a3aC969cQoPqmIOiCq'lNF-g$3OC9`9EF7XB:4<6dE6OS(%:5k/W%5fC3>*&R??#0IsW4u
|
|
||||||
km\]dn<ZV=DKL=4?&W,K-k+t$V#R/i7I-1d^/2eo;W%U@TJ&pjRQus`qFd'JKP*e!WXJIU?a2C3H:P%-
|
|
||||||
ek#;g8;AJf8ZdO(2JJ*Q$[M%SfRs8S%%AsE>DLJo7iXR.lp'L6$,EhQ@t2P@\*%$gr!6&cM=tGc2,:kK
|
|
||||||
kct4RU*%E&&;L^^+#_"0!fp%mnn\3[A(-pE9c-M8#No4B0t.HSa!M@in^"\(,S'\@q+htgiLUS4KXm/'
|
|
||||||
rrkN\2]_Gc)i1D[80u5nS6P.hmnTN!jNl+Ql]ekg>VoqJc=[AF>YB#iP`IX5c03&iqd$PArCZ"+_oCI"
|
|
||||||
+qDB2%aH'/b76cX^[(-,\c"/8Ji\A4>drTZg4\?@?VuKUF[f(j4G?]*/9=SP=3O4S>)2]LK3$V%@7F=1
|
|
||||||
9Q"8j5o_+Ujh(Nn0@SYlhO(9BW5m`^/?L+1Q'NUWY+&Lhe@8La9e/1egD(IoekT@j9="h6P87sDb.4@"
|
|
||||||
,R`Bp.e5mK,fZIIhs!"i%iG@2*.d:&_oXfdIcB+D:QH2Q#em(S*ET-[i4=\R.\88#,EDH!UFW[qWDftN
|
|
||||||
_HpdqJ#5BiWcVfe"^eBA#'iK)28@MDX.=CtY7D&5f](CNhL6L3],NtcTiX?']&eeFWlU2g5jH2YHV!H#
|
|
||||||
fmZG5*u$^hrbT2JUKb>>DOl"MNpIm<[P7^HF1r%6n?sbj'[]&&"^Sj]Z08B9Ph']pA&-`c\8Xic:IJd;
|
|
||||||
#:lSPoB&rt!?M[6E,dQ[a:2P*G:%U8`*#4BV[L-\^8\o[fRo\klUC8F^lRh*\S1^N/`g(f%buag(#O/e
|
|
||||||
iirPX^,+g$c$PrpUY5qnH7-_5II2r"R_Grp?CTuL\RB@dB/q^LJj2RPP'b\>.*6`&TUookUA)Hs-?1Rh
|
|
||||||
Z?buLb=#*n.h[(J_U8cO[6J,'<^$J>j`qEdo.W#=o.[O)cQdMG$_E-ILs/"1]\AHZRoTRgl;qXL4b%E2
|
|
||||||
g<@<:_Xkht;'3^Ia=29k10R=q*^nt4#`#=_'e=Uj6bi8!rda*U]]S6%AnY"q@Aee))s/Y[+tPf%$p0`Z
|
|
||||||
"%559e)K"fVfL4s`p?Z"o#M0c]^/VCl.E*C8b\[<)sWc\b.5kSREit1\Ug:aP+<5Gn`9Q/5i=H)ggBor
|
|
||||||
Z;]>@*HoS%$ThoJ?$8hMJt4Z=eqZ0_HO%Q(eZRKF?KD?T00<`d2Sr4/#&0uAo1PM,hm%8&lN;be8q)&\
|
|
||||||
L>s<Z7l@qV6i$:BF%OeIhO=3dkDk_>YBq*-$LGA^n&lZp=Xu=$6<5)U]MV%/D'pTIPk^]Rn_J]8f:9%2
|
|
||||||
mij<$e8Pk8eBeVBe9ZOiUDafY;&f%^A;`@o]D:;6#ti&R'p[m?)K)762'_T[2.,oD?h9$jXblQZm$.il
|
|
||||||
D^:\CSc=5F=(2I$Tfmu#/UaHgWkI<Mf^`kQceXoCn@VqkU(J@.NX4dQ[8M*=C5lph"P/'KWbt7Z[9'*#
|
|
||||||
&N/%,8pgm;(bt'0#WqcQXG=j%)nIW5NHJR0Z3kJt9).54"aN@FX`Mn(1hKF$p.`+HcJC'T[sTr\c]td&
|
|
||||||
?cCe^,s_FeTlKE]a*)Nkin%[l.a46X(CdoV+qDFqabtYA7CYj_]b4c?Po,*S3>T?PN2Na`EQ=gX`T`rI
|
|
||||||
#U8HZT++#OUW'f`f&4L!.hQ?s=(6tGkNaWX>'hR!ro6bpJ+!S/-Wt_rra(&+@CD"BB!_E4QKEBM>kid)
|
|
||||||
D\92bs*F\(a4Yq?`S9L1EM$.O?R:%5eaPD(^9A9Zj=KXHdMI(LJ"EimZbZ82qtmnfbMAAFrQr]jK-u"V
|
|
||||||
pQq`6EUneBTCMhtfQAt=A*+3S>q+T*8(uE*pOYP,Ar*os<E%_.mB2T.b^D1`43U_A_Q?Upl&A^Sq>-TI
|
|
||||||
.t1!KIC%qIZXqTilk$5R&@KlrE"<\GD?;$e7J4jjO^>`qnI9^f(VZ@:/?=)]4c<)6"i<uA-(S(q1cN&`
|
|
||||||
FNC&>M&Xj$9%g>'.%'%lX>=3JGZWX;)SrV0QetQfrUdUZIGo@NZ:,)VS]0:!but:,,A:9eZSUo]XVRpr
|
|
||||||
?P[\bY4qe+RdjDK"FENN'^(;MS:H'VrFu>G2_ugg?d4&ALjJ\i-E$&\&B8=W;(G,9?\/dN]qCP&$4<e'
|
|
||||||
2go\$R/cNoN3Hi'pgW8B*&>CoJ&@EeC][1?L>NarR2LJ.k)gNcB'#,Bd3*:fMW+B07qRM=@n,dgm/Y7e
|
|
||||||
.J8,2N&^o%AB)LY4(LfO1It3-i@cW9HuI(7^Ijo&O!kb0Do`>WUK3><1.+*Ub`GG]),cPOo9q>Vi.OR]
|
|
||||||
bQolXkd=H10@H'pDiXq!K(G*K-`MUuHt6hgbh^VOb!*QkOM,nBD[OHbaV4"Q;,Ec-!f-?5jBfPZGai%d
|
|
||||||
kHd[C$MHDhL8.Tr7]KgI%d1NuP.h81W^f!rJ+-LJD)CoISUUAt5u+o5)^C3W;XsQYXQTWG[g+q,>`+"+
|
|
||||||
%70<G1?.m04TC92r:S(n,Ms>d,d(8B`q,htoPs[9?M9B;2kLCP,OA4@[:[9:g?i8`^c^=q29=t-4Y@f*
|
|
||||||
EF.AUR`Tbs;bMiDF_&f6fL*)nNkW9[mX3-$O8/.ZcHi)&]bYb@o"2_k`-9/QpR(TrSC(]V2uƪ#>;
|
|
||||||
lB1=^*W6/YfQ'0X(oa2IFElYpS$tqblj&P>_-Cqh%Hs8hDiGC\%-_KPjRtMiE5]\=^k$d8:Mru+Q3]jW
|
|
||||||
L+[fKhqP!,T$`h(eW6t,a#u7p$)>=R>2,pjl"%8;q.W[#U#15%'_E/IIKL+1C$YbLpQ>V>k>C)8FaBCL
|
|
||||||
lW;%YAI300la/S&Ru[5$fnH/iQ"X)GDIK9;jnC^ir2j1_#5kIZ5-j!L["RhD,56o&ceX5DC.V[gM9L_Q
|
|
||||||
97FL)Tp6,Y"2&lEfP],AIbrOe>'X`r3+V6uX9oEHdCP?F@/UHT!k0)5E`rimL`]X)4.IcX+Pp>icdi?5
|
|
||||||
")^'M1>M#;:r82d^nATk%m&[(Z&Vu>7ZCc+6XT);T=Eg&-0l#R`e)sCC'f6srB?3N/U)aS_3X*)Nk!\9
|
|
||||||
+l0g0cmuTCgDfdZbkV-'p0QORM7^-bf8$hIcH80rA+r#<mRCIi<d&8eBg6RSf\4bGf")Mq&^,[7_n3kS
|
|
||||||
?D=geT/VK`-LA3>q&IfiG=RlHO1!`Sh<Oc(<)0ir<H/E#dCD6M's1^giE"%eBc;@0`^;u,[FU)n%VW5'
|
|
||||||
YtfU=_L=Z):]G978'W86an'(KU*-#:6#@u@CS'DCEkK;\iQk.ETNFId<d;=6mp=`iS9'`*;UX**V]e(P
|
|
||||||
YFp:70m(9e(K>XSX"Z-o:>jFTYjZQ/4O8N8m!D#OD"i_EehM4$:4?C&:rg77#qKat#TK3tb>;4Yc?QQ8
|
|
||||||
^o(P&^j&&Ihht*CrSW!4XY8OJAuJ">/9tQFVrD%A>O@&jI!E*5kV6A8rJ'G0kkS"ZGi/725&ehNe%4=_
|
|
||||||
.DH[7h(o+[7Z-@nnaWI80D)+#NV&23dk\u8T/_3I]8<L?ak&3$g(YUT#-fRR]G8Vd)L(`H4[/;oFPMe^
|
|
||||||
i8`%6FCq%&];q]6Af#W?Dq3PG2b2sU+3/_mjMe(4no8iMojB<2MgT3\'c)]$X#4j"[TE6`54lF4T[R9-
|
|
||||||
j_+PfbN--]lFi%`=6Zc[&pbf1hXpbd;qs7I0E:\PNT"5rf3n@-jfUIV"i^F0482`9mP)NSdI&k(M@g9!
|
|
||||||
+8K^rNE<bop/tb1J`JKW..cjN;Oj&rk+e6kRLfkE09]bVGM\heE0.eK`a_DtLn_Z$96]gk&gQLb%it.>
|
|
||||||
;$fAu"`O7sDQt@7chHCO6dA:+LOEQW^;$qDTXV]nGen8O-k5&T[QjK'T1tCaj0;Klr[.'jRR4M_/#Ujb
|
|
||||||
'cF=3e-c,-1jKS8,g>\p9kD\\n6c?8git@ij2jqXp^;iuddKE*?XU[]]GNZFbu#@nT'1<0N%f<H#s,-F
|
|
||||||
(9oY37Lf4B*)jJRToBnJh<nagBWU0UmQdPn0R-qCW&)lRH[^Hh]\.$1)!-\5(@g]ucN?ZGf\T^-@VJ3<
|
|
||||||
`j?C9W7k_/:^Yg1\9#\EX,>Pc90rLm58m;h_kY]#`G.!=U+$8R5<L+A^);&sb^V&N<D]n,/3XGmj>.,>
|
|
||||||
Oi[=Z7Z@nj[jKYs^fN%%1n[@hpW1nBWa"aK)RX%MG3UCZ*GX)C!L9\R0oLXKQ#8kER^gWVf$1\K[pr$Q
|
|
||||||
?*"=E?"eqL3bDHn<Ye_eUcMh\;IjR6l3/bAUPK3J2L\S?G5GVSl37"G\FfkdGMOT.ejiph503N/\0Dh]
|
|
||||||
oG7/YR=8l0>iWM@N_OY!m&S)YW_ngL;O&-c0@16[X,5D9lHRZ[omMiVIE)O&Up,0,?Op'XE,Y_F`7iPV
|
|
||||||
)`MV5(P1uTp<"/ma&@LlGI1<_`O(q:8r2aOk5F$5i]_&',8]2X1#XdFVMJ?D:K2V?N7V694p,u/[Ikq.
|
|
||||||
Z5\DK?Q_b&;`h*%1Wa##r;ciR`5lu>G7*?T::KW_(Q/2)q`,GjdRLh.]qof$q:3nj)[8G<"`O7O..5sB
|
|
||||||
0E60u_Ytk.hsLsBId)"Qg+,3@s,omirLt4rQ1&u0`<_8YT#E?u_R"K*ojn@WP?e6;cNZP,FGSq>SS(,a
|
|
||||||
3NY`5e58fU/F7X8AVjcJ:Tb`N7=U/LSonc+8FbP(LE[XL<3`\LM1R[7'k)!IY>H0G[WSI\NA:"gaL%;1
|
|
||||||
d/Yo,]$\Y;q=YX.^GHV6JmYh<fNg\nl:2*G9t(%Md(hRX9ra:`6uqWp*6_s(d,\K``76L]_4CJ8rJlg!
|
|
||||||
jBi`Y`p*5J))@a>F3l,9c\3!l=qgL(`Y-5mJJR+GT\JHZYn(9Sg#gIl0rFeD:as;0Fj@,9<:(FY(_;W0
|
|
||||||
r.\^/_AF@e5A5Ff(o:Nu)]a!QnVn%#jTq<%$YOk>_bt9o:r1H'bkLl>\88+oiY]Y<SIQ@o"$Yb=<.*W`
|
|
||||||
p\+WGkC:Bu.N>m,'GL?o',nm_f^N*_>r&G"Pl+ffMe`$H+l^l-2*)6I%X3l,eX`YsquONGBBNjd4'kWo
|
|
||||||
6;%.id9GIVYj0;[2pi8=N]?a5Y7Sr;0)WXR!C$N&m`:L\.N4[GYK,TO7220N;c.(CdD..c2nV&-H)4/c
|
|
||||||
&]a9?.q29G]U">kC$U8ANWJ4Wr"$fG$`]*j8_48egj5#g<khX>n#a5e:1#jfbK,,BmE>K`DWYWbb)o^m
|
|
||||||
9M#J9%q=FhP+A&d?_Us$U5SL9nek\]Wu":qlemWskR7$hhiVDs>[*Em($RXkoNBla8.qm68CMeOo;qK*
|
|
||||||
8W0J?:;8qp?PQA07m`r[n4$t+r3CXq>OS]b[eSD<iFqli2GO,Z$cgYn'dMfn8V_ODWPkutK':U?^7[GG
|
|
||||||
;kD[r[pQD%)aVedEEsd7m(r6o:ufDJ<TohW"YUM+1KtO$+.h/;pDBLT>4UboWZ.$u-P67UWO`@?,>?T`
|
|
||||||
aN(45=an2Eq?8Z;ZX)Y&e9-lL.h'a?LZWG8hA`%(^Ilh24V@p:qS"@1cYOi$LXNqE50#9a.CK3&B)!kq
|
|
||||||
Yn5VNTbmh#f!B4JO2UbsJ*iSP`?%VIDe[8YoR9skk`9('m$Ad-`>X>8>obs=4VA)3+i\3j99,8*%6F!Z
|
|
||||||
U_8#"rHZld#HLh2,u<]*?s,sWntu+BQ7!4\0sm0RcZ!N*b4p-]>mF`hdM)+`F8.X7R1@R*LIYqAnH?;X
|
|
||||||
XY!=V0A/D=M!^\>noV;F(LlN`j9ZBQjklS^%ifaf^]27C"SS&pT'0h+Qn$J+md9LSc0jtG/o:HfSsf+`
|
|
||||||
*;-ZS8<I91BP9Y@j3Fc>c3gL[[q9AEENe_EIM'W%nBWHPp&Zo6]kqS>^[ZKm*2q31Thuco1M!X@3b*_E
|
|
||||||
r=+heeLga+Z&opXam`''(#uD6YupkSRF/!=+JHNg?t_okjloXg[O"G"l8JF?]E+U"qrPHh-H7V3*QTo%
|
|
||||||
,Ks;VhKD/i.pYcRTe_Y0gK7P8N=@_e]%:\18(Ki;?e*2Vbq#RQ]$THMT>C\N0bhr9Vua0SnTaE$>glf.
|
|
||||||
c`"U2B<^'83/Z+/[?cVW+neN'`Ej'h=Lo^:M_[K2hYa.i3*/bdZar`IlG3-&\-d9i=>/l6N1bEsI^GjM
|
|
||||||
?,i<aU"PBsc<B#`cCdTq`J]]LN%W7<-fh)W1u9HVM()C%rUJ9bhd%ZGlOmo#im<:RBEeCPTbW9T'ha:9
|
|
||||||
L/oj.lQKr\OsRe2JWd/NOB5P?eInkfe/YTVF<Bg5YHT^@<jZTF.n_B2l<@FHckPA9W\p@fOh;Kr(6%.#
|
|
||||||
@VS?L]1OSWc`##;HbGJ@Zn[ghX_;CEi6m4YNo4>F7YoXT]:%0+`=JR_X]VUSeYX'=UX4n%q8H[X<",t(
|
|
||||||
NX;s[=:n4+%<llBI\,^enOE0uiL3U#A'/Gl]Cj<E":]Q,>Om3+Ve0Uq0&1$=%@WFX!'^?BSB9prYA7aP
|
|
||||||
+-g";nbA)%m*V)l=Konq)=l0la+t#]_m>S;Nk^JkhcTO"o:J!8K'rGRRC$aC\Gl05P_D73rmRnp9^%*i
|
|
||||||
r6G>bokF,7!?3eKrN/+g't\31Ufd.hSUitX;"nD+e(Ppq?:Ds#([Qr>U:LB="'/N;oS(ISK=d0LG<FWR
|
|
||||||
TkpV>FV0QsPPUBNZ>->%!<)"Z2NYCJ-MR-_o'VKf^'dmF<dS-meLP$$[l1)u*Z?C#R3!_0CiSCmb6gUA
|
|
||||||
Z.Y]c;,+(JZ*ZjBW6(_j>5,3/g0hR;?='H#-m-Y.oJb=DY+c7kZo1?j9Q/I][ZsD$1sUH+U8E)MHHfPP
|
|
||||||
'ig(2>"PF=&t%PQ^\l?5HOcPTN2@E=+7a/*e#S7Ab.Rb@?Z11tdlQDEG[@<lm;/_9`J0/46+&fiKt%[E
|
|
||||||
EWf.)a+Tkd]A)][-cG_X'aEYgSh];k1[o2b8m(8n?`HidhW_Y2[;+M];UEB`,,"FUS_Fu31B!(`=UcZ"
|
|
||||||
edQ!bV5bjLUGX'o't/L`-H_'KK+CrGIoOqo^g8V#^@?Pp3BpID-!CPLWhL!E5THTj[WuS@Wk'=[6nZZl
|
|
||||||
IeiO.:;7KBEF1>@G0d\%=,;>52bSMfGT*1R`:<c_rqb7@fD`[;r:s_"p0[CB^\s5.bXajGHW]/))?.s3
|
|
||||||
s0k5mrBL<05NcL%]Qm?*.S&:hBAc@_k<H5C2bL^eJ+YCe&+>">^A[Tjs-17rlb>AO'@Gt1DUU8WnK0b/
|
|
||||||
Ic(?+:@k+g^7Rn2FE;orDDm"?'5!S];&e&7Jb3m!5u$+jfX-:-QK.f+IeU[?KCcaBlFl]cI:Z8or+0GO
|
|
||||||
;&cs5I=-a[@'r(:9Q<G/TnXtJq%?`f>hR"4Xd41=Hu$[W]Fd@crs8tYeUoGWf(c-1EOLjF1AZ]IPhjiA
|
|
||||||
b\14uTVQq-U?k*R<"[Y6&_6#jLM,rsIV3$,]m&bh(s8%+#SCA^NES&7gu0A:$nnT;=A2&^kAGp(-kd3b
|
|
||||||
D-660p+:L:)rG3:Yg<&/7*_hJ'\oQYH<8-+4grnqR)feT^5E&GZ't]qf'IcsVgC]GZeO,I5n(,.4:-;^
|
|
||||||
[be7;\/,dkKoolI?b07bHYldskD%bu&S&6jF^OsD^O:tl?GAR*rJGjsAO<!\!,(i8?3A%$>D(d\+c#[O
|
|
||||||
miSVhCf[igkk!EB)kN['P6EU?jKN7cU=C/OSH)$>d9OfRLfTs&J^X'.egZ:I.=8?tak%id;\qTtH:h*i
|
|
||||||
-Prn57"4i^FpXsKXY/u140NC.o8sk'h2\5HZ0MdYW$m*UE6;^$jZ"G^M:(6.4ChGF86TI\0DM1A=X7GF
|
|
||||||
'ThF`<_/oclc#7ogUcYT2.^P9G@X8AL6P=HW'A(q/MY\B+WUq&>k<]ke(\g'59.EM&QEOtAt7f'/\S`\
|
|
||||||
1MjVP\:qnrS-s?qOBZ#",a*H*ORD-+@S]e?*AQ>AS\g18-#9b]"o?k%5C8N8knNet)@'9"UD5-k7@/U5
|
|
||||||
Fu))o>rnuR=5V1hD:1];B&ZtQN+Ykj7Rd#01,VPOZgc=27G[#+X]LeJh!o4/rW"gnR`qVbi/+]L?2Q[I
|
|
||||||
kI(I=jf<TEm6Kf\V+t_0\b7ELLJ*8j$s:6rE&kd:1,Qi2[i@!d6hgh6dO@;L&/p>r<\&F<1_)u=n6XBL
|
|
||||||
khWp_EG1;b?&;e#5i[)tiir8ce+L$CJ[]U;[m.*u\4=Zmmo[oVpHES!IBNY+0[K0*V@?8XfX!2]"qZ'1
|
|
||||||
RG5n0\AOB<-tYQp*PjW!VTd?p5%WlSm9bmVYB7oViNom_WHkk;P0V:t?A/sXc%3P0/M-6XUiKBT,?#<q
|
|
||||||
`RZOWqlKtnA:uH0E%q4X(S)aKRPI\WSa;]`(2rudrF!o^5tlM*FWeC+FZVA(n;Mo(,u,[MT6^&aZlAUK
|
|
||||||
k@m?naEiCcqe/KPD5;%c'F*;O%,p&83f*dWKa[/I(Q@5A0,%e?cjo+F8A*LDYX8a$2M(DY,]lnp0IQhW
|
|
||||||
;;[a\?Al<'_uhd?`f1Cdi:B_pLReGfS\)V;b=ojQmlISlJRueP*,FM[aLuh:"e?E4>>oBXf3!`KEh$,/
|
|
||||||
*&5X!Y]m?[%t2o9,<=UW;4eIP\F3sFO%KmH>iE"QKJ:u&>&Bl6hKm)BLYJr`]-u+&Pr\I5;"Wm[`807+
|
|
||||||
Z!X9;R'6g_OY9BXgY%XHr#je4BdePpbh\nq:[X/\We]_^JhJr*)4;mfV=2WUF]T(P9upu!$oR+L2gR%:
|
|
||||||
TL+JpfOg2Tm^R2ecic9O^2Mm0D>_<Pnr\llMk^GmQkTjNWN=Up),[TR9"j;ZMOa=8hbq5I1t(;GaTbhQ
|
|
||||||
MD7sE>lkO9!7&qA)>#!>*2qm5]`!DPVMTFEF@#W-WhV$6J-<(k2_3fMEi0Q,]1&(Ac[-TnWY+DDn4h?8
|
|
||||||
an+bQgA;U/m\<a21G+1.d*c`Pm6l&A>csjT_kAjoA)=d=OXmh7d!LSX(;<*A=Rq@sAqTE<2K-P.&gN,V
|
|
||||||
MYbKLG9AS<e2S,+Lq:kH<\K)X#MN&f]tjg*cOj]h?Jof,>q.Bj6iWq,F+[N`j'thmH;lO3V`-#8%<&Q)
|
|
||||||
n\C_]7LnbR,GLE^"as@`a%iJ,rpN)hCV<nakcP'!L]66M(n=GC#r78VV3/,`F7bBj]dJ&4F0OB1HW:`W
|
|
||||||
/qo'1H=B7_hl#c(H9ZBA]QLPNMsY`\`(q&ZE4g\5E3uBiT32'hpLAMU:BU>te>dgD\/V:&p11p*l?=5W
|
|
||||||
/)+Kfhm%#R#Nc=hY?PBn-2G)VY?PBn-2FNFgu2oo*,m+Bl\UjESCT,q&&E.p,%Br%H!:/I0L&3A[iCk-
|
|
||||||
_l7b/?5_W_B50@YRTd\p`-'E0*sM)61$$)CQ'GJL]ucTuXM'57rkg7mG`Pa7n%HJmq=-Y;EjaVl:kf;h
|
|
||||||
I`]2\3<pE\q:`Q@,JRC8ms23&O:K5%U3CYEpIOjL'mCbk4%6VnZPP$\8U;c@9O@(?5U(\9EU;QIGI718
|
|
||||||
aqsBofN3;.XXThH2tXpSdj[?+6&4<\r9!a<o9UD]U[VT+OE8]egH]Db)9hY8r<57SNn3c\4(U[pb4j++
|
|
||||||
3oD9ohlCR%C4)i^"X+th%K`6kXlj%MhRKoEn'h+RDqoU&2.T<[XdA\)F.TI3@Q2,KcWE=5Vc!DifiemV
|
|
||||||
B.J6KXcT@fb#S,'1Mn(W(noQYl"k3)B]FO0`!)P0nT;Sfe9k2On*lp'W+uL7bcJZZNf;[]at:H<pNs<F
|
|
||||||
Dr*<K5p^n7Yu8a($t\"eags>p-nRh)!`.<A/)X,tf4IGh5<VKI,aX&$.O(9/Z+j:pA*[33oCnEgC]EPq
|
|
||||||
$1jm"lD,%PA:Oc"reUtO]ITt\Je>4[X_h+tX=pX)i(H-[NYC5t1OcHB/KH'lo51;u\6@AU^4)LFlu"an
|
|
||||||
;#Jc*`ZCDKi96"gm(<;!2hUY4KDd<>Fq;Ei@6!pDEMC\b4@$[483ak/\lJHn<hj)03c<Vf5ln/^&eokl
|
|
||||||
\"J8\@_OoLh.tL7.6^c?EK7toI9*@f]sSAS_=s&V(f+GE<\Kt*&cQE*ru6-ZlfVWV)IjW7<>+uh@';@G
|
|
||||||
YM<W&W#=REG_/T%U<LToXWHhN7BbK)[ncAKXFS_j=1:Md;-'=%k8-895I=o"d#)[ipCDXc`V.`;rBi+/
|
|
||||||
QWEH+Xk5kF@Xi+2?E*[iO8=W4ln>Kn?Ws^aBZ9tE(&LWSp6ShH+'PqaZsoAgj*InYBZ9u!MZ'@dgP8gG
|
|
||||||
Mf_9cVY-L?<WkHeOdK=W]sM8Gd#.du(&LWSp(+:X?Ws_LZss'^MZ'@dqb&I5hm?kmd#.du(&LWSp-qX1
|
|
||||||
61ISup<$FfHQ5=b`Lb37BROU6rh1(CVp^IiV%qa?<p[S8`M$U\g^J,1_ZI0%$t_ej5'Y7[<+1V&*`s)r
|
|
||||||
;>t%D&0b.?\Q_GEZ1\IeQMIVhggHZiBXsE=>3\KTcuOQJ[5d['CAgNCoa`W5&kAId.S=BbnY,'a9?Rch
|
|
||||||
e)WQJk2/9m>,eo%,ITu$L,afqo(2d:).<t!c;tBW?1SjiS?bWJ1HOB=*r&P;c[TCeJ%[RnlhSO'/dbjf
|
|
||||||
HQUR`icM\JG#G65cL))i`80BqBpiI<43`6/7'G5$4NUbD9q&pYCKA0k-"kf<[kTQ)_eZXKloe4.rpaV7
|
|
||||||
=-[+,C*n6/?IOF<Blu_SfjFcUXmKl92ohp+?d0I3"[/#c5_"jVQs,KD[;#TdK+9n7[R$b6H<[&8XVf'E
|
|
||||||
kc5Fci0YZO>u8FjD^q\i_!b7Q17GKo?C,-PHd+W*n![bB=)6esI>'Qm=$2s0L,NmSp6mt!eedn_..sT_
|
|
||||||
[AXa8lcO^F#%SM<eP0J4+aIB7!BlSccMFshoAtmlZPq`U,^K!UGA:6FP@GQ=eBQmT*4p0l>$,.,E#$-r
|
|
||||||
cbK`<<r@D6do>sL-d!6RKE[pc2=fJq)VMRl^90?aR]k;Y?F]B/>,O/'iRLN:`i_cMjE6uK@Z='o^r?5J
|
|
||||||
Djp"*juK]]K7)EVSY_oX0`$.IaD8Oo2qPaA#ElUVlE%sea[%QTTO:Nn_uokuKU^Wr(Yms]:DZtWj-pD@
|
|
||||||
6gd?VT]NW,BQ/>dT.Y%qX`k1eNE[Hi4L2eL0,WGome5?oXs\UuKf!mYK':Rn:p3l"fg-$XQ$@6<hYXXM
|
|
||||||
Yo<?[d3D)*d,,K=_J#[o8#dC;q(?,AjN7n39oenBqDWnfj4Eg?=Abq8h7JI/((k[BB<+]"Z3C\]&)Zd8
|
|
||||||
4R*"-%3(:6)GO?n"WTjEM6GoV4S;8!(&^;,f+'lJXl@Mq"FZ_@&L!"c2C`:B8)k`b=*UGH!IgF@C1cJ=
|
|
||||||
BkZ)fe^R_nB.4>C^Y\U2;;AS20DtZk,l//GTV6d:7TA8a*BLo:1IN`dTeh9EYFZIt7X4iYDla13)/>]?
|
|
||||||
Or[F_A7rYfLGPo:asNscDG[DjG.W&gFE'j`7`/SRUjVY$&BK.((?gnI_+e:l1nQRLHD>XL%_mF@Z1`r9
|
|
||||||
^Ch+UYg/+FQ*!fj/AKKn.^q3TrbT>^13_HVC:Es#TliBQ4/aT_5<BT7BErO:/0)Br]m9O9,6f>S;"l)V
|
|
||||||
qX%T(Jpm8#.=ZQR<\6>@'D:kB/g8Xe?`cbtr!J)hL_f""?<#H#30d#h&E7IfM&c9,fD$%gg8t1WcM5h"
|
|
||||||
Y;j7l'rm>@NhN!UUa9<%nTk*.e#F;Vn_&6Y*hWBQ`ojI]C7o;N:lI'@@oub>W;_gppXejuYuX*_D9PA9
|
|
||||||
kSZ@'&CHOgpR&2]6$8`nCY%J,McOTKlilNFHAPW:ZIW9Y:5up`Qb<8n4d`*+q6,F+H+H4;o47l]%gDJ#
|
|
||||||
Dci91F3toBF^:+tUIp4cRM71od0$d2:)!aE+"iG8QUI16Q":2hZLpFYRH-b*:oAtjK*D\=%%LI/#JYrj
|
|
||||||
dsH.J1lYk]S]0k+[4hCr\s]=Mek.s>H^tZ"YGM3fQ`--s[oF-\%F";:iX@<Z18%!k-gU=o)GlJ^q?Ldm
|
|
||||||
;3@#@ZnbT&Qd@TOC=PpQ<DCp-^!\XA7A$>Fq6fo0hGJ?-KMNsfaP5]UZfbTs(#>ad^W1B7^1=fe8&+Dr
|
|
||||||
\OA<BY-3G/lHr%-:qY?m?1Nk7jmjo1jm^K7e_Vc<ZWsn1p3KBeSUb?"'U0bqB?"<,1ZNi"1ZMR$?fcWA
|
|
||||||
8c9+E/9<11Xc7*!r)+,*H=kqmU1[M^H8H*mr>cg$rmEIaRIRuLRIR?Mbh/\`i'lN>`T<NLWg/lCJ%^7?
|
|
||||||
lbsmNaE<[*Y(>rtfWfWgB*=DK00NO"B86.%ri3GW`?%UbCON="Cd'K-@-=Di?`(`=5?-Mg^0:6dUU&eU
|
|
||||||
-(aYn\ji.6c4IOc?<"M2D(ka;ib_SO@(1pd=N8qVCVK<."I`@6()Jk3IGe/)Fj;mgHZBKia%"H_Skf1P
|
|
||||||
FBW?$Dgc>n-7qV)d5(YNQg0mE9>(iC:B:0KVs_$,;"r`8\,>q.Lr0m^?)b2+cO^==oWii2AbXp%m^763
|
|
||||||
IXZgMq.^@7f&j=Kg1&d62C)cr@`a1&>Iru61YsDcf^1[9]/uEXoRj!2/Cn_FCN6O_ocW`Xq&f-Pme12Z
|
|
||||||
Q;IT(c@<3"&&%A4>u@a31_e:*B,);(IFMi#^#!55V"_o:J[O7-Vm5!O2jN!=[+h?^@(.LnT0guMrmtqe
|
|
||||||
j(ciEm>aA@]tIsiD<=<`K4;;[(qVs/V0n^a=f9<hjd!A@e;GO/("C"m&$>kukLntU[LB0$'2r[j`?7FB
|
|
||||||
8bq*#TX3B_^W(!pEUSOnG%OgAO]U#tMHK>?398jr#JD.K*[:s8f#q3SMq?sT=cr5$b.nKe[_cL!/\+-I
|
|
||||||
M7]M^W3nQJ[,Ul-MW7&SNXr6O?<V?][,R0#7]eXV>&h3^iqUlXj/!L29.Q#5XZ0WjEP/[<ps=oY6S%j@
|
|
||||||
Xk5kFiqUlXj$P>WQW>(ZXZ/MOico1Vn^<<T6S"1`<ZEcp3'O_/G\P.edYd'DXZ2U;25(bA@t_2%f0<4*
|
|
||||||
<&*E3&WIU8@g)Ue>sAm3eF\Zi%RrRj)aI298uF;9I;;=E,=g;&q9I:4o*=G7Z]5mKomTGIYm0<K/?OJ(
|
|
||||||
puoH/-;*i%J^=,3rZaW!97Ol_&p2G)A9)BSU8(m8EOXKJ:LM3$9foFMn(aaSL#.KB=PCH13LX/uV?0a@
|
|
||||||
+5)gDlE,pj3mZ'MSM@50VXij267X68^A85U)N]LdT*7_s^)-cTD0cuFNQs^QQJDbk/T&L1PHVL$11,p>
|
|
||||||
qReCO^<=>jL+%3[o"pi->J@-_XOo)qN[Uq@\$5&p4/-l$GV=GX`[Y#`%JK)$;gZodke-:47Mi1*HOBXC
|
|
||||||
o[U)A%j!BAQ>L!SYO^EQl-cu&g>F[\'Xk>V#d*.2#)X+Vreq-TTYFNtF1p2#F.`,XNuV'X2/cREcGmN]
|
|
||||||
_TKbH;7:O0-m8Y2iQ=)bnVa573jf%bHUIO%jm0E."MN9M^GHb!:#M?ul&VndF@cipME-o0>XVNW0A?C4
|
|
||||||
.@_`]8iCYLMuFk=K6!2<&7Sf7JLCs:Q@<01A09(>-?+'C>tM0iaF2O;HmpkFO@r@ZeP![T^&&N;DV+gn
|
|
||||||
O#Caeb8L%!&BF_UI/XfZJ%AF,(FJYJKH,1Or:07Uq*/;Y<PPR%/BE9t5knu"h,0j.5Ogtu5)"K6hu:=d
|
|
||||||
'HZ,8rP,@ROd)<7F,GOXY[.,$4'$gEo4e(B?dIbZT/b+bP0<87<WD(CO\&LmXJ,,kbD<jdp\Fd(Vajd,
|
|
||||||
k]_t!nrCuDYG2?22>W<W)nV_dI(_1_HnXCUYn?E_DpSc<ch-M-E6u[M(Os=Iis)Guq(VCNQo@FPo:J!g
|
|
||||||
M#Me]X2hs#mkdNlF^^m<aI]+*#M`F>r*ZjpY'Z\5VH$mQ3'pnlf.W0/Vt0&bpW[lHh5_+XB00ge<q)]a
|
|
||||||
E#_SBW5J%=)pP.Qi]XTgk!=I1/FXP'o'_$:;,#;cim-9C^<Z9B1=7o!N5[i+e`1>KT:-4J^\b?d;TquR
|
|
||||||
,-C.\>"PbpJ09]6FKHP_nulL,7?))3Ipg:.c[qcKqXoEU',ga"gp%P')YgJkn$q;)YR<N/ABZjT0Gs)8
|
|
||||||
3-Q8#6q)7%ET8GpTI/aG(;:*WKrqeDE1b%qi-R7+`@Rdf0-'Wm<R`Q,I)_BLLkA4mr$TdgmG\s<jP9$I
|
|
||||||
PD7:?=0E;*B8A:=(k9q<31<10<P)GHO4`okEeLm?B00g-IDhROo?1J3$_AcLM9h+o#6fMLk'%e$*nY,<
|
|
||||||
BP)Y3$cX8E?8C]r7pJGDT2RfSG0bD)YL^WmbkUlqSf-G28Xf2:?e();SJ_Yr1VE^CcOB8,i\TVaeAmMd
|
|
||||||
cC<.)8c1/Qgb)2]1N$2bcCA'dPC(;Bi>cK.Eh""#Q#3&],&-QbNu@U.^ZQCh?LakL)p0i#opE&B&cDt$
|
|
||||||
#9Orqf.lKUoP`mukH1lRb!]q6$8dJ7=j7(Q2u1[lK;HAto,MPSqp]#Wbr[ie.q#oHIP2Z:!;f&EXA;(\
|
|
||||||
MPpEUT6SiSLkNL[qb!S!PI0(71OK-hCNHSD(:QYnpdtdCm=5H0pSjjIrmacej?4]eiauXa805N6CtW`!
|
|
||||||
KiG:kKD]?/5el:FigFp#a&YuO:!t1*#[^.V)WElO35glk^\NX)Q2$]1_,6%6BGL!U]A3,U/rn+m;Qm%B
|
|
||||||
4'Efjko'Ru[/9XleN3BHFuV5:I`7rg,3s]-,C5Th/e6KHYPuELqPg9j'=89nImtebKoag#o:PgXogJ-G
|
|
||||||
T63n$5*&o`M-M\2;n*QN4TC8uq1%Thk9&QiIs7H9F(m5Jk>'PeK(rtdo-O6m)pUeFDJ2Bd6:]d-6b[QJ
|
|
||||||
pjh>bc^O9:\4"0Z5'<P@3r9Vnc1H>[da(*_n+BtuphJ"O?M92R.Ou8/N2KfXo/E1BFY.RMWM=&`_nP30
|
|
||||||
=FH7UqO"$"c*?G.qFRuY-Y\/%8^lAVgXS@`=oQjP\@g_bqoJ;-b`I-=<%7^Jq;Wndo:$[OWVI8g8*7Rg
|
|
||||||
J7!P@ei(j\JFYZ;P5*cOB7rO@c8.fRb;uT[-C;NPW;:'l2DF!G'>Hd2dT7<!@ZN2Eof$a\i>u?r43TVo
|
|
||||||
2s8>!Nq]60IiGM=EY,oX]Eca.#pmW`BC5S*f'@aX1VuI]d$_6D>3/*\hB9`m75i2WB=`sFnFr_0/Fo'e
|
|
||||||
-VG,e`eP.1frTO\JT8B4rM"q>DB/%m@L'$,=)W<3q)n23JDF&`fJBNhUc.69),\7F3>_C*B)FE$j@nUI
|
|
||||||
(.jHt6!9_fRV)cgn!W3_Hp;a+XPS$B?c3rUm@*'(WNSKPbs,[CL;?a+cN^4m`duD@M*7V=^C88%Er=-?
|
|
||||||
2(HmQONQ,I6t/V8G=XQ#Ndi)iF,od>igU1#cc,\sVeq99=.j&B[aSX&R'Y(Z>kB(q>1*.n/A?E$A4R='
|
|
||||||
B-ghse]bR11Z&Z!jOl!(C1f&CjI-9$o<,k:H_*-ER<-$MK+!I+"]tQ":#D.m3O4f`Nd-&h&3lAA<a*^[
|
|
||||||
bteB8=VaAi#]_\G![Z5TG[Bup*ND*:5fC89"8bM0nOl8c$t2&^kE"<;"/g]8IG"hu5:jjB\)m+m8OrU'
|
|
||||||
7D?6>=;$t9+eHR(]$<3K04NcQ(f0b9!8l9\effsY@]hVjF[;'AgWf_QAKU5?H67DnF9*KGY&c.iMbZWW
|
|
||||||
da2WaW9J+ji65i#cctHp,tQX5(7#;?,8[!F',f`s>a>%IUam8G17Wi=_M!$iBcl6)G@#$Y.62AhMJ&Go
|
|
||||||
+\M<7J9@U2U4/3!%CBB[&GqY2/Y9tND@\'hO,\;/U%d(Ea\J2XYF(e2mtb3U8U"HilPH(?R#iSqH4O=E
|
|
||||||
j&Z29Z0F3"g%=m_ZZ5+f6aOlbiV>Ica39`j>KEEW[&%FVLYj.0.>:msVC<$uYn_]&$,36"nD)qG+(Nj%
|
|
||||||
V[(N[L6\q)4!Ak_)MrJpTtMk\m!hRuCe)>_5qZf?Vt@75GtRjA39aPX%kUO7K$23aZf#(Q]?ajTr4MJG
|
|
||||||
[-NZ]Ps<9PGHg+pRskph2Qg(O4''%/9&$3Z%=*j@3<ZD>(]TSl-:`SdY:s"jqGO1#S[-2uVKh5pdm;)K
|
|
||||||
f*JbK24Ul$7Z63uhp3FN`tr<3H[q_ioH*Ph.n62Z?N#XZ[`a:$f+h>32>5`eL8B++'jd0=Uu/p:qh7sf
|
|
||||||
huoSiqj>,uoINeH'n,a@=YnB&E1'[JcZEaP5IUtf(%jioLYoAbgCGH./5OTA@`A$LK6LS'IF#s\i!DMd
|
|
||||||
d^[>^Jl_\d*GH0L8_T`d2d/I4=*umsN$.b3iOMR.7AgNsq26-X)PPJjB2:?nXeus)Ama3)[IV`:l\!'[
|
|
||||||
V8CjYI\'JNQei0F4hL#u3$5iGX4D5'B\n&Z2sEt"NRbNkbrcRRHIe_Ue?im0eBtFCpY\Z3H?W0=H.TZT
|
|
||||||
b0F(]H0\bqd(<N]KPS+=o\ikY*flpX%)'9bZnP:IKbifQbLJ(:M\E'YA%PLBL>L`h;1X*kkZqZ_h(;d"
|
|
||||||
mCYC=QErlj]A(Tj]-"trV5na#Y?^O6q7Pr\6gEL0'j_mc?$_ho%D>D.]t:+D?172IM5K,U`Mn'*V]JI^
|
|
||||||
#X\"J7_]tGTaTW6`6c,#)UA".H'ioE..lr`J*\2RatH%QF-?5JIWe?Pp(K3\ZBptla6Mcaj`Jb7UNt+V
|
|
||||||
iI&%FNkQ.V9CLl9VY%$b'm`qPH<3)5^lB/H$VQEAq+iQTCOJdJGL\M5>sn53]/XXW]kaPOf82EF8^Zt5
|
|
||||||
Q9V(9CCRtX)+cOfrR/<]q>1CeH^X]75;Gb&4*0IN;"=H2roZ[rs5AD'4'=02(%'7E.t)!"."WZ`EL7[M
|
|
||||||
=!D;L2gdclkh7!5=Y3h1G`<6mbShSIC2#`f[+(_6J21RPi1\]RqrCm_NDVLX]S$6GQEnQiCIDt#=%ZPk
|
|
||||||
@nU$OO\<"Bq;No];s-HJ3>&d\r$$WIGU_O>,?LJn&G+W>rP5#1^Ti[YBVMmTCF_I,Y+3D+Q2E%;U0qTi
|
|
||||||
.I##;ZidYS7m&r%rMo\eI*nbr_WhrNo3a5?/Fb^uQgN&i:t;$!4!<QA.D!<b<CHpNkC+UE:iTs3.-OB!
|
|
||||||
I6=>PPsO*+fIjq-(:"h*,Hd6L+#E-/Xu3XICZ/M"k`AiX[kf4/CX>-pADH&>NI:8=`SQ%IN`^/TZ4k+G
|
|
||||||
f:D)KAuk7_/Si:2mikuLdE6UW4@kUZK.V8i?SZfJpI=M8?^6#37uZ90^Ca+dNK5j/hHG]L#d)"R,Bh7Z
|
|
||||||
0m9$pq'1&uGXUoLd#-I-d5Ad5RqQe-r\>lLs+7\Je3NA$<(?4T1SWO(Ak]I6iB)+`r/T6s2qG>@JW(1M
|
|
||||||
`GUeEVV&$.*ttp6!\55^7ZNX@[sei^6tZlYHc0h#VNIo:;<oAu.^9M?h3Ol3EG''EfI]t=D@`<PXn2\j
|
|
||||||
bOEIF/@`!2CVJfoKq=-1[iA,\D@8T7`,:]#Vq<SA^/"#1)01VjqMC@g`GUAD]+mg%f!C4J'Vk:?i3*g+
|
|
||||||
j_QVo>Nd.Ydk&8@T&9-[[3ik@1(^sM)>&:!"*=k)[&e0>p,ZC1T@KMR@^gV5Np:e"CC^qceK3S)\`,eh
|
|
||||||
q2-4KB8$5MeZmNPX&ZF'4b=1hFt:-HG)MkP`GUAD]0/.*i,?VQ295B9(;>:CLYe;aNJ4^-ZKM`=:juhN
|
|
||||||
XCE?`+Ef7B.s>]qS2`fSXod<CMVSn$G&+k:&\BnWY1Tl;BJH7n1'bF#r=*l4*Nb%CfgNBs=6rsk!O5jC
|
|
||||||
N5\K59>;Ua,T"IGR'@?m)+nWBL(S,>bR0p3>Wg1Cre>#udGI3<l))+$DT1/M=Rs'PU,AjlkOC@i?RLTd
|
|
||||||
V]3>a6U4tN@L.G;_:OMe#Y2lIT4Z)@Ni3`.\hD/mL1858:7!'_2pT)^HG/BFHQA3TWNgI*'bg+rV'OC1
|
|
||||||
HUJPc9>o%Vi\-_)PdK"CG43*A;,NrG8SK9bZ*i&L1KSLdZ';YB9gM/1mCp>e6^Y-#o+*i2Es0]hb*'G=
|
|
||||||
c^1$(PdNP``_r*5m28PWSZ"i5+>T82@a$[R1KnBAOV8sL.4GT*q\IT?Hr+?)h>M!Km\0oabhQ37)qh'6
|
|
||||||
[p.fR'n9dijA`LpU%NZ,--m5\pjnC^I^P4-<R3e("b]@gS[4D6>-[+(<^B82"Q;GK3<'#O4NsrKfG?_s
|
|
||||||
R9eJVmIX)?\3lcCmP-.(G:;H3^OeA!-g/aZUuXh#j9Am-bM?jNHSGauE=7g,R_P6;LfXOBVJ3e8>a.ge
|
|
||||||
L_XK%H[A1e8h"ZcQZpsAot@!?'OA3)%=BoupE-Up7CAV]9[S=Th4i^VAW$2V=j'!Tp'5U@:Lk90MkP?q
|
|
||||||
emBe'F</&!Q-#aod;eL..ZZl`U@K[r.CtPrXic4JOY`AbhCVgK'_=dI]j''iPtON$R4117]j%jfX\@,f
|
|
||||||
oIeO^.6<OH/WrP6MT-\Fl!9>+/aJ;>HBmHYa3,7U:?@4bhQ??odu/r.;fj%nHh1$DMU#Gp&t_kFEV+-C
|
|
||||||
g5h7fd=(Y)\MPo07PDGZ(,_O97JG3AjM[62.LW(Hd=(Y)E;7NhNW)jGUcU$%<SGoE8X9!kRBP<JUjf9&
|
|
||||||
o$\"9[+8*hkHB&h!Nl<JePX'RDJbmIf=SF>#@JPdHFG@6E\^_n+)km*56$I,n+t9fTm*P\"^LNI/l!>Z
|
|
||||||
R&G50U1`DqEHmSP]!O4AGu26JDAB@ehQ>N'eJtr#n)![1ccO\=+5*mkSIK?PjKIq%:L*pVRC`F.^AXTr
|
|
||||||
?!DC"G-W)N5JE[>8)PE?o;=fm5?W\"J+nN?oDIGm^9\CL]MVVoQFJGgMd,3.%>4D[UHaakP/EE"^b>&&
|
|
||||||
MVJ-tbrbJiM>Z%KYienB]ZJR\-l2+dpkcB'rO.G:Obhg#.@)B@fWBVE>Z\SIa0#(1C.IWV4mLf17=TYp
|
|
||||||
5[cLsl6G[ZE_;*3jX+H&&A'4l47@YHI9U5s<mDgs[sE?SZPDXX\VnW!.!BACgp#JliOSZr1YOW@n%!B%
|
|
||||||
ft#Ma(.U/OQ\>A0Y5HDhiHBT5dEsIcMh)AOJXOEk?Qs[:pU8/d`E1fO48>/7jkVH`gEH_,UPW"nBj7Y]
|
|
||||||
!5;["n*<qfMcn3\=N#)ar/H-Rd3o+@\Km3*9:e^Uc=J:T,3MkPTUa3JINdm[Lu[e;n+J."BCi/]931s@
|
|
||||||
M-j-*A`@np>L=\V>UE'qE\<'.F6UI+3Slh<.Hf-oe!5!BSc=srkpNV0agiu#X`?%6<gbt7""-C%F(o93
|
|
||||||
*&6X)De_"uaioF=Nf]%K.?hsm+_K#rB@OJ&%D9/9)M6hqGH;(3%o3`"_NT'*kN]3N5+=hS8<S!G-2Yc0
|
|
||||||
hNEol`=-0IUiY:fF>2m@:FkT`;>+Weh`3]`R1bFJb=l&/^7]SYo(jX!Wb@JI\k&aC(L<8Hg%_X\.ROH(
|
|
||||||
0#G]?`%RBsl/sYqagq$WF^7"ocpl0g/8S81AL;mTk;#n5N.'p,S4h9]E\=t.dE\9*^8^(44",,%JK&Y_
|
|
||||||
].?I0b\r\"j]j.t!p!V_M(RtV!Gcgu0$CA:L3-&!jSn4F8ulKCBuO09f3?!dj7kXZmU#ZAi%3+-B")CZ
|
|
||||||
#aC^=3P:n\-1[e$`oWjQ7mc#/:)<f6Es.D!-E151bfuYUn>5\pLQFY\,orEH$0M'Fb`p_C6&k?+('?Od
|
|
||||||
kB_rY^VmdsR_LC5LVWWg*MtYrAaP:o;=L=KMrHeVNBC>&p_(oXpmjtTCZZ*BBglWAJl@rH3F=!2B&s^*
|
|
||||||
ZK%g.^@tG9?WE,"\udjeHZU(jZW`^.JH!Mmo'<u\`uSO[9]tUOdG^0Q)>2DHQQ.I3S$/@NFK!,!Ob,N?
|
|
||||||
R0D<CeT$M!E+I1<jbM`uDG`B<1&(;1RCl:Jqbt_:*Oh$d/KD8J_9`tgBBQ<q14]kq78;X,@4I[aD)%C>
|
|
||||||
G2J)oCPj/O&,`J(*1ltja7[H`73VIi;sjn9dkfdRJG`ZEs/@a3=ln.><cF'X-[FS>C$jtIe.ubo<@(oi
|
|
||||||
WP4`q>VBhso,Dtikp9gUB%CN@J"<[an4&dtkpQXbqipR&W<\PUgF;U4PjQs0b+ihM/[K9RX3<SH4`XPd
|
|
||||||
0F>hEhM"rr*c7b*U-..je^c47_`'b^Tp:r/NDCU4V+g6!HDWgY->=D;':oB0)S$6dZ(5aSUQoYp[JpO:
|
|
||||||
>q*=c5lr[i*8i?iUV5fN=fe%;GK`'ek.O9!bG/mtZ,HFNB&?A5Zh2t\4B8#1rR0oBk9b3\f7(-A/EU,P
|
|
||||||
j(Y\C)sNW3es'7!%LRVTb^),DAOdbn-1*t4@thm4%?Sa[ZG.d1p15s=;J"N.9!`7hA9i@B;M>Q_XDe7n
|
|
||||||
0rghAB3K5"X"K.>9@om-*Nloc?AiS#<\:kh`nJJiE3.Z+EP:_bqQN2"OD:^u1MH=7P9W-\*jk^UIDmjB
|
|
||||||
/S':^TCoKSm#ef(+2kGGdn4p6BJ.^VR:a.rSR>Ym_8Jq](On6a(@uqE[Y:!Eh1!8gPK;LE7a9/JV57cV
|
|
||||||
ZW992g[9iPR#?B6eDqT[Am`$*UVsXagD8s)jq35a-`_<A\%lYH<TS'NIMZ/8Z9p-_^9:P#kBP%9C6.,t
|
|
||||||
lu?'5*:(!No,RG]W809gEb^j]'j8IEd?tM+H/$d*aI03lU"R_<:Yp8imVAnT'3`o_qpMHJOUSTl7`?9]
|
|
||||||
b)fP/1fW?*f#i4WFQ[Kk6D_>37BfrY?W,bSUo%)Ip7t\fFeI*E0nPUu/ShDgDZ6p^:-<gVNOfrfW6n($
|
|
||||||
M`7fqcY0*Rs6*!np<&<%j0<\.I,LWJLG5A#`-VD))_(bVp[.E;MZ$:?]8p@!<co&SH#O8[GBs1BS%e<;
|
|
||||||
%):gTs-q>aialB:n)Zdl*>nbR`9a\Z9(q"$S&Yb]6sar?7D,;P(JC[*EI[i8me9"SfIJ7Q?hA\M0782/
|
|
||||||
Z#6bHf3C!.3dBkA4_j#kYgI.D/9o'grRP@HkUgR^i>2.,)[VtjU1nV'Lfb<F%Q^an^VSG3/%SbO4mJ>R
|
|
||||||
*dF"RhGb>s7NX&K?#u)F92;!%pE?$iUZcIs[3EW)*I#MYnYVFEs0jJ`[HThTlejn82hi4Nm8U:V*$+GM
|
|
||||||
E>bu/nMk4O5#L:ag:$dQY;fM1\:rf&Cp<UTWF%VDo,s'1?*0T7_Mo(T6`]%_s.Wq3lj4Z[&F6k%#J-E^
|
|
||||||
Fc67bi.`']^XQA2frP!,h*rlO1\-:3^,E]@"BDk?)L,DeIdaD\_`jUC<0<B/C"t;&gnS%ok6Ups\Z<XE
|
|
||||||
]8ZN%X3Y,kUm*j7WXn[B)nYigiPrJg#3goA?hBTWBag#:Z0!a\gqS*#!g,_%?EK4:ni1T,*&76p2oiC^
|
|
||||||
eMAZ^)`R.'c)?4V?K3JQ4k;j)&>0!@;5(9sc40Jc)S+?\;,e6C&MZ`AkuM9kj`"r&e93W]eZMqD@,]tr
|
|
||||||
,W/]KZ;:21fC>BI#0FUBQa&=0d4Bd#pr%1<>>Go6lcIKEr$S)A1k?HY)FM95IVG[NI+:G>=bql_d8%t8
|
|
||||||
dZ!k;VMVL*`Q[lJKCbL6mY&)f4;Eh0;?_4#;^mMI1Njd"N+f4H4`QFqDfYLihf82a#FsaK^#t$5L&K<Z
|
|
||||||
isX(tD.OF0o]SQ1@7"!iq`)@*E):0?[gWg\o?JA'*DNkf?/Ngap0/N>kD9`2fDBphGShn#qnnoT\aW.P
|
|
||||||
hpgF`jW+#!S"1ZONYnV_8=P[s,IErIf^kc8S"p+\JhMS`i[^C-hX7\f"7F_"`bjA9k[.H&Q!^Y"F[s0]
|
|
||||||
.W6iN$G?ebI4As!ej1PW:_+RqIaI=?X)@K%hB3;3o*q<Or&@YL2%*P_?T4_uKtj`YUKig@c_*Y>g^@5@
|
|
||||||
D5u<YNmHXJ-AN<YGRPscME[!/@3@B0d?N,o&(j)FEj0R>,d"DiRc]qOiGfgHH+<a0BA/1p8j!#tj1K9R
|
|
||||||
\2rfW0`pIE?s;&Mk58<9$q@8EFt[Q&>'A=gXm('j6,OUf+Q2+6>-bYPr^"9mN-it,WOoh=gH643%bp"L
|
|
||||||
*n!eRU5JBL,,JE)G@fKs=jF+(?!?>tV\8A0:]JlrFjV$a*:_AKU?KS:587m<?^E.G`I6h&S_dP(3'7d^
|
|
||||||
H6PFjNZ+["mHa=2[OCN$$MAi/CFn^kH6R-E(=9WPT<;&Be1*!69<CZ,lVRSk'uM,/cuLa4l%TLY^n[
|
|
||||||
T/\qF^XF;fOgXE9(<0&6fB%j.9I7]NY;tG2J7d0nRdls4_mh)"`SL/SG*[?j>0-1;;5a/Whs3]-AHbns
|
|
||||||
=B5D:N+E1jPknqpG.Xl,7s`#Z&;:i)]GM$eXMED5V+)0\Z3'o,h=,7\AHbmh3Ei=;/RrlU/$S!-d0cOY
|
|
||||||
Y,hV#=Fn8p!]Q[!.SBNB!lRSe5.l."YQF19ME'L)"ZVOH!aFMKB@QCU!kZ4L!,"m*9cdF4@c[0e!k[;i
|
|
||||||
@+X-?&;:iAVA8#BBa%>DpfIRBDi^i(5?;0QK.]cO'aRJ`YiSNQMY=]b!3$`7!-PVZ"\:N9GuX>u<e,9j
|
|
||||||
CY)a/*_m4rYhO!uG.XjVC,d:c5TEfISkf*#5-;WgD_"(Z=1.dOYH[bG>60aaX]g3mhH]TV!dia(@.2hW
|
|
||||||
gjG'E[]+>FOZO_Fq9,b$k(<pim(tgrRascRImUbJfK%JLnK38NhLj9\?GA&hMV_Rek>V+:H6a"Q9g&ls
|
|
||||||
GV903]f+/_B@KP>037[GoS@.APGFsTNNHKgmIeZ*'c5c^q&Oi_'O<AfgkhO*I9BU>9p0_(lA',p(*si*
|
|
||||||
Wl!Pd4t44q=fK`n?ku4sD$5E=k[o[`'lWY83QXE6b<9X8%%a#F`n8a?AbCjj$R^k*rJVWXXBEV'V3kCJ
|
|
||||||
]AmLbmI0VW3g]q89t+C>E<bi3T)j9lWL=jtX<HhkGiOV2>Q("A3#s$,ETP[b90@.$Q0L@#^/a-H>d89E
|
|
||||||
5\`gTX8bF)lb$E_lhgMFUieXI32K<_`U"l(/jCkS'L`bW.nZ\MpPdAhoA444eOu&TD@'^3TLI@;knoCL
|
|
||||||
X\IYJ5'2lN,:_6B#>P`8&&##]L.Ka6n>!qjT@i6+BAmp,%RM.<=E3Akp)c$KaG-quo5S<C13:t<T&DQ2
|
|
||||||
OuFW)L:31.>HTAqcI*cO.tVQ-a4((!df3ush;U0XJ%^4C@E"B%1ucT&'C/6O=C4hrBcc-Vjmio2IsD8Z
|
|
||||||
?Y`,2Q"M)iW._F\-</D3&NmP$d;[jbP%Rnh/?Co#!a>4WTqB5lkNm+j,Hi1XlBB8/I`:5]1ui>Qnt?^.
|
|
||||||
33^/2LcD\""EEK=s$5b1VMZT+,EAiY+c^Ap2SS&_7iXPPEn`=/+KShnP`%iaK-[s-bt7HiG#JTbjI@F"
|
|
||||||
4hcNCH]7[`5@gS.M@+O=.*h8ORRMA&b*o`@`C_QA/M46THG#rA5NXW>C<Tml2<JRek$ba*4>bZc=mI`E
|
|
||||||
"r<$(E9-b./EcbB9K9CII]pbE[2R56E_MR1`V3Y&jnEN6n2S!2hlAKZk4%G\EU5(4^.6P85+L5?:XZ0$
|
|
||||||
/*<nb=bn.AoCl$@%ocSTS[fn-B5Va8T<\4-nbbW14O?<sDguDdfB(J!OM`*s`)AaZeNK!1?G[4fD"ZVk
|
|
||||||
lDlF>)tF2Z[u%O?ot1'K?+ZM38CWn\("dSGq9+LD6,'gB8!75579>NgmJpmLS'FOZj5Vbr8%:*cK=)'U
|
|
||||||
M]*t-g;21?MHgKQ2eRs0Cp4.^4c*D9^"<.S1IC#NPph,2b2L$43J./Yf:M?V_KdC"2rXoZ@I./ZXsm+?
|
|
||||||
TVBL4Nq%-sI(h#gp?J@Xn;?K?^tcu4?7@.rW)uk:WHs3[FdYb^H\%cRQ882D4i9<L^/acPj_-5n'7T2V
|
|
||||||
n`RSlb<3Mm5SNN9MaFH#n1hh0gY)%?7J-lCc0>ae>/BWr8U*1mWTjrTY?051F_&gaW%!j^8;hn"X(T*2
|
|
||||||
QZ^"!lW#XuX.1@,LhK6ML26duLRL5U4CLY5FM\<+].He:4*&%KAR*GBk'fr7p@(#mpS\=7L_U2ad+RTB
|
|
||||||
<jsOo!H;dII\k%*r]m+.MB;/*:tH#K^n,-$Pn$KNo\Knf1=QQmRDq@@G,2:O-*p.`<AD@pm`0O$A5Aff
|
|
||||||
njp)u4:sk81'cN/KXm%2<<kmt+1.;+le#gu1P0V4bjKH/3B><2];fAN]LKWSiMc_;.*pp5X\6uPdDatK
|
|
||||||
@g=*]\%%'O<b-^h[df"_ERG.<5+QqUEO'^"%EU'r"MVn/4nMQ+]g!ckZPV73:OMUX=5ppb7@/\`;k2Dp
|
|
||||||
q<pX>Drrn]c?pD]F>;YSd\CFgB<IEpWHl\Gn"hG?T-#m2:^5:B2&ohBESG_Y,`J'6.tX9N7<(]91m+o%
|
|
||||||
G*=6B3%jbPFs@GD/J<70-k2-9;s$^5Ifp+)7r(Y=]&^TFGuJC1MA&J/(mPgu0)`XMr5,^7ShPa!9On5%
|
|
||||||
8Ns'4!8=)*n%5cf,`b)t(Z"LVid%a/8`H/"7N6?m"MUtlOQ;DRK*NbLme5\a\67^!!G3_=C`MdAG3$V"
|
|
||||||
RJZS]aJlL[@i1QORCpuZcZ8g!Qd$E4f%CpR1W/P:-n7!*8[!rbP!Y-kX7U@34t'1+&.b?sX43YlBDA.W
|
|
||||||
QriN"c<T8s/1G]"?lrj,1#o&pG`K^@kD2.8KC`f<nT)C\:O]krAl!g)^'@Fj(.q=C#FuG6BD)usBD1rR
|
|
||||||
(>L-QYf$k#n/U-=A,!.rYuXI\I/E6Lbe3Yu9K.^%DV4G%XblXBd[E>E>JZrbXQ9JbWRGfdaIeaKe7lF?
|
|
||||||
FQeCeQ_JMO[,tib&bo=hkM<)eGu@5*UFI'jIM[,._CjC1SZj_&h6mQdZXfC,A><CBbKAB#RH,eeN=_+%
|
|
||||||
C0!UG,6OjmoCO(9/S3uT:[F>A0AY2<^"7Oc*9R'5RsQnsrPEi9B?QGLl+KmYKTFJ_\KLJol(Xkm0'U%m
|
|
||||||
;<-ue5<i+qB?@mc3d@rN6/oLOobWtN,`6Y+eO=KH#=jl08F_/:b!SMWU<-at':Y4(=(NHA;*J[4BWqF8
|
|
||||||
<-8^B,L>OHq-Q7V1DbDh=:GB3=5bU7/m[RWT<aT\L9]&A:pKtX&=)1K[;ubA-c`YT_4PRLD<3`gk*@J;
|
|
||||||
'Ya7i>+i,8dcEK]35'Ld,WOPE&q!fOHOD8C8##t8nS4Wh\d55";O5pcds(Llef00./=mH,B/q.t"k#g9
|
|
||||||
2s$%Dq%[hj4QEC/n9b_knX3YQ*90(XMDL8_L2fC4=rX:R-VGDnWNfK%Xg@j-enT)TVq,UW\)IjfogZ_/
|
|
||||||
1[2S:a'3Te>M%lQk)n)3&F$m2ek(?!-OV;o/b@?D=!(0/p9-=#S,E6FS2%[X6=<uJPs00$Xb(3GKN7N3
|
|
||||||
TZ\hCI%RLW.^rEn=h[77Y?l3PgMI[+HWu3SDQC#<D&%"RmoTbQYR5cH<e51`<PUW9@his[:f[V[%E1-(
|
|
||||||
^9*"M/=k@r4eC\shH"']gF=>h8lgKkX9.pN/@s"SXRrq.a=WQT5d$T$#+7o8UQA+_XmNP(I[Bb>'qC"e
|
|
||||||
(!%Q*$9/"_:e'9a.M1&Yn4U+qX1bWe;`q.uWjc!d#f`,7fWNe!/QnN%nZiMC)O"/D&(<#0r3crVs-Y)k
|
|
||||||
.B,@`]+4fhI!NnNrW1(2f,G+`_Wm,7F^]unr+eLrM2UZ9At57$qREk@&?NZFWG,M/AP:pZI?4Pa-%W)0
|
|
||||||
:u5]n^DGEJk+tWZf#bZ!)dZ$uCrP@P?>QsW3pF+b6Q1,A>NcF\O<.,*\A)3kBG0Z<+&.>-;eZloFc[rd
|
|
||||||
roqfU_U9]U3leu0$r*JbjOb9pMOeb-4fhLMQ&;>.ZJQlIRUg2F2OWaXDb[MPBC!bu)Fou&KdIt?-:*ZK
|
|
||||||
1s)&=V0p!/X21m#UFWi=*L[+r1et+&1Tc-U+!<BHpS=c[`$*JQ0E,B,7H"NF8VCaC\G+>qg^+Kc@_Gs=
|
|
||||||
8O.:[$Ig=]RNs9VVMVspN[EHVG^uS`3gt^a9MMf-Bhjd"&cJVdCjC3J%GB6(KtDrdIMr$ckbTbc/89J!
|
|
||||||
apu[`UNXmh:@_*+3mUkJi@CqT1,oZ?cnafjU-d%2gd!k1it\a_B##cX>"$.*`$6b=%.XnhHI>9V6AqS8
|
|
||||||
WmDs)7XN_Jijs8Wn42hg?`c9e`qkL5'sD9<Dpu2<Z.ls=<+ri)n"3LOjd_B"eWY&<P'FulanA7&:H?&_
|
|
||||||
&+A91Xj@]Y%STLmm[[6=QbhO5e`+CYGoT3E;S:H6h<qPk$Wgqu@RoYT4]Z>p2VVhlMTWX2#!SXE2iO7u
|
|
||||||
kHLr`$/[,%9A-1D[1:*GNPJ5.X:[3Da+prYQohJ%b]++n;F0/9^T*3:er3ZH.=@=lK.sM,<)O;31A]=p
|
|
||||||
-h:F8VTB0$RFt3V`F9&KZo*O2R&;[](G!63ENK,G``[se`3JjcajGB&NB)-Q?lmfCLW##:lB_\-H[Ka4
|
|
||||||
Q4La!;7D5.ViBjWQ)#FLSh<iTk.)JXY(FFA,#=53(^dR3a<:^%>&O=J`=lft+gN4q=>qSu?+g4]8)_t=
|
|
||||||
WW)"E&&g?qPW48rN%giC?n4aj?]l(\11/,#cWARsn1^U&6M276oKOgo/A)p)>YB24g'nGYcE-?<8A!!D
|
|
||||||
3K%OW:fD#s";g>B9oY532Dmg>*Vq5t7SohB%qYj-LPBjCqa"PfSBnpV^hVrk2bqL\=ZSkJ`h%2Go6`Ve
|
|
||||||
Y1^.?atdagPNk<."E0DM%E*DTn/uFF?`ad9*@dtFXoipAQ1(8N0ji&.Q%",)hbR86M%P)oRf"3]nY+>/
|
|
||||||
T\-t-G2E"-?MZa."7USGh9sMprY)KAb.p("H6]ZOU$?RgFj1;,i<M6Sh_bQm_/&,giEX;#;qnc+CU3QQ
|
|
||||||
q0*Qd"VZ8JQ8+\a$!Ak?2bkkiqfgPDbQLaLpWS:WpKAs8YeDS""@*S"JR@':'r((;YBP!YCM^]Lg%hAe
|
|
||||||
dYtnZ<SRtSs27[EMn;0F31b&<`::P?T4#ZC!I(%C:^?2M[U.cb.Pk6F2ZOnk5"C&iEMr9aD2-YiE/&O4
|
|
||||||
`b+F^;!NFeI/ad+0#E+dS=dUe`o%b!s,!M3N=k!32e@Y:1O]EnQ;N9F:jNXfm-o0GX'ps_T,OJ@Tmn$,
|
|
||||||
1p3AgU9qU6F*a/;\8W2[nJCKkl2EpuQ1rW$%]"FYJM15IP7)M+4JJEqcu'/T1O@b`W5$OAfo0"ui6Go9
|
|
||||||
TYk4IUSrji];"4mJ,SdIhlDS0<\h@tfdufnD)P%8B/>iW^"BD!daS0tojD8/d7Od_FMU_LZd)HqNE(>/
|
|
||||||
_YR!ZdBt#DkZt\Z<`UrlHJNbW[sIG28j-B<kKC9DqC>i)HE20ZoC&>>Tp1F,`Um)JCah[=MHJAXW<N^)
|
|
||||||
?B+!1>G9=qee_2JH,J>+g"gZOg?l&BH6^FF2\9o$-%,J=BT2dMLRcu"T&mZOMOGp,+8#PHGDQ)DhHG&s
|
|
||||||
GuiJ$M;9b`RMc(.0@mhA]m0o@e-F$Yr(O#,<=OQ@m@pUH=^UD&T6MXj)Oq[nh5la5rA%gg)InR^X6&QR
|
|
||||||
occiYMmoNQdAe.)K2l9opSulPfnZu_b56,a9u\Of%p#FY\eE$I5)bc`R5;FV'Uh<fWD\[1obm"'pcE!N
|
|
||||||
!9+2g;ftmEV\rOLgW%<Mpk;*nC":Bbl.l/NeZNP1h&u,`'DKA]KGm5O<Nhs'CW$gU-tB@%[UYB]PURBQ
|
|
||||||
/r9qg]omG;qhZ>bB&.S2a3%F\WF0;;dOg\IhA9#-+m%P\72$bA-^_sMM1A"M<FB15;+p%qX?DB2s2;MI
|
|
||||||
(\k57cOI%X96W5+*Y;,NCo)N?036LEq@W[.]V-Sj'"D9F;UjqS[XN2_\R2H4@lI[rCIbFm8P3!;*E4(X
|
|
||||||
I[tZTm!ar//!%RU#Q4j>p4Yg4muR:;m^]U'qh\K<GqUH`,J>21kiQ/Z][?IOI"UmaKXH$Po:J#0V:pkA
|
|
||||||
kYK\93[&E9]-EN<VE7>=oqlOZ-3pYgO)kF!Rd8UN]."984Rq:Io9I&44\cs$X7?2c]&1GmBO,n-e/!P5
|
|
||||||
pU[M<4W*D&4#9caFanP<M@jb7V"Y'6Q?IlWbBkKAc@!K)'JHJQgfDuJr"A`C9j3FdO`kD^YZ`JMVgM\f
|
|
||||||
M.-UnC,ORCW`j!4]g9V3)=4GKdpm6(IspEnK3X:7mFgu%AaIcjA]MYhgT*j3UVH1R)Vm;d/)D4ekctPX
|
|
||||||
91P^aU+bgAl-A%6rKGmqXM7PP_O\5[KQ0rc6!3AfJ(k3@"S[YB8i]"N=A>Biq:e/7aI[7Pf#F+BXI985
|
|
||||||
;hZ-lW-0ZSatuq%#^-@0(f(GIR]u+O0"]dUb;Sp!g<OI*%(;:rf2_,`a$cKKd<]86H@OW6*nmIhEn:fS
|
|
||||||
:-lCDn>qUsG"TdBQd9&l,j5"%@CC6`iB9,*NA"uJ?A8\:WlNKY]$Cmb[;=;Vo?]C/rL.nrd*VC-la@p?
|
|
||||||
$(jmd9m3H5_YEQ\*H^u?nB8?9M[]P!fk@6,IOk!:bY?8h@kTdCN*FAp[@Ju?\)ZZWZHdjWBstJ=^LY5U
|
|
||||||
\-;S'1tq=^YKiD4'^E%1JjP+?\q/XDj*7p')9%4%`Zdk*%%B6E(b`A@@a6HMjRZ"`/L\uVOpNf(A^Uhd
|
|
||||||
[qPO+/m?+R^YG/,mW^W>Rm1Cb0;K0]&#\2:e8DpC3;;@3SMn1t,Ok^/D;dT@>#ot=mE`T2=b4?fP%F?@
|
|
||||||
)gHA4/TP1,iZ?:p.3AZe72=i9>8O>3:Ed%gg&Bi49iF3R!Gt3:\SlW$mCfULNAmiN2r6o/3jEu1Bf2B7
|
|
||||||
;8X>k@MO7N>']l^[T)<sH*X$$0@mlrFFf/Xa#]q0<nZXdfBq7l]^=BJ+#dA]dElWR'1GoDi/LhCr,OE9
|
|
||||||
C$`O)eXp2[eKIh?%T_aEJN1*,=tA_%Z=8-hpIqLDJV:EakE+fU`fo9V(S4*W-Y/%#?@%&,2PLFB>3e]n
|
|
||||||
B0@\Ar#&k$&PV#/5$&@rA$j^u%GkSm#-p1rgXi&=NBfRbf)&5\R@M%.b3cuFc\[tCo/hDA'/!F\miV%@
|
|
||||||
Co7qG!&6='$T8Q/L,G=,k0<+&HSD'\3sDH@55_PJ1C&,s<dF0ElCNp?:`\<#Xp-r@T+G@5%j]8npj4)h
|
|
||||||
pNk8P"Ks0n13C%o;2-bCDr$XPfUs%1.I6l*]NNG[.(BVbL8Xhk:L:3=Prs?&P'9q6V\\`.XCFss,UuQ6
|
|
||||||
C@7d3J1&S[&j2"%0&F1lMI.ub7IOO[%_`:A:lg-ebD#HQ52'B98"F]tU1hNd='$P(D<bM.:u?(n5.7%d
|
|
||||||
V8)QD9:G.ZQb$MReQ;$5U@/WGf8D'I'=Q'BPbF?Up!R@QI44,5]qOOG>G5L%$5C-$Htc=BhFG9rS/Jcc
|
|
||||||
<SXT:I)TNDI6D>dC#Q2[oh22Bhn(9IPHC@UQBD$U1s^R<pAG*NS*7(_<16D9^$,?fplLc(^E0c;M\fsH
|
|
||||||
,+k`9g@jXrQGfcfhM3VE7W>_@<O\'l_@$%_D*(U,<GId6o'R3QWmsK-IXNRAQS%n@[-IDXeb89*qrhsF
|
|
||||||
NM^CnmJbs1LJI(djO0JF34!f,(T]\kHMf-E2!d-4;CBN>gt&Y$M:Sh)SI[5LMj<)Eagr?-.D3&\X%^!c
|
|
||||||
:5a.fN>8M])K%G22>5TV\m?-MVYrWWeGk8Oh9taVYq#79g3H'!&(rD:Vj3ec<NS5-d.YgA\Ur8*NRfi3
|
|
||||||
/m=)]:^;$P@]p2EC"N2@^3mYLr-!Ab^6:^%>"4[ZLSbZ!Kmi"G[]ufqaU;J<KFb`!gI%V"[DVC*cT_%L
|
|
||||||
H`2#rm%LK,hm)rLHGn/@i947d,bNDm$HjL%<8i8r]6>TK-rtFm/@fsN_YCthR`7QUkVGEdLZ^n,dJs)R
|
|
||||||
52=_VcRZjnT/:*7Gi:Wc4=JK*W*:>+Q)>+<S6$c=@g-Q5F^B@_9s$RL=!!UP/!"Nrc7?7Yn[q1mWLm7F
|
|
||||||
F^EJEW`;2AYC4%=bg.dMNq:EB_q8`f.%aS4d>cXZdWqZ4bDG[#)D]>-SDSEp&^kqn)!;Y5;S,Q62tS#$
|
|
||||||
K0)t-?W,b7lb#RMS[\<7?D4LQcRYe7cR[udS6'%p6X/i#SQCK2>FFNa64:/m=%I>&3_47md&k",/'n"=
|
|
||||||
qV@qtn]UJaYD=t+Koh'+/)6KCH0*TVn#25;eg4kNd(7_"?C,t`Y=l+cBjL#gPe_&%H2_\fQeKRI;U^K'
|
|
||||||
\^`^B95Wj7ajG7Cq+Fr"oj4!=H>@*krY,1?"M;(m-2*;`;EO>iWSP3>[@>bod`-R<Qcu_JBI0Z3jE+,^
|
|
||||||
&ncm`P#\PYlVa=hIjW1fZh#kgf<b&KpZC.T`h2aMSiQ"!^(ro`m*G]2%.XAo4/.f9^0Z:W+9+jrk=6dQ
|
|
||||||
EqeFjZTiHiIF&38_jtt+AZ.Jq/3Ghf-<nbMD#)A<D>;iP771(22W<GK4'%=VUWK\n!m>?U=eF>M%GAj^
|
|
||||||
W&$M*@+\$tmt_c(grjV?cQ^9K?n>kHZoScd,OM201&jh-SIff4kcl?"m@FQ!ATrG?U>NGQSVOZfFs)i/
|
|
||||||
^<1BA45PW0,PS\Wk[*CHeneU?8>*]QIGO0Q&aA+MldW6mk]2r4'60_lHYIt!l_[_`D9VlJI-;0odg+S)
|
|
||||||
pnkrBc,mT0fB3M8UC&H1d91QJl*!/%Zj_9Hk'M%!]:Q4U9G_:r4FUpS"kq*lk?.Fagd!mMXhT(VB5?9X
|
|
||||||
3Qc,98+="F"!W4jh&AAcNL&r2(Y+mee%\g>?2+ot?p&C@?65"--gJpC+Pcn.O:>"mpM].dP;G\Gm_`am
|
|
||||||
n_(FcmJ"WAmqRPe=3f=SHSd[S\&F36/6(>BPCE>$=@Yp-V6s/V9?Fd)K/^6OHS=0PgPVBIX!14-'7"n8
|
|
||||||
='Cc36d-C`kgq'O"'3\%WiR53dA0,cncN2$aEs=mPJ<<I,M`X#Kpt'A;p"]NClpL1nbaaUcZm4\KCM@:
|
|
||||||
Y36b0)^<W$mB0.fT=)3p7.0[D")8MndV>[\o5>k+N,3C@1PZNYQ^@\%T2F&aH%'J<d66&Lb=u!&'3\@\
|
|
||||||
:QL=@B0Bi=\,Rs_/%"EZkVbS^4>i\Uo&b7@/+,PAT2_ar?o@J%6kObQ6jlY*eXa0BHOD[XFF,$M*%]:(
|
|
||||||
GKj[%_mkallZDq$TG+)FFdH2bNIr.A?>%"2';'rGGHF8?0.g!pG:B^b8\$pU6*)Dhpl*`+e;Vtq)tW4f
|
|
||||||
KWR(3LoXM`Ecnq,Xb=-iNoNSJCj41'IVZ3tbS_)!.A]odG=3>O,t^ti4q>6f)qX>_\NBR=aLZEA1uodn
|
|
||||||
"+,Am%Cr)LZ53WA!2oS@W]Y1,#=n(!O8*iO$N3jaa]=C6=&3.X8*UuhI^LZP])gT=72Z`gh<J/mpPfd&
|
|
||||||
_kirEeZKQB<jbQO%,.HG9hF7#=9g(M=/'Bf@uo2HdNCdS;Of-mF[OGajoJr`j+E6UHC,;WqnB"Tiu\",
|
|
||||||
f_B2r4o-W?kIDt`n'gI^QJXd'QZV+a&]=LB`G#YX%<pY2eaoD8>+KZ$eEZ=%Vfi".?/t!#FkVjodd!Qd
|
|
||||||
cX9`_0srdciXIC36s&L^@moSj5,lgr3Be2L8)'cq_1f%4/F0:BgD_q<W_0<mg6SEY(jQXmVfXPmD"hE<
|
|
||||||
@8je\E;nM>C20<fji&g7O-ZcN62HVk&9cg4BQ(%#MTOYr@CKfXABr6pj_q`;>3e8ZX&%4=gZY>H:WrF$
|
|
||||||
FDL8pALi8E6IbSK<%IY?Uog.I^fmDm<j'SnPHBW<-$:3/eKHeME^,IdR@T,]/qRU!WHF@'']hr8pnApH
|
|
||||||
+,`K5XF6LdW$@UGXWs"8H58\JN:ANYKrWl&dF1tkX@Jau#sDOYY?''Cg0p?;C\TQZrE%+*@72@bNk(2]
|
|
||||||
%bGAms(U<>];bIZ[F]bA?1STZ[7XBc]f/.IbgTmrotJ,jqt@;O\(>r`?L*$?EE]0O0!4lPF$=N25XXj!
|
|
||||||
D=uNn_+Sg5V/?K@5"g[3K_:GA'Mi&`8j'(1c?QX[b`f.5&(25=;JW&YCHLb,H0\d9lEp+2(G0HcBtf$E
|
|
||||||
\p.un56E,pK)0^'+lBX/jj'P7m2QB->eYSe$0u:rG'iU:>C6<HHVV7UHVSsr;m23"a=0jmT=o]1>QB)R
|
|
||||||
gZeL`_73M0#fN]kluY3*[?fDt[=6:PV/.*11u$EQ9OGaf0')-%Fnn$PDQ@adr#L#B[YjCC\9;Fihj2mT
|
|
||||||
'?B_BhC%I[Sho_&Q4iZ66"\W7DO^Z@Y?"f\,9Cq'MVeZ!*[-<f-SC<91$ZR\RUaOT&pPY9hg4<>iq*.U
|
|
||||||
*Y7_I,6+?tcq]Lua2C`J>Mh7>N\=M:[GPpccgQ;AkYYjh)7-@A$uG`DH$TOO(/HLF:VYWF5,e;%6hF]i
|
|
||||||
65][g3mA63R7+.lp$N3.V]ajW6"fIfgoO;(Yt\"9Na!tS'^$[XcTZ(nNof4:X4Dpt<kl\8Y?#8p7us7,
|
|
||||||
a\Q83EAmDh)ej/<WZ-2DT*<"h\jqTNopr>igNmsCS->MY[Z]i[Flq%uYFp+,l*QH$lOd.1%b/J&ZO7Ac
|
|
||||||
\l.i@kH:s9WMbNln)e)$Q?<K+ltbR.Aj_F3^^%;&r:5\F\c1IFR:rb5Y;<3E6*+mRrW8q&0Q1fiZR5-3
|
|
||||||
Ja]J.V^aPqDbfca-=cq!o/H%`fA:<U[6Fat(3RVAPRkE[D)[&IpbD<@UL%$_W]M8oE=B5iB`%`Z@Hd#H
|
|
||||||
8Nerf]9krX?Y0-AHc3N*eYV^!*96(SN$$H*X)!,t*PJ!k2"h<&!"g[=oJ:km:BnR^G+]ZqnDQP6^V'&-
|
|
||||||
o=`bN2=.Bi\Z.`VT9Ids&H]oWJ\M:[$Z`nP3,?3br/bO(k,E)&F$FO5G'&1Yd.[G$Yb'lKKq.+go4I2H
|
|
||||||
j/$FJ8(p&^"?fW[>kB/1II!c[RE$1.H=C(3Qhh*.*SsPnQg<55g>V/f=q7o0j)>N+d%_W&q[<=\L@=5_
|
|
||||||
'n:.r_gPDHO@l0RU@4.:nL^57^#YG>j%H>8p@JDa(&MO_dGh=98coBr&HJR=)LknZmr&#\MP*Z+=VLWR
|
|
||||||
)H%9qB<7D&(HDo=SCGI,:J!^`MK>bC6kgUro4[VdN>1gj3frr(V5dP#cEB0TrO1>!4PkhLAs=0LD7o+6
|
|
||||||
oA$cL/RKC1Y_'oH'#fFDj7oHq*:F>C`)jpW(3/;<N;a"rgYrR!<p\r?9<D<Bmj=#In!Nddj$;#L:!I_r
|
|
||||||
78SWWn_QZh7D7&mqlV1O-MkG)mQB?,@/lC7Hul,4IV5f0*^A./i,Rte(]gjc>e?A+r4RrES!jF<eA/QS
|
|
||||||
@T)!,gs3mdg9YnR#qj!%ELnQ(`48D*UVH1RRRU#VNk[O#Tt`Db1:W3nM7u1gZdNZVY]97&3+3d*FrW"5
|
|
||||||
T)Z`ThVPcQruK`4&>*S]rW^BD)fKk+.4cr<gr"P3(\cjjg^9Tc2oe[4O5ZC.LYft=Ic(stM!ot"@dmOu
|
|
||||||
&eQCAlOi=0Z,LDHQPXX5I;nAUr)OfuI/?(:#N>+0Usta.Z!lbP*WfiTN)/alEs2ia4m]ppT<*jcJ*mG,
|
|
||||||
G&=u`e=8/.MsmL>SR7hDa,X3N-_?>6kaaPRs58$Yc<KZ]=U2;LW)QdS"ZIsR28!(+EoAr-\Wc0^kV$Eq
|
|
||||||
q(sRRktR.U)I+Y7LE,r9c5=Ud?Vqb.rU7&HFC0)M`ekfRo&7Ge:8UZ/LM3:\mLdlo2tmOm4jID8=HiXA
|
|
||||||
fnD=*n9;"=7tA?ijkNOE"PSMh)U%9fS#t$L(<K%W'],k>$IcFs$Eh(_.:-\;F2ZlfZ"0V.A*B9+/W<)m
|
|
||||||
C`oq:K5TnA5,ed#rs$5bX)%DBEkc2)n'^j!g^bE;lXSL!5CS[AHH-UTG_Fh9Xd8`G<iCe2nEdjX&[!,q
|
|
||||||
X+hTp*PU3MGcL3$c9?$cr\F+.CL5nOE3^9LhA#q:@L)hEPI=_E'fF7c,=ZIS^Je;]Nt;('H(nJDbSG]U
|
|
||||||
)3^o+@oZFAL:t:<5M9SLmdRP?=N!6_NmAVm!lYFrU"As1SFZ=/bB#VWg(Bar*d,uCAUf8?KYZJr-S*A[
|
|
||||||
]Ur<drR^WuMtV.JFQ>b_'J\C.Tii40?Zn=`F:HntMao.W65FFe;6-]AcV@".5t<6J:+L2Kn7/F4;>^3d
|
|
||||||
SOh4ViVCi%O^1@<+tBrX&BB%R2u(>)+rd]FAi?1i0?U%E%c3385XX3c@aqf2`6EpI-0m;"8^6(RLW*I`
|
|
||||||
2u^*6NgB,ck+0n$0gk4<OYks2r\1+'%W6p^EFJ[f407NQFqT^'2"X0cFkDUXJiYI&cVF?$$0$RVi_V`'
|
|
||||||
`BZm%ZN@0Boe"qV*1>.2q'M^sr6@(?)/U;_R0:Ae?[.dWpuM]#X790DN'-@')u@M(0(bime0,16*H=r?
|
|
||||||
L*@uf:W"j`G_nIj],PK;G=9dW65NU>(3e7?Q/CQjH*RhM5g%.mgeD."`81/RH?XBmXR$tYq`ai+9'Nag
|
|
||||||
UAr&,p#]<M@CjFI)sWN3Vr>XB$RcFtIEk$i0(o>3\T$a1&3\jo#MDuE[!-Hl.^M<Z\jcX7pt;pPLQlNH
|
|
||||||
h^'?##B-`//b'u@$$hGNZ"ilb+;+@%&1\o/D,3S!m^VT9ET<_X*m'&.:KKn*d+XQPpToYDM8,\Ho)ZGT
|
|
||||||
c/qN7(T0nnh<b]EBfk,1Fneqj@I)qL[mqRoUP4i&W5E-$E;Q]2rp4^OX]^ZFT=mY!U&RJ.kj>L;H@DHI
|
|
||||||
qbFm^l.rQOQ,`#i]sE0mF&MIiF_Vn*f/Oe8U;lGZB53rs&\5odh2Z9@^:44`NU9]/1_+P?GjF:oVK'2S
|
|
||||||
*]8uFeQh,W@u3%&,AJ3-C)gdYON+Q]A(!?XM^".5N%378aR'QXLSJjK@s^/d3$#Sh)Q0&MKuWADl`b)n
|
|
||||||
DYkJh*$Wuo`7-bP.kQ'\RQ*-!f=HX=C0e\Y-aa4;:*7t0BS?ql`^*(]coA`'^".tY2jq\MQ$o=FkuuuD
|
|
||||||
EOqt'1_/D2NPk4p`p(pLVK(6Ul4isL2%HY*HL%<.8phHoLNK60Z#cXT*(SL<`7-d&,*JaAqPG%%`p&sC
|
|
||||||
LM7_io=2fF;chC,I1T?;FKIm-3*i>"]!#FG1d.OP5[e@H,1efme2Y->4q"@)ZGrRX;<&d3OE?ANC6HR;
|
|
||||||
]tG/D9oWXqpnVo.'rZ,N0rT)tIY#KVS\;%8I2$#93+Mn!DoNFTq%)8'XqpGeY@2PAn<6!@S3[T<HprNs
|
|
||||||
AIS-B1Gm(AI!1YDiK"Z'r`mbS*aG[7_3Z<bebW]O7M!O,&k:=[HD.a@cQuD_/V?$B^]"fMrRr?udJs)R
|
|
||||||
5-PEc^=&IHX&#:$*ZJia?-J=D-lqtH];i:4JP>pEpN>!?I'd,&M#*#HE?YqQI[paj&/L@F[8;kZ)dgQh
|
|
||||||
fP,M0[ZSmKj6d&PNR+?6S[:h]gOm<o-8AFHLrV.r<#G]eCioIOTG&b5FoEnaS?stqJoCG&K5h80"p`l+
|
|
||||||
Z`eP=bf;G`2n#L<m\@ZkK=%DD-.W,q3h9Zu,qk_L64MPnkX556>6fq*kq7OUZk"n^KB\^;/&aKD(=1c'
|
|
||||||
X.ZkPM]R;VdJG.&PQq-4R&dE#4#^UY)t(L(W#;>b:6W(mno6^hfWZTfq!J\;Qeg.pfXf2<B]E5;[;_bo
|
|
||||||
`'c,W"NG$5`*S$PSb=U9"4UUo(7?eCBrDEs;2)HX4q)_M+b*limG8(jT66e`<jcD]=Vf!'k?rcc-'LIr
|
|
||||||
%4<_oG&#?N%'@2IHZ:WESZoC^4rCnDe"iqJqTu)A@2Hq)4mKM6Y,d[C7e7qn`Yh':;K".Tk4<WnL5=4^
|
|
||||||
@i#>@^H;\Vjeu8J<Jb^:LVN2cj2UhJc!nf/@9RKX[OL'?+HC<Q=s;dO3ZT7^LAlcsdS-<A7mo,sgUm0e
|
|
||||||
`-)50kUe:fhaE=`G>p_.]g-2QNr$!."fMD@RUR"\B9<hV3@;(fX&c,n5!3+GRuP[8(qd>g%Z&t&@mRFA
|
|
||||||
nfE&P:dWd^goQSIe=dUQUOm?%4LlhCDo`:]:A;lKQ,kl_:pAn/`Tc3G[FI6co?>_h3<!m3e0)ZEO,RK(
|
|
||||||
Z('U1);=,aO$V=G?Kcrdg.u#_G'gD%I".t776'"Fln?(;qYQ%P^*!&YeF=rB[_jg5rY?R,DoD'.@=S9c
|
|
||||||
)mJRM/oM-h$?=kHrT!pMgUZ]$+$9?X')m'eL&(Wof(4B,h4hZtDuXD),2*=bAPHmu7tNJYYgNZ/SHd^X
|
|
||||||
>&?4IP])RD.bN+>RbuGkK8AA1>Dr(6D7B6>2qfA#X4.![aB%rn07@seRYO&C#h7b2>GeUo@0]dq^Cb;9
|
|
||||||
r5E*86G8rU/Gf$Mk?(!j<I(Sq+l+qIFCa0OH@6g1&Lrj6SQkVWVjtGE2-TE:%JUi1A^jT(N-)3"S2%q=
|
|
||||||
JJ[dSEkJ#Hj#*=q;#VV7UtMlm6kSK]l-,qQ4D5&-Q$c<-lci1Mb?'FQ*&3X8@QVsg7$%"qXFc<NlV0N4
|
|
||||||
X4\]pQ^@0gHgg:$TS[t?$B;#t?SgJ#4']e+o@BN&i-?/b'2b0l,GW1\DSNk)KC+2NWF9Z;$KG&Sqe,OQ
|
|
||||||
[@7'7IlT^L4#jrC]5WTJ&TrB4o))oc]5h9hg>!qW\EVH=AepoNr3X^b<4%%+"IZ1<Z@cGi7eSU/l"AW`
|
|
||||||
k,T47;Rth?7;-!s'$rQ1oSF>-oE])UjeLcu_!qp#h32i5mk)Zc7(W`sb[B`HWIrH4<O_'=Wt8j[4OB-/
|
|
||||||
QMDimbZ!DjFrds[6k(Zb_Tf+dJVWq-j?=Ml7#B?XpBt8)+#?.:[KMUOZpQ."K$>c)$8s#F2=&,3C=$P^
|
|
||||||
2BQdGc[/5l[$hd;3LSLYr<$K/A4G;dUsJaCI+..Y7n>W:"MQLq!SriOp>7110D;-%[q;?Bo[[Q;VW]dL
|
|
||||||
ZeD4KAni0rVqWeSCNobaSX-NTl-Zhq\M-+UT:.]T<iaE9#+P0u3]4dgp^>g`8%LPPAmm&nWrMS8Pao79
|
|
||||||
i`>HbOm8AK:Ln/&a<s,(WMEeA4MO^QOA/k5LT#ic*77#;Df8)<^-d?P>h_(oS)9>3'U%<dJTLcVS(e3c
|
|
||||||
D)Sj<3,DaE0sk4pKXgJ#Hs!@Kcb#2YqlR2Ue,/SUZ*/K?h^@RXs73K#ShS@1G^j_Y]PcHp>j5Xs=nTJN
|
|
||||||
XS22]9=\F@o(UK5e;.A#GKBn.5EYmNe!@<EjlO$[e<a3IEr".pI-nk)Q"]\mW#\4dM.a/mBU+$%mTWeg
|
|
||||||
-gu7D(c$Hj8t!(oC5pdn=)0,FQqoB,dZ=$!O70UP>*s6n[Zslon%WYWAY(#_SYWT+X!R.4VbV%5IA@ka
|
|
||||||
qZuhm;o:7Qh*Jd<rUO;rXN=,9:2N$d?V.V'^Mn'k+HC<8<b46Jhni:jg%(kGAKS'UW"l9.h)<ZfZPp)&
|
|
||||||
:XK#;V*.']ppd@I`KA4.6-.PVdKllN/5,;B+n$St)_;4m3SnYN9VphTlZ7\)eN$m0:&Tj+TMFdPX9J0&
|
|
||||||
?iBB1Mg*oNAo$esSdOXll.iC'Ff+Eta8G>-hcd)OeCj]3Q(a\Zp"`/=Zeh@\la%q.5[DqD@b1[nC:/QM
|
|
||||||
,-)NPs2:23c6!JO0tH/4j):[DjJ8V,iE=R.#1ntqcBETcNr9D1"(9K@=WnlYgr`9J3LK^O]iKFX327un
|
|
||||||
CbrXe.'Poa[/Pm)k8WSrV0]ja])Q5T^Iuhqk<JM19(e_mlhug]LNN\[ZdrbOKam!'AX_UXm?&Q)W/[f_
|
|
||||||
Z6Ss-3oV%J.-E^,F#<c4>Zl8:c[XrISPCtZm*G,F.<sg=)eI*?iTXBca,me].TMG0CIc4GENdfCAfIWm
|
|
||||||
dW`m<PNRQSe>"BL6fWesj#qEHs+qC*H0#4Sq8hH8ls.Hk5B5t`h4V,+S!F@[\2#jEO)4e+IBM<t%M8:Q
|
|
||||||
\E4RON'1D?XQa)&*"2.]DJhU@;9\cpN*'3$lrfm"T'4U_/5a`B[fl8C-S[4#r$CdQl+MOPk>s%VG=O^q
|
|
||||||
gL7>$^Am,)U34NUK615LOcrDIb:*cnoU#2mb;8\:Ss)qpDk#U:qdfXt;WsO``gtGIU'86@_a)2mfRKET
|
|
||||||
:M!$X);BY2c#7X(W0X[O@mc9`25iH7fHon;`O0`H0E5V&)1h7Yg:d*Y"VJ2ai$J>M9;"]AB]U^@Je!Zn
|
|
||||||
(6o7!'.J`r^G#T+ro7_r'7O8k6X[Nm4*fD_dhD$%/nLjD=1_[pRD"5#3]gP+U(/K@Pa0'Jl/[3-H1VQ>
|
|
||||||
]'cko%Yf?qB]si?h'"T)X&UNF>ALFfUYO\^:#7ZfjHXQ6^@Fm:N^%kYL[Pa7;-+`8K/U8ehYQR+\,ISC
|
|
||||||
$%pPhOj3j=q.Hl(qm9nh`LXL*"'M%Bgl%QXi9gEI35F,31=Q&*=qRng%a\'9>I92c,pKS\),]^D0uaqg
|
|
||||||
c^(RdMDOHh,8W5=Y/kVfPUPAK'6,QD+C$p&O<Tlgc>]$Y]95/.foTss\WaUp3qmA"XB+[0CC?0(MZ5$<
|
|
||||||
i\#jQ>:EK3A^JJG[8&<TCWLM9$4*@7m]Q9L4#-2&:QkZ;PB]4tl%K#`(okH'Q0B^S2Pok]XSmT.Vi%4*
|
|
||||||
O%gu1jO]iD9B)jZ?`Af^3I#'$61k(&*qG@'>80Fk&(_9O@p@)"p0_'ofZH):4@oAW%TP`X[89s<W949+
|
|
||||||
VKV<C>'lj)nN]:L7oC8^?$.2ABZ1kddIT]IBdFbZf@p8E6,1R1I::M&qdVh(0gkQ?QPsGKp/8a^deV<[
|
|
||||||
YM\M%qHdo]9o6R3)h%1Nk<EtQV$:ZA5e_[K`sHiem>dc$cD[HKB,qkt*oBV=k^0_XBn<YR$<un's!=Ds
|
|
||||||
o\R^]eNa>@LI8/NEh.L:5lK6E5B-50>FV]Mp.JOmp"h<*B0e'!`Q-RRjS^BBAjt*#Okj+-%6Cj4Q!2lf
|
|
||||||
^?GG=;qs4H:]*8T(A\[;)[#g[HoEg\A9\t,Flo;Z:lJdq%s5P`QcQ+upBDAEHmnU0H\AdurRK%:Sks2o
|
|
||||||
<hY)'C[p7*4-k<&SD9l[hYeb_B_Y5\[&B:Pkb+Ku86dVO-Mi>RX\t?haC2,f\SL;f\390Q$U9!G#@U)o
|
|
||||||
[<d)sHpfTG-5eP?aaEq"qP^Dl=QKtU"G'UE+PI!n'aa?Jh1EZSDKEL&5taa/SqMMSFhKYK?14&0c[TEW
|
|
||||||
7NMt$o^(1!eW+n!k]Lk_;^ETH^SR@/0AFQ5m(?L63PR,.aUm/9T9UBZ)-C?;9n27"rPO&r^ELJQHpeR:
|
|
||||||
G8,Var('ZeT@fe9-R'Q0^:sQt&J!$^kRe<K;%aHNa@MK!\+:BeK?RrIgaF6&^s"6dE2^9Pc3OY!p]!=`
|
|
||||||
#`#G8D5ZX3QIjBR74'bEPc6A=p,DPU^W+;uHcS&^&It):(MT;qgDStcr-+*=5cpL@&IoOYP8W?;GoFr]
|
|
||||||
cQ"Gi6=BXn/+PZUEk#oR&ItT@,906c76&d^5@VVp_SNS&n>p-[rPUm+MtraaW[EZHMd]H#DClZcE$/aT
|
|
||||||
6!_],KDUD5@I'5=\s_MGGFgoANhM[-iuiN_>:jm7Iq`(4]se_g%k<O?\^;XrLi>U#g5+BHB"'^JSnE]Y
|
|
||||||
Z/Q'>a+mcLqJVsn/G4X8$5L+7GlHaur,c%`_\fMu_6f&*n#_f6Z`dEj=(%)Fh"$TjO"i/O1WTiWjuJ=8
|
|
||||||
,H-K>)<$*<+.!QLW"C$_e3*IfUJdJ!\H=]4HZm)+(ZcgZ^0H_<5\/)MKL8ZR;r!R7`_],a36%Mh8NijC
|
|
||||||
:?[t>B;&u)bh%p[89%(k\OSWdc/2DWR,*B,%dd0+FWtK_g'-1VQQa5>OGDce0B=6E3/EpG6ddSIl7tC*
|
|
||||||
):fY`cji(Y+rlR<c80;@#Edk+[8&kh^DK7H)8DX(_Mh=BLI#'I]S22#g*VV=_LYK=_L_Mt(u`:!G_?0G
|
|
||||||
_$GfC^4o<7N_M6&Rse8SdnOq8hXUpWdY/8CcYX]Drs[s#%ZY?)nm=/hTcNFAGd]nf<qP?Y<h&m=DP1(Y
|
|
||||||
)uMu0I^@fKKb4fj$R6W^1QQK_-6G]:1s;prk\VR8#`#:W':\MdaBVpD("@G8KL8\'$+!hmA;Ok+")>>Q
|
|
||||||
O)Sd2EY,@S'TqJUUj+\sI$IqDSVd-[UgO$@,=[U_:EN"e"1rJMfP:=?O>X%0CQ(m=Yo+D*Ye84;PW+?k
|
|
||||||
d"j(<#Us,L=kH1e2g*,h>\_p/\=t6?ID9p2kCQM\njF2,BkEOHpPPjZD^sVjj*aP&(frOC3J0%c;le%>
|
|
||||||
i[lhL5@[BKKhjEp>H;?6@apF3eGnY/5?^.Nohs]&qW2cS]hcf>ffQ,:?$/e0U:9dKiGD/hX?GF@bU3q@
|
|
||||||
0';X=-?_A)dQ-BDLf\Wp*OttEc9l:P]S1c]MH`IIZg.("Qd6k%kYeiac%s!0Y2$b&/Vc2u*kg0+XJ'9B
|
|
||||||
lnl>SXrpp3E7_$jUA]!l[ArAEB!Ot!a\U-742hlI]BuU9U\M;iRI.X/]XS%'2J_$Wq0.kVpY=OOW#$h'
|
|
||||||
!Fh?];tM#tK9Bd64l?oc$H=Wa4SoUsW3uh>]lN4P,KkDL4H>T4i$^]@:^^0m.q['a8f>Hlc0YO-bYu]s
|
|
||||||
fQ$JbLcBD(Cr&u/e+9o\GE&q-c5!&&ZqeAT@m1KQmh83Me`OH8nS8q"`Ug=n4h(gQN=N=aVfH[7Nd!H.
|
|
||||||
?acl%M^t!*66b;O'#TU)?h+]meGamkHZs.h#q;](Ebac(I:.79U=FHN(<`Em&`:4-^ULguX5jouOnY3P
|
|
||||||
0c$dNc?=6hH\2fp420!e7QjU&dJ$/.\?'r$o8`cBoGT@P/u<emmNl^'nCS>Xiq5Pk!as[)oLmico\PB_
|
|
||||||
e#Kj%k:3#GfO&")Am3^neb%&('gf7537%<K3HL7;JjX,k%=cK^o"@d'I[*m$WmXl\`P[T-`/3G`e)1<S
|
|
||||||
0'Q]squ\"On*\iZi\e5tG"/$2S^UlQWE#(CDkol*npJaVm!?'3-4h8tI,*0qe9:1M(.LF-#=@2L0%$Hk
|
|
||||||
XeP[tlrmh3SUY/"6:>pcAUWC^1O:h.da#ekd.$C5>[RqG=nBm0CGMqkiE\&#Xp0Q-8bXW!;#Tn7f1=C^
|
|
||||||
o?/"Yj:BC8`ZE*RD#CUhM@EHF):^UdJ#47[aEFCCW!=1HZ=S/EoN;]]B*=UF^Vtu!eCs[E>EsE-?K#2N
|
|
||||||
>q,^f_76PsOqpTVncISFG$"*\T+$+nH_I@ZUV"V\`:u<*]'F?ZFrCB7m7t?naK34KZgG<tTU%P4hNP^N
|
|
||||||
O*sgH9k>iNhdRtAC_*7ugEIKN#$t-5)gI<jr]S-pA3EW_B_UF6Nch2L-@Tbs-/We4qKd(FAht\ON+aWo
|
|
||||||
ct(p3V;0gs@k1'W=m<Ar4,hg'0ApVF%iO?Ard4R\ma;\`jgg_"TrrB!`)enX.3UEUB;G83Y'u.gLmTX'
|
|
||||||
&^!*Tg*?,LK<^c6^T9Nn3ts?jdI`3khnd2m(R$Jq0sPjJA"J]4S!pJ*UbMA<#GT1n=L-oOAGV2K/rT]4
|
|
||||||
B+`9knc$""/UVXPB*nI8ko%pGa9k>;:agCKcZX?5l^&]NAtr'8Z/pu`9>8_j(f\QNAZ7r0A>c)K@Ja2d
|
|
||||||
;1?5LZDbM-Q.G"paS")Yr7j>RW_S>.@8^u^j5\V,?QJ?$!r+>'bLgi!c.MT^*9O0>GMI(XWm8J?92,5D
|
|
||||||
;o8OPYpOhI5'c=s[??Bi2p]4HVqB^U>*$,\:c\.$^KShoJ'$>`^,KMbO*54BoQD:]rq]g9Z'EgBaYO]:
|
|
||||||
I[YH7:.'!.omn+"f^d6Nj=&+cA'9]H>CJ(!Hl9\UPt$mPQ"uAM!)L^Pf$XIt](p+8[D/6`(gF^MoU(l:
|
|
||||||
J#7]U%l:YaaCED7ru(?`\l0423I-'Q`93BH$o2k+A<*oSjh7u^RcAkH^:r#jkM5h-aPXa=382FM4I5lf
|
|
||||||
W3OV?O](V7;LKKAi#>V;Dj-K!1fqdsI^SK-oUC"pM"2E,oChOpVl$MCBOTEJbu0!,Fc/ie?.:HqnQ7YA
|
|
||||||
q:0qd;LW2dg5O,<+luf&h<T'11SgO(N&\8thXL6ODI;WD7"tI5aV[<XXiUju(/dog;GKn7^H,.6E_ZEa
|
|
||||||
9)4aed%pS_Se<_9,7qoH;sPW7%Rr%Q/ENZNV48Ml.F7qP&Y)!Wq-=tk8blY>f4D&Aa\mScG,cUCk&SZX
|
|
||||||
7(f%k`J(r`6H[E,0'-/\$LrU?rn@463*`4QrF[\0i5lKb&m-o/.>;\&(dY$&1?SHe3n$mEP83Jm(qJaD
|
|
||||||
T>asD9`._7=Dkf),D[WR)0t^p)0pNJ]=eIFg%r`rN[5or)D\$G5t:JeabY6D)*tX"UgTG\g=m+g78[io
|
|
||||||
<'Wojll0dWjNEpL$!^%kNbfeAc`SN<LO9+?d99A9kZqI`#&;R_$AR.oA^j234J>cQn1fK3OqH(Q1*R3Z
|
|
||||||
N4eq@&WJiJ5XpDM,GT@plRnj!A-=WBOqCQ]0j(9,9T\6s[2/^]"=Hf26lF+fe_49C&r180WW/3F9qJp"
|
|
||||||
,6CnORO;E[quK<@i-s?9KPSGlS'.0Jkc7/51\4!(<S[=P6rcsfG[nMd"7]Br]1)ppEsF5I3:Bi2gH?8L
|
|
||||||
AmDeI78-nFR:J$UO-[I"mp-tTjX7P-jZSckgfRX,r8j\NX/q5'"W_..&=$Z!&-W['rL,C#enZ2?cbRdV
|
|
||||||
c,4$tl;\fB.?URM\A3-KAfg%<LU'u/[;,_q?QOJTnD:HnMRq2Jp4t,kl8HtAS^6l>hVu5+PgEL0F-H(0
|
|
||||||
]t_u`)@]oOoXFe)l;Z%&HEACgC=UIe8C0b3;LHQ\O(c3f\M%$+g3i;]o_^(3;6tDlA&5+s3`QW2`>d(&
|
|
||||||
dc1eOitOB*)YJV)$JW`>2d1Xs3/&TO:`'duZ3d)(a>_R5*r1Q>Kun1q[^)::-4Z6`gtZ(E_a[&QPqA:W
|
|
||||||
Y4Vf-Kh$UDKHd/[KM@.4J`@8#k30JN(3)_V8f&YF;#>`1'OL%+V(j:0eOnCsXh==-X''AK.UTa6Wc;sT
|
|
||||||
LdLKX&5fAD77:RQ?cJ16ffV7S=$@Rm62<=;+^]"8^3$;.9ujUSkH\H.^\2T@XfNh;/EZ`(,#n3ka^3u-
|
|
||||||
ZFoXZPSm14FXQk*JN2m@h_Mj2qC3tLX%/-2Dlt7E``?%(i>s&Te8@_D]p*nlp%$>V'4QG5MK`2c(n'6g
|
|
||||||
!gqPKeX+2L!fn>g+8MaK[bTi,GWJ@QaI\+)?EH0n?&Rb]N(LReD%0MV656&*$?,9=FGm0-#]JjueC05:
|
|
||||||
d$p'A/?6O.k5nV@))Gm?Wg[+@Rh5Ih>9R^9<)m0@i[7-\NRaFRhbHO>JohpBS`h#[qsO+=7G3iQi2Y7*
|
|
||||||
$Z`SoIbm!D;20q^e>fkO,iX^"4q4(k0KZRhRa*)aNZ[!Teh@%LDY]9J$bMiXm%[%53ba]sB0t$.m=.,o
|
|
||||||
Bd`..l7PtkR]U;MF[se_IKEs0RD&M104Z4(#GR`\.%_JN9H925o-9Fn'=WHDb5#uBgOC7*CjDF#IKNHm
|
|
||||||
^r)q9;2lo2Zel0\3MReD@&tt1&e0nB7fAiCDlDcBH;N@I49jGXA,`!-]i%&XD2;'7phIR8S\&P<l>M_7
|
|
||||||
<,$a(EFlbima2UD7:c'0J^psL-tcMDl5o&GqE^ZLcD[tr)<D9k7L6*:*;cR2cJCWFb*gaePV];_-tgh]
|
|
||||||
7lbY14Na)EXaM&T-giFP!YJ@f,.9^-'JV^4b*eVO#`*=9e)RL+U#FBSfC33rbcneq`ksX,kLj:0!cMq'
|
|
||||||
'7OZYh0?,7XnJq.ZU^ra6ltFu*94;o6,2C;;9m+=.>GJE`Z&.5m\c,rqb[f@k/VqBV>ZJ8F6aqm`mi^Z
|
|
||||||
\#NdZMC[PA-nfu`1Sc'(Gmm$25L5OQ).\sfYt$fM4*u-?3R90-[`F_#BqkgU6L#_Kal]ocNnNt*'A3Z;
|
|
||||||
X7]:1$?Io].u\MW9MF"bCYe?>q/_+A7l`Zfbkql4;u"ND]s?,mkaB8jY)p3q?M,^ZN+R=!ZXp$m5%G/c
|
|
||||||
BrD\21GWQM2KG3S3R:/96:=Bi#:VQ%]PP;QrfYYEZZ>1Ga+i7aN,f-qSYPTX8(i4SX[g^gTSqR0a/83k
|
|
||||||
:*#7p(=b/UgDK$Glqu&URSh@X';M)<VIW/^DX_dWY3hcjqL2!5AM4D^992IWkUhj9-=*M3QNdmV;ilM*
|
|
||||||
*#r$DgMCcORqnG!+/EQ:9JmYsCY@K[<h&^Tn0hr=p[BToEOZo2L]>q/8KL>?@hL=KR;q3rMCZD:CPB-b
|
|
||||||
<YFIO?^-elY;DHJ27"8Wb1Z?2>MXr'`D#2X6#X0BA="S9*p$(t^aNi1$q<6irSm?U4g-$0^Xj$+cZ1eE
|
|
||||||
f'/-u4!4a8TT^uS*jJV+>34`nPk`BLYn"]s\KPM0f"dSe%Jmk@eqGuYHL<Jg,E3SK3dC`<Da7V`KGZTh
|
|
||||||
rK7e&);UlejF)?tb\`MVIAg;c=-E;"J,*1][t`FuOW]r=gglVp9%GUN=[NZ0UD7(kmFb3jAemEk4W3@)
|
|
||||||
.!pa,.?I3K:sN(2F)ulas!G[p\Z)u7_@NF=TK/8"X`7(=S;gqk7^SOZE>i:&1j7)Q_]$Mg2U<@L[7b)T
|
|
||||||
,\1$Td=`.G,PsOqAL=70OW7*k(J'l^R5fT&T:gY^fWon#XF3ZL&;Lr46QFdUCi0$e_kV=_G2$s%;79\E
|
|
||||||
gDbM%&&`qpL*ODB[.qF^6sEhG13-)tHfLr]Yj*=)$DJQG(^ZOQ&,(;;D"IDbM8.aP1Km574upg"Pk!g0
|
|
||||||
eok2/B)XlU^#_:pQDD=%kU]U6nL0kiNU/bh[3!I>U/_]t!nT6rE//83=.5ldXXkVcF.d1]ha>sJJ!6U,
|
|
||||||
%]*.P.Ti_cXb%RYU/go6?ZB+SUBJ>@;&;T0KD%H\la$?M/:3ZL<,3WNd1Ohta'[7bGBA/,+.HW6+.Lb]
|
|
||||||
4N\.%HJd4u\6DA.b2l^IMh[*iOhCE'-qC<XN^1uALEdX!^8,G-_j*a>.YeYiAl)a[.[t:t[AEgefeH\,
|
|
||||||
Z,qjja!RG,7s?6Ak+a/YBU.(sO7g<1'=N;Ia9].t$@?:9ncO*u6/iBCq._U=e)LhGWmf:$A*p[o1MLDi
|
|
||||||
A;I^`pN+-9rud8&^>?k4/+hn@E)f61jU9+Rl<B;]7l3tfCF*H9S;=+q_9@bFP=mKK,2thAB27NTQoX[H
|
|
||||||
l79h_SBSlpo!a`_a50e0Qnug$PBc49:q]B+b@li28b5S>ZhA[eS5\'QGu`UW!lFEM0YcYhS5ZAmg2`8F
|
|
||||||
Qlbu%%CSFJ!BZ4_N&B+ab]P^qIRHCEF&%),S=U6rU0S?I@kf1klBK9_%dd]?D6dTo&P/=2KKYg`4ljjH
|
|
||||||
%Z\Bs+1Oi'S2;>[RihW=1(rO7Mhp[?7;(Bs?<^U]7bN0td/tlT0Y;I1Hkq1^`Fn:dQ+Wi6C7ZdajKX&J
|
|
||||||
".hs*KVWh$5gB8V_If(r0+lf!b+S+(WBPi>pVBpd\C^RMD)O4/m.ut(BCiL]_V7/`B4&Jn:Qr7(435QF
|
|
||||||
SC!BK8S`?kVh'ti76BbFh1InWa3Eu'V-"7<>t+ad=B@AkBVdkj"dKeRrS[V1cF&'6Jb)=JcMgR2E9EfZ
|
|
||||||
OZqRR79%Ec(njP"NuV%rE\X?hi<<S_`5Gc'[O5@<,WjCbP!`b9(crtUGB7ZIqXRuKC8*tc%JSS/^Cf'L
|
|
||||||
rZ7T!ak_^r<$qDEES\snYo92HFQnaC)!%$LA#BOj\jbRIXX6G#FO=h\3'5c\(OcOn9g:IG"**.[3Q7gc
|
|
||||||
lRV["H=*dV0j4mo='W5A6D;V;8(>YsY$_0jN%oM)-i5FFLVMhtpa88qq?MEtIp_+4)deJn-$T,^%%m$[
|
|
||||||
fQeiQZND9E;AraYlWX5G`Y6'#gLm&A'a'2a(*8#?Sd+/nH32oK(%iLs2EFhSYZUDjV&jLL;C^G^91e18
|
|
||||||
C`:XS_K167K]f!3<kl(j.Aik`pekpP[,jpur&/pOL=-?Yj\)jc-H]U]]4Pg!QNAY[QVmq)#Z(4nJ:4VW
|
|
||||||
nUBJgI46MZM'Ah?&k*5$n>--.QXV^tj@cbCk(GD,V"i;_!ft/p%1KQOSZo2qkCm%C:Ab_`NT7^Ik-#9L
|
|
||||||
&$&2giCbmFj+1S"';+cj!6^_Wpa+H$"Z<@T@i0qR`HH<8B>;lOX9>u#HgBhX2pps)n@\"dQm)-Y."!IX
|
|
||||||
rod!(S3csK$>"n)'\HADI4;D1DYd8NV6j*Oi/QC[V:^05GtU'W6,&T^C0d&/bgGEOj@`X=C29C,&UZ$^
|
|
||||||
:A@_?2snu=1O<tl?<PA[IH4&+PugjB5kFKA*<%iGBiL;6_CCej:hT`!BL_eq$G!7d%fd[)1JmjE`66u;
|
|
||||||
FZr>["3c/BHj*$6RcVX2`VtWG`V(2+](BeJ\qD_J;YCSLN)o^O#2A33!=iJZU_L_51>*"Ab+`DfpB)s;
|
|
||||||
EHi1&VY>kaS!D*"&)V`#oAO[r$L'la$G7?kqFj3SVq8chY'bEHgmIIIrg`#_'_k1Yns(1Y>FY6Y$"(h%
|
|
||||||
%ik<u@5W]k`D4<@^6H_tq,2r,E<jJE9@6odmA?.ZZ][(SU[Te_A5'_%7=F%VGBn;"Hms6Mf\oJSoVtoS
|
|
||||||
?'=!8jQFD&=#h4]FB&`mrBakK,R:ps>*a`)=l?!CfpOd1V7KqS1`pPQC3+9cTeiq5b:8pAUqYj%;\g0u
|
|
||||||
obTiHXo%St3;`##`8Rgq/%d\7-2]LfhW<0Zn%ju)3>u5koYtR1[)=ro?Ddn?.On`lX`j_je!Cd+gbUcb
|
|
||||||
HuheTR6^m8&f#s6\P8Cg76Oc+g9jjNLcIYL2`$!.Pue&u#M>V0`9NL(c\+MKgkHCD0.t_#C:1XKr_':!
|
|
||||||
S)1ca8!C.;7ZW(o^Q)0l`r%;).<73I*Uk(HM6dk%Q7i$2R8j-Q.`lTo/*i%G>scR@<VXi\*a!uF6Q16P
|
|
||||||
m2-:Rr:;F3lQCPa4BG##roB11_I7;>lk(IkM&[]/hPo>NAS^SY>jq[;Pt+2ZiCLd*5mMjo<,rJ6[,.JE
|
|
||||||
^HrH57dlreb8[r)cYdLHdILtM>q,*i@N.`R6^78n$9lrm3U8QEmn3LVW2EG9W4PhA>5Le?'CB_(G$s,(
|
|
||||||
OS^\@!Nr4JF;*"3f;7>MGbQY9kn*:eQ1S'Q:d)tn"iYJN)o#u"0$L2mmP\fliUm&WhU(iH!B9mcDiK\W
|
|
||||||
EVdJQmtA+#Ar_1Jb]r*8T3ppEIHgY$Va3ebnd)L%*]\p8@.4@WGE9CD%'ELB$#p=C9jI7DDW_cYHhP*U
|
|
||||||
`N>H*AoG-J/@_CE=a=O2l5e[8?Z@!hB:&=THkUZlk!e3TrD^_=ZLS9J7$$3U3KX5(N[@q2m)2F"Y<5-$
|
|
||||||
;*Q&f?Vr7@X5V41hN(_ZiR=kjEr:$jo66E8ibA_Q^3&+6)@W4ecu4792?Z4[mI\A9<j0FEXZDk7!4tU^
|
|
||||||
GbUk?G&bZr!KPeYhWrj7pf1qH@?6Z#hul[E<\$Y>Hj<_gfZs+A?5X&i43#mA^HLHTcEh"0hZ9=)]@l9V
|
|
||||||
3%?6I=<eKFY='5AP!JM$&+.g0KcSl-Z'FXtq3!-&aB,*baB'RUmsO"'m-W@7neHS_R/`:i,TQYQE81E>
|
|
||||||
=$ku^'&jaXjda)*pA+17nWpB>2':=>S(9/kCc^"_I)FoF1f@\_?T:ObfRJ)"hmYQ2\A*Nlq,O,&oCW"$
|
|
||||||
q*'3;rFJ/n;$FFn)tdb9C-s<_oGL]P9]JidXRgV`,]`])ApuN7P535&G!GOd2X)7=GM]$uf\a2oh_jH,
|
|
||||||
V;K`T#J[#-:2*R+#AIO_4X01UAiJ%`]P.o$h-.HMkV,QF:4Mb)'MS/tOX]_/PH;KTCPY'nFb=#cQfbiA
|
|
||||||
iPeIlfT"/t-q:FcAm29lK<\W-+hfn6.%/!/k)hMTq\tq*>%!sorNb^9j`R_<qJ(<KW-n6H]LJ[hHJNga
|
|
||||||
d%O?u%WZ:3Z'i=TH[4>7GpfigQe_,&qJY$T"?NtQJYd?Um7nel]H=m%=EO+aLKQ!c)F!6BG66`Qp/^QO
|
|
||||||
&P&KWLuJ]CljE@sf0l[_]t*,.mX+\McWe:"B<Q7P"-,?m90IC]F+taCRksE=hlG_'ML4!:cO=R(`7i<0
|
|
||||||
p>4L[VlWi"3T-ID,!"q.j8ACA@%L`N\5lXUWO9X-QWhmmQhkGUa;_j=2u)=`%FRI`Wd>@s8@Q6aWKm&q
|
|
||||||
50+CDC:@<n)p>p($PZo^iD*0/1bHAiMctA9k7da8IGFR?jJC`*,03E*`&[Hfb:;(FgB0KaZ>L)"9.tE>
|
|
||||||
4<^/[\e]&03<HaXg@=_P1SuICh\soj06QhjaJ41ELONGol#HAsN*K]S`RXTA?1N??oG/&+U@.8adTbUY
|
|
||||||
q:"n49:*LgmAK0;kq=]tG,X="=[K1Hr%G&BYfF=hc<Yk?FHMpdatp=ZMEBa`S&7>`jbp%;b1krhUq>a\
|
|
||||||
U9rkIi=Q4A:hc?SiMBl&`[\R@90LBMc+Wl0VGc3=I,q+>P%L4FO7Q(N"o0][HgN@'nl"qC:>)=jIX&5W
|
|
||||||
[q\.Y9#uF*WNUL*XJ#dae0u9<?iCWjob_UC0X5tVa*rAaJ&C<=o2>UJIJ[rqe;r+=).%'q/%j:@2nlQ+
|
|
||||||
o3Tjt)K.3e?RWS:O*$@-0D-P'%oVfh#DE[i\1n6a[U(C-q(OZ(fkM@1l5_*GIqO#*e+i-j*VQr.PHD^Z
|
|
||||||
nTK2WX^>"5l=YnA+F0ri[F.]b2nH*Bf27Kg3-'#26ofV_$8498+YP^O/2-Z+'I-lAV$;r!IQ$%Y4Hj'k
|
|
||||||
4MHXr:\7Ya#k2jf26hPGcRr\.\9lg9`Qc&0ps9s\XY91Q%/XW2XOM<u0#$T>:==O'G8Z+3HgaSl&Q!_O
|
|
||||||
%B&_Y)X98o_Fk=h0D4K&i2,;+Aps>>?Ku*/QIc].$_Ln;B@Y]qHJK<`^UJ62Mu!6_l4K-HlLXr$90G:f
|
|
||||||
nkr9+PA^:mUO[7[%<6@k)Hh[S+SFZZkU'*t6dJM"dRuaXX0keGMVi6VDpk4\-.Qg#O'mf%[+n=AfIU<O
|
|
||||||
TBhUb]lucGph_\J)=pbPB"t.k:!h\/e/6Gf)h5aRWuHPPg8u#So>sY=aCUY1H16o98:uQUV%t_.k)]d;
|
|
||||||
_+Qq.1i_PoOJ?dTY6PS@s%hdU$*K+cEaJuUVS,L.mRl.ooTV_o(@Q^GNF$@/pJUTsHDtPbn'F_Pn$n^Q
|
|
||||||
]qC^<MQb3_rk$+pc9@tFn@a$++0N-D2^f9JnMg9FAd\+4>C=;tnMdC@ET:t6?g:cu0:-SSJ_p#scd"Fo
|
|
||||||
>oNP!ps%K/(`C%R(OE&P1\XdOPf$VoUD/'@?>:\Y;?K^@WQcf,El<3YCGJ@42<BX#C<E&/K0<&X414'B
|
|
||||||
--:Sjp`!K6._-7V%aS/'0?dJQ6<fqo@uu?(>o6bOESnk8GRp*!U/B+XQ(O*VK9IhS$`EafpjU*=h.V;V
|
|
||||||
I%E:$r!VY*)5L%$[i!qXX5qe/-1cgVCUX]BrLZ(,R(BQjre)Tf7>'WIRiYVbhf,SbhqgL!Np$5O:Rl`Q
|
|
||||||
4!dCb5_X>jl;1[@=?!02Gl[SX0(jlQJJLiSO=b3l[J%brP)ZM?>?0PE.%rM9URnuef+E>gpg"K\,.)/a
|
|
||||||
O*MaV]N:Ld428CA'd`g)Hm^\#^9fJ=(`'`Lp%3r%NCrZdpp`VD?#n_n3i(Kj>r(cSFq#-EW&F=pjBXP)
|
|
||||||
'\o?B>hc:9FDB5Y:4:geC*:t%VH-pOQSO,8mUOFuL<9YQ\ZbDdlchZ$bVoGjR6V^`+$I?69V@C2I+Yp5
|
|
||||||
H8jPD-i/rp&=GckcQ\(Ua'Xkb`4/*UWL5DXfhm\)/NW39bM8A$:gKYtR"_0bZg_=M%5r0RbOk#k1Qg;)
|
|
||||||
`j=2GAB+s7T@F4VG!WbaKFt6(4QtErrmJ'=C&BTtf5dr+GntkLh6Z&YL\]1>>OT514nrs%5!8O3[YMKS
|
|
||||||
<N^iGBYW&.+U'0'MoA9r/,&(CF^(VS4`@QL<il!>:u'V5?@4DLg#1pa)4(1B[b;VZW>:_/GfSZuoM.0c
|
|
||||||
Ojfpp._3%]cI[CjFk\7[?>KAC<BBCA07R.D97+X!j#]9e0m,tmqD6Ish9r5XdpK544YGe_kf'L>Nj!?G
|
|
||||||
"uQ#*)<ni$F(8&tf2ht7BSH0>^4uR1/H#6]*`X50(s3_N+!+f*rk#8&]/\5m=no2G^s'ZFVtOuqKeffZ
|
|
||||||
TpiVj+WF@+K?llmf?SET/GtaC>hqU0d)5I)-&&6?gSJ2bFtD%&k8e/8`Nu*Tm.uF%pVrb[DX]OWUUqh[
|
|
||||||
rW21cp\,1XDXcmYhjA0po<#VWi)\[llf(IDP)oMN%FL7#8)3$d,fhA*hS20ko0"%@(FDRV*[3\NGg>:-
|
|
||||||
OoQ#Taj^SAYI&8-^OFP-IaG9=#$uPo%DFY@s%%#_o-n3c@/7#2p(-KH-SGFlV=O*@gOF(>X<uEH/X=+'
|
|
||||||
.P@2C9R'&hCW\gHFbEI*[]$uXg9@OXZcQr\"_OF+:FqG3b_Nkmc/2a#!jd<_WWMdP&ZEIpm4<O1X>as.
|
|
||||||
TUP0UDrL/0].,L=^T&b;RoZ[hJ_>-;<<_O.DkdB?Teo)D<FE7B3p#_A/LiH,)QLR].]WXB$5=_$DQ+oT
|
|
||||||
o*NG6M/,/5=>nf"O_/t2TEoLX9-pk.*S]hlW>;d2D2V$.VJVde.,3%TWT'r2Kj)u9<R9'-X+^LnnND3e
|
|
||||||
H@6Mb;3+*g<FGfn@EWQa)fAN()-@W@bnAl4J_@,r/LiG-Nkt(A<ftq<J$'VqeB@5K\*]D`l'Let]p7VV
|
|
||||||
h<1)M"QYlk4Fa`XKBq>kfd-g/<-C?G='@*N&/Xr8=3VB-4/mFL.h6L+?%$Xe,V>:)[k1cXUK:VK]3/+5
|
|
||||||
>:P!]GK`t<$[[P!R"h&6.NNHa."e,=H_G#\lM!n61Y@"@l8iVHQ:d+5eRZX;-AR:8!KWr">*s!XmNDRs
|
|
||||||
<G#P?&+(5_.POXj7;(aOE7AH*6Hm)WCe?0Ra0]K>lKuhnh<\%rC3Hr'ZZ?A<prV61'&%\(@E9?:0OMu!
|
|
||||||
.PP5P?;#M7*^!S4H5j_'CIgGL&&(O+g.(I^)g(I!n\)8iKtOf,?(e[^;G"IH;V;:hW0pgYDd#FHfC&Rm
|
|
||||||
\B!\Np[i9QriG;c2/dR8DJlH?NBD/B%jY2nFb;<R<0G*qJbb0:2^j^:!#:m/<_0i:,sh4X_Y(,%.p.pY
|
|
||||||
d\.Z.;G!9TjjY%okj>Ll>?)D@#M1lYc/<dqB*4="bbD7II`U:*g=j2F4n,r@lB]Ri3C;HuLtttNkB$XD
|
|
||||||
q`@RA?[p!khmAQah`+aga!H4T"L%mH)lN=p7W>>*21eJiE!1PP._%B7qh<Mr7smP[kjeTrcF2:q)H#f6
|
|
||||||
]@L=f)V?83-b7C,^=BA[>(7!a.Cd3gft]2#cB1uiYP)^JUl2O@h5p;:k)!mP-(<]Q.($_Km<;uDlS$JS
|
|
||||||
COK<rV=bXJ.+J+::04.^0q-JccP\(:rmD:d':l5tf:qsmXhPWq7`[m)\1^`[fgQB+X`'IO?+X8<c_U6#
|
|
||||||
<e80BTh2Fl?GE</=3G@\D!oaJX&@,J^G45*le?h-fD8-U!4>"qC],PBYBrnoZp)7b>(=)SDdV?gl?2gX
|
|
||||||
[JoIX,?b?0$bAm4mh]9.p"/92R]2U\^>mq<^SUT>eoKC=95?kt6#UN:V)9B58+!DN_:-j/U^ORo.I:6q
|
|
||||||
m``5m?W]gtoncr>"&%s`_Hg='=%_'?PTspkb+.msoftf]Dq4j%E9&fjI;/q#I;01*rN'@N+/a*c`k*b>
|
|
||||||
IWo@=^P+.U9fk45(PTfmmRl#ea?$/j)fDQQ9!I`dPh`i&jk6Aqf4>oI>t\f\gseEU4)i/qS\I=7Zu=uo
|
|
||||||
^j?j-b4k`=A8.*X].<*-DPpH+KC3g1O4M8L><@=T)l^cf/)+,3<WpAka1Ek?IWoB#R:6E7Fa$:3M41<s
|
|
||||||
/[FAM&"U#2hi><!,rC^C*4n+BK#;N,@e:hom-`$M4ILd#FcmKQAO!DG%SQMJ?/iOpiVH/Mp6YME[o7#a
|
|
||||||
Nig'"Z+F`skhpYd81!+u+U*.8-1gjAh:@1b1UA$?pa>MPZ"J6$gf][O,<ibk"$W<*6YgCq1IU'8HW=:k
|
|
||||||
T&#Wmb'Q?d74m@B4,hdtKLF`nQ<(M`O@Ngpa*<>6&K+Zba+<D<EV#"QNE)&Pfm@^m?Sd#qRGBhLAp1LS
|
|
||||||
QSGE$A&]%/X4p76e%sd2BKLZI$+0!1*QBmBrHcenB.)d9@-H_[f+iUEqerurj3:f_.F%A5l@bp[QTF/3
|
|
||||||
cd(`Xiloaep8hj-XXg$6/5rFZkK8D`)+;3^:!L*1qh3g(^<=dq.c9@MGNBe*^8/en,2_M)H]KtZ;*:Ub
|
|
||||||
dNN76%<K,I-EMC5Z-ce1FP^QA;VGVqT^^NI24gm;gSFfH^#?*\jL;u&>qL<`9QHWn?*:8;Nq4Uh9]@SA
|
|
||||||
Bt1S=3JT$?A`=ePe;@2dcbR\I\_(QU]3O&S'_MgGMgUkTh^og6bH'\Ag+i%Lk;@O#Hh<\iJW@6h43`(N
|
|
||||||
"%BrIe:2d[okrV&!tHm_DpTjA/TO'lPh5#k."VHb>[tHAE8`'W>4^rN`j>5Z]>[1c"i6CY..Ge3Sn.0j
|
|
||||||
/TRZ"c<Gq8mg^f8JUC`J7`@FeZG,07[6tDq)E_RgoAjb,/]Z7,lOEe`3iFs/W)Vt:m!SNt[[GGX,7"WP
|
|
||||||
18mblXBTU3ee[+sHJ*PH:'R%^-<mdWBS2E5Rj>],``)f@o.,-*kBBOV5-%3-Yq(]?OCR[T4+E*%G'^@\
|
|
||||||
#d8KdZa0!*.Cn0-(6@->'#p(!Q\E%W]baYr=$].F.+.cZe,t0>o4s?b)*%%>*M%plGW`>2_C%I>D),Vf
|
|
||||||
iB"<N2d'cE6j5F0%_8nD7GZ8JXgeV4'aj].9J?:60mpCV5KLOpbt'd:ZjVd#W94[NB-,lY^ltZM<,a^c
|
|
||||||
^`-J-Fb4o[obfWa]6*WpV#JAJRNCUT>)tKTUG&9EQpCc#!:j!+"t.+3Bmr38X@r02&'Q#i3.6'P6-E7F
|
|
||||||
HBU>9`2s4]lZm)*Ffs_PZLO[c-"Z_'oC<s9I\5MbqIje<ICurO`6GC_#FtWm2eA>b*/UF'i-LHiCB)F\
|
|
||||||
NfNLmA"AiV]B*/&6BYjEjEbQIpc?m,q5MKGK^@A89l7"<rlYZP9q]XaN>e_!$7X:Y#t<snp?(]kMtn;^
|
|
||||||
V8N"m*9*oEfZ?X^I+UGhW-kOD5a;k8:-T+2*kn4.kbQbkU!?94g:>ria1AbnnWmZjUq5Anneni>Nd6(V
|
|
||||||
q%15sb(lOV'6OL@2.n50oo9aoV6<bX0'R.P]aX9k>)aPb<Hd0^Qa.du]nO/$X<2__7mpYgN`^E.$Wj30
|
|
||||||
j1S<`d(LakbBp&=L=0e9I->3mNpWWLG4(4ZhG4$1YBG4SH:"V%@FNn]Pn/^Sq65D\<;@*+B2h(5QW3bl
|
|
||||||
go&k\i74cRLm,;OYsPJ]@H"PmI#,*E.j'qOXF+,'TB5?8V57>jOADqJ+l7<gAu?eP5/_3/b(l8.U:+Ll
|
|
||||||
Z`8E*?EG(1EN6l*X3-4gkG%O@Hg9#f7--f'D,g>8CR%V4?=$VGF/@$79d_^iagIi:5:Rb@cn0uEXGU">
|
|
||||||
f]uO6Yq*'3WEAnL=)0rPN^'f^e1^hJ.j%gZDi;plFNd9R(\L^T#KsB;eCpj^;#*q6D`A2ABXXeEg`8]L
|
|
||||||
W-m7T3gBRAM>sp)L]=O8Tm)%4+92/skHDr+ci7(nci;1thZ%P2&S9:&Bm0rkqo!mK)=.=tl[Ss"n\>+s
|
|
||||||
If[Ru\1%~>
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
58816
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/Parent null
|
|
||||||
/Type /Pages
|
|
||||||
/MediaBox [0.0000 0.0000 731.00 403.00]
|
|
||||||
/Resources 8 0 R
|
|
||||||
/Kids [5 0 R]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
[/PDF /Text /ImageC]
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
/I true
|
|
||||||
/K false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
11 0 obj
|
|
||||||
<<
|
|
||||||
/Alpha1
|
|
||||||
<<
|
|
||||||
/ca 1.0000
|
|
||||||
/CA 1.0000
|
|
||||||
/BM /Normal
|
|
||||||
/AIS false
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/ProcSet 9 0 R
|
|
||||||
/ExtGState 11 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 12
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000315 00000 n
|
|
||||||
0000059559 00000 n
|
|
||||||
0000000445 00000 n
|
|
||||||
0000000521 00000 n
|
|
||||||
0000000609 00000 n
|
|
||||||
0000059535 00000 n
|
|
||||||
0000060013 00000 n
|
|
||||||
0000059729 00000 n
|
|
||||||
0000059768 00000 n
|
|
||||||
0000059870 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/Size 12
|
|
||||||
/Root 2 0 R
|
|
||||||
/Info 1 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
60086
|
|
||||||
%%EOF
|
|
@ -1,274 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0" xml:space="preserve"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="115.8239999999999" width="93.73760000000004" x="680.0096000000001" y="128.20672000000008"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.060546875" x="4.0" xml:space="preserve" y="7.633644259571071">spacepackets<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.5" nodeRatioY="-0.4340927246548981" offsetX="4.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="38.232" width="93.73760000000004" x="680.0096000000001" y="253.37632000000008"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="30.056640625" x="31.840479687499965" xml:space="preserve" y="10.131624999999985">cfdp<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="163.40159999999997" width="284.4319999999997" x="783.8771200000003" y="128.20672000000008"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.896484375" x="12.842478099131654" xml:space="preserve" y="10.580257051368562">sat-rs core<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.45484868756282143" nodeRatioY="-0.4352499788780002" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="75.46560000000002" width="82.73087999999973" x="985.5782400000003" y="128.20672000000008"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="27.91796875" x="5.868388937197551" xml:space="preserve" y="7.831944999999905">HAL<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.42906652344085205" nodeRatioY="-0.39621834319213123" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="57.03359999999975" x="998.4268800000001" y="165.02472000000006"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.763671875" x="7.634964062499762" xml:space="preserve" y="1.015625">TCP/IP<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n5">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="62.76800000000037" x="796.3430400000003" y="236.42112000000003"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="27.63671875" x="17.56564062500013" xml:space="preserve" y="1.015625">PUS<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n6">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="62.76800000000014" x="796.3430400000004" y="212.21056000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.62890625" x="9.069546875000015" xml:space="preserve" y="1.015625">Events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n7">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="80.30272000000002" x="969.0342399999995" y="260.6316800000001"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="40.708984375" x="19.796867812500068" xml:space="preserve" y="1.015625">Power<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n8">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="62.76800000000014" x="796.3430400000003" y="163.78943999999998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.947265625" x="9.910367187500128" xml:space="preserve" y="1.015625">Modes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n9">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="21.39839999999998" width="62.76800000000014" x="695.4944" y="164.06207999999998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.232421875" x="8.767789062500015" xml:space="preserve" y="1.7148249999999905">CCSDS<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n10">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="21.39839999999998" width="62.76800000000014" x="695.4943999999999" y="198.76255999999998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.1953125" x="13.786343750000128" xml:space="preserve" y="1.7148249999999905">ECSS<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n11">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="93.73760000000004" x="867.20384" y="163.78943999999998"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.62890625" x="20.05434687500008" xml:space="preserve" y="1.015625">Thermal<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n12">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="93.73760000000004" x="867.2038400000001" y="188.43504"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="89.494140625" x="2.1217296874999647" xml:space="preserve" y="1.015625">Housekeeping<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n13">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="50.0" width="104.03008000000023" x="1082.8518400000003" y="128.20672000000008"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.765625" x="22.802665047002165" xml:space="preserve" y="16.015624999999986">sat-rs MIB<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.025669859592552413" nodeRatioY="2.220446049250313E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n14">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="93.73760000000016" x="867.20384" y="212.21056000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.22265625" x="10.257471875000078" xml:space="preserve" y="1.015625">Parameters<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n15">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="80.30272000000025" x="969.0342399999994" y="236.42112000000003"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.25390625" x="25.52440687500018" xml:space="preserve" y="1.015625">Pool<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n16">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="93.73760000000004" x="867.2038400000001" y="260.6316800000001"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.392578125" x="28.172510937499965" xml:space="preserve" y="1.015625">TMTC<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n17">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="62.76800000000037" x="796.3430400000002" y="260.6316800000001"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.01953125" x="15.374234375000242" xml:space="preserve" y="1.015625">FDIR<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n18">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="50.0" width="104.03008000000023" x="1082.8518400000003" y="184.90752000000006"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="71.505859375" x="18.932547859502165" xml:space="preserve" y="16.015625">sat-rs Book<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.025669859592552413" nodeRatioY="2.220446049250313E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n19">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="80.30272000000002" x="969.0342399999997" y="212.21056000000002"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="70.22265625" x="5.040031875000068" xml:space="preserve" y="1.015625">Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n20">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="93.73760000000016" x="867.20384" y="236.42112000000003"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="48.044921875" x="22.84633906250008" xml:space="preserve" y="1.015625">Actions<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n21">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="20.0" width="62.76800000000014" x="796.3430400000004" y="188.0"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="43.404296875" x="9.681851562500015" xml:space="preserve" y="1.015625">Health<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n22">
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="50.0" width="104.03008000000023" x="1082.8518400000003" y="241.60832000000005"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="93.701171875" x="7.834891609502165" xml:space="preserve" y="16.015625">sat-rs Example<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.025669859592552413" nodeRatioY="2.220446049250313E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,607 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%âãÏÓ
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/Title ()
|
|
||||||
/Author ()
|
|
||||||
/Subject ()
|
|
||||||
/Keywords ()
|
|
||||||
/Creator (yExport 1.5)
|
|
||||||
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
|
|
||||||
/CreationDate (D:20240129115539+01'00')
|
|
||||||
/ModDate (D:20240129115539+01'00')
|
|
||||||
/Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/Type /Catalog
|
|
||||||
/Pages 3 0 R
|
|
||||||
/ViewerPreferences 4 0 R
|
|
||||||
/OpenAction [5 0 R /Fit]
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/FitWindow true
|
|
||||||
/CenterWindow false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Parent 3 0 R
|
|
||||||
/Type /Page
|
|
||||||
/Contents 6 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Length 7 0 R
|
|
||||||
/Filter [/ASCII85Decode /FlateDecode]
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GauF[bH</%%P0O,YLl(tZJLY97g9BH@!gh:YlW`<fOajJs+$:F5t\]Qi,fnlDGR9B.l3M[OVCAa<;r/S
|
|
||||||
s1ZqITDp7Q@,C;HT<I_@J,N`(]c[1N=0M5I_*<tQb0OG`:]1.`YJ:(g^]';W49+\tp[`n6l[R)bs5j:V
|
|
||||||
pn[ob^]2Kls82iohuAuAh;A8"+.jVS:]D/Zl]X6!A,i@L-gaHos1]u[s/G>al/DYSN;a2iOfS98U1f!=
|
|
||||||
RtII8p?rM\oeCm\q;md^YJ<A_CRW1frY*?Ds8)&2s0">ss1^_p*lS)&qPUOsiH_9m#Q9^5NV,E+^N*Jl
|
|
||||||
8q&0LBDj^"iuc=<4Ub5JJpdP2a7K?-l[S`2fFOVocO[[<D<G96I-"CX%@b,Jp"*mN[0J)=[_BaV^LdPI
|
|
||||||
"/ntfZG4-R17UCFrYfSPRMdMLT\:DLc`ZqVQ.*<n258NM17s!oB[=t2CB8X+G5dp>kkRK:B!:],iKhj%
|
|
||||||
Ldo7**tfH;*ap-_!Id4NBNTbrOjEl.&l@_sqc=lT"]Vq8=5^YUM)BjU:Dh@KmRI3uh!K<?rLBRdJ0#%&
|
|
||||||
R\J!AF.p!Es.><Hr6"FmUPRcD!cLt]=oYs._FCke,E?oZ>"^*_Uf%Fi9f-[Ehd'ni7%huWD>pDG-p4V-
|
|
||||||
5Bo*ri)T;blTk<pib6cSl2OHO54h@sd/WKhoPaW6DK<\e-M]@]=.Be*AKM7_4+#WCNVHf"_ak^\*PZ1i
|
|
||||||
.\*J%S\L"c5p&SVXHJ2pgN%:[-fEKBQef^rl-Q9]A/i\IH&4@`jL)?\EQD!5dC[cC@XM3S#2+6U;1\6!
|
|
||||||
UjSJ8F1<3$i)V:J'rVQ3HQ^a`P\@Ie-R*RmA@^>WUs%83ao.''CA[>!?bRV!1nKt,Zh)Dln%_(*e!X`4
|
|
||||||
\bq$GHf-T-rtF":`ukh-p)@S?E(7GK^ej4\GKq@$1A7C?d[6Y@^F?H9-!m^;oDO'YjE+7.T_[qWEC2U<
|
|
||||||
+'Z*h-7XSXc)3]T/=t=[e'q*>GA4Ma<u7[UHUO:qrjp&FB[j&EDA1?h__SXmrDug0Z%o(3qGl/!HbMIl
|
|
||||||
bLD89nGD/,,FOdic3:CF8i8$5&JS+IH?2n-E#!VZg8#+gY;U(][Q)3pc'7Peh=do<o;h/_H6a1=(EVX6
|
|
||||||
]fDborO^Ni?GF(m:$N=uOS@@LP`J;9dp)@>kRcVdQ1V,OTR(_/f-ig]V)F"@p^NaOj8#'ESS/Yt78:+[
|
|
||||||
[qEkMeZ_7Wdd$G:EZrYDi+Y!.2\U?2`cmWRCr*H=cqn(6/#8ldII,P"FA^'IfVc.1KRtsI^%_qDcSk(^
|
|
||||||
Brf*ENtS6X*p''_I0_r/=1mei"3M)=T%<;d#=H^ac5/\7FPLQ-/5rD/CN__S380K=qt7L)`XEmP2#;`g
|
|
||||||
P"s5!3N\]3Ldg&p-L.+d`-7mY$k:M)I%`CiXO`s*a73b'S<;W>BurAf3)t"HKZl!m@.0+.8d#+KGh+f+
|
|
||||||
'pRCnV%Zgt^.**s$`1Qar6j@Qjb?B0NMP%'VtEp`_t>Qo-u1YT4L,B"_IslmHNQmi/^p=deDCp?Fir4@
|
|
||||||
`Ukc2UG[h>?+>Vhn'g30M#J2o>$)Rpl`k>BXB00n;[pL)_9It?O//tK\rs09S19EQHjlR-Z,gEdS]9?C
|
|
||||||
X(3@iEb!(,4fE?1N/o'F0?\e+9`()sI8D"Oo*:Q(EhAC;dG((O9o4L'2KW_]_m6Mi6EBJASIUgK$iFZb
|
|
||||||
[MEV3>g@PkF3o*r0NinUUBO88IhsOKUP[PjGrP-MFBHORhgYfpTX09&'E*(M:?2-<6gl<-Yc"n+%anQ>
|
|
||||||
[-2p@f]\$2"6g9LQFc!?7l-7`q9gMYUD>.kR;$lbqV]R=)nmqkmCNe;r?_T`LAGdEj=M=4LB,O$d!
|
|
||||||
XA@2)cf"%=DoXk%bM.m)eO!<,%PC=J-!6s/_.&/CS%lf0-(2"Ar<6JUn70,`%!;7%%NZV)rH@@KVqLAl
|
|
||||||
C[I_%CfsPgY>DYSja$BXU&EuL7Cua*&$<c;&!,mj/kPgtL3n/1L:$51dr52goih/20C.'thVH+#j5!J;
|
|
||||||
.t<=ZOg=g?`AIsMWo<>Js&\\+a1D+]*VAifrR%Rn_@*\_)s?;HU]"pPV[d[&UJCh5E.bi3Dh\^;hlWT_
|
|
||||||
^G.[:I*ic,0Z1?-rlu<M/Zt+Kh-n3FTHtO3BNSLI#)>$V\).$M*P+tu^roO#U"`(Ff$T+"[9)?@-2nP=
|
|
||||||
kNUNY-/1]TUEqXHLi1kOP;98k@Dn*`iBXE`U("/<>Ep9UIh,^f*8NL+=8u^tP\R("GZTp]S!o%3gF%/_
|
|
||||||
.c13DojDKYduY*oS>g6#XE@h*Q!qpA2<sr*+U=)ST6"6_O(sq%9C=4W4Zr;\KB;25B,,,5GZTH$b;gX1
|
|
||||||
U#(HH(#P:#'u.=.Sa$f``;AjUjJ,SQ7S:kMl/6`Y;i+PM;\7g9HbQH?'L@k*'c=ZDokK2Q@n8+-f3@Q^
|
|
||||||
NRdggj(2AYmVV%,&q2T>)n!34XDSR4]3u/7IJU#d#mJ;%iQK0/n8mU$^5i)oEkO'`GFFlVrH1iqUJ^@H
|
|
||||||
<kKkB`-7"&HMjckW#jde"iY9IfF5Ho#9U-YekDQ`2Y;VWXVQ4a26g6TXr]E1_iu+f^2m!:b2+CT9O$(A
|
|
||||||
IWFoB_1f6Mn*31d2MV4eP^2b3VcCGS=0`(=LsOb\Q0U0<X4jNLl!6;in*4:aPV5'`gXJt+gq>5YmS3Kn
|
|
||||||
5-]H9:N*U`RT4*N*?cdgNc[ii/R0MK@h6/Ho:aKh'og_\%&u2O6h@H%,d33a<S;bdhGoF*HG0s(/'%:b
|
|
||||||
m*0r<6d\6pY&&QD9oi@Y7J*aShEu4nGNEpdn*4<7Q3Wnu@<:'gs#m<%D'A?eh.2Wqr6d^YqL`h04R"]S
|
|
||||||
C!Bo[klhn5'lV4>^&5$Y<tE`*DVior&p;CiB,(NEG?'jIloJdpj,i>==d.D5e7BjO[-eq"K$AUDSfDN+
|
|
||||||
[cW3#OfalZbpld:8RJ5g8+4S\<><.B(cpW$/TEA;GNIJjX-UkeoWB72r<qZGQ@60=GL_lFFVnC+eAK0]
|
|
||||||
<73oSZ9U5kM\8O^r1KCbIrL_"D'MqpOPZ6If?0-<?29u^Y5Go74oJIH?.BU6YG]-Q$i=V3F[\\_kht<_
|
|
||||||
;i*Qi.7ipMl0I2kA,!-pI!^0@p*JD/U,T2[H<2\D*%$m+XbABPd*W8tGImFf18]-]lClnTf2s,bAZm$^
|
|
||||||
LW@rh]5eVX*jP=T04e9;l$V*rX1I'j1[jVn;*I+uP']m`GT4o=>NIT-X>h5*P+5/:Y+1+9Y$V$#Np#-o
|
|
||||||
r;<<pN8kch-N4<f1'/KHQHLj3_mWigKnlk<Zo"EKH*7Z/BIt4]T._2OeNNXCpAR3"k_$6^s6sFmK9+F7
|
|
||||||
o*7JYiIg:_%snUY)LV,6If.)i3@j1e#Hc_gmu:LCXj)(6bX1Na<,qs-_.SPIcoV/7Q]:$@G7U0urh2K'
|
|
||||||
r57AY-7XN.p(+8Aa$6pP*riGp!p;?/N:j9L7[JQ$_["`H5O;PgkT<!e*rjTLqhL3r+534)5O>\"grAdp
|
|
||||||
2_Yd#l-VKi@@%/b1=>3-WjQpHUOUqB*^\6CpkR-<oFG=,".AAD0gtaHHhS,>IFl+"l^31S,N_Q,nDJcn
|
|
||||||
_qoc:g[r(:i:[\.oj.8bhWr3Hlk-:nIqY',raD.)8H7$sgASojs!]2`Bp1^Tqd>cEOaYjINN:J#k</d$
|
|
||||||
LFN31:\]<l'JiBI?2LqgZV:p3B)3FohU\Hpk=EnC[(O$n^cscT2E9"rgM^rLL(_%?ft8ntolTcYU;oS.
|
|
||||||
YV2&"Rc\dHnaY<')ZSm`nqiodrqn,JrI7#n"#Dd@<[]1;YVIur(`j8J46Ai7-6@6POJ7!,4@XNfd4C=`
|
|
||||||
ott>OHJ*nFGuOjIc+V*ebi>X\E@W9OY\ZU0Z%dWgA7Pg@7`L/,DR;?Z#1:";1@/'W+^PP)B_4M%H_77M
|
|
||||||
F48bkrA%no@-h-LbuT/;TI^V#GP\rDCkd+.TaV>YWb"!SBKBE#S!?35A8pO0'Rus6*jHs"8R^5]*M]%3
|
|
||||||
jl)tm<o:%(U_:!mB[Me(qmFGgUX6VYUf,.47Dj.kHp>F;\SY%YQ$Sb^NR*?_7S\HeDr*kXPn3FkW1\W$
|
|
||||||
XkdP<&"p%3fhN?cCB[[9bbNK'l+=jn#J5Hh72`jc:qAD;^u(Zm<q*/LhH;!,]4YH?hV;U8PU#<j0d@q,
|
|
||||||
6D&a@:Ic9?L'MuUD7%HP1Tbg3UI^Ck)rZ;*bt@e27L&'0m8/kfE?-iaDq?HtqN\78O_YCO[qE_DCDREp
|
|
||||||
K5VX0>"^IHn^9)JaLM*Xq-hDulNlcD2Oa-W7b_%":,^fRp?2Hs2O_T@+3V/f"N#X1S8DMiLd+MM0]K%c
|
|
||||||
Lh\=m!]r?th&\Fk`$QcJjqeRRqN";U.6]ggl`e[p]7]Y,$Ih4ZrD:u4AmG4qTJGcb3i]p/+l(^W]a;];
|
|
||||||
mrjc!lW;2ZhBR'=pD8Wq_e@Q"O`j4`7>GdGL,%[UN2XB;f\'WZf4,SVLLJT"\CX)dl:LDj(n//@d5nPr
|
|
||||||
br8AV1U?S)37cbY/$oJ/Ke`#,m]f+IJ5-ci$9a#rXWA2K(=mAehTQa9MXMlp*uQ4a_hCs4UY=AU?%J^_
|
|
||||||
'#!%6?1pRWETiL3Dbj>)gkcUHiASIHS'bPENTCbp7ppfgN8uP0gMQ(kr%.`^L7X+T:hOl-%A:P)HEl+Z
|
|
||||||
@AECG(<9h]3f%#m4H\Xs_ru(8[s2!A)sI)V*qE#t2APs6NouTToRDYhHh%"A5Q)Vq`X<FKT]\+c/H/P?
|
|
||||||
+q8N]$<3SDGVC??/@_E6kdqj`J'8.-SCi=F2ILWofUkF)2Z8JBjSo-+``C@q@Z$Z$Rhnp[[D7=[s.D[r
|
|
||||||
duMR+!sUM]Yh.9`N"q"GNf96;`_EfMo1&5G"I(<Eg7d_HKe6l7<m"N*aCWS8@16+RAoaQY$_>I#I$]fb
|
|
||||||
0CP%!8Gru8dqDMB(B9XeLZCN^U/GG27DZo5qE82MYsHWf&c=i&_,`";S;lo.liFLe.pJW9B[8G>?Xi2.
|
|
||||||
R;F.d&ZgFb[je"q'9%9dG>mZ^NudC%4`]TtGKfiD^R8LPq0.70nmCM4jZVV7k5FsQcUXa2NDshR%e3h%
|
|
||||||
8\Z_^W^f!r\YITG+j&cZ%u.40\3G**-['4uk<ErMGjI6Hbr%L*e(m-d_^cImE)*Fo:iTXW^<toU3mEYA
|
|
||||||
<Wk[YH`NseH+Rt&P4Kaul;9#N`=*)PUAR_XRt'%$d=5/Y>4tKbXT!s5CGanJO]"8#*F*eJQ)IiUK%)ib
|
|
||||||
q;MgcM[+_RX;L%U#KOBKc8Q8;T9&q]OjL>?%+X8rcAqPr@N<+`o-PZ3f6>uoKAL86815l=/;\,NN;Ig)
|
|
||||||
(B%;`WSSjlZ1lj]gde-jl)f2O^1H*!UgIC\).,$7s3Fa@>ST8]IO6<f>NsEN&)jb(p&$3:LM&qSC7^jD
|
|
||||||
/J9$?b5CG=Q'(rP2M`OP<=^OEB7:?=@&61#H'kBHC5f%N%&A=)SFN[i3bm2?l`fJm=XGAcXTQlr`cpA)
|
|
||||||
(m=(Vr)fW6I>83^N`PQ3FeVIRFDO+@%"VF+#=Yum!8<AU@:c_CQ\b!k0&!Y1Ue>>OTFW0Y]@_W/HJ:Oa
|
|
||||||
6^889k7pKM3F)^4MH5W\>/6Bp*mBq*7r1.@hd[I!f_iOF6qQ^ZH*Kc5GJ.1*\tih65UCl'4'aD;g0n[>
|
|
||||||
c(31@1c@_D0`_^cj(QCBYXLaWrtA`t?aYO54rZZ6<ce+%pbB:RLId4YA?fA`d6F1&7d,2BhHKc@k&b>(
|
|
||||||
")"C-L\XF9DY:^)S\CVg%Db[6mab&HkAT?@crq<r.HKE\XH;]=-4`52pq^'&jRP<:(eZWQBhnc$K&M0!
|
|
||||||
VB=;_N:J)\`Z-cB@E1Bn`*Z*i(X+L%N3Uo)R8]D#qnE5QXNDV;V!qngXYe26+sol<<9t)M=".]OG^\lo
|
|
||||||
dsW``cPIZ<)CJ8a%HET8faj9;;MR,VV86JIgWYbRCi.ru>VMZ]+ed*ml,eED*$P][Ln/bB*/8&i]Z.Xg
|
|
||||||
_hea_No#ZD*k\t4XS7s6DiAgsgE'O/brV^\ODB]*iUskJA9rW+p18:coK+bgoI%BB#F=8$hCp%Q(A=#'
|
|
||||||
*^okU.N7ECr^[>PBUAF#NoAA;Yk=cVdF\p4C8_\dc3g=k7QocH7M9Q=g5T"XZ]_;nd&NZ%agkKc&lC?%
|
|
||||||
L:$\#(GR6s3J4(e\gS$cF(=S2?T<+%[k__Zho[a?:=s/f@HHeLV7"UKVHqMMo-X@5f'2uVH&s8[Iu!(3
|
|
||||||
N\b3\0S]e#<edp*8IRbOQ,>F4c\)(hgIn25S2dk4WKPf+5)jt9HSI9-WF&ot6RO9<R&rAok1$`q)X>J1
|
|
||||||
_E;Jm372V_c6YndHjlQRYoI=W,]IquY>pO;0r.&(TT'W2Ipsru#Fd>j.$>N0T3Q$>X.?g%gh`)uEoFHH
|
|
||||||
F^GLuShr9mU[RCOn^!1@JapB,j6FR;$J.LH.l/sd4!If#m2BbF6Yk]fb\@^Kq"SZPNF+&T5&nrQqC/C&
|
|
||||||
#5s$8k(C6X14"qIs"d#2Go+7\#@gQik</d$p&+F#a-2a0<.6R3g?,=Bg/jN>'!_H<\sFJ(k!H,o_3cKN
|
|
||||||
p1A;Tcg"Jj=rVPJnZ.D/'/q(WV>:\2Bd_):UjZnpa3h1E(I$U>27KEto)-El@t4Ll%n=_O:J-tU,h=d=
|
|
||||||
;/@AWYWZ\ob!n0Kgp8@WQU[I+@FAnU]61"fP4Xs@__$W+K:!7E%]#W(1A1"%5Kp'=0"N^pfR8S]f:s!o
|
|
||||||
EkQ3tW$s\AqP,Cp[DP[k%Q;R7_TJQY/_a(NTlna`TeB9/9*Tg$ZDcOB99ePpN`h7"gm:"QFKiHXdt0uQ
|
|
||||||
Vc;kQ0V[[C6s6<@I>OkO3Zg[qik[/;h[d`l=i*XplumpT&iKQuZk@,6ZXjfPRl6=A"ekE3%\EUd/E'6(
|
|
||||||
jK`++KJHNg93=qrXROfud]`?#,#)/)Ek`i]L:LU(KZ0M!-8AJtq\$n7],tB(Y^?a>h8>R-4RFe/6F.9<
|
|
||||||
K]k(+krki%<jpa*[,<L%o2orhXJSZTPb-^VLS,Q6M435KkkR^GY-<;1ROHMS,q=>En0L2pP.j?3kn!V'
|
|
||||||
Kf3N;&fOL>&g])@o-Hd@q@`,T\\jafX:3b"B9da)?77`qX#7N\9?N"9Rp&,A99dC/47jh(k.:<%hf_A\
|
|
||||||
h5Sth4)5U%9pB5`-!VtIQ`0JLkY&S\$g`VFdsB[Bs*aBu%Gg*a,A:f4cV##rBd7A_()DgBS>KO=lBDP(
|
|
||||||
Yj8BWRsW(iodi9pgK%UMO!/;F0!7/VV]j`ZWuV<FQ-[T?Yl<q4%R\FSgQQ[iJ$ibse9b_2]V<//2E$T8
|
|
||||||
><1,lTSq9m,6aHVOEG*"ZmaRH\CBcdFKY)*GL*Rb)tV+;[Y-<qcm]Kg'N-_DFPQPDp$h_f/\O#:Q']J@
|
|
||||||
MBt5<M/Uo^ZAq+':'Wh&ZmOKM1ER>'St;\erQ.!8k$!/ak.#/DF98I`cKeV'>IqIYdC\;<HNqNK%Ujpt
|
|
||||||
i9JIqg#QhQoO%MjNZW_q+Htj+'j"Tk/0X&lVXs.AQ>MQOm^A^rk%>=X363Q/GXM&oVK(UOmi$oUh+gV.
|
|
||||||
(3<t;!p;H7pMf,N30(Dp:\-N-6?7Jj8drILXc#BiL@sIQqC@NLF+:N)S&<!fkV1hJ"-qL`Vr1;,(WAJr
|
|
||||||
Z&>S40t5@%TG+GRnVjIAW%hap-lP)VM@g,4=IXWCA(gA?*\&e/=d&jYaCRjUGM-^$Wn#gNc,H0rP<%N3
|
|
||||||
m+?fqH6!mq.*aTI^\'XclYlVj,QC5TLj\)40&;;72gn!^hWZ,nB4+l6ME3sHkfpHgNJemc,.m1:V;@RA
|
|
||||||
9$b<D11_mETK4(GZSfB$CK94+4bKo=+SQ:,/!Z\lO1[!<9TQ8+H3V7F$!=uSs,"W,nFo/PHNoe2Wu=>5
|
|
||||||
rCfWA)>*2X3G#'<?;Jn?2ie@]U-j.PC>>qlh+2i_FR=N@a?5+3]d<pFTY$MaBCM?SGQoC>^*b[o0"bT>
|
|
||||||
oGKZYJSE`)SarF?]mu^!L`(3NZpYUZlU[CdrR/1,QQ^j&7Ang"P/kmc*?5XpVaWVQ51/4,q6i]scSItZ
|
|
||||||
cS:a2+5faApu*9A8[5\FhiZeUoK-4LF%;;9P*31qXth0DI9l05;*rfTQD&j\Z*89GTVc;=TQ`U:4SqE;
|
|
||||||
em+:-dM<kj,L0/(T_X`h>3-2"VUhMiI*]O?RYa"?LOY^HEq/%nO;a+SG>ljBCq&=6"D6Qh"!(f,ec#u)
|
|
||||||
bm/"eNAT43'8p1#^+f6#n<rrtX,T<(Z\%nTj_3oOjn6:5\frW/abeJn/B[HbZ^gk;/PuEQ*3HOIe%%"*
|
|
||||||
s780rJpS[4ic"PoD1fnl-U%Kds5BR-hE!l_Gd_("jW>i3,mA1FHKA\l)?0p,q$1Qum2pT%s,H2+0p7'j
|
|
||||||
htt?<55P/5om?_E00XILr$=/FqSE,s1hu&(5*U?OlZmp;Bfe.sn9g;]U#gNY@=O7+@3kDuB[D3f(Od.H
|
|
||||||
fCS+aHe7_m7le>2A472=4X+53L!1TfiqN\*mh<_pr^RISN3`pD'@rXCV0'aBJX<T*s,$^Ms3.10jtjGk
|
|
||||||
`r>09a$0gl^r==i#JS6Irsb"Oom<>+RLMnI_\8FX-<uq_qVYuu8%U3*6gTjcm&=NbqjH^%!OMO/Lnp2M
|
|
||||||
*[ZO%L70?6ergM=Wtd,BQT>`U!6pK@+2AZkd/6[W.Q_D#rs@K>](haG-K3jQN)[j$6FTCEGgO>)/'T*0
|
|
||||||
n^adB>Mu%^3^X``RS]8E6,YC-^6:HF@"a(`H6Y^6^;R;er]A9J#A.aSLYaSr@]2=#^!g]?j9$Ht1b'76
|
|
||||||
^[g'/d;`;CPX33-TgNa--<U&4aDHq*U3)@X:$rtd.sdGa<U0NW5o5R$>I3CD@j<"WIofV/d<2?0Z?>%%
|
|
||||||
rCr:o/gKA#+48`2!V4INd':MSC^$_?^_Fk:^%#p(Zts_sT/TBeT<4cO/_'bH95aQL1JcE<Y@9FGHGnf?
|
|
||||||
hVZQTkkFitj%ZnFf$PG1FnBg2&A;EZ"W1EuIQ>q(%=k&",fqm\b>F3PYQ^<jbm&7>^Y=&-07K2JVS#DM
|
|
||||||
-0"l'k#[L<qhBYh$Ym>HpsOJriJjLd*Nm+*;GeT^-'N9*r.3W$4MEd/-g-EZXkAE':O<!R?XIWP5K0P;
|
|
||||||
_3]=Gie=XT3hP+dW5GNVipZ#.Z[!%Qg:<<_Vh$[__W"M-.6c:+K2;0@iXYWPI&d/MTAJTA%CaM<BrEtB
|
|
||||||
LAJLV<;dH%>2\T+;mY;`Soj3Cn>XbPTk!Skh@uUI]>:EA<Um&7,X,4XK24AAi7""KI&ZXbidYLLb`+;Q
|
|
||||||
WCSH4rKhmKiLdnkfUr2^rqt[7gLV?O.h/IG^CMCJ`7dE>B#SE'"^BQp9:>B6e[u6hY8S>7-\0$0'M0iX
|
|
||||||
0!&*\Gp<R%Wb6mFWYTNI_EiRYnr.Yf.S>:\O"c1LDt2V?H[NPKCjE&jHTo3j#_BC)g`/i!kRA=2M[aCX
|
|
||||||
ZZn"oJ-t1%nm.n*TlB1DZJOs'OjeLZALN"ME(<>[K$+r."hI//r"GHHp.pYA'Dm%SL#(Lr08^I8<]Lj+
|
|
||||||
`kKZNZ%$GcGG(q+EGO;k_2)CN!XtOUJ<$>6Y_U\s,OXMu-In"@Uj*QiFF=rE=7N=1)9(GWU"DjJp@6YN
|
|
||||||
5(;M5<?Uf%IGr"K(%0?np1ek;)QDQ&gGg)_HH#CdOVZ7JUD:'(GdDmI\)Fsq<M(H.aO0'?g\:Vck8QK?
|
|
||||||
`g;Y,44Zu3gInbL$`:tT-Xl1aN/JAD,XPsVT)hs#%:e9,T`uXo4mG$(6=eCtS(V!bB,aA;%5gatiZ:]:
|
|
||||||
+6&ORWY#*F+E!jL*U89:<jbdH2#j`?[kji'mn0l@>B^OUX,KEQcEkGDRdM$o3-]5kG?bJt*V[QGUPqt8
|
|
||||||
^C4l"[EpBEdS?2i.!Ig)WCRo/I00pBCM-Lji]1,b`h9tFd!Hb(W<dMbNiIn^7Vf=Z!_T7]"r^W$SE.[b
|
|
||||||
$5`]!m?^ja8fQ3M'f"#Is5;^"m_g.qap]!O`De+J928'.l`ontFj;aR>5\tgYD<KS.h3'm?)GA>Dn7@K
|
|
||||||
Z=<jCnDs+nR'#Kf'55H^3A__Q2H6VLp$BKI"NBNiPQTOL-3!%scfG"0JNMtlELfF,4QrMFfLg#3=]NIT
|
|
||||||
C8Zq+<F;gU<EOD.6I]n5;1$?tPdJ<t=.hF/rU_C+f`I1g<WsBTrL_il7;oU)>`BJ2d9\*M]t&$,BCiAq
|
|
||||||
@q>#qhYQ>P:CtSka[J9lZ=&KL78noXK)*4]r84%f=HN&8[0N<CA;'a@i$Fm)KDu%?m($jG4R`S.VKB$)
|
|
||||||
5B5bi+fk$,XPF0NmjIi.rX2kU7W"H\r/IdAdr<61nVnb#F;ukL>?R]6T_ct,49>oR(Gfgt?hjU`=pR76
|
|
||||||
AHseqP<)1$C$(;@I_2k!h9reSj4&T&PNr;%V/X92l=FVNQmN&R+p6LZ+R>_7^Y/*iVj/6;Y:q&rnQFtk
|
|
||||||
;1"YN#V#[h@8<+cW6"fGXRl74RMbi]'5)Ru1W(aOBEQ@W6]!taN?2jnHXk(LE5^Njj#q<L8]/DcK#mU0
|
|
||||||
h_9A['=@uU86CqK+aPD5[D"UinNa`9Nb+fTl/Ue#;haO(hR#BaHCq0X$c6[G*8;C0'rbf!V.#L+1[s5g
|
|
||||||
1pg8bAG/;/P%hbhr.Fq\b7^Q!2#&*8bHg:iBrl51k5#caV"3&tQW<5RJDu:]kak'goGtB?\+52h8^*hq
|
|
||||||
<iLBr4)4:e1[)9(9Wo5]GI.9`[!2OaF(+)7It<hFG,_Ldq;dN4M=/6K.o4&cYEG;t`%trCF(/tAiM\))
|
|
||||||
i$8Z"omo["c8_ru3st]kCS>9N[o5%n-IYH*=@J4Mb)M-grW1&aYke&!%]uMh5B08rhQp=$c`lX7.Xsgo
|
|
||||||
(Ks\qTO^8[;0faCXq4Xf&mSU;$30L6jXba/b%RFaZpu&!=P]PMePRSGAh_\O\E=Yj>[a.lVLEjaqd:LS
|
|
||||||
!u_-S-1Nh#5Q:t+>%nJd/7\,cqHs=q:F\SOPFB^qJ']NWb[kP2dJ)l1nC91a3Ia5)'))nCgY+;E0!o0p
|
|
||||||
HaV=(A="#Pbsq]b%NX]1BfFU_N.7R._'_Dd_rq'8PIkAoq1)-oFV]A2Pq-@SG^_bRE%p/OR31FCah4<@
|
|
||||||
r`!bQ.\8qd2KD8jkSZau?<W+1L8)(q2.:r`e(X$]rN267baWC5g'jIu([@UP,)g?+l<;"Z%4pNcdgkmf
|
|
||||||
(UG"f%LO,SgUS-r08nPcBP`ql%;d5VY_GkFZpEQ#)/<i#XnV0.ZZBuO8]^ibX-&8*(D:#,40jR.Jqt/&
|
|
||||||
oU`FJ\0a#*`jq*i?+t?bB[^*cI<<l*[*"PdKAU5[L/)]8(MEEVbnI&G2ap)BmI]E&(01P?;Joq<Cq0H&
|
|
||||||
oVWsZ&$`^@s7]^C<0_.a/lUc3X^>4+(J-f'UXCbkY+(Ne)]4uSU1j/B19BA5Got&``0S)&(E'QZgcW#b
|
|
||||||
okR`\.O1B?p;s5U3cq@Fc7A72dFjU^BYtS2-u@tfFJ5eDX2Sh]q5(&*K"iN*jP&.'4pBX`;s&+6I;[T-
|
|
||||||
25-`-bgku/"u?B<f8J<M6Wr0*3FoPmqXMueJ*.((JQjJD83CURPPMYIIf_93J,U,Yo7*)aJQf]e)[*Vp
|
|
||||||
3hD17!jWI$NTo11)]Gg7n//Ba2JWu!rkPtsLPR%8)euIGnX*+'(283Ua7P?MOf9,R7H(iH^\h.<ZJ)r]
|
|
||||||
*.djNkkek;7ZG*$33Wfnn\8Re6WQ@a4rd)-clkcG#T60[F7&Lh9YQ>sH@]OY_H<!"c&%?.r4>m))#r+m
|
|
||||||
9?qlp]B(h<89@<lr,Zge/DJ5BM1*&:@V,uuq!?*ROlor/Ok2A9DbGI5`2YrRjfmdV*/BL\B7^S$hLmZE
|
|
||||||
i.8:#X>^a\nF+ZF'/0?c'?c[8kS]"!19&#Vhon-\=6lW1Y;^;'q#50MmhG38>HSPLG4L@:hnVq/ni*!3
|
|
||||||
VXIi5^oms-d%d8l7GmPm`KM]V%ea7=XWX:]\%1C5QPI&1!NE@q4m7iT(c=qac,kUaqX-d@Nj!FmNBN_L
|
|
||||||
].]'1R%8B-VI/$6ZruhYI+NCLbS'!?)R;5N4UMlC,)jqES$#!Nh2;d<eY!P$'GS4DqgC5nln>2[-<,#e
|
|
||||||
g_a&'@-3@NB^U%*)Z%>4rHngQ>j^q=rVGiC6@<^cE!"(Q(mk**mrge`qSLj/[*N*.Z*$i9guilDe\h1f
|
|
||||||
AgOaS4Hme)PE\'pkrZF2PbJog3[&Ee+6c_,aZ?QMb$4AthF+GrZB_PRIZZRd125AGAo_GN"7oEJ;lj]j
|
|
||||||
5O3M`re`?db,X.E5g!"ScCp?r]PlYerpL\>\QYn(`F+FY`hpD;U![:53+G]q@?6(`<\6#r<rC4YoQbl:
|
|
||||||
QL68a$,!r6F")@g0%N%qNNo>/>*A[+F(HUQ2[p!"VE9PY*)h.QO)efpHG#Z\)7\8h9.U1]N%)h_\gTb(
|
|
||||||
Xb8m!UCUi;HTK.Ce[dFUIrh2&?^06fmh$'X*.^,k7sjo91,>[fV$5"@YHouYq!FT.3.dqGYc3)6YHJfG
|
|
||||||
ReQ_'-fq`/?c.@.Lqb^0qpO!;KO8kM%ZOb!T@`ASY$2:dn_6:ud5l[S&%l$g*IT+"5d&YDV<k)IS.p6-
|
|
||||||
Ho=c\\6iK"%hFOuq:5=2jN0bjrbC.o0dA42JA7i6[!VfO@?*_s<ET^Po-TJ<KtN*;q+pA's4r^PAhN:*
|
|
||||||
o8@`DljQ)J3NYJ,.uN;MrOU`dQh_:jO'Cg3YWm*TDJeO8n2VK[?G_?:;Wi=6s.9)eM7l.R6Mq\8LtKO^
|
|
||||||
mMKKTL+LMt(7kt08>hl5jVkT"U.8.N#@.m&11^BHUSaEPA(G>_2[:SIqQ<o*-qkeT_t-;J20DZTIu@bs
|
|
||||||
6!KEVlE(=d`1&/]I=7I(;025S0In%rmjCs(=1C^.G]BPFHoL8^?PSjU,>ms-n+TQH9RUJjQf8jse=#F:
|
|
||||||
rVi\'4f4d;\YVCi1N=eth`rmt@I@h+RtE^9qJK,,^P_EOkRma$kjMtL@qFjl.rYFZ2fUD;HrmetkuT[e
|
|
||||||
:9$gE]YBIe)Lk[1eTZWUqGgBa4V@p:qL5I`5h#W^<<Fr!$DftC.2t-.ECc)(s+uM6'?$=7^m4)=mJI3q
|
|
||||||
K9pHVRZj$"E9<O5]%Gcrq/</Wn!H2$k".mXS^-I8V9t.l3PDj(';AVc(V=7C[]p>Mc4G_)(2oMjGDl"m
|
|
||||||
r)Y]KXjeRm>5c]PZU(?pX,iK&I[gnJWY\XOM>NqgP+gb('miF#8+?C`BbR)!SV)9Lf^I!>G2=!a)4PcI
|
|
||||||
LVVU5oARS!H^/BR<bYLH=c\k`g'*V)9'c,;4`]&tNf!'kek.@7:.5*)dAhZ`]W][/hBQ_Jp"(G%4rUhD
|
|
||||||
Us_W)K7DLP`*[WoZ:?SEnuO9Y1h?!ph`'EIU$1R$$Z#ZM@!?0qh-q^hFO0I3)m)`T]@`g8OMA%R%8pms
|
|
||||||
GUmP[9V-[6b9]l0A)^!p&n54$Z\R`bCBRKJIK($ap$2M*qOH=]6fhCj)Llml9=F$`ZoDWI/=mn\b%cl*
|
|
||||||
L+TH:T2o!NOg_@$<8ccM(>=rI*//[;k`J[tB:@-^k4B/O')%^YV0f%*L+BA=afnWl*%@.Ai\Fba?J/Os
|
|
||||||
m*=rFc)$V&d#d$B=Jqgt8%+?Mih+jZA,\7C,J`BU`dOn`9_/l3#%m3]ZMr<(XO&_@Y%P2cObl-ep"([-
|
|
||||||
c7b_q!%0,^YpdM&+lTN`5B1rh31T<61=>Ohs6)Rg;%EWU$2>]jE7^0F&^8+UleS[tb".$DLrrP!h8dGs
|
|
||||||
RrQH`$P:Vi)c$ig/'m%si]@ff+5Md%$fu]@C;QW]RHl!O;KLg>*K`qC(!Ge<n=6Api/&7o[Q5kHg&"H8
|
|
||||||
AO#S>W=<PFF3haCAIO%XZpn+*T'n;ho4YKKr!Yf$@\eWccR,l>nLF3(+ha^&]'nl@C6IDIk('870l9$`
|
|
||||||
fLHa[R;98)/LKR`HWR:+lJF1X+4f(@4c;=kL;I8^W"j?N1fbe";4IAt\?9oLA,VH0:?R0KWu1*YcDoGt
|
|
||||||
KkaA':FEo9ZPQT?-c$ssHA,D=62Ia'B2oC<PVraWg)AC'Sa9/&QJa>`X@8*IZ7Yn*s3@_CZhLoL'B@H9
|
|
||||||
EDO!Hs%WCcNE(gIiso%c*+>W86J6S#-"WXRZ`#=[G?&qNB"DePg-M#$YB>ScAFeU)ToB9d:@"4#<Q,=!
|
|
||||||
4k9:6LV>j\j2Wf1Eq%I0IsYhJ`kHO9)#8+,q(j#j"CBgZpZFcG5Q>qOAi0'.Wj2Ic\F?l@<6_oZGLB==
|
|
||||||
M5J93[u42G)u"=gN]Q^mpW9a)\@Xf68'IGuF1SU3&DfE?Frs:ZZm;sFE(Go`>kd+H:>X*^,TU<n^>f^p
|
|
||||||
M2T"*9h8lDGNu=Gs4,41ZI@nW7=UZ]Q(2,EADZ][GS\YEH4TE4rDuf%.BGg$'WMN::;;'YGj/PIbdtQ1
|
|
||||||
nIB9aSXikPm<8baPr6LJ:5Lc_m.&S^`6J8t7i^hud@(nOR^e%)ODH>r&5TB21N=IE-JiN#EC(pIcs+$N
|
|
||||||
Iei6/@;=u;q:mX<F@rKZ7?8!aMuG'$$h#&,ej:;Xa6Z(s78'Z0]*$e><_i,%gZEGRAj-12ou&lIZ#b<7
|
|
||||||
0"]-n9\aTI\$V?IEBFn?SDsuq[WdaE'nF3llb\+O\i;lLY5QZ9RC@9pj"p-O(<8N-)/r90pG^4ara"]3
|
|
||||||
s&F'OH6f*=(ZO*<N)h%"DG1fdSE$/4jERT\o_UJPIODl\UooX99;Y-EN\A'0<ZKG-\\mr2h/SH.M>"26
|
|
||||||
IRi-.]qKT!3[lT?pcPJkSE"[7D^FYNQQ."6.-N6VNqYKSnbuhWGlLca]_=rZ]q%qf(N*QZeKZ="pl]>k
|
|
||||||
43&/g3Z#gu]ufdtr[,[(q?A?Yrqg*R,+.eHB&;CX(t6[@.=8VFcZ/7Q0kOhiDf+?eSr+cLqG??hm9H!o
|
|
||||||
3k5!<CIe'p\[-[Q*Oi`ZI1b;e,i0VenDXm%=f3J>Au0@[47&?I,OYaoN6'c5/`<puH(UC`#%e;.U<",4
|
|
||||||
@*uQ"]QnqS^M5)e]K'J"$4CblffY``pHaH/,_:6a+jW'_dc@a&I+mRd'1p"#%3hLrdJnT64;7Rc'b2T&
|
|
||||||
mmF9>=3_cK=lj>f?;B$)SVoOi2agRg^]+WLjj)V..d^']0ng?U-Z?LFXQ28;-?ADUjZ2knO*oM\msf#@
|
|
||||||
H,\#:XaK?,Fkq!2Fh5A&1c21]1fI?CiiQjI1S7Z@9t-QjY&<cGilp?c*L6nsSSo]V*VO;jr`1S(El:&j
|
|
||||||
khAm5<1UZBZo!gh-,7&U;2aA0n4Hn+/kM_q\#0LAJ]F7EeuW*EFXDLH*)5$!LA%HB`jOf`hpZ<$]tuRF
|
|
||||||
VHAP-)&8l)2,NFOD!J1G)g%*hiVakKjYudCpZbcin'8&oaU$(fT"LPFpWlQ5c^m8D-9m:[@eahkm]BN3
|
|
||||||
,p1XT\W1sMV8$12DA65$e5lRLa;p<_@0Ftbf7L3rnX_,bX3-qUhH<dg@ubLea10N"2G/h\[U[jdMiu9<
|
|
||||||
''eV<<o-(d'I+;]`_a=)F-F1$"KPcD270+<*q`:O%;IKk]-OjLSg@\;)BVGmib<%*EU9_ulQ1&S<JB+^
|
|
||||||
^8e[Zl1AenYG5+:iZp,,%Fp#f<J@u?d9:5VE`HgWqg;BuYHl0J@\2P7<Q1*(.L.W`L97!jICJ9Q<T04$
|
|
||||||
^8e\Mb>kCD$?N9u4rRd.Ras!l.SX*8X%F>l$XZn/SA\Hn-JOQ]8de:QX,:Di.W)[a[RpE\Y>rPiI'+se
|
|
||||||
`H0jK#b.AbV=AF-De7H[XX^I"3q-.Gn]hKD.P89W20kD^bh&lSfN4?(?,N.:]2-aQKu_]gWks[bI9!Nn
|
|
||||||
7]\NEn#K(0%\R!pa+m*M<3K<`7$DFe;=.#Y.ZSmV3B6V5`OBY`BrphGX%I0q6eSL!EAXgJXjSQj<J=N<
|
|
||||||
SlLK2FS`#sFQ'7J['/erb!XW8@o]e&Tef^a8%tu_k,QjmmoO-#9&%stic;t;`FE"T2d3mW[>WmVD6tY#
|
|
||||||
Wd&X8<u%Vl<u'=(>ZG,,je#":/4o5hRLi!="M)NRPiNLuo7%Q6@9.Zj&YIsbo.?`/pugT'(olOj]GRuC
|
|
||||||
3ktCohE4pTc[XkXFA$OY4;7=CX(1kt'754SbFP2J8;Qm95'SL5Jd:NKgZljpq1c1kbCXqLp[dHEJnfqG
|
|
||||||
\:KBNS)m1"8$:;J3QIIEIDLVE_7;LMK8#YE9\p:f1rG<;6Ml#ia9^B<IsdGG*WL##Dd0uFl#!Ik\a\b!
|
|
||||||
H6J<NmaK=@k!I7MFNDGi>9oJSTq+sdk/(4$l'o$I5BU5@2_@\UAk[@fc@8ka+aDRgTd%lG"3^,>)]!Lk
|
|
||||||
mfD0?Lgt;fZm)S[#X/^9*W>`E?ZAOCQXrq[j;UN"eKP?H4.a)RQ>`Wd0:q&qh=#+>YGP&&Q9BP=d=%)8
|
|
||||||
8G$etQ"Qjj$X\O)lFXR(^qQj2#W):Z*-s!OZ"]t:k&s):cgQJkZfqqRfbWUfZPKf1:j?Q1Tdol@ZBdCQ
|
|
||||||
EH`Jh08$/-:hbr7+/=-;FB]uP4@Pb&1u-",ICfeS%WIJ?V*+C&VH`884)K#bD>JJD,[^*][0mIP6$@M9
|
|
||||||
AYD";534u:F:lPOLoEIXrfEH^)3ls7;^/O=V9/1[XZV/K(Z*NYKENF@16AMo"tmPeOS'ekUMZYr5*c(P
|
|
||||||
GiG4%A67PCjZHf\pc'8>qYS,!S#9gJ3Kna3\T/e>E2&rjh'4))3o8JVVFtHEMWetZZiF`S$ZukE</);g
|
|
||||||
>HXS5!MKF`Uj,P+;nW.5EN7;,WYI%(d&$N,m7!AY>69(f1hY*5+PO&[EH9@OU3,-qc$!9s"3%U#1C#Q8
|
|
||||||
QEnd#&q:Sb#'jg:'OD!_)eoV'U+nEd\?][DfM5\JV;%k!i_mmFM%h<JYB?W"pSB4Z8s:>mRF?#]Mjg:k
|
|
||||||
d2QjtUIUTjZd^E];Z^7b<(%k4eh)m1.JI")Mpknr5.S%>*DG_`X^@`@=Ju+2*654%>,$28`bDA+lPdh;
|
|
||||||
kgQYR[VM!sY>DtrGWXt)\&RCKH#MfXXjT2?+Z)"cldrbgW@2-7)TRtUS:fcrA*_N"%R&UBE@T<.]>g9*
|
|
||||||
Tet%=`C&[!1p1heF<i_=9@WO^3"6`q8)lF?.r(T?\M@(C3-(#=Ql;l6Y&BOUg;?G@=<>CE)MdKi?/mKF
|
|
||||||
Y,$''CCsEQjNok$Y5-I)Wm)cJ;e73'LdCB#j#D4A;fTR7CE1lmOiFYOgm?$c$7b(LSE8H23^ruXNO*nZ
|
|
||||||
:9Bc<6Pn?i0=k!?[,dO<AkGU#ZS47snTPA#dh9\6)Fq5k1_02VCRdij1,KJMe48Ou<Z@.?&RD2Pl>R2"
|
|
||||||
NDhnr`U_c5`s@ZGd-E;lBcL,JP/aIL.WdLBS^_`/338+t3e\qqNlE6U]leB*Q,Z+kph1Y<m$>Y2NBrQq
|
|
||||||
dP$+50s/8r$9SE;X/<C><'0f4COc<ss2,93`@R_(^tt#d6q4R<4[HuNdC<!=\XU2b=7d1'oD6-rHnqS.
|
|
||||||
DWYK4bO:)V`LR?[QThZh;?t4EgeN_CiM`iOao+;ki\fMUbu<!S\U`^+.<:(GSlJ6!nCkNRO]?l?.5Qh6
|
|
||||||
%"%g<X4?d9oBMaQNgf5@3"Cg?mXBQAaI^rRT6M,PKs\QBLY9!5m+,4VW&&eFfE,MF(mB,i$:`ep2pIHQ
|
|
||||||
KGMsM[*r2@qm6eTAKu$>[5c\Y-$7Dq6brHoRPaA)NqQ`!$R>Cc-Ul,Y%n8])p4`oq#tb@>7"`1Ego9@+
|
|
||||||
=5>mZCr^D9>]F<#`bkr`[q?6*3RtL-c]K<V\WA7eaEh*Z\RVO>1TTD[/nAAO6K2_HbY3D0/&Rjkg9g0&
|
|
||||||
ofG_W[>>rcLnP\9f?Wk\Z'U@!_)rcl/9^;"JVMH,S;0VZ[](k@lD$j9b(U7_[1R=,4$W8IWks\!VVue`
|
|
||||||
O0K#V#7p2Pn">KnMJ]&0qjtKlp!q<WSdPiCP+r6G]c"Gio8t^*!Cs!2Gai^s2]!m]L%.g[e4ojHAMB/'
|
|
||||||
'NZ>a-H.jVaq7mV%;^E/S_!'&T6ALAn[9+WL"s6]Q+<"7@E&<%,`:7GpIG7W'ePn?)4CpgU=lC*b/;Pf
|
|
||||||
fP0M2(mQ]&0KVpV+L6`&bh14'29G[;MS,rh;*t3Geh*1do`fU%mFN#&;+$OPnd&*c`[H(>f@lfPo8,8A
|
|
||||||
6AakiZ*YL"Hb#G0cQY<dO>I][>`N**d&KSg!7AN+1@mCrD]RUH^^^pSXR,k^B=5'DqH(m3p,VV9riKhZ
|
|
||||||
r*`&t[8d(ob\3uY[r(dee^%KfPa0CY]JA%2fkYsVg-p;3lnch7SIq.T4#(C`Vbh;uiVP^8rj,aI97&>(
|
|
||||||
9\7cSh2%NAZ-Qeqmrd<gYKMr'H#N"48tKSN]&e"U.Q6(C%'h!G:o68/\1Y+re3u=(53oMJ<mJ8b)K/uL
|
|
||||||
D$I@>pBt\@%NONN^>&Y,0u!!X''%0l%u`1=lt_"%1QF1R`V;<B'KQEX@o4pubr[JB%[?-6^8t!\N\<'3
|
|
||||||
UZ`NbnMq'cSA)&p`Z]oVZeXFt,]\/p+d')%,a$i&=!Wo34XlL>\W'LCk&#=,m'9nfaUpUk2Fg>ZYr$)G
|
|
||||||
2$L+1LG!qq)chf!4G/G&=>T]pGp]0R*isr]b-@h>kUsu6`R%H0l'-<8`8(OUBI)HaBaoMmq7s*3;-B'3
|
|
||||||
UATikc]!7MGg8m#%Bg\`>)QSp7fQZEHac9l"qnOWL2-(-"_unL^o/%AVA"&QD;<<MoGRcmn+[\_nMjXZ
|
|
||||||
'dut-LNF*l>DT3pc>fRofBi<NR4-*I3`<0#ft"6>.'4:#86MdfQt/2"qKqkBB+a+"m0g0?K4?1mi([DE
|
|
||||||
[aLH.4+DB6B.-i2PDcAZ9TRflqg^X^'Qg=U]6.O]W5!<64;DV;`VEj$mbU>ZSR&PoV\Z`BbVH0N:R9@F
|
|
||||||
WL806.rTNqSCHU\ZhSce$DgLV<ZY3f-$5;d/CCgRf)Rj`nSH<1-pu[)R1c@.:TAkV8(P!=n=28PXP[es
|
|
||||||
%=\J^eT8ILQE&Iki?Wh[[5=(+EN1F5o.bC)\@aERm.p4\*$<HID_:g3s6i]eL[Vs9D3(kr*54V2+RSst
|
|
||||||
T/`GB)df5$Q?Qnpm4pS(NdhP1W]`5-s7-DTkILG:i'M'1T)Za;Qcj^Prd/R24cfSkiqU3:rO!EeDf;/C
|
|
||||||
GXf%iCcgXa?trpGXZ#h2c0/AUfbcWL[W1Jh+d7_[06RuGdC)R?XB!22#^GW/6WWN9h^p:X_XK#pF[i4q
|
|
||||||
<Oo$IC7X^pco:f>Q*n94F!"D#ZG,JsDJ+%uko#5[)hcqr6<AYjEF;X^@\iPMM[oO4TFuPb'YN]-I8de"
|
|
||||||
LY]1G#nu(cl<7#Ml>Cqh(d_<-AfhXn7D[C;9:*%4:l$FC,kgZhM.Kq8"sG:tF$rgFtglB*7%a4VnJ
|
|
||||||
R.Xt\Ok+4ugl9dPRCdD-h9:,?IPip%W#btFr>!msBX@J)H"Q3cJQ@Y,#sAV&hXi,5(\\(GX,RG?gSlZS
|
|
||||||
B%m+m]QJOhf\X+/?YbjDE;/>Fg2Y4qh6RtV:97p<';M>(S%?"7RWU*0Z_FG.(</>-W"!15'h\BC;*`>3
|
|
||||||
[P0:FLq"p`9Z[(Rb3P(94<*tU54R#-rj>OlFgq/;gP`CO[2aDLN[q]!?B[J)nhH7g0`K;[2L/ls2^e"2
|
|
||||||
rAATC%c(-9$5%3>=Nd[]&\p2t7ZH$t@`C>Z+$27O_MjWJ*r.s$pH(&YYQ"Sq[ital],<Q.2Jes40SIo-
|
|
||||||
Ri0\EFkKn^b2n-5eqK38_`=@?l'uGM`\pW(g2GuXi+?$ML7>^JB<<Hr&hSfWaq&D+aq@RJA;NfREuqC%
|
|
||||||
7]SB%@.,%9'_)h7+o]C3kb-EL)FtfrDi]*#b"]'sN@mE,.U!GQa*#oKRS1tEnegAWM4,@,A>kSSH,g70
|
|
||||||
*LsDh)egB86^dn2a&j;;>:4JPi9c(fe6P1P];6_[UWggL#A5!mOU4CLTePIEN:L%Z4%[$hZQ7,hS..ie
|
|
||||||
e5,pNFQk<%aBJ;gs5ceVm<)TD+(UnOhV2,OiI!&RJO<Q60(JpA8'/Lb\2+mbM6&nZjrRq$)](E5qjMo_
|
|
||||||
09==q<gha$H@"m(J^&6qH`Kp0Sm9+i3f#EH(K.b#k9\P`(51KUZ$?t.A,A@7AH(eH0tO\dnkI4Rr/!ic
|
|
||||||
LAksYk."T2hHB-E"\*^Do"=tie@M-T/?e]o$AmPFfnW8C3;S=@<Sg*V[jjWaN;FMEWaG&sk,O%^C?9c'
|
|
||||||
-kWQ<RWM`VE$Ijr^9$uHX0BR0a#n,m,,P1DZ@Zc,+[AD1*DT$cHo`>op-5(f&]kIg?n=?9643V9HS,pU
|
|
||||||
@;3;XG5gbNdN6:K-_lKtFseQAk^Pc/D55SVK3$hK@_Y7*Q"tO0U.E^)o,SXBTm9GfJ(\%4VrPbKZRf\C
|
|
||||||
R:>&Jb;K2Vk!g>V)>e?sH,f^8/roLI^:3\,k05<+,oDGVK8*V3MKW(RUGT^Ar'QL-Cgm/:*P3,774b"e
|
|
||||||
dN4H#fUAAj[&>m,,V4\n<='n[eh$/oeC2PR2?%31%tRU3`%lY.Obgs@;d9,JQ$2cidGFj%d68=^2cKda
|
|
||||||
(d0WVKRXJ$&rbf31'c"B@aR!fR+^k3,1nN^lW`he(Y8>C<"o%A:N"#(3;U[_oVe+@+&&ZZPFQ]&fe+W5
|
|
||||||
/._/HSoKAF89nOUWXM?LCY7>Vo1SnBEgE`?>%MDC7TN0rUqA+^KMnuM^*Mg:\.;"WGO/5Q!oGWlT%?R`
|
|
||||||
03EO\Pd@U/96j*6<@Gm=T7<Y!YA;rPP\*I3Au4pWnuQXJ8pMK%d\;s>*@Z2+.G'tc8U3dJg`tp+\phta
|
|
||||||
rm(!gXM6R(2NDVo*M@^SP&eOL6coc<5q_9E+dDO6s$4^70;o=/f83HuH-,K4%fVVR;+`jPTl=/rD0.DN
|
|
||||||
RJ4=KPUdC7Xc-!?c-1UmOE.,?#V")YkX`k&elS4/=8s089^_^R7Ll:hB`I263JEE::,Z[ZFLQND,A"Be
|
|
||||||
AMV,DO<FeVI5B!d]Y)ds>?G-"QH'^c,Klnb19t18)WR1Tng#Hs+X9'"ahl-_4>O3UCP@]u`^qF#doSJl
|
|
||||||
!pb?LA>BY.OjMf2'pOj/>UCjDKfAm"7W`-rA;'0fLh"RuOnVBadFl9rOePq`AV>9MKG;59^1:b[T"1Ot
|
|
||||||
Nf7qdj2_LBI$YH.&B`(PIjcPI8OGt5NrUrPr!o&*hO].+,Wmj'aE5-T.4CAqWQRgMjCP`>#MS(bg>ar_
|
|
||||||
o=C*I_EECj87>5V,9qF4J$cO+NK%/p"*^kXQ1lVRi3W"Eq*4Ck>cB0V`Ju?,WK5kgl81c7eZKK]fn0mo
|
|
||||||
n>-R7HeG"XQZ[/Mgj8=_$9._K%Por4Z^=ISkl:lbF@]LTb4aUc7/$7Yn2d0^^9X,JZ:?Mnm-g&[h>DJ#
|
|
||||||
[D%^dFGSS(W2.Li.#=hCrd)nWa2-O(&c=h;0p4b+U[Ee!b)0q><#f-d&mJ#I&?V#!4irnq\T]j_X3o4^
|
|
||||||
XS[7V><Et?doZ$lH6a1=f8((6d1hdUNHO&!8)KJo)6us$Q[0q5U])</`6gtgGUIaB]9^ukL[4e.R_`'&
|
|
||||||
O)FfWi\9-*o+/elUMsNp5^]ZBWnchtNp!T!\io=cFF2MW@rrufs2;rACe,4?[,ndWdg*Lm+od,`r-W"d
|
|
||||||
-_Lq3)lmc2jBYR$$k7F6p]IXd)T)3^G`RWA4kmllZ.A'b0]SnY4;Q-5U#D:t`1F%@Q$*;i[MN+Ocd&$B
|
|
||||||
Xa$Z;`n)21[n>M%lCN[[)!Bo8Del&ULg4jm*tpN^Zeub5]JNXC$GG!0D83LC&Q@_s`dRai0mBG<fG/[V
|
|
||||||
en5\/c[1,WQ+a`I?bhqeOd^*bnSPTuHLlq7:@@OVh/Y=me#W@^-L&2t]&n=3,?ZI!Z`hi&1QtVp]A1*l
|
|
||||||
?VRf+e.:9=Ar%&ta^9ACcndk6=0Z^nGKP,s?g85648LJGZ*e[W.m5]%ff[QVml$M[r2MT2H?@W&'M#kI
|
|
||||||
Cc>mN3Q?6G+iL0rVTdoJq*s)!r_#4qB"aY`='6KY9TD[6HF7A=FZ?.-BZ,`6;PrZJL+'4[k5dE(6et1N
|
|
||||||
@%;B<q(&%+,tr]CrR]8:IM_0Xd0jQ!RoRuRS]<$PL3e!\)(=mZ8lD-W'm;DJ]BI+=/b@0dXjeC1IQJ!%
|
|
||||||
:';I&>%<t!d[Na41F^!#kAf&:2*>pQ.5f]mk^RjCijPmFHQ:[=kFUIX+&b#@g9iY0&^aD</EShpa#cch
|
|
||||||
EkRldFc*mP4&PiSm6mpDY!q"#ULQ9PB[oj.[1heDF;Lq;R]XcqJs$J$f02K.f9t^o:Fped],gR7D#<lU
|
|
||||||
diJ;`=3bS$r"F<9f(5NdTE!5!l8dmZMJOiXnS?!q3FNH,)aSLW$%n360+=7i?ml%aNZXeaXA+hN7b!9X
|
|
||||||
,<Tac24R^Z2ZR325+aEPPeW&Z"Ut!*9/8<QcpJ0W*eZ\;$X9jNhk\qjEP"a/3dCh?J]hFC.T"qh$OHQ7
|
|
||||||
k^NUSg$+ZMD0[sK^Vc4BL)*Tss6Z<cn3Cd.n$#X-5u&5*3c]s1ck*m_5Bbk'"It&/+GDn=--B?iLEk3Y
|
|
||||||
SlUgjkGs2,>+L2ImoiUL:_D]2S;']8`D'"q1^aN`QWUE4In-tY7k_*L7h;#0m/8"gkCCtAX#939(5aiR
|
|
||||||
pab[-$^4j*'i9/L)u4[ufD65#Fr5Muh2Q^7rT8$a,r/F[X8V\"kp?hVSF]31ZBT4aZ4:/:8J*d`c:amG
|
|
||||||
#B#I%%4GM\0Inc[bJqE&AA,N"F1CO"kg+*3QT58q$uJKQCV<n-^<=A'cMNHrUjsESrX+Y$Je9[7K)7tl
|
|
||||||
DYh`4TlHqX"^5#Y&1@pkNOMbqgnk-&UU=l$FulQ,(HA"4Q9:<W^FR%JC^oo@05R0qYKat1hsBgf]$S?G
|
|
||||||
9o#7*psLI1TtakQ^;XA,+*=m.`=kHXFM^0f\YQ!QINL[t(MA-S'9Bq%1a#5QUd"H-%c)Mm;(h-e7N/X,
|
|
||||||
[g][TDAdbeX^.BnFs>@ICQ3#mK9]Ro6n(nJlL^AEitMaF^,Vl\.d"<&"6UWNUI!.2f_[oMV5s#S3B61@
|
|
||||||
o0sgo6PbR'fd4jb_U=<$elP\m=#G$l=nAk<8*eO%``6IB$FkLHr"@R8NA`#_Ar-E/ae.\,R6(,ZPk3_F
|
|
||||||
Jhku1#Yn1&Fb<R@qO]C>T19,kA+eETB@eKiBO(DB`X4:?:)-395,M)ZgEXK(j1O?Z>fSu^X7X6aFik(:
|
|
||||||
=6Ja/s+uk]s2&@h3YL''p)L.L*2OC-nC?Sc9ep8!U7pQ0qt(!cl;pNLSj0`VFs+nsEBkuLh*hf%`RhW=
|
|
||||||
f#Bn?K/u/P^h<RJ>l)HqI]fq_R.UFSQk/F&QRKd>+7-Gcckj-4E>KpUYD4d4kO"2890*j'=Ik@)7s5cV
|
|
||||||
bJSt$Uk/:.):n^%[U,,-+[nnG[edULX_tsm,$MUH1p=9j=9QbXf3r?_5WekLrLVc4V'FkWBWUA]0qtP^
|
|
||||||
*"u?coo',:%5jJcNG84rN"MC$Q_I2FX_;o^KfY>sIF5I7r6MR6&a9ME=5o7aHi?""/\0`30-M=L6qdL`
|
|
||||||
kiiue^-pf&Z!8Y-FS?Na6s[:aXKI2P=6ho]`Ik%;W=h)W-(^[eZE7b"s,b#]>1WgC#O>+[&p6g%j`m*W
|
|
||||||
&Zk%SA^ONNSo+$EWnVmk-0c7Y_3\iWm=7US^+LqPMA1Gl0@>sL$YY5ZU5Q7'$,%cpU:KY69;V&\1KE^`
|
|
||||||
Xlsnuoj)'5=6jtC,987f02]dU]0kEWWRU$`3"<Lr.po$^<<cg)\lArAU:O>O?UFCO0;0H07]UF%ab$SZ
|
|
||||||
[c"/i8an%kep"K]M(\?F.Qp`+&qTtmNiTleq\-\sju@_pn[ElPn[ElPof%7)N@'%0l4u1'$.238Z\@MU
|
|
||||||
/mk=_ff![fMT_=t$=?-PARe87Rsp&h-"!CXV#Xjh1?U]18R&_0=t^>l?!GPupYeb9/'+%gQcbZ)oaC/A
|
|
||||||
2RH6$,&X%nYu.MK;8#mUGNd\[]q+m.5!$>Xn`pRlc/!mYHB85bgQht'#$oOG`92L6s!RO>fEaQTm5)Th
|
|
||||||
?$Gsd8Q*6WDO\E1rPanI<MPj$mRnef-<o;[2g,Q7\nnk[":6.:8?C(mkjkT&!+q%l%GPW=eD+LD>kBT?
|
|
||||||
l)NpYeN$RC>4n`Sp,[M!cZm/g`cmL=R>&BC&JqqV#A<kPgG+/:XcR&c,eK>=Sn<$7E2s/Vcg]Ia(.7#'
|
|
||||||
d>lg;l+:fQZctL)A)'6Lmk<)_a7qLN,^W9k1[;e@kNe'"3;`@kkb?Jt[7h$=G=M2i(t4R76A#E0nLSfP
|
|
||||||
c"t'E\j;BH)<JZ@[:!W_rX8DnTFFh)K8<S9=%Iar`B!luWinD"2'DgfiHD0oLrcPXjDg@*Z/4j=.t]3J
|
|
||||||
UCU$iYTAP["qOIi;ipuK$LdfcLM*UoPNUq!Q1-e4<JCDI:[HF-?2YJ/K!8ng:EU5cjF7-e]SW0o]!96B
|
|
||||||
M=%[*m=t,7Yb)+Lfh^b'R#L"JZSf1\:_M(n"QGt#_Nkt&^AL\Pg%fiuDHc^?c,ef3*cV>>?_["MnaXh'
|
|
||||||
@QgJXl%h8FB*Vf$Nh]t1:H2L#Rmg!J6r5)!3nL-5cTl,r_O2dCph#*Ulea;b7"RHj8+RelilILLU0M`2
|
|
||||||
Z,3Td^l&$aGX&HopKold@XEa(lK54fCd;GsPmF_;9WXu#eIC8N;rK<)CU*_MoogOa9m9;P4S^;XUU+JN
|
|
||||||
,!4o'Eq8,OKB3`PSA1c;ektH9:m'jNrU5:Hfq)Y=,-[V$61;GR?eS%"ZTd=>67.IGT%agVlGLGp-ZO#b
|
|
||||||
O`EQ#</PqBZ.%FAn;7%YK>#_?S][A+)hrP,D\YL?@sOe[n;IHEko.DF\P69(h1KK_#1\UcdEmd]A@\_:
|
|
||||||
laFCDgn)r>[YOP*$5*sQ24&"88q*BrpR-)`*Ygmc[ZH&XZ%Yk(+qNF2[#J#E65QK+#%&3%\\KVDM)hAl
|
|
||||||
ZJ2'rk*M:($c&;KL@(S3SN+2kl^&d<D'pNoToX$?W@)#7!?)aR%`d&oh8qHLKrpi/!(UYWB]_9/->-+Y
|
|
||||||
`X^I66(%n`aN^m`LAGF;c8^pIVr'6jlK?uZ1tJ^mqu?BGc*LjFDtPo&P@MYd$?<'oHLil;E(S=gj"iZO
|
|
||||||
UsV-PJ?"CE:p*'R!S2ZLaj2e8%iSGn!__aI`pqXEX2<jfP0(+e>&hG31TV6VKmF:&X9*6'Ti,b[\<-ge
|
|
||||||
#L8h/$\/-h1Nf0h@tIat'lo[KSd%QQhp(p):6KTt9XjWGC<Y(7Fh3sV`@@D*Wg-=MHkLNi(!b(QIFOX0
|
|
||||||
q]'DrhAu?Z5Hd_G6C[r9M2UYD1BgHUIe2K-BR-*Yl`T>.O,Z`S#EkCf0PW>Yr0<R<KsG^*4R/7JfCGkX
|
|
||||||
2I)<<%ba*MhXK<u'r_bmniHLV>$gA9Uuh!fK]TO:b6P'(LVV:%D`X#$\9fL.HslJBI,P`_a3=:SQM7^R
|
|
||||||
eFFGH5?`Ll9h&<81Po"U*4=_qdINbUcd<).Pa9@8W,ubL*G]9MM9;5Qb<'_rn!I;8hX%E,EQYL+/W]^E
|
|
||||||
Y2qUkM581opL>0p+*<X2^MqIWL/-GrKoqZ+e`P9M36flW"/0%*E]q.)c4X;%FDIch#A^/U5f7]Nr8nSS
|
|
||||||
BmE(o:7Gn?nig5@YdF='W^@(U!6P`%o(G/DT"Y(B($uQIH0]I=0)(j7lCfFVeuQ3hc<i^4eGIYa`g`s;
|
|
||||||
Q4H_*7;kA%jZ8t[/m14YUZr17[86rUP>\>*<GD>J`a;QYWq_Bh(2-)XEspgB1ErAi6Ie-b&`h<?e2;Nu
|
|
||||||
IRX&N43o$%kiJJS8nii^nhOunrdg!m;)h%p?'W>8O,KZlZHp5L=QSaZrDHq+TM0DJ;M@7'C=#\WPBFhO
|
|
||||||
/Ucc&k<a$Y3cTj7%oMm*Z'Lm>(m!bD'?Bm$288F`Mt+n%Nq&h:9T2bN>9cZ^6!hl3lQ,UHC,5Y\P#`JC
|
|
||||||
+)CfMp_)1;NUJ"?WY5leM<!-9?sf2tq+p@X1b'UX?_pU+DtkfAb(T#\d[OfS[7[3#?U#M;e)fRrSj`2$
|
|
||||||
%"En"GRR/U\G"tr;U[@qXb=-.aW&Q$1t'(9?Ai;=]DpeoOF3(@#M69#F(\b@7eb&0V0&p;PMFp>l**Wa
|
|
||||||
_fS1Q71^Z7MX'eNh*c[sQ5eH;jsO+ThaKsSAeZ"ckc.g(B-h^`PX0+]'B@"<1H`k=fqM*Q)NsM48O9@:
|
|
||||||
H5N\).VJ-a/_!K'hO<6r6f#BA*Y'cY/<9+^3EfR^q)PcIqFb"Y9:4BcH-s*#FhR/`_+:<0Cs(!`BZKPY
|
|
||||||
8\K4c'Go,07:F6R;JE(iVq!>3=Ia/Y6Zd52jr\VCjm%hho;p+o.%ctcf!cPn+r0r+am5"i'fr$&f?@MF
|
|
||||||
.+bJ&o!QVlf;Kc=CS8(ncTrF`n]#<Y>;;uQ'c^M,[!2OqW`,1c-h@EA]+ufa2<I+WYEo*CkPr=l&[d6=
|
|
||||||
c`)ok[Ll96Oe7Q(>#TO:nsil\XC:ml.\Z=[&)EltfX725+*_,>!h;1o^I,/YoB[g-nSB5L+iML$&fg;;
|
|
||||||
WQpsm(dDs;pr%r:I/XWrPlpLNpsEfF2(pQDfX4a+pV1YX/QReakGTdFROEGeY^J)$>$\n1SuRccX(sG\
|
|
||||||
G4F-YrD,"G]Bd5oB;JW:/Z/C#dUI>eCs('\.DCtX8s])W:0.%D\FEW8D4q`Gh//CrMHrC;QD(rD/Z)`*
|
|
||||||
]X7kIYQigkrLlj@?ZTP!\lW(ZO_Z\le7)DiP#IjO?WsPZ09d6Y7"k&*%IJRXZ.aG\NESF4[@e;9[.6PA
|
|
||||||
eNkXZ(39tZ(45Q^:D7ksH`k<rYL$adqmU[ebTj0;->S[h@RfltrRYPYF]#dlc_2-+EKb%@HG?gh/ZpZE
|
|
||||||
ldqsI>"j-oKGJq-T4a5Gb:00k>BhSVa&C%coOiD%<^]o1;uDP$0Nnt^9gGm^W$NNK>*Ii6[>6S.`/;rB
|
|
||||||
)N!Wll_`d`8t?4SXCoT@jjrO`r5,-bo_^'H>HMf;fhAPcX&fn>$7(+P[[N2TL99%tm15%3_36I_hhBY@
|
|
||||||
d5@V&:.3(*U-<b[YSH$$[!`GFL!?]3chqcPVs@&f]JqNH'ij"I2[7P^\\6gE>"ga;Vo>)eMTsUZ&Z<"b
|
|
||||||
;JqtFKUl:BXj-6/4.Xa81LZ!WeeeW_4G=OlG"FWep,UcQhprW/7#:q3NP6Vf0Xg,kh88bgg97`Loldp+
|
|
||||||
YrGcW[9E]HUL;+C@ooInVnFS3L8<:)N%d%Ke#)S&J=CO-(Y4Rd,LJps=-_L/95?iVYE4+1dG)1_g,A@i
|
|
||||||
9'_Ea]2ih?rR@i)#06U+-#2p=\l<^X^0A-(0hA/+2J`#!e+QdrVsS$0gD\RVm"ikA)npVYrOMoS%iH>H
|
|
||||||
SX=&dB\h]EBH8-3lJt=7[ak1mn[m_@iGl6\Dkuh+Xu[$6fs$Z/D0QJGnJ\!R1q`V>`:[i+^DkpR^?JpG
|
|
||||||
JuLK5@)hImFM[X4T^[&0,#*tX@Qp:'Qg$hCD:c%',9fa+;tA$FZo)2*F[C-iDm=HAA.feon=CE'N[p\3
|
|
||||||
\gTJl`8=^dD1bJ#hmDYU0E(HcpMT?&6+)TBqu&Y-/B:<V-ZBmqk<H49s.B%FpZCm7/%Xi[n_f9!S1IM3
|
|
||||||
_KGuO(s#mK8S)Q:1&EV,3"4G[!t7?YUg<LMDZk=nbYmm.-9*HgX*\aD=)f$_oEZ[trd%i@2*P^NNQXGB
|
|
||||||
/N4F:eJl:gkNfq20AHR21k:tBQ)mptH\,l\hF,(NTJOG7>1m>)IhU?4ik)KRj&]*@-$k7!kG,eU)&P*F
|
|
||||||
aV+l=Hf@f2O7s=bGlM?iB"<EB;r!B.@*<L*#3%W9-=X`X4.Y_e)ib#ql5BojNC^Gs!\a'RhddTrs6cbS
|
|
||||||
`inrZ'CX?"S?TbtQ'i`,^]E<'eB0]dIcYt/o%*<2BS@up?Dl`FoTts.8c6gc%UC!^dp\)fU_8%8'YNZ,
|
|
||||||
I8_+7&&7p`F,XJ#Ch06][ui&&E$d$0$7G>EO-\ng-q5>lODX6#DId5QG7PGYX.?)feK^Y>nG^FQ".P@#
|
|
||||||
[:1KCc)tFDml(+SJ$n)q&ip_FpLs2rlOSG'kTKkE.ZHgC2gn8;S63=HgobBO1R+qD0mhS"<HFWs<fL`C
|
|
||||||
n9]CbkYRdO4K^W^j8*Z_U4-+4WZI"Te_d#DcAnFGrd&YL:&jVmNMftCiGkR??XiQ8h@u%:]8$mp7AVWM
|
|
||||||
5RLiiI8N+O*%/:+L@e--4e,WbH$hqW30-j_aa'E?N*o8=ON70o\4tjD<-5hXVm8h<`U*'PESD>@>3da\
|
|
||||||
\`d,m]1(G3^RALmargT,YL+'S(.ZO9*]heT'<jtm_2H%%pr;'V2m2=S5)SBm^qLHVo:O[-s(D.E")1:R
|
|
||||||
^Q&$L[k2$<rY,2#J!8cuE=f72iH64Sj:hMQfIL42!:V(T2Vh]*E,G4hfUn5^qo&=omNu/^jRd-V(SaJS
|
|
||||||
rlRW?)ck?B$Sg?kme-P]7uoFnM>.16WEmZjfIUr0iU<2RHnZ&=TBCpSLtVf(I#UsLAMM%g:HEggfP-jQ
|
|
||||||
_=nIf7^-9uJ)^'IpE%*>kn3BcYeRD/Q/ft)e#7IZN+a^+NZ,d;"H)Z"*aUG@]Tn&[+:o(GQP\sh=mV3*
|
|
||||||
Ok54;c77\nX;GP(nM0.90Nk*.KtE3L;HCtNPa<8$-@)#>1\YPWVJZ[dGBmEGpL!p#/5GXsdd7P009N!`
|
|
||||||
jg9'(PBe4D_lS`NLe8="$;SEobtb2*/E^%Fi23(EJtP`pNE>o'"<+"YibWhd]5mT]Hb:bV>;>^2M"FI<
|
|
||||||
Zn`+N)g<],BVY'G-gicmF@J4Td@Dh;o86AkRs8bhfD4Qrgem$S.5.21qpdE'?][DhSb;R+?R!qQ@MI\1
|
|
||||||
W%0nZRP!n(NT&ckS.8#@SR\'8o&B,Qo+WP[NJ.RCI?P=M_[=#qp#eR05LdKZ?2Ej^2;Xl;dM^[pVQp='
|
|
||||||
^g$WFRJ0g4igCeXq*2*jL\KsF+)S7iX)3iqMY4im.Mo2E.*dMamil'A"s2j"5A64DWi@Td`j[@sL+HrR
|
|
||||||
qgSYnH?5)O.\ld."4i$QGNb>1s4)\c;r/aZ_#$3jpYd/88'K2nQm:-3N&`B3Rio/s[Q*6]R1T"2*<'<H
|
|
||||||
pP5?nDo9)l?LNNl#_`l@a.&X,.f:n2n"_L2S'W#f,M^7NdJHT/*TMu:IZ4j_[QT^fNEVU0i#&_<HpQ).
|
|
||||||
iqnYXM4BCcrW_6?6d'Z(h89o2-:<dHL3I<3AmFkt)+49t7?R,>S<qb][BfP&Kl0o:GS<"(ebNXg$GZ:R
|
|
||||||
>\3mX=,s*rDPtcsYK@m,af=G;ooT\0,&,-:B9CIdr,ZiLK9>@nHmh)h`BJNP(t@T)fmORGVfrP\65kQr
|
|
||||||
_2B#eqEOR;90'_H2srlWS^.CBp\#W/m@3j!)R_efM;*Wc3U?%ZR:#$rC;cenS,u8bb8T?!=)j2h;NKME
|
|
||||||
%M`'9(a?HDTfMhXERm$jTOij>nduooat6M$N:YjSmdXY<2<-i_T(!V;4n0HJ5HS1tE4Y12/m>jZ<4GoJ
|
|
||||||
I/Xf"o7'iPLf&q\c2-<&K@s7]Ac[,+=OD!0Wft;4X"(8G4c>LgMub18$7J!QZZ]sE[)19dMe>5UO>hYf
|
|
||||||
Q&FQJ-6/-Jice8'6qC=]0$N$$Lj5c@Ze(.<,ki5E=*5>T?lp;*ZrZW/n%129[L-9GdmZ3^cS&:W0f2l:
|
|
||||||
.,eM"'U>"7E/cnE\:_C5<.Z`_EOqVoY?Z>c%TS(!;n9WSd-C:Bi-FX/:)hH"Up7@0CEcA:V;-<B)K<C(
|
|
||||||
CFAK8=1;!.EZ,Q,<d=dR^cV/2lp'dSk]a&!M,Q(-Wqd'O\Z1G.d7&P'SYCcP$T,dCVKL@aLdT`Hh&G6i
|
|
||||||
jPP*Mg/kqTGog%.*;'s:4'D6J-MjcAMX#-R=qV__Oop2cLN??&/^brML5[ONU7tR"2s&"ZEqFaQZM`C)
|
|
||||||
a[VJiAGljHHSqEshO!*jOZAfWcL4.>CO<HAU3Yf@gTmP^-f/4(0db3hhYK[+Mjbo(%^2-u%kUWP89`F]
|
|
||||||
;ApfPQ&CMFX/K&8Bg0s2`OR+1e!G<JcRY!30;A_R-U3kL<?;NS6f##*',`ZdegA,TWie-`)H9X)B*_Le
|
|
||||||
L>4ZXND_kX4Rr#/-_<gseJ[VEhZocW8,_Y($eGbbhFNdkg8*Wdn;9>aF_)?X1XqKZ5W,Q7[qM)?2,!Nd
|
|
||||||
<NP5):N\b@I+3oR^"/1KAij^U=VWDZ,Hn&p#+8UQB-c"I!uL3BDX2566YYPP)ln]ai2V(^b-c"]NUlef
|
|
||||||
=__3PesB=[=#J!ec^IA87;L9X=As1MU$Zs]Q`uLDE`g^8e/;9Y%&k/#nFW.tH?uU.:m#:4eRkR]HZc:H
|
|
||||||
P-+`H,P@T"UD#IbDftHa3e^j&FQja=>$%Q)H;X=oA*F+a4qg76^CNdk$ru.errs^DnT1<%^cB$O,[=<(
|
|
||||||
CV,f`,-=tI>a31#XD=&*^'E&-oH!lQ55dMTW9940DFkSDZu)-i<ANV'G&`&9p=(*UG/5^7ANta:h(J<h
|
|
||||||
:N>V<#Q:rDlo4?=l0+]L<A&=*>ZJ<?[M88X_3m6C`Uf^(c_GMXQ?74K/on,7*8/l</e:43*1[MAalaaG
|
|
||||||
IZfaM&TpU2UFf&(X'JHB?eIjF'sAC\A0F9$PpP`.)Q1*)2mPY0??89+,@C6*g%E2190Ct8@u^jG8b$p:
|
|
||||||
PoRrt[rdd?@o?UC$6F?#'Q$0Om6]@(2>]Gl&!c1nrPA(UAjj5(B)P?FrbTfNAorjPj@]c607g,QiCd/%
|
|
||||||
&F_!Gg\C=cC3SLE^JmQL1hUqO/A6q`9rGB[DlDes^O:rBodoI+>$\V8K>Nr\bh=XKX25E_[kD1/j,Yj%
|
|
||||||
l&j#Q5\7(@Q`+fjad8rD)o@q,`L--@lB%fgKqTY/Zpq0on0Nb)QO(5lmra#73i%*t*NF6=;ft_7.sc&F
|
|
||||||
I96HteQUet83s*49@WP/DNTpnL!o?]e?I>p[T>Q)7!5O=^-m+E9YQ1++`42"b!rH!p^ZThA\hBUP):`d
|
|
||||||
p"PfE?0"YO[SQZ2Jc*-?PJ4Da02]>^Uf/M>*FGOac"$.=^Yqh!1uV),\!ZnNFHQN,Mq^\LBj-NM;2[D[
|
|
||||||
^6!^K@@%G48Gp_Xq\#%rq%AiNf.g!gc8AE_^'.?HFA1;1j\&,paHr".O=tV>(KE)fma8(9RbQuKQHH=F
|
|
||||||
%<_#B&%QMok8'>r&>;(lH^j"Y.pC2d6^ZU9A[ajW'5^.NR:pU)]?.%R?VROYk*?VE5I/$lh]LnU\OVTO
|
|
||||||
F3]TjEa,t@FJS\fZXY,\3T*L&,V?Jbg-"mF+O34<GbOB&?f6<FQZs+-Is8i;r)M92;ND#.<W*q(SSNTk
|
|
||||||
EKMgsYG_&GUY:ncH;]89=,hBaC6u!,'u#\_+ibo=8qYK4aUqj\1Y\4oJJGY>jD,7'#3Q:NA"2n]MiN`O
|
|
||||||
8K,Z#BiEcqV,l)liihG(g9WB5%&g,ZQ8FKn8D\:YB?ADone/_**P[c4\ds.TGqjqK0W`ccRH?[ll$r&g
|
|
||||||
HTkD7X,8g3Zas3-31t]5],nd<g5@LB=kc)t)R(s-HZc:@(J(s:KJU":hopfIX1GJTZB4j^0'U&?%tI1&
|
|
||||||
?_VK4Ip]<!iSp?pdJJ.DUPDn:m_cr:l/BH3J!LrC`A`'id_C.V_>Qo&l/bFlo_lKQ%ZU=SDdO8fGC>BL
|
|
||||||
Wk1_KSBpUA!S-,>lQ=%j\pJ$QJt\G8I)7hA+oT"h'g9,.*S<$7+XeBI6n`a@Z=hG-(<WiL-I">r28+=#
|
|
||||||
55LaTUIhWUApj?I6h]%u>^H4S2grHS(>-9FI&,L@mI#IEp9s,,kB=HA5jNIhT5N^7E2b1FDs+PLhn=8S
|
|
||||||
bN8i'Z:E3Z,9;fRB1MYD(0^<4(E`8m42^G<nG"CaFlT.YDd`&ohfKn]+0s1!FXiAu>O=@t?PF"l+8K"R
|
|
||||||
pi\g,2i>e:.Yi<1"Z&A*rWB;G\jfNXWIgo?VK3]kHVr1u$94;6E\8F$lT:0rK4?7!a4H+59NdD:nTjk"
|
|
||||||
k]"VU8&I9<a4H*Jn!>BRLSA_`MW49=Rb*TA>ZmQJ1F>#pV%W\"jO$`s*efAR7B.:eo/g8=Y%YoBqs/r3
|
|
||||||
H^E<.@IN)<bNnnKE1Y,=p"^h<5O--8O8f=^Z[-S;'qT84lG;qA[olWo^c:)!A>$V\c]0$/hEZ7>UXri,
|
|
||||||
.s^M>]2`OLE_&YS.RU.ZZ07JA\TuVqX`O$F(H*oUF[""(I(S`uB-n6q'@2#.0$uu+DKMu/p^bg+I!o(/
|
|
||||||
s-ODji5&>nQJ(s'.-V03#+@Y'a"t+RYu3m=l*k<l<E1%h.'[p+d85\R=6S,m>tftHgo]k>>qE2B+J*t%
|
|
||||||
N(UAKk0t:"#,F:9bU-NH/'C%\Yo_1.Vj:qbhW($$#FBF6Q_.Zn1TGNd]T&OkS`a7`;0\7=]K(!BY:G5d
|
|
||||||
1P$L?s7UQb.Fj<"rH(oun*jS2ospMPe""cpS]J2Y2t)7J/RZogC6-r2r2`jB=6e'.U'l.1%8G@WW]/pO
|
|
||||||
A&g"GK/_,uUSURmofS.2H8m-YI2JJc4/oYfR/uJ88T.TkX,s*U>'QFOTF6cOK>L#=5<#L4f?G<M?fYWW
|
|
||||||
bpC<W\tm*Fh2!m8.hXD]E3c(b;C80^&cN3,><m@tI;V[8e6-tB>HQne%#kQSn4h/%p@I6q>akGbSU5bK
|
|
||||||
(S(,'NE*\hZhSb&3&eEp<&M:-5p"p;EA=nqc+18F^,<P]"\630B2Et2U>D,o@`DPkE;a6`KX[P:rWPh-
|
|
||||||
,nb54X]?FBEYp+9Fs2lE##bD<a"a0VW&6eYS?)>W5B.JeS]ZdiLM'IcWk+ErWUoCQ2C=tqC`%/Me)uH`
|
|
||||||
\MV5C[t].klBPup:a$hql.TAMfVqB5;,QAF?d<mqFVu2UIUbTXQ$MXNi=7lg"Z_ip\99b.a_SG/Rbc7H
|
|
||||||
NMoVDr;,hfdDBohGCN\Kg<EDkUU>BD3kSTuUEI:O)IrWob4E40OcZ;U(LI$f+?u7#piel6eUJ<F"/8:J
|
|
||||||
X6KTUPFOfT[;)L`;fmiaMG!`qTI>,.dhd)b4nd@h>?*Y-8$V`T<6X^(C0Br(&a6%"SRgXXDe7P'ABCiV
|
|
||||||
rV;?MJe>3G@EOa]jiCK0EFnbjo_AFl2=$C/pIrN9Z=[.TY(?e>9T!*N>b$Hd*Te`/cABhQS9*[.=<i&?
|
|
||||||
YQEG;N$M38jq/;__/iFD>aWp:r[$T*rS0ndablLHkB?<;:Q+qd.St%gg:W?P(355]ALVWd=b70YS1S*g
|
|
||||||
fc#3;YMtr0(S%1`^7tTg51/SMHBKfqA&Eg;NK2(G0`cC9&W.gl+en)I'dJDc^5PO*PTa*7:rK5\rc"*5
|
|
||||||
P\*-O&W$6-U<sR*aEb-,OXM9.F<a?U_N)t'RU;7L]4XNTd\84DDe4<A9+d$FBQW0?6'GOjXZcq80g?A3
|
|
||||||
%N1.P8aXQ4#G][t`5P\nA@W6r)BD?\g6+Bb)q!l0n`8o/Kjh$U2chh^o^7s$'hL0nU<t"QWG(q3(pD1t
|
|
||||||
oUXp2DrKPdltH#sS!`_iI+"eb4-#&idA%G;,$<MbBW5[[o-E\Z[At!SDN%&/FK8J7bf`L4"Q./-W\<Gr
|
|
||||||
)V5a%[+#=8`em6Hq9?:-Se4t4_tGPu4aNl:oT1fBfN9a`@VkYVM+1jqHu;$CHnFq6U^K1$h3cURCRN_H
|
|
||||||
kq-.?N^fkkNgh+sF4@1\n(,#:h,Pr5N#Cs<dHK-gN(3OmAMj7aIShpO]_$f87a\/]<X_F#iZ,O'X\#$F
|
|
||||||
QPi]?aYU,J479DuLr)hTT0F=U9Diq:U3D..$g-o5Zj]_-8Zi3GfZ*&sFC;7b[VBJlVtr@%%\1C7hg[AD
|
|
||||||
oJr5,gMpZEU[<L#Mc$nR0'?N.X8XD@l?#KA*h?&RFF#!s(\:$^>,NUSf;6rAHc8$/GAHu/&WNLC58eO1
|
|
||||||
YL4a,&"V##1e4J8^6`tg`]$-JoK^i1JMFq39BgQTj>o=b\"CJj!jmt_7ujoKV_c3^k]HK'3ZU6Y6Ds"?
|
|
||||||
odlZZ3)Jr5N,pH6oK\_cO=n_&mPm6Jq;*`pc3RS=FP\ORf&f]/UXRaTQSrXSFJKU3S[ZJq\^Z^H;CXma
|
|
||||||
L69:"kttYG>u-VYf6]i^Wtj(IC8&"XI_;0#>Q-8Bh@2:PgTApN([6$P'+CEQaNl_u:=h.qdi"?rFLpVD
|
|
||||||
F?9!LKhduI6`6tDU!jlHD4N;i8>l&;S9;DQCT5d6P')'nl1Dbt)r--Fr3G3@?2#\BdriiR7Ji]]VmCSE
|
|
||||||
g9(BfXS\dAW>2M`WA1L'DuE"nY+&eXg'\l]Wg$SU7;OS:fDc:=T2l0Q=[=sol-L^8*+LV8<,>S$07t5.
|
|
||||||
m&LdoUX.C!EU<pQp.O#PEU`VYc*W\=/kXQ=QScXdN<5M"flbdU93,EAY1rFE,O=K1pt>h97c5#>>OQ\s
|
|
||||||
\0oe%))o\/+tRWFmpA2l@=$su0iouK9l2/F:D3EEh)ACodeO0E1;)c&dk\Su;9HS7\P<h,L`Kc!VFCfZ
|
|
||||||
VQ<u0+a7=5m0-Nq:t,^$?9AO!HU-)Fi]em@\oP'pQQ<4%LX%LMk[6qJ1LP"&dZ-LYh59%;bY-Kh^_Tr7
|
|
||||||
'!5YPE:(Ut1Sa[:>^<h#%Jmi*^/oFpYBF0=5LWUn[4]9<;jK8`o-2)r2J@hUZGPDg7r*R"Dl&^BSB9p/
|
|
||||||
2j%kd8/u:6Q8lDJd,rMMA.O]#;5ga*C("R:1D@a8(\8>GHSL:3D)e4%]tN.'28Xc/][kt">-caF*O9',
|
|
||||||
-HNA+3q+(aft"=rlhoFse<)?N)O%a]c;TU:IH%X=U*eqA`RMa@hK!F8r<%P(;"6X`%W-nnBYS'I4pU5@
|
|
||||||
XjiN0Kj>?PeUmN5\=XA6b?!S4Dl8_.;.&D(;HQqaG#\1JY=!.,^BsAuOn\Z<]L?EPrMfaV3;P#(rpb_H
|
|
||||||
o^B%ZXSt@$?2!`lSVnOGcVO5;]p-O)a<7K9r@=$Uqc@<JZ@>@3?[:IEC-FQ1_+3hW0&.C(=!H?2>`$i`
|
|
||||||
jpk%l=qe9D:ZP+NC=iB#a)3lBMUG"s@/mk$X7S19[.-d-d'+jD?#P3-Ii]K.`)IKiXb)CgfZ[m/s,R+6
|
|
||||||
VQubnA&l*fW[Q+\`r%`>a*][blu*aHht<E!kJ=VG1Ond142TE>=NCS`nY%*"7X`8Q`4et6aL_V$h&(u8
|
|
||||||
YPJF#ZNoCV-\k5_bV)NgIi\IVp),hMa:=CIGlJkkj=KBq+I^N!_"dZD08tbc[,g9#BV2;nSS)RZGfD1$
|
|
||||||
TXK)nOR8a)ouHkh$9<[!M#`Ci^f7`P'":TnanG"d\:5OF<CXQElXETk[7FF9e6^sKNo!1g,hhGYQG_^q
|
|
||||||
acI-q@7#R(m?N&8Dp0+a\l""3`;AkfT!ZP!o%m`oS.?:8i+s:TEq*\H"X*uf`L!uZVjTE,f4(+?#T4o=
|
|
||||||
h,f&RhcrLlb-K)i<r50+mprl@_V>ho>XIa=='S(:lSOq>ns(K,#=75>E!\ND`@9VMVHRQ.$Hcd5oo324
|
|
||||||
%:=i9_:H]XJ'W%\T+I?=")\"?K:>Ud1GdV[!Dd;?M#+<"9'72,^DJ5F@r$mu0LJT>N'0KSJRTBHj:MqA
|
|
||||||
bTeg#5#48)"4T;4oi+.0Y?M>qT>@:M"r#n9*di\;7(*3B9&qH>33R1W@k'<\F#%_,IrI5na->J\s,&!"
|
|
||||||
YIF&s;D+*Gr/4q?,oZLkKMTosruJ`NogJ=J`=p3H't?dF]Dal%?E1sTiPFZbo7sEBmIn@m0G\O//M._o
|
|
||||||
j<a%4@WU;&#Fep?Y:jCi\]$5<jqc9Kc7jbK2p\?pIX,WY\(>DCJ(AP9FBQ67Qi(;7ceTNtB>@&KjDQ,L
|
|
||||||
Esq(&)L$5tka2(/*mY9U/FO..m/=I8$/Y7O]Ho0g2uG^lL=gEZTj)2T*R1>f"6;FG4B)pm=eOu0n759$
|
|
||||||
rGn#^?dH+ooiC<S[7E>Zm0sN;%nPt7-#9`7M,[."kX^L!XneYCA;cCUL'Y+ErZupVk?8@AX/)O,8V/Vd
|
|
||||||
Ac;r*5I@S0<Pl#d$8,fcL0Hmq&VRW"0#P:^R&5+tBA4<b>8eQ20G]f*[XmsYnZ%*NH)O1mV9DB8In@)L
|
|
||||||
V30n]a_93GAs6n9WR>[,cnoZAbBJGTnF7G-4lkM8A)'m)m[T!%Dj)6t*6/6)N%ks+qU?sBn4f$ZaE+t7
|
|
||||||
?$gMN7tiAQNM##6jcQ2U+4d!K3r#R/U(:>r4hg($ceR8I&m'OZVCKfu5do[lN#(EjDrf!>mF<,V\0,0p
|
|
||||||
YZB?rbQX(q>CM/7^\:Z14JJ>2XCgb&aIn(AEEK$\5bRWNPC=XW\JcuH1P44P)']C`7[&"+F`n&FQ-:N$
|
|
||||||
*m4bu$RhuOe[J/A4Y<#Eh_*%T/KDt75n:F`%\J74qVCn'.MC>"[>iB=JKu%?gm^cV!bib!HZ9#JXcc;*
|
|
||||||
G1hc-!=ebPh4'fb>u\p2InJ/q4&X`T_TJt0J?gD71:M0HP'net-<n4.bI21"2s,lDgh#t#NQ)TL^#`$/
|
|
||||||
6nN55=WIi^l=0)pjBk\sV7`/XQM/")8M600A"Jb$]"&n>rS532n5+rkZThtEH4RD]di^[UfcR_^9[;S<
|
|
||||||
))02Zmku"WN&aR-Z)o7;JZn='=_Ba]-r\e(SbY7Hm4\*&1EN&VjR1[s7rRA_Uh6^hUMOJVYKZMcSPmR-
|
|
||||||
(E03'0]=LPdRVMKP0"$ASB4,K*G^[!MR:ijIea6%jml/&Rh!'#n6qYV_If!Y_bpFe]__hU-SM:cf^*WL
|
|
||||||
KI?\?YW.S"gKX[kNr!]rhf25nF&i5\n)phKqS!o@qJ>U^Nq[BC2L`GFb2+1a0oC!`kl4^HJTkGP/Et#<
|
|
||||||
!B4<d,"l0Qjs<'9Z]GJMJP^/<9t(Zoe@(ol8f,nL4PU?ckZ(Z$@X]:I9^b!0.WQc<c[c@?[mAec_E,r/
|
|
||||||
4?25Co3gr54d8b%H\BW/[SMWU+j1lY>AmIkgRjIK?/@8:l$(G]J%<,O1A=kI+8pj$';bE)!9X9^98W*Q
|
|
||||||
)(?IL0Q[MJ8[L9EoBL[/SST,[NW+pBZV;LgD7%?\5UQrEDu4M&V)[8M0+2=#Nu;##hXQ2GH/hJA:UX]p
|
|
||||||
^ODS8TAhZ^8*k"Hp(B?[rh,5?X?i.<nD<-G_\,b\S!e.9`t.l>4_R<Lp^Ttuks!==pra)Q5KU.KP"atA
|
|
||||||
'BHTXG0XUDBP1ItBg:4jA=Fs[$?.4b@rNY&N`ZN.j3W)Q>?YGpk;V8NYk>hF0oI[3Sd0P>Y40=V<R0Ys
|
|
||||||
[.A8b`!<O7jK,O&D(+'nCs%Wqq'LI7V.Af]Waso$=YC/p:eKes`:4OBHb*'ph-MXfY;\J(V(d0Q;dU.Z
|
|
||||||
IDdN2Nq:)P.imuB49\93pd%.>XOPbCb4R4_auRd_*F@FLr0@41jh:&7K@$UqA5!O3#r]puND[f?R5HJS
|
|
||||||
7cdM'r'3Sq(@"ih_C<fAi'^n/=2FZHhBfI4;O30pPW9Vp8/bf&@,,OI_2[K^aWc!"'WdX+A)i]A-J)@h
|
|
||||||
GZ/HlNU\dS=^r_b32J]aD2F)_/tAN.rb1[s:5_mdPXO\<SP*u)IMf?'J")6!a.lKNFY]KT%g;d0XP3jq
|
|
||||||
j(n*Qc_\YdpX@K0Lbst:L!O*6Qch<ke.Gr6(!!9mK?5fMmr!urb[[q5P&=^Ekhe2.nnFA<Sa`'#XT+<U
|
|
||||||
rdTI%ERV9G,cU]a0G;?fC%H%g^n"i7UAc\"F-F&Q_%4'n[s+Ql>^7*M5Vl#EcHcAiba_?'Y;e\GdAP_B
|
|
||||||
r,luN<:p>(C,02dHr:&s'9[Wg;C6V(`nL6t2QEGmpV(p7p`lV>U]1j+_)i/&WW"u6r4h+."!bb>3dVY'
|
|
||||||
rT.5XoP>Tt[,3Q<GeGia!B)F_(UqthK8^OsRCh9[NohCbD3/N\r0@GEC8"qBHKCh7=dAO#%eQuap-^F<
|
|
||||||
&Wd34,M<`7U.1>ZDo,S*kh!N,1["`6GML/sgu(E4Y2J%U=tAJ@fa'\>6go<pWN[*8Y9;TH3c9oqc)\Lb
|
|
||||||
CXm@62Qi77\%Y#T!)W3X(c,;7IJ7(n8fm#Znbjua_i1<AdrIA,..L^g?,ioENa;Z<(eE,mI>m%VZMeq8
|
|
||||||
j*XK-[nf'1b(.S2]iF/`XK,P>$XZiE11Vq`C$iJF\%:E`L:ag3(G&0u(S]s"iZ$j*8Un[oZ>g,h/Z7.K
|
|
||||||
#(3`d[&WGTIs4CYDoB9l<G2@q;0SFh(N$*]+(%De,AZ+Q^Goi3jW_G25tQ1idfM*Vq5XD'MpZ6LDq'=,
|
|
||||||
'g.T>64@I5TFA!ZoYatX3t9D3h!nLVQSQX6ZJ48r,k$&EU?<aUV[K=XP4NrMh"HTf,*lU^8ZCu8Q6C3S
|
|
||||||
o_a5pH-PlT9KqBS7Uq8c`:@9J3?'PDCB#tKnrH](l0<@Z2O]$:]]@M++(jJ$7u*pg+[:0aCiGRk&8k!O
|
|
||||||
N6Q_3pIsH>>e$uiVoAr.mDHF#1n06kC2"1,`4&tUDZNbQS4f"oB.\![Y,WAq0(eXdk/(+ol<%%bDrI/6
|
|
||||||
>qsI9n]%$K*+DBI^,Oq6;=@cTQ&XOBP"VS<$a$(kAC;<SGBn-*_Dla//T`\[JFg9aAWb.p929"[c70LV
|
|
||||||
$R98NnH*BCI+!ugW@ps.e\>AZ(<oMB`Fc55nFSCM896@=&g:jrAm@un<m@OFLq6[*-i:Kb]#>_[\ao>X
|
|
||||||
,ME]M@R/8sE[&s?H#'C3IK^e/=ir,)j1YRrXa3U8>+>0Ja%3d?P8Vb'Gg@lrg-s^?1U\QNMaf-fPF`.o
|
|
||||||
;oX3D,\FToQIe7]b0h,HDD.!B+smBSne&Y/bePg<4r>:\j4hNnbZWH;fZD2fU.`qM?T/`:@,Ri#i\&jK
|
|
||||||
)<&W+I>g>B44iJ]<pXRh0kR]R8T%C_Ln+FeFCtA:*uOt8*_7pBUq0r=A:..%JtY8?-&_Rb[@>g$PMTJ"
|
|
||||||
G&1*;2P$,(R<_W0?DAQ&_0pI*[0ufMX]rI7*_EJiHb*Gp^#lSA!cGekILgI+#ii!%N0q^JjTRl[?0h@U
|
|
||||||
GhQaB?KBQ]?[+M&q=K[^I*0XEM)iBK$:aN&oQf/qbaCbEXha&+d+s^"40/.ZRH)3_en"/<4,3L>q-cbV
|
|
||||||
^<";E1sJ4LCmV^4?*j/<L]!;mp+%b-/A9(km)R]#M0u#0fl'pi$SO8q7f@P$bZ@AR='BmaLcMLj7c]-n
|
|
||||||
\fu32ItEN'H2Pj@q,4nho!0gbcTAOt=??+UE;F*L]4QK![0thD`n9oYN?^:()FJnlQ4Y>0pmSPLD,(Vd
|
|
||||||
fi!bha(ICaCZ5epHBu*lfdHAYF,k!@8iEqhH11b_eNX!Q.0nV[_;cb0Ui?f8dT^a8h&&A9kbD9M?c(I0
|
|
||||||
7-8?"q%C5?6Y*RB2SPP#9=+%k/.M(,)qiAr?7f1EA81ShW'nn47,=S?<]`sNgXD.hY6i[En1X$!Cd`4#
|
|
||||||
M7T3-?-hVM1`"h=$Vh,_`V*UNpT/5L4=D$!!K!c2<W\\<j\'DXqZ9qEOQH%(P<G*'#[_&9Eqp?[%HD-!
|
|
||||||
@S6baHDtP,='sQZ>3pI/OAbidS!3YsDO*0,`TOld?U?`"$.Y=pTdRsKn7^I@Yl(=LUP7s/rNb?R]e-a'
|
|
||||||
.qpl8p2PHda[Np3B&nSlM1b`]VQh"b'!2shFI&dE8ToaK2gpNfCaZ)-g)IbZB9Rb#NEH`D>fh]66tUgD
|
|
||||||
p_'"B8+Ir]j+ZPe(tk68\EZXjn,=Wbgsb\66smfH:-,7OBf]rNp!0C?fjskqH>[VlZgo^!7q`>-&hj+C
|
|
||||||
dk')*VdpD27R$@o_)3q$q2QQO$0:jZ%<nE*7K%EM(3U_%(3^e&(-1.HNnn%2D76cDJ8_bb)cUgU[TAB]
|
|
||||||
fcN/uZ4-<b)qNa_k?Im1>pNIUqIY$'okJc[lohWLrT0lgh7lI@f(!P&UFFO!M4^!<pCWT<b7:j+I3<RK
|
|
||||||
gAcoB(MMUJY4hTn^\tiXYQ';^f)?`d6`gmQI+9!L^6C87,j`r2r"hQk8NJSV(4)-='3[RJ1jNL)HafN`
|
|
||||||
m$T./c0csoNZFb]NmI'-lh:),])=ecj7mt<WpUS=l8_Mknp0d"3(d'%Xl762HM`&Ych.!Z@Q1^V/S'A"
|
|
||||||
i%2=lFNoaslRU;.B#rlY/<[b<hU^`U8M8uj"dZn>3,+m8J$7PRPhp:d?&I>.o&qp:__,Zp[T^a7CHGKO
|
|
||||||
1Angtbl_0;]p6@a]'@Gq'+TMqft)sRf6fVX%NG!/pR85.,hCdm"shLMUUScTPs2V\iU;oboD!Z.^AU4>
|
|
||||||
>YF^Xa'ql9[LB,KSrf]a4P;rX-@%MOomp6^Zb!'HNBNi'B%QG,[?=WBeUtc@fpd+Y00XMJh<M\Y*7XdA
|
|
||||||
g\@j4Ec&PNNit&g\;:0KDh=OQ2Cd\h=D-u0\]dQ!h,;jbh/0?"DL>=tHRZ(6mF[RVZWi@P[!1c3BoB@1
|
|
||||||
mX3D5j41>]]\_/Q^dY>lrUfQ`DlWe"_gWZ:34lg:p;!Sd.l_qF;Yq!kTD]#hYM"+5O775`Y=X)NJ*hA[
|
|
||||||
I-R?ng!u\f7i]7te'q.Z$BIt`iO_8!X7$gPK(&F*gNS#@RBMsY>\Xc^1S<Ht]uAha]kSHU1q_buod7,p
|
|
||||||
m_*(uD6tE]AI3P+r&[(Tkl#Y]n((>@Z`Om7Y!,()/_o/41&(Fh;2qE=^Y7j2F$e]*9;L=OKuH+>Oim8;
|
|
||||||
5!srD).ED+#7Yaq$d2`*04j>4\LU_:ne[GZi>-\tm.9S'lJ&sDLX$0;$TX_?\9fUB%c.(H-O[n<B`ri*
|
|
||||||
g,+E)?aJ==fjV>o*^n89\>6'geE?A"J\mmW&"]iOhu*8mDYpuS#OQhQpV+6L$Af.S=i\9V/eC0iH.USN
|
|
||||||
QY)FW?;cdV*d`m[M:,s"0c`9EkPBjWWH;Dt]&ZM@&+1"^H'/C1Rd@[M_rp@6,,;ecdJN[Fn7J!C/VW).
|
|
||||||
N<cTUdJo$AiB6ID^hXoW*jG;'(p^fIPK)+r_i?iAB,MGrnafX.Rr->WHM5P2T'>sdVn]%C8"\IOi!Q"J
|
|
||||||
GK<UTg4S`THI>@F;P*kcTKRJNH:;_+BHbsS\CMYb:@5eI*BmO.l=W;.;ML]7fGTE\::+^0/"ZGKiD)$m
|
|
||||||
^U-20NFm#@>-3:-oVJJ*I'lVebF..EoD2&sC/;TU8DP:BUshrHk7?aS7]/<q7pu*ce)(b7hFjDV*U$GQ
|
|
||||||
//b)KpSS8E$KJF7d9D%$-q*Ih2BeKD[ed4(%6IHD\jp\BpA]\DqJSUlIGF;kVO)f3prC]fhpqc(a$9Oo
|
|
||||||
f73b>^ZUZms6l$@VeZ8'pHS]V?iT6fpTE+4"O=(<bknC"k9$q.mCrSSO8o,qn_\fn*uFoMFo~>
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
40412
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/Parent null
|
|
||||||
/Type /Pages
|
|
||||||
/MediaBox [0.0000 0.0000 537.00 194.00]
|
|
||||||
/Resources 8 0 R
|
|
||||||
/Kids [5 0 R]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
[/PDF /Text /ImageC]
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
/I true
|
|
||||||
/K false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
11 0 obj
|
|
||||||
<<
|
|
||||||
/Alpha1
|
|
||||||
<<
|
|
||||||
/ca 1.0000
|
|
||||||
/CA 1.0000
|
|
||||||
/BM /Normal
|
|
||||||
/AIS false
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/ProcSet 9 0 R
|
|
||||||
/ExtGState 11 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 12
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000315 00000 n
|
|
||||||
0000041155 00000 n
|
|
||||||
0000000445 00000 n
|
|
||||||
0000000521 00000 n
|
|
||||||
0000000609 00000 n
|
|
||||||
0000041131 00000 n
|
|
||||||
0000041609 00000 n
|
|
||||||
0000041325 00000 n
|
|
||||||
0000041364 00000 n
|
|
||||||
0000041466 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/Size 12
|
|
||||||
/Root 2 0 R
|
|
||||||
/Info 1 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
41682
|
|
||||||
%%EOF
|
|
@ -1,348 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
|
||||||
<!--Created by yEd 3.23.2-->
|
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
|
||||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
|
||||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
|
||||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
|
||||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
|
||||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
|
||||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
|
||||||
<graph edgedefault="directed" id="G">
|
|
||||||
<data key="d0"/>
|
|
||||||
<node id="n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="174.17919999999998" width="273.1071999999999" x="1000.3040000000003" y="433.61760000000004"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.380859375" x="12.06892812499973" xml:space="preserve" y="7.247609374999911">Static Pool<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.45580882479480683" nodeRatioY="-0.45838992615076934" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n1">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="120.0" x="1141.8400000000001" y="536.1087687499992"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.142578125" x="16.9287109375" xml:space="preserve" y="21.015625">1 x 128 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="90.0" width="164.73919999999998" x="1113.1776" y="451.01919999999996"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="164.73919999999998" x="0.0" xml:space="preserve" y="0.0">Group 1</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="15" leftF="14.739199999999983" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n2:">
|
|
||||||
<node id="n2::n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="60.0" x="1142.9168" y="466.01919999999996"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n2::n1">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="120.0" x="1142.9168" y="466.01919999999996"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="20.746093749999773" xml:space="preserve" y="4.862813159989173">2 x 64 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="-1.1657341758564144E-15" nodeRatioY="-0.41895311400018" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n3" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="89.99999999999994" width="150.0" x="997.9168" y="451.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="150.0" x="0.0" xml:space="preserve" y="0.0">Group 2</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="997.9168" y="451.01919999999996"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n3:">
|
|
||||||
<node id="n3::n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="20.0" x="1092.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3::n1">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="20.0" x="1072.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3::n2">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="20.0" x="1032.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3::n3">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="20.0" x="1052.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3::n4">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="20.0" x="1012.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n3::n5">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="120.0" x="1012.9168" y="466.0192"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="14.814773932043863" xml:space="preserve" y="4.392671444094276">6 x 16 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="-0.04942766514963404" nodeRatioY="-0.426788809265095" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
<node id="n4" yfiles.foldertype="group">
|
|
||||||
<data key="d4" xml:space="preserve"/>
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ProxyAutoBoundsNode>
|
|
||||||
<y:Realizers active="0">
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="90.0" width="150.0" x="997.9168" y="521.1759843749999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="150.0" x="0.0" xml:space="preserve" y="0.0">Group 3</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
<y:GroupNode>
|
|
||||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
|
||||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
|
|
||||||
<y:Shape type="roundrectangle"/>
|
|
||||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
|
||||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
|
||||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
|
||||||
</y:GroupNode>
|
|
||||||
</y:Realizers>
|
|
||||||
</y:ProxyAutoBoundsNode>
|
|
||||||
</data>
|
|
||||||
<graph edgedefault="directed" id="n4:">
|
|
||||||
<node id="n4::n0">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="30.0" x="1012.9168" y="536.1759843749999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n1">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="30.0" x="1042.9168" y="536.1759843749999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n2">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="30.0" x="1072.9168" y="536.1759843749999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
|
|
||||||
<y:LabelModel>
|
|
||||||
<y:SmartNodeLabelModel distance="4.0"/>
|
|
||||||
</y:LabelModel>
|
|
||||||
<y:ModelParameter>
|
|
||||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
|
||||||
</y:ModelParameter>
|
|
||||||
</y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
<node id="n4::n3">
|
|
||||||
<data key="d5"/>
|
|
||||||
<data key="d6">
|
|
||||||
<y:ShapeNode>
|
|
||||||
<y:Geometry height="60.0" width="120.0" x="1012.9168" y="536.1759843749999"/>
|
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="20.74609375" xml:space="preserve" y="4.392671444094276">4 x 32 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="-0.426788809265095" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
|
||||||
<y:Shape type="rectangle"/>
|
|
||||||
</y:ShapeNode>
|
|
||||||
</data>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
</node>
|
|
||||||
</graph>
|
|
||||||
<data key="d7">
|
|
||||||
<y:Resources/>
|
|
||||||
</data>
|
|
||||||
</graphml>
|
|
@ -1,256 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%âãÏÓ
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/Title ()
|
|
||||||
/Author ()
|
|
||||||
/Subject ()
|
|
||||||
/Keywords ()
|
|
||||||
/Creator (yExport 1.5)
|
|
||||||
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
|
|
||||||
/CreationDate (D:20240209121153+01'00')
|
|
||||||
/ModDate (D:20240209121153+01'00')
|
|
||||||
/Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/Type /Catalog
|
|
||||||
/Pages 3 0 R
|
|
||||||
/ViewerPreferences 4 0 R
|
|
||||||
/OpenAction [5 0 R /Fit]
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/FitWindow true
|
|
||||||
/CenterWindow false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Parent 3 0 R
|
|
||||||
/Type /Page
|
|
||||||
/Contents 6 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Length 7 0 R
|
|
||||||
/Filter [/ASCII85Decode /FlateDecode]
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GauHqbDmmZP2*dc,(JOJpjOd%&g0Gt.kP?b'JS!oXFWk;Im?:Y1FF&?pX/3EoVU?*%+=B^)P-:.4Rk3i
|
|
||||||
s*]RdT>/;PVD+YBq=ssLgOFU_/,o=0j/OX---Sln^>2M2IJ`Ed^O5qQVk*kY?bcTdDuVnJr7h6u29C@I
|
|
||||||
n%\htn,N!oI>bl:q_/!E8Gq*>`4k*>H&qp+S%%gYRm0Q`^J"46=8=LjUt,*E8fY3j6_j1O)dT5Rr;36d
|
|
||||||
2TE1sT$toUNk^6OlG0M$`a8\SDu][+2SjlHja.QR?iT*F^H[dDqOmlY/O#F`o1-puZR+(6c_!Z$9=T]T
|
|
||||||
3D@>hs2JCF[r;<4>?W&7(f(C?hGX+g+/RK5>#oRYmB]N&/Qlj"0&n123'X=7S:R]6PWlg21E6kE\4XeS
|
|
||||||
2\[`<&[K"H.d`i7:KfE2]p+JeNVt]"Y*L+:.OV5^Fb.U2n0a"#gC<8<]$kU=OJ*NV)=du!KCWJ.>a@;!
|
|
||||||
AiTJpf@'RJ7&^"(Yb"R/l(pRdQ<kVs%?8)!>V`PO_WeB@PWi!pB+5<W>7FTapG>"eLU:Cra(k/I;;^ah
|
|
||||||
mAcX]0+W@c9"cLWCDs?VR[8F?6<T5F$_0C:m"MjrBBs4k)B)!QUC!)4)NFtV@<<r9q$g4$m3nNi+q1ru
|
|
||||||
e]XeUoN<]O"rT6rdIc7nDrJXG_<,>V[+c%pmXpGV6L..t<8>20'_?%rO!N4JZZYrn7!-+)W,<%t>ADS8
|
|
||||||
/TJAA+-bJKcYS>'s3KC9l,0-0e5A(AITl4'3i\f46/U?l%$3[[D1-a[<d(=Q9X^_,!@p9Wm'TZV7GRh3
|
|
||||||
rF)Ja082PP(B7n)^+A+X7k=ASO*kK6,(6!VRDEO4*]U9%D@#R--_LN6iTq@`,YPD6Cs8m4P?AfT*5Gd]
|
|
||||||
[;(LpJ)b!=C%su^D3#$WIOeISK@*am.##XK[S[_P!?^>tVXHJ#qEt3?2Z8-=k2XWs.,17T[bN*>#j]U:
|
|
||||||
+n?FH0^6*EPO[^;:H%KH3f("]U9q;P]"j#B)eLQ$@1?"IBSn;YH6lYb&!tVhGK>8uB'7MF"FFfqpVGc<
|
|
||||||
e,4hFfV*0s,_P,I_QT;tW4=HSW2;Ra[gBM4`qdES$e#Amc(@.hj5@*2oF=>L%a<mBs"l[h;q(=d*OOlf
|
|
||||||
J\oOmWJbKB>#1\>#4"cMCBF9nZ_8(\3B3X.Ce5M5@GC=9ETuK##3d.F9`om9\CVZIc4fpE_>+:BO<J]+
|
|
||||||
H82#q>$E&a":@K&ZD/,"^/e9SMaTXR4:emV+:YVSV']$J([5Yf)XL^]K57i74uu_XpD*@olnX,&NO9ib
|
|
||||||
%&N3p%WmY0V3,o$NEeT^eb<HbXfh=$96H48FpusINN>n+-?`G3[[)E[c:Y2<_OIriakDd!l>kAQ%VsAW
|
|
||||||
AF8/If$8]MoQQX3ZSq>[Jc02#p-mQXojmFp?*'U_akBmhOKg^+ZZ`MGY*I2DWtB:KeU(@CG1Jk?KA87s
|
|
||||||
@g/HOM6nM9!.9`I^:<BOrbA\h]cSgEhCU?uURj'M1$c"YMq5^c](iQ#&>PQ:2.[P-3c2mhE-_@`J$@fS
|
|
||||||
Dr,`=a@ib&>8l*Uf6+&oBsDgN[$uSRWT9WU_`,ciCF7Mt7gQI=T/'sF(R6T"e#_EoZA5)r#HW*R#p`]_
|
|
||||||
;dE]PNpSIdg\6CCQ:6F]TV^1VrK%68^6q0\hmJKElGB.7UIAF+V^67qcJ?fV+8sR\g-Z3Xb'QW-m65AL
|
|
||||||
UC0S+Tsnq;>ibcqXPfO;:H"8jre($\6sJQ*T!GIQ4Lo&VH+V6@U@aE[<9K#^&i7s"S2Z@R:6kOt\?n?\
|
|
||||||
@p8!#:7CKqp!R+&Y]'#C/Qrg2RMVR=@b(t;`foH&?N-VC;Id]"Fln?G<NR>6`9P]52`L$-C<*l4plc.7
|
|
||||||
4LX"1<nT+Nr-?OgD0lC<YFkX:nR7D1lb[?95#;tmOVMWgf]B,h03"K'->^:DU1!shS^`%C-di:.>MJtE
|
|
||||||
:J,\\RN)(L`nK=]CO(1Tdmj\N7gT%#Z1B<^qo0QCdR_QNI?m?+,6&K!4fE0TH4Xp3/#bs@]Ql.@WaMU]
|
|
||||||
BWhp*L`O1NZaDS.g0J?#[[Y>lJYclJ%HQ=qZk].NXgD>/K%5%V<?,\IWJYSHNOHKbeii>SSm3KlhX7(m
|
|
||||||
EbVLh^[9V@pJ^+\Sq#\_][o)h+u?TPo1!NNWGPYC7aKRZ"@1!5)Z=%7Gk!')(2ETj@O9&6$UQ$l,W5#f
|
|
||||||
q92>l8u!Ra*@P4\#";duf5O:q?Lihd1dQS3oQCa.7'^3sQ.A<TLOPreY-r$(%o1.R9pT8^-Ke$C46_+/
|
|
||||||
`eS`mioh5qd#K&0oVC7:jQWId]7!m5,a&m0hQ=c"ZPH0.p1eq0dAGT7:YaXJ`ZN^=N>&k8^p7rB.2tdh
|
|
||||||
>7]pt>(T/ji2PU,j)3m?DJX0V4aUHr-0P!<FjKu<`jQ5I@g'TS-p/qo6bA*h`@>K!O@WDGNGfcFPDn8k
|
|
||||||
]N0g#G1lYPO'jlnkp+!`qs>Ub7G_G4*lj8smU>/I@Y3o^Fm>;3YP,@#_qiT]gE-XV3s"W]nb.b<p$Y?3
|
|
||||||
@p.AZZUN>E*hXPKbOH9YrARdUTCN!PnOqQaZPu(cT3X,pj&";:8,VKDOZqD-aSKhVqQ$O/25[h-T_6]R
|
|
||||||
C9+8bQB+N5[;fCAnV-i0_0PGC#EHtl39hG%(g^PAcZlqVGQ/U8AnYrZ(Kg>P)i3=ig+4SW7aRXtd$=q8
|
|
||||||
L%9Mtd$HV]Gf>!pdr-2@flXdJ(LPtj,MLHnp\kW*SaaSJb1esgYlB$7O7W0)U9S1#a./*sn^kcE-_o=M
|
|
||||||
I85TH:.DQ2I1D!Gi$Z1lT>/6I"-Du2cT.EHFQ=[6rO+36h85+pMnhi2=P5BW=f<9C4LP$c2Li\nn2Q>/
|
|
||||||
86aIt6etQI5`[KZ2E3b_<i]HljKMH:KY"CGZ?V7LVr;CX2Oif;5.FFdQb;jG+0?nF_p@U)<,M@*<4`aK
|
|
||||||
)7I3"r9K'"d!+\r*2V?N3SEg7/[&sW'\#jaP1H<_Znr1[lc&EWhnM0pMdc"_:8QfFUdiam=V#(qFVU2P
|
|
||||||
6`9k0jf`3QgpUO`EW+<g2@2a5%OX_YV6HW:\If`r(V&+CI%c@aW^)q#c$?X%X2Vsm_OT3YPd"2`l=-kK
|
|
||||||
Das:U\!CiR(YnG5i>#L&.%$!kq/NSCgUU3Qm"p'\LpGKB":LoFAXH@$VMV`V[,8V9DI=TQ(@-18S2PtA
|
|
||||||
#p\$$-S5\jm7qD$]fs/,rG[Ujfqmc#bj$fCNm`;<$&dI'B'Vt#8i)P7BZhobd:A7W,=T4K&,FXOip8p_
|
|
||||||
LOQ5mBJ%hI`BU#`MR>[AM[6@;S!hjY_\ScsBmPt3:"*f2Dc=`_qlG]U3EaMtbh9gYQsI$c->q1Tn^X<F
|
|
||||||
B^MnK`hQN!$r;KHG.U1*@D,\s_idP]nd8k`lcJB9?)Sci:d<<iAk*aB)c^K%1m?I&*P6=t5d9rE,m2_$
|
|
||||||
Vn<<o:eoNRDDM*'FcQa361sg4]H*<Ip6FiBg6!taI_2m/9DeN52='`Z"+09,F'QE1KsjB9h>)I?UQK\,
|
|
||||||
Up?#_#P95ma[2];_gZ=I=Nu-1=+4sjm!L#3.`Ou)]!rM#B/5^beuNBCAU0$?f$DdgQ%CA>f7M`$=0J%X
|
|
||||||
#SL]o3a$TV'C?#TA`hfd@]!MG@]tt`ak0Va-H6NQ)O3e[.sNgi/Z@KEodYrunT!k2)NU0T2=8&]Z&LD;
|
|
||||||
c<'Bue0<IHeB2N,>,\p9X>jgXQ&<0+A6*E=(@bpE05=Rp<EU4MM1k"f?*Z'J_JWF3?gT?.Y$SZE.+,Ca
|
|
||||||
JE/T+Z'D)kYS9lJ]rKo2IL8aF+jlujC-9UUnMXTFe4`&:/f&qAB\Iin/1n_3=D--;0Ul[BZ1&_8Db2^b
|
|
||||||
['Hk%40co"!"29l%*"Z3rktlWD2eW.9%^ZRNk3u9Ma+0YGen(&OLn,.GrJr91gVYDbJj.Yg8UN4YgmP3
|
|
||||||
Y!GJBV;Jg7ITqVTi[KWJoL/WD1pI$.H@n./I[Wi"]__D15CcfGaQP!u1TR[g9*6Yui6*Kp(MI7$T^Bl4
|
|
||||||
l4#-LRH0"HG"N)]RB]N\G0l5^ge]fV#>g27[#Sj(5X\1A7I["a?*hQ&War9\^@fsYo-*5J)#@>mOb#;?
|
|
||||||
;rr^6]^*0nWSp<R?kd:ah*%WZO-=Wa0k[Tf`^=_Ta#4<ck0d5oZlbe`(65[J6f8.>=Y/`2oR4nkGg!L3
|
|
||||||
%J;@oKhW'B3<Gm]mR*t:^!&E,Pp]&mJ/lP,])<P^q`ktSqPu_Ah:VB"opQE8:i>@QNd)%R?^]@8KY8]$
|
|
||||||
.r78lfYIc>Z5b_;0L8GRq9"\nSE.(2XlM$>`^k0:f5@FBorM*W69'%!m.skVQ$M@lE?!AMY-NVO\nH:c
|
|
||||||
I]NFkbrYC#-2CHlUBd/88?o[?IRlfq>0bkMj&cg6/OrroTlBHl3H3lDAfm4L&<hpKl\%T0QiHDJP2GYt
|
|
||||||
U4`DcUq^i\OV:fom2J'(`3G#k[*W\[L/r8*/XHqTASA"9I+f,)`E[XCaqGQPOc=m_YU=#U$X\O5;D-bN
|
|
||||||
)KjiEYJCg\-^#-k8sCYcRC4CWSN<^;C2_WT">0?Ci>16s["nQ],:-2g"!];L8L@r=lHRtr7#oB4OWs60
|
|
||||||
q1)H*ql\Sf6AMaE!paTSP<K+=347A-D?f:";sM31R/=_i`c*-TWc/n!ra];sAjc'/K%G]7C_t<Z&M3N\
|
|
||||||
o2O3_Bl#c^,'p/l2gC*(nr,[5&7Q7Yns@;$M!\[u0nNi882R-/,!R5mP_*HroaS(^qChALk+Wk-X)r5T
|
|
||||||
L&+!bYbJHM\<[mH\6b2=ZQcl@#jk0<SaoR\E`Q-QN&@sqofX7`lNXo;FpmT-hCRaF]Ma><OaX=s8_V`T
|
|
||||||
]d`0A9isrCnq?=ce?6$HHMJib`Y-?ooV$!Vh;1M(#kM0aQFH``Rk2hFk/$UJ[U'l"_q\sAX\5RrVE@Z^
|
|
||||||
Mk1`ZpI_NpdE*>q]jT*IDlNbQDjG._M'_?Y`&ZD]98h)+.paUXcgoPB\@KipB8heU`Q9djs)G2Sq9W]T
|
|
||||||
;!cin^gFr9$)@J=Hi8H`c,WVn[9%;6O7i2O^*efCg"R1`h]1Y;,lHbK%)+[AqJWJ39CJJ9qBI'trTSA$
|
|
||||||
SK+&//4\jh6tD.*^G[^]c/)S#$%)V_mk9Z2Z+G_DK;)r/='roX^-%4mLQ5c)]S2rg*Ui2]A4I`kOEEfP
|
|
||||||
rP96Qn+P%L+/mr]/_^N\Y.DO?ikpHkqrupaO,_jm?!tiu+:HrYnnnmO6a`q30V$j_(GT'QV)'H6A*ldc
|
|
||||||
]n+r.`R3,V1mAT'D>#PR[kEgOTr$k+/H):Z7Vdi2^R.pdMTcN6WlRK1T?9+C,m@a`P-A9`=38dnZBTZd
|
|
||||||
cP=(*]@"kFZb#iW6#VQ4_d\oA&(DIWh&*J,mg2-5Pp>"RL,A(Zd9eoZMUH5TY[Zo+774bhDLhCRQZiSA
|
|
||||||
Q=Qr+h0$%kMf@YrO7f&KrOCIsG@W80QL#&Cb?3Jf'#W#5?RR#ZMAYSa+X:q8PbO[]5KbWkHIi\YIN:MJ
|
|
||||||
bPjZfHf0IcRJ\7Ioj=lmHp7QL118I/*6;M[ZcG.!PK775p!*trXbdXWmgIVK1/'rqmlP?%X,0V!fFZaW
|
|
||||||
pAG0$FOWBnG[M7jBoj-eftY.@*`$C;leS_WVs1m,cMY1Ko"+c:%A)'?1\jOeY[1U.l-eL`?eCJ%D:aZ=
|
|
||||||
%1tOj_N@\O"pcgtpjni0>?sfuP%g;u*1LRhLU5?d="2]P4<Da`>A)#?>K]bs8g?;P!F\B1SoqS!Vtd_+
|
|
||||||
+KXRk,Qm>c]!^n6[9Gh^q^+A2WmWG)B=8a0?9-NPrM"IH^MUs9NV:R3Y49T!?k];[]8upK4&*tHAr[8M
|
|
||||||
Kda\eZ1(J+=fk<41YLXa_4(U?Q]QE![nEsZ0dr`,?\/(740cZ?,#.CFA2@`.4f3C`Wjr,m=6d82hPNFq
|
|
||||||
X4(6OR`5)FgKD3k_no,.?f(lZHhMh]P0Ni-&SjRBLJJe:Ki+JD2"tn,XDnjOEdY6U\3=gNQa_IoJ)fNp
|
|
||||||
/gPaQeORpaB,oM"hd8$^EQX?1bhWl3"R_K$o0eYZEl0WRKCRSN<WZaegA*;"'Ve\8,$e(3%Ai$21/.O.
|
|
||||||
CcC!NMX!(O>K`08#kG8R7U0(4Hu16/[/6NY1=/g5+-L:lnoT/.XD[)k8BfhY:P-c6]8?JgYLI7XbY6[[
|
|
||||||
roOisYrC=ZUfPC!\L[[mk]nkB'!Z=8F#ELjOaDr?6Z^n*W*PkEY!n4!HXsJ1PeHQ(?uEmUNTb%kS2aE`
|
|
||||||
/00(<b-Y2Ec/>-]8jqg8]Zs.iNL'iq(bpCX1H2(@*UT;Kd,9P<;4Njo>SaWsb]p65_n@Hn_>/B:iP%k"
|
|
||||||
QbUGtF[(CX&(TOH8baq85o.GU=3dT2r/gCWrFh]eS336gbE25d0bEqP?2/2Fb;^GYm!%J:bY6VTaImZJ
|
|
||||||
`@(lW',Y-"KJ/mg&R![Bb.B0$Fh>"V3TB#"-%$/O@-Ai_)am0X-r3]@m.FYVbb0d&)mYF@]7ornZ"^uH
|
|
||||||
*)62G&'m*Fj1F$!Yfg0c(Z.SELp8da&Ij(B"C3.n;/)B>>Vt0kcI?+-a8-aA?#H&WZu$DEPhBtd*\3/B
|
|
||||||
9TDO0^nR5b;=US1T1%]E(VK:h?b6/V@FP8.CHe+OalME4S^0%)k"kc$o>9/5T]71+Qam+?7[o;7;r[uM
|
|
||||||
IX9Gd"`tp-\DpHO4uhT]n?V):Xg!!?#;\0u;0Bh1.77$G#/0$?MR^:C9i[Lq<Wmd2Br^U2]5\YY6egKj
|
|
||||||
pptXT*knOP.DBjDpt@:Xqr#.8V656ensl7ts!SfiSo9H%LZ]12.=Oe!S,La8p',2;qZabrhRa-/\G"@h
|
|
||||||
F]g('HASO0Q]I\$A0I"%AU`G._[K/s#\&KV-'"Y,h:#m=9>1W),\Xh$k,]d(7=HbkN1NS'[uk?NA<,W7
|
|
||||||
#`5Muq!/]:LCJIIghG0P7si>'2'O-PEC?"k&8sHOb0<iij1r+L0L"nP&4U=nVQ+=t*b!Z<cE(J:4g?PU
|
|
||||||
7/!@so4gA1AMQ!$LaNcjXuo5'<ZRNIQ8TqW#+4jj5PQ]biLAeW0ADi+n0k#$W_lBl3=fl1Gg=?=(JV/K
|
|
||||||
j-?KT3d;r.`S),CA#&+2&KXMH"Zo(?jZRFn\",*kB$Qt;O%:;:Et)d$(ZX*96I[0h@7Z/d]9OoL/JW;"
|
|
||||||
pIoB:-S^Mu%`YMXam??RE*X-$jK8IK2D@p5_&(s7g?Dh<TR=]:9kE_Zb.+u0Hm#g)*:7NEBp=&kk;^V>
|
|
||||||
L4-7]P@lBsg(h*0d8sL]]4"5`Y`N"r?349-dU&1*.]aSICOB:PKi0r'mq\\;V*t"Y*ZX-D)kFP:G-PSU
|
|
||||||
^Ea<VoRBh400\iI^U$@:L!FM5ib6Jf3PYL0$qcn2:*[b?VZFGO(o[JSs(hmNIHP_Gp3QSuf))Z?5M]Om
|
|
||||||
oi>G5eQ;)4=88EUXhK,IYMS,GH1tWp=)[WZ>CQ8=*m8mOiudM;^3Y]eYdZi[q;Ucj/&Pe6N5s;4@4M%i
|
|
||||||
#'ed,roNh1=S;^$bh&I>fAFf"Kq.9!7,=GuKQUYDg:\jF]G5l=QkO])Q;EIK7#WMQD(NU;hm_Xfj_8mV
|
|
||||||
GHng4Kcf-fLEp&9WUV-WJZNb"8pu3L9d8P2[ji$J;-FHdSE%;#3CZs3Bt!*i*dSBu6VHOhi1XMmeKl=V
|
|
||||||
0QjE(U>DSRoi4I$B6LgdQAGmPb3hD;P1_g@`&[4C:!-?3etmbZl9$sb>KjdOZdnTk!:R9t&YsjH:ER1D
|
|
||||||
/Lu0Z[gCt\e9OCec8[<e<@%a[He^lN\MNmgjDh1eX"(5f2c(%;3@jDR=si[4C!*+r8&rh3H$&.8GIiao
|
|
||||||
<e/g9U-c/QCjTaGhGi3pR.Un>X]D8Z.G@kD4MN2/f5e2BdV8k1CsA+>aMJs)e)nHU\*g.\*`TW@,gL\.
|
|
||||||
I\U'"Bb/2M!S4PL#LM+H<pnMb$Z66DKbghd_k+X/`YmiAB]6q^'(<'he4o%a.#iNf0$HR(VS-ji)-=Ao
|
|
||||||
jL]lj>%Mt\s+[k]H)eU!T\4h.aq+=HILYd=jo$*_IOQQC`]SXB5-e0KS@IRu%^05]IqW]Ik_YAoa6ruC
|
|
||||||
Y(<$kM-6I[1L*'"/tQttcaO_:[u9G(`UiK9oBkQ5G4Bs,nU.rn^0NkY?9I*tSo5Fk[hmB"Ms&j6oBjNg
|
|
||||||
mGdqbnU,]RI(OX;>s."niq`<-q=`hDG4BslGeUk9^0NkY/tQu7So5Fk[u9G(Ms&j6oBkQ5p>YmknU.rn
|
|
||||||
^:ed*>s.!sSo69RD\pa/Ms&iQkM*uImGds8iq[cfI(OX;]m.7s4Pqc_D_I]#(XKRKkM(pXgVrdOiq`<-
|
|
||||||
q0):U]6M''GeRHGh_(L=0:m(ucaShqgPb7L`UiKVq=YKJpWg1->U=C14-o2E#/m7[#'sc)YOFFY7JP-u
|
|
||||||
HBh2=THn&jh*S3M>YciqUiuA":/o"lc7VuV>3DHA`"?1kPKR:0\%=D7/d(kRGC+T&4Lr,Bn&"It/sNg1
|
|
||||||
ai9l=m4&a"?9?gR&fphF`\V,&jHQ78);R9XZZfVL0qMQbi1"&`ncr#(3bCmYNU>F?CV<XN13;oSk@41S
|
|
||||||
%.4jRIj<=6n\P/(&pCsLOM0JhnGUiFn)^>\\a$aM<S]MRN`)ud]A8E%Qc46s'!iY/i*q;L/]?7Z5;FQJ
|
|
||||||
LZb<"dmJ[6_>/B:^-.=8'_eq=D8:Kj.o3UL);$SmG\BM-gQ9BV\/Mdn*6;(J2K-<:j`c,sj>.6pD-ZlO
|
|
||||||
f;`1a\-j7EL/sOB4Vcca2Bqa``Y(YL$+l[D7(kNW<-Ln1PTua5ed-X.82ua0l-%aS(MA$,IC3"GeF:i^
|
|
||||||
*#TY9HrGhsbZBl)`Xff/;Z.D<2EPi9=VtE"G,s:m/<fh3%`L(O9:=Hk*D\jjT&tB/L::FBel[F^Z#0(u
|
|
||||||
MQtl0FJJ/M<34KpjZRaNdU.kN7GY?VI#ME+IH*HmrV!CN:&eS(q7mE`Y.*Qq,'h&U,C.&@&NZNJG^D%#
|
|
||||||
m@BA]2r;bKr&@A]AaS'C=d5Pr?%0-N=d7(,2t+fT!nl8=0]bOOSV'l?\T-*bCkk)4EL+)[g)9YE9kAE^
|
|
||||||
f_o@:F%%N5p?/DkYjpEu[jU2(BVAD'UdB1j8U093l,e72oT7<:RX1<a\*WZk[>EYarEV;@pSg)Qao7Y:
|
|
||||||
m1%FVp5N35in2CNg=VSEkVX<'Z?GMpfdU+Gb*jVHYTN4id-:,UED)PVQ.o55"((qj:WYa+)XmGjAtcGW
|
|
||||||
%?A4eOf9A0e+UlN&]jQXLLpb9./n+[h&JS#_U<*.q=,M.OO).[;hMhl_[)o]ZhJTpB#3=X%nV_7pZR3L
|
|
||||||
Wlkd0+gPu=5ku7*%,p]o@Sj4NGqq,<h4[E>-[1$o1[Lt@AP>%GXU<C7;OkPi?,!!+.WNXJ%!p+1<0Zq_
|
|
||||||
7nV)Y1u?OdH>Al9rGq9"&j#$ANchp;D^L4?/0NeR%%188c;K-&p-@&acD.qE'RmnI]9fVn+[\;XGtBuC
|
|
||||||
Q,XA;\,0:^<=4b)e$V%1QLHnYgqutDB9oNQ!,q.nMASbLEdAr3N2=#;^h3RH><DIAn.iAW9XK@!7SS[;
|
|
||||||
O&E4EH:MB-+WfL)oO>R>@E:T?8($'+Y3@LFGC_Kh,O0.kp1&#qQjbrcNO%XTqU-P^)(\B5Y`R;B?348B
|
|
||||||
.c]<[;gs_RTlFVDdt*7d=3"D^b-Enu_H^Eu]OK->Yh'js;YD^Qjlj?VkP*Bt\o50TMQC2E'f]]F4'Ptf
|
|
||||||
nrGSTaN<L42@AQ]?]M&Epe.^bs!VAV?G6#5DuJ5_]D(m.HL#cn=O"H:iqYN[n%Yp4QR8.!g:cT4VTD&e
|
|
||||||
%E!D,l[-[THu9SrEa/k,f4Y%@<rG69)tmpfos_89q!rUaL!8>2h)\R=hj7aH)l*CPIsYJ12`BLL?A-'5
|
|
||||||
+"c-sf0A5m;ZQ-uEVA/A+jh6UIEQR,N5P3M^L$IPG=gE7(NW`\47W\IbH$'+^O*&*pW.mf7mR*hfU's?
|
|
||||||
K`.hF_:h[jjk`4oT-pf8ASdc`2fpean+(iiDin.+1TR(GfQX_=+sXSG;!s44<W)-98'`Z6A&qKeCk[l@
|
|
||||||
(5[#$>FLJD*Ye9PUXi/@RId:(/S9.pmbo_8aP]0Q"8+TtpMbdDZ1$2Afic#>a`]3'-V$7WKFfNW;V633
|
|
||||||
2=XFZqoW#b`&Gn<njm3IkQXZb&EQ\S*]`)RYS['?S/_D"(A._H,&bH'VI;o&l_X'o\S8,Q44Sc5_<-l,
|
|
||||||
[md53DP!2n](+VEn0Zk!g\8T!&"U@9Iq,j?gH.C/Fec1]hs5:ZXj)4/Q[b6Fh-HXKUkg^D_eFa!i]V58
|
|
||||||
*kkJCnPq8?g>f6HXZ*PEkM?uX;Ob:Pr%;c#-V>2RC\#=heh7OB+@r;TkpP/GHW@I8G1Nhs<4>g\NN@A9
|
|
||||||
ioX\0$YQrA;I'2JFGs4rRCreKoZ;>3f5kM`^fIdZfdH(][0=V5'k@4=9GPim%[rkq=>L0i6,)C;)s2a,
|
|
||||||
<_nrM'8=1ZG##F5M1<NOhnCaM(q/;/BCINllnJ>P'`4bM@H7bBf)]^<fV7TjL&8GUEf7J_D<HWl/@heK
|
|
||||||
J::T-QF>S',Q?sMEPkAYK>ALUEMUM3R^./2c)k,Rh=A]hkI017ctbj<LTQoc=g5MlYW%&ec7o!#ICj3,
|
|
||||||
=TWN3LL'3]o>Y,44q2:/hQ5SESjM93'PA=T;=OH?Pd"E`]-VO"/>ZZ&Da9Aj>lH(%I:$gNnU&JSiWJFn
|
|
||||||
^l:&f`kSA12b*02ijsWg`P*u`9.hUr(C&`O%\CVN]tqE92XBU3hW`SmhagF)\Q@&W4\ROiDj^:lJ/'gp
|
|
||||||
]n!tuXn^XeH6h4Dr<gLT__OhK`4m'Z)Njps@!"F:=^>.79#LI8T3Tj&r_Mqaen_bN;%`os?eQ]apr*_*
|
|
||||||
=W*u&]@^aF"'<6-mMD<5CF<Th]H=!7H;Zi(g,1I]=)*s4SbcnM$0)oooD0a0=khF0IrU0glAD!h+QaXc
|
|
||||||
bSJjrp3ZiPV+O6l@_9):L3>[j$hBkiMuCQi&CW8m0u_^p,:1R`Z/$Whj@(J,Om!MtZU5r4;^62;Mdni]
|
|
||||||
XW_V`DgPVn>M*)EWo&hh"tRFKO[UPA;,)@Zir!eCm_G[_f;:MlD`kI847&Q*>1Q;XVIT;7hiAaZA,(3t
|
|
||||||
i!/ZUEE=!#QV21OO:LV)r!c<S-4XR/mIi?oYDoU\`mNH-`_G+40P#)/YbueY-Is"\ZP_AW7;<'&$:V_C
|
|
||||||
__G\N6aYp1VrqudM%^EF!)FAr;)ud3U_W@pY"rXZ$MPbDh?3:cTtOVD<c4mQk1t:\DFH^.@T9jVFVdNn
|
|
||||||
+h]W5I\'prGX8lu.-0o/i[rcj2:gZpluJ"p6dM#PIB8Lj1%;-LV73TXmOK*2r5+Ef(k7_Cqngcg]$G]@
|
|
||||||
<PR10cGpME\+<bWEUo"EO0p@T:Dh:a*=mHkPOV\t#/(f-jBHgJ'W%"l$Sa7MO4$R2Sl&A4>14)<bhE"4
|
|
||||||
V6bGbMUIn$<H720g<Uh"C7#r09mMV4MDX'=(<JsVbDLt1f+80M=:b.5MtNsmm$6egP@iF\@rpKEEA2<K
|
|
||||||
.Ul:<O+[%G@HOG)IRh#>rR%\>#&'Y+#,7i==$b\PQcd4NRfYq9\fRaZ$SkhkOMY3O:A_jZ(!nCY\*D"j
|
|
||||||
;r'Tr,F;<uH^%aplh;I4CRi+n7c9lZe.+QRHeG$e,o2!2"a7J'AZQu&8]iF/BFjpL7OD2>k(k@tI,)m?
|
|
||||||
&Do[H)F@eSj$srLMmb^cBP)bmn>6uD`s<bGn*n72+u^")W85\_<pomAn5nli)&+7HWpms/&WSUYU,f&C
|
|
||||||
#5Ac'b6FZf]Bth(A]g3ZQ;6$>CcPBH_iW^Q&9OpeP^i8RATr]Y8RAZK;&i.\MQGHi*^P7lUqYf9B`/I\
|
|
||||||
qZ8&2`u8jM;rLjE<=4aV*i+Z4\od.U6pol:BW7($Ur,NE1[b*JD)#'+91GP5KBnu9,J!g<bt<HTar4PG
|
|
||||||
9"SP)e4Vp:.G?(tDgW`&KH3L"=1F5#f-pPdc#M5b<3Yg0R?d&rcH`u^%DL7/j%m?]0<c#"Z\j&8A=Z>>
|
|
||||||
i>j2R^V<J)bmI8.G3]Ran2u^b7T[MIlijF^3R+SfBA72s/?(G/3)h87Q"'1kBs7D/5>A6+\`OTi[<lmg
|
|
||||||
IecDbl*!]>gnF[RrpflHVtto@Tle->l0J>$j)m_ArI._+p*8=1b\E&As5i"M58+"hg]%MM_N%=~>
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
11983
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/Parent null
|
|
||||||
/Type /Pages
|
|
||||||
/MediaBox [0.0000 0.0000 311.00 209.00]
|
|
||||||
/Resources 8 0 R
|
|
||||||
/Kids [5 0 R]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
[/PDF /Text /ImageC]
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
/I true
|
|
||||||
/K false
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
11 0 obj
|
|
||||||
<<
|
|
||||||
/Alpha1
|
|
||||||
<<
|
|
||||||
/ca 1.0000
|
|
||||||
/CA 1.0000
|
|
||||||
/BM /Normal
|
|
||||||
/AIS false
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/ProcSet 9 0 R
|
|
||||||
/ExtGState 11 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 12
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000315 00000 n
|
|
||||||
0000012726 00000 n
|
|
||||||
0000000445 00000 n
|
|
||||||
0000000521 00000 n
|
|
||||||
0000000609 00000 n
|
|
||||||
0000012702 00000 n
|
|
||||||
0000013180 00000 n
|
|
||||||
0000012896 00000 n
|
|
||||||
0000012935 00000 n
|
|
||||||
0000013037 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/Size 12
|
|
||||||
/Root 2 0 R
|
|
||||||
/Info 1 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
13253
|
|
||||||
%%EOF
|
|
Before Width: | Height: | Size: 49 KiB |
@ -1,19 +0,0 @@
|
|||||||
sat-rs book
|
|
||||||
=========
|
|
||||||
|
|
||||||
High-level documentation of the [sat-rs project](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
If you have not done so, install the pre-requisites first:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo install mdbook --locked
|
|
||||||
cargo install mdbook-linkcheck --locked
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, you can build the book with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
mdbook build
|
|
||||||
```
|
|
@ -4,6 +4,3 @@ language = "en"
|
|||||||
multilingual = false
|
multilingual = false
|
||||||
src = "src"
|
src = "src"
|
||||||
title = "The sat-rs book"
|
title = "The sat-rs book"
|
||||||
|
|
||||||
[output.html]
|
|
||||||
[output.linkcheck]
|
|
||||||
|
@ -2,16 +2,9 @@
|
|||||||
|
|
||||||
- [Introduction](./introduction.md)
|
- [Introduction](./introduction.md)
|
||||||
- [Design](./design.md)
|
- [Design](./design.md)
|
||||||
|
|
||||||
# Basic concepts and components
|
|
||||||
|
|
||||||
- [Communication with Space Systems](./communication.md)
|
- [Communication with Space Systems](./communication.md)
|
||||||
- [Working with Constrained Systems](./constrained-systems.md)
|
- [Working with Constrained Systems](./constrained-systems.md)
|
||||||
- [Actions](./actions.md)
|
- [Actions](./actions.md)
|
||||||
- [Modes and Health](./modes-and-health.md)
|
- [Modes and Health](./modes-and-health.md)
|
||||||
- [Housekeeping Data](./housekeeping.md)
|
- [Housekeeping Data](./housekeeping.md)
|
||||||
- [Events](./events.md)
|
- [Events](./events.md)
|
||||||
|
|
||||||
# Example project
|
|
||||||
|
|
||||||
- [The satrs-example application](./example.md)
|
|
||||||
|
@ -15,7 +15,7 @@ action commanding could look like.
|
|||||||
2. Target ID and Action String based. The target ID is the same as in the first proposal, but
|
2. Target ID and Action String based. The target ID is the same as in the first proposal, but
|
||||||
the unique action is identified by a string.
|
the unique action is identified by a string.
|
||||||
|
|
||||||
The library provides an `ActionRequest` abstraction to model both of these cases.
|
The framework provides an `ActionRequest` abstraction to model both of these cases.
|
||||||
|
|
||||||
## Commanding with ECSS PUS 8
|
## Commanding with ECSS PUS 8
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
<div id="communication-chapter"/>
|
|
||||||
|
|
||||||
# Communication with sat-rs based software
|
# Communication with sat-rs based software
|
||||||
|
|
||||||
Communication is a vital topic for remote system which are usually not (directly)
|
Communication is a vital topic for remote system which are usually not (directly)
|
||||||
@ -17,16 +15,10 @@ it is still centered around small packets. `sat-rs` provides support for these E
|
|||||||
standards and also attempts to fill the gap to the internet protocol by providing the following
|
standards and also attempts to fill the gap to the internet protocol by providing the following
|
||||||
components.
|
components.
|
||||||
|
|
||||||
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/index.html).
|
1. [UDP TMTC Server](https://docs.rs/satrs-core/0.1.0-alpha.0/satrs_core/hal/host/udp_server/index.html#).
|
||||||
UDP is already packet based which makes it an excellent fit for exchanging space packets.
|
UDP is already packet based which makes it an excellent fit for exchanging space packets.
|
||||||
2. [TCP TMTC Server Components](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/index.html).
|
2. TCP TMTC Server. This is a stream based protocol, so the server uses the COBS framing protocol
|
||||||
TCP is a stream based protocol, so the library provides building blocks to parse telemetry
|
to always deliver complete packets.
|
||||||
from an arbitrary bytestream. Two concrete implementations are provided:
|
|
||||||
- [TCP spacepackets server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
|
||||||
to parse tightly packed CCSDS Spacepackets.
|
|
||||||
- [TCP COBS server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html)
|
|
||||||
to parse generic frames wrapped with the
|
|
||||||
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
|
||||||
|
|
||||||
# Working with telemetry and telecommands (TMTC)
|
# Working with telemetry and telecommands (TMTC)
|
||||||
|
|
||||||
@ -39,12 +31,8 @@ task might be to store all arriving telemetry persistently. This is especially i
|
|||||||
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
|
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
|
||||||
|
|
||||||
The most important task of a TC source is to deliver the telecommands to the correct recipients.
|
The most important task of a TC source is to deliver the telecommands to the correct recipients.
|
||||||
For component oriented software using message passing, this usually includes staged demultiplexing
|
For modern component oriented software using message passing, this usually includes staged
|
||||||
components to determine where a command needs to be sent.
|
demultiplexing components to determine where a command needs to be sent.
|
||||||
|
|
||||||
Using a generic concept of a TC source and a TM sink as part of the software design simplifies
|
|
||||||
the flexibility of the TMTC infrastructure: Newly added TM generators and TC receiver only have to
|
|
||||||
forward their generated or received packets to those handler objects.
|
|
||||||
|
|
||||||
# Low-level protocols and the bridge to the communcation subsystem
|
# Low-level protocols and the bridge to the communcation subsystem
|
||||||
|
|
||||||
|
@ -11,56 +11,23 @@ time where the OBSW might be running on Linux based systems with hundreds of MBs
|
|||||||
|
|
||||||
A useful pattern used commonly in space systems is to limit heap allocations to program
|
A useful pattern used commonly in space systems is to limit heap allocations to program
|
||||||
initialization time and avoid frequent run-time allocations. This prevents issues like
|
initialization time and avoid frequent run-time allocations. This prevents issues like
|
||||||
running out of memory (something even Rust can not protect from) or heap fragmentation on systems
|
running out of memory (something even Rust can not protect from) or heap fragmentation.
|
||||||
without a MMU.
|
|
||||||
|
|
||||||
# Using pre-allocated pool structures
|
# Using pre-allocated pool structures
|
||||||
|
|
||||||
A huge candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all
|
A huge candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all
|
||||||
candidates where the data size might vary greatly. The regular solution for host systems
|
candidates where the data size might vary greatly. The regular solution for host systems
|
||||||
might be to send around this data as a `Vec<u8>` until it is dropped. `sat-rs` provides
|
might be to send around this data as a `Vec<u8>` until it is dropped. `sat-rs` provides
|
||||||
another solution to avoid run-time allocations by offering pre-allocated static
|
another solution to avoid run-time allocations by offering and recommendng pre-allocated static
|
||||||
pools. These pools are split into subpools where each subpool can have different page sizes.
|
pools.
|
||||||
For example, a very small telecommand (TC) pool might look like this:
|
|
||||||
|
|
||||||
![Example Pool](images/pools/static-pools.png)
|
These pools are split into subpools where each subpool can have different page sizes.
|
||||||
|
For example, a very small TC pool might look like this:
|
||||||
|
|
||||||
The core of the pool abstractions is the
|
TODO: Add image
|
||||||
[PoolProvider trait](https://docs.rs/satrs/latest/satrs/pool/trait.PoolProvider.html).
|
|
||||||
This trait specifies the general API a pool structure should have without making assumption
|
|
||||||
of how the data is stored.
|
|
||||||
|
|
||||||
This trait is implemented by a static memory pool implementation.
|
|
||||||
The code to generate this static pool would look like this:
|
|
||||||
|
|
||||||
<!-- Would be nice to test this code sample, but need to wait
|
|
||||||
for https://github.com/rust-lang/mdBook/issues/706 to be merged.. -->
|
|
||||||
```rust, ignore
|
|
||||||
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
|
||||||
|
|
||||||
let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![
|
|
||||||
(6, 16),
|
|
||||||
(4, 32),
|
|
||||||
(2, 64),
|
|
||||||
(1, 128)
|
|
||||||
]));
|
|
||||||
```
|
|
||||||
|
|
||||||
It should be noted that the buckets only show the maximum size of data being stored inside them.
|
|
||||||
The store will keep a separate structure to track the actual size of the data being stored.
|
|
||||||
A TC entry inside this pool has a store address which can then be sent around without having
|
A TC entry inside this pool has a store address which can then be sent around without having
|
||||||
to dynamically allocate memory. The same principle can also be applied to the telemetry (TM) and
|
to dynamically allocate memory. The same principle can also be applied to the TM and IPC data.
|
||||||
inter-process communication (IPC) data.
|
|
||||||
|
|
||||||
You can read
|
|
||||||
|
|
||||||
- [`StaticPoolConfig` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticPoolConfig.html)
|
|
||||||
- [`StaticMemoryPool` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticMemoryPool.html)
|
|
||||||
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
In the future, optimized pool structures which use standard containers or are
|
|
||||||
[`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html) by default might be added as well.
|
|
||||||
|
|
||||||
# Using special crates to prevent smaller allocations
|
# Using special crates to prevent smaller allocations
|
||||||
|
|
||||||
@ -68,7 +35,7 @@ Another common way to use the heap on host systems is using containers like `Str
|
|||||||
to work with data where the size is not known beforehand. The most common solution for embedded
|
to work with data where the size is not known beforehand. The most common solution for embedded
|
||||||
systems is to determine the maximum expected size and then use a pre-allocated `u8` buffer and a
|
systems is to determine the maximum expected size and then use a pre-allocated `u8` buffer and a
|
||||||
size variable. Alternatively, you can use the following crates for more convenience or a smart
|
size variable. Alternatively, you can use the following crates for more convenience or a smart
|
||||||
behaviour which at the very least reduces heap allocations:
|
behaviour which at the very least reduce heap allocations:
|
||||||
|
|
||||||
1. [`smallvec`](https://docs.rs/smallvec/latest/smallvec/).
|
1. [`smallvec`](https://docs.rs/smallvec/latest/smallvec/).
|
||||||
2. [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec/index.html) which also contains an
|
2. [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec/index.html) which also contains an
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
# Library Design
|
# Framework Design
|
||||||
|
|
||||||
Satellites and space systems in general are complex systems with a wide range of requirements for
|
Satellites and space systems in general are complex systems with a wide range of requirements for
|
||||||
both the hardware and the software. Consequently, the general design of the library is centered
|
both the hardware and the software. Consequently, the general design of the framework is centered
|
||||||
around many light-weight components which try to impose as few restrictions as possible on how to
|
around many light-weight components which try to impose as few restrictions as possible on how to
|
||||||
solve certain problems. This is also the reason why sat-rs is explicitely called a library
|
solve certain problems.
|
||||||
instead of a framework.
|
|
||||||
|
|
||||||
There are still a lot of common patterns and architectures across these systems where guidance
|
There are still a lot of common patterns and architectures across these systems where guidance
|
||||||
of how to solve a problem and a common structure would still be extremely useful to avoid pitfalls
|
of how to solve a problem and a common structure would still be extremely useful to avoid pitfalls
|
||||||
which were already solved and to avoid boilerplate code. This library tries to provide this
|
which were already solved and to avoid boilerplate code. This framework tries to provide this
|
||||||
structure and guidance the following way:
|
structure and guidance the following way:
|
||||||
|
|
||||||
1. Providing this book which explains the architecture and design patterns in respect to common
|
1. Providing this book which explains the architecture and design patterns in respect to common
|
||||||
@ -19,7 +18,7 @@ structure and guidance the following way:
|
|||||||
3. Providing a good test suite. This includes both unittests and integration tests. The integration
|
3. Providing a good test suite. This includes both unittests and integration tests. The integration
|
||||||
tests can also serve as smaller usage examples than the large `satrs-example` application.
|
tests can also serve as smaller usage examples than the large `satrs-example` application.
|
||||||
|
|
||||||
This library has special support for standards used in the space industry. This especially
|
This framework has special support for standards used in the space industry. This especially
|
||||||
includes standards provided by Consultative Committee for Space Data Systems (CCSDS) and European
|
includes standards provided by Consultative Committee for Space Data Systems (CCSDS) and European
|
||||||
Cooperation for Space Standardization (ECSS). It does not enforce using any of those standards,
|
Cooperation for Space Standardization (ECSS). It does not enforce using any of those standards,
|
||||||
but it is always recommended to use some sort of standard for interoperability.
|
but it is always recommended to use some sort of standard for interoperability.
|
||||||
@ -31,10 +30,10 @@ Flying Laptop Project by the University of Stuttgart with Airbus Defence and Spa
|
|||||||
It has flight heritage through the 2 mssions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
It has flight heritage through the 2 mssions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
||||||
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
||||||
Therefore, a lot of the design concepts were ported more or less unchanged to the `sat-rs`
|
Therefore, a lot of the design concepts were ported more or less unchanged to the `sat-rs`
|
||||||
library.
|
framework.
|
||||||
FLP is a medium-size small satellite with a higher budget and longer development time than EIVE,
|
FLP is a medium-size small satellite with a higher budget and longer development time than EIVE,
|
||||||
which allowed to build a highly reliable system while EIVE is a smaller 6U+ cubesat which had a
|
which allowed to build a highly reliable system while EIVE is a smaller 6U+ cubesat which had a
|
||||||
shorter development cycle and was built using cheaper COTS components. This library also tries
|
shorter development cycle and was built using cheaper COTS components. This framework also tries
|
||||||
to accumulate the knowledge of developing the OBSW and operating the satellite for both these
|
to accumulate the knowledge of developing the OBSW and operating the satellite for both these
|
||||||
different systems and provide a solution for a wider range of small satellite systems.
|
different systems and provide a solution for a wider range of small satellite systems.
|
||||||
|
|
||||||
|
@ -1,24 +1,16 @@
|
|||||||
# Events
|
# Events
|
||||||
|
|
||||||
Events are an important mechanism used for remote systems to monitor unexpected
|
Events can be an extremely important mechanism used for remote systems to monitor unexpected
|
||||||
or expected anomalies and events occuring on these systems.
|
or expected anomalies and events occuring on these systems. They are oftentimes tied to
|
||||||
|
|
||||||
One common use case for events on remote systems is to offer a light-weight publish-subscribe
|
|
||||||
mechanism and IPC mechanism for software and hardware events which are also packaged as telemetry
|
|
||||||
(TM) or can trigger a system response. They can also be tied to
|
|
||||||
Fault Detection, Isolation and Recovery (FDIR) operations, which need to happen autonomously.
|
Fault Detection, Isolation and Recovery (FDIR) operations, which need to happen autonomously.
|
||||||
|
|
||||||
The PUS Service 5 standardizes how the ground interface for events might look like, but does not
|
Events can also be used as a convenient Inter-Process Communication (IPC) mechansism, which is
|
||||||
specify how other software components might react to those events. There is the PUS Service 19,
|
also observable for the Ground segment. The PUS Service 5 standardizes how the ground interface
|
||||||
which might be used for that purpose, but the event components recommended by this framework do not
|
for events might look like, but does not specify how other software components might react
|
||||||
rely on the present of this service.
|
to those events. There is the PUS Service 19, which might be used for that purpose, but the
|
||||||
|
event components recommended by this framework do not really need this service.
|
||||||
|
|
||||||
The following images shows how the flow of events could look like in a system where components
|
The following images shows how the flow of events could look like in a system where components
|
||||||
can generate events, and where other system components might be interested in those events:
|
can generate events, and where other system components might be interested in those events:
|
||||||
|
|
||||||
![Event flow](images/events/event_man_arch.png)
|
![Event flow](images/event_man_arch.png)
|
||||||
|
|
||||||
For the concrete implementation of your own event management and/or event routing system, you
|
|
||||||
can have a look at the event management documentation inside the
|
|
||||||
[API documentation](https://docs.rs/satrs/latest/satrs/event_man/index.html) where you can also
|
|
||||||
find references to all examples.
|
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
# sat-rs Example Application
|
|
||||||
|
|
||||||
The `sat-rs` library includes a monolithic example application which can be found inside
|
|
||||||
the [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
|
||||||
subdirectory of the repository. The primary purpose of this example application is to show how
|
|
||||||
the various components of the sat-rs framework could be used as part of a larger on-board
|
|
||||||
software application.
|
|
||||||
|
|
||||||
## Structure of the example project
|
|
||||||
|
|
||||||
The example project contains components which could also be expected to be part of a production
|
|
||||||
On-Board Software. A structural diagram of the example application is given to provide
|
|
||||||
a brief high-level view of the components used inside the example application:
|
|
||||||
|
|
||||||
![satrs-example component structure](images/satrs-example/satrs-example-structure.png)
|
|
||||||
|
|
||||||
The dotted lines are used to denote optional components. In this case, the static pool components
|
|
||||||
are optional because the heap can be used as a simpler mechanism to store TMTC packets as well.
|
|
||||||
Some additional explanation is provided for the various components.
|
|
||||||
|
|
||||||
### TCP/IP server components
|
|
||||||
|
|
||||||
The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This
|
|
||||||
might be an optional component for an OBSW which is only used during the development phase on
|
|
||||||
ground. The UDP server is strongly based on the
|
|
||||||
[UDP TC server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/struct.UdpTcServer.html).
|
|
||||||
This server component is wrapped by a TMTC server which handles all telemetry to the last connected
|
|
||||||
client.
|
|
||||||
|
|
||||||
The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
|
||||||
class. It parses space packets by using the CCSDS space packet ID as the packet
|
|
||||||
start delimiter. All available telemetry will be sent back to a client after having read all
|
|
||||||
telecommands from the client.
|
|
||||||
|
|
||||||
### TMTC Infrastructure
|
|
||||||
|
|
||||||
The most important components of the TMTC infrastructure include the following components:
|
|
||||||
|
|
||||||
- A TC source component which demultiplexes and routes telecommands based on parameters like
|
|
||||||
packet APID or PUS service and subservice type.
|
|
||||||
- A TM sink sink component which is the target of all sent telemetry and sends it to downlink
|
|
||||||
handlers like the UDP and TCP server.
|
|
||||||
|
|
||||||
You can read the [Communications chapter](./communication.md) for more
|
|
||||||
background information on the chosen TMTC infrastructure approach.
|
|
||||||
|
|
||||||
### PUS Service Components
|
|
||||||
|
|
||||||
A PUS service stack is provided which exposes some functionality conformant with the ECSS PUS
|
|
||||||
services. This currently includes the following services:
|
|
||||||
|
|
||||||
- Service 1 for telecommand verification. The verification handling is handled locally: Each
|
|
||||||
component which generates verification telemetry in some shape or form receives a
|
|
||||||
[reporter](https://docs.rs/satrs/latest/satrs/pus/verification/struct.VerificationReporterWithSender.html)
|
|
||||||
object which can be used to send PUS 1 verification telemetry to the TM funnel.
|
|
||||||
- Service 3 for housekeeping telemetry handling.
|
|
||||||
- Service 5 for management and downlink of on-board events.
|
|
||||||
- Service 8 for handling on-board actions.
|
|
||||||
- Service 11 for scheduling telecommands to be released at a specific time. This component
|
|
||||||
uses the [PUS scheduler class](https://docs.rs/satrs/latest/satrs/pus/scheduler/alloc_mod/struct.PusScheduler.html)
|
|
||||||
which performs the core logic of scheduling telecommands. All telecommands released by the
|
|
||||||
scheduler are sent to the central TC source using a message.
|
|
||||||
- Service 17 for test purposes like pings.
|
|
||||||
|
|
||||||
### Event Management Component
|
|
||||||
|
|
||||||
An event manager based on the sat-rs
|
|
||||||
[event manager component](https://docs.rs/satrs/latest/satrs/event_man/index.html)
|
|
||||||
is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5
|
|
||||||
telemetry by the
|
|
||||||
[PUS event dispatcher](https://docs.rs/satrs/latest/satrs/pus/event_man/alloc_mod/struct.PusEventDispatcher.html).
|
|
||||||
|
|
||||||
You can read the [events](./events.md) chapter for more in-depth information about event management.
|
|
||||||
|
|
||||||
### Sample Application Components
|
|
||||||
|
|
||||||
These components are example mission specific. They provide an idea how mission specific modules
|
|
||||||
would look like the sat-rs context. It currently includes the following components:
|
|
||||||
|
|
||||||
- An Attitute and Orbit Control (AOCS) example task which can also process some PUS commands.
|
|
||||||
|
|
||||||
## Dataflow
|
|
||||||
|
|
||||||
The interaction of the various components is provided in the following diagram:
|
|
||||||
|
|
||||||
![satrs-example dataflow diagram](images/satrs-example/satrs-example-dataflow.png)
|
|
||||||
|
|
||||||
It should be noted that an arrow coming out of a component group refers to multiple components
|
|
||||||
in that group. An explanation for important component groups will be given.
|
|
||||||
|
|
||||||
#### TMTC component group
|
|
||||||
|
|
||||||
This groups is the primary interface for clients to communicate with the on-board software
|
|
||||||
using a standardized TMTC protocol. The example uses the
|
|
||||||
[ECSS PUS protocol](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
|
||||||
In the future, this might be extended with the
|
|
||||||
[CCSDS File Delivery Protocol](https://public.ccsds.org/Pubs/727x0b5.pdf).
|
|
||||||
|
|
||||||
A client can connect to the UDP or TCP server to send these PUS packets to the on-board software.
|
|
||||||
These servers then forward the telecommads to a centralized TC source component using a dedicated
|
|
||||||
message abstraction.
|
|
||||||
|
|
||||||
This TC source component then demultiplexes the message and forwards it to the relevant components.
|
|
||||||
Right now, it forwards all PUS requests to the respective PUS service handlers using the PUS
|
|
||||||
receiver component. The individual PUS services are running in a separate thread. In the future,
|
|
||||||
additional forwarding to components like a CFDP handler might be added as well. It should be noted
|
|
||||||
that PUS11 commands might contain other PUS commands which should be scheduled in the future.
|
|
||||||
These wrapped commands are forwarded to the PUS11 handler. When the schedule releases those
|
|
||||||
commands, it forwards the released commands to the TC source again. This allows the scheduler
|
|
||||||
and the TC source to run in separate threads and keeps them cleanly separated.
|
|
||||||
|
|
||||||
All telemetry generated by the on-board software is sent to a centralized TM funnel. This component
|
|
||||||
also performs a demultiplexing step to forward all telemetry to the relevant TM recipients.
|
|
||||||
In the example case, this is the last UDP client, or a connected TCP client. In the future,
|
|
||||||
forwarding to a persistent telemetry store and a simulated communication component might be
|
|
||||||
added here as well. The centralized TM funnel also takes care of some packet processing steps which
|
|
||||||
need to be applied for each ECSS PUS packet, for example CCSDS specific APID incrementation and
|
|
||||||
PUS specific message counter incrementation.
|
|
||||||
|
|
||||||
#### Application Group
|
|
||||||
|
|
||||||
The application components generally do not receive raw PUS packets directly, even though
|
|
||||||
this is certainly possible. Instead, they receive internalized messages from the PUS service
|
|
||||||
handlers. For example, instead of receiving a PUS 8 Action Telecommand directly, an application
|
|
||||||
component will receive a special `ActionRequest` message type reduced to the basic important
|
|
||||||
information required to execute a request. These special requests are denoted by the blue arrow
|
|
||||||
in the diagram.
|
|
||||||
|
|
||||||
It should be noted that the arrow pointing towards the event manager points in both directions.
|
|
||||||
This is because the application components might be interested in events generated by other
|
|
||||||
components as well. This mechanism is oftentimes used to implement the FDIR functionality on system
|
|
||||||
and component level.
|
|
||||||
|
|
||||||
#### Shared components and functional interfaces
|
|
||||||
|
|
||||||
It should be noted that sometimes, a functional interface is used instead of a message. This
|
|
||||||
is used for the generation of verification telemetry. The verification reporter is a clonable
|
|
||||||
component which generates and sends PUS1 verification telemetry directly to the TM funnel. This
|
|
||||||
introduces a loose coupling to the PUS standard but was considered the easiest solution for
|
|
||||||
a project which utilizes PUS as the main communication protocol. In the future, a generic
|
|
||||||
verification abstraction might be introduced to completely decouple the application layer from
|
|
||||||
PUS.
|
|
||||||
|
|
||||||
The same concept is applied if the backing store of TMTC packets are shared pools. Every
|
|
||||||
component which needs to read telecommands inside that shared pool or generate new telemetry
|
|
||||||
into that shared pool will received a clonable shared handle to that pool.
|
|
||||||
|
|
||||||
The same concept could be extended to power or thermal handling. For example, a shared power helper
|
|
||||||
component might be used to retrieve power state information and send power switch commands through
|
|
||||||
a functional interface. The actual implementation of the functional interface might still use
|
|
||||||
shared memory and/or messages, but the functional interface makes using and testing the interaction
|
|
||||||
with these components easier.
|
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
<!--Created by yEd 3.23.2-->
|
<!--Created by yEd 3.22-->
|
||||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<y:Geometry height="509.9999999999999" width="768.7000000000003" x="579.3105418719211" y="304.7"/>
|
<y:Geometry height="509.9999999999999" width="768.7000000000003" x="579.3105418719211" y="304.7"/>
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="24.177873611450195" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="168.3929901123047" x="26.197490701913352" xml:space="preserve" y="24.234711021505348">Example Event Flow<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.46591974671274444" nodeRatioY="-0.452480958781362" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.936037063598633" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="150.1282958984375" x="26.197490701913352" xml:space="preserve" y="24.234711021505348">Example Event Flow<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.46591974671274444" nodeRatioY="-0.452480958781362" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
</data>
|
</data>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<y:Geometry height="60.0" width="203.0" x="814.0" y="506.6799999999999"/>
|
<y:Geometry height="60.0" width="203.0" x="814.0" y="506.6799999999999"/>
|
||||||
<y:Fill color="#FFFF00" transparent="false"/>
|
<y:Fill color="#FFFF00" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="24.177873611450195" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="127.31723022460938" x="37.84138488769531" xml:space="preserve" y="17.911063194274846">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.21258544921875" x="58.393707275390625" xml:space="preserve" y="21.27395296096796">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
</data>
|
</data>
|
||||||
@ -39,10 +39,10 @@
|
|||||||
<node id="n2">
|
<node id="n2">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="60.0" width="92.24000000000001" x="607.36" y="413.23"/>
|
<y:Geometry height="60.0" width="82.0" x="617.6" y="413.23"/>
|
||||||
<y:Fill color="#FF9900" transparent="false"/>
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.16012573242188" x="10.039937133789067" xml:space="preserve" y="10.063962936401367">Event
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="13.4398193359375" xml:space="preserve" y="14.547905921936035">Event
|
||||||
Creator 0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Creator 0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -54,7 +54,7 @@ Creator 0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
|
|||||||
<y:Geometry height="60.0" width="76.55999999999995" x="988.5" y="335.62999999999994"/>
|
<y:Geometry height="60.0" width="76.55999999999995" x="988.5" y="335.62999999999994"/>
|
||||||
<y:Fill color="#FF9900" transparent="false"/>
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.16012573242188" x="2.199937133789035" xml:space="preserve" y="10.063962936401367">Event
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="10.719819335937473" xml:space="preserve" y="14.547905921936035">Event
|
||||||
Creator 2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Creator 2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -63,10 +63,10 @@ Creator 2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
|
|||||||
<node id="n4">
|
<node id="n4">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="60.0" width="87.27999999999997" x="845.9410837438425" y="335.62999999999994"/>
|
<y:Geometry height="60.0" width="72.55999999999983" x="860.6610837438426" y="335.62999999999994"/>
|
||||||
<y:Fill color="#FF9900" transparent="false"/>
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.16012573242188" x="7.559937133789049" xml:space="preserve" y="10.063962936401367">Event
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="8.719819335937359" xml:space="preserve" y="14.547905921936035">Event
|
||||||
Creator 1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Creator 1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -78,7 +78,7 @@ Creator 1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
|
|||||||
<y:Geometry height="60.0" width="87.27999999999997" x="1112.52" y="335.62999999999994"/>
|
<y:Geometry height="60.0" width="87.27999999999997" x="1112.52" y="335.62999999999994"/>
|
||||||
<y:Fill color="#FF9900" transparent="false"/>
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.16012573242188" x="7.559937133788935" xml:space="preserve" y="10.063962936401367">Event
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="16.079819335937373" xml:space="preserve" y="14.547905921936035">Event
|
||||||
Creator 3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Creator 3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -87,10 +87,10 @@ Creator 3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
|
|||||||
<node id="n6">
|
<node id="n6">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="60.0" width="145.19999999999993" x="734.2060377358491" y="620.26"/>
|
<y:Geometry height="60.0" width="126.0" x="781.0" y="620.26"/>
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="122.38423156738281" x="11.407884216308503" xml:space="preserve" y="10.063962936401367">PUS Service 5
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.78865051269531" x="16.605674743652344" xml:space="preserve" y="14.547905921936035">PUS Service 5
|
||||||
Event Reporting
|
Event Reporting
|
||||||
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
@ -100,10 +100,10 @@ Event Reporting
|
|||||||
<node id="n7">
|
<node id="n7">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="60.0" width="136.8599999999999" x="901.8" y="620.26"/>
|
<y:Geometry height="60.0" width="118.63999999999987" x="928.2" y="620.26"/>
|
||||||
<y:Fill color="#FFCC00" transparent="false"/>
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="110.78424072265625" x="13.037879638671825" xml:space="preserve" y="10.063962936401367">PUS Service 19
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.08859252929688" x="17.2757037353515" xml:space="preserve" y="14.547905921936035">PUS Service 19
|
||||||
Event Action<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Event Action<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -112,10 +112,10 @@ Event Action<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel>
|
|||||||
<node id="n8">
|
<node id="n8">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="60.0" width="97.27999999999997" x="787.1260377358491" y="733.8400000000001"/>
|
<y:Geometry height="60.0" width="87.27999999999997" x="792.1260377358491" y="733.8400000000001"/>
|
||||||
<y:Fill color="#FFCC99" transparent="false"/>
|
<y:Fill color="#FFCC99" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.872074127197266" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.57614135742188" x="9.351929321289049" xml:space="preserve" y="10.063962936401367">Telemetry
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="59.932403564453125" x="13.673798217773424" xml:space="preserve" y="14.547905921936035">Telemetry
|
||||||
Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
<y:Shape type="rectangle"/>
|
<y:Shape type="rectangle"/>
|
||||||
</y:ShapeNode>
|
</y:ShapeNode>
|
||||||
@ -124,10 +124,10 @@ Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:Model
|
|||||||
<node id="n9">
|
<node id="n9">
|
||||||
<data key="d6">
|
<data key="d6">
|
||||||
<y:ShapeNode>
|
<y:ShapeNode>
|
||||||
<y:Geometry height="218.79999999999995" width="256.8800000000001" x="1068.6599999999999" y="575.0400000000002"/>
|
<y:Geometry height="170.79999999999995" width="210.80000000000018" x="1076.84" y="601.88"/>
|
||||||
<y:Fill hasColor="false" transparent="false"/>
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
<y:NodeLabel alignment="left" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="190.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="240.7890625" x="10.203400059311889" xml:space="preserve" y="9.536167573623516">Subscriptions
|
<y:NodeLabel alignment="left" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="143.6875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="181.591796875" x="8.373079774614325" xml:space="preserve" y="7.444138124199753">Subscriptions
|
||||||
|
|
||||||
1. Event Creator 0 subscribes
|
1. Event Creator 0 subscribes
|
||||||
for event 0
|
for event 0
|
||||||
@ -144,10 +144,10 @@ Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:Model
|
|||||||
<edge id="e0" source="n4" target="n1">
|
<edge id="e0" source="n4" target="n1">
|
||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="15.418916256157559" sy="0.0" tx="-10.5" ty="0.0"/>
|
<y:Path sx="8.058916256157545" sy="0.0" tx="-10.5" ty="0.0"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.23976135253906" x="4.48011932373015" xml:space="preserve" y="27.465240361497138">event 1
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="8.639817810058275" xml:space="preserve" y="29.00100609374465">event 1
|
||||||
(group 1)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="35.59999999999969" distanceToCenter="true" position="left" ratio="0.34252387409930674" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
(group 1)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="35.59999999999969" distanceToCenter="true" position="left" ratio="0.34252387409930674" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
@ -161,7 +161,7 @@ Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:Model
|
|||||||
</y:Path>
|
</y:Path>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.23976135253906" x="24.477808328186484" xml:space="preserve" y="-43.213945007324355">event 0
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="25.334655000000453" xml:space="preserve" y="-40.972107505798476">event 0
|
||||||
(group 0)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="25.520000000000095" distanceToCenter="true" position="left" ratio="0.20267159489379444" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
(group 0)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="25.520000000000095" distanceToCenter="true" position="left" ratio="0.20267159489379444" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
@ -173,7 +173,7 @@ Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:Model
|
|||||||
<y:Path sx="-23.719999999999914" sy="5.5" tx="87.56000000000006" ty="0.0"/>
|
<y:Path sx="-23.719999999999914" sy="5.5" tx="87.56000000000006" ty="0.0"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.23976135253906" x="5.6761352539062955" xml:space="preserve" y="26.10821814399378">event 2
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="5.6761352539062955" xml:space="preserve" y="27.551854405966765">event 2
|
||||||
(group 3)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="5.676132812499983" distanceToCenter="false" position="left" ratio="0.3219761157957032" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
(group 3)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="5.676132812499983" distanceToCenter="false" position="left" ratio="0.3219761157957032" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
@ -187,8 +187,8 @@ Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:Model
|
|||||||
</y:Path>
|
</y:Path>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.94757080078125" x="8.646225134939186" xml:space="preserve" y="27.90167113105076">event 3 (group 2)
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.38468933105469" x="26.667665869801795" xml:space="preserve" y="43.287014528669715">event 3 (group 2)
|
||||||
event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="65.12000000000057" distanceToCenter="true" position="left" ratio="0.18074782715730137" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="75.3599999999999" distanceToCenter="true" position="left" ratio="0.2967848459873102" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
</data>
|
</data>
|
||||||
@ -196,10 +196,10 @@ event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false
|
|||||||
<edge id="e4" source="n1" target="n6">
|
<edge id="e4" source="n1" target="n6">
|
||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="-80.36000000000001" sy="0.0" tx="28.333962264150955" ty="0.0"/>
|
<y:Path sx="-65.0" sy="0.0" tx="6.5" ty="0.0"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="19.693931579589844" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="96.35763549804688" x="-105.378832397461" xml:space="preserve" y="15.634602566150534"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="57.20000000000004" distanceToCenter="true" position="right" ratio="0.4441995640590947" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="-98.78228302001958" xml:space="preserve" y="16.63042580701972"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="57.20000000000004" distanceToCenter="true" position="right" ratio="0.4441995640590947" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
</data>
|
</data>
|
||||||
@ -207,10 +207,10 @@ event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false
|
|||||||
<edge id="e5" source="n1" target="n7">
|
<edge id="e5" source="n1" target="n7">
|
||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="32.38107215104537" sy="0.0" tx="-22.34892784895453" ty="0.0"/>
|
<y:Path sx="42.660000000000196" sy="0.0" tx="-29.359999999999786" ty="0.0"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="19.693931579589844" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" underlinedText="true" verticalTextPosition="bottom" visible="true" width="96.35763549804688" x="13.821211921553186" xml:space="preserve" y="16.782337120427428"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.0" distanceToCenter="true" position="left" ratio="0.492249939452652" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="20.4177438354493" xml:space="preserve" y="17.885881494816203"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.0" distanceToCenter="true" position="left" ratio="0.492249939452652" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
</data>
|
</data>
|
||||||
@ -219,11 +219,11 @@ event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false
|
|||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
||||||
<y:Point x="653.48" y="536.6799999999998"/>
|
<y:Point x="658.6" y="536.6799999999998"/>
|
||||||
</y:Path>
|
</y:Path>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.47381591796875" x="-139.88417228306275" xml:space="preserve" y="-47.69392425537126">event 1
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.69230651855469" x="-131.99129340961497" xml:space="preserve" y="-45.45208675384538">event 1
|
||||||
event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6426904695623505" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6426904695623505" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
@ -232,10 +232,10 @@ event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultA
|
|||||||
<edge id="e7" source="n1" target="n4">
|
<edge id="e7" source="n1" target="n4">
|
||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="-35.69940886699487" sy="0.0" tx="-9.780492610837314" ty="1.5"/>
|
<y:Path sx="-35.69940886699487" sy="0.0" tx="-17.140492610837327" ty="1.5"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="19.693931579589844" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.16780090332031" x="-57.86390746318625" xml:space="preserve" y="-80.0118020361142">group 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="31.279999999999973" distanceToCenter="true" position="left" ratio="0.6800790648728832" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.14430236816406" x="-54.352158195608126" xml:space="preserve" y="-79.29459128622307">group 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="31.279999999999973" distanceToCenter="true" position="left" ratio="0.6800790648728832" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
||||||
</data>
|
</data>
|
||||||
@ -243,10 +243,10 @@ event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultA
|
|||||||
<edge id="e8" source="n6" target="n8">
|
<edge id="e8" source="n6" target="n8">
|
||||||
<data key="d10">
|
<data key="d10">
|
||||||
<y:PolyLineEdge>
|
<y:PolyLineEdge>
|
||||||
<y:Path sx="28.960000000000036" sy="0.0" tx="0.0" ty="-21.423522388059723"/>
|
<y:Path sx="0.0" sy="0.0" tx="8.233962264150945" ty="-21.42352238805968"/>
|
||||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
<y:Arrows source="none" target="standard"/>
|
<y:Arrows source="none" target="standard"/>
|
||||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="35.38786315917969" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="101.29963684082031" x="-107.4498329306548" xml:space="preserve" y="9.082203674316474">enabled Events
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="87.40060424804688" x="-100.50030212402339" xml:space="preserve" y="11.337896156311103">enabled Events
|
||||||
as PUS 5 TM<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="56.79999999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
as PUS 5 TM<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="56.79999999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
<y:BendStyle smoothed="false"/>
|
<y:BendStyle smoothed="false"/>
|
||||||
</y:PolyLineEdge>
|
</y:PolyLineEdge>
|
BIN
satrs-book/src/images/event_man_arch.png
Normal file
After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 167 KiB |
@ -1,49 +1,23 @@
|
|||||||
The sat-rs book
|
The sat-rs book
|
||||||
======
|
======
|
||||||
|
|
||||||
This book is the primary information resource for the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
This book is the primary information resource for the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
||||||
in addition to the regular API documentation. It contains the following resources:
|
in addition to the regular API documentation. It contains the following resources:
|
||||||
|
|
||||||
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
||||||
2. General information on how to build on-board Software and how `sat-rs` can help to fulfill
|
2. General information on how to build On-Board Software and how `sat-rs` can help to fulfill
|
||||||
the unique requirements of writing software for remote systems.
|
the unique requirements of writing software for remote systems.
|
||||||
|
2. A Getting-Started workshop where a small On-Board Software is built from scratch using
|
||||||
|
sat-rs components.
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
The primary goal of the sat-rs library is to provide re-usable components
|
The primary goal of the sat-rs framework is to provide re-usable components
|
||||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||||
for the special requirements for these systems.
|
for the special requirements for these systems.
|
||||||
|
|
||||||
It should be noted that sat-rs is early-stage software. Important features are missing. New releases
|
|
||||||
with breaking changes are released regularly, with all changes documented inside respective
|
|
||||||
changelog files. You should only use this library if your are willing to work in this
|
|
||||||
environment.
|
|
||||||
|
|
||||||
A lot of the architecture and general design considerations are based on the
|
A lot of the architecture and general design considerations are based on the
|
||||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
||||||
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
||||||
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
||||||
|
|
||||||
# Getting started with the example
|
|
||||||
|
|
||||||
The [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
|
||||||
provides various practical usage examples of the `sat-rs` framework. If you are more interested in
|
|
||||||
the practical application of `sat-rs` inside an application, it is recommended to have a look at
|
|
||||||
the example application. The [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim)
|
|
||||||
applicatin complements the example application and can be used to simulate some physical devices
|
|
||||||
for the `satrs-example` device handlers.
|
|
||||||
|
|
||||||
# Flight Heritage
|
|
||||||
|
|
||||||
There is an active and continuous effort to get early flight heritage for the sat-rs library.
|
|
||||||
Currently this library has the following flight heritage:
|
|
||||||
|
|
||||||
- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT)
|
|
||||||
which has also
|
|
||||||
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
|
||||||
The application is strongly based on the sat-rs example application. You can find the repository
|
|
||||||
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
|
||||||
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
|
|
||||||
alongside a Flight System Simulator in the context of a
|
|
||||||
[Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
|
|
||||||
at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# Modes
|
# Modes
|
||||||
|
|
||||||
Modes are an extremely useful concept to model complex systems. They allow simplified
|
Modes are an extremely useful concept for complex system in general. They also allow simplified
|
||||||
system reasoning for both system operators and OBSW developers. They also provide a way to alter
|
system reasoning for both system operators and OBSW developers. They model the behaviour of a
|
||||||
the behaviour of a component and also provide observability of a system. A few examples of how to
|
component and also provide observability of a system. A few examples of how to model
|
||||||
model the mode of different components within a space system with modes will be given.
|
different components of a space system with modes will be given.
|
||||||
|
|
||||||
## Pyhsical device component with modes
|
## Modelling a pyhsical devices with modes
|
||||||
|
|
||||||
The following simple mode scheme with the following three mode
|
The following simple mode scheme with the following three mode
|
||||||
|
|
||||||
@ -13,8 +13,7 @@ The following simple mode scheme with the following three mode
|
|||||||
- `ON`
|
- `ON`
|
||||||
- `NORMAL`
|
- `NORMAL`
|
||||||
|
|
||||||
can be applied to a large number of simpler device controllers of a remote system, for example
|
can be applied to a large number of simpler devices of a remote system, for example sensors.
|
||||||
sensors.
|
|
||||||
|
|
||||||
1. `OFF` means that a device is physically switched off, and the corresponding software component
|
1. `OFF` means that a device is physically switched off, and the corresponding software component
|
||||||
does not poll the device regularly.
|
does not poll the device regularly.
|
||||||
@ -32,7 +31,7 @@ for the majority of devices:
|
|||||||
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
|
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
|
||||||
before powering off the device.
|
before powering off the device.
|
||||||
|
|
||||||
## Controller components with modes
|
## Modelling a controller with modes
|
||||||
|
|
||||||
Controller components are not modelling physical devices, but a mode scheme is still the best
|
Controller components are not modelling physical devices, but a mode scheme is still the best
|
||||||
way to model most of these components.
|
way to model most of these components.
|
||||||
|
9
satrs-core/CHANGELOG.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Change Log
|
||||||
|
=======
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# [unreleased]
|
@ -1,57 +1,49 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "satrs"
|
name = "satrs-core"
|
||||||
version = "0.2.1"
|
version = "0.1.0-alpha.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.71.1"
|
rust-version = "1.61"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
description = "A framework to build software for remote systems"
|
description = "Core components of the sat-rs framework to build software for remote systems"
|
||||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
keywords = ["no-std", "space", "aerospace"]
|
keywords = ["no-std", "space", "aerospace"]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
delegate = ">0.7, <=0.13"
|
delegate = ">0.7, <=0.10"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
derive-new = ">=0.6, <=0.7"
|
# TODO: Remove this as soon as the image including the description was moved to the satrs-book.
|
||||||
smallvec = "1"
|
embed-doc-image = "0.1"
|
||||||
crc = "3"
|
|
||||||
|
|
||||||
[dependencies.satrs-shared]
|
[dependencies.smallvec]
|
||||||
version = ">=0.1.3, <=0.2"
|
version = "1"
|
||||||
|
|
||||||
[dependencies.num_enum]
|
[dependencies.num_enum]
|
||||||
version = ">0.5, <=0.7"
|
version = ">0.5, <=0.7"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
[dependencies.crc]
|
||||||
version = "0.13"
|
version = "3"
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.cobs]
|
|
||||||
git = "https://github.com/robamu/cobs.rs.git"
|
|
||||||
version = "0.2.3"
|
|
||||||
branch = "all_features"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.num-traits]
|
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.dyn-clone]
|
[dependencies.dyn-clone]
|
||||||
version = "1"
|
version = "1"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.hashbrown]
|
[dependencies.hashbrown]
|
||||||
version = ">=0.14, <=0.15"
|
version = "0.14"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.heapless]
|
[dependencies.heapless]
|
||||||
version = "0.8"
|
version = "0.7"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.num-traits]
|
||||||
|
version = "0.2"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.downcast-rs]
|
[dependencies.downcast-rs]
|
||||||
version = "1.2"
|
version = "1.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
@ -67,8 +59,8 @@ default-features = false
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.thiserror]
|
[dependencies.thiserror]
|
||||||
version = "2"
|
version = "1"
|
||||||
default-features = false
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
@ -80,22 +72,25 @@ version = "0.5.4"
|
|||||||
features = ["all"]
|
features = ["all"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.mio]
|
[dependencies.spacepackets]
|
||||||
version = "1"
|
version = "0.7.0-beta.2"
|
||||||
features = ["os-poll", "net"]
|
default-features = false
|
||||||
optional = true
|
# git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git"
|
||||||
|
# rev = "79d26e1a6"
|
||||||
|
# branch = ""
|
||||||
|
|
||||||
[dependencies.defmt]
|
[dependencies.cobs]
|
||||||
version = "0.3"
|
git = "https://github.com/robamu/cobs.rs.git"
|
||||||
optional = true
|
version = "0.2.3"
|
||||||
|
branch = "all_features"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = "1"
|
serde = "1"
|
||||||
zerocopy = "0.8"
|
zerocopy = "0.7"
|
||||||
once_cell = "1"
|
once_cell = "1.13"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
tempfile = "3"
|
|
||||||
|
|
||||||
[dev-dependencies.postcard]
|
[dev-dependencies.postcard]
|
||||||
version = "1"
|
version = "1"
|
||||||
@ -111,9 +106,8 @@ std = [
|
|||||||
"serde/std",
|
"serde/std",
|
||||||
"spacepackets/std",
|
"spacepackets/std",
|
||||||
"num_enum/std",
|
"num_enum/std",
|
||||||
"thiserror/std",
|
"thiserror",
|
||||||
"socket2",
|
"socket2"
|
||||||
"mio"
|
|
||||||
]
|
]
|
||||||
alloc = [
|
alloc = [
|
||||||
"serde/alloc",
|
"serde/alloc",
|
||||||
@ -122,12 +116,11 @@ alloc = [
|
|||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"downcast-rs"
|
"downcast-rs"
|
||||||
]
|
]
|
||||||
serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"]
|
serde = ["dep:serde", "spacepackets/serde"]
|
||||||
crossbeam = ["crossbeam-channel"]
|
crossbeam = ["crossbeam-channel"]
|
||||||
heapless = ["dep:heapless"]
|
heapless = ["dep:heapless"]
|
||||||
defmt = ["dep:defmt", "spacepackets/defmt"]
|
doc-images = []
|
||||||
test_util = []
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--cfg", "doc_cfg"]
|
9
satrs-core/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[![Crates.io](https://img.shields.io/crates/v/satrs-core)](https://crates.io/crates/satrs-core)
|
||||||
|
[![docs.rs](https://img.shields.io/docsrs/satrs-core)](https://docs.rs/satrs-core)
|
||||||
|
|
||||||
|
satrs-core
|
||||||
|
======
|
||||||
|
|
||||||
|
This crate contains the core components of the sat-rs framework.
|
||||||
|
You can find more information on [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
||||||
|
|
259
satrs-core/images/event_man_arch.graphml
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
|
<!--Created by yEd 3.22-->
|
||||||
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
|
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||||
|
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||||
|
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||||
|
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||||
|
<graph edgedefault="directed" id="G">
|
||||||
|
<data key="d0" xml:space="preserve"/>
|
||||||
|
<node id="n0">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="509.9999999999999" width="768.7000000000003" x="579.3105418719211" y="304.7"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.936037063598633" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="150.1282958984375" x="26.197490701913352" xml:space="preserve" y="24.234711021505348">Example Event Flow<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.46591974671274444" nodeRatioY="-0.452480958781362" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="203.0" x="814.0" y="506.6799999999999"/>
|
||||||
|
<y:Fill color="#FFFF00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.21258544921875" x="58.393707275390625" xml:space="preserve" y="21.27395296096796">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="82.0" x="617.6" y="413.23"/>
|
||||||
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="13.4398193359375" xml:space="preserve" y="14.547905921936035">Event
|
||||||
|
Creator 0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n3">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="76.55999999999995" x="988.5" y="335.62999999999994"/>
|
||||||
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="10.719819335937473" xml:space="preserve" y="14.547905921936035">Event
|
||||||
|
Creator 2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n4">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="72.55999999999983" x="860.6610837438426" y="335.62999999999994"/>
|
||||||
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="8.719819335937359" xml:space="preserve" y="14.547905921936035">Event
|
||||||
|
Creator 1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n5">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="87.27999999999997" x="1112.52" y="335.62999999999994"/>
|
||||||
|
<y:Fill color="#FF9900" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="16.079819335937373" xml:space="preserve" y="14.547905921936035">Event
|
||||||
|
Creator 3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n6">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="126.0" x="781.0" y="620.26"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.78865051269531" x="16.605674743652344" xml:space="preserve" y="14.547905921936035">PUS Service 5
|
||||||
|
Event Reporting
|
||||||
|
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n7">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="118.63999999999987" x="928.2" y="620.26"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.08859252929688" x="17.2757037353515" xml:space="preserve" y="14.547905921936035">PUS Service 19
|
||||||
|
Event Action<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n8">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.0" width="87.27999999999997" x="792.1260377358491" y="733.8400000000001"/>
|
||||||
|
<y:Fill color="#FFCC99" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="59.932403564453125" x="13.673798217773424" xml:space="preserve" y="14.547905921936035">Telemetry
|
||||||
|
Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n9">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="170.79999999999995" width="210.80000000000018" x="1076.84" y="601.88"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="left" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="143.6875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="181.591796875" x="8.373079774614325" xml:space="preserve" y="7.444138124199753">Subscriptions
|
||||||
|
|
||||||
|
1. Event Creator 0 subscribes
|
||||||
|
for event 0
|
||||||
|
2. Event Creator 1 subscribes
|
||||||
|
for event group 2
|
||||||
|
3. PUS Service 5 handler
|
||||||
|
subscribes for all events
|
||||||
|
4. PUS Service 19 handler
|
||||||
|
subscribes for all events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4602795077105583" nodeRatioY="-0.45641605313700395" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<edge id="e0" source="n4" target="n1">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="8.058916256157545" sy="0.0" tx="-10.5" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="8.639817810058275" xml:space="preserve" y="29.00100609374465">event 1
|
||||||
|
(group 1)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="35.59999999999969" distanceToCenter="true" position="left" ratio="0.34252387409930674" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e1" source="n2" target="n1">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="11.93999999999994" tx="-83.5" ty="0.0">
|
||||||
|
<y:Point x="832.0" y="455.16999999999996"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="25.334655000000453" xml:space="preserve" y="-40.972107505798476">event 0
|
||||||
|
(group 0)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="25.520000000000095" distanceToCenter="true" position="left" ratio="0.20267159489379444" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e2" source="n3" target="n1">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-23.719999999999914" sy="5.5" tx="87.56000000000006" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="5.6761352539062955" xml:space="preserve" y="27.551854405966765">event 2
|
||||||
|
(group 3)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="5.676132812499983" distanceToCenter="false" position="left" ratio="0.3219761157957032" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e3" source="n5" target="n1">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-6.275467980295616" sy="0.0" tx="57.5" ty="8.5">
|
||||||
|
<y:Point x="1149.8845320197042" y="545.1799999999998"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.38468933105469" x="26.667665869801795" xml:space="preserve" y="43.287014528669715">event 3 (group 2)
|
||||||
|
event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="75.3599999999999" distanceToCenter="true" position="left" ratio="0.2967848459873102" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e4" source="n1" target="n6">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-65.0" sy="0.0" tx="6.5" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="-98.78228302001958" xml:space="preserve" y="16.63042580701972"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="57.20000000000004" distanceToCenter="true" position="right" ratio="0.4441995640590947" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e5" source="n1" target="n7">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="42.660000000000196" sy="0.0" tx="-29.359999999999786" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="20.4177438354493" xml:space="preserve" y="17.885881494816203"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.0" distanceToCenter="true" position="left" ratio="0.492249939452652" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e6" source="n1" target="n2">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="658.6" y="536.6799999999998"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.69230651855469" x="-131.99129340961497" xml:space="preserve" y="-45.45208675384538">event 1
|
||||||
|
event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6426904695623505" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e7" source="n1" target="n4">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-35.69940886699487" sy="0.0" tx="-17.140492610837327" ty="1.5"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.14430236816406" x="-54.352158195608126" xml:space="preserve" y="-79.29459128622307">group 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="31.279999999999973" distanceToCenter="true" position="left" ratio="0.6800790648728832" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e8" source="n6" target="n8">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="8.233962264150945" ty="-21.42352238805968"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="87.40060424804688" x="-100.50030212402339" xml:space="preserve" y="11.337896156311103">enabled Events
|
||||||
|
as PUS 5 TM<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="56.79999999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
</graph>
|
||||||
|
<data key="d7">
|
||||||
|
<y:Resources/>
|
||||||
|
</data>
|
||||||
|
</graphml>
|
BIN
satrs-core/images/event_man_arch.png
Normal file
After Width: | Height: | Size: 70 KiB |
@ -3,11 +3,12 @@ Checklist for new releases
|
|||||||
|
|
||||||
# Pre-Release
|
# Pre-Release
|
||||||
|
|
||||||
1. Make sure any new modules are documented sufficiently enough and check docs by running `docs.sh`.
|
1. Make sure any new modules are documented sufficiently enough and check docs with
|
||||||
|
`cargo +nightly doc --all-features --config 'rustdocflags=["--cfg", "doc_cfg"]' --open`.
|
||||||
2. Bump version specifier in `Cargo.toml`.
|
2. Bump version specifier in `Cargo.toml`.
|
||||||
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
|
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
|
||||||
`unreleased` section.
|
`unreleased` section.
|
||||||
4. Run `cargo test --all-features` or `cargo nextest r --all-features` and `cargo test --doc`.
|
4. Run `cargo test --all-features`.
|
||||||
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
|
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
|
||||||
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
|
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
|
||||||
targets.
|
targets.
|
||||||
@ -18,5 +19,7 @@ Checklist for new releases
|
|||||||
|
|
||||||
# Post-Release
|
# Post-Release
|
||||||
|
|
||||||
1. Create a new release on `EGit` with the name `satrs-<version>`.
|
1. Create a new annotaged tag and push it with `git tag -a satrs-core-<version>` and
|
||||||
|
`git push -u origin satrs-core-<version>`
|
||||||
|
2. Create a new release on `EGit` based on the tag.
|
||||||
|
|
871
satrs-core/src/cfdp/dest.rs
Normal file
@ -0,0 +1,871 @@
|
|||||||
|
use core::str::{from_utf8, Utf8Error};
|
||||||
|
use std::{
|
||||||
|
fs::{metadata, File},
|
||||||
|
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cfdp::user::TransactionFinishedParams;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
user::{CfdpUser, MetadataReceivedParams},
|
||||||
|
PacketInfo, PacketTarget, State, TransactionId, TransactionStep, CRC_32,
|
||||||
|
};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
|
pdu::{
|
||||||
|
eof::EofPdu,
|
||||||
|
file_data::FileDataPdu,
|
||||||
|
finished::{DeliveryCode, FileStatus, FinishedPdu},
|
||||||
|
metadata::{MetadataGenericParams, MetadataPdu},
|
||||||
|
CommonPduConfig, FileDirectiveType, PduError, PduHeader,
|
||||||
|
},
|
||||||
|
tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, TlvType},
|
||||||
|
ConditionCode, PduType, TransmissionMode,
|
||||||
|
},
|
||||||
|
util::UnsignedByteField,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub struct DestinationHandler {
|
||||||
|
id: UnsignedByteField,
|
||||||
|
step: TransactionStep,
|
||||||
|
state: State,
|
||||||
|
tparams: TransactionParams,
|
||||||
|
packets_to_send_ctx: PacketsToSendContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct PacketsToSendContext {
|
||||||
|
packet_available: bool,
|
||||||
|
directive: Option<FileDirectiveType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FileProperties {
|
||||||
|
src_file_name: [u8; u8::MAX as usize],
|
||||||
|
src_file_name_len: usize,
|
||||||
|
dest_file_name: [u8; u8::MAX as usize],
|
||||||
|
dest_file_name_len: usize,
|
||||||
|
dest_path_buf: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TransferState {
|
||||||
|
transaction_id: Option<TransactionId>,
|
||||||
|
progress: usize,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
delivery_code: DeliveryCode,
|
||||||
|
file_status: FileStatus,
|
||||||
|
metadata_params: MetadataGenericParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TransferState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
transaction_id: None,
|
||||||
|
progress: Default::default(),
|
||||||
|
condition_code: ConditionCode::NoError,
|
||||||
|
delivery_code: DeliveryCode::Incomplete,
|
||||||
|
file_status: FileStatus::Unreported,
|
||||||
|
metadata_params: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TransactionParams {
|
||||||
|
tstate: TransferState,
|
||||||
|
pdu_conf: CommonPduConfig,
|
||||||
|
file_properties: FileProperties,
|
||||||
|
cksum_buf: [u8; 1024],
|
||||||
|
msgs_to_user_size: usize,
|
||||||
|
msgs_to_user_buf: [u8; 1024],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileProperties {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
src_file_name: [0; u8::MAX as usize],
|
||||||
|
src_file_name_len: Default::default(),
|
||||||
|
dest_file_name: [0; u8::MAX as usize],
|
||||||
|
dest_file_name_len: Default::default(),
|
||||||
|
dest_path_buf: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionParams {
|
||||||
|
fn file_size(&self) -> usize {
|
||||||
|
self.tstate.metadata_params.file_size as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata_params(&self) -> &MetadataGenericParams {
|
||||||
|
&self.tstate.metadata_params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TransactionParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pdu_conf: Default::default(),
|
||||||
|
cksum_buf: [0; 1024],
|
||||||
|
msgs_to_user_size: 0,
|
||||||
|
msgs_to_user_buf: [0; 1024],
|
||||||
|
tstate: Default::default(),
|
||||||
|
file_properties: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionParams {
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.tstate.condition_code = ConditionCode::NoError;
|
||||||
|
self.tstate.delivery_code = DeliveryCode::Incomplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DestError {
|
||||||
|
/// File directive expected, but none specified
|
||||||
|
#[error("expected file directive")]
|
||||||
|
DirectiveExpected,
|
||||||
|
#[error("can not process packet type {0:?}")]
|
||||||
|
CantProcessPacketType(FileDirectiveType),
|
||||||
|
#[error("can not process file data PDUs in current state")]
|
||||||
|
WrongStateForFileDataAndEof,
|
||||||
|
// Received new metadata PDU while being already being busy with a file transfer.
|
||||||
|
#[error("busy with transfer")]
|
||||||
|
RecvdMetadataButIsBusy,
|
||||||
|
#[error("empty source file field")]
|
||||||
|
EmptySrcFileField,
|
||||||
|
#[error("empty dest file field")]
|
||||||
|
EmptyDestFileField,
|
||||||
|
#[error("pdu error {0}")]
|
||||||
|
Pdu(#[from] PduError),
|
||||||
|
#[error("io error {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("path conversion error {0}")]
|
||||||
|
PathConversion(#[from] Utf8Error),
|
||||||
|
#[error("error building dest path from source file name and dest folder")]
|
||||||
|
PathConcatError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DestinationHandler {
|
||||||
|
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
step: TransactionStep::Idle,
|
||||||
|
state: State::Idle,
|
||||||
|
tparams: Default::default(),
|
||||||
|
packets_to_send_ctx: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_machine(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
||||||
|
match self.state {
|
||||||
|
State::Idle => todo!(),
|
||||||
|
State::BusyClass1Nacked => self.fsm_nacked(cfdp_user),
|
||||||
|
State::BusyClass2Acked => todo!("acknowledged mode not implemented yet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_packet(&mut self, packet_info: &PacketInfo) -> Result<(), DestError> {
|
||||||
|
if packet_info.target() != PacketTarget::DestEntity {
|
||||||
|
// Unwrap is okay here, a PacketInfo for a file data PDU should always have the
|
||||||
|
// destination as the target.
|
||||||
|
return Err(DestError::CantProcessPacketType(
|
||||||
|
packet_info.pdu_directive().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match packet_info.pdu_type {
|
||||||
|
PduType::FileDirective => {
|
||||||
|
if packet_info.pdu_directive.is_none() {
|
||||||
|
return Err(DestError::DirectiveExpected);
|
||||||
|
}
|
||||||
|
self.handle_file_directive(
|
||||||
|
packet_info.pdu_directive.unwrap(),
|
||||||
|
packet_info.raw_packet,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PduType::FileData => self.handle_file_data(packet_info.raw_packet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packet_to_send_ready(&self) -> bool {
|
||||||
|
self.packets_to_send_ctx.packet_available
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_next_packet_to_send(
|
||||||
|
&self,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<Option<(FileDirectiveType, usize)>, DestError> {
|
||||||
|
if !self.packet_to_send_ready() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let directive = self.packets_to_send_ctx.directive.unwrap();
|
||||||
|
let written_size = match directive {
|
||||||
|
FileDirectiveType::FinishedPdu => {
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0);
|
||||||
|
let finished_pdu = if self.tparams.tstate.condition_code == ConditionCode::NoError
|
||||||
|
|| self.tparams.tstate.condition_code == ConditionCode::UnsupportedChecksumType
|
||||||
|
{
|
||||||
|
FinishedPdu::new_default(
|
||||||
|
pdu_header,
|
||||||
|
self.tparams.tstate.delivery_code,
|
||||||
|
self.tparams.tstate.file_status,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// TODO: Are there cases where this ID is actually the source entity ID?
|
||||||
|
let entity_id = EntityIdTlv::new(self.id);
|
||||||
|
FinishedPdu::new_with_error(
|
||||||
|
pdu_header,
|
||||||
|
self.tparams.tstate.condition_code,
|
||||||
|
self.tparams.tstate.delivery_code,
|
||||||
|
self.tparams.tstate.file_status,
|
||||||
|
entity_id,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
finished_pdu.write_to_bytes(buf)?
|
||||||
|
}
|
||||||
|
FileDirectiveType::AckPdu => todo!(),
|
||||||
|
FileDirectiveType::NakPdu => todo!(),
|
||||||
|
FileDirectiveType::KeepAlivePdu => todo!(),
|
||||||
|
_ => {
|
||||||
|
// This should never happen and is considered an internal impl error
|
||||||
|
panic!("invalid file directive {directive:?} for dest handler send packet");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some((directive, written_size)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_file_directive(
|
||||||
|
&mut self,
|
||||||
|
pdu_directive: FileDirectiveType,
|
||||||
|
raw_packet: &[u8],
|
||||||
|
) -> Result<(), DestError> {
|
||||||
|
match pdu_directive {
|
||||||
|
FileDirectiveType::EofPdu => self.handle_eof_pdu(raw_packet)?,
|
||||||
|
FileDirectiveType::FinishedPdu
|
||||||
|
| FileDirectiveType::NakPdu
|
||||||
|
| FileDirectiveType::KeepAlivePdu => {
|
||||||
|
return Err(DestError::CantProcessPacketType(pdu_directive));
|
||||||
|
}
|
||||||
|
FileDirectiveType::AckPdu => {
|
||||||
|
todo!(
|
||||||
|
"check whether ACK pdu handling is applicable by checking the acked directive field"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?,
|
||||||
|
FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_metadata_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> {
|
||||||
|
if self.state != State::Idle {
|
||||||
|
return Err(DestError::RecvdMetadataButIsBusy);
|
||||||
|
}
|
||||||
|
let metadata_pdu = MetadataPdu::from_bytes(raw_packet)?;
|
||||||
|
self.tparams.reset();
|
||||||
|
self.tparams.tstate.metadata_params = *metadata_pdu.metadata_params();
|
||||||
|
let src_name = metadata_pdu.src_file_name();
|
||||||
|
if src_name.is_empty() {
|
||||||
|
return Err(DestError::EmptySrcFileField);
|
||||||
|
}
|
||||||
|
self.tparams.file_properties.src_file_name[..src_name.len_value()]
|
||||||
|
.copy_from_slice(src_name.value());
|
||||||
|
self.tparams.file_properties.src_file_name_len = src_name.len_value();
|
||||||
|
let dest_name = metadata_pdu.dest_file_name();
|
||||||
|
if dest_name.is_empty() {
|
||||||
|
return Err(DestError::EmptyDestFileField);
|
||||||
|
}
|
||||||
|
self.tparams.file_properties.dest_file_name[..dest_name.len_value()]
|
||||||
|
.copy_from_slice(dest_name.value());
|
||||||
|
self.tparams.file_properties.dest_file_name_len = dest_name.len_value();
|
||||||
|
self.tparams.pdu_conf = *metadata_pdu.pdu_header().common_pdu_conf();
|
||||||
|
self.tparams.msgs_to_user_size = 0;
|
||||||
|
if metadata_pdu.options().is_some() {
|
||||||
|
for option_tlv in metadata_pdu.options_iter().unwrap() {
|
||||||
|
if option_tlv.is_standard_tlv()
|
||||||
|
&& option_tlv.tlv_type().unwrap() == TlvType::MsgToUser
|
||||||
|
{
|
||||||
|
self.tparams
|
||||||
|
.msgs_to_user_buf
|
||||||
|
.copy_from_slice(option_tlv.raw_data().unwrap());
|
||||||
|
self.tparams.msgs_to_user_size += option_tlv.len_full();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.tparams.pdu_conf.trans_mode == TransmissionMode::Unacknowledged {
|
||||||
|
self.state = State::BusyClass1Nacked;
|
||||||
|
} else {
|
||||||
|
self.state = State::BusyClass2Acked;
|
||||||
|
}
|
||||||
|
self.step = TransactionStep::TransactionStart;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_file_data(&mut self, raw_packet: &[u8]) -> Result<(), DestError> {
|
||||||
|
if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus {
|
||||||
|
return Err(DestError::WrongStateForFileDataAndEof);
|
||||||
|
}
|
||||||
|
let fd_pdu = FileDataPdu::from_bytes(raw_packet)?;
|
||||||
|
let mut dest_file = File::options()
|
||||||
|
.write(true)
|
||||||
|
.open(&self.tparams.file_properties.dest_path_buf)?;
|
||||||
|
dest_file.seek(SeekFrom::Start(fd_pdu.offset()))?;
|
||||||
|
dest_file.write_all(fd_pdu.file_data())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_if)]
|
||||||
|
pub fn handle_eof_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> {
|
||||||
|
if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus {
|
||||||
|
return Err(DestError::WrongStateForFileDataAndEof);
|
||||||
|
}
|
||||||
|
let eof_pdu = EofPdu::from_bytes(raw_packet)?;
|
||||||
|
let checksum = eof_pdu.file_checksum();
|
||||||
|
// For a standard disk based file system, which is assumed to be used for now, the file
|
||||||
|
// will always be retained. This might change in the future.
|
||||||
|
self.tparams.tstate.file_status = FileStatus::Retained;
|
||||||
|
if self.checksum_check(checksum)? {
|
||||||
|
self.tparams.tstate.condition_code = ConditionCode::NoError;
|
||||||
|
self.tparams.tstate.delivery_code = DeliveryCode::Complete;
|
||||||
|
} else {
|
||||||
|
self.tparams.tstate.condition_code = ConditionCode::FileChecksumFailure;
|
||||||
|
}
|
||||||
|
// TODO: Check progress, and implement transfer completion timer as specified in the
|
||||||
|
// standard. This timer protects against out of order arrival of packets.
|
||||||
|
if self.tparams.tstate.progress != self.tparams.file_size() {}
|
||||||
|
if self.state == State::BusyClass1Nacked {
|
||||||
|
self.step = TransactionStep::TransferCompletion;
|
||||||
|
} else {
|
||||||
|
self.step = TransactionStep::SendingAckPdu;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checksum_check(&mut self, expected_checksum: u32) -> Result<bool, DestError> {
|
||||||
|
let mut digest = CRC_32.digest();
|
||||||
|
let file_to_check = File::open(&self.tparams.file_properties.dest_path_buf)?;
|
||||||
|
let mut buf_reader = BufReader::new(file_to_check);
|
||||||
|
loop {
|
||||||
|
let bytes_read = buf_reader.read(&mut self.tparams.cksum_buf)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
digest.update(&self.tparams.cksum_buf[0..bytes_read]);
|
||||||
|
}
|
||||||
|
if digest.finalize() == expected_checksum {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fsm_nacked(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
||||||
|
if self.step == TransactionStep::TransactionStart {
|
||||||
|
self.transaction_start(cfdp_user)?;
|
||||||
|
}
|
||||||
|
if self.step == TransactionStep::TransferCompletion {
|
||||||
|
self.transfer_completion(cfdp_user)?;
|
||||||
|
}
|
||||||
|
if self.step == TransactionStep::SendingAckPdu {
|
||||||
|
todo!("no support for acknowledged mode yet");
|
||||||
|
}
|
||||||
|
if self.step == TransactionStep::SendingFinishedPdu {
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the step, which denotes the exact step of a pending CFDP transaction when applicable.
|
||||||
|
pub fn step(&self) -> TransactionStep {
|
||||||
|
self.step
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the step, which denotes whether the CFDP handler is active, and which CFDP class
|
||||||
|
/// is used if it is active.
|
||||||
|
pub fn state(&self) -> State {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transaction_start(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
||||||
|
let dest_name = from_utf8(
|
||||||
|
&self.tparams.file_properties.dest_file_name
|
||||||
|
[..self.tparams.file_properties.dest_file_name_len],
|
||||||
|
)?;
|
||||||
|
let dest_path = Path::new(dest_name);
|
||||||
|
self.tparams.file_properties.dest_path_buf = dest_path.to_path_buf();
|
||||||
|
let source_id = self.tparams.pdu_conf.source_id();
|
||||||
|
let id = TransactionId::new(source_id, self.tparams.pdu_conf.transaction_seq_num);
|
||||||
|
let src_name = from_utf8(
|
||||||
|
&self.tparams.file_properties.src_file_name
|
||||||
|
[0..self.tparams.file_properties.src_file_name_len],
|
||||||
|
)?;
|
||||||
|
let mut msgs_to_user = SmallVec::<[MsgToUserTlv<'_>; 16]>::new();
|
||||||
|
let mut num_msgs_to_user = 0;
|
||||||
|
if self.tparams.msgs_to_user_size > 0 {
|
||||||
|
let mut index = 0;
|
||||||
|
while index < self.tparams.msgs_to_user_size {
|
||||||
|
// This should never panic as the validity of the options was checked beforehand.
|
||||||
|
let msgs_to_user_tlv =
|
||||||
|
MsgToUserTlv::from_bytes(&self.tparams.msgs_to_user_buf[index..])
|
||||||
|
.expect("message to user creation failed unexpectedly");
|
||||||
|
msgs_to_user.push(msgs_to_user_tlv);
|
||||||
|
index += msgs_to_user_tlv.len_full();
|
||||||
|
num_msgs_to_user += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let metadata_recvd_params = MetadataReceivedParams {
|
||||||
|
id,
|
||||||
|
source_id,
|
||||||
|
file_size: self.tparams.tstate.metadata_params.file_size,
|
||||||
|
src_file_name: src_name,
|
||||||
|
dest_file_name: dest_name,
|
||||||
|
msgs_to_user: &msgs_to_user[..num_msgs_to_user],
|
||||||
|
};
|
||||||
|
self.tparams.tstate.transaction_id = Some(id);
|
||||||
|
cfdp_user.metadata_recvd_indication(&metadata_recvd_params);
|
||||||
|
|
||||||
|
if dest_path.exists() {
|
||||||
|
let dest_metadata = metadata(dest_path)?;
|
||||||
|
if dest_metadata.is_dir() {
|
||||||
|
// Create new destination path by concatenating the last part of the source source
|
||||||
|
// name and the destination folder. For example, for a source file of /tmp/hello.txt
|
||||||
|
// and a destination name of /home/test, the resulting file name should be
|
||||||
|
// /home/test/hello.txt
|
||||||
|
let source_path = Path::new(from_utf8(
|
||||||
|
&self.tparams.file_properties.src_file_name
|
||||||
|
[..self.tparams.file_properties.src_file_name_len],
|
||||||
|
)?);
|
||||||
|
|
||||||
|
let source_name = source_path.file_name();
|
||||||
|
if source_name.is_none() {
|
||||||
|
return Err(DestError::PathConcatError);
|
||||||
|
}
|
||||||
|
let source_name = source_name.unwrap();
|
||||||
|
self.tparams.file_properties.dest_path_buf.push(source_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This function does exactly what we require: Create a new file if it does not exist yet
|
||||||
|
// and trucate an existing one.
|
||||||
|
File::create(&self.tparams.file_properties.dest_path_buf)?;
|
||||||
|
self.step = TransactionStep::ReceivingFileDataPdus;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
||||||
|
let transaction_finished_params = TransactionFinishedParams {
|
||||||
|
id: self.tparams.tstate.transaction_id.unwrap(),
|
||||||
|
condition_code: self.tparams.tstate.condition_code,
|
||||||
|
delivery_code: self.tparams.tstate.delivery_code,
|
||||||
|
file_status: self.tparams.tstate.file_status,
|
||||||
|
};
|
||||||
|
cfdp_user.transaction_finished_indication(&transaction_finished_params);
|
||||||
|
// This function should never be called with metadata parameters not set
|
||||||
|
if self.tparams.metadata_params().closure_requested {
|
||||||
|
self.prepare_finished_pdu()?;
|
||||||
|
self.step = TransactionStep::SendingFinishedPdu;
|
||||||
|
} else {
|
||||||
|
self.reset();
|
||||||
|
self.state = State::Idle;
|
||||||
|
self.step = TransactionStep::Idle;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.step = TransactionStep::Idle;
|
||||||
|
self.state = State::Idle;
|
||||||
|
self.packets_to_send_ctx.packet_available = false;
|
||||||
|
self.tparams.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_finished_pdu(&mut self) -> Result<(), DestError> {
|
||||||
|
self.packets_to_send_ctx.packet_available = true;
|
||||||
|
self.packets_to_send_ctx.directive = Some(FileDirectiveType::FinishedPdu);
|
||||||
|
self.step = TransactionStep::SendingFinishedPdu;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use core::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::println;
|
||||||
|
use std::{env::temp_dir, fs};
|
||||||
|
|
||||||
|
use alloc::{format, string::String};
|
||||||
|
use rand::Rng;
|
||||||
|
use spacepackets::{
|
||||||
|
cfdp::{lv::Lv, ChecksumType},
|
||||||
|
util::{UbfU16, UnsignedByteFieldU16},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
|
||||||
|
const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
|
||||||
|
|
||||||
|
const SRC_NAME: &str = "__cfdp__source-file";
|
||||||
|
const DEST_NAME: &str = "__cfdp__dest-file";
|
||||||
|
|
||||||
|
static ATOMIC_COUNTER: AtomicU8 = AtomicU8::new(0);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TestCfdpUser {
|
||||||
|
next_expected_seq_num: u64,
|
||||||
|
expected_full_src_name: String,
|
||||||
|
expected_full_dest_name: String,
|
||||||
|
expected_file_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCfdpUser {
|
||||||
|
fn generic_id_check(&self, id: &crate::cfdp::TransactionId) {
|
||||||
|
assert_eq!(id.source_id, LOCAL_ID.into());
|
||||||
|
assert_eq!(id.seq_num().value(), self.next_expected_seq_num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CfdpUser for TestCfdpUser {
|
||||||
|
fn transaction_indication(&mut self, id: &crate::cfdp::TransactionId) {
|
||||||
|
self.generic_id_check(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_sent_indication(&mut self, id: &crate::cfdp::TransactionId) {
|
||||||
|
self.generic_id_check(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transaction_finished_indication(
|
||||||
|
&mut self,
|
||||||
|
finished_params: &crate::cfdp::user::TransactionFinishedParams,
|
||||||
|
) {
|
||||||
|
self.generic_id_check(&finished_params.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata_recvd_indication(
|
||||||
|
&mut self,
|
||||||
|
md_recvd_params: &crate::cfdp::user::MetadataReceivedParams,
|
||||||
|
) {
|
||||||
|
self.generic_id_check(&md_recvd_params.id);
|
||||||
|
assert_eq!(
|
||||||
|
String::from(md_recvd_params.src_file_name),
|
||||||
|
self.expected_full_src_name
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
String::from(md_recvd_params.dest_file_name),
|
||||||
|
self.expected_full_dest_name
|
||||||
|
);
|
||||||
|
assert_eq!(md_recvd_params.msgs_to_user.len(), 0);
|
||||||
|
assert_eq!(md_recvd_params.source_id, LOCAL_ID.into());
|
||||||
|
assert_eq!(md_recvd_params.file_size as usize, self.expected_file_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_segment_recvd_indication(
|
||||||
|
&mut self,
|
||||||
|
_segment_recvd_params: &crate::cfdp::user::FileSegmentRecvdParams,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_indication(&mut self, _id: &crate::cfdp::TransactionId) {}
|
||||||
|
|
||||||
|
fn suspended_indication(
|
||||||
|
&mut self,
|
||||||
|
_id: &crate::cfdp::TransactionId,
|
||||||
|
_condition_code: ConditionCode,
|
||||||
|
) {
|
||||||
|
panic!("unexpected suspended indication");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resumed_indication(&mut self, _id: &crate::cfdp::TransactionId, _progresss: u64) {}
|
||||||
|
|
||||||
|
fn fault_indication(
|
||||||
|
&mut self,
|
||||||
|
_id: &crate::cfdp::TransactionId,
|
||||||
|
_condition_code: ConditionCode,
|
||||||
|
_progress: u64,
|
||||||
|
) {
|
||||||
|
panic!("unexpected fault indication");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abandoned_indication(
|
||||||
|
&mut self,
|
||||||
|
_id: &crate::cfdp::TransactionId,
|
||||||
|
_condition_code: ConditionCode,
|
||||||
|
_progress: u64,
|
||||||
|
) {
|
||||||
|
panic!("unexpected abandoned indication");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_recvd_indication(&mut self, id: &crate::cfdp::TransactionId) {
|
||||||
|
self.generic_id_check(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_check(handler: &DestinationHandler) {
|
||||||
|
assert_eq!(handler.state(), State::Idle);
|
||||||
|
assert_eq!(handler.step(), TransactionStep::Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_full_filenames() -> (PathBuf, PathBuf) {
|
||||||
|
let mut file_path = temp_dir();
|
||||||
|
let mut src_path = file_path.clone();
|
||||||
|
// Atomic counter used to allow concurrent tests.
|
||||||
|
let unique_counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
// Create unique test filenames.
|
||||||
|
let src_name_unique = format!("{SRC_NAME}{}.txt", unique_counter);
|
||||||
|
let dest_name_unique = format!("{DEST_NAME}{}.txt", unique_counter);
|
||||||
|
src_path.push(src_name_unique);
|
||||||
|
file_path.push(dest_name_unique);
|
||||||
|
(src_path, file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let dest_handler = DestinationHandler::new(REMOTE_ID);
|
||||||
|
init_check(&dest_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pdu_header(seq_num: impl Into<UnsignedByteField>) -> PduHeader {
|
||||||
|
let mut pdu_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(LOCAL_ID, REMOTE_ID, seq_num).unwrap();
|
||||||
|
pdu_conf.trans_mode = TransmissionMode::Unacknowledged;
|
||||||
|
PduHeader::new_no_file_data(pdu_conf, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_metadata_pdu<'filename>(
|
||||||
|
pdu_header: &PduHeader,
|
||||||
|
src_name: &'filename Path,
|
||||||
|
dest_name: &'filename Path,
|
||||||
|
file_size: u64,
|
||||||
|
) -> MetadataPdu<'filename, 'filename, 'static> {
|
||||||
|
let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, file_size);
|
||||||
|
MetadataPdu::new(
|
||||||
|
*pdu_header,
|
||||||
|
metadata_params,
|
||||||
|
Lv::new_from_str(src_name.as_os_str().to_str().unwrap()).unwrap(),
|
||||||
|
Lv::new_from_str(dest_name.as_os_str().to_str().unwrap()).unwrap(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_metadata_pdu(
|
||||||
|
metadata_pdu: &MetadataPdu,
|
||||||
|
buf: &mut [u8],
|
||||||
|
dest_handler: &mut DestinationHandler,
|
||||||
|
) {
|
||||||
|
let written_len = metadata_pdu
|
||||||
|
.write_to_bytes(buf)
|
||||||
|
.expect("writing metadata PDU failed");
|
||||||
|
let packet_info =
|
||||||
|
PacketInfo::new(&buf[..written_len]).expect("generating packet info failed");
|
||||||
|
let insert_result = dest_handler.insert_packet(&packet_info);
|
||||||
|
if let Err(e) = insert_result {
|
||||||
|
panic!("insert result error: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_eof_pdu(
|
||||||
|
file_data: &[u8],
|
||||||
|
pdu_header: &PduHeader,
|
||||||
|
buf: &mut [u8],
|
||||||
|
dest_handler: &mut DestinationHandler,
|
||||||
|
) {
|
||||||
|
let mut digest = CRC_32.digest();
|
||||||
|
digest.update(file_data);
|
||||||
|
let crc32 = digest.finalize();
|
||||||
|
let eof_pdu = EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64);
|
||||||
|
let result = eof_pdu.write_to_bytes(buf);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("generating packet info failed");
|
||||||
|
let result = dest_handler.insert_packet(&packet_info);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_file_transfer() {
|
||||||
|
let (src_name, dest_name) = init_full_filenames();
|
||||||
|
assert!(!Path::exists(&dest_name));
|
||||||
|
let mut buf: [u8; 512] = [0; 512];
|
||||||
|
let mut test_user = TestCfdpUser {
|
||||||
|
next_expected_seq_num: 0,
|
||||||
|
expected_full_src_name: src_name.to_string_lossy().into(),
|
||||||
|
expected_full_dest_name: dest_name.to_string_lossy().into(),
|
||||||
|
expected_file_size: 0,
|
||||||
|
};
|
||||||
|
// We treat the destination handler like it is a remote entity.
|
||||||
|
let mut dest_handler = DestinationHandler::new(REMOTE_ID);
|
||||||
|
init_check(&dest_handler);
|
||||||
|
|
||||||
|
let seq_num = UbfU16::new(0);
|
||||||
|
let pdu_header = create_pdu_header(seq_num);
|
||||||
|
let metadata_pdu =
|
||||||
|
create_metadata_pdu(&pdu_header, src_name.as_path(), dest_name.as_path(), 0);
|
||||||
|
insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("dest handler fsm error: {e}");
|
||||||
|
}
|
||||||
|
assert_ne!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus);
|
||||||
|
|
||||||
|
insert_eof_pdu(&[], &pdu_header, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::Idle);
|
||||||
|
assert!(Path::exists(&dest_name));
|
||||||
|
let read_content = fs::read(&dest_name).expect("reading back string failed");
|
||||||
|
assert_eq!(read_content.len(), 0);
|
||||||
|
assert!(fs::remove_file(dest_name).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_small_file_transfer() {
|
||||||
|
let (src_name, dest_name) = init_full_filenames();
|
||||||
|
assert!(!Path::exists(&dest_name));
|
||||||
|
let file_data_str = "Hello World!";
|
||||||
|
let file_data = file_data_str.as_bytes();
|
||||||
|
let mut buf: [u8; 512] = [0; 512];
|
||||||
|
let mut test_user = TestCfdpUser {
|
||||||
|
next_expected_seq_num: 0,
|
||||||
|
expected_full_src_name: src_name.to_string_lossy().into(),
|
||||||
|
expected_full_dest_name: dest_name.to_string_lossy().into(),
|
||||||
|
expected_file_size: file_data.len(),
|
||||||
|
};
|
||||||
|
// We treat the destination handler like it is a remote entity.
|
||||||
|
let mut dest_handler = DestinationHandler::new(REMOTE_ID);
|
||||||
|
init_check(&dest_handler);
|
||||||
|
|
||||||
|
let seq_num = UbfU16::new(0);
|
||||||
|
let pdu_header = create_pdu_header(seq_num);
|
||||||
|
let metadata_pdu = create_metadata_pdu(
|
||||||
|
&pdu_header,
|
||||||
|
src_name.as_path(),
|
||||||
|
dest_name.as_path(),
|
||||||
|
file_data.len() as u64,
|
||||||
|
);
|
||||||
|
insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("dest handler fsm error: {e}");
|
||||||
|
}
|
||||||
|
assert_ne!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus);
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
let filedata_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, offset, file_data);
|
||||||
|
filedata_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
let result = dest_handler.insert_packet(&packet_info);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("destination handler packet insertion error: {e}");
|
||||||
|
}
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
insert_eof_pdu(file_data, &pdu_header, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::Idle);
|
||||||
|
|
||||||
|
assert!(Path::exists(&dest_name));
|
||||||
|
let read_content = fs::read_to_string(&dest_name).expect("reading back string failed");
|
||||||
|
assert_eq!(read_content, file_data_str);
|
||||||
|
assert!(fs::remove_file(dest_name).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_segmented_file_transfer() {
|
||||||
|
let (src_name, dest_name) = init_full_filenames();
|
||||||
|
assert!(!Path::exists(&dest_name));
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut random_data = [0u8; 512];
|
||||||
|
rng.fill(&mut random_data);
|
||||||
|
let mut buf: [u8; 512] = [0; 512];
|
||||||
|
let mut test_user = TestCfdpUser {
|
||||||
|
next_expected_seq_num: 0,
|
||||||
|
expected_full_src_name: src_name.to_string_lossy().into(),
|
||||||
|
expected_full_dest_name: dest_name.to_string_lossy().into(),
|
||||||
|
expected_file_size: random_data.len(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We treat the destination handler like it is a remote entity.
|
||||||
|
let mut dest_handler = DestinationHandler::new(REMOTE_ID);
|
||||||
|
init_check(&dest_handler);
|
||||||
|
|
||||||
|
let seq_num = UbfU16::new(0);
|
||||||
|
let pdu_header = create_pdu_header(seq_num);
|
||||||
|
let metadata_pdu = create_metadata_pdu(
|
||||||
|
&pdu_header,
|
||||||
|
src_name.as_path(),
|
||||||
|
dest_name.as_path(),
|
||||||
|
random_data.len() as u64,
|
||||||
|
);
|
||||||
|
insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("dest handler fsm error: {e}");
|
||||||
|
}
|
||||||
|
assert_ne!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus);
|
||||||
|
|
||||||
|
// First file data PDU
|
||||||
|
let mut offset: usize = 0;
|
||||||
|
let segment_len = 256;
|
||||||
|
let filedata_pdu = FileDataPdu::new_no_seg_metadata(
|
||||||
|
pdu_header,
|
||||||
|
offset as u64,
|
||||||
|
&random_data[0..segment_len],
|
||||||
|
);
|
||||||
|
filedata_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
let result = dest_handler.insert_packet(&packet_info);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("destination handler packet insertion error: {e}");
|
||||||
|
}
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Second file data PDU
|
||||||
|
offset += segment_len;
|
||||||
|
let filedata_pdu = FileDataPdu::new_no_seg_metadata(
|
||||||
|
pdu_header,
|
||||||
|
offset as u64,
|
||||||
|
&random_data[segment_len..],
|
||||||
|
);
|
||||||
|
filedata_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
let result = dest_handler.insert_packet(&packet_info);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("destination handler packet insertion error: {e}");
|
||||||
|
}
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
insert_eof_pdu(&random_data, &pdu_header, &mut buf, &mut dest_handler);
|
||||||
|
let result = dest_handler.state_machine(&mut test_user);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(dest_handler.state(), State::Idle);
|
||||||
|
assert_eq!(dest_handler.step(), TransactionStep::Idle);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
assert!(Path::exists(&dest_name));
|
||||||
|
let read_content = fs::read(&dest_name).expect("reading back string failed");
|
||||||
|
assert_eq!(read_content, random_data);
|
||||||
|
assert!(fs::remove_file(dest_name).is_ok());
|
||||||
|
}
|
||||||
|
}
|
320
satrs-core/src/cfdp/mod.rs
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
use crc::{Crc, CRC_32_CKSUM};
|
||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
|
pdu::{FileDirectiveType, PduError, PduHeader},
|
||||||
|
ChecksumType, PduType, TransmissionMode,
|
||||||
|
},
|
||||||
|
util::UnsignedByteField,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod dest;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod source;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityType {
|
||||||
|
Sending,
|
||||||
|
Receiving,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic abstraction for a check timer which has different functionality depending on whether
|
||||||
|
/// the using entity is the sending entity or the receiving entity for the unacknowledged
|
||||||
|
/// transmission mode.
|
||||||
|
///
|
||||||
|
/// For the sending entity, this timer determines the expiry period for declaring a check limit
|
||||||
|
/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
|
||||||
|
/// Also see 4.6.3.2 of the CFDP standard.
|
||||||
|
///
|
||||||
|
/// For the receiving entity, this timer determines the expiry period for incrementing a check
|
||||||
|
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||||
|
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
|
||||||
|
pub trait CheckTimerProvider {
|
||||||
|
fn has_expired(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic trait which allows CFDP entities to create check timers which are required to
|
||||||
|
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
|
||||||
|
/// and 4.6.3.3. The [CheckTimerProvider] provides more information about the purpose of the
|
||||||
|
/// check timer.
|
||||||
|
///
|
||||||
|
/// This trait also allows the creation of different check timers depending on
|
||||||
|
/// the ID of the local entity, the ID of the remote entity for a given transaction, and the
|
||||||
|
/// type of entity.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub trait CheckTimerCreator {
|
||||||
|
fn get_check_timer_provider(
|
||||||
|
local_id: &UnsignedByteField,
|
||||||
|
remote_id: &UnsignedByteField,
|
||||||
|
entity_type: EntityType,
|
||||||
|
) -> Box<dyn CheckTimerProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple implementation of the [CheckTimerProvider] trait assuming a standard runtime.
|
||||||
|
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub struct StdCheckTimer {
|
||||||
|
expiry_time_seconds: u64,
|
||||||
|
start_time: std::time::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl StdCheckTimer {
|
||||||
|
pub fn new(expiry_time_seconds: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
expiry_time_seconds,
|
||||||
|
start_time: std::time::Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl CheckTimerProvider for StdCheckTimer {
|
||||||
|
fn has_expired(&self) -> bool {
|
||||||
|
let elapsed_time = self.start_time.elapsed();
|
||||||
|
if elapsed_time.as_secs() > self.expiry_time_seconds {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RemoteEntityConfig {
|
||||||
|
pub entity_id: UnsignedByteField,
|
||||||
|
pub max_file_segment_len: usize,
|
||||||
|
pub closure_requeted_by_default: bool,
|
||||||
|
pub crc_on_transmission_by_default: bool,
|
||||||
|
pub default_transmission_mode: TransmissionMode,
|
||||||
|
pub default_crc_type: ChecksumType,
|
||||||
|
pub check_limit: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RemoteEntityConfigProvider {
|
||||||
|
fn get_remote_config(&self, remote_id: &UnsignedByteField) -> Option<&RemoteEntityConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct TransactionId {
|
||||||
|
source_id: UnsignedByteField,
|
||||||
|
seq_num: UnsignedByteField,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionId {
|
||||||
|
pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
|
||||||
|
Self { source_id, seq_num }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source_id(&self) -> &UnsignedByteField {
|
||||||
|
&self.source_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seq_num(&self) -> &UnsignedByteField {
|
||||||
|
&self.seq_num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum TransactionStep {
|
||||||
|
Idle = 0,
|
||||||
|
TransactionStart = 1,
|
||||||
|
ReceivingFileDataPdus = 2,
|
||||||
|
SendingAckPdu = 3,
|
||||||
|
TransferCompletion = 4,
|
||||||
|
SendingFinishedPdu = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum State {
|
||||||
|
Idle = 0,
|
||||||
|
BusyClass1Nacked = 2,
|
||||||
|
BusyClass2Acked = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum PacketTarget {
|
||||||
|
SourceEntity,
|
||||||
|
DestEntity,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a helper struct which contains base information about a particular PDU packet.
|
||||||
|
/// This is also necessary information for CFDP packet routing. For example, some packet types
|
||||||
|
/// like file data PDUs can only be used by CFDP source entities.
|
||||||
|
pub struct PacketInfo<'raw_packet> {
|
||||||
|
pdu_type: PduType,
|
||||||
|
pdu_directive: Option<FileDirectiveType>,
|
||||||
|
target: PacketTarget,
|
||||||
|
raw_packet: &'raw_packet [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'raw> PacketInfo<'raw> {
|
||||||
|
pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
|
||||||
|
let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
|
||||||
|
if pdu_header.pdu_type() == PduType::FileData {
|
||||||
|
return Ok(Self {
|
||||||
|
pdu_type: pdu_header.pdu_type(),
|
||||||
|
pdu_directive: None,
|
||||||
|
target: PacketTarget::DestEntity,
|
||||||
|
raw_packet,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if pdu_header.pdu_datafield_len() < 1 {
|
||||||
|
return Err(PduError::FormatError);
|
||||||
|
}
|
||||||
|
// Route depending on PDU type and directive type if applicable. Retrieve directive type
|
||||||
|
// from the raw stream for better performance (with sanity and directive code check).
|
||||||
|
// The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
|
||||||
|
// procedure.
|
||||||
|
let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
|
||||||
|
PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len],
|
||||||
|
expected: None,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let packet_target = match directive {
|
||||||
|
// Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
|
||||||
|
// the source handler
|
||||||
|
FileDirectiveType::NakPdu
|
||||||
|
| FileDirectiveType::FinishedPdu
|
||||||
|
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
|
||||||
|
// Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
|
||||||
|
// the destination handler
|
||||||
|
FileDirectiveType::MetadataPdu
|
||||||
|
| FileDirectiveType::EofPdu
|
||||||
|
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
|
||||||
|
// Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
|
||||||
|
// extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
|
||||||
|
// the source handler, for a Finished PDU, it is passed to the destination handler.
|
||||||
|
FileDirectiveType::AckPdu => {
|
||||||
|
let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1])
|
||||||
|
.map_err(|_| PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len],
|
||||||
|
expected: None,
|
||||||
|
})?;
|
||||||
|
if acked_directive == FileDirectiveType::EofPdu {
|
||||||
|
PacketTarget::SourceEntity
|
||||||
|
} else if acked_directive == FileDirectiveType::FinishedPdu {
|
||||||
|
PacketTarget::DestEntity
|
||||||
|
} else {
|
||||||
|
// TODO: Maybe a better error? This might be confusing..
|
||||||
|
return Err(PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len + 1],
|
||||||
|
expected: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
pdu_type: pdu_header.pdu_type(),
|
||||||
|
pdu_directive: Some(directive),
|
||||||
|
target: packet_target,
|
||||||
|
raw_packet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdu_type(&self) -> PduType {
|
||||||
|
self.pdu_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdu_directive(&self) -> Option<FileDirectiveType> {
|
||||||
|
self.pdu_directive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> PacketTarget {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_packet(&self) -> &[u8] {
|
||||||
|
self.raw_packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use spacepackets::cfdp::{
|
||||||
|
lv::Lv,
|
||||||
|
pdu::{
|
||||||
|
eof::EofPdu,
|
||||||
|
file_data::FileDataPdu,
|
||||||
|
metadata::{MetadataGenericParams, MetadataPdu},
|
||||||
|
CommonPduConfig, FileDirectiveType, PduHeader,
|
||||||
|
},
|
||||||
|
PduType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cfdp::PacketTarget;
|
||||||
|
|
||||||
|
use super::PacketInfo;
|
||||||
|
|
||||||
|
fn generic_pdu_header() -> PduHeader {
|
||||||
|
let pdu_conf = CommonPduConfig::default();
|
||||||
|
PduHeader::new_no_file_data(pdu_conf, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_metadata_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let metadata_params = MetadataGenericParams::default();
|
||||||
|
let src_file_name = "hello.txt";
|
||||||
|
let dest_file_name = "hello-dest.txt";
|
||||||
|
let src_lv = Lv::new_from_str(src_file_name).unwrap();
|
||||||
|
let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
|
||||||
|
let metadata_pdu = MetadataPdu::new(pdu_header, metadata_params, src_lv, dest_lv, None);
|
||||||
|
metadata_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing metadata PDU failed");
|
||||||
|
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||||
|
assert!(packet_info.pdu_directive().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
packet_info.pdu_directive().unwrap(),
|
||||||
|
FileDirectiveType::MetadataPdu
|
||||||
|
);
|
||||||
|
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filedata_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
|
||||||
|
file_data_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileData);
|
||||||
|
assert!(packet_info.pdu_directive().is_none());
|
||||||
|
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eof_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
|
||||||
|
eof_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||||
|
assert!(packet_info.pdu_directive().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
packet_info.pdu_directive().unwrap(),
|
||||||
|
FileDirectiveType::EofPdu
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
15
satrs-core/src/cfdp/source.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use spacepackets::util::UnsignedByteField;
|
||||||
|
|
||||||
|
pub struct SourceHandler {
|
||||||
|
id: UnsignedByteField,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceHandler {
|
||||||
|
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
|
||||||
|
Self { id: id.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
65
satrs-core/src/cfdp/user.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
|
pdu::{
|
||||||
|
file_data::RecordContinuationState,
|
||||||
|
finished::{DeliveryCode, FileStatus},
|
||||||
|
},
|
||||||
|
tlv::msg_to_user::MsgToUserTlv,
|
||||||
|
ConditionCode,
|
||||||
|
},
|
||||||
|
util::UnsignedByteField,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::TransactionId;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TransactionFinishedParams {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub condition_code: ConditionCode,
|
||||||
|
pub delivery_code: DeliveryCode,
|
||||||
|
pub file_status: FileStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub source_id: UnsignedByteField,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub src_file_name: &'src_file str,
|
||||||
|
pub dest_file_name: &'dest_file str,
|
||||||
|
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FileSegmentRecvdParams<'seg_meta> {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub offset: u64,
|
||||||
|
pub length: usize,
|
||||||
|
pub rec_cont_state: Option<RecordContinuationState>,
|
||||||
|
pub segment_metadata: Option<&'seg_meta [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CfdpUser {
|
||||||
|
fn transaction_indication(&mut self, id: &TransactionId);
|
||||||
|
fn eof_sent_indication(&mut self, id: &TransactionId);
|
||||||
|
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
|
||||||
|
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
|
||||||
|
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
|
||||||
|
// TODO: The standard does not strictly specify how the report information looks..
|
||||||
|
fn report_indication(&mut self, id: &TransactionId);
|
||||||
|
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
|
||||||
|
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
|
||||||
|
fn fault_indication(
|
||||||
|
&mut self,
|
||||||
|
id: &TransactionId,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
fn abandoned_indication(
|
||||||
|
&mut self,
|
||||||
|
id: &TransactionId,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
fn eof_recvd_indication(&mut self, id: &TransactionId);
|
||||||
|
}
|
269
satrs-core/src/encoding/ccsds.rs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use spacepackets::PacketId;
|
||||||
|
|
||||||
|
use crate::tmtc::ReceivesTcCore;
|
||||||
|
|
||||||
|
pub trait PacketIdLookup {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl PacketIdLookup for Vec<u16> {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.contains(&packet_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl PacketIdLookup for HashSet<u16> {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.contains(&packet_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketIdLookup for [u16] {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.binary_search(&packet_id).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl PacketIdLookup for Vec<PacketId> {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.contains(&PacketId::from(packet_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl PacketIdLookup for HashSet<PacketId> {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.contains(&PacketId::from(packet_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketIdLookup for [PacketId] {
|
||||||
|
fn validate(&self, packet_id: u16) -> bool {
|
||||||
|
self.binary_search(&PacketId::from(packet_id)).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the
|
||||||
|
/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then
|
||||||
|
/// uses the length field of the packet to extract CCSDS packets.
|
||||||
|
///
|
||||||
|
/// This function is also able to deal with broken tail packets at the end as long a the parser
|
||||||
|
/// can read the full 7 bytes which constitue a space packet header plus one byte minimal size.
|
||||||
|
/// If broken tail packets are detected, they are moved to the front of the buffer, and the write
|
||||||
|
/// index for future write operations will be written to the `next_write_idx` argument.
|
||||||
|
///
|
||||||
|
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`
|
||||||
|
/// and return the number of packets found. If the [ReceivesTcCore::pass_tc] calls fails, the
|
||||||
|
/// error will be returned.
|
||||||
|
pub fn parse_buffer_for_ccsds_space_packets<E>(
|
||||||
|
buf: &mut [u8],
|
||||||
|
packet_id_lookup: &(impl PacketIdLookup + ?Sized),
|
||||||
|
tc_receiver: &mut (impl ReceivesTcCore<Error = E> + ?Sized),
|
||||||
|
next_write_idx: &mut usize,
|
||||||
|
) -> Result<u32, E> {
|
||||||
|
*next_write_idx = 0;
|
||||||
|
let mut packets_found = 0;
|
||||||
|
let mut current_idx = 0;
|
||||||
|
let buf_len = buf.len();
|
||||||
|
loop {
|
||||||
|
if current_idx + 7 >= buf.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap());
|
||||||
|
if packet_id_lookup.validate(packet_id) {
|
||||||
|
let length_field =
|
||||||
|
u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap());
|
||||||
|
let packet_size = length_field + 7;
|
||||||
|
if (current_idx + packet_size as usize) <= buf_len {
|
||||||
|
tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?;
|
||||||
|
packets_found += 1;
|
||||||
|
} else {
|
||||||
|
// Move packet to start of buffer if applicable.
|
||||||
|
if current_idx > 0 {
|
||||||
|
buf.copy_within(current_idx.., 0);
|
||||||
|
*next_write_idx = buf.len() - current_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_idx += packet_size as usize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current_idx += 1;
|
||||||
|
}
|
||||||
|
Ok(packets_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use spacepackets::{
|
||||||
|
ecss::{tc::PusTcCreator, SerializablePusPacket},
|
||||||
|
PacketId, SpHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::encoding::tests::TcCacher;
|
||||||
|
|
||||||
|
use super::parse_buffer_for_ccsds_space_packets;
|
||||||
|
|
||||||
|
const TEST_APID_0: u16 = 0x02;
|
||||||
|
const TEST_APID_1: u16 = 0x10;
|
||||||
|
const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0);
|
||||||
|
const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
|
let mut buffer: [u8; 32] = [0; 32];
|
||||||
|
let packet_len = ping_tc
|
||||||
|
.write_to_bytes(&mut buffer)
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let valid_packet_ids = [TEST_PACKET_ID_0];
|
||||||
|
let mut tc_cacher = TcCacher::default();
|
||||||
|
let mut next_write_idx = 0;
|
||||||
|
let parse_result = parse_buffer_for_ccsds_space_packets(
|
||||||
|
&mut buffer,
|
||||||
|
valid_packet_ids.as_slice(),
|
||||||
|
&mut tc_cacher,
|
||||||
|
&mut next_write_idx,
|
||||||
|
);
|
||||||
|
assert!(parse_result.is_ok());
|
||||||
|
let parsed_packets = parse_result.unwrap();
|
||||||
|
assert_eq!(parsed_packets, 1);
|
||||||
|
assert_eq!(tc_cacher.tc_queue.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
tc_cacher.tc_queue.pop_front().unwrap(),
|
||||||
|
buffer[..packet_len]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multi_packet() {
|
||||||
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
|
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
||||||
|
let mut buffer: [u8; 32] = [0; 32];
|
||||||
|
let packet_len_ping = ping_tc
|
||||||
|
.write_to_bytes(&mut buffer)
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let packet_len_action = action_tc
|
||||||
|
.write_to_bytes(&mut buffer[packet_len_ping..])
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let valid_packet_ids = [TEST_PACKET_ID_0];
|
||||||
|
let mut tc_cacher = TcCacher::default();
|
||||||
|
let mut next_write_idx = 0;
|
||||||
|
let parse_result = parse_buffer_for_ccsds_space_packets(
|
||||||
|
&mut buffer,
|
||||||
|
valid_packet_ids.as_slice(),
|
||||||
|
&mut tc_cacher,
|
||||||
|
&mut next_write_idx,
|
||||||
|
);
|
||||||
|
assert!(parse_result.is_ok());
|
||||||
|
let parsed_packets = parse_result.unwrap();
|
||||||
|
assert_eq!(parsed_packets, 2);
|
||||||
|
assert_eq!(tc_cacher.tc_queue.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
tc_cacher.tc_queue.pop_front().unwrap(),
|
||||||
|
buffer[..packet_len_ping]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tc_cacher.tc_queue.pop_front().unwrap(),
|
||||||
|
buffer[packet_len_ping..packet_len_ping + packet_len_action]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multi_apid() {
|
||||||
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
|
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
||||||
|
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
||||||
|
let mut buffer: [u8; 32] = [0; 32];
|
||||||
|
let packet_len_ping = ping_tc
|
||||||
|
.write_to_bytes(&mut buffer)
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let packet_len_action = action_tc
|
||||||
|
.write_to_bytes(&mut buffer[packet_len_ping..])
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
||||||
|
let mut tc_cacher = TcCacher::default();
|
||||||
|
let mut next_write_idx = 0;
|
||||||
|
let parse_result = parse_buffer_for_ccsds_space_packets(
|
||||||
|
&mut buffer,
|
||||||
|
valid_packet_ids.as_slice(),
|
||||||
|
&mut tc_cacher,
|
||||||
|
&mut next_write_idx,
|
||||||
|
);
|
||||||
|
assert!(parse_result.is_ok());
|
||||||
|
let parsed_packets = parse_result.unwrap();
|
||||||
|
assert_eq!(parsed_packets, 2);
|
||||||
|
assert_eq!(tc_cacher.tc_queue.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
tc_cacher.tc_queue.pop_front().unwrap(),
|
||||||
|
buffer[..packet_len_ping]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tc_cacher.tc_queue.pop_front().unwrap(),
|
||||||
|
buffer[packet_len_ping..packet_len_ping + packet_len_action]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_packet_multi() {
|
||||||
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
|
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
||||||
|
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
||||||
|
let mut buffer: [u8; 32] = [0; 32];
|
||||||
|
let packet_len_ping = ping_tc
|
||||||
|
.write_to_bytes(&mut buffer)
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let packet_len_action = action_tc
|
||||||
|
.write_to_bytes(&mut buffer[packet_len_ping..])
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
||||||
|
let mut tc_cacher = TcCacher::default();
|
||||||
|
let mut next_write_idx = 0;
|
||||||
|
let parse_result = parse_buffer_for_ccsds_space_packets(
|
||||||
|
&mut buffer[..packet_len_ping + packet_len_action - 4],
|
||||||
|
valid_packet_ids.as_slice(),
|
||||||
|
&mut tc_cacher,
|
||||||
|
&mut next_write_idx,
|
||||||
|
);
|
||||||
|
assert!(parse_result.is_ok());
|
||||||
|
let parsed_packets = parse_result.unwrap();
|
||||||
|
assert_eq!(parsed_packets, 1);
|
||||||
|
assert_eq!(tc_cacher.tc_queue.len(), 1);
|
||||||
|
// The broken packet was moved to the start, so the next write index should be after the
|
||||||
|
// last segment missing 4 bytes.
|
||||||
|
assert_eq!(next_write_idx, packet_len_action - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_split_packet() {
|
||||||
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
|
let mut buffer: [u8; 32] = [0; 32];
|
||||||
|
let packet_len_ping = ping_tc
|
||||||
|
.write_to_bytes(&mut buffer)
|
||||||
|
.expect("writing packet failed");
|
||||||
|
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
||||||
|
let mut tc_cacher = TcCacher::default();
|
||||||
|
let mut next_write_idx = 0;
|
||||||
|
let parse_result = parse_buffer_for_ccsds_space_packets(
|
||||||
|
&mut buffer[..packet_len_ping - 4],
|
||||||
|
valid_packet_ids.as_slice(),
|
||||||
|
&mut tc_cacher,
|
||||||
|
&mut next_write_idx,
|
||||||
|
);
|
||||||
|
assert_eq!(next_write_idx, 0);
|
||||||
|
assert!(parse_result.is_ok());
|
||||||
|
let parsed_packets = parse_result.unwrap();
|
||||||
|
assert_eq!(parsed_packets, 0);
|
||||||
|
assert_eq!(tc_cacher.tc_queue.len(), 0);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{tmtc::PacketSenderRaw, ComponentId};
|
use crate::tmtc::ReceivesTcCore;
|
||||||
use cobs::{decode_in_place, encode, max_encoding_length};
|
use cobs::{decode_in_place, encode, max_encoding_length};
|
||||||
|
|
||||||
/// This function encodes the given packet with COBS and also wraps the encoded packet with
|
/// This function encodes the given packet with COBS and also wraps the encoded packet with
|
||||||
@ -13,7 +13,7 @@ use cobs::{decode_in_place, encode, max_encoding_length};
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use cobs::decode_in_place_report;
|
/// use cobs::decode_in_place_report;
|
||||||
/// use satrs::encoding::{encode_packet_with_cobs};
|
/// use satrs_core::encoding::{encode_packet_with_cobs};
|
||||||
//
|
//
|
||||||
/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5];
|
/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5];
|
||||||
/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1];
|
/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1];
|
||||||
@ -55,12 +55,11 @@ pub fn encode_packet_with_cobs(
|
|||||||
/// future write operations will be written to the `next_write_idx` argument.
|
/// future write operations will be written to the `next_write_idx` argument.
|
||||||
///
|
///
|
||||||
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`.
|
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`.
|
||||||
pub fn parse_buffer_for_cobs_encoded_packets<SendError>(
|
pub fn parse_buffer_for_cobs_encoded_packets<E>(
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
sender_id: ComponentId,
|
tc_receiver: &mut dyn ReceivesTcCore<Error = E>,
|
||||||
packet_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
|
|
||||||
next_write_idx: &mut usize,
|
next_write_idx: &mut usize,
|
||||||
) -> Result<u32, SendError> {
|
) -> Result<u32, E> {
|
||||||
let mut start_index_packet = 0;
|
let mut start_index_packet = 0;
|
||||||
let mut start_found = false;
|
let mut start_found = false;
|
||||||
let mut last_byte = false;
|
let mut last_byte = false;
|
||||||
@ -79,10 +78,8 @@ pub fn parse_buffer_for_cobs_encoded_packets<SendError>(
|
|||||||
let decode_result = decode_in_place(&mut buf[start_index_packet..i]);
|
let decode_result = decode_in_place(&mut buf[start_index_packet..i]);
|
||||||
if let Ok(packet_len) = decode_result {
|
if let Ok(packet_len) = decode_result {
|
||||||
packets_found += 1;
|
packets_found += 1;
|
||||||
packet_sender.send_packet(
|
tc_receiver
|
||||||
sender_id,
|
.pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?;
|
||||||
&buf[start_index_packet..start_index_packet + packet_len],
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
start_found = false;
|
start_found = false;
|
||||||
} else {
|
} else {
|
||||||
@ -103,39 +100,32 @@ pub fn parse_buffer_for_cobs_encoded_packets<SendError>(
|
|||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use cobs::encode;
|
use cobs::encode;
|
||||||
|
|
||||||
use crate::{
|
use crate::encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET};
|
||||||
encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET},
|
|
||||||
ComponentId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::parse_buffer_for_cobs_encoded_packets;
|
use super::parse_buffer_for_cobs_encoded_packets;
|
||||||
|
|
||||||
const PARSER_ID: ComponentId = 0x05;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parsing_simple_packet() {
|
fn test_parsing_simple_packet() {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut encoded_buf: [u8; 16] = [0; 16];
|
let mut encoded_buf: [u8; 16] = [0; 16];
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
||||||
let mut next_read_idx = 0;
|
let mut next_read_idx = 0;
|
||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
&mut encoded_buf[0..current_idx],
|
&mut encoded_buf[0..current_idx],
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_read_idx,
|
&mut next_read_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 1);
|
assert_eq!(packets, 1);
|
||||||
let queue = test_sender.tc_queue.borrow();
|
assert_eq!(test_sender.tc_queue.len(), 1);
|
||||||
assert_eq!(queue.len(), 1);
|
let packet = &test_sender.tc_queue[0];
|
||||||
let packet = &queue[0];
|
assert_eq!(packet, &SIMPLE_PACKET);
|
||||||
assert_eq!(packet.packet, &SIMPLE_PACKET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parsing_consecutive_packets() {
|
fn test_parsing_consecutive_packets() {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut encoded_buf: [u8; 16] = [0; 16];
|
let mut encoded_buf: [u8; 16] = [0; 16];
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
||||||
@ -149,23 +139,21 @@ pub(crate) mod tests {
|
|||||||
let mut next_read_idx = 0;
|
let mut next_read_idx = 0;
|
||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
&mut encoded_buf[0..current_idx],
|
&mut encoded_buf[0..current_idx],
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_read_idx,
|
&mut next_read_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 2);
|
assert_eq!(packets, 2);
|
||||||
let queue = test_sender.tc_queue.borrow();
|
assert_eq!(test_sender.tc_queue.len(), 2);
|
||||||
assert_eq!(queue.len(), 2);
|
let packet0 = &test_sender.tc_queue[0];
|
||||||
let packet0 = &queue[0];
|
assert_eq!(packet0, &SIMPLE_PACKET);
|
||||||
assert_eq!(packet0.packet, &SIMPLE_PACKET);
|
let packet1 = &test_sender.tc_queue[1];
|
||||||
let packet1 = &queue[1];
|
assert_eq!(packet1, &INVERTED_PACKET);
|
||||||
assert_eq!(packet1.packet, &INVERTED_PACKET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_tail_packet_only() {
|
fn test_split_tail_packet_only() {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut encoded_buf: [u8; 16] = [0; 16];
|
let mut encoded_buf: [u8; 16] = [0; 16];
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
encode_simple_packet(&mut encoded_buf, &mut current_idx);
|
||||||
@ -173,19 +161,17 @@ pub(crate) mod tests {
|
|||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
// Cut off the sentinel byte at the end.
|
// Cut off the sentinel byte at the end.
|
||||||
&mut encoded_buf[0..current_idx - 1],
|
&mut encoded_buf[0..current_idx - 1],
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_read_idx,
|
&mut next_read_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 0);
|
assert_eq!(packets, 0);
|
||||||
let queue = test_sender.tc_queue.borrow();
|
assert_eq!(test_sender.tc_queue.len(), 0);
|
||||||
assert_eq!(queue.len(), 0);
|
|
||||||
assert_eq!(next_read_idx, 0);
|
assert_eq!(next_read_idx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generic_test_split_packet(cut_off: usize) {
|
fn generic_test_split_packet(cut_off: usize) {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut encoded_buf: [u8; 16] = [0; 16];
|
let mut encoded_buf: [u8; 16] = [0; 16];
|
||||||
assert!(cut_off < INVERTED_PACKET.len() + 1);
|
assert!(cut_off < INVERTED_PACKET.len() + 1);
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
@ -207,15 +193,13 @@ pub(crate) mod tests {
|
|||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
// Cut off the sentinel byte at the end.
|
// Cut off the sentinel byte at the end.
|
||||||
&mut encoded_buf[0..current_idx - cut_off],
|
&mut encoded_buf[0..current_idx - cut_off],
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 1);
|
assert_eq!(packets, 1);
|
||||||
let queue = test_sender.tc_queue.borrow();
|
assert_eq!(test_sender.tc_queue.len(), 1);
|
||||||
assert_eq!(queue.len(), 1);
|
assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET);
|
||||||
assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
|
|
||||||
assert_eq!(next_write_idx, next_expected_write_idx);
|
assert_eq!(next_write_idx, next_expected_write_idx);
|
||||||
assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start);
|
assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start);
|
||||||
}
|
}
|
||||||
@ -237,7 +221,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zero_at_end() {
|
fn test_zero_at_end() {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut encoded_buf: [u8; 16] = [0; 16];
|
let mut encoded_buf: [u8; 16] = [0; 16];
|
||||||
let mut next_write_idx = 0;
|
let mut next_write_idx = 0;
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
@ -249,35 +233,31 @@ pub(crate) mod tests {
|
|||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
// Cut off the sentinel byte at the end.
|
// Cut off the sentinel byte at the end.
|
||||||
&mut encoded_buf[0..current_idx],
|
&mut encoded_buf[0..current_idx],
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 1);
|
assert_eq!(packets, 1);
|
||||||
let queue = test_sender.tc_queue.borrow_mut();
|
assert_eq!(test_sender.tc_queue.len(), 1);
|
||||||
assert_eq!(queue.len(), 1);
|
assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET);
|
||||||
assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
|
|
||||||
assert_eq!(next_write_idx, 1);
|
assert_eq!(next_write_idx, 1);
|
||||||
assert_eq!(encoded_buf[0], 0);
|
assert_eq!(encoded_buf[0], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_all_zeroes() {
|
fn test_all_zeroes() {
|
||||||
let test_sender = TcCacher::default();
|
let mut test_sender = TcCacher::default();
|
||||||
let mut all_zeroes: [u8; 5] = [0; 5];
|
let mut all_zeroes: [u8; 5] = [0; 5];
|
||||||
let mut next_write_idx = 0;
|
let mut next_write_idx = 0;
|
||||||
let packets = parse_buffer_for_cobs_encoded_packets(
|
let packets = parse_buffer_for_cobs_encoded_packets(
|
||||||
// Cut off the sentinel byte at the end.
|
// Cut off the sentinel byte at the end.
|
||||||
&mut all_zeroes,
|
&mut all_zeroes,
|
||||||
PARSER_ID,
|
&mut test_sender,
|
||||||
&test_sender,
|
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(packets, 0);
|
assert_eq!(packets, 0);
|
||||||
let queue = test_sender.tc_queue.borrow();
|
assert!(test_sender.tc_queue.is_empty());
|
||||||
assert!(queue.is_empty());
|
|
||||||
assert_eq!(next_write_idx, 0);
|
assert_eq!(next_write_idx, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,14 +6,9 @@ pub use crate::encoding::cobs::{encode_packet_with_cobs, parse_buffer_for_cobs_e
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use core::cell::RefCell;
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
|
||||||
use alloc::collections::VecDeque;
|
use crate::tmtc::ReceivesTcCore;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
tmtc::{PacketAsVec, PacketSenderRaw},
|
|
||||||
ComponentId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::cobs::encode_packet_with_cobs;
|
use super::cobs::encode_packet_with_cobs;
|
||||||
|
|
||||||
@ -22,15 +17,14 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct TcCacher {
|
pub(crate) struct TcCacher {
|
||||||
pub(crate) tc_queue: RefCell<VecDeque<PacketAsVec>>,
|
pub(crate) tc_queue: VecDeque<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketSenderRaw for TcCacher {
|
impl ReceivesTcCore for TcCacher {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
let mut mut_queue = self.tc_queue.borrow_mut();
|
self.tc_queue.push_back(tc_raw.to_vec());
|
||||||
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
49
satrs-core/src/error.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
pub enum FsrcGroupIds {
|
||||||
|
Tmtc = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FsrcErrorRaw {
|
||||||
|
pub group_id: u8,
|
||||||
|
pub unique_id: u8,
|
||||||
|
pub group_name: &'static str,
|
||||||
|
pub info: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FsrcErrorHandler {
|
||||||
|
fn error(&mut self, e: FsrcErrorRaw);
|
||||||
|
fn error_with_one_param(&mut self, e: FsrcErrorRaw, _p1: u32) {
|
||||||
|
self.error(e);
|
||||||
|
}
|
||||||
|
fn error_with_two_params(&mut self, e: FsrcErrorRaw, _p1: u32, _p2: u32) {
|
||||||
|
self.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FsrcErrorRaw {
|
||||||
|
pub const fn new(
|
||||||
|
group_id: u8,
|
||||||
|
unique_id: u8,
|
||||||
|
group_name: &'static str,
|
||||||
|
info: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
FsrcErrorRaw {
|
||||||
|
group_id,
|
||||||
|
unique_id,
|
||||||
|
group_name,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct SimpleStdErrorHandler {}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_std")]
|
||||||
|
impl FsrcErrorHandler for SimpleStdErrorHandler {
|
||||||
|
fn error(&mut self, e: FsrcErrorRaw) {
|
||||||
|
println!(
|
||||||
|
"Received error from group {} with ID ({},{}): {}",
|
||||||
|
e.group_name, e.group_id, e.unique_id, e.info
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
720
satrs-core/src/event_man.rs
Normal file
@ -0,0 +1,720 @@
|
|||||||
|
//! Event management and forwarding
|
||||||
|
//!
|
||||||
|
//! This module provides components to perform event routing. The most important component for this
|
||||||
|
//! task is the [EventManager]. It receives all events and then routes them to event subscribers
|
||||||
|
//! where appropriate.
|
||||||
|
#![cfg_attr(feature = "doc-images",
|
||||||
|
cfg_attr(all(),
|
||||||
|
doc = ::embed_doc_image::embed_image!("event_man_arch", "images/event_man_arch.png"
|
||||||
|
)))]
|
||||||
|
#![cfg_attr(
|
||||||
|
not(feature = "doc-images"),
|
||||||
|
doc = "**Doc images not enabled**. Compile with feature `doc-images` and Rust version >= 1.54 \
|
||||||
|
to enable."
|
||||||
|
)]
|
||||||
|
//! One common use case for satellite systems is to offer a light-weight publish-subscribe mechanism
|
||||||
|
//! and IPC mechanism for software and hardware events which are also packaged as telemetry (TM) or
|
||||||
|
//! can trigger a system response.
|
||||||
|
//!
|
||||||
|
//! The following graph shows how the event flow for such a setup could look like:
|
||||||
|
//!
|
||||||
|
//! ![Event flow][event_man_arch]
|
||||||
|
//!
|
||||||
|
//! The event manager has a listener table abstracted by the [ListenerTable], which maps
|
||||||
|
//! listener groups identified by [ListenerKey]s to a [sender ID][ChannelId].
|
||||||
|
//! It also contains a sender table abstracted by the [SenderTable] which maps these sender IDs
|
||||||
|
//! to a concrete [SendEventProvider]s. A simple approach would be to use one send event provider
|
||||||
|
//! for each OBSW thread and then subscribe for all interesting events for a particular thread
|
||||||
|
//! using the send event provider ID.
|
||||||
|
//!
|
||||||
|
//! This can be done with the [EventManager] like this:
|
||||||
|
//!
|
||||||
|
//! 1. Provide a concrete [EventReceiver] implementation. This abstraction allow to use different
|
||||||
|
//! message queue backends. A straightforward implementation where dynamic memory allocation is
|
||||||
|
//! not a big concern could use [std::sync::mpsc::channel] to do this and is provided in
|
||||||
|
//! form of the [MpscEventReceiver].
|
||||||
|
//! 2. To set up event creators, create channel pairs using some message queue implementation.
|
||||||
|
//! Each event creator gets a (cloned) sender component which allows it to send events to the
|
||||||
|
//! manager.
|
||||||
|
//! 3. The event manager receives the receiver component as part of a [EventReceiver]
|
||||||
|
//! implementation so all events are routed to the manager.
|
||||||
|
//! 4. Create the [send event providers][SendEventProvider]s which allow routing events to
|
||||||
|
//! subscribers. You can now use their [sender IDs][SendEventProvider::id] to subscribe for
|
||||||
|
//! event groups, for example by using the [EventManager::subscribe_single] method.
|
||||||
|
//! 5. Add the send provider as well using the [EventManager::add_sender] call so the event
|
||||||
|
//! manager can route listener groups to a the send provider.
|
||||||
|
//!
|
||||||
|
//! Some components like a PUS Event Service or PUS Event Action Service might require all
|
||||||
|
//! events to package them as telemetry or start actions where applicable.
|
||||||
|
//! Other components might only be interested in certain events. For example, a thermal system
|
||||||
|
//! handler might only be interested in temperature events generated by a thermal sensor component.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! You can check [integration test](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/pus_events.rs)
|
||||||
|
//! for a concrete example using multi-threading where events are routed to
|
||||||
|
//! different threads.
|
||||||
|
use crate::events::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
|
||||||
|
use crate::params::{Params, ParamsHeapless};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::slice::Iter;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use crate::ChannelId;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use stdmod::*;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||||
|
pub enum ListenerKey {
|
||||||
|
Single(LargestEventRaw),
|
||||||
|
Group(LargestGroupIdRaw),
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type EventWithHeaplessAuxData<Event> = (Event, Option<ParamsHeapless>);
|
||||||
|
pub type EventU32WithHeaplessAuxData = EventWithHeaplessAuxData<EventU32>;
|
||||||
|
pub type EventU16WithHeaplessAuxData = EventWithHeaplessAuxData<EventU16>;
|
||||||
|
|
||||||
|
pub type EventWithAuxData<Event> = (Event, Option<Params>);
|
||||||
|
pub type EventU32WithAuxData = EventWithAuxData<EventU32>;
|
||||||
|
pub type EventU16WithAuxData = EventWithAuxData<EventU16>;
|
||||||
|
|
||||||
|
pub trait SendEventProvider<Provider: GenericEvent, AuxDataProvider = Params> {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn id(&self) -> ChannelId;
|
||||||
|
fn send_no_data(&self, event: Provider) -> Result<(), Self::Error> {
|
||||||
|
self.send(event, None)
|
||||||
|
}
|
||||||
|
fn send(&self, event: Provider, aux_data: Option<AuxDataProvider>) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic abstraction for an event receiver.
|
||||||
|
pub trait EventReceiver<Event: GenericEvent, AuxDataProvider = Params> {
|
||||||
|
/// This function has to be provided by any event receiver. A receive call may or may not return
|
||||||
|
/// an event.
|
||||||
|
///
|
||||||
|
/// To allow returning arbitrary additional auxiliary data, a mutable slice is passed to the
|
||||||
|
/// [Self::receive] call as well. Receivers can write data to this slice, but care must be taken
|
||||||
|
/// to avoid panics due to size missmatches or out of bound writes.
|
||||||
|
fn receive(&self) -> Option<(Event, Option<AuxDataProvider>)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ListenerTable {
|
||||||
|
fn get_listeners(&self) -> Vec<ListenerKey>;
|
||||||
|
fn contains_listener(&self, key: &ListenerKey) -> bool;
|
||||||
|
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>>;
|
||||||
|
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool;
|
||||||
|
fn remove_duplicates(&mut self, key: &ListenerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SenderTable<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params> {
|
||||||
|
fn contains_send_event_provider(&self, id: &ChannelId) -> bool;
|
||||||
|
fn get_send_event_provider(
|
||||||
|
&self,
|
||||||
|
id: &ChannelId,
|
||||||
|
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>;
|
||||||
|
fn add_send_event_provider(
|
||||||
|
&mut self,
|
||||||
|
send_provider: Box<
|
||||||
|
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
||||||
|
>,
|
||||||
|
) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic event manager implementation.
|
||||||
|
///
|
||||||
|
/// # Generics
|
||||||
|
///
|
||||||
|
/// * `SendProviderError`: [SendEventProvider] error type
|
||||||
|
/// * `Event`: Concrete event provider, currently either [EventU32] or [EventU16]
|
||||||
|
/// * `AuxDataProvider`: Concrete auxiliary data provider, currently either [Params] or
|
||||||
|
/// [ParamsHeapless]
|
||||||
|
pub struct EventManager<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params>
|
||||||
|
{
|
||||||
|
listener_table: Box<dyn ListenerTable>,
|
||||||
|
sender_table: Box<dyn SenderTable<SendProviderError, Event, AuxDataProvider>>,
|
||||||
|
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: It is safe to implement [Send] because all fields in the [EventManager] are [Send]
|
||||||
|
/// as well
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
unsafe impl<E, Event: GenericEvent + Send, AuxDataProvider: Send> Send
|
||||||
|
for EventManager<E, Event, AuxDataProvider>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub type EventManagerWithMpscQueue<Event, AuxDataProvider> = EventManager<
|
||||||
|
std::sync::mpsc::SendError<(Event, Option<AuxDataProvider>)>,
|
||||||
|
Event,
|
||||||
|
AuxDataProvider,
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EventRoutingResult<Event: GenericEvent, AuxDataProvider> {
|
||||||
|
/// No event was received
|
||||||
|
Empty,
|
||||||
|
/// An event was received and routed.
|
||||||
|
/// The first tuple entry will contain the number of recipients.
|
||||||
|
Handled(u32, Event, Option<AuxDataProvider>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EventRoutingError<E> {
|
||||||
|
SendError(E),
|
||||||
|
NoSendersForKey(ListenerKey),
|
||||||
|
NoSenderForId(ChannelId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EventRoutingErrorsWithResult<Event: GenericEvent, AuxDataProvider, E> {
|
||||||
|
pub result: EventRoutingResult<Event, AuxDataProvider>,
|
||||||
|
pub errors: [Option<EventRoutingError<E>>; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, Event: GenericEvent + Copy> EventManager<E, Event> {
|
||||||
|
pub fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||||
|
self.listener_table.remove_duplicates(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe for a unique event.
|
||||||
|
pub fn subscribe_single(&mut self, event: &Event, sender_id: ChannelId) {
|
||||||
|
self.update_listeners(ListenerKey::Single(event.raw_as_largest_type()), sender_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe for an event group.
|
||||||
|
pub fn subscribe_group(&mut self, group_id: LargestGroupIdRaw, sender_id: ChannelId) {
|
||||||
|
self.update_listeners(ListenerKey::Group(group_id), sender_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe for all events received by the manager.
|
||||||
|
///
|
||||||
|
/// For example, this can be useful for a handler component which sends every event as
|
||||||
|
/// a telemetry packet.
|
||||||
|
pub fn subscribe_all(&mut self, sender_id: ChannelId) {
|
||||||
|
self.update_listeners(ListenerKey::All, sender_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: 'static, Event: GenericEvent + Copy + 'static, AuxDataProvider: Clone + 'static>
|
||||||
|
EventManager<E, Event, AuxDataProvider>
|
||||||
|
{
|
||||||
|
/// Create an event manager where the sender table will be the [DefaultSenderTableProvider]
|
||||||
|
/// and the listener table will be the [DefaultListenerTableProvider].
|
||||||
|
pub fn new(event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>) -> Self {
|
||||||
|
let listener_table: Box<DefaultListenerTableProvider> = Box::default();
|
||||||
|
let sender_table: Box<DefaultSenderTableProvider<E, Event, AuxDataProvider>> =
|
||||||
|
Box::default();
|
||||||
|
Self::new_custom_tables(listener_table, sender_table, event_receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
|
||||||
|
EventManager<E, Event, AuxDataProvider>
|
||||||
|
{
|
||||||
|
pub fn new_custom_tables(
|
||||||
|
listener_table: Box<dyn ListenerTable>,
|
||||||
|
sender_table: Box<dyn SenderTable<E, Event, AuxDataProvider>>,
|
||||||
|
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
||||||
|
) -> Self {
|
||||||
|
EventManager {
|
||||||
|
listener_table,
|
||||||
|
sender_table,
|
||||||
|
event_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_sender(
|
||||||
|
&mut self,
|
||||||
|
send_provider: impl SendEventProvider<Event, AuxDataProvider, Error = E> + 'static,
|
||||||
|
) {
|
||||||
|
if !self
|
||||||
|
.sender_table
|
||||||
|
.contains_send_event_provider(&send_provider.id())
|
||||||
|
{
|
||||||
|
self.sender_table
|
||||||
|
.add_send_event_provider(Box::new(send_provider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_listeners(&mut self, key: ListenerKey, sender_id: ChannelId) {
|
||||||
|
self.listener_table.add_listener(key, sender_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function will use the cached event receiver and try to receive one event.
|
||||||
|
/// If an event was received, it will try to route that event to all subscribed event listeners.
|
||||||
|
/// If this works without any issues, the [EventRoutingResult] will contain context information
|
||||||
|
/// about the routed event.
|
||||||
|
///
|
||||||
|
/// This function will track up to 3 errors returned as part of the
|
||||||
|
/// [EventRoutingErrorsWithResult] error struct.
|
||||||
|
pub fn try_event_handling(
|
||||||
|
&self,
|
||||||
|
) -> Result<
|
||||||
|
EventRoutingResult<Event, AuxDataProvider>,
|
||||||
|
EventRoutingErrorsWithResult<Event, AuxDataProvider, E>,
|
||||||
|
> {
|
||||||
|
let mut err_idx = 0;
|
||||||
|
let mut err_slice = [None, None, None];
|
||||||
|
let mut num_recipients = 0;
|
||||||
|
let mut add_error = |error: EventRoutingError<E>| {
|
||||||
|
if err_idx < 3 {
|
||||||
|
err_slice[err_idx] = Some(error);
|
||||||
|
err_idx += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut send_handler =
|
||||||
|
|key: &ListenerKey, event: Event, aux_data: &Option<AuxDataProvider>| {
|
||||||
|
if self.listener_table.contains_listener(key) {
|
||||||
|
if let Some(ids) = self.listener_table.get_listener_ids(key) {
|
||||||
|
for id in ids {
|
||||||
|
if let Some(sender) = self.sender_table.get_send_event_provider(id) {
|
||||||
|
if let Err(e) = sender.send(event, aux_data.clone()) {
|
||||||
|
add_error(EventRoutingError::SendError(e));
|
||||||
|
} else {
|
||||||
|
num_recipients += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_error(EventRoutingError::NoSenderForId(*id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_error(EventRoutingError::NoSendersForKey(*key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some((event, aux_data)) = self.event_receiver.receive() {
|
||||||
|
let single_key = ListenerKey::Single(event.raw_as_largest_type());
|
||||||
|
send_handler(&single_key, event, &aux_data);
|
||||||
|
let group_key = ListenerKey::Group(event.group_id_as_largest_type());
|
||||||
|
send_handler(&group_key, event, &aux_data);
|
||||||
|
send_handler(&ListenerKey::All, event, &aux_data);
|
||||||
|
if err_idx > 0 {
|
||||||
|
return Err(EventRoutingErrorsWithResult {
|
||||||
|
result: EventRoutingResult::Handled(num_recipients, event, aux_data),
|
||||||
|
errors: err_slice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Ok(EventRoutingResult::Handled(num_recipients, event, aux_data));
|
||||||
|
}
|
||||||
|
Ok(EventRoutingResult::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DefaultListenerTableProvider {
|
||||||
|
listeners: HashMap<ListenerKey, Vec<ChannelId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultSenderTableProvider<
|
||||||
|
SendProviderError,
|
||||||
|
Event: GenericEvent = EventU32,
|
||||||
|
AuxDataProvider = Params,
|
||||||
|
> {
|
||||||
|
senders: HashMap<
|
||||||
|
ChannelId,
|
||||||
|
Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SendProviderError, Event: GenericEvent, AuxDataProvider> Default
|
||||||
|
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
senders: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListenerTable for DefaultListenerTableProvider {
|
||||||
|
fn get_listeners(&self) -> Vec<ListenerKey> {
|
||||||
|
let mut key_list = Vec::new();
|
||||||
|
for key in self.listeners.keys() {
|
||||||
|
key_list.push(*key);
|
||||||
|
}
|
||||||
|
key_list
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_listener(&self, key: &ListenerKey) -> bool {
|
||||||
|
self.listeners.contains_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>> {
|
||||||
|
self.listeners.get(key).map(|vec| vec.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool {
|
||||||
|
if let Some(existing_list) = self.listeners.get_mut(&key) {
|
||||||
|
existing_list.push(sender_id);
|
||||||
|
} else {
|
||||||
|
let new_list = vec![sender_id];
|
||||||
|
self.listeners.insert(key, new_list);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||||
|
if let Some(list) = self.listeners.get_mut(key) {
|
||||||
|
list.sort_unstable();
|
||||||
|
list.dedup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SendProviderError, Event: GenericEvent, AuxDataProvider>
|
||||||
|
SenderTable<SendProviderError, Event, AuxDataProvider>
|
||||||
|
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
||||||
|
{
|
||||||
|
fn contains_send_event_provider(&self, id: &ChannelId) -> bool {
|
||||||
|
self.senders.contains_key(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_send_event_provider(
|
||||||
|
&self,
|
||||||
|
id: &ChannelId,
|
||||||
|
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>> {
|
||||||
|
self.senders
|
||||||
|
.get(id)
|
||||||
|
.filter(|sender| sender.id() == *id)
|
||||||
|
.map(|v| v.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_send_event_provider(
|
||||||
|
&mut self,
|
||||||
|
send_provider: Box<
|
||||||
|
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
||||||
|
>,
|
||||||
|
) -> bool {
|
||||||
|
let id = send_provider.id();
|
||||||
|
if self.senders.contains_key(&id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.senders.insert(id, send_provider).is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod stdmod {
|
||||||
|
use super::*;
|
||||||
|
use crate::event_man::{EventReceiver, EventWithAuxData};
|
||||||
|
use crate::events::{EventU16, EventU32, GenericEvent};
|
||||||
|
use crate::params::Params;
|
||||||
|
use std::sync::mpsc::{Receiver, SendError, Sender};
|
||||||
|
|
||||||
|
pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> {
|
||||||
|
mpsc_receiver: Receiver<(Event, Option<Params>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Event: GenericEvent + Send> MpscEventReceiver<Event> {
|
||||||
|
pub fn new(receiver: Receiver<(Event, Option<Params>)>) -> Self {
|
||||||
|
Self {
|
||||||
|
mpsc_receiver: receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Event: GenericEvent + Send> EventReceiver<Event> for MpscEventReceiver<Event> {
|
||||||
|
fn receive(&self) -> Option<EventWithAuxData<Event>> {
|
||||||
|
if let Ok(event_and_data) = self.mpsc_receiver.try_recv() {
|
||||||
|
return Some(event_and_data);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MpscEventU32Receiver = MpscEventReceiver<EventU32>;
|
||||||
|
pub type MpscEventU16Receiver = MpscEventReceiver<EventU16>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MpscEventSendProvider<Event: GenericEvent + Send> {
|
||||||
|
id: u32,
|
||||||
|
sender: Sender<(Event, Option<Params>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Event: GenericEvent + Send> MpscEventSendProvider<Event> {
|
||||||
|
pub fn new(id: u32, sender: Sender<(Event, Option<Params>)>) -> Self {
|
||||||
|
Self { id, sender }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Event: GenericEvent + Send> SendEventProvider<Event> for MpscEventSendProvider<Event> {
|
||||||
|
type Error = SendError<(Event, Option<Params>)>;
|
||||||
|
|
||||||
|
fn id(&self) -> u32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
||||||
|
self.sender.send((event, aux_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MpscEventU32SendProvider = MpscEventSendProvider<EventU32>;
|
||||||
|
pub type MpscEventU16SendProvider = MpscEventSendProvider<EventU16>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::event_man::EventManager;
|
||||||
|
use crate::events::{EventU32, GenericEvent, Severity};
|
||||||
|
use crate::params::ParamsRaw;
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use std::format;
|
||||||
|
use std::sync::mpsc::{channel, Receiver, SendError, Sender};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MpscEventSenderQueue {
|
||||||
|
id: u32,
|
||||||
|
mpsc_sender: Sender<EventU32WithAuxData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MpscEventSenderQueue {
|
||||||
|
fn new(id: u32, mpsc_sender: Sender<EventU32WithAuxData>) -> Self {
|
||||||
|
Self { id, mpsc_sender }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendEventProvider<EventU32> for MpscEventSenderQueue {
|
||||||
|
type Error = SendError<EventU32WithAuxData>;
|
||||||
|
|
||||||
|
fn id(&self) -> u32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
fn send(&self, event: EventU32, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
||||||
|
self.mpsc_sender.send((event, aux_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_next_event(
|
||||||
|
expected: EventU32,
|
||||||
|
receiver: &Receiver<EventU32WithAuxData>,
|
||||||
|
) -> Option<Params> {
|
||||||
|
if let Ok(event) = receiver.try_recv() {
|
||||||
|
assert_eq!(event.0, expected);
|
||||||
|
return event.1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_handled_event(
|
||||||
|
res: EventRoutingResult<EventU32, Params>,
|
||||||
|
expected: EventU32,
|
||||||
|
expected_num_sent: u32,
|
||||||
|
) {
|
||||||
|
assert!(matches!(res, EventRoutingResult::Handled { .. }));
|
||||||
|
if let EventRoutingResult::Handled(num_recipients, event, _aux_data) = res {
|
||||||
|
assert_eq!(event, expected);
|
||||||
|
assert_eq!(num_recipients, expected_num_sent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generic_event_man() -> (
|
||||||
|
Sender<EventU32WithAuxData>,
|
||||||
|
EventManager<SendError<EventU32WithAuxData>>,
|
||||||
|
) {
|
||||||
|
let (event_sender, manager_queue) = channel();
|
||||||
|
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
||||||
|
(
|
||||||
|
event_sender,
|
||||||
|
EventManager::new(Box::new(event_man_receiver)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let (event_sender, mut event_man) = generic_event_man();
|
||||||
|
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||||
|
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||||
|
let (single_event_sender, single_event_receiver) = channel();
|
||||||
|
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
||||||
|
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
||||||
|
event_man.add_sender(single_event_listener);
|
||||||
|
let (group_event_sender_0, group_event_receiver_0) = channel();
|
||||||
|
let group_event_listener = MpscEventSenderQueue {
|
||||||
|
id: 1,
|
||||||
|
mpsc_sender: group_event_sender_0,
|
||||||
|
};
|
||||||
|
event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.id());
|
||||||
|
event_man.add_sender(group_event_listener);
|
||||||
|
|
||||||
|
// Test event with one listener
|
||||||
|
event_sender
|
||||||
|
.send((event_grp_0, None))
|
||||||
|
.expect("Sending single error failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||||
|
check_next_event(event_grp_0, &single_event_receiver);
|
||||||
|
|
||||||
|
// Test event which is sent to all group listeners
|
||||||
|
event_sender
|
||||||
|
.send((event_grp_1_0, None))
|
||||||
|
.expect("Sending group error failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
||||||
|
check_next_event(event_grp_1_0, &group_event_receiver_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_basic_aux_data() {
|
||||||
|
let (event_sender, mut event_man) = generic_event_man();
|
||||||
|
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||||
|
let (single_event_sender, single_event_receiver) = channel();
|
||||||
|
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
||||||
|
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
||||||
|
event_man.add_sender(single_event_listener);
|
||||||
|
event_sender
|
||||||
|
.send((event_grp_0, Some(Params::Heapless((2_u32, 3_u32).into()))))
|
||||||
|
.expect("Sending group error failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||||
|
let aux = check_next_event(event_grp_0, &single_event_receiver);
|
||||||
|
assert!(aux.is_some());
|
||||||
|
let aux = aux.unwrap();
|
||||||
|
if let Params::Heapless(ParamsHeapless::Raw(ParamsRaw::U32Pair(pair))) = aux {
|
||||||
|
assert_eq!(pair.0, 2);
|
||||||
|
assert_eq!(pair.1, 3);
|
||||||
|
} else {
|
||||||
|
panic!("{}", format!("Unexpected auxiliary value type {:?}", aux));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test listening for multiple groups
|
||||||
|
#[test]
|
||||||
|
fn test_multi_group() {
|
||||||
|
let (event_sender, mut event_man) = generic_event_man();
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let hres = res.unwrap();
|
||||||
|
assert!(matches!(hres, EventRoutingResult::Empty));
|
||||||
|
|
||||||
|
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||||
|
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||||
|
let (event_grp_0_sender, event_grp_0_receiver) = channel();
|
||||||
|
let event_grp_0_and_1_listener = MpscEventSenderQueue {
|
||||||
|
id: 0,
|
||||||
|
mpsc_sender: event_grp_0_sender,
|
||||||
|
};
|
||||||
|
event_man.subscribe_group(event_grp_0.group_id(), event_grp_0_and_1_listener.id());
|
||||||
|
event_man.subscribe_group(event_grp_1_0.group_id(), event_grp_0_and_1_listener.id());
|
||||||
|
event_man.add_sender(event_grp_0_and_1_listener);
|
||||||
|
|
||||||
|
event_sender
|
||||||
|
.send((event_grp_0, None))
|
||||||
|
.expect("Sending Event Group 0 failed");
|
||||||
|
event_sender
|
||||||
|
.send((event_grp_1_0, None))
|
||||||
|
.expect("Sendign Event Group 1 failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
||||||
|
|
||||||
|
check_next_event(event_grp_0, &event_grp_0_receiver);
|
||||||
|
check_next_event(event_grp_1_0, &event_grp_0_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test listening to the same event from multiple listeners. Also test listening
|
||||||
|
/// to both group and single events from one listener
|
||||||
|
#[test]
|
||||||
|
fn test_listening_to_same_event_and_multi_type() {
|
||||||
|
let (event_sender, mut event_man) = generic_event_man();
|
||||||
|
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
||||||
|
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||||
|
let (event_0_tx_0, event_0_rx_0) = channel();
|
||||||
|
let (event_0_tx_1, event_0_rx_1) = channel();
|
||||||
|
let event_listener_0 = MpscEventSenderQueue {
|
||||||
|
id: 0,
|
||||||
|
mpsc_sender: event_0_tx_0,
|
||||||
|
};
|
||||||
|
let event_listener_1 = MpscEventSenderQueue {
|
||||||
|
id: 1,
|
||||||
|
mpsc_sender: event_0_tx_1,
|
||||||
|
};
|
||||||
|
let event_listener_0_sender_id = event_listener_0.id();
|
||||||
|
event_man.subscribe_single(&event_0, event_listener_0_sender_id);
|
||||||
|
event_man.add_sender(event_listener_0);
|
||||||
|
let event_listener_1_sender_id = event_listener_1.id();
|
||||||
|
event_man.subscribe_single(&event_0, event_listener_1_sender_id);
|
||||||
|
event_man.add_sender(event_listener_1);
|
||||||
|
event_sender
|
||||||
|
.send((event_0, None))
|
||||||
|
.expect("Triggering Event 0 failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_0, 2);
|
||||||
|
check_next_event(event_0, &event_0_rx_0);
|
||||||
|
check_next_event(event_0, &event_0_rx_1);
|
||||||
|
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
||||||
|
event_sender
|
||||||
|
.send((event_0, None))
|
||||||
|
.expect("Triggering Event 0 failed");
|
||||||
|
event_sender
|
||||||
|
.send((event_1, None))
|
||||||
|
.expect("Triggering Event 1 failed");
|
||||||
|
|
||||||
|
// 3 Events messages will be sent now
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_0, 2);
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_1, 1);
|
||||||
|
// Both the single event and the group event should arrive now
|
||||||
|
check_next_event(event_0, &event_0_rx_0);
|
||||||
|
check_next_event(event_1, &event_0_rx_0);
|
||||||
|
|
||||||
|
// Do double insertion and then remove duplicates
|
||||||
|
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
||||||
|
event_man.remove_duplicates(&ListenerKey::Group(event_1.group_id()));
|
||||||
|
event_sender
|
||||||
|
.send((event_1, None))
|
||||||
|
.expect("Triggering Event 1 failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_events_listener() {
|
||||||
|
let (event_sender, manager_queue) = channel();
|
||||||
|
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
||||||
|
let mut event_man: EventManager<SendError<EventU32WithAuxData>> =
|
||||||
|
EventManager::new(Box::new(event_man_receiver));
|
||||||
|
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
||||||
|
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||||
|
let (event_0_tx_0, all_events_rx) = channel();
|
||||||
|
let all_events_listener = MpscEventSenderQueue {
|
||||||
|
id: 0,
|
||||||
|
mpsc_sender: event_0_tx_0,
|
||||||
|
};
|
||||||
|
event_man.subscribe_all(all_events_listener.id());
|
||||||
|
event_man.add_sender(all_events_listener);
|
||||||
|
event_sender
|
||||||
|
.send((event_0, None))
|
||||||
|
.expect("Triggering event 0 failed");
|
||||||
|
event_sender
|
||||||
|
.send((event_1, None))
|
||||||
|
.expect("Triggering event 1 failed");
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_0, 1);
|
||||||
|
let res = event_man.try_event_handling();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
check_handled_event(res.unwrap(), event_1, 1);
|
||||||
|
check_next_event(event_0, &all_events_rx);
|
||||||
|
check_next_event(event_1, &all_events_rx);
|
||||||
|
}
|
||||||
|
}
|
@ -18,14 +18,14 @@
|
|||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use satrs::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
|
//! use satrs_core::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
|
||||||
//!
|
//!
|
||||||
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::new(1, 0);
|
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(1, 0);
|
||||||
//! const MSG_FAILED: EventU32 = EventU32::new(Severity::Low, 1, 1);
|
//! const MSG_FAILED: EventU32 = EventU32::const_new(Severity::LOW, 1, 1);
|
||||||
//!
|
//!
|
||||||
//! const TEMPERATURE_HIGH: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::new(2, 0);
|
//! const TEMPERATURE_HIGH: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::const_new(2, 0);
|
||||||
//!
|
//!
|
||||||
//! let small_event = EventU16::new(Severity::Info, 3, 0);
|
//! let small_event = EventU16::new(Severity::INFO, 3, 0);
|
||||||
//! ```
|
//! ```
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::hash::Hash;
|
use core::hash::Hash;
|
||||||
@ -40,17 +40,12 @@ pub type LargestEventRaw = u32;
|
|||||||
/// Using a type definition allows to change this to u32 in the future more easily
|
/// Using a type definition allows to change this to u32 in the future more easily
|
||||||
pub type LargestGroupIdRaw = u16;
|
pub type LargestGroupIdRaw = u16;
|
||||||
|
|
||||||
pub const MAX_GROUP_ID_U32_EVENT: u16 = 2_u16.pow(14) - 1;
|
|
||||||
pub const MAX_GROUP_ID_U16_EVENT: u16 = 2_u16.pow(6) - 1;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
Info = 0,
|
INFO = 0,
|
||||||
Low = 1,
|
LOW = 1,
|
||||||
Medium = 2,
|
MEDIUM = 2,
|
||||||
High = 3,
|
HIGH = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
|
pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
|
||||||
@ -61,31 +56,31 @@ pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
|
|||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct SeverityInfo {}
|
pub struct SeverityInfo {}
|
||||||
impl HasSeverity for SeverityInfo {
|
impl HasSeverity for SeverityInfo {
|
||||||
const SEVERITY: Severity = Severity::Info;
|
const SEVERITY: Severity = Severity::INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type level support struct
|
/// Type level support struct
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct SeverityLow {}
|
pub struct SeverityLow {}
|
||||||
impl HasSeverity for SeverityLow {
|
impl HasSeverity for SeverityLow {
|
||||||
const SEVERITY: Severity = Severity::Low;
|
const SEVERITY: Severity = Severity::LOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type level support struct
|
/// Type level support struct
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct SeverityMedium {}
|
pub struct SeverityMedium {}
|
||||||
impl HasSeverity for SeverityMedium {
|
impl HasSeverity for SeverityMedium {
|
||||||
const SEVERITY: Severity = Severity::Medium;
|
const SEVERITY: Severity = Severity::MEDIUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type level support struct
|
/// Type level support struct
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct SeverityHigh {}
|
pub struct SeverityHigh {}
|
||||||
impl HasSeverity for SeverityHigh {
|
impl HasSeverity for SeverityHigh {
|
||||||
const SEVERITY: Severity = Severity::High;
|
const SEVERITY: Severity = Severity::HIGH;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GenericEvent: EcssEnumeration + Copy + Clone {
|
pub trait GenericEvent: EcssEnumeration {
|
||||||
type Raw;
|
type Raw;
|
||||||
type GroupId;
|
type GroupId;
|
||||||
type UniqueId;
|
type UniqueId;
|
||||||
@ -104,29 +99,27 @@ impl TryFrom<u8> for Severity {
|
|||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
x if x == Severity::Info as u8 => Ok(Severity::Info),
|
x if x == Severity::INFO as u8 => Ok(Severity::INFO),
|
||||||
x if x == Severity::Low as u8 => Ok(Severity::Low),
|
x if x == Severity::LOW as u8 => Ok(Severity::LOW),
|
||||||
x if x == Severity::Medium as u8 => Ok(Severity::Medium),
|
x if x == Severity::MEDIUM as u8 => Ok(Severity::MEDIUM),
|
||||||
x if x == Severity::High as u8 => Ok(Severity::High),
|
x if x == Severity::HIGH as u8 => Ok(Severity::HIGH),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
struct EventBase<RAW, GID, UID> {
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
struct EventBase<Raw, GroupId, UniqueId> {
|
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
group_id: GroupId,
|
group_id: GID,
|
||||||
unique_id: UniqueId,
|
unique_id: UID,
|
||||||
phantom: PhantomData<Raw>,
|
phantom: PhantomData<RAW>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Raw: ToBeBytes, GroupId, UniqueId> EventBase<Raw, GroupId, UniqueId> {
|
impl<RAW: ToBeBytes, GID, UID> EventBase<RAW, GID, UID> {
|
||||||
fn write_to_bytes(
|
fn write_to_bytes(
|
||||||
&self,
|
&self,
|
||||||
raw: Raw,
|
raw: RAW,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
width: usize,
|
width: usize,
|
||||||
) -> Result<usize, ByteConversionError> {
|
) -> Result<usize, ByteConversionError> {
|
||||||
@ -274,7 +267,6 @@ macro_rules! const_from_fn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct EventU32 {
|
pub struct EventU32 {
|
||||||
base: EventBase<u32, u16, u16>,
|
base: EventBase<u32, u16, u16>,
|
||||||
}
|
}
|
||||||
@ -317,12 +309,12 @@ impl EventU32 {
|
|||||||
/// next 14 bits after the severity. Therefore, the size is limited by dec 16383 hex 0x3FFF.
|
/// next 14 bits after the severity. Therefore, the size is limited by dec 16383 hex 0x3FFF.
|
||||||
/// * `unique_id`: Each event has a unique 16 bit ID occupying the last 16 bits of the
|
/// * `unique_id`: Each event has a unique 16 bit ID occupying the last 16 bits of the
|
||||||
/// raw event ID
|
/// raw event ID
|
||||||
pub fn new_checked(
|
pub fn new(
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
if group_id > MAX_GROUP_ID_U32_EVENT {
|
if group_id > (2u16.pow(14) - 1) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(Self {
|
Some(Self {
|
||||||
@ -334,14 +326,12 @@ impl EventU32 {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub const fn const_new(
|
||||||
/// This constructor will panic if the passed group is is larger than [MAX_GROUP_ID_U32_EVENT].
|
|
||||||
pub const fn new(
|
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if group_id > MAX_GROUP_ID_U32_EVENT {
|
if group_id > (2u16.pow(14) - 1) {
|
||||||
panic!("Group ID too large");
|
panic!("Group ID too large");
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
@ -354,67 +344,32 @@ impl EventU32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
|
|
||||||
Self::from(u32::from_be_bytes(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
const_from_fn!(const_from_info, EventU32TypedSev, SeverityInfo);
|
const_from_fn!(const_from_info, EventU32TypedSev, SeverityInfo);
|
||||||
const_from_fn!(const_from_low, EventU32TypedSev, SeverityLow);
|
const_from_fn!(const_from_low, EventU32TypedSev, SeverityLow);
|
||||||
const_from_fn!(const_from_medium, EventU32TypedSev, SeverityMedium);
|
const_from_fn!(const_from_medium, EventU32TypedSev, SeverityMedium);
|
||||||
const_from_fn!(const_from_high, EventU32TypedSev, SeverityHigh);
|
const_from_fn!(const_from_high, EventU32TypedSev, SeverityHigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for EventU32 {
|
|
||||||
fn from(raw: u32) -> Self {
|
|
||||||
// Severity conversion from u8 should never fail
|
|
||||||
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
|
|
||||||
let group_id = ((raw >> 16) & 0x3FFF) as u16;
|
|
||||||
let unique_id = (raw & 0xFFFF) as u16;
|
|
||||||
// Sanitized input, should never fail
|
|
||||||
Self::new(severity, group_id, unique_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnsignedEnum for EventU32 {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
core::mem::size_of::<u32>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
self.base.write_to_bytes(self.raw(), buf, self.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> u64 {
|
|
||||||
self.raw().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EcssEnumeration for EventU32 {
|
|
||||||
fn pfc(&self) -> u8 {
|
|
||||||
u32::BITS as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
|
impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
|
||||||
/// This is similar to [EventU32::new] but the severity is a type generic, which allows to
|
/// This is similar to [EventU32::new] but the severity is a type generic, which allows to
|
||||||
/// have distinct types for events with different severities
|
/// have distinct types for events with different severities
|
||||||
pub fn new_checked(
|
pub fn new(
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let event = EventU32::new_checked(SEVERITY::SEVERITY, group_id, unique_id)?;
|
let event = EventU32::new(SEVERITY::SEVERITY, group_id, unique_id)?;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
event,
|
event,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U32_EVENT].
|
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||||
pub const fn new(
|
pub const fn const_new(
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let event = EventU32::new(SEVERITY::SEVERITY, group_id, unique_id);
|
let event = EventU32::const_new(SEVERITY::SEVERITY, group_id, unique_id);
|
||||||
Self {
|
Self {
|
||||||
event,
|
event,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -426,24 +381,50 @@ impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
|
|||||||
if severity != expected {
|
if severity != expected {
|
||||||
return Err(severity);
|
return Err(severity);
|
||||||
}
|
}
|
||||||
Ok(Self::new(
|
Ok(Self::const_new(
|
||||||
((raw >> 16) & 0x3FFF) as u16,
|
((raw >> 16) & 0x3FFF) as u16,
|
||||||
(raw & 0xFFFF) as u16,
|
(raw & 0xFFFF) as u16,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try_from_impls!(SeverityInfo, Severity::Info, u32, EventU32TypedSev);
|
impl From<u32> for EventU32 {
|
||||||
try_from_impls!(SeverityLow, Severity::Low, u32, EventU32TypedSev);
|
fn from(raw: u32) -> Self {
|
||||||
try_from_impls!(SeverityMedium, Severity::Medium, u32, EventU32TypedSev);
|
// Severity conversion from u8 should never fail
|
||||||
try_from_impls!(SeverityHigh, Severity::High, u32, EventU32TypedSev);
|
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
|
||||||
|
let group_id = ((raw >> 16) & 0x3FFF) as u16;
|
||||||
|
let unique_id = (raw & 0xFFFF) as u16;
|
||||||
|
// Sanitized input, should never fail
|
||||||
|
Self::const_new(severity, group_id, unique_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_from_impls!(SeverityInfo, Severity::INFO, u32, EventU32TypedSev);
|
||||||
|
try_from_impls!(SeverityLow, Severity::LOW, u32, EventU32TypedSev);
|
||||||
|
try_from_impls!(SeverityMedium, Severity::MEDIUM, u32, EventU32TypedSev);
|
||||||
|
try_from_impls!(SeverityHigh, Severity::HIGH, u32, EventU32TypedSev);
|
||||||
|
|
||||||
|
impl UnsignedEnum for EventU32 {
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
core::mem::size_of::<u32>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
self.base.write_to_bytes(self.raw(), buf, self.size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EcssEnumeration for EventU32 {
|
||||||
|
fn pfc(&self) -> u8 {
|
||||||
|
u32::BITS as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//noinspection RsTraitImplementation
|
//noinspection RsTraitImplementation
|
||||||
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU32TypedSev<SEVERITY> {
|
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU32TypedSev<SEVERITY> {
|
||||||
delegate!(to self.event {
|
delegate!(to self.event {
|
||||||
fn size(&self) -> usize;
|
fn size(&self) -> usize;
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||||
fn value(&self) -> u64;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,8 +436,6 @@ impl<SEVERITY: HasSeverity> EcssEnumeration for EventU32TypedSev<SEVERITY> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct EventU16 {
|
pub struct EventU16 {
|
||||||
base: EventBase<u16, u8, u8>,
|
base: EventBase<u16, u8, u8>,
|
||||||
}
|
}
|
||||||
@ -491,7 +470,7 @@ impl EventU16 {
|
|||||||
/// next 6 bits after the severity. Therefore, the size is limited by dec 63 hex 0x3F.
|
/// next 6 bits after the severity. Therefore, the size is limited by dec 63 hex 0x3F.
|
||||||
/// * `unique_id`: Each event has a unique 8 bit ID occupying the last 8 bits of the
|
/// * `unique_id`: Each event has a unique 8 bit ID occupying the last 8 bits of the
|
||||||
/// raw event ID
|
/// raw event ID
|
||||||
pub fn new_checked(
|
pub fn new(
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
@ -509,8 +488,8 @@ impl EventU16 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U16_EVENT].
|
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||||
pub const fn new(
|
pub const fn const_new(
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
@ -527,66 +506,32 @@ impl EventU16 {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from_be_bytes(bytes: [u8; 2]) -> Self {
|
|
||||||
Self::from(u16::from_be_bytes(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
const_from_fn!(const_from_info, EventU16TypedSev, SeverityInfo);
|
const_from_fn!(const_from_info, EventU16TypedSev, SeverityInfo);
|
||||||
const_from_fn!(const_from_low, EventU16TypedSev, SeverityLow);
|
const_from_fn!(const_from_low, EventU16TypedSev, SeverityLow);
|
||||||
const_from_fn!(const_from_medium, EventU16TypedSev, SeverityMedium);
|
const_from_fn!(const_from_medium, EventU16TypedSev, SeverityMedium);
|
||||||
const_from_fn!(const_from_high, EventU16TypedSev, SeverityHigh);
|
const_from_fn!(const_from_high, EventU16TypedSev, SeverityHigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u16> for EventU16 {
|
|
||||||
fn from(raw: <Self as GenericEvent>::Raw) -> Self {
|
|
||||||
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
|
|
||||||
let group_id = ((raw >> 8) & 0x3F) as u8;
|
|
||||||
let unique_id = (raw & 0xFF) as u8;
|
|
||||||
// Sanitized input, new call should never fail
|
|
||||||
Self::new(severity, group_id, unique_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnsignedEnum for EventU16 {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
core::mem::size_of::<u16>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
self.base.write_to_bytes(self.raw(), buf, self.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> u64 {
|
|
||||||
self.raw().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl EcssEnumeration for EventU16 {
|
|
||||||
#[inline]
|
|
||||||
fn pfc(&self) -> u8 {
|
|
||||||
u16::BITS as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
|
impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
|
||||||
/// This is similar to [EventU16::new] but the severity is a type generic, which allows to
|
/// This is similar to [EventU16::new] but the severity is a type generic, which allows to
|
||||||
/// have distinct types for events with different severities
|
/// have distinct types for events with different severities
|
||||||
pub fn new_checked(
|
pub fn new(
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let event = EventU16::new_checked(SEVERITY::SEVERITY, group_id, unique_id)?;
|
let event = EventU16::new(SEVERITY::SEVERITY, group_id, unique_id)?;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
event,
|
event,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U16_EVENT].
|
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||||
pub const fn new(
|
pub const fn const_new(
|
||||||
group_id: <Self as GenericEvent>::GroupId,
|
group_id: <Self as GenericEvent>::GroupId,
|
||||||
unique_id: <Self as GenericEvent>::UniqueId,
|
unique_id: <Self as GenericEvent>::UniqueId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let event = EventU16::new(SEVERITY::SEVERITY, group_id, unique_id);
|
let event = EventU16::const_new(SEVERITY::SEVERITY, group_id, unique_id);
|
||||||
Self {
|
Self {
|
||||||
event,
|
event,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -598,18 +543,36 @@ impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
|
|||||||
if severity != expected {
|
if severity != expected {
|
||||||
return Err(severity);
|
return Err(severity);
|
||||||
}
|
}
|
||||||
Ok(Self::new(((raw >> 8) & 0x3F) as u8, (raw & 0xFF) as u8))
|
Ok(Self::const_new(
|
||||||
|
((raw >> 8) & 0x3F) as u8,
|
||||||
|
(raw & 0xFF) as u8,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_event_provider!(EventU16, EventU16TypedSev, u16, u8, u8);
|
impl_event_provider!(EventU16, EventU16TypedSev, u16, u8, u8);
|
||||||
|
|
||||||
|
impl UnsignedEnum for EventU16 {
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
core::mem::size_of::<u16>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
self.base.write_to_bytes(self.raw(), buf, self.size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl EcssEnumeration for EventU16 {
|
||||||
|
#[inline]
|
||||||
|
fn pfc(&self) -> u8 {
|
||||||
|
u16::BITS as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//noinspection RsTraitImplementation
|
//noinspection RsTraitImplementation
|
||||||
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU16TypedSev<SEVERITY> {
|
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU16TypedSev<SEVERITY> {
|
||||||
delegate!(to self.event {
|
delegate!(to self.event {
|
||||||
fn size(&self) -> usize;
|
fn size(&self) -> usize;
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||||
fn value(&self) -> u64;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,10 +583,20 @@ impl<SEVERITY: HasSeverity> EcssEnumeration for EventU16TypedSev<SEVERITY> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try_from_impls!(SeverityInfo, Severity::Info, u16, EventU16TypedSev);
|
impl From<u16> for EventU16 {
|
||||||
try_from_impls!(SeverityLow, Severity::Low, u16, EventU16TypedSev);
|
fn from(raw: <Self as GenericEvent>::Raw) -> Self {
|
||||||
try_from_impls!(SeverityMedium, Severity::Medium, u16, EventU16TypedSev);
|
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
|
||||||
try_from_impls!(SeverityHigh, Severity::High, u16, EventU16TypedSev);
|
let group_id = ((raw >> 8) & 0x3F) as u8;
|
||||||
|
let unique_id = (raw & 0xFF) as u8;
|
||||||
|
// Sanitized input, new call should never fail
|
||||||
|
Self::const_new(severity, group_id, unique_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_from_impls!(SeverityInfo, Severity::INFO, u16, EventU16TypedSev);
|
||||||
|
try_from_impls!(SeverityLow, Severity::LOW, u16, EventU16TypedSev);
|
||||||
|
try_from_impls!(SeverityMedium, Severity::MEDIUM, u16, EventU16TypedSev);
|
||||||
|
try_from_impls!(SeverityHigh, Severity::HIGH, u16, EventU16TypedSev);
|
||||||
|
|
||||||
impl<Severity: HasSeverity> PartialEq<EventU32> for EventU32TypedSev<Severity> {
|
impl<Severity: HasSeverity> PartialEq<EventU32> for EventU32TypedSev<Severity> {
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -664,10 +637,12 @@ mod tests {
|
|||||||
assert_eq!(size_of::<T>(), val);
|
assert_eq!(size_of::<T>(), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::new(0, 0);
|
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(0, 0);
|
||||||
const INFO_EVENT_SMALL: EventU16TypedSev<SeverityInfo> = EventU16TypedSev::new(0, 0);
|
const INFO_EVENT_SMALL: EventU16TypedSev<SeverityInfo> = EventU16TypedSev::const_new(0, 0);
|
||||||
const HIGH_SEV_EVENT: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::new(0x3FFF, 0xFFFF);
|
const HIGH_SEV_EVENT: EventU32TypedSev<SeverityHigh> =
|
||||||
const HIGH_SEV_EVENT_SMALL: EventU16TypedSev<SeverityHigh> = EventU16TypedSev::new(0x3F, 0xff);
|
EventU32TypedSev::const_new(0x3FFF, 0xFFFF);
|
||||||
|
const HIGH_SEV_EVENT_SMALL: EventU16TypedSev<SeverityHigh> =
|
||||||
|
EventU16TypedSev::const_new(0x3F, 0xff);
|
||||||
|
|
||||||
/// This working is a test in itself.
|
/// This working is a test in itself.
|
||||||
const INFO_REDUCED: EventU32 = EventU32::const_from_info(INFO_EVENT);
|
const INFO_REDUCED: EventU32 = EventU32::const_from_info(INFO_EVENT);
|
||||||
@ -698,7 +673,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_normal_event_getters() {
|
fn test_normal_event_getters() {
|
||||||
assert_eq!(INFO_EVENT.severity(), Severity::Info);
|
assert_eq!(INFO_EVENT.severity(), Severity::INFO);
|
||||||
assert_eq!(INFO_EVENT.unique_id(), 0);
|
assert_eq!(INFO_EVENT.unique_id(), 0);
|
||||||
assert_eq!(INFO_EVENT.group_id(), 0);
|
assert_eq!(INFO_EVENT.group_id(), 0);
|
||||||
let raw_event = INFO_EVENT.raw();
|
let raw_event = INFO_EVENT.raw();
|
||||||
@ -707,7 +682,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_small_event_getters() {
|
fn test_small_event_getters() {
|
||||||
assert_eq!(INFO_EVENT_SMALL.severity(), Severity::Info);
|
assert_eq!(INFO_EVENT_SMALL.severity(), Severity::INFO);
|
||||||
assert_eq!(INFO_EVENT_SMALL.unique_id(), 0);
|
assert_eq!(INFO_EVENT_SMALL.unique_id(), 0);
|
||||||
assert_eq!(INFO_EVENT_SMALL.group_id(), 0);
|
assert_eq!(INFO_EVENT_SMALL.group_id(), 0);
|
||||||
let raw_event = INFO_EVENT_SMALL.raw();
|
let raw_event = INFO_EVENT_SMALL.raw();
|
||||||
@ -716,7 +691,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_ones_event_regular() {
|
fn all_ones_event_regular() {
|
||||||
assert_eq!(HIGH_SEV_EVENT.severity(), Severity::High);
|
assert_eq!(HIGH_SEV_EVENT.severity(), Severity::HIGH);
|
||||||
assert_eq!(HIGH_SEV_EVENT.group_id(), 0x3FFF);
|
assert_eq!(HIGH_SEV_EVENT.group_id(), 0x3FFF);
|
||||||
assert_eq!(HIGH_SEV_EVENT.unique_id(), 0xFFFF);
|
assert_eq!(HIGH_SEV_EVENT.unique_id(), 0xFFFF);
|
||||||
let raw_event = HIGH_SEV_EVENT.raw();
|
let raw_event = HIGH_SEV_EVENT.raw();
|
||||||
@ -725,7 +700,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_ones_event_small() {
|
fn all_ones_event_small() {
|
||||||
assert_eq!(HIGH_SEV_EVENT_SMALL.severity(), Severity::High);
|
assert_eq!(HIGH_SEV_EVENT_SMALL.severity(), Severity::HIGH);
|
||||||
assert_eq!(HIGH_SEV_EVENT_SMALL.group_id(), 0x3F);
|
assert_eq!(HIGH_SEV_EVENT_SMALL.group_id(), 0x3F);
|
||||||
assert_eq!(HIGH_SEV_EVENT_SMALL.unique_id(), 0xFF);
|
assert_eq!(HIGH_SEV_EVENT_SMALL.unique_id(), 0xFF);
|
||||||
let raw_event = HIGH_SEV_EVENT_SMALL.raw();
|
let raw_event = HIGH_SEV_EVENT_SMALL.raw();
|
||||||
@ -734,19 +709,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_group_id_normal() {
|
fn invalid_group_id_normal() {
|
||||||
assert!(EventU32TypedSev::<SeverityMedium>::new_checked(2_u16.pow(14), 0).is_none());
|
assert!(EventU32TypedSev::<SeverityMedium>::new(2_u16.pow(14), 0).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_group_id_small() {
|
fn invalid_group_id_small() {
|
||||||
assert!(EventU16TypedSev::<SeverityMedium>::new_checked(2_u8.pow(6), 0).is_none());
|
assert!(EventU16TypedSev::<SeverityMedium>::new(2_u8.pow(6), 0).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regular_new() {
|
fn regular_new() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EventU32TypedSev::<SeverityInfo>::new_checked(0, 0)
|
EventU32TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
|
||||||
.expect("Creating regular event failed"),
|
|
||||||
INFO_EVENT
|
INFO_EVENT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -754,8 +728,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn small_new() {
|
fn small_new() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EventU16TypedSev::<SeverityInfo>::new_checked(0, 0)
|
EventU16TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
|
||||||
.expect("Creating regular event failed"),
|
|
||||||
INFO_EVENT_SMALL
|
INFO_EVENT_SMALL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -794,8 +767,6 @@ mod tests {
|
|||||||
assert!(HIGH_SEV_EVENT.write_to_be_bytes(&mut buf).is_ok());
|
assert!(HIGH_SEV_EVENT.write_to_be_bytes(&mut buf).is_ok());
|
||||||
let val_from_raw = u32::from_be_bytes(buf);
|
let val_from_raw = u32::from_be_bytes(buf);
|
||||||
assert_eq!(val_from_raw, 0xFFFFFFFF);
|
assert_eq!(val_from_raw, 0xFFFFFFFF);
|
||||||
let event_read_back = EventU32::from_be_bytes(buf);
|
|
||||||
assert_eq!(event_read_back, HIGH_SEV_EVENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -804,8 +775,6 @@ mod tests {
|
|||||||
assert!(HIGH_SEV_EVENT_SMALL.write_to_be_bytes(&mut buf).is_ok());
|
assert!(HIGH_SEV_EVENT_SMALL.write_to_be_bytes(&mut buf).is_ok());
|
||||||
let val_from_raw = u16::from_be_bytes(buf);
|
let val_from_raw = u16::from_be_bytes(buf);
|
||||||
assert_eq!(val_from_raw, 0xFFFF);
|
assert_eq!(val_from_raw, 0xFFFF);
|
||||||
let event_read_back = EventU16::from_be_bytes(buf);
|
|
||||||
assert_eq!(event_read_back, HIGH_SEV_EVENT_SMALL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -836,13 +805,13 @@ mod tests {
|
|||||||
fn severity_from_invalid_raw_val() {
|
fn severity_from_invalid_raw_val() {
|
||||||
let invalid = 0xFF;
|
let invalid = 0xFF;
|
||||||
assert!(Severity::try_from(invalid).is_err());
|
assert!(Severity::try_from(invalid).is_err());
|
||||||
let invalid = Severity::High as u8 + 1;
|
let invalid = Severity::HIGH as u8 + 1;
|
||||||
assert!(Severity::try_from(invalid).is_err());
|
assert!(Severity::try_from(invalid).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reduction() {
|
fn reduction() {
|
||||||
let event = EventU32TypedSev::<SeverityInfo>::new(1, 1);
|
let event = EventU32TypedSev::<SeverityInfo>::const_new(1, 1);
|
||||||
let raw = event.raw();
|
let raw = event.raw();
|
||||||
let reduced: EventU32 = event.into();
|
let reduced: EventU32 = event.into();
|
||||||
assert_eq!(reduced.group_id(), 1);
|
assert_eq!(reduced.group_id(), 1);
|
@ -1,13 +1,13 @@
|
|||||||
//! Task scheduling module
|
//! Task scheduling module
|
||||||
use alloc::string::String;
|
|
||||||
use bus::BusReader;
|
use bus::BusReader;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
|
use std::error::Error;
|
||||||
use std::sync::mpsc::TryRecvError;
|
use std::sync::mpsc::TryRecvError;
|
||||||
|
use std::thread;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use std::{io, thread};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum OpResult {
|
pub enum OpResult {
|
||||||
@ -34,49 +34,47 @@ pub trait Executable: Send {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `executable`: Executable task
|
/// * `executable`: Executable task
|
||||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks.
|
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||||
/// If [None] is passed, no sleeping will be performed.
|
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||||
/// * `op_code`: Operation code which is passed to the executable task
|
|
||||||
/// [operation call][Executable::periodic_op]
|
|
||||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||||
pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
pub fn exec_sched_single<
|
||||||
|
T: Executable<Error = E> + Send + 'static + ?Sized,
|
||||||
|
E: Error + Send + 'static,
|
||||||
|
>(
|
||||||
mut executable: Box<T>,
|
mut executable: Box<T>,
|
||||||
task_freq: Option<Duration>,
|
task_freq: Option<Duration>,
|
||||||
op_code: i32,
|
op_code: i32,
|
||||||
mut termination: Option<BusReader<()>>,
|
mut termination: Option<BusReader<()>>,
|
||||||
) -> Result<JoinHandle<Result<OpResult, E>>, io::Error> {
|
) -> JoinHandle<Result<OpResult, E>> {
|
||||||
let mut cycle_count = 0;
|
let mut cycle_count = 0;
|
||||||
thread::Builder::new()
|
thread::spawn(move || loop {
|
||||||
.name(String::from(executable.task_name()))
|
if let Some(ref mut terminator) = termination {
|
||||||
.spawn(move || loop {
|
match terminator.try_recv() {
|
||||||
if let Some(ref mut terminator) = termination {
|
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||||
match terminator.try_recv() {
|
|
||||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
|
||||||
return Ok(OpResult::Ok);
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match executable.exec_type() {
|
|
||||||
ExecutionType::OneShot => {
|
|
||||||
executable.periodic_op(op_code)?;
|
|
||||||
return Ok(OpResult::Ok);
|
return Ok(OpResult::Ok);
|
||||||
}
|
}
|
||||||
ExecutionType::Infinite => {
|
Err(TryRecvError::Empty) => (),
|
||||||
executable.periodic_op(op_code)?;
|
}
|
||||||
}
|
}
|
||||||
ExecutionType::Cycles(cycles) => {
|
match executable.exec_type() {
|
||||||
executable.periodic_op(op_code)?;
|
ExecutionType::OneShot => {
|
||||||
cycle_count += 1;
|
executable.periodic_op(op_code)?;
|
||||||
if cycle_count == cycles {
|
return Ok(OpResult::Ok);
|
||||||
return Ok(OpResult::Ok);
|
}
|
||||||
}
|
ExecutionType::Infinite => {
|
||||||
|
executable.periodic_op(op_code)?;
|
||||||
|
}
|
||||||
|
ExecutionType::Cycles(cycles) => {
|
||||||
|
executable.periodic_op(op_code)?;
|
||||||
|
cycle_count += 1;
|
||||||
|
if cycle_count == cycles {
|
||||||
|
return Ok(OpResult::Ok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(freq) = task_freq {
|
}
|
||||||
thread::sleep(freq);
|
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
||||||
}
|
thread::sleep(freq);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function allows executing multiple tasks as long as the tasks implement the
|
/// This function allows executing multiple tasks as long as the tasks implement the
|
||||||
@ -88,56 +86,55 @@ pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E:
|
|||||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||||
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||||
pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
pub fn exec_sched_multi<
|
||||||
task_name: &'static str,
|
T: Executable<Error = E> + Send + 'static + ?Sized,
|
||||||
|
E: Error + Send + 'static,
|
||||||
|
>(
|
||||||
mut executable_vec: Vec<Box<T>>,
|
mut executable_vec: Vec<Box<T>>,
|
||||||
task_freq: Option<Duration>,
|
task_freq: Option<Duration>,
|
||||||
op_code: i32,
|
op_code: i32,
|
||||||
mut termination: Option<BusReader<()>>,
|
mut termination: Option<BusReader<()>>,
|
||||||
) -> Result<JoinHandle<Result<OpResult, E>>, io::Error> {
|
) -> JoinHandle<Result<OpResult, E>> {
|
||||||
let mut cycle_counts = vec![0; executable_vec.len()];
|
let mut cycle_counts = vec![0; executable_vec.len()];
|
||||||
let mut removal_flags = vec![false; executable_vec.len()];
|
let mut removal_flags = vec![false; executable_vec.len()];
|
||||||
|
thread::spawn(move || loop {
|
||||||
thread::Builder::new()
|
if let Some(ref mut terminator) = termination {
|
||||||
.name(String::from(task_name))
|
match terminator.try_recv() {
|
||||||
.spawn(move || loop {
|
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||||
if let Some(ref mut terminator) = termination {
|
removal_flags.iter_mut().for_each(|x| *x = true);
|
||||||
match terminator.try_recv() {
|
|
||||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
|
||||||
removal_flags.iter_mut().for_each(|x| *x = true);
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => (),
|
|
||||||
}
|
}
|
||||||
|
Err(TryRecvError::Empty) => (),
|
||||||
}
|
}
|
||||||
for (idx, executable) in executable_vec.iter_mut().enumerate() {
|
}
|
||||||
match executable.exec_type() {
|
for (idx, executable) in executable_vec.iter_mut().enumerate() {
|
||||||
ExecutionType::OneShot => {
|
match executable.exec_type() {
|
||||||
executable.periodic_op(op_code)?;
|
ExecutionType::OneShot => {
|
||||||
|
executable.periodic_op(op_code)?;
|
||||||
|
removal_flags[idx] = true;
|
||||||
|
}
|
||||||
|
ExecutionType::Infinite => {
|
||||||
|
executable.periodic_op(op_code)?;
|
||||||
|
}
|
||||||
|
ExecutionType::Cycles(cycles) => {
|
||||||
|
executable.periodic_op(op_code)?;
|
||||||
|
cycle_counts[idx] += 1;
|
||||||
|
if cycle_counts[idx] == cycles {
|
||||||
removal_flags[idx] = true;
|
removal_flags[idx] = true;
|
||||||
}
|
}
|
||||||
ExecutionType::Infinite => {
|
|
||||||
executable.periodic_op(op_code)?;
|
|
||||||
}
|
|
||||||
ExecutionType::Cycles(cycles) => {
|
|
||||||
executable.periodic_op(op_code)?;
|
|
||||||
cycle_counts[idx] += 1;
|
|
||||||
if cycle_counts[idx] == cycles {
|
|
||||||
removal_flags[idx] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut removal_iter = removal_flags.iter();
|
}
|
||||||
executable_vec.retain(|_| !*removal_iter.next().unwrap());
|
let mut removal_iter = removal_flags.iter();
|
||||||
removal_iter = removal_flags.iter();
|
executable_vec.retain(|_| !*removal_iter.next().unwrap());
|
||||||
cycle_counts.retain(|_| !*removal_iter.next().unwrap());
|
removal_iter = removal_flags.iter();
|
||||||
removal_flags.retain(|&i| !i);
|
cycle_counts.retain(|_| !*removal_iter.next().unwrap());
|
||||||
if executable_vec.is_empty() {
|
removal_flags.retain(|&i| !i);
|
||||||
return Ok(OpResult::Ok);
|
if executable_vec.is_empty() {
|
||||||
}
|
return Ok(OpResult::Ok);
|
||||||
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
}
|
||||||
thread::sleep(freq);
|
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
||||||
})
|
thread::sleep(freq);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -297,8 +294,7 @@ mod tests {
|
|||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
None,
|
None,
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||||
assert!(thread_res.is_ok());
|
assert!(thread_res.is_ok());
|
||||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||||
@ -323,8 +319,7 @@ mod tests {
|
|||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
op_code_inducing_failure,
|
op_code_inducing_failure,
|
||||||
None,
|
None,
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||||
assert!(thread_res.is_err());
|
assert!(thread_res.is_err());
|
||||||
let error = thread_res.unwrap_err();
|
let error = thread_res.unwrap_err();
|
||||||
@ -361,13 +356,11 @@ mod tests {
|
|||||||
assert_eq!(task.task_name(), ONE_SHOT_TASK_NAME);
|
assert_eq!(task.task_name(), ONE_SHOT_TASK_NAME);
|
||||||
}
|
}
|
||||||
let jhandle = exec_sched_multi(
|
let jhandle = exec_sched_multi(
|
||||||
"multi-task-name",
|
|
||||||
task_vec,
|
task_vec,
|
||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
None,
|
None,
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||||
assert!(thread_res.is_ok());
|
assert!(thread_res.is_ok());
|
||||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||||
@ -393,8 +386,7 @@ mod tests {
|
|||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
None,
|
None,
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
let thread_res = jh.join().expect("Cycles Task failed");
|
let thread_res = jh.join().expect("Cycles Task failed");
|
||||||
assert!(thread_res.is_ok());
|
assert!(thread_res.is_ok());
|
||||||
let data = shared.lock().expect("Locking Mutex failed");
|
let data = shared.lock().expect("Locking Mutex failed");
|
||||||
@ -426,13 +418,11 @@ mod tests {
|
|||||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||||
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
||||||
let jh = exec_sched_multi(
|
let jh = exec_sched_multi(
|
||||||
"multi-task-name",
|
|
||||||
task_vec,
|
task_vec,
|
||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
None,
|
None,
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
let thread_res = jh.join().expect("Cycles Task failed");
|
let thread_res = jh.join().expect("Cycles Task failed");
|
||||||
assert!(thread_res.is_ok());
|
assert!(thread_res.is_ok());
|
||||||
let data = shared.lock().expect("Locking Mutex failed");
|
let data = shared.lock().expect("Locking Mutex failed");
|
||||||
@ -459,8 +449,7 @@ mod tests {
|
|||||||
Some(Duration::from_millis(20)),
|
Some(Duration::from_millis(20)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
Some(terminator.add_rx()),
|
Some(terminator.add_rx()),
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
thread::sleep(Duration::from_millis(40));
|
thread::sleep(Duration::from_millis(40));
|
||||||
terminator.broadcast(());
|
terminator.broadcast(());
|
||||||
let thread_res = jh.join().expect("Periodic Task failed");
|
let thread_res = jh.join().expect("Periodic Task failed");
|
||||||
@ -496,13 +485,11 @@ mod tests {
|
|||||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||||
vec![cycled_task, periodic_task_0, periodic_task_1];
|
vec![cycled_task, periodic_task_0, periodic_task_1];
|
||||||
let jh = exec_sched_multi(
|
let jh = exec_sched_multi(
|
||||||
"multi-task-name",
|
|
||||||
task_vec,
|
task_vec,
|
||||||
Some(Duration::from_millis(20)),
|
Some(Duration::from_millis(20)),
|
||||||
expected_op_code,
|
expected_op_code,
|
||||||
Some(terminator.add_rx()),
|
Some(terminator.add_rx()),
|
||||||
)
|
);
|
||||||
.expect("thread creation failed");
|
|
||||||
thread::sleep(Duration::from_millis(60));
|
thread::sleep(Duration::from_millis(60));
|
||||||
terminator.broadcast(());
|
terminator.broadcast(());
|
||||||
let thread_res = jh.join().expect("Periodic Task failed");
|
let thread_res = jh.join().expect("Periodic Task failed");
|
@ -1,3 +1,4 @@
|
|||||||
//! # Hardware Abstraction Layer module
|
//! # Hardware Abstraction Layer module
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||||
pub mod std;
|
pub mod std;
|
@ -1,25 +1,20 @@
|
|||||||
use alloc::sync::Arc;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use cobs::encode;
|
use cobs::encode;
|
||||||
use core::sync::atomic::AtomicBool;
|
|
||||||
use core::time::Duration;
|
|
||||||
use delegate::delegate;
|
use delegate::delegate;
|
||||||
use mio::net::{TcpListener, TcpStream};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::net::TcpListener;
|
||||||
|
use std::net::TcpStream;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use crate::encoding::parse_buffer_for_cobs_encoded_packets;
|
use crate::encoding::parse_buffer_for_cobs_encoded_packets;
|
||||||
use crate::tmtc::PacketSenderRaw;
|
use crate::tmtc::ReceivesTc;
|
||||||
use crate::tmtc::PacketSource;
|
use crate::tmtc::TmPacketSource;
|
||||||
|
|
||||||
use crate::hal::std::tcp_server::{
|
use crate::hal::std::tcp_server::{
|
||||||
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
|
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
|
||||||
};
|
};
|
||||||
use crate::ComponentId;
|
|
||||||
|
|
||||||
use super::tcp_server::HandledConnectionHandler;
|
|
||||||
use super::tcp_server::HandledConnectionInfo;
|
|
||||||
|
|
||||||
/// Concrete [TcpTcParser] implementation for the [TcpTmtcInCobsServer].
|
/// Concrete [TcpTcParser] implementation for the [TcpTmtcInCobsServer].
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -29,16 +24,15 @@ impl<TmError, TcError: 'static> TcpTcParser<TmError, TcError> for CobsTcParser {
|
|||||||
fn handle_tc_parsing(
|
fn handle_tc_parsing(
|
||||||
&mut self,
|
&mut self,
|
||||||
tc_buffer: &mut [u8],
|
tc_buffer: &mut [u8],
|
||||||
sender_id: ComponentId,
|
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
|
||||||
tc_sender: &(impl PacketSenderRaw<Error = TcError> + ?Sized),
|
conn_result: &mut ConnectionResult,
|
||||||
conn_result: &mut HandledConnectionInfo,
|
|
||||||
current_write_idx: usize,
|
current_write_idx: usize,
|
||||||
next_write_idx: &mut usize,
|
next_write_idx: &mut usize,
|
||||||
) -> Result<(), TcpTmtcError<TmError, TcError>> {
|
) -> Result<(), TcpTmtcError<TmError, TcError>> {
|
||||||
|
// Reader vec full, need to parse for packets.
|
||||||
conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets(
|
conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets(
|
||||||
&mut tc_buffer[..current_write_idx],
|
&mut tc_buffer[..current_write_idx],
|
||||||
sender_id,
|
tc_receiver.upcast_mut(),
|
||||||
tc_sender,
|
|
||||||
next_write_idx,
|
next_write_idx,
|
||||||
)
|
)
|
||||||
.map_err(|e| TcpTmtcError::TcError(e))?;
|
.map_err(|e| TcpTmtcError::TcError(e))?;
|
||||||
@ -65,8 +59,8 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
|
|||||||
fn handle_tm_sending(
|
fn handle_tm_sending(
|
||||||
&mut self,
|
&mut self,
|
||||||
tm_buffer: &mut [u8],
|
tm_buffer: &mut [u8],
|
||||||
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
|
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
|
||||||
conn_result: &mut HandledConnectionInfo,
|
conn_result: &mut ConnectionResult,
|
||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
|
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
|
||||||
let mut tm_was_sent = false;
|
let mut tm_was_sent = false;
|
||||||
@ -104,7 +98,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
|
|||||||
/// Telemetry will be encoded with the COBS protocol using [cobs::encode] in addition to being
|
/// Telemetry will be encoded with the COBS protocol using [cobs::encode] in addition to being
|
||||||
/// wrapped with the sentinel value 0 as the packet delimiter as well before being sent back to
|
/// wrapped with the sentinel value 0 as the packet delimiter as well before being sent back to
|
||||||
/// the client. Please note that the server will send as much data as it can retrieve from the
|
/// the client. Please note that the server will send as much data as it can retrieve from the
|
||||||
/// [PacketSource] in its current implementation.
|
/// [TmPacketSource] in its current implementation.
|
||||||
///
|
///
|
||||||
/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data
|
/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data
|
||||||
/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full
|
/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full
|
||||||
@ -115,34 +109,13 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
|
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs)
|
||||||
/// test also serves as the example application for this module.
|
/// test also serves as the example application for this module.
|
||||||
pub struct TcpTmtcInCobsServer<
|
pub struct TcpTmtcInCobsServer<TmError, TcError: 'static> {
|
||||||
TmSource: PacketSource<Error = TmError>,
|
generic_server: TcpTmtcGenericServer<TmError, TcError, CobsTmSender, CobsTcParser>,
|
||||||
TcSender: PacketSenderRaw<Error = SendError>,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError,
|
|
||||||
SendError: 'static,
|
|
||||||
> {
|
|
||||||
pub generic_server: TcpTmtcGenericServer<
|
|
||||||
TmSource,
|
|
||||||
TcSender,
|
|
||||||
CobsTmSender,
|
|
||||||
CobsTcParser,
|
|
||||||
HandledConnection,
|
|
||||||
TmError,
|
|
||||||
SendError,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<TmError: 'static, TcError: 'static> TcpTmtcInCobsServer<TmError, TcError> {
|
||||||
TmSource: PacketSource<Error = TmError>,
|
|
||||||
TcReceiver: PacketSenderRaw<Error = TcError>,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError: 'static,
|
|
||||||
TcError: 'static,
|
|
||||||
> TcpTmtcInCobsServer<TmSource, TcReceiver, HandledConnection, TmError, TcError>
|
|
||||||
{
|
|
||||||
/// Create a new TCP TMTC server which exchanges TMTC packets encoded with
|
/// Create a new TCP TMTC server which exchanges TMTC packets encoded with
|
||||||
/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
||||||
///
|
///
|
||||||
@ -155,11 +128,9 @@ impl<
|
|||||||
/// forwarded to this TC receiver.
|
/// forwarded to this TC receiver.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cfg: ServerConfig,
|
cfg: ServerConfig,
|
||||||
tm_source: TmSource,
|
tm_source: Box<dyn TmPacketSource<Error = TmError>>,
|
||||||
tc_receiver: TcReceiver,
|
tc_receiver: Box<dyn ReceivesTc<Error = TcError>>,
|
||||||
handled_connection: HandledConnection,
|
) -> Result<Self, TcpTmtcError<TmError, TcError>> {
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
generic_server: TcpTmtcGenericServer::new(
|
generic_server: TcpTmtcGenericServer::new(
|
||||||
cfg,
|
cfg,
|
||||||
@ -167,8 +138,6 @@ impl<
|
|||||||
CobsTmSender::new(cfg.tm_buffer_size),
|
CobsTmSender::new(cfg.tm_buffer_size),
|
||||||
tm_source,
|
tm_source,
|
||||||
tc_receiver,
|
tc_receiver,
|
||||||
handled_connection,
|
|
||||||
stop_signal,
|
|
||||||
)?,
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -181,10 +150,9 @@ impl<
|
|||||||
/// useful if using the port number 0 for OS auto-assignment.
|
/// useful if using the port number 0 for OS auto-assignment.
|
||||||
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
||||||
|
|
||||||
/// Delegation to the [TcpTmtcGenericServer::handle_all_connections] call.
|
/// Delegation to the [TcpTmtcGenericServer::handle_next_connection] call.
|
||||||
pub fn handle_all_connections(
|
pub fn handle_next_connection(
|
||||||
&mut self,
|
&mut self,
|
||||||
poll_duration: Option<Duration>,
|
|
||||||
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
|
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,29 +167,21 @@ mod tests {
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
|
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
|
||||||
panic,
|
|
||||||
sync::mpsc,
|
|
||||||
thread,
|
thread,
|
||||||
time::Instant,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET},
|
encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET},
|
||||||
hal::std::tcp_server::{
|
hal::std::tcp_server::{
|
||||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
tests::{SyncTcCacher, SyncTmSource},
|
||||||
ConnectionResult, ServerConfig,
|
ServerConfig,
|
||||||
},
|
},
|
||||||
queue::GenericSendError,
|
|
||||||
tmtc::PacketAsVec,
|
|
||||||
ComponentId,
|
|
||||||
};
|
};
|
||||||
use alloc::sync::Arc;
|
use alloc::{boxed::Box, sync::Arc};
|
||||||
use cobs::encode;
|
use cobs::encode;
|
||||||
|
|
||||||
use super::TcpTmtcInCobsServer;
|
use super::TcpTmtcInCobsServer;
|
||||||
|
|
||||||
const TCP_SERVER_ID: ComponentId = 0x05;
|
|
||||||
|
|
||||||
fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) {
|
fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) {
|
||||||
encode_packet(&SIMPLE_PACKET, encoded_buf, current_idx)
|
encode_packet(&SIMPLE_PACKET, encoded_buf, current_idx)
|
||||||
}
|
}
|
||||||
@ -240,22 +200,13 @@ mod tests {
|
|||||||
|
|
||||||
fn generic_tmtc_server(
|
fn generic_tmtc_server(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
tc_sender: mpsc::Sender<PacketAsVec>,
|
tc_receiver: SyncTcCacher,
|
||||||
tm_source: SyncTmSource,
|
tm_source: SyncTmSource,
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
) -> TcpTmtcInCobsServer<(), ()> {
|
||||||
) -> TcpTmtcInCobsServer<
|
|
||||||
SyncTmSource,
|
|
||||||
mpsc::Sender<PacketAsVec>,
|
|
||||||
ConnectionFinishedHandler,
|
|
||||||
(),
|
|
||||||
GenericSendError,
|
|
||||||
> {
|
|
||||||
TcpTmtcInCobsServer::new(
|
TcpTmtcInCobsServer::new(
|
||||||
ServerConfig::new(TCP_SERVER_ID, *addr, Duration::from_millis(2), 1024, 1024),
|
ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024),
|
||||||
tm_source,
|
Box::new(tm_source),
|
||||||
tc_sender,
|
Box::new(tc_receiver),
|
||||||
ConnectionFinishedHandler::default(),
|
|
||||||
stop_signal,
|
|
||||||
)
|
)
|
||||||
.expect("TCP server generation failed")
|
.expect("TCP server generation failed")
|
||||||
}
|
}
|
||||||
@ -263,10 +214,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_server_basic_no_tm() {
|
fn test_server_basic_no_tm() {
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
||||||
let (tc_sender, tc_receiver) = mpsc::channel();
|
let tc_receiver = SyncTcCacher::default();
|
||||||
let tm_source = SyncTmSource::default();
|
let tm_source = SyncTmSource::default();
|
||||||
let mut tcp_server =
|
let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source);
|
||||||
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source, None);
|
|
||||||
let dest_addr = tcp_server
|
let dest_addr = tcp_server
|
||||||
.local_addr()
|
.local_addr()
|
||||||
.expect("retrieving dest addr failed");
|
.expect("retrieving dest addr failed");
|
||||||
@ -274,20 +224,13 @@ mod tests {
|
|||||||
let set_if_done = conn_handled.clone();
|
let set_if_done = conn_handled.clone();
|
||||||
// Call the connection handler in separate thread, does block.
|
// Call the connection handler in separate thread, does block.
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
|
let result = tcp_server.handle_next_connection();
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||||
}
|
}
|
||||||
let result = result.unwrap();
|
let conn_result = result.unwrap();
|
||||||
assert_eq!(result, ConnectionResult::HandledConnections(1));
|
assert_eq!(conn_result.num_received_tcs, 1);
|
||||||
tcp_server
|
assert_eq!(conn_result.num_sent_tms, 0);
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_last_connection(0, 1);
|
|
||||||
tcp_server
|
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_no_connections_left();
|
|
||||||
set_if_done.store(true, Ordering::Relaxed);
|
set_if_done.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
// Send TC to server now.
|
// Send TC to server now.
|
||||||
@ -309,20 +252,24 @@ mod tests {
|
|||||||
panic!("connection was not handled properly");
|
panic!("connection was not handled properly");
|
||||||
}
|
}
|
||||||
// Check that the packet was received and decoded successfully.
|
// Check that the packet was received and decoded successfully.
|
||||||
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
|
let mut tc_queue = tc_receiver
|
||||||
assert_eq!(packet_with_sender.packet, &SIMPLE_PACKET);
|
.tc_queue
|
||||||
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
|
.lock()
|
||||||
|
.expect("locking tc queue failed");
|
||||||
|
assert_eq!(tc_queue.len(), 1);
|
||||||
|
assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET);
|
||||||
|
drop(tc_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_server_basic_multi_tm_multi_tc() {
|
fn test_server_basic_multi_tm_multi_tc() {
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
||||||
let (tc_sender, tc_receiver) = mpsc::channel();
|
let tc_receiver = SyncTcCacher::default();
|
||||||
let mut tm_source = SyncTmSource::default();
|
let mut tm_source = SyncTmSource::default();
|
||||||
tm_source.add_tm(&INVERTED_PACKET);
|
tm_source.add_tm(&INVERTED_PACKET);
|
||||||
tm_source.add_tm(&SIMPLE_PACKET);
|
tm_source.add_tm(&SIMPLE_PACKET);
|
||||||
let mut tcp_server =
|
let mut tcp_server =
|
||||||
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source.clone(), None);
|
generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone());
|
||||||
let dest_addr = tcp_server
|
let dest_addr = tcp_server
|
||||||
.local_addr()
|
.local_addr()
|
||||||
.expect("retrieving dest addr failed");
|
.expect("retrieving dest addr failed");
|
||||||
@ -330,20 +277,13 @@ mod tests {
|
|||||||
let set_if_done = conn_handled.clone();
|
let set_if_done = conn_handled.clone();
|
||||||
// Call the connection handler in separate thread, does block.
|
// Call the connection handler in separate thread, does block.
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
|
let result = tcp_server.handle_next_connection();
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||||
}
|
}
|
||||||
let result = result.unwrap();
|
let conn_result = result.unwrap();
|
||||||
assert_eq!(result, ConnectionResult::HandledConnections(1));
|
assert_eq!(conn_result.num_received_tcs, 2, "Not enough TCs received");
|
||||||
tcp_server
|
assert_eq!(conn_result.num_sent_tms, 2, "Not enough TMs received");
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_last_connection(2, 2);
|
|
||||||
tcp_server
|
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_no_connections_left();
|
|
||||||
set_if_done.store(true, Ordering::Relaxed);
|
set_if_done.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
// Send TC to server now.
|
// Send TC to server now.
|
||||||
@ -417,78 +357,13 @@ mod tests {
|
|||||||
panic!("connection was not handled properly");
|
panic!("connection was not handled properly");
|
||||||
}
|
}
|
||||||
// Check that the packet was received and decoded successfully.
|
// Check that the packet was received and decoded successfully.
|
||||||
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
|
let mut tc_queue = tc_receiver
|
||||||
let packet = &packet_with_sender.packet;
|
.tc_queue
|
||||||
assert_eq!(packet, &SIMPLE_PACKET);
|
.lock()
|
||||||
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
|
.expect("locking tc queue failed");
|
||||||
let packet = &packet_with_sender.packet;
|
assert_eq!(tc_queue.len(), 2);
|
||||||
assert_eq!(packet, &INVERTED_PACKET);
|
assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET);
|
||||||
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
|
assert_eq!(tc_queue.pop_front().unwrap(), &INVERTED_PACKET);
|
||||||
}
|
drop(tc_queue);
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_server_accept_timeout() {
|
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
|
||||||
let (tc_sender, _tc_receiver) = mpsc::channel();
|
|
||||||
let tm_source = SyncTmSource::default();
|
|
||||||
let mut tcp_server =
|
|
||||||
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source, None);
|
|
||||||
let start = Instant::now();
|
|
||||||
// Call the connection handler in separate thread, does block.
|
|
||||||
let thread_jh = thread::spawn(move || loop {
|
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
|
|
||||||
if result.is_err() {
|
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
|
||||||
}
|
|
||||||
let result = result.unwrap();
|
|
||||||
if result == ConnectionResult::AcceptTimeout {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if Instant::now() - start > Duration::from_millis(100) {
|
|
||||||
panic!("regular stop signal handling failed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread_jh.join().expect("thread join failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_server_stop_signal() {
|
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
|
||||||
let (tc_sender, _tc_receiver) = mpsc::channel();
|
|
||||||
let tm_source = SyncTmSource::default();
|
|
||||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
|
||||||
let mut tcp_server = generic_tmtc_server(
|
|
||||||
&auto_port_addr,
|
|
||||||
tc_sender.clone(),
|
|
||||||
tm_source,
|
|
||||||
Some(stop_signal.clone()),
|
|
||||||
);
|
|
||||||
let dest_addr = tcp_server
|
|
||||||
.local_addr()
|
|
||||||
.expect("retrieving dest addr failed");
|
|
||||||
let stop_signal_copy = stop_signal.clone();
|
|
||||||
let start = Instant::now();
|
|
||||||
// Call the connection handler in separate thread, does block.
|
|
||||||
let thread_jh = thread::spawn(move || loop {
|
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
|
|
||||||
if result.is_err() {
|
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
|
||||||
}
|
|
||||||
let result = result.unwrap();
|
|
||||||
if result == ConnectionResult::AcceptTimeout {
|
|
||||||
panic!("unexpected accept timeout");
|
|
||||||
}
|
|
||||||
if stop_signal_copy.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if Instant::now() - start > Duration::from_millis(100) {
|
|
||||||
panic!("regular stop signal handling failed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// We connect but do not do anything.
|
|
||||||
let _stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
|
||||||
stop_signal.store(true, Ordering::Relaxed);
|
|
||||||
// No need to drop the connection, the stop signal should take take of everything.
|
|
||||||
thread_jh.join().expect("thread join failed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +1,21 @@
|
|||||||
//! Generic TCP TMTC servers with different TMTC format flavours.
|
//! Generic TCP TMTC servers with different TMTC format flavours.
|
||||||
use alloc::sync::Arc;
|
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::{boxed::Box, vec::Vec};
|
||||||
use core::sync::atomic::AtomicBool;
|
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use mio::net::{TcpListener, TcpStream};
|
|
||||||
use mio::{Events, Interest, Poll, Token};
|
|
||||||
use socket2::{Domain, Socket, Type};
|
use socket2::{Domain, Socket, Type};
|
||||||
use std::io::{self, Read};
|
use std::io::Read;
|
||||||
use std::net::SocketAddr;
|
use std::net::TcpListener;
|
||||||
|
use std::net::{SocketAddr, TcpStream};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crate::tmtc::{PacketSenderRaw, PacketSource};
|
use crate::tmtc::{ReceivesTc, TmPacketSource};
|
||||||
use crate::ComponentId;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
// Re-export the TMTC in COBS server.
|
// Re-export the TMTC in COBS server.
|
||||||
pub use crate::hal::std::tcp_cobs_server::{CobsTcParser, CobsTmSender, TcpTmtcInCobsServer};
|
pub use crate::hal::std::tcp_cobs_server::{CobsTcParser, CobsTmSender, TcpTmtcInCobsServer};
|
||||||
pub use crate::hal::std::tcp_spacepackets_server::{SpacepacketsTmSender, TcpSpacepacketsServer};
|
pub use crate::hal::std::tcp_spacepackets_server::{
|
||||||
|
SpacepacketsTcParser, SpacepacketsTmSender, TcpSpacepacketsServer,
|
||||||
|
};
|
||||||
|
|
||||||
/// Configuration struct for the generic TCP TMTC server
|
/// Configuration struct for the generic TCP TMTC server
|
||||||
///
|
///
|
||||||
@ -27,7 +25,7 @@ pub use crate::hal::std::tcp_spacepackets_server::{SpacepacketsTmSender, TcpSpac
|
|||||||
/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or
|
/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or
|
||||||
/// no TM needs to be sent, the TCP server will delay for the specified amount of time
|
/// no TM needs to be sent, the TCP server will delay for the specified amount of time
|
||||||
/// to reduce CPU load.
|
/// to reduce CPU load.
|
||||||
/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [PacketSource] and
|
/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and
|
||||||
/// encoding of that data. This buffer should at large enough to hold the maximum expected
|
/// encoding of that data. This buffer should at large enough to hold the maximum expected
|
||||||
/// TM size read from the packet source.
|
/// TM size read from the packet source.
|
||||||
/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from
|
/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from
|
||||||
@ -43,7 +41,6 @@ pub use crate::hal::std::tcp_spacepackets_server::{SpacepacketsTmSender, TcpSpac
|
|||||||
/// default.
|
/// default.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub id: ComponentId,
|
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub inner_loop_delay: Duration,
|
pub inner_loop_delay: Duration,
|
||||||
pub tm_buffer_size: usize,
|
pub tm_buffer_size: usize,
|
||||||
@ -54,20 +51,18 @@ pub struct ServerConfig {
|
|||||||
|
|
||||||
impl ServerConfig {
|
impl ServerConfig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: ComponentId,
|
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
inner_loop_delay: Duration,
|
inner_loop_delay: Duration,
|
||||||
tm_buffer_size: usize,
|
tm_buffer_size: usize,
|
||||||
tc_buffer_size: usize,
|
tc_buffer_size: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
|
||||||
addr,
|
addr,
|
||||||
inner_loop_delay,
|
inner_loop_delay,
|
||||||
tm_buffer_size,
|
tm_buffer_size,
|
||||||
tc_buffer_size,
|
tc_buffer_size,
|
||||||
reuse_addr: true,
|
reuse_addr: false,
|
||||||
reuse_port: true,
|
reuse_port: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,62 +79,37 @@ pub enum TcpTmtcError<TmError, TcError> {
|
|||||||
|
|
||||||
/// Result of one connection attempt. Contains the client address if a connection was established,
|
/// Result of one connection attempt. Contains the client address if a connection was established,
|
||||||
/// in addition to the number of telecommands and telemetry packets exchanged.
|
/// in addition to the number of telecommands and telemetry packets exchanged.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Default)]
|
||||||
pub enum ConnectionResult {
|
pub struct ConnectionResult {
|
||||||
AcceptTimeout,
|
pub addr: Option<SocketAddr>,
|
||||||
HandledConnections(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HandledConnectionInfo {
|
|
||||||
pub addr: SocketAddr,
|
|
||||||
pub num_received_tcs: u32,
|
pub num_received_tcs: u32,
|
||||||
pub num_sent_tms: u32,
|
pub num_sent_tms: u32,
|
||||||
/// The generic TCP server can be stopped using an external signal. If this happened, this
|
|
||||||
/// boolean will be set to true.
|
|
||||||
pub stopped_by_signal: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HandledConnectionInfo {
|
|
||||||
pub fn new(addr: SocketAddr) -> Self {
|
|
||||||
Self {
|
|
||||||
addr,
|
|
||||||
num_received_tcs: 0,
|
|
||||||
num_sent_tms: 0,
|
|
||||||
stopped_by_signal: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HandledConnectionHandler {
|
|
||||||
fn handled_connection(&mut self, info: HandledConnectionInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic parser abstraction for an object which can parse for telecommands given a raw
|
/// Generic parser abstraction for an object which can parse for telecommands given a raw
|
||||||
/// bytestream received from a TCP socket and send them using a generic [PacketSenderRaw]
|
/// bytestream received from a TCP socket and send them to a generic [ReceivesTc] telecommand
|
||||||
/// implementation. This allows different encoding schemes for telecommands.
|
/// receiver. This allows different encoding schemes for telecommands.
|
||||||
pub trait TcpTcParser<TmError, SendError> {
|
pub trait TcpTcParser<TmError, TcError> {
|
||||||
fn handle_tc_parsing(
|
fn handle_tc_parsing(
|
||||||
&mut self,
|
&mut self,
|
||||||
tc_buffer: &mut [u8],
|
tc_buffer: &mut [u8],
|
||||||
sender_id: ComponentId,
|
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
|
||||||
tc_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
|
conn_result: &mut ConnectionResult,
|
||||||
conn_result: &mut HandledConnectionInfo,
|
|
||||||
current_write_idx: usize,
|
current_write_idx: usize,
|
||||||
next_write_idx: &mut usize,
|
next_write_idx: &mut usize,
|
||||||
) -> Result<(), TcpTmtcError<TmError, SendError>>;
|
) -> Result<(), TcpTmtcError<TmError, TcError>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic sender abstraction for an object which can pull telemetry from a given TM source
|
/// Generic sender abstraction for an object which can pull telemetry from a given TM source
|
||||||
/// using a [PacketSource] and then send them back to a client using a given [TcpStream].
|
/// using a [TmPacketSource] and then send them back to a client using a given [TcpStream].
|
||||||
/// The concrete implementation can also perform any encoding steps which are necessary before
|
/// The concrete implementation can also perform any encoding steps which are necessary before
|
||||||
/// sending back the data to a client.
|
/// sending back the data to a client.
|
||||||
pub trait TcpTmSender<TmError, TcError> {
|
pub trait TcpTmSender<TmError, TcError> {
|
||||||
fn handle_tm_sending(
|
fn handle_tm_sending(
|
||||||
&mut self,
|
&mut self,
|
||||||
tm_buffer: &mut [u8],
|
tm_buffer: &mut [u8],
|
||||||
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
|
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
|
||||||
conn_result: &mut HandledConnectionInfo,
|
conn_result: &mut ConnectionResult,
|
||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
) -> Result<bool, TcpTmtcError<TmError, TcError>>;
|
) -> Result<bool, TcpTmtcError<TmError, TcError>>;
|
||||||
}
|
}
|
||||||
@ -151,9 +121,9 @@ pub trait TcpTmSender<TmError, TcError> {
|
|||||||
/// through the following 4 core abstractions:
|
/// through the following 4 core abstractions:
|
||||||
///
|
///
|
||||||
/// 1. [TcpTcParser] to parse for telecommands from the raw bytestream received from a client.
|
/// 1. [TcpTcParser] to parse for telecommands from the raw bytestream received from a client.
|
||||||
/// 2. Parsed telecommands will be sent using the [PacketSenderRaw] object.
|
/// 2. Parsed telecommands will be sent to the [ReceivesTc] telecommand receiver.
|
||||||
/// 3. [TcpTmSender] to send telemetry pulled from a TM source back to the client.
|
/// 3. [TcpTmSender] to send telemetry pulled from a TM source back to the client.
|
||||||
/// 4. [PacketSource] as a generic TM source used by the [TcpTmSender].
|
/// 4. [TmPacketSource] as a generic TM source used by the [TcpTmSender].
|
||||||
///
|
///
|
||||||
/// It is possible to specify custom abstractions to build a dedicated TCP TMTC server without
|
/// It is possible to specify custom abstractions to build a dedicated TCP TMTC server without
|
||||||
/// having to re-implement common logic.
|
/// having to re-implement common logic.
|
||||||
@ -161,49 +131,23 @@ pub trait TcpTmSender<TmError, TcError> {
|
|||||||
/// Currently, this framework offers the following concrete implementations:
|
/// Currently, this framework offers the following concrete implementations:
|
||||||
///
|
///
|
||||||
/// 1. [TcpTmtcInCobsServer] to exchange TMTC wrapped inside the COBS framing protocol.
|
/// 1. [TcpTmtcInCobsServer] to exchange TMTC wrapped inside the COBS framing protocol.
|
||||||
/// 2. [TcpSpacepacketsServer] to exchange space packets via TCP.
|
|
||||||
pub struct TcpTmtcGenericServer<
|
pub struct TcpTmtcGenericServer<
|
||||||
TmSource: PacketSource<Error = TmError>,
|
|
||||||
TcSender: PacketSenderRaw<Error = TcSendError>,
|
|
||||||
TmSender: TcpTmSender<TmError, TcSendError>,
|
|
||||||
TcParser: TcpTcParser<TmError, TcSendError>,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError,
|
TmError,
|
||||||
TcSendError,
|
TcError,
|
||||||
|
TmHandler: TcpTmSender<TmError, TcError>,
|
||||||
|
TcHandler: TcpTcParser<TmError, TcError>,
|
||||||
> {
|
> {
|
||||||
pub id: ComponentId,
|
base: TcpTmtcServerBase<TmError, TcError>,
|
||||||
pub finished_handler: HandledConnection,
|
tc_handler: TcHandler,
|
||||||
pub(crate) listener: TcpListener,
|
tm_handler: TmHandler,
|
||||||
pub(crate) inner_loop_delay: Duration,
|
|
||||||
pub(crate) tm_source: TmSource,
|
|
||||||
pub(crate) tm_buffer: Vec<u8>,
|
|
||||||
pub(crate) tc_sender: TcSender,
|
|
||||||
pub(crate) tc_buffer: Vec<u8>,
|
|
||||||
poll: Poll,
|
|
||||||
events: Events,
|
|
||||||
pub tc_handler: TcParser,
|
|
||||||
pub tm_handler: TmSender,
|
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
TmSource: PacketSource<Error = TmError>,
|
|
||||||
TcSender: PacketSenderRaw<Error = TcSendError>,
|
|
||||||
TmSender: TcpTmSender<TmError, TcSendError>,
|
|
||||||
TcParser: TcpTcParser<TmError, TcSendError>,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError: 'static,
|
TmError: 'static,
|
||||||
TcSendError: 'static,
|
TcError: 'static,
|
||||||
>
|
TmSender: TcpTmSender<TmError, TcError>,
|
||||||
TcpTmtcGenericServer<
|
TcParser: TcpTcParser<TmError, TcError>,
|
||||||
TmSource,
|
> TcpTmtcGenericServer<TmError, TcError, TmSender, TcParser>
|
||||||
TcSender,
|
|
||||||
TmSender,
|
|
||||||
TcParser,
|
|
||||||
HandledConnection,
|
|
||||||
TmError,
|
|
||||||
TcSendError,
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
/// Create a new generic TMTC server instance.
|
/// Create a new generic TMTC server instance.
|
||||||
///
|
///
|
||||||
@ -215,77 +159,38 @@ impl<
|
|||||||
/// * `tm_sender` - Sends back telemetry to the client using the specified TM source.
|
/// * `tm_sender` - Sends back telemetry to the client using the specified TM source.
|
||||||
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
|
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
|
||||||
/// then sent back to the client.
|
/// then sent back to the client.
|
||||||
/// * `tc_sender` - Any received telecommand which was decoded successfully will be forwarded
|
/// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded
|
||||||
/// using this TC sender.
|
/// to this TC receiver.
|
||||||
/// * `stop_signal` - Can be used to stop the server even if a connection is ongoing.
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cfg: ServerConfig,
|
cfg: ServerConfig,
|
||||||
tc_parser: TcParser,
|
tc_parser: TcParser,
|
||||||
tm_sender: TmSender,
|
tm_sender: TmSender,
|
||||||
tm_source: TmSource,
|
tm_source: Box<dyn TmPacketSource<Error = TmError>>,
|
||||||
tc_receiver: TcSender,
|
tc_receiver: Box<dyn ReceivesTc<Error = TcError>>,
|
||||||
finished_handler: HandledConnection,
|
) -> Result<TcpTmtcGenericServer<TmError, TcError, TmSender, TcParser>, std::io::Error> {
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
// Create a TCP listener bound to two addresses.
|
|
||||||
let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
|
|
||||||
|
|
||||||
socket.set_reuse_address(cfg.reuse_addr)?;
|
|
||||||
#[cfg(unix)]
|
|
||||||
socket.set_reuse_port(cfg.reuse_port)?;
|
|
||||||
// MIO does not do this for us. We want the accept calls to be non-blocking.
|
|
||||||
socket.set_nonblocking(true)?;
|
|
||||||
let addr = (cfg.addr).into();
|
|
||||||
socket.bind(&addr)?;
|
|
||||||
socket.listen(128)?;
|
|
||||||
|
|
||||||
// Create a poll instance.
|
|
||||||
let poll = Poll::new()?;
|
|
||||||
// Create storage for events.
|
|
||||||
let events = Events::with_capacity(32);
|
|
||||||
let listener: std::net::TcpListener = socket.into();
|
|
||||||
let mut mio_listener = TcpListener::from_std(listener);
|
|
||||||
|
|
||||||
// Start listening for incoming connections.
|
|
||||||
poll.registry().register(
|
|
||||||
&mut mio_listener,
|
|
||||||
Token(0),
|
|
||||||
Interest::READABLE | Interest::WRITABLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: cfg.id,
|
base: TcpTmtcServerBase::new(cfg, tm_source, tc_receiver)?,
|
||||||
tc_handler: tc_parser,
|
tc_handler: tc_parser,
|
||||||
tm_handler: tm_sender,
|
tm_handler: tm_sender,
|
||||||
poll,
|
|
||||||
events,
|
|
||||||
listener: mio_listener,
|
|
||||||
inner_loop_delay: cfg.inner_loop_delay,
|
|
||||||
tm_source,
|
|
||||||
tm_buffer: vec![0; cfg.tm_buffer_size],
|
|
||||||
tc_sender: tc_receiver,
|
|
||||||
tc_buffer: vec![0; cfg.tc_buffer_size],
|
|
||||||
stop_signal,
|
|
||||||
finished_handler,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the internal [TcpListener] class.
|
/// Retrieve the internal [TcpListener] class.
|
||||||
pub fn listener(&mut self) -> &mut TcpListener {
|
pub fn listener(&mut self) -> &mut TcpListener {
|
||||||
&mut self.listener
|
self.base.listener()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can be used to retrieve the local assigned address of the TCP server. This is especially
|
/// Can be used to retrieve the local assigned address of the TCP server. This is especially
|
||||||
/// useful if using the port number 0 for OS auto-assignment.
|
/// useful if using the port number 0 for OS auto-assignment.
|
||||||
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||||
self.listener.local_addr()
|
self.base.local_addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This call is used to handle all connection from clients. Right now, it performs
|
/// This call is used to handle the next connection to a client. Right now, it performs
|
||||||
/// the following steps:
|
/// the following steps:
|
||||||
///
|
///
|
||||||
/// 1. It calls the [std::net::TcpListener::accept] method until a client connects. An optional
|
/// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API
|
||||||
/// timeout can be specified for non-blocking acceptance.
|
/// until a client connects.
|
||||||
/// 2. It reads all the telecommands from the client and parses all received data using the
|
/// 2. It reads all the telecommands from the client and parses all received data using the
|
||||||
/// user specified [TcpTcParser].
|
/// user specified [TcpTcParser].
|
||||||
/// 3. After reading and parsing all telecommands, it sends back all telemetry using the
|
/// 3. After reading and parsing all telecommands, it sends back all telemetry using the
|
||||||
@ -294,78 +199,26 @@ impl<
|
|||||||
/// The server will delay for a user-specified period if the client connects to the server
|
/// The server will delay for a user-specified period if the client connects to the server
|
||||||
/// for prolonged periods and there is no traffic for the server. This is the case if the
|
/// for prolonged periods and there is no traffic for the server. This is the case if the
|
||||||
/// client does not send any telecommands and no telemetry needs to be sent back to the client.
|
/// client does not send any telecommands and no telemetry needs to be sent back to the client.
|
||||||
pub fn handle_all_connections(
|
pub fn handle_next_connection(
|
||||||
&mut self,
|
&mut self,
|
||||||
poll_timeout: Option<Duration>,
|
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>> {
|
||||||
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcSendError>> {
|
let mut connection_result = ConnectionResult::default();
|
||||||
let mut handled_connections = 0;
|
|
||||||
// Poll Mio for events.
|
|
||||||
self.poll.poll(&mut self.events, poll_timeout)?;
|
|
||||||
let mut acceptable_connection = false;
|
|
||||||
// Process each event.
|
|
||||||
for event in self.events.iter() {
|
|
||||||
if event.token() == Token(0) {
|
|
||||||
acceptable_connection = true;
|
|
||||||
} else {
|
|
||||||
// Should never happen..
|
|
||||||
panic!("unexpected TCP event token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// I'd love to do this in the loop above, but there are issues with multiple borrows.
|
|
||||||
if acceptable_connection {
|
|
||||||
// There might be mutliple connections available. Accept until all of them have
|
|
||||||
// been handled.
|
|
||||||
loop {
|
|
||||||
match self.listener.accept() {
|
|
||||||
Ok((stream, addr)) => {
|
|
||||||
if let Err(e) = self.handle_accepted_connection(stream, addr) {
|
|
||||||
self.reregister_poll_interest()?;
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
handled_connections += 1;
|
|
||||||
}
|
|
||||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => break,
|
|
||||||
Err(err) => {
|
|
||||||
self.reregister_poll_interest()?;
|
|
||||||
return Err(TcpTmtcError::Io(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if handled_connections > 0 {
|
|
||||||
return Ok(ConnectionResult::HandledConnections(handled_connections));
|
|
||||||
}
|
|
||||||
Ok(ConnectionResult::AcceptTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reregister_poll_interest(&mut self) -> io::Result<()> {
|
|
||||||
self.poll.registry().reregister(
|
|
||||||
&mut self.listener,
|
|
||||||
Token(0),
|
|
||||||
Interest::READABLE | Interest::WRITABLE,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_accepted_connection(
|
|
||||||
&mut self,
|
|
||||||
mut stream: TcpStream,
|
|
||||||
addr: SocketAddr,
|
|
||||||
) -> Result<(), TcpTmtcError<TmError, TcSendError>> {
|
|
||||||
let mut current_write_idx;
|
let mut current_write_idx;
|
||||||
let mut next_write_idx = 0;
|
let mut next_write_idx = 0;
|
||||||
let mut connection_result = HandledConnectionInfo::new(addr);
|
let (mut stream, addr) = self.base.listener.accept()?;
|
||||||
|
stream.set_nonblocking(true)?;
|
||||||
|
connection_result.addr = Some(addr);
|
||||||
current_write_idx = next_write_idx;
|
current_write_idx = next_write_idx;
|
||||||
loop {
|
loop {
|
||||||
let read_result = stream.read(&mut self.tc_buffer[current_write_idx..]);
|
let read_result = stream.read(&mut self.base.tc_buffer[current_write_idx..]);
|
||||||
match read_result {
|
match read_result {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
// Connection closed by client. If any TC was read, parse for complete packets.
|
// Connection closed by client. If any TC was read, parse for complete packets.
|
||||||
// After that, break the outer loop.
|
// After that, break the outer loop.
|
||||||
if current_write_idx > 0 {
|
if current_write_idx > 0 {
|
||||||
self.tc_handler.handle_tc_parsing(
|
self.tc_handler.handle_tc_parsing(
|
||||||
&mut self.tc_buffer,
|
&mut self.base.tc_buffer,
|
||||||
self.id,
|
self.base.tc_receiver.as_mut(),
|
||||||
&self.tc_sender,
|
|
||||||
&mut connection_result,
|
&mut connection_result,
|
||||||
current_write_idx,
|
current_write_idx,
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
@ -376,11 +229,10 @@ impl<
|
|||||||
Ok(read_len) => {
|
Ok(read_len) => {
|
||||||
current_write_idx += read_len;
|
current_write_idx += read_len;
|
||||||
// TC buffer is full, we must parse for complete packets now.
|
// TC buffer is full, we must parse for complete packets now.
|
||||||
if current_write_idx == self.tc_buffer.capacity() {
|
if current_write_idx == self.base.tc_buffer.capacity() {
|
||||||
self.tc_handler.handle_tc_parsing(
|
self.tc_handler.handle_tc_parsing(
|
||||||
&mut self.tc_buffer,
|
&mut self.base.tc_buffer,
|
||||||
self.id,
|
self.base.tc_receiver.as_mut(),
|
||||||
&self.tc_sender,
|
|
||||||
&mut connection_result,
|
&mut connection_result,
|
||||||
current_write_idx,
|
current_write_idx,
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
@ -393,9 +245,8 @@ impl<
|
|||||||
// both UNIX and Windows.
|
// both UNIX and Windows.
|
||||||
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => {
|
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => {
|
||||||
self.tc_handler.handle_tc_parsing(
|
self.tc_handler.handle_tc_parsing(
|
||||||
&mut self.tc_buffer,
|
&mut self.base.tc_buffer,
|
||||||
self.id,
|
self.base.tc_receiver.as_mut(),
|
||||||
&self.tc_sender,
|
|
||||||
&mut connection_result,
|
&mut connection_result,
|
||||||
current_write_idx,
|
current_write_idx,
|
||||||
&mut next_write_idx,
|
&mut next_write_idx,
|
||||||
@ -403,26 +254,14 @@ impl<
|
|||||||
current_write_idx = next_write_idx;
|
current_write_idx = next_write_idx;
|
||||||
|
|
||||||
if !self.tm_handler.handle_tm_sending(
|
if !self.tm_handler.handle_tm_sending(
|
||||||
&mut self.tm_buffer,
|
&mut self.base.tm_buffer,
|
||||||
&mut self.tm_source,
|
self.base.tm_source.as_mut(),
|
||||||
&mut connection_result,
|
&mut connection_result,
|
||||||
&mut stream,
|
&mut stream,
|
||||||
)? {
|
)? {
|
||||||
// No TC read, no TM was sent, but the client has not disconnected.
|
// No TC read, no TM was sent, but the client has not disconnected.
|
||||||
// Perform an inner delay to avoid burning CPU time.
|
// Perform an inner delay to avoid burning CPU time.
|
||||||
thread::sleep(self.inner_loop_delay);
|
thread::sleep(self.base.inner_loop_delay);
|
||||||
// Optional stop signal handling.
|
|
||||||
if self.stop_signal.is_some()
|
|
||||||
&& self
|
|
||||||
.stop_signal
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.load(std::sync::atomic::Ordering::Relaxed)
|
|
||||||
{
|
|
||||||
connection_result.stopped_by_signal = true;
|
|
||||||
self.finished_handler.handled_connection(connection_result);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -432,13 +271,53 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tm_handler.handle_tm_sending(
|
self.tm_handler.handle_tm_sending(
|
||||||
&mut self.tm_buffer,
|
&mut self.base.tm_buffer,
|
||||||
&mut self.tm_source,
|
self.base.tm_source.as_mut(),
|
||||||
&mut connection_result,
|
&mut connection_result,
|
||||||
&mut stream,
|
&mut stream,
|
||||||
)?;
|
)?;
|
||||||
self.finished_handler.handled_connection(connection_result);
|
Ok(connection_result)
|
||||||
Ok(())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TcpTmtcServerBase<TmError, TcError> {
|
||||||
|
pub(crate) listener: TcpListener,
|
||||||
|
pub(crate) inner_loop_delay: Duration,
|
||||||
|
pub(crate) tm_source: Box<dyn TmPacketSource<Error = TmError>>,
|
||||||
|
pub(crate) tm_buffer: Vec<u8>,
|
||||||
|
pub(crate) tc_receiver: Box<dyn ReceivesTc<Error = TcError>>,
|
||||||
|
pub(crate) tc_buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmError, TcError> TcpTmtcServerBase<TmError, TcError> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
cfg: ServerConfig,
|
||||||
|
tm_source: Box<dyn TmPacketSource<Error = TmError>>,
|
||||||
|
tc_receiver: Box<dyn ReceivesTc<Error = TcError>>,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
// Create a TCP listener bound to two addresses.
|
||||||
|
let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
|
||||||
|
socket.set_reuse_address(cfg.reuse_addr)?;
|
||||||
|
socket.set_reuse_port(cfg.reuse_port)?;
|
||||||
|
let addr = (cfg.addr).into();
|
||||||
|
socket.bind(&addr)?;
|
||||||
|
socket.listen(128)?;
|
||||||
|
Ok(Self {
|
||||||
|
listener: socket.into(),
|
||||||
|
inner_loop_delay: cfg.inner_loop_delay,
|
||||||
|
tm_source,
|
||||||
|
tm_buffer: vec![0; cfg.tm_buffer_size],
|
||||||
|
tc_receiver,
|
||||||
|
tc_buffer: vec![0; cfg.tc_buffer_size],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn listener(&mut self) -> &mut TcpListener {
|
||||||
|
&mut self.listener
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||||
|
self.listener.local_addr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,9 +327,21 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
use alloc::{collections::VecDeque, sync::Arc, vec::Vec};
|
use alloc::{collections::VecDeque, sync::Arc, vec::Vec};
|
||||||
|
|
||||||
use crate::tmtc::PacketSource;
|
use crate::tmtc::{ReceivesTcCore, TmPacketSourceCore};
|
||||||
|
|
||||||
use super::*;
|
#[derive(Default, Clone)]
|
||||||
|
pub(crate) struct SyncTcCacher {
|
||||||
|
pub(crate) tc_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
||||||
|
}
|
||||||
|
impl ReceivesTcCore for SyncTcCacher {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed");
|
||||||
|
tc_queue.push_back(tc_raw.to_vec());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub(crate) struct SyncTmSource {
|
pub(crate) struct SyncTmSource {
|
||||||
@ -464,7 +355,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketSource for SyncTmSource {
|
impl TmPacketSourceCore for SyncTmSource {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
|
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
@ -484,30 +375,4 @@ pub(crate) mod tests {
|
|||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ConnectionFinishedHandler {
|
|
||||||
connection_info: VecDeque<HandledConnectionInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HandledConnectionHandler for ConnectionFinishedHandler {
|
|
||||||
fn handled_connection(&mut self, info: HandledConnectionInfo) {
|
|
||||||
self.connection_info.push_back(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectionFinishedHandler {
|
|
||||||
pub fn check_last_connection(&mut self, num_tms: u32, num_tcs: u32) {
|
|
||||||
let last_conn_result = self
|
|
||||||
.connection_info
|
|
||||||
.pop_back()
|
|
||||||
.expect("no connection info available");
|
|
||||||
assert_eq!(last_conn_result.num_received_tcs, num_tcs);
|
|
||||||
assert_eq!(last_conn_result.num_sent_tms, num_tms);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_no_connections_left(&self) {
|
|
||||||
assert!(self.connection_info.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,44 +1,48 @@
|
|||||||
use alloc::sync::Arc;
|
|
||||||
use core::{sync::atomic::AtomicBool, time::Duration};
|
|
||||||
use delegate::delegate;
|
use delegate::delegate;
|
||||||
use mio::net::{TcpListener, TcpStream};
|
use std::{
|
||||||
use std::{io::Write, net::SocketAddr};
|
io::Write,
|
||||||
|
net::{SocketAddr, TcpListener, TcpStream},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
encoding::{ccsds::SpacePacketValidator, parse_buffer_for_ccsds_space_packets},
|
encoding::{ccsds::PacketIdLookup, parse_buffer_for_ccsds_space_packets},
|
||||||
tmtc::{PacketSenderRaw, PacketSource},
|
tmtc::{ReceivesTc, TmPacketSource},
|
||||||
ComponentId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::tcp_server::{
|
use super::tcp_server::{
|
||||||
ConnectionResult, HandledConnectionHandler, HandledConnectionInfo, ServerConfig, TcpTcParser,
|
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
|
||||||
TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: SpacePacketValidator, TmError, TcError: 'static> TcpTcParser<TmError, TcError> for T {
|
/// Concrete [TcpTcParser] implementation for the [TcpSpacepacketsServer].
|
||||||
|
pub struct SpacepacketsTcParser {
|
||||||
|
packet_id_lookup: Box<dyn PacketIdLookup + Send>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpacepacketsTcParser {
|
||||||
|
pub fn new(packet_id_lookup: Box<dyn PacketIdLookup + Send>) -> Self {
|
||||||
|
Self { packet_id_lookup }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmError, TcError: 'static> TcpTcParser<TmError, TcError> for SpacepacketsTcParser {
|
||||||
fn handle_tc_parsing(
|
fn handle_tc_parsing(
|
||||||
&mut self,
|
&mut self,
|
||||||
tc_buffer: &mut [u8],
|
tc_buffer: &mut [u8],
|
||||||
sender_id: ComponentId,
|
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
|
||||||
tc_sender: &(impl PacketSenderRaw<Error = TcError> + ?Sized),
|
conn_result: &mut ConnectionResult,
|
||||||
conn_result: &mut HandledConnectionInfo,
|
|
||||||
current_write_idx: usize,
|
current_write_idx: usize,
|
||||||
next_write_idx: &mut usize,
|
next_write_idx: &mut usize,
|
||||||
) -> Result<(), TcpTmtcError<TmError, TcError>> {
|
) -> Result<(), TcpTmtcError<TmError, TcError>> {
|
||||||
// Reader vec full, need to parse for packets.
|
// Reader vec full, need to parse for packets.
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
conn_result.num_received_tcs += parse_buffer_for_ccsds_space_packets(
|
||||||
&tc_buffer[..current_write_idx],
|
&mut tc_buffer[..current_write_idx],
|
||||||
self,
|
self.packet_id_lookup.as_ref(),
|
||||||
sender_id,
|
tc_receiver.upcast_mut(),
|
||||||
tc_sender,
|
next_write_idx,
|
||||||
)
|
)
|
||||||
.map_err(|e| TcpTmtcError::TcError(e))?;
|
.map_err(|e| TcpTmtcError::TcError(e))?;
|
||||||
if let Some(broken_tail_start) = parse_result.incomplete_tail_start {
|
|
||||||
// Copy broken tail to front of buffer.
|
|
||||||
tc_buffer.copy_within(broken_tail_start..current_write_idx, 0);
|
|
||||||
*next_write_idx = current_write_idx - broken_tail_start;
|
|
||||||
}
|
|
||||||
conn_result.num_received_tcs += parse_result.packets_found;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,8 +55,8 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
|
|||||||
fn handle_tm_sending(
|
fn handle_tm_sending(
|
||||||
&mut self,
|
&mut self,
|
||||||
tm_buffer: &mut [u8],
|
tm_buffer: &mut [u8],
|
||||||
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
|
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
|
||||||
conn_result: &mut HandledConnectionInfo,
|
conn_result: &mut ConnectionResult,
|
||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
|
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
|
||||||
let mut tm_was_sent = false;
|
let mut tm_was_sent = false;
|
||||||
@ -79,73 +83,44 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
|
|||||||
///
|
///
|
||||||
/// This serves only works if
|
/// This serves only works if
|
||||||
/// [CCSDS 133.0-B-2 space packets](https://public.ccsds.org/Pubs/133x0b2e1.pdf) are the only
|
/// [CCSDS 133.0-B-2 space packets](https://public.ccsds.org/Pubs/133x0b2e1.pdf) are the only
|
||||||
/// packet type being exchanged. It uses the CCSDS space packet header [spacepackets::SpHeader] and
|
/// packet type being exchanged. It uses the CCSDS [spacepackets::PacketId] as the packet delimiter
|
||||||
/// a user specified [SpacePacketValidator] to determine the space packets relevant for further
|
/// and start marker when parsing for packets. The user specifies a set of expected
|
||||||
/// processing.
|
/// [spacepackets::PacketId]s as part of the server configuration for that purpose.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
|
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs)
|
||||||
/// also serves as the example application for this module.
|
/// also serves as the example application for this module.
|
||||||
pub struct TcpSpacepacketsServer<
|
pub struct TcpSpacepacketsServer<TmError, TcError: 'static> {
|
||||||
TmSource: PacketSource<Error = TmError>,
|
generic_server:
|
||||||
TcSender: PacketSenderRaw<Error = SendError>,
|
TcpTmtcGenericServer<TmError, TcError, SpacepacketsTmSender, SpacepacketsTcParser>,
|
||||||
Validator: SpacePacketValidator,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError,
|
|
||||||
SendError: 'static,
|
|
||||||
> {
|
|
||||||
pub generic_server: TcpTmtcGenericServer<
|
|
||||||
TmSource,
|
|
||||||
TcSender,
|
|
||||||
SpacepacketsTmSender,
|
|
||||||
Validator,
|
|
||||||
HandledConnection,
|
|
||||||
TmError,
|
|
||||||
SendError,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<TmError: 'static, TcError: 'static> TcpSpacepacketsServer<TmError, TcError> {
|
||||||
TmSource: PacketSource<Error = TmError>,
|
/// Create a new TCP TMTC server which exchanges CCSDS space packets.
|
||||||
TcSender: PacketSenderRaw<Error = TcError>,
|
|
||||||
Validator: SpacePacketValidator,
|
|
||||||
HandledConnection: HandledConnectionHandler,
|
|
||||||
TmError: 'static,
|
|
||||||
TcError: 'static,
|
|
||||||
> TcpSpacepacketsServer<TmSource, TcSender, Validator, HandledConnection, TmError, TcError>
|
|
||||||
{
|
|
||||||
///
|
///
|
||||||
/// ## Parameter
|
/// ## Parameter
|
||||||
///
|
///
|
||||||
/// * `cfg` - Configuration of the server.
|
/// * `cfg` - Configuration of the server.
|
||||||
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
|
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
|
||||||
/// then sent back to the client.
|
/// then sent back to the client.
|
||||||
/// * `tc_sender` - Any received telecommands which were decoded successfully will be
|
/// * `tc_receiver` - Any received telecommands which were decoded successfully will be
|
||||||
/// forwarded using this [PacketSenderRaw].
|
/// forwarded to this TC receiver.
|
||||||
/// * `validator` - Used to determine the space packets relevant for further processing and
|
/// * `packet_id_lookup` - This lookup table contains the relevant packets IDs for packet
|
||||||
/// to detect broken space packets.
|
/// parsing. This mechanism is used to have a start marker for finding CCSDS packets.
|
||||||
/// * `handled_connection_hook` - Called to notify the user about a succesfully handled
|
|
||||||
/// connection.
|
|
||||||
/// * `stop_signal` - Can be used to shut down the TCP server even for longer running
|
|
||||||
/// connections.
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cfg: ServerConfig,
|
cfg: ServerConfig,
|
||||||
tm_source: TmSource,
|
tm_source: Box<dyn TmPacketSource<Error = TmError>>,
|
||||||
tc_sender: TcSender,
|
tc_receiver: Box<dyn ReceivesTc<Error = TcError>>,
|
||||||
validator: Validator,
|
packet_id_lookup: Box<dyn PacketIdLookup + Send>,
|
||||||
handled_connection_hook: HandledConnection,
|
) -> Result<Self, TcpTmtcError<TmError, TcError>> {
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
generic_server: TcpTmtcGenericServer::new(
|
generic_server: TcpTmtcGenericServer::new(
|
||||||
cfg,
|
cfg,
|
||||||
validator,
|
SpacepacketsTcParser::new(packet_id_lookup),
|
||||||
SpacepacketsTmSender::default(),
|
SpacepacketsTmSender::default(),
|
||||||
tm_source,
|
tm_source,
|
||||||
tc_sender,
|
tc_receiver,
|
||||||
handled_connection_hook,
|
|
||||||
stop_signal,
|
|
||||||
)?,
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -158,10 +133,9 @@ impl<
|
|||||||
/// useful if using the port number 0 for OS auto-assignment.
|
/// useful if using the port number 0 for OS auto-assignment.
|
||||||
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
||||||
|
|
||||||
/// Delegation to the [TcpTmtcGenericServer::handle_all_connections] call.
|
/// Delegation to the [TcpTmtcGenericServer::handle_next_connection] call.
|
||||||
pub fn handle_all_connections(
|
pub fn handle_next_connection(
|
||||||
&mut self,
|
&mut self,
|
||||||
poll_timeout: Option<Duration>
|
|
||||||
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
|
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,70 +152,39 @@ mod tests {
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
|
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
|
||||||
sync::mpsc,
|
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
use alloc::{boxed::Box, sync::Arc};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
ecss::{tc::PusTcCreator, SerializablePusPacket},
|
||||||
CcsdsPacket, PacketId, SpHeader,
|
PacketId, SpHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::hal::std::tcp_server::{
|
||||||
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
tests::{SyncTcCacher, SyncTmSource},
|
||||||
hal::std::tcp_server::{
|
ServerConfig,
|
||||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
|
||||||
ConnectionResult, ServerConfig,
|
|
||||||
},
|
|
||||||
queue::GenericSendError,
|
|
||||||
tmtc::PacketAsVec,
|
|
||||||
ComponentId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::TcpSpacepacketsServer;
|
use super::TcpSpacepacketsServer;
|
||||||
|
|
||||||
const TCP_SERVER_ID: ComponentId = 0x05;
|
|
||||||
const TEST_APID_0: u16 = 0x02;
|
const TEST_APID_0: u16 = 0x02;
|
||||||
const TEST_PACKET_ID_0: PacketId = PacketId::new_for_tc(true, TEST_APID_0);
|
const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0);
|
||||||
const TEST_APID_1: u16 = 0x10;
|
const TEST_APID_1: u16 = 0x10;
|
||||||
const TEST_PACKET_ID_1: PacketId = PacketId::new_for_tc(true, TEST_APID_1);
|
const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1);
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SimpleValidator(pub HashSet<PacketId>);
|
|
||||||
|
|
||||||
impl SpacePacketValidator for SimpleValidator {
|
|
||||||
fn validate(&self, sp_header: &SpHeader, _raw_buf: &[u8]) -> SpValidity {
|
|
||||||
if self.0.contains(&sp_header.packet_id()) {
|
|
||||||
return SpValidity::Valid;
|
|
||||||
}
|
|
||||||
// Simple case: Assume that the interface always contains valid space packets.
|
|
||||||
SpValidity::Skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generic_tmtc_server(
|
fn generic_tmtc_server(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
tc_sender: mpsc::Sender<PacketAsVec>,
|
tc_receiver: SyncTcCacher,
|
||||||
tm_source: SyncTmSource,
|
tm_source: SyncTmSource,
|
||||||
validator: SimpleValidator,
|
packet_id_lookup: HashSet<PacketId>,
|
||||||
stop_signal: Option<Arc<AtomicBool>>,
|
) -> TcpSpacepacketsServer<(), ()> {
|
||||||
) -> TcpSpacepacketsServer<
|
|
||||||
SyncTmSource,
|
|
||||||
mpsc::Sender<PacketAsVec>,
|
|
||||||
SimpleValidator,
|
|
||||||
ConnectionFinishedHandler,
|
|
||||||
(),
|
|
||||||
GenericSendError,
|
|
||||||
> {
|
|
||||||
TcpSpacepacketsServer::new(
|
TcpSpacepacketsServer::new(
|
||||||
ServerConfig::new(TCP_SERVER_ID, *addr, Duration::from_millis(2), 1024, 1024),
|
ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024),
|
||||||
tm_source,
|
Box::new(tm_source),
|
||||||
tc_sender,
|
Box::new(tc_receiver),
|
||||||
validator,
|
Box::new(packet_id_lookup),
|
||||||
ConnectionFinishedHandler::default(),
|
|
||||||
stop_signal,
|
|
||||||
)
|
)
|
||||||
.expect("TCP server generation failed")
|
.expect("TCP server generation failed")
|
||||||
}
|
}
|
||||||
@ -249,16 +192,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic_tc_only() {
|
fn test_basic_tc_only() {
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
||||||
let (tc_sender, tc_receiver) = mpsc::channel();
|
let tc_receiver = SyncTcCacher::default();
|
||||||
let tm_source = SyncTmSource::default();
|
let tm_source = SyncTmSource::default();
|
||||||
let mut validator = SimpleValidator::default();
|
let mut packet_id_lookup = HashSet::new();
|
||||||
validator.0.insert(TEST_PACKET_ID_0);
|
packet_id_lookup.insert(TEST_PACKET_ID_0);
|
||||||
let mut tcp_server = generic_tmtc_server(
|
let mut tcp_server = generic_tmtc_server(
|
||||||
&auto_port_addr,
|
&auto_port_addr,
|
||||||
tc_sender.clone(),
|
tc_receiver.clone(),
|
||||||
tm_source,
|
tm_source,
|
||||||
validator,
|
packet_id_lookup,
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
let dest_addr = tcp_server
|
let dest_addr = tcp_server
|
||||||
.local_addr()
|
.local_addr()
|
||||||
@ -267,24 +209,17 @@ mod tests {
|
|||||||
let set_if_done = conn_handled.clone();
|
let set_if_done = conn_handled.clone();
|
||||||
// Call the connection handler in separate thread, does block.
|
// Call the connection handler in separate thread, does block.
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
|
let result = tcp_server.handle_next_connection();
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||||
}
|
}
|
||||||
let conn_result = result.unwrap();
|
let conn_result = result.unwrap();
|
||||||
matches!(conn_result, ConnectionResult::HandledConnections(1));
|
assert_eq!(conn_result.num_received_tcs, 1);
|
||||||
tcp_server
|
assert_eq!(conn_result.num_sent_tms, 0);
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_last_connection(0, 1);
|
|
||||||
tcp_server
|
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_no_connections_left();
|
|
||||||
set_if_done.store(true, Ordering::Relaxed);
|
set_if_done.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
let ping_tc =
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
let tc_0 = ping_tc.to_vec().expect("packet generation failed");
|
let tc_0 = ping_tc.to_vec().expect("packet generation failed");
|
||||||
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
||||||
stream
|
stream
|
||||||
@ -301,40 +236,40 @@ mod tests {
|
|||||||
if !conn_handled.load(Ordering::Relaxed) {
|
if !conn_handled.load(Ordering::Relaxed) {
|
||||||
panic!("connection was not handled properly");
|
panic!("connection was not handled properly");
|
||||||
}
|
}
|
||||||
let packet = tc_receiver.try_recv().expect("receiving TC failed");
|
// Check that TC has arrived.
|
||||||
assert_eq!(packet.packet, tc_0);
|
let mut tc_queue = tc_receiver.tc_queue.lock().unwrap();
|
||||||
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
|
assert_eq!(tc_queue.len(), 1);
|
||||||
|
assert_eq!(tc_queue.pop_front().unwrap(), tc_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multi_tc_multi_tm() {
|
fn test_multi_tc_multi_tm() {
|
||||||
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
|
||||||
let (tc_sender, tc_receiver) = mpsc::channel();
|
let tc_receiver = SyncTcCacher::default();
|
||||||
let mut tm_source = SyncTmSource::default();
|
let mut tm_source = SyncTmSource::default();
|
||||||
|
|
||||||
// Add telemetry
|
// Add telemetry
|
||||||
let mut total_tm_len = 0;
|
let mut total_tm_len = 0;
|
||||||
let verif_tm =
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 1, 1, &[], true);
|
let verif_tm = PusTcCreator::new_simple(&mut sph, 1, 1, None, true);
|
||||||
let tm_0 = verif_tm.to_vec().expect("writing packet failed");
|
let tm_0 = verif_tm.to_vec().expect("writing packet failed");
|
||||||
total_tm_len += tm_0.len();
|
total_tm_len += tm_0.len();
|
||||||
tm_source.add_tm(&tm_0);
|
tm_source.add_tm(&tm_0);
|
||||||
let verif_tm =
|
let mut sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
||||||
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_1), 1, 3, &[], true);
|
let verif_tm = PusTcCreator::new_simple(&mut sph, 1, 3, None, true);
|
||||||
let tm_1 = verif_tm.to_vec().expect("writing packet failed");
|
let tm_1 = verif_tm.to_vec().expect("writing packet failed");
|
||||||
total_tm_len += tm_1.len();
|
total_tm_len += tm_1.len();
|
||||||
tm_source.add_tm(&tm_1);
|
tm_source.add_tm(&tm_1);
|
||||||
|
|
||||||
// Set up server
|
// Set up server
|
||||||
let mut validator = SimpleValidator::default();
|
let mut packet_id_lookup = HashSet::new();
|
||||||
validator.0.insert(TEST_PACKET_ID_0);
|
packet_id_lookup.insert(TEST_PACKET_ID_0);
|
||||||
validator.0.insert(TEST_PACKET_ID_1);
|
packet_id_lookup.insert(TEST_PACKET_ID_1);
|
||||||
let mut tcp_server = generic_tmtc_server(
|
let mut tcp_server = generic_tmtc_server(
|
||||||
&auto_port_addr,
|
&auto_port_addr,
|
||||||
tc_sender.clone(),
|
tc_receiver.clone(),
|
||||||
tm_source,
|
tm_source,
|
||||||
validator,
|
packet_id_lookup,
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
let dest_addr = tcp_server
|
let dest_addr = tcp_server
|
||||||
.local_addr()
|
.local_addr()
|
||||||
@ -344,20 +279,16 @@ mod tests {
|
|||||||
|
|
||||||
// Call the connection handler in separate thread, does block.
|
// Call the connection handler in separate thread, does block.
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
|
let result = tcp_server.handle_next_connection();
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||||
}
|
}
|
||||||
let conn_result = result.unwrap();
|
let conn_result = result.unwrap();
|
||||||
matches!(conn_result, ConnectionResult::HandledConnections(1));
|
assert_eq!(
|
||||||
tcp_server
|
conn_result.num_received_tcs, 2,
|
||||||
.generic_server
|
"wrong number of received TCs"
|
||||||
.finished_handler
|
);
|
||||||
.check_last_connection(2, 2);
|
assert_eq!(conn_result.num_sent_tms, 2, "wrong number of sent TMs");
|
||||||
tcp_server
|
|
||||||
.generic_server
|
|
||||||
.finished_handler
|
|
||||||
.check_no_connections_left();
|
|
||||||
set_if_done.store(true, Ordering::Relaxed);
|
set_if_done.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
||||||
@ -366,14 +297,14 @@ mod tests {
|
|||||||
.expect("setting reas timeout failed");
|
.expect("setting reas timeout failed");
|
||||||
|
|
||||||
// Send telecommands
|
// Send telecommands
|
||||||
let ping_tc =
|
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
||||||
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
|
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
let tc_0 = ping_tc.to_vec().expect("ping tc creation failed");
|
let tc_0 = ping_tc.to_vec().expect("ping tc creation failed");
|
||||||
stream
|
stream
|
||||||
.write_all(&tc_0)
|
.write_all(&tc_0)
|
||||||
.expect("writing to TCP server failed");
|
.expect("writing to TCP server failed");
|
||||||
let action_tc =
|
let mut sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
||||||
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_1), 8, 0, &[], true);
|
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
||||||
let tc_1 = action_tc.to_vec().expect("action tc creation failed");
|
let tc_1 = action_tc.to_vec().expect("action tc creation failed");
|
||||||
stream
|
stream
|
||||||
.write_all(&tc_1)
|
.write_all(&tc_1)
|
||||||
@ -408,10 +339,9 @@ mod tests {
|
|||||||
panic!("connection was not handled properly");
|
panic!("connection was not handled properly");
|
||||||
}
|
}
|
||||||
// Check that TC has arrived.
|
// Check that TC has arrived.
|
||||||
let packet_0 = tc_receiver.try_recv().expect("receiving TC failed");
|
let mut tc_queue = tc_receiver.tc_queue.lock().unwrap();
|
||||||
assert_eq!(packet_0.packet, tc_0);
|
assert_eq!(tc_queue.len(), 2);
|
||||||
let packet_1 = tc_receiver.try_recv().expect("receiving TC failed");
|
assert_eq!(tc_queue.pop_front().unwrap(), tc_0);
|
||||||
assert_eq!(packet_1.packet, tc_1);
|
assert_eq!(tc_queue.pop_front().unwrap(), tc_1);
|
||||||
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,7 @@
|
|||||||
//! Generic UDP TC server.
|
//! Generic UDP TC server.
|
||||||
use crate::tmtc::PacketSenderRaw;
|
use crate::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||||
use crate::ComponentId;
|
use std::boxed::Box;
|
||||||
use core::fmt::Debug;
|
use std::io::{Error, ErrorKind};
|
||||||
use std::io::{self, ErrorKind};
|
|
||||||
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
|
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
|
||||||
use std::vec;
|
use std::vec;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
@ -12,46 +11,45 @@ use std::vec::Vec;
|
|||||||
///
|
///
|
||||||
/// It caches all received telecomands into a vector. The maximum expected telecommand size should
|
/// It caches all received telecomands into a vector. The maximum expected telecommand size should
|
||||||
/// be declared upfront. This avoids dynamic allocation during run-time. The user can specify a TC
|
/// be declared upfront. This avoids dynamic allocation during run-time. The user can specify a TC
|
||||||
/// sender in form of a special trait object which implements [PacketSenderRaw]. For example, this
|
/// receiver in form of a special trait object which implements [ReceivesTc]. Please note that the
|
||||||
/// can be used to send the telecommands to a centralized TC source component for further
|
/// receiver should copy out the received data if it the data is required past the
|
||||||
/// processing and routing.
|
/// [ReceivesTcCore::pass_tc] call.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||||
/// use std::sync::mpsc;
|
/// use spacepackets::ecss::SerializablePusPacket;
|
||||||
/// use spacepackets::ecss::WritablePusPacket;
|
/// use satrs_core::hal::std::udp_server::UdpTcServer;
|
||||||
/// use satrs::hal::std::udp_server::UdpTcServer;
|
/// use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||||
/// use satrs::ComponentId;
|
|
||||||
/// use satrs::tmtc::PacketSenderRaw;
|
|
||||||
/// use spacepackets::SpHeader;
|
/// use spacepackets::SpHeader;
|
||||||
/// use spacepackets::ecss::tc::PusTcCreator;
|
/// use spacepackets::ecss::tc::PusTcCreator;
|
||||||
///
|
///
|
||||||
/// const UDP_SERVER_ID: ComponentId = 0x05;
|
/// #[derive (Default)]
|
||||||
|
/// struct PingReceiver {}
|
||||||
|
/// impl ReceivesTcCore for PingReceiver {
|
||||||
|
/// type Error = ();
|
||||||
|
/// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
/// assert_eq!(tc_raw.len(), 13);
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// let (packet_sender, packet_receiver) = mpsc::channel();
|
/// let mut buf = [0; 32];
|
||||||
/// let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
/// let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
||||||
/// let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, packet_sender)
|
/// let ping_receiver = PingReceiver::default();
|
||||||
|
/// let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||||
/// .expect("Creating UDP TMTC server failed");
|
/// .expect("Creating UDP TMTC server failed");
|
||||||
/// let sph = SpHeader::new_from_apid(0x02);
|
/// let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
|
||||||
/// let pus_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
|
/// let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
/// // Can not fail.
|
/// let len = pus_tc
|
||||||
/// let ping_tc_raw = pus_tc.to_vec().unwrap();
|
/// .write_to_bytes(&mut buf)
|
||||||
///
|
/// .expect("Error writing PUS TC packet");
|
||||||
/// // Now create a UDP client and send the ping telecommand to the server.
|
/// assert_eq!(len, 13);
|
||||||
/// let client = UdpSocket::bind("127.0.0.1:0").expect("creating UDP client failed");
|
/// let client = UdpSocket::bind("127.0.0.1:7778").expect("Connecting to UDP server failed");
|
||||||
/// client
|
/// client
|
||||||
/// .send_to(&ping_tc_raw, dest_addr)
|
/// .send_to(&buf[0..len], dest_addr)
|
||||||
/// .expect("Error sending PUS TC via UDP");
|
/// .expect("Error sending PUS TC via UDP");
|
||||||
/// let recv_result = udp_tc_server.try_recv_tc();
|
|
||||||
/// assert!(recv_result.is_ok());
|
|
||||||
/// // The packet is received by the UDP TC server and sent via the mpsc channel.
|
|
||||||
/// let sent_packet_with_sender = packet_receiver.try_recv().expect("expected telecommand");
|
|
||||||
/// assert_eq!(sent_packet_with_sender.packet, ping_tc_raw);
|
|
||||||
/// assert_eq!(sent_packet_with_sender.sender_id, UDP_SERVER_ID);
|
|
||||||
/// // No more packets received.
|
|
||||||
/// matches!(packet_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The [satrs-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/satrs-example)
|
/// The [satrs-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/satrs-example)
|
||||||
@ -59,45 +57,65 @@ use std::vec::Vec;
|
|||||||
/// [example code](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/tmtc.rs#L67)
|
/// [example code](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/tmtc.rs#L67)
|
||||||
/// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port
|
/// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port
|
||||||
/// and then forwards them to a generic CCSDS packet receiver.
|
/// and then forwards them to a generic CCSDS packet receiver.
|
||||||
pub struct UdpTcServer<TcSender: PacketSenderRaw<Error = SendError>, SendError> {
|
pub struct UdpTcServer<E> {
|
||||||
pub id: ComponentId,
|
|
||||||
pub socket: UdpSocket,
|
pub socket: UdpSocket,
|
||||||
recv_buf: Vec<u8>,
|
recv_buf: Vec<u8>,
|
||||||
sender_addr: Option<SocketAddr>,
|
sender_addr: Option<SocketAddr>,
|
||||||
pub tc_sender: TcSender,
|
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug)]
|
||||||
pub enum ReceiveResult<SendError: Debug + 'static> {
|
pub enum ReceiveResult<E> {
|
||||||
#[error("nothing was received")]
|
|
||||||
NothingReceived,
|
NothingReceived,
|
||||||
#[error(transparent)]
|
IoError(Error),
|
||||||
Io(#[from] io::Error),
|
ReceiverError(E),
|
||||||
#[error(transparent)]
|
|
||||||
Send(SendError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
impl<E> From<Error> for ReceiveResult<E> {
|
||||||
UdpTcServer<TcSender, SendError>
|
fn from(e: Error) -> Self {
|
||||||
{
|
ReceiveResult::IoError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: PartialEq> PartialEq for ReceiveResult<E> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
use ReceiveResult::*;
|
||||||
|
match (self, other) {
|
||||||
|
(IoError(ref e), IoError(ref other_e)) => e.kind() == other_e.kind(),
|
||||||
|
(NothingReceived, NothingReceived) => true,
|
||||||
|
(ReceiverError(e), ReceiverError(other_e)) => e == other_e,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Eq + PartialEq> Eq for ReceiveResult<E> {}
|
||||||
|
|
||||||
|
impl<E: 'static> ReceivesTcCore for UdpTcServer<E> {
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.tc_receiver.pass_tc(tc_raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: 'static> UdpTcServer<E> {
|
||||||
pub fn new<A: ToSocketAddrs>(
|
pub fn new<A: ToSocketAddrs>(
|
||||||
id: ComponentId,
|
|
||||||
addr: A,
|
addr: A,
|
||||||
max_recv_size: usize,
|
max_recv_size: usize,
|
||||||
tc_sender: TcSender,
|
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
|
||||||
) -> Result<Self, io::Error> {
|
) -> Result<Self, Error> {
|
||||||
let server = Self {
|
let server = Self {
|
||||||
id,
|
|
||||||
socket: UdpSocket::bind(addr)?,
|
socket: UdpSocket::bind(addr)?,
|
||||||
recv_buf: vec![0; max_recv_size],
|
recv_buf: vec![0; max_recv_size],
|
||||||
sender_addr: None,
|
sender_addr: None,
|
||||||
tc_sender,
|
tc_receiver,
|
||||||
};
|
};
|
||||||
server.socket.set_nonblocking(true)?;
|
server.socket.set_nonblocking(true)?;
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<SendError>> {
|
pub fn try_recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<E>> {
|
||||||
let res = match self.socket.recv_from(&mut self.recv_buf) {
|
let res = match self.socket.recv_from(&mut self.recv_buf) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -110,9 +128,9 @@ impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
|||||||
};
|
};
|
||||||
let (num_bytes, from) = res;
|
let (num_bytes, from) = res;
|
||||||
self.sender_addr = Some(from);
|
self.sender_addr = Some(from);
|
||||||
self.tc_sender
|
self.tc_receiver
|
||||||
.send_packet(self.id, &self.recv_buf[0..num_bytes])
|
.pass_tc(&self.recv_buf[0..num_bytes])
|
||||||
.map_err(ReceiveResult::Send)?;
|
.map_err(|e| ReceiveResult::ReceiverError(e))?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,35 +142,29 @@ impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||||
use crate::queue::GenericSendError;
|
use crate::tmtc::ReceivesTcCore;
|
||||||
use crate::tmtc::PacketSenderRaw;
|
|
||||||
use crate::ComponentId;
|
|
||||||
use core::cell::RefCell;
|
|
||||||
use spacepackets::ecss::tc::PusTcCreator;
|
use spacepackets::ecss::tc::PusTcCreator;
|
||||||
use spacepackets::ecss::WritablePusPacket;
|
use spacepackets::ecss::SerializablePusPacket;
|
||||||
use spacepackets::SpHeader;
|
use spacepackets::SpHeader;
|
||||||
|
use std::boxed::Box;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
fn is_send<T: Send>(_: &T) {}
|
fn is_send<T: Send>(_: &T) {}
|
||||||
|
|
||||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct PingReceiver {
|
struct PingReceiver {
|
||||||
pub sent_cmds: RefCell<VecDeque<Vec<u8>>>,
|
pub sent_cmds: VecDeque<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketSenderRaw for PingReceiver {
|
impl ReceivesTcCore for PingReceiver {
|
||||||
type Error = GenericSendError;
|
type Error = ();
|
||||||
|
|
||||||
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
assert_eq!(sender_id, UDP_SERVER_ID);
|
|
||||||
let mut sent_data = Vec::new();
|
let mut sent_data = Vec::new();
|
||||||
sent_data.extend_from_slice(tc_raw);
|
sent_data.extend_from_slice(tc_raw);
|
||||||
let mut queue = self.sent_cmds.borrow_mut();
|
self.sent_cmds.push_back(sent_data);
|
||||||
queue.push_back(sent_data);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,11 +175,11 @@ mod tests {
|
|||||||
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
||||||
let ping_receiver = PingReceiver::default();
|
let ping_receiver = PingReceiver::default();
|
||||||
is_send(&ping_receiver);
|
is_send(&ping_receiver);
|
||||||
let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, ping_receiver)
|
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||||
.expect("Creating UDP TMTC server failed");
|
.expect("Creating UDP TMTC server failed");
|
||||||
is_send(&udp_tc_server);
|
is_send(&udp_tc_server);
|
||||||
let sph = SpHeader::new_from_apid(0x02);
|
let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
|
||||||
let pus_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
|
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
||||||
let len = pus_tc
|
let len = pus_tc
|
||||||
.write_to_bytes(&mut buf)
|
.write_to_bytes(&mut buf)
|
||||||
.expect("Error writing PUS TC packet");
|
.expect("Error writing PUS TC packet");
|
||||||
@ -183,10 +195,9 @@ mod tests {
|
|||||||
udp_tc_server.last_sender().expect("No sender set"),
|
udp_tc_server.last_sender().expect("No sender set"),
|
||||||
local_addr
|
local_addr
|
||||||
);
|
);
|
||||||
let ping_receiver = &mut udp_tc_server.tc_sender;
|
let ping_receiver: &mut PingReceiver = udp_tc_server.tc_receiver.downcast_mut().unwrap();
|
||||||
let mut queue = ping_receiver.sent_cmds.borrow_mut();
|
assert_eq!(ping_receiver.sent_cmds.len(), 1);
|
||||||
assert_eq!(queue.len(), 1);
|
let sent_cmd = ping_receiver.sent_cmds.pop_front().unwrap();
|
||||||
let sent_cmd = queue.pop_front().unwrap();
|
|
||||||
assert_eq!(sent_cmd, buf[0..len]);
|
assert_eq!(sent_cmd, buf[0..len]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,11 +205,11 @@ mod tests {
|
|||||||
fn test_nothing_received() {
|
fn test_nothing_received() {
|
||||||
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7779);
|
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7779);
|
||||||
let ping_receiver = PingReceiver::default();
|
let ping_receiver = PingReceiver::default();
|
||||||
let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, ping_receiver)
|
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||||
.expect("Creating UDP TMTC server failed");
|
.expect("Creating UDP TMTC server failed");
|
||||||
let res = udp_tc_server.try_recv_tc();
|
let res = udp_tc_server.try_recv_tc();
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let err = res.unwrap_err();
|
let err = res.unwrap_err();
|
||||||
matches!(err, ReceiveResult::NothingReceived);
|
assert_eq!(err, ReceiveResult::NothingReceived);
|
||||||
}
|
}
|
||||||
}
|
}
|
16
satrs-core/src/hk.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
pub type CollectionIntervalFactor = u32;
|
||||||
|
pub type UniqueId = u32;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum HkRequest {
|
||||||
|
OneShot(UniqueId),
|
||||||
|
Enable(UniqueId),
|
||||||
|
Disable(UniqueId),
|
||||||
|
ModifyCollectionInterval(UniqueId, CollectionIntervalFactor),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct TargetedHkRequest {
|
||||||
|
target: u32,
|
||||||
|
hk_request: HkRequest,
|
||||||
|
}
|
49
satrs-core/src/lib.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//! # Core components of the sat-rs framework
|
||||||
|
//!
|
||||||
|
//! You can find more information about the sat-rs framework on the
|
||||||
|
//! [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
||||||
|
//!
|
||||||
|
//! ## Overview
|
||||||
|
//!
|
||||||
|
//! The core modules of this crate include
|
||||||
|
//!
|
||||||
|
//! - The [event manager][event_man] module which provides a publish and
|
||||||
|
//! and subscribe to route events.
|
||||||
|
//! - The [pus] module which provides special support for projects using
|
||||||
|
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||||
|
#![no_std]
|
||||||
|
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate alloc;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate downcast_rs;
|
||||||
|
#[cfg(any(feature = "std", test))]
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
pub mod cfdp;
|
||||||
|
pub mod encoding;
|
||||||
|
pub mod error;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||||
|
pub mod event_man;
|
||||||
|
pub mod events;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||||
|
pub mod executable;
|
||||||
|
pub mod hal;
|
||||||
|
pub mod hk;
|
||||||
|
pub mod mode;
|
||||||
|
pub mod objects;
|
||||||
|
pub mod params;
|
||||||
|
pub mod pool;
|
||||||
|
pub mod power;
|
||||||
|
pub mod pus;
|
||||||
|
pub mod request;
|
||||||
|
pub mod res_code;
|
||||||
|
pub mod seq_count;
|
||||||
|
pub mod tmtc;
|
||||||
|
|
||||||
|
pub use spacepackets;
|
||||||
|
|
||||||
|
// Generic channel ID type.
|
||||||
|
pub type ChannelId = u32;
|
94
satrs-core/src/mode.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use crate::tmtc::TargetId;
|
||||||
|
use core::mem::size_of;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use spacepackets::ByteConversionError;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct ModeAndSubmode {
|
||||||
|
mode: u32,
|
||||||
|
submode: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeAndSubmode {
|
||||||
|
pub const fn new_mode_only(mode: u32) -> Self {
|
||||||
|
Self { mode, submode: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new(mode: u32, submode: u16) -> Self {
|
||||||
|
Self { mode, submode }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_len() -> usize {
|
||||||
|
size_of::<u32>() + size_of::<u16>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_be_bytes(buf: &[u8]) -> Result<Self, ByteConversionError> {
|
||||||
|
if buf.len() < 6 {
|
||||||
|
return Err(ByteConversionError::FromSliceTooSmall {
|
||||||
|
expected: 6,
|
||||||
|
found: buf.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
mode: u32::from_be_bytes(buf[0..4].try_into().unwrap()),
|
||||||
|
submode: u16::from_be_bytes(buf[4..6].try_into().unwrap()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode(&self) -> u32 {
|
||||||
|
self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submode(&self) -> u16 {
|
||||||
|
self.submode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct ModeCommand {
|
||||||
|
pub address: TargetId,
|
||||||
|
pub mode_submode: ModeAndSubmode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeCommand {
|
||||||
|
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
|
||||||
|
Self {
|
||||||
|
address,
|
||||||
|
mode_submode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> TargetId {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode_submode(&self) -> ModeAndSubmode {
|
||||||
|
self.mode_submode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode(&self) -> u32 {
|
||||||
|
self.mode_submode.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submode(&self) -> u16 {
|
||||||
|
self.mode_submode.submode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum ModeRequest {
|
||||||
|
SetMode(ModeAndSubmode),
|
||||||
|
ReadMode,
|
||||||
|
AnnounceMode,
|
||||||
|
AnnounceModeRecursive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct TargetedModeRequest {
|
||||||
|
target_id: TargetId,
|
||||||
|
mode_request: ModeRequest,
|
||||||
|
}
|
307
satrs-core/src/objects.rs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
//! # Module providing addressable object support and a manager for them
|
||||||
|
//!
|
||||||
|
//! Each addressable object can be identified using an [object ID][ObjectId].
|
||||||
|
//! The [system object][ManagedSystemObject] trait also allows storing these objects into the
|
||||||
|
//! [object manager][ObjectManager]. They can then be retrieved and casted back to a known type
|
||||||
|
//! using the object ID.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use std::any::Any;
|
||||||
|
//! use std::error::Error;
|
||||||
|
//! use satrs_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||||
|
//!
|
||||||
|
//! struct ExampleSysObj {
|
||||||
|
//! id: ObjectId,
|
||||||
|
//! dummy: u32,
|
||||||
|
//! was_initialized: bool,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl ExampleSysObj {
|
||||||
|
//! fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
||||||
|
//! ExampleSysObj {
|
||||||
|
//! id,
|
||||||
|
//! dummy,
|
||||||
|
//! was_initialized: false,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl SystemObject for ExampleSysObj {
|
||||||
|
//! type Error = ();
|
||||||
|
//! fn get_object_id(&self) -> &ObjectId {
|
||||||
|
//! &self.id
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||||
|
//! self.was_initialized = true;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl ManagedSystemObject for ExampleSysObj {}
|
||||||
|
//!
|
||||||
|
//! let mut obj_manager = ObjectManager::default();
|
||||||
|
//! let obj_id = ObjectId { id: 0, name: "Example 0"};
|
||||||
|
//! let example_obj = ExampleSysObj::new(obj_id, 42);
|
||||||
|
//! obj_manager.insert(Box::new(example_obj));
|
||||||
|
//! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&obj_id);
|
||||||
|
//! let example_obj = obj_back_casted.unwrap();
|
||||||
|
//! assert_eq!(example_obj.id, obj_id);
|
||||||
|
//! assert_eq!(example_obj.dummy, 42);
|
||||||
|
//! ```
|
||||||
|
use crate::tmtc::TargetId;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub use alloc_mod::*;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use downcast_rs::Downcast;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||||
|
pub struct ObjectId {
|
||||||
|
pub id: TargetId,
|
||||||
|
pub name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod alloc_mod {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Each object which is stored inside the [object manager][ObjectManager] needs to implemented
|
||||||
|
/// this trait
|
||||||
|
pub trait SystemObject: Downcast {
|
||||||
|
type Error;
|
||||||
|
fn get_object_id(&self) -> &ObjectId;
|
||||||
|
fn initialize(&mut self) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
downcast_rs::impl_downcast!(SystemObject assoc Error);
|
||||||
|
|
||||||
|
pub trait ManagedSystemObject: SystemObject + Send {}
|
||||||
|
downcast_rs::impl_downcast!(ManagedSystemObject assoc Error);
|
||||||
|
|
||||||
|
/// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them
|
||||||
|
/// using an [object ID][ObjectId]
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub struct ObjectManager<E> {
|
||||||
|
obj_map: HashMap<ObjectId, Box<dyn ManagedSystemObject<Error = E>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl<E: 'static> Default for ObjectManager<E> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl<E: 'static> ObjectManager<E> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ObjectManager {
|
||||||
|
obj_map: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn insert(&mut self, sys_obj: Box<dyn ManagedSystemObject<Error = E>>) -> bool {
|
||||||
|
let obj_id = sys_obj.get_object_id();
|
||||||
|
if self.obj_map.contains_key(obj_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.obj_map.insert(*obj_id, sys_obj).is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes all System Objects in the hash map and returns the number of successful
|
||||||
|
/// initializations
|
||||||
|
pub fn initialize(&mut self) -> Result<u32, Box<dyn Error>> {
|
||||||
|
let mut init_success = 0;
|
||||||
|
for val in self.obj_map.values_mut() {
|
||||||
|
if val.initialize().is_ok() {
|
||||||
|
init_success += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(init_success)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a reference to an object stored inside the manager. The type to retrieve needs to
|
||||||
|
/// be explicitly passed as a generic parameter or specified on the left hand side of the
|
||||||
|
/// expression.
|
||||||
|
pub fn get_ref<T: ManagedSystemObject<Error = E>>(&self, key: &ObjectId) -> Option<&T> {
|
||||||
|
self.obj_map.get(key).and_then(|o| o.downcast_ref::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a mutable reference to an object stored inside the manager. The type to retrieve
|
||||||
|
/// needs to be explicitly passed as a generic parameter or specified on the left hand side
|
||||||
|
/// of the expression.
|
||||||
|
pub fn get_mut<T: ManagedSystemObject<Error = E>>(
|
||||||
|
&mut self,
|
||||||
|
key: &ObjectId,
|
||||||
|
) -> Option<&mut T> {
|
||||||
|
self.obj_map
|
||||||
|
.get_mut(key)
|
||||||
|
.and_then(|o| o.downcast_mut::<T>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::string::String;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
struct ExampleSysObj {
|
||||||
|
id: ObjectId,
|
||||||
|
dummy: u32,
|
||||||
|
was_initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExampleSysObj {
|
||||||
|
fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
||||||
|
ExampleSysObj {
|
||||||
|
id,
|
||||||
|
dummy,
|
||||||
|
was_initialized: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemObject for ExampleSysObj {
|
||||||
|
type Error = ();
|
||||||
|
fn get_object_id(&self) -> &ObjectId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.was_initialized = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagedSystemObject for ExampleSysObj {}
|
||||||
|
|
||||||
|
struct OtherExampleObject {
|
||||||
|
id: ObjectId,
|
||||||
|
string: String,
|
||||||
|
was_initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemObject for OtherExampleObject {
|
||||||
|
type Error = ();
|
||||||
|
fn get_object_id(&self) -> &ObjectId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.was_initialized = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagedSystemObject for OtherExampleObject {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_obj_manager_simple() {
|
||||||
|
let mut obj_manager = ObjectManager::default();
|
||||||
|
let expl_obj_id = ObjectId {
|
||||||
|
id: 0,
|
||||||
|
name: "Example 0",
|
||||||
|
};
|
||||||
|
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
||||||
|
assert!(obj_manager.insert(Box::new(example_obj)));
|
||||||
|
let res = obj_manager.initialize();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
assert_eq!(res.unwrap(), 1);
|
||||||
|
let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&expl_obj_id);
|
||||||
|
assert!(obj_back_casted.is_some());
|
||||||
|
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||||
|
assert_eq!(expl_obj_back_casted.dummy, 42);
|
||||||
|
assert!(expl_obj_back_casted.was_initialized);
|
||||||
|
|
||||||
|
let second_obj_id = ObjectId {
|
||||||
|
id: 12,
|
||||||
|
name: "Example 1",
|
||||||
|
};
|
||||||
|
let second_example_obj = OtherExampleObject {
|
||||||
|
id: second_obj_id,
|
||||||
|
string: String::from("Hello Test"),
|
||||||
|
was_initialized: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(obj_manager.insert(Box::new(second_example_obj)));
|
||||||
|
let res = obj_manager.initialize();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
assert_eq!(res.unwrap(), 2);
|
||||||
|
let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get_ref(&second_obj_id);
|
||||||
|
assert!(obj_back_casted.is_some());
|
||||||
|
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||||
|
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
||||||
|
assert!(expl_obj_back_casted.was_initialized);
|
||||||
|
|
||||||
|
let existing_obj_id = ObjectId {
|
||||||
|
id: 12,
|
||||||
|
name: "Example 1",
|
||||||
|
};
|
||||||
|
let invalid_obj = OtherExampleObject {
|
||||||
|
id: existing_obj_id,
|
||||||
|
string: String::from("Hello Test"),
|
||||||
|
was_initialized: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!obj_manager.insert(Box::new(invalid_obj)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_man_threaded() {
|
||||||
|
let obj_manager = Arc::new(Mutex::new(ObjectManager::new()));
|
||||||
|
let expl_obj_id = ObjectId {
|
||||||
|
id: 0,
|
||||||
|
name: "Example 0",
|
||||||
|
};
|
||||||
|
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
||||||
|
let second_obj_id = ObjectId {
|
||||||
|
id: 12,
|
||||||
|
name: "Example 1",
|
||||||
|
};
|
||||||
|
let second_example_obj = OtherExampleObject {
|
||||||
|
id: second_obj_id,
|
||||||
|
string: String::from("Hello Test"),
|
||||||
|
was_initialized: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut obj_man_handle = obj_manager.lock().expect("Mutex lock failed");
|
||||||
|
assert!(obj_man_handle.insert(Box::new(example_obj)));
|
||||||
|
assert!(obj_man_handle.insert(Box::new(second_example_obj)));
|
||||||
|
let res = obj_man_handle.initialize();
|
||||||
|
std::mem::drop(obj_man_handle);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
assert_eq!(res.unwrap(), 2);
|
||||||
|
let obj_man_0 = obj_manager.clone();
|
||||||
|
let jh0 = thread::spawn(move || {
|
||||||
|
let locked_man = obj_man_0.lock().expect("Mutex lock failed");
|
||||||
|
let obj_back_casted: Option<&ExampleSysObj> = locked_man.get_ref(&expl_obj_id);
|
||||||
|
assert!(obj_back_casted.is_some());
|
||||||
|
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||||
|
assert_eq!(expl_obj_back_casted.dummy, 42);
|
||||||
|
assert!(expl_obj_back_casted.was_initialized);
|
||||||
|
std::mem::drop(locked_man)
|
||||||
|
});
|
||||||
|
|
||||||
|
let jh1 = thread::spawn(move || {
|
||||||
|
let locked_man = obj_manager.lock().expect("Mutex lock failed");
|
||||||
|
let obj_back_casted: Option<&OtherExampleObject> = locked_man.get_ref(&second_obj_id);
|
||||||
|
assert!(obj_back_casted.is_some());
|
||||||
|
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||||
|
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
||||||
|
assert!(expl_obj_back_casted.was_initialized);
|
||||||
|
std::mem::drop(locked_man)
|
||||||
|
});
|
||||||
|
jh0.join().expect("Joining thread 0 failed");
|
||||||
|
jh1.join().expect("Joining thread 1 failed");
|
||||||
|
}
|
||||||
|
}
|
679
satrs-core/src/params.rs
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
//! Parameter types and enums.
|
||||||
|
//!
|
||||||
|
//! This module contains various helper types.
|
||||||
|
//!
|
||||||
|
//! # Primtive Parameter Wrappers and Enumeration
|
||||||
|
//!
|
||||||
|
//! This module includes wrapper for primitive rust types using the newtype pattern.
|
||||||
|
//! This was also done for pairs and triplets of these primitive types.
|
||||||
|
//! The [WritableToBeBytes] was implemented for all those types as well, which allows to easily
|
||||||
|
//! convert them into a network friendly raw byte format. The [ParamsRaw] enumeration groups
|
||||||
|
//! all newtypes and implements the [WritableToBeBytes] trait itself.
|
||||||
|
//!
|
||||||
|
//! ## Example for primitive type wrapper
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use satrs_core::params::{ParamsRaw, ToBeBytes, U32Pair, WritableToBeBytes};
|
||||||
|
//!
|
||||||
|
//! let u32_pair = U32Pair(0x1010, 25);
|
||||||
|
//! assert_eq!(u32_pair.0, 0x1010);
|
||||||
|
//! assert_eq!(u32_pair.1, 25);
|
||||||
|
//! // Convert to raw stream
|
||||||
|
//! let raw_buf = u32_pair.to_be_bytes();
|
||||||
|
//! assert_eq!(raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
||||||
|
//!
|
||||||
|
//! // Convert to enum variant
|
||||||
|
//! let params_raw: ParamsRaw = u32_pair.into();
|
||||||
|
//! assert_eq!(params_raw, (0x1010_u32, 25_u32).into());
|
||||||
|
//!
|
||||||
|
//! // Convert to stream using the enum variant
|
||||||
|
//! let mut other_raw_buf: [u8; 8] = [0; 8];
|
||||||
|
//! params_raw.write_to_be_bytes(&mut other_raw_buf).expect("Writing parameter to buffer failed");
|
||||||
|
//! assert_eq!(other_raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
||||||
|
//!
|
||||||
|
//! // Create a pair from a raw stream
|
||||||
|
//! let u32_pair_from_stream: U32Pair = raw_buf.as_slice().try_into().unwrap();
|
||||||
|
//! assert_eq!(u32_pair_from_stream.0, 0x1010);
|
||||||
|
//! assert_eq!(u32_pair_from_stream.1, 25);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Generic Parameter Enumeration
|
||||||
|
//!
|
||||||
|
//! The module also contains generic parameter enumerations.
|
||||||
|
//! This includes the [ParamsHeapless] enumeration for contained values which do not require heap
|
||||||
|
//! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which
|
||||||
|
//! require [alloc] support but allow for more flexbility.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use crate::pool::StoreAddr;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::string::{String, ToString};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use core::mem::size_of;
|
||||||
|
use paste::paste;
|
||||||
|
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8};
|
||||||
|
use spacepackets::util::UnsignedEnum;
|
||||||
|
use spacepackets::ByteConversionError;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub use alloc_mod::*;
|
||||||
|
pub use spacepackets::util::ToBeBytes;
|
||||||
|
|
||||||
|
/// Generic trait which is used for objects which can be converted into a raw network (big) endian
|
||||||
|
/// byte format.
|
||||||
|
pub trait WritableToBeBytes {
|
||||||
|
fn raw_len(&self) -> usize;
|
||||||
|
/// Writes the object to a raw buffer in network endianness (big)
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! param_to_be_bytes_impl {
|
||||||
|
($Newtype: ident) => {
|
||||||
|
impl WritableToBeBytes for $Newtype {
|
||||||
|
#[inline]
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
size_of::<<Self as ToBeBytes>::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
let raw_len = self.raw_len();
|
||||||
|
if buf.len() < raw_len {
|
||||||
|
return Err(ByteConversionError::ToSliceTooSmall {
|
||||||
|
found: buf.len(),
|
||||||
|
expected: raw_len,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buf[0..raw_len].copy_from_slice(&self.to_be_bytes());
|
||||||
|
Ok(raw_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitive_newtypes_with_eq {
|
||||||
|
($($ty: ty,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct [<$ty:upper>](pub $ty);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
||||||
|
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper>]);
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
||||||
|
|
||||||
|
impl From<$ty> for [<$ty:upper>] {
|
||||||
|
fn from(v: $ty) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
||||||
|
fn from(v: ($ty, $ty)) -> Self {
|
||||||
|
Self(v.0, v.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
||||||
|
fn from(v: ($ty, $ty, $ty)) -> Self {
|
||||||
|
Self(v.0, v.1, v.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitive_newtypes {
|
||||||
|
($($ty: ty,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct [<$ty:upper>](pub $ty);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
||||||
|
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper>]);
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
||||||
|
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
||||||
|
|
||||||
|
impl From<$ty> for [<$ty:upper>] {
|
||||||
|
fn from(v: $ty) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
||||||
|
fn from(v: ($ty, $ty)) -> Self {
|
||||||
|
Self(v.0, v.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
||||||
|
fn from(v: ($ty, $ty, $ty)) -> Self {
|
||||||
|
Self(v.0, v.1, v.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive_newtypes_with_eq!(u8, u16, u32, u64, i8, i16, i32, i64,);
|
||||||
|
primitive_newtypes!(f32, f64,);
|
||||||
|
|
||||||
|
macro_rules! scalar_byte_conversions_impl {
|
||||||
|
($($ty: ty,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
impl ToBeBytes for [<$ty:upper>] {
|
||||||
|
type ByteArray = [u8; size_of::<$ty>()];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
self.0.to_be_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for [<$ty:upper>] {
|
||||||
|
type Error = ByteConversionError;
|
||||||
|
|
||||||
|
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
if v.len() < size_of::<$ty>() {
|
||||||
|
return Err(ByteConversionError::FromSliceTooSmall{
|
||||||
|
expected: size_of::<$ty>(),
|
||||||
|
found: v.len()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok([<$ty:upper>]($ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pair_byte_conversions_impl {
|
||||||
|
($($ty: ty,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
impl ToBeBytes for [<$ty:upper Pair>] {
|
||||||
|
type ByteArray = [u8; size_of::<$ty>() * 2];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; size_of::<$ty>() * 2];
|
||||||
|
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
||||||
|
array[
|
||||||
|
size_of::<$ty>()..2 * size_of::<$ty>()
|
||||||
|
].copy_from_slice(&self.1.to_be_bytes());
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for [<$ty:upper Pair>] {
|
||||||
|
type Error = ByteConversionError;
|
||||||
|
|
||||||
|
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
if v.len() < 2 * size_of::<$ty>() {
|
||||||
|
return Err(ByteConversionError::FromSliceTooSmall{
|
||||||
|
expected: 2 * size_of::<$ty>(),
|
||||||
|
found: v.len()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok([<$ty:upper Pair>](
|
||||||
|
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
||||||
|
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! triplet_to_be_bytes_impl {
|
||||||
|
($($ty: ty,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
impl ToBeBytes for [<$ty:upper Triplet>] {
|
||||||
|
type ByteArray = [u8; size_of::<$ty>() * 3];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; size_of::<$ty>() * 3];
|
||||||
|
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
||||||
|
array[
|
||||||
|
size_of::<$ty>()..2 * size_of::<$ty>()
|
||||||
|
].copy_from_slice(&self.1.to_be_bytes());
|
||||||
|
array[
|
||||||
|
2 * size_of::<$ty>()..3 * size_of::<$ty>()
|
||||||
|
].copy_from_slice(&self.2.to_be_bytes());
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<&[u8]> for [<$ty:upper Triplet>] {
|
||||||
|
type Error = ByteConversionError;
|
||||||
|
|
||||||
|
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
if v.len() < 3 * size_of::<$ty>() {
|
||||||
|
return Err(ByteConversionError::FromSliceTooSmall{
|
||||||
|
expected: 3 * size_of::<$ty>(),
|
||||||
|
found: v.len()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok([<$ty:upper Triplet>](
|
||||||
|
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
||||||
|
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap()),
|
||||||
|
$ty::from_be_bytes(v[2 * size_of::<$ty>()..3 * size_of::<$ty>()].try_into().unwrap())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar_byte_conversions_impl!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64,);
|
||||||
|
|
||||||
|
impl ToBeBytes for U8Pair {
|
||||||
|
type ByteArray = [u8; 2];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; 2];
|
||||||
|
array[0] = self.0;
|
||||||
|
array[1] = self.1;
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToBeBytes for I8Pair {
|
||||||
|
type ByteArray = [u8; 2];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; 2];
|
||||||
|
array[0] = self.0 as u8;
|
||||||
|
array[1] = self.1 as u8;
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToBeBytes for U8Triplet {
|
||||||
|
type ByteArray = [u8; 3];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; 3];
|
||||||
|
array[0] = self.0;
|
||||||
|
array[1] = self.1;
|
||||||
|
array[2] = self.2;
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToBeBytes for I8Triplet {
|
||||||
|
type ByteArray = [u8; 3];
|
||||||
|
|
||||||
|
fn written_len(&self) -> usize {
|
||||||
|
size_of::<Self::ByteArray>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||||
|
let mut array = [0; 3];
|
||||||
|
array[0] = self.0 as u8;
|
||||||
|
array[1] = self.1 as u8;
|
||||||
|
array[2] = self.2 as u8;
|
||||||
|
array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pair_byte_conversions_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
||||||
|
triplet_to_be_bytes_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
||||||
|
|
||||||
|
/// Generic enumeration for additonal parameters only consisting of primitive data types.
|
||||||
|
///
|
||||||
|
/// All contained variants and the enum itself implement the [WritableToBeBytes] trait, which
|
||||||
|
/// allows to easily convert them into a network-friendly format.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum ParamsRaw {
|
||||||
|
U8(U8),
|
||||||
|
U8Pair(U8Pair),
|
||||||
|
U8Triplet(U8Triplet),
|
||||||
|
I8(I8),
|
||||||
|
I8Pair(I8Pair),
|
||||||
|
I8Triplet(I8Triplet),
|
||||||
|
U16(U16),
|
||||||
|
U16Pair(U16Pair),
|
||||||
|
U16Triplet(U16Triplet),
|
||||||
|
I16(I16),
|
||||||
|
I16Pair(I16Pair),
|
||||||
|
I16Triplet(I16Triplet),
|
||||||
|
U32(U32),
|
||||||
|
U32Pair(U32Pair),
|
||||||
|
U32Triplet(U32Triplet),
|
||||||
|
I32(I32),
|
||||||
|
I32Pair(I32Pair),
|
||||||
|
I32Triplet(I32Triplet),
|
||||||
|
F32(F32),
|
||||||
|
F32Pair(F32Pair),
|
||||||
|
F32Triplet(F32Triplet),
|
||||||
|
U64(U64),
|
||||||
|
I64(I64),
|
||||||
|
F64(F64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WritableToBeBytes for ParamsRaw {
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
ParamsRaw::U8(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U8Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U8Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I8(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I8Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I8Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U16(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U16Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U16Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I16(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I16Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I16Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U32(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U32Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U32Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I32(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I32Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I32Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::F32(v) => v.raw_len(),
|
||||||
|
ParamsRaw::F32Pair(v) => v.raw_len(),
|
||||||
|
ParamsRaw::F32Triplet(v) => v.raw_len(),
|
||||||
|
ParamsRaw::U64(v) => v.raw_len(),
|
||||||
|
ParamsRaw::I64(v) => v.raw_len(),
|
||||||
|
ParamsRaw::F64(v) => v.raw_len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
match self {
|
||||||
|
ParamsRaw::U8(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U8Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U8Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I8(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I8Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I8Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U16(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U16Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U16Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I16(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I16Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I16Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U32(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U32Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U32Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I32(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I32Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I32Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::F32(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::F32Pair(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::F32Triplet(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::U64(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::I64(v) => v.write_to_be_bytes(buf),
|
||||||
|
ParamsRaw::F64(v) => v.write_to_be_bytes(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! params_raw_from_newtype {
|
||||||
|
($($newtype: ident,)+) => {
|
||||||
|
$(
|
||||||
|
impl From<$newtype> for ParamsRaw {
|
||||||
|
fn from(v: $newtype) -> Self {
|
||||||
|
Self::$newtype(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params_raw_from_newtype!(
|
||||||
|
U8, U8Pair, U8Triplet, U16, U16Pair, U16Triplet, U32, U32Pair, U32Triplet, I8, I8Pair,
|
||||||
|
I8Triplet, I16, I16Pair, I16Triplet, I32, I32Pair, I32Triplet, F32, F32Pair, F32Triplet, U64,
|
||||||
|
I64, F64,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum EcssEnumParams {
|
||||||
|
U8(EcssEnumU8),
|
||||||
|
U16(EcssEnumU16),
|
||||||
|
U32(EcssEnumU32),
|
||||||
|
U64(EcssEnumU64),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! writable_as_be_bytes_ecss_enum_impl {
|
||||||
|
($EnumIdent: ident) => {
|
||||||
|
impl WritableToBeBytes for $EnumIdent {
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
self.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
<Self as UnsignedEnum>::write_to_be_bytes(self, buf).map(|_| self.raw_len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU8);
|
||||||
|
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU16);
|
||||||
|
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU32);
|
||||||
|
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU64);
|
||||||
|
|
||||||
|
impl WritableToBeBytes for EcssEnumParams {
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
EcssEnumParams::U8(e) => e.raw_len(),
|
||||||
|
EcssEnumParams::U16(e) => e.raw_len(),
|
||||||
|
EcssEnumParams::U32(e) => e.raw_len(),
|
||||||
|
EcssEnumParams::U64(e) => e.raw_len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
match self {
|
||||||
|
EcssEnumParams::U8(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||||
|
EcssEnumParams::U16(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||||
|
EcssEnumParams::U32(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||||
|
EcssEnumParams::U64(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic enumeration for parameters which do not rely on heap allocations.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum ParamsHeapless {
|
||||||
|
Raw(ParamsRaw),
|
||||||
|
EcssEnum(EcssEnumParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_conversions_for_raw {
|
||||||
|
($(($raw_ty: ty, $TargetPath: path),)+) => {
|
||||||
|
$(
|
||||||
|
impl From<$raw_ty> for ParamsRaw {
|
||||||
|
fn from(val: $raw_ty) -> Self {
|
||||||
|
$TargetPath(val.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$raw_ty> for ParamsHeapless {
|
||||||
|
fn from(val: $raw_ty) -> Self {
|
||||||
|
ParamsHeapless::Raw(val.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
from_conversions_for_raw!(
|
||||||
|
(u8, Self::U8),
|
||||||
|
((u8, u8), Self::U8Pair),
|
||||||
|
((u8, u8, u8), Self::U8Triplet),
|
||||||
|
(i8, Self::I8),
|
||||||
|
((i8, i8), Self::I8Pair),
|
||||||
|
((i8, i8, i8), Self::I8Triplet),
|
||||||
|
(u16, Self::U16),
|
||||||
|
((u16, u16), Self::U16Pair),
|
||||||
|
((u16, u16, u16), Self::U16Triplet),
|
||||||
|
(i16, Self::I16),
|
||||||
|
((i16, i16), Self::I16Pair),
|
||||||
|
((i16, i16, i16), Self::I16Triplet),
|
||||||
|
(u32, Self::U32),
|
||||||
|
((u32, u32), Self::U32Pair),
|
||||||
|
((u32, u32, u32), Self::U32Triplet),
|
||||||
|
(i32, Self::I32),
|
||||||
|
((i32, i32), Self::I32Pair),
|
||||||
|
((i32, i32, i32), Self::I32Triplet),
|
||||||
|
(f32, Self::F32),
|
||||||
|
((f32, f32), Self::F32Pair),
|
||||||
|
((f32, f32, f32), Self::F32Triplet),
|
||||||
|
(u64, Self::U64),
|
||||||
|
(f64, Self::F64),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
mod alloc_mod {
|
||||||
|
use super::*;
|
||||||
|
/// Generic enumeration for additional parameters, including parameters which rely on heap
|
||||||
|
/// allocations.
|
||||||
|
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Params {
|
||||||
|
Heapless(ParamsHeapless),
|
||||||
|
Store(StoreAddr),
|
||||||
|
Vec(Vec<u8>),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StoreAddr> for Params {
|
||||||
|
fn from(x: StoreAddr) -> Self {
|
||||||
|
Self::Store(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParamsHeapless> for Params {
|
||||||
|
fn from(x: ParamsHeapless) -> Self {
|
||||||
|
Self::Heapless(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Params {
|
||||||
|
fn from(val: Vec<u8>) -> Self {
|
||||||
|
Self::Vec(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a byte slice into the [Params::Vec] variant
|
||||||
|
impl From<&[u8]> for Params {
|
||||||
|
fn from(val: &[u8]) -> Self {
|
||||||
|
Self::Vec(val.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Params {
|
||||||
|
fn from(val: String) -> Self {
|
||||||
|
Self::String(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a string slice into the [Params::String] variant
|
||||||
|
impl From<&str> for Params {
|
||||||
|
fn from(val: &str) -> Self {
|
||||||
|
Self::String(val.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_u32_pair() {
|
||||||
|
let u32_pair = U32Pair(4, 8);
|
||||||
|
assert_eq!(u32_pair.0, 4);
|
||||||
|
assert_eq!(u32_pair.1, 8);
|
||||||
|
let raw = u32_pair.to_be_bytes();
|
||||||
|
let mut u32_conv_back = u32::from_be_bytes(raw[0..4].try_into().unwrap());
|
||||||
|
assert_eq!(u32_conv_back, 4);
|
||||||
|
u32_conv_back = u32::from_be_bytes(raw[4..8].try_into().unwrap());
|
||||||
|
assert_eq!(u32_conv_back, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_signed_test_pair() {
|
||||||
|
let i8_pair = I8Pair(-3, -16);
|
||||||
|
assert_eq!(i8_pair.0, -3);
|
||||||
|
assert_eq!(i8_pair.1, -16);
|
||||||
|
let raw = i8_pair.to_be_bytes();
|
||||||
|
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
||||||
|
assert_eq!(i8_conv_back, -3);
|
||||||
|
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
||||||
|
assert_eq!(i8_conv_back, -16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_signed_test_triplet() {
|
||||||
|
let i8_triplet = I8Triplet(-3, -16, -126);
|
||||||
|
assert_eq!(i8_triplet.0, -3);
|
||||||
|
assert_eq!(i8_triplet.1, -16);
|
||||||
|
assert_eq!(i8_triplet.2, -126);
|
||||||
|
let raw = i8_triplet.to_be_bytes();
|
||||||
|
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
||||||
|
assert_eq!(i8_conv_back, -3);
|
||||||
|
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
||||||
|
assert_eq!(i8_conv_back, -16);
|
||||||
|
i8_conv_back = i8::from_be_bytes(raw[2..3].try_into().unwrap());
|
||||||
|
assert_eq!(i8_conv_back, -126);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conversion_test_string() {
|
||||||
|
let param: Params = "Test String".into();
|
||||||
|
if let Params::String(str) = param {
|
||||||
|
assert_eq!(str, String::from("Test String"));
|
||||||
|
} else {
|
||||||
|
panic!("Params type is not String")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conversion_from_slice() {
|
||||||
|
let test_slice: [u8; 5] = [0; 5];
|
||||||
|
let vec_param: Params = test_slice.as_slice().into();
|
||||||
|
if let Params::Vec(vec) = vec_param {
|
||||||
|
assert_eq!(vec, test_slice.to_vec());
|
||||||
|
} else {
|
||||||
|
panic!("Params type is not a vector")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|