Skip to content

Commit

Permalink
EVALG-75: Add Application Design tutorial (#693)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alxe authored Jul 11, 2024
1 parent 0e3e9b1 commit 8585de1
Show file tree
Hide file tree
Showing 7 changed files with 534 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/lint_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3.8
- name: Install necessary tools
run: pip install black==22.3.0
- name: Perform format check in a pull request
Expand Down
5 changes: 5 additions & 0 deletions tutorials/application_design/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Tutorial: Application Design

This code is part of the Connext Application Design tutorial.

There are multiple subfolders which contain a toolchain-specific tutorial.
103 changes: 103 additions & 0 deletions tutorials/application_design/VehicleModeling.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://community.rti.com/schema/7.3.0/rti_dds_profiles.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<dds>
<types>
<const name="VIN_LENGTH" type="uint8" value="17"/>
<typedef name="VIN" type="string" stringMaxLength="VIN_LENGTH"/>

<struct name="Coord" extensibility="final" nested="true">
<member name="lat" type="float64"/>
<member name="lon" type="float64"/>
</struct>
<struct name="VehicleTransit" extensibility="appendable">
<member name="vehicle_vin"
type="nonBasic"
nonBasicTypeName="VIN"
key="true" />
<member name="current_position"
type="nonBasic"
nonBasicTypeName="Coord" />
<member name="current_route"
type="nonBasic"
nonBasicTypeName="Coord"
sequenceMaxLength="-1"
optional="true" /> <!-- 'no route' == standby -->
</struct>

<typedef name="Percentage" type="float64" min="0.0" max="100.0"/>
<struct name="VehicleMetrics" extensibility="appendable">
<member name="vehicle_vin"
type="nonBasic"
nonBasicTypeName="VIN"
key="true"/>
<member name="fuel_level"
type="nonBasic"
nonBasicTypeName="Percentage"/>
</struct>
</types>

<qos_library name="VehicleModeling_Library">
<qos_profile name="VehicleMetrics_Profile">
<datawriter_qos base_name="BuiltinQosLib::Generic.BestEffort">
<deadline>
<period>
<sec>10</sec>
<nanosec>0</nanosec>
</period>
</deadline>
</datawriter_qos>
<datareader_qos base_name="BuiltinQosLib::Generic.BestEffort">
<deadline>
<period>
<sec>15</sec>
<nanosec>0</nanosec>
</period>
</deadline>
</datareader_qos>
</qos_profile>
<qos_profile name="VehicleTransit_Profile">
<datawriter_qos base_name="BuiltinQosLib::Generic.StrictReliable">
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
</datawriter_qos>
<datareader_qos base_name="BuiltinQosLib::Generic.KeepLastReliable">
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
</datareader_qos>
</qos_profile>
</qos_library>

<domain_library name="DomainLibrary">
<domain name="VehicleDomain" domain_id="0">
<topic name="VehicleMetricsTopic" register_type_ref="VehicleMetrics" />
<topic name="VehicleTransitTopic" register_type_ref="VehicleTransit" />
</domain>
</domain_library>

<domain_participant_library name="ParticipantLibrary">
<domain_participant name="PublisherApp" domain_ref="DomainLibrary::VehicleDomain">
<publisher name="Publisher">
<data_writer name="MetricsWriter" topic_ref="VehicleMetricsTopic">
<datawriter_qos base_name="VehicleModeling_Library::VehicleMetrics_Profile" />
</data_writer>

<data_writer name="TransitWriter" topic_ref="VehicleTransitTopic">
<datawriter_qos base_name="VehicleModeling_Library::VehicleTransit_Profile" />
</data_writer>
</publisher>
</domain_participant>

<domain_participant name="SubscriberApp" domain_ref="DomainLibrary::VehicleDomain">
<subscriber name="Subscriber">
<data_reader name="MetricsReader" topic_ref="VehicleMetricsTopic">
<datareader_qos base_name="VehicleModeling_Library::VehicleMetrics_Profile" />
</data_reader>
<data_reader name="TransitReader" topic_ref="VehicleTransitTopic">
<datareader_qos base_name="VehicleModeling_Library::VehicleTransit_Profile" />
</data_reader>
</subscriber>
</domain_participant>
</domain_participant_library>
</dds>
47 changes: 47 additions & 0 deletions tutorials/application_design/py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Tutorial: Data Persistence

This code is part of the Connext Application Design tutorial and is included
here in full for convenience.
Please see the tutorial for instructions.

This code targets Python 3.8 and uses the Connext Python API.

## How to build

The example already provides generated types (`VehicleModeling.py`), so nothing
needs to be done for Python applications.

## How to run

The publisher can be run as a file or module. The expected output contains a
description of the application. It will end once the vehicle has run out of fuel.

```console
$ python publisher.py
Running simulation: simulation=Simulation(self._metrics_writer=<rti.connextdds.DataWriter object at 0x7f7e693cdb30>, self._transit_writer=<rti.connextdds.DataWriter object at 0x7f7e697d83f0>, self._vehicle_vin='3P524A8JB256YZOQY', self._vehicle_fuel=100.0, self._vehicle_route=[Coord(lat=10.851231171123665, lon=4.549741897319626), Coord(lat=-19.336436875210726, lon=45.5761709545348), Coord(lat=-28.473080284108043, lon=-3.3714508132107524), Coord(lat=-44.45875114267031, lon=11.536506807784141), Coord(lat=-30.68510788813299, lon=3.7515235253391954), Coord(lat=-45.485688894791096, lon=-19.45044753805537)], self._vehicle_position=Coord(lat=-9.361148743978752, lon=10.532865637410172))
Vehicle '3P524A8JB256YZOQY' has reached its destination, now moving to a new location...
Vehicle '3P524A8JB256YZOQY' has reached its destination, now moving to a new location...
Vehicle '3P524A8JB256YZOQY' ran out of fuel!

```

The subscriber can be run as a file or a module. The expected output contains a
description of the application, followed by periodic updates on the understanding
of the system. It will run until interrupted.

```console
$ python subscriber.py
[[ DASHBOARD: 2024-07-11 13:28:39.503181 ]]
Online vehicles: 1
- Vehicle F01UV8KCSAY5EVBL3:
Fuel updates: 4
Last known destination: Coord(lat=-29.739379575147705, lon=8.811412382546946)
Last known fuel level: 90.24212596289948
Offline vehicles: 1
- Vehicle V93YZUZIYQB71P77Z:
Mean fuel consumption: 2.5009597229252654
Known reached destinations: 3
- Coord(lat=-19.42303588068077, lon=4.543146614919191)
- Coord(lat=-23.903966240016082, lon=-1.5759817176247415)
- Coord(lat=-27.18890271220428, lon=-23.879609290719095)
```
58 changes: 58 additions & 0 deletions tutorials/application_design/py/VehicleModeling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#
# (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved.
#
# RTI grants Licensee a license to use, modify, compile, and create derivative
# works of the Software solely for use with RTI products. The Software is
# provided "as is", with no warranty of any type, including any warranty for
# fitness for any purpose. RTI is under no obligation to maintain or support
# the Software. RTI shall not be liable for any incidental or consequential
# damages arising out of the use or inability to use the software.
#

import os
import sys
from dataclasses import field
from enum import IntEnum
from typing import Optional, Sequence, Union

import rti.idl as idl

VIN_LENGTH = 17

VIN = str


@idl.struct(type_annotations=[idl.final])
class Coord:
lat: float = 0.0
lon: float = 0.0


VehicleMetricsTopic = "VehicleMetrics"


@idl.struct(
member_annotations={
"vehicle_vin": [idl.key, idl.bound(VIN_LENGTH)],
"current_route": [idl.bound(100)],
}
)
class VehicleTransit:
vehicle_vin: str = ""
current_position: Coord = field(default_factory=Coord)
current_route: Optional[Sequence[Coord]] = None


Percentage = float

VehicleTransitTopic = "VehicleTransit"


@idl.struct(
member_annotations={
"vehicle_vin": [idl.key, idl.bound(VIN_LENGTH)],
}
)
class VehicleMetrics:
vehicle_vin: str = ""
fuel_level: float = 0.0
138 changes: 138 additions & 0 deletions tutorials/application_design/py/publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#
# (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved.
#
# RTI grants Licensee a license to use, modify, compile, and create derivative
# works of the Software solely for use with RTI products. The Software is
# provided "as is", with no warranty of any type, including any warranty for
# fitness for any purpose. RTI is under no obligation to maintain or support
# the Software. RTI shall not be liable for any incidental or consequential
# damages arising out of the use or inability to use the software.
#

import random
import time
import typing

import rti.connextdds as dds

from VehicleModeling import Coord, VehicleMetrics, VehicleTransit


def new_route(
n: int = 5,
start: typing.Optional[Coord] = None,
end: typing.Optional[Coord] = None,
):
def new_random_coord():
return Coord(
(0.5 - random.random()) * 100,
(0.5 - random.random()) * 100,
)

start = start or new_random_coord()
intermediate = (new_random_coord() for _ in range(n))
end = end or new_random_coord()

return [start, *intermediate, end]


class PublisherSimulation:
def __init__(
self,
metrics_writer: "dds.DataWriter",
transit_writer: "dds.DataWriter",
):
self._metrics_writer = metrics_writer
self._transit_writer = transit_writer
self._vehicle_vin: str = "".join(
random.choices("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=17)
)
self._vehicle_fuel = 100.0
self._vehicle_route = new_route()
self._vehicle_position = self._vehicle_route.pop(0)

def __repr__(self):
return (
f"Simulation("
f"{self._metrics_writer=}, "
f"{self._transit_writer=}, "
f"{self._vehicle_vin=}, "
f"{self._vehicle_fuel=}, "
f"{self._vehicle_route=}, "
f"{self._vehicle_position=})"
)

@property
def has_ended(self):
return self._is_out_of_fuel

@property
def _is_out_of_fuel(self):
return self._vehicle_fuel <= 0.0

@property
def _is_on_standby(self):
return not self._vehicle_route

def run(self):
while not self.has_ended:
self._metrics_writer.write(
VehicleMetrics(
self._vehicle_vin,
self._vehicle_fuel,
)
)

self._transit_writer.write(
VehicleTransit(
self._vehicle_vin,
current_route=self._vehicle_route,
current_position=self._vehicle_position,
)
)

time.sleep(1)

if self._is_on_standby:
print(
f"Vehicle '{self._vehicle_vin}' has reached its destination, now moving to a new location..."
)
self._vehicle_route = new_route(start=self._vehicle_position)

self._vehicle_position = self._vehicle_route.pop(0)
self._vehicle_fuel -= 10 * random.random()

if self._is_out_of_fuel:
self._vehicle_fuel = 0.0

print(f"Vehicle '{self._vehicle_vin}' ran out of fuel!")


def main():
dds.DomainParticipant.register_idl_type(VehicleMetrics, "VehicleMetrics")
dds.DomainParticipant.register_idl_type(VehicleTransit, "VehicleTransit")

qos_provider = dds.QosProvider("../VehicleModeling.xml")

with qos_provider.create_participant_from_config(
"ParticipantLibrary::PublisherApp"
) as participant:
metrics_writer = dds.DataWriter(
participant.find_datawriter("Publisher::MetricsWriter")
)
transit_writer = dds.DataWriter(
participant.find_datawriter("Publisher::TransitWriter")
)

simulation = PublisherSimulation(
metrics_writer=metrics_writer, transit_writer=transit_writer
)
print(f"Running simulation: {simulation=}")
simulation.run()


if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
Loading

0 comments on commit 8585de1

Please sign in to comment.