pntos-cobra 2.1.0.post0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pntos/__init__.py +1 -0
- pntos/cobra/__init__.py +84 -0
- pntos/cobra/advanced_plugins/Aspn23RosTransportPlugin.py +149 -0
- pntos/cobra/advanced_plugins/buscat/BuscatControllerPlugin.py +261 -0
- pntos/cobra/advanced_plugins/buscat/BuscatMediator.py +113 -0
- pntos/cobra/advanced_plugins/ui/CobraUiLogPlayerPlugin.py +279 -0
- pntos/cobra/advanced_plugins/ui/ExperimentalCobraUiPlugin.py +261 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/Cobra_Logo-Standards_Horizontal-White-NoTagline_Vers1-BRfZe-iO.png +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/Cobra_Logo-Standards_Horizontal-White-Tagline_Vers1-ClwmN2Dp.png +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Black-Cuts-7cD.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Black-os_tY5hn.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Bold-BVFJaemX.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Bold-DiAsa6G6.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-BoldItalic-DUIV9VND.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-BoldItalic-Dd9Vjcig.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-ExtraBold-1GgzYkPj.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-ExtraBold-CFEB4Nq7.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Italic-BdaAcyO1.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Italic-CvfvqgpG.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Light-BiiL_zoC.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Light-D_3u9Ke2.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-LightItalic-BfN20AIg.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-LightItalic-D8lCSEYW.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Medium-DSEqpuun.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Medium-Dkrd-Odz.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-MediumItalic-CpB315hL.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-MediumItalic-DKNIea0p.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Regular-BLbITkWW.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Regular-Gz7hSEw8.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBold-Bq0Ehvj3.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBold-rrjtAbTs.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBoldItalic-BrsCcVDJ.woff +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBoldItalic-C5HpS0vu.woff2 +0 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/ground_speed_dark-DXZwT6_z.svg +11 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/index-BfheAgQK.css +1 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/assets/index-CZzgVHg8.js +21 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/index.html +14 -0
- pntos/cobra/advanced_plugins/ui/_static/dist/purple-cobra-icon.ico +0 -0
- pntos/cobra/advanced_plugins/ui/models.py +88 -0
- pntos/cobra/advanced_plugins/ui/types.py +75 -0
- pntos/cobra/advanced_plugins/ui/utils.py +300 -0
- pntos/cobra/config/BaseConfig.py +17 -0
- pntos/cobra/config/ClockBiasStateBlockConfig.py +48 -0
- pntos/cobra/config/ConstantStateBlockConfig.py +41 -0
- pntos/cobra/config/ControllerConfig.py +41 -0
- pntos/cobra/config/FogmConfig.py +22 -0
- pntos/cobra/config/FusionEngineConfig.py +43 -0
- pntos/cobra/config/ImuConfig.py +53 -0
- pntos/cobra/config/InertialConfig.py +32 -0
- pntos/cobra/config/LcmTransportConfig.py +51 -0
- pntos/cobra/config/ManualAlignmentConfig.py +89 -0
- pntos/cobra/config/ManualHeadingAlignmentConfig.py +36 -0
- pntos/cobra/config/MountingConfig.py +23 -0
- pntos/cobra/config/OrchestrationConfig.py +790 -0
- pntos/cobra/config/PreprocessorConfig.py +167 -0
- pntos/cobra/config/StaticAlignmentConfig.py +25 -0
- pntos/cobra/config/VirtualStateBlockConfig.py +87 -0
- pntos/cobra/config/__init__.py +69 -0
- pntos/cobra/config/ui_config.py +56 -0
- pntos/cobra/config/utils.py +623 -0
- pntos/cobra/dummy_plugins/DummyControllerPlugin.py +92 -0
- pntos/cobra/dummy_plugins/DummyMediator.py +114 -0
- pntos/cobra/dummy_plugins/DummyMessageStreamConfig.py +70 -0
- pntos/cobra/dummy_plugins/DummyOrchestrationPlugin.py +72 -0
- pntos/cobra/dummy_plugins/DummyTransportPlugin.py +118 -0
- pntos/cobra/internal.py +125 -0
- pntos/cobra/py.typed +0 -0
- pntos/cobra/standard_plugins/DiagnosticLogPlugin.py +69 -0
- pntos/cobra/standard_plugins/EkfFusionStrategyPlugin.py +290 -0
- pntos/cobra/standard_plugins/LcmLogTransportPlugin.py +163 -0
- pntos/cobra/standard_plugins/LcmTransportPlugin.py +185 -0
- pntos/cobra/standard_plugins/ManualHeadingAlignInitializationPlugin.py +128 -0
- pntos/cobra/standard_plugins/StandardInertialPlugin.py +252 -0
- pntos/cobra/standard_plugins/StandardLoggingPlugin.py +85 -0
- pntos/cobra/standard_plugins/StandardOrchestrationPlugin.py +933 -0
- pntos/cobra/standard_plugins/StandardRegistryPlugin.py +619 -0
- pntos/cobra/standard_plugins/StaticAlignInitializationPlugin.py +120 -0
- pntos/cobra/standard_plugins/controller/StandardControllerPlugin.py +298 -0
- pntos/cobra/standard_plugins/controller/StandardMediator.py +177 -0
- pntos/cobra/standard_plugins/controller/StandardMessageStreamConfig.py +81 -0
- pntos/cobra/standard_plugins/fusion/StandardFusionPlugin.py +967 -0
- pntos/cobra/standard_plugins/fusion/VirtualStateBlockManager.py +372 -0
- pntos/cobra/standard_plugins/preprocessor/BarometerToAltitudePreprocessor.py +66 -0
- pntos/cobra/standard_plugins/preprocessor/DownsamplerPreprocessor.py +79 -0
- pntos/cobra/standard_plugins/preprocessor/ImuRotationPreprocessor.py +41 -0
- pntos/cobra/standard_plugins/preprocessor/OutagePreprocessor.py +70 -0
- pntos/cobra/standard_plugins/preprocessor/StandardPreprocessorPlugin.py +216 -0
- pntos/cobra/standard_plugins/preprocessor/TimeAdjusterPreprocessor.py +62 -0
- pntos/cobra/standard_plugins/preprocessor/TimeBiasPreprocessor.py +56 -0
- pntos/cobra/standard_plugins/state_modeling/AltitudeMeasurementProcessor.py +186 -0
- pntos/cobra/standard_plugins/state_modeling/ClockBiasStateBlock.py +116 -0
- pntos/cobra/standard_plugins/state_modeling/ConstantStateBlock.py +54 -0
- pntos/cobra/standard_plugins/state_modeling/Direction3DToPointsMeasurementProcessor.py +278 -0
- pntos/cobra/standard_plugins/state_modeling/FogmBlock.py +96 -0
- pntos/cobra/standard_plugins/state_modeling/Pinson15NedBlock.py +399 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonBodyVelocityMeasurementProcessor.py +279 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonPosVelMeasurementProcessor.py +239 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonPositionMeasurementProcessor.py +256 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonVelocityMeasurementProcessor.py +137 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonWithLeverArmPositionMeasurementProcessor.py +282 -0
- pntos/cobra/standard_plugins/state_modeling/PinsonWithNedFogmPositionMeasurementProcessor.py +240 -0
- pntos/cobra/standard_plugins/state_modeling/PositionMeasurementProcessor.py +147 -0
- pntos/cobra/standard_plugins/state_modeling/StandardStateModelingPlugin.py +558 -0
- pntos/cobra/standard_plugins/state_modeling/virtual_state_blocks/PinsonErrorToStandard.py +216 -0
- pntos/cobra/standard_plugins/state_modeling/virtual_state_blocks/StateExtractor.py +113 -0
- pntos/cobra/tutorial_plugins/TutorialInitializationPlugin.py +176 -0
- pntos/cobra/tutorial_plugins/TutorialLcmTransportPlugin.py +109 -0
- pntos/cobra/tutorial_plugins/TutorialPosOrchestrationPlugin.py +346 -0
- pntos/cobra/tutorial_plugins/TutorialPosVelOrchestrationPlugin.py +358 -0
- pntos/cobra/tutorial_plugins/UiLogPlottingPlugin.py +118 -0
- pntos/cobra/tutorial_plugins/state_modeling/TutorialFogmBlock.py +71 -0
- pntos/cobra/tutorial_plugins/state_modeling/TutorialPinson15NedBlock.py +329 -0
- pntos/cobra/tutorial_plugins/state_modeling/TutorialPinsonVelocityMeasurementProcessor.py +75 -0
- pntos/cobra/tutorial_plugins/state_modeling/TutorialPinsonWithNedFogmPositionMeasurementProcessor.py +114 -0
- pntos/cobra/tutorial_plugins/state_modeling/TutorialPosInsStateModelingPlugin.py +207 -0
- pntos/cobra/utils/__init__.py +98 -0
- pntos/cobra/utils/apps.py +85 -0
- pntos/cobra/utils/arrays.py +140 -0
- pntos/cobra/utils/conversions.py +432 -0
- pntos/cobra/utils/hdf5.py +128 -0
- pntos/cobra/utils/lcm_utils.py +299 -0
- pntos/cobra/utils/logger.py +84 -0
- pntos/cobra/utils/logging.py +78 -0
- pntos/cobra/utils/navigation.py +380 -0
- pntos/cobra/utils/orchestration_utils.py +520 -0
- pntos/cobra/utils/plots.py +533 -0
- pntos/cobra/utils/plugins.py +219 -0
- pntos/cobra/utils/registry.py +286 -0
- pntos/cobra/utils/ros.py +84 -0
- pntos/cobra/utils/ui.py +390 -0
- pntos/py.typed +0 -0
- pntos_cobra-2.1.0.post0.dev0.dist-info/METADATA +34 -0
- pntos_cobra-2.1.0.post0.dev0.dist-info/RECORD +136 -0
- pntos_cobra-2.1.0.post0.dev0.dist-info/WHEEL +5 -0
- pntos_cobra-2.1.0.post0.dev0.dist-info/licenses/LICENSE +177 -0
- pntos_cobra-2.1.0.post0.dev0.dist-info/top_level.txt +1 -0
pntos/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
pntos/cobra/__init__.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
|
|
3
|
+
with contextlib.suppress(ImportError):
|
|
4
|
+
from .advanced_plugins.Aspn23RosTransportPlugin import (
|
|
5
|
+
Aspn23RosTransportPlugin as Aspn23RosTransportPlugin,
|
|
6
|
+
)
|
|
7
|
+
from .advanced_plugins.buscat.BuscatControllerPlugin import (
|
|
8
|
+
BuscatControllerPlugin as BuscatControllerPlugin,
|
|
9
|
+
)
|
|
10
|
+
from .advanced_plugins.ui.CobraUiLogPlayerPlugin import (
|
|
11
|
+
CobraUiLogPlayerPlugin as CobraUiLogPlayerPlugin,
|
|
12
|
+
)
|
|
13
|
+
from .advanced_plugins.ui.ExperimentalCobraUiPlugin import (
|
|
14
|
+
ExperimentalCobraUiPlugin as ExperimentalCobraUiPlugin,
|
|
15
|
+
)
|
|
16
|
+
from .dummy_plugins.DummyControllerPlugin import (
|
|
17
|
+
DummyControllerPlugin as DummyControllerPlugin,
|
|
18
|
+
)
|
|
19
|
+
from .dummy_plugins.DummyOrchestrationPlugin import (
|
|
20
|
+
DummyOrchestrationPlugin as DummyOrchestrationPlugin,
|
|
21
|
+
)
|
|
22
|
+
from .dummy_plugins.DummyTransportPlugin import (
|
|
23
|
+
DummyTransportPlugin as DummyTransportPlugin,
|
|
24
|
+
)
|
|
25
|
+
from .standard_plugins.controller.StandardControllerPlugin import (
|
|
26
|
+
StandardControllerPlugin as StandardControllerPlugin,
|
|
27
|
+
)
|
|
28
|
+
from .standard_plugins.DiagnosticLogPlugin import (
|
|
29
|
+
DiagnosticLogPlugin as DiagnosticLogPlugin,
|
|
30
|
+
)
|
|
31
|
+
from .standard_plugins.EkfFusionStrategyPlugin import (
|
|
32
|
+
EkfFusionStrategyPlugin as EkfFusionStrategyPlugin,
|
|
33
|
+
)
|
|
34
|
+
from .standard_plugins.fusion.StandardFusionPlugin import (
|
|
35
|
+
StandardFusionPlugin as StandardFusionPlugin,
|
|
36
|
+
)
|
|
37
|
+
from .standard_plugins.LcmLogTransportPlugin import (
|
|
38
|
+
LcmLogTransportPlugin as LcmLogTransportPlugin,
|
|
39
|
+
)
|
|
40
|
+
from .standard_plugins.LcmTransportPlugin import (
|
|
41
|
+
LcmTransportPlugin as LcmTransportPlugin,
|
|
42
|
+
)
|
|
43
|
+
from .standard_plugins.ManualHeadingAlignInitializationPlugin import (
|
|
44
|
+
ManualHeadingAlignInitializationPlugin as ManualHeadingAlignInitializationPlugin,
|
|
45
|
+
)
|
|
46
|
+
from .standard_plugins.preprocessor.StandardPreprocessorPlugin import (
|
|
47
|
+
StandardPreprocessorPlugin as StandardPreprocessorPlugin,
|
|
48
|
+
)
|
|
49
|
+
from .standard_plugins.StandardInertialPlugin import (
|
|
50
|
+
StandardInertialPlugin as StandardInertialPlugin,
|
|
51
|
+
)
|
|
52
|
+
from .standard_plugins.StandardLoggingPlugin import (
|
|
53
|
+
StandardLoggingPlugin as StandardLoggingPlugin,
|
|
54
|
+
)
|
|
55
|
+
from .standard_plugins.StandardOrchestrationPlugin import (
|
|
56
|
+
StandardOrchestrationPlugin as StandardOrchestrationPlugin,
|
|
57
|
+
)
|
|
58
|
+
from .standard_plugins.StandardRegistryPlugin import (
|
|
59
|
+
StandardRegistryPlugin as StandardRegistryPlugin,
|
|
60
|
+
)
|
|
61
|
+
from .standard_plugins.state_modeling.StandardStateModelingPlugin import (
|
|
62
|
+
StandardStateModelingPlugin as StandardStateModelingPlugin,
|
|
63
|
+
)
|
|
64
|
+
from .standard_plugins.StaticAlignInitializationPlugin import (
|
|
65
|
+
StaticAlignInitializationPlugin as StaticAlignInitializationPlugin,
|
|
66
|
+
)
|
|
67
|
+
from .tutorial_plugins.state_modeling.TutorialPosInsStateModelingPlugin import (
|
|
68
|
+
TutorialPosInsStateModelingPlugin as TutorialPosInsStateModelingPlugin,
|
|
69
|
+
)
|
|
70
|
+
from .tutorial_plugins.TutorialInitializationPlugin import (
|
|
71
|
+
TutorialInitializationPlugin as TutorialInitializationPlugin,
|
|
72
|
+
)
|
|
73
|
+
from .tutorial_plugins.TutorialLcmTransportPlugin import (
|
|
74
|
+
TutorialLcmTransportPlugin as TutorialLcmTransportPlugin,
|
|
75
|
+
)
|
|
76
|
+
from .tutorial_plugins.TutorialPosOrchestrationPlugin import (
|
|
77
|
+
TutorialPosOrchestrationPlugin as TutorialPosOrchestrationPlugin,
|
|
78
|
+
)
|
|
79
|
+
from .tutorial_plugins.TutorialPosVelOrchestrationPlugin import (
|
|
80
|
+
TutorialPosVelOrchestrationPlugin as TutorialPosVelOrchestrationPlugin,
|
|
81
|
+
)
|
|
82
|
+
from .tutorial_plugins.UiLogPlottingPlugin import (
|
|
83
|
+
UiLogPlottingPlugin as UiLogPlottingPlugin,
|
|
84
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import rclpy # type: ignore[import-not-found]
|
|
5
|
+
except ImportError as e:
|
|
6
|
+
raise ImportError(
|
|
7
|
+
'Is ROS installed and is its environment sourced? See the ROS usage '
|
|
8
|
+
'tutorial in the documentation.'
|
|
9
|
+
) from e
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from aspn23_ros_utils import AspnMsg, AspnRosNode # type: ignore[import-not-found]
|
|
13
|
+
except ImportError as e:
|
|
14
|
+
raise ImportError(
|
|
15
|
+
'Is the ASPN-ROS environment sourced? See the ROS usage tutorial in '
|
|
16
|
+
'the documentation.'
|
|
17
|
+
) from e
|
|
18
|
+
|
|
19
|
+
import contextlib
|
|
20
|
+
|
|
21
|
+
from pntos.api import LoggingLevel, Mediator, Message, TransportPlugin
|
|
22
|
+
from rclpy.executors import ( # type: ignore[import-not-found]
|
|
23
|
+
ExternalShutdownException,
|
|
24
|
+
SingleThreadedExecutor,
|
|
25
|
+
)
|
|
26
|
+
from rclpy.node import Subscription # type: ignore[import-not-found]
|
|
27
|
+
from rclpy.timer import Timer # type: ignore[import-not-found]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Aspn23RosTransportPlugin(TransportPlugin):
|
|
31
|
+
"""An example ROS Transport Plugin for ASPN23 implemented in Python"""
|
|
32
|
+
|
|
33
|
+
identifier: str
|
|
34
|
+
aspn_ros_node: AspnRosNode
|
|
35
|
+
mediator: Mediator
|
|
36
|
+
executor: SingleThreadedExecutor
|
|
37
|
+
thread: Thread
|
|
38
|
+
|
|
39
|
+
def __init__(self, identifier: str) -> None:
|
|
40
|
+
self.identifier = identifier
|
|
41
|
+
self._subs: list[Subscription] = []
|
|
42
|
+
self._topics: list[str] = []
|
|
43
|
+
self._scan_timer: Timer
|
|
44
|
+
|
|
45
|
+
def init_plugin(
|
|
46
|
+
self,
|
|
47
|
+
plugin_resources_location: str | None = None,
|
|
48
|
+
mediator: Mediator | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
PntOS plugin initialization function
|
|
52
|
+
|
|
53
|
+
This is called by the pntOS system before calling any other function.
|
|
54
|
+
"""
|
|
55
|
+
if mediator is not None:
|
|
56
|
+
self.mediator = mediator
|
|
57
|
+
if not rclpy.ok():
|
|
58
|
+
rclpy.init()
|
|
59
|
+
|
|
60
|
+
self.executor = SingleThreadedExecutor()
|
|
61
|
+
self.aspn_ros_node = AspnRosNode('aspn23_ros_transport')
|
|
62
|
+
self.executor.add_node(self.aspn_ros_node)
|
|
63
|
+
|
|
64
|
+
def execute() -> None:
|
|
65
|
+
with contextlib.suppress(ExternalShutdownException):
|
|
66
|
+
self.executor.spin()
|
|
67
|
+
|
|
68
|
+
self.thread = Thread(target=execute)
|
|
69
|
+
|
|
70
|
+
def shutdown_plugin(self) -> None:
|
|
71
|
+
"""
|
|
72
|
+
PntOS plugin shutdown function
|
|
73
|
+
|
|
74
|
+
This is called by the pntOS system when it is done with the plugin.
|
|
75
|
+
"""
|
|
76
|
+
self.stop_listening()
|
|
77
|
+
|
|
78
|
+
self.executor.shutdown()
|
|
79
|
+
rclpy.try_shutdown()
|
|
80
|
+
self.thread.join()
|
|
81
|
+
|
|
82
|
+
self.aspn_ros_node.destroy_node()
|
|
83
|
+
self.mediator.log_message(
|
|
84
|
+
LoggingLevel.INFO, f'Shutdown plugin for {self.identifier}.'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _scan_for_topics(self) -> None:
|
|
88
|
+
topic_info = self.aspn_ros_node.get_topic_names_and_types()
|
|
89
|
+
topics = [
|
|
90
|
+
topic
|
|
91
|
+
for topic, msg_types in topic_info
|
|
92
|
+
if 'aspn' in msg_types[0] and 'cobra' not in topic and 'pntos' not in topic
|
|
93
|
+
]
|
|
94
|
+
for topic in topics:
|
|
95
|
+
if topic in self._topics:
|
|
96
|
+
continue
|
|
97
|
+
self._subs.append(
|
|
98
|
+
self.aspn_ros_node.subscribe_aspn(
|
|
99
|
+
lambda msg, topic=topic: self.mediator.process_pntos_message(
|
|
100
|
+
Message(msg, topic),
|
|
101
|
+
),
|
|
102
|
+
topic,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
self._topics.append(topic)
|
|
106
|
+
self.mediator.log_message(
|
|
107
|
+
LoggingLevel.DEBUG, f'Subscribed to ROS topic {topic}.'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def start_listening(self) -> None:
|
|
111
|
+
"""Begin listening for ROS messages"""
|
|
112
|
+
|
|
113
|
+
self._scan_timer = self.aspn_ros_node.create_timer(0.01, self._scan_for_topics)
|
|
114
|
+
self.thread.start()
|
|
115
|
+
self.mediator.log_message(LoggingLevel.INFO, 'ROS transport started.')
|
|
116
|
+
|
|
117
|
+
def stop_listening(self) -> None:
|
|
118
|
+
"""Shut down all ROS subscriptions belonging to this plugin"""
|
|
119
|
+
self._scan_timer.cancel()
|
|
120
|
+
for sub in self._subs:
|
|
121
|
+
self.aspn_ros_node.destroy_subscription(sub)
|
|
122
|
+
self._subs.clear()
|
|
123
|
+
self.mediator.log_message(LoggingLevel.INFO, 'ROS transport stopped.')
|
|
124
|
+
|
|
125
|
+
def broadcast_message(
|
|
126
|
+
self, message: Message, channel_name: str | None = None
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Publish a ROS message"""
|
|
129
|
+
if not isinstance(message.wrapped_message, AspnMsg):
|
|
130
|
+
self.mediator.log_message(
|
|
131
|
+
LoggingLevel.ERROR,
|
|
132
|
+
f'Cannot publish message of type {type(message.wrapped_message)} on '
|
|
133
|
+
f'topic {channel_name}.',
|
|
134
|
+
)
|
|
135
|
+
return
|
|
136
|
+
if channel_name is None:
|
|
137
|
+
self.mediator.log_message(
|
|
138
|
+
LoggingLevel.WARN,
|
|
139
|
+
'No channel name specified (required in this implementation).',
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
if '-' in channel_name:
|
|
143
|
+
self.mediator.log_message(
|
|
144
|
+
LoggingLevel.WARN,
|
|
145
|
+
f'Channel name {channel_name} has dashes, which are not supported in '
|
|
146
|
+
f'ROS. Replacing with underscores.',
|
|
147
|
+
)
|
|
148
|
+
channel_name = channel_name.replace('-', '_')
|
|
149
|
+
self.aspn_ros_node.publish_aspn(message.wrapped_message, channel_name)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from pntos.api import (
|
|
4
|
+
CommonPlugin,
|
|
5
|
+
ControllerPlugin,
|
|
6
|
+
LoggingLevel,
|
|
7
|
+
LoggingPlugin,
|
|
8
|
+
Mediator,
|
|
9
|
+
RegistryPlugin,
|
|
10
|
+
TransportPlugin,
|
|
11
|
+
UiPlugin,
|
|
12
|
+
)
|
|
13
|
+
from pntos.cobra.config import BuscatConfig, config_from_registry
|
|
14
|
+
from pntos.cobra.utils import (
|
|
15
|
+
SortedPlugins,
|
|
16
|
+
find_base_plugin_type,
|
|
17
|
+
print_message,
|
|
18
|
+
sort_plugins_dataclass,
|
|
19
|
+
validate_plugins,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .BuscatMediator import BuscatMediator
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BuscatControllerPlugin(ControllerPlugin):
|
|
26
|
+
"""
|
|
27
|
+
This is a simple single-threaded Buscat controller plugin.
|
|
28
|
+
|
|
29
|
+
The purpose of this plugin is to route one or more data streams from
|
|
30
|
+
:class:`TransportPlugins <pntos.api.TransportPlugin>` back out through one or more
|
|
31
|
+
:class:`TransportPlugins <pntos.api.TransportPlugin>`, as specified in the supplied
|
|
32
|
+
:attr:`BuscatConfig.output_transports <pntos.cobra.config.BuscatConfig.output_transports>`.
|
|
33
|
+
This has the effect of combining multiple data streams and converting all input to the formats
|
|
34
|
+
supported by the output :class:`TransportPlugins <pntos.api.TransportPlugin>`. Note that this
|
|
35
|
+
controller does not do any buffering or sorting of the input data before publishing. It (or more
|
|
36
|
+
specifically, the :class:`BuscatMediator <pntos.cobra.internal.BuscatMediator>`) also does not
|
|
37
|
+
explicitly pass input data to plugins other than :class:`TransportPlugins <pntos.api.TransportPlugin>`,
|
|
38
|
+
and thus will not support sensor fusion without some modification.
|
|
39
|
+
|
|
40
|
+
Here are the plugins and corresponding expected number of instances this controller
|
|
41
|
+
looks for:
|
|
42
|
+
|
|
43
|
+
- LoggingPlugin - 1
|
|
44
|
+
- RegistryPlugin - 1
|
|
45
|
+
- TransportPlugin - at least 1
|
|
46
|
+
- UiPlugin - any, but only 1 can require the main thread
|
|
47
|
+
|
|
48
|
+
It checks for the expected plugins, sets up mediators, and then passes the mediators
|
|
49
|
+
to each plugin in :meth:`pntos.api.CommonPlugin.init_plugin`, then passes off to the
|
|
50
|
+
:meth:`_main` function.
|
|
51
|
+
|
|
52
|
+
Inside the main function, calls :meth:`pntos.api.TransportPlugin.start_listening` on
|
|
53
|
+
all transport plugins, checks if the UI needs to update (and passes it the thread if
|
|
54
|
+
so), then waits for a ctrl+c to exit pntOS.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_plugin_resources_location: str | None
|
|
58
|
+
|
|
59
|
+
def __init__(self, identifier: str) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Cobra Buscat Controller
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
identifier (str): The plugin identifier passed to the
|
|
65
|
+
:attr:`pntos.api.CommonPlugin.identifier` field.
|
|
66
|
+
"""
|
|
67
|
+
self.identifier = identifier
|
|
68
|
+
self._plugins: list[CommonPlugin] = []
|
|
69
|
+
self._logging_plugin: LoggingPlugin | None = None
|
|
70
|
+
self._transport_plugins: list[TransportPlugin] = []
|
|
71
|
+
self._ui_plugins: list[UiPlugin] = []
|
|
72
|
+
self._registry_plugin: RegistryPlugin | None = None
|
|
73
|
+
|
|
74
|
+
def init_plugin(
|
|
75
|
+
self,
|
|
76
|
+
plugin_resources_location: str | None = None,
|
|
77
|
+
mediator: Mediator | None = None,
|
|
78
|
+
) -> None:
|
|
79
|
+
if mediator is not None:
|
|
80
|
+
self._log(
|
|
81
|
+
LoggingLevel.ERROR, 'Controller plugin should not be passed a mediator.'
|
|
82
|
+
)
|
|
83
|
+
self._plugin_resources_location = plugin_resources_location
|
|
84
|
+
|
|
85
|
+
def shutdown_plugin(self) -> None:
|
|
86
|
+
self._log(LoggingLevel.INFO, 'Shutting down all plugins...')
|
|
87
|
+
|
|
88
|
+
for plugin in self._plugins:
|
|
89
|
+
# Don't shut down registry and logging plugins before the others
|
|
90
|
+
if not isinstance(plugin, (RegistryPlugin, LoggingPlugin)):
|
|
91
|
+
plugin.shutdown_plugin()
|
|
92
|
+
|
|
93
|
+
# Now close the registry and logging plugin last
|
|
94
|
+
if self._registry_plugin is not None:
|
|
95
|
+
self._registry_plugin.shutdown_plugin()
|
|
96
|
+
if self._logging_plugin is not None:
|
|
97
|
+
self._logging_plugin.shutdown_plugin()
|
|
98
|
+
|
|
99
|
+
identifier: str
|
|
100
|
+
|
|
101
|
+
def take_control(
|
|
102
|
+
self,
|
|
103
|
+
plugins: list[CommonPlugin],
|
|
104
|
+
plugin_resources_locations: list[str | None] | None = None,
|
|
105
|
+
initial_config: str | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
self._plugins = plugins
|
|
108
|
+
|
|
109
|
+
# Make sure there are enough plugins to run
|
|
110
|
+
(
|
|
111
|
+
self._registry_plugin,
|
|
112
|
+
self._logging_plugin,
|
|
113
|
+
self._transport_plugins,
|
|
114
|
+
self._ui_plugins,
|
|
115
|
+
) = self._sort_and_validate_plugins(plugins)
|
|
116
|
+
|
|
117
|
+
# Hand mediators controller plugin so that mediators can call "shutdown_plugin"
|
|
118
|
+
# on error.
|
|
119
|
+
BuscatMediator._controller_plugin = self
|
|
120
|
+
|
|
121
|
+
# Create separate mediators to pass to each plugin
|
|
122
|
+
mediators = [
|
|
123
|
+
BuscatMediator(p.identifier, find_base_plugin_type(p))
|
|
124
|
+
for p in self._plugins
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
# We need to make sure we got a valid plugin_resources_locations
|
|
128
|
+
if plugin_resources_locations is not None and len(
|
|
129
|
+
plugin_resources_locations
|
|
130
|
+
) != len(self._plugins):
|
|
131
|
+
self._log(
|
|
132
|
+
LoggingLevel.ERROR,
|
|
133
|
+
'Length of plugin_resources_location '
|
|
134
|
+
+ f'({len(plugin_resources_locations)}) does not equal the '
|
|
135
|
+
+ f'number of plugins ({len(self._plugins)}). Passing '
|
|
136
|
+
+ 'None to all plugins instead.',
|
|
137
|
+
)
|
|
138
|
+
plugin_resources_locations = None
|
|
139
|
+
|
|
140
|
+
# Initialize registry plugin first thing
|
|
141
|
+
reg_i = self._plugins.index(self._registry_plugin)
|
|
142
|
+
self._registry_plugin.init_plugin(
|
|
143
|
+
None
|
|
144
|
+
if plugin_resources_locations is None
|
|
145
|
+
else plugin_resources_locations[reg_i],
|
|
146
|
+
mediators[reg_i],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Give mediators a registry
|
|
150
|
+
BuscatMediator.registry = self._registry_plugin.new_registry(initial_config)
|
|
151
|
+
|
|
152
|
+
# Initialize logger second
|
|
153
|
+
assert self._logging_plugin is not None
|
|
154
|
+
log_i = self._plugins.index(self._logging_plugin)
|
|
155
|
+
self._logging_plugin.init_plugin(
|
|
156
|
+
None
|
|
157
|
+
if plugin_resources_locations is None
|
|
158
|
+
else plugin_resources_locations[log_i],
|
|
159
|
+
mediators[log_i],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Give mediators a logging plugin
|
|
163
|
+
BuscatMediator._logging_plugin = self._logging_plugin
|
|
164
|
+
|
|
165
|
+
# call init_plugin() on all the other plugins
|
|
166
|
+
for i, plugin in enumerate(self._plugins):
|
|
167
|
+
if i in (reg_i, log_i):
|
|
168
|
+
continue
|
|
169
|
+
plugin.init_plugin(
|
|
170
|
+
plugin_resources_location=None
|
|
171
|
+
if plugin_resources_locations is None
|
|
172
|
+
else plugin_resources_locations[i],
|
|
173
|
+
mediator=mediators[i],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Give the mediators other needed plugins
|
|
177
|
+
BuscatMediator._transport_plugins = self._transport_plugins
|
|
178
|
+
|
|
179
|
+
# Set the mediators' output transport
|
|
180
|
+
temp_mediator = BuscatMediator(self.identifier, ControllerPlugin)
|
|
181
|
+
config = config_from_registry(BuscatConfig, temp_mediator, BuscatConfig.group)
|
|
182
|
+
if config is None:
|
|
183
|
+
self._log(
|
|
184
|
+
LoggingLevel.ERROR,
|
|
185
|
+
'Could not extract BuscatConfig from group "buscat". Cannot initialize Buscat controller plugin.',
|
|
186
|
+
)
|
|
187
|
+
return
|
|
188
|
+
BuscatMediator._output_transports = config.output_transports
|
|
189
|
+
|
|
190
|
+
# Pass off to main control loop
|
|
191
|
+
self._main()
|
|
192
|
+
|
|
193
|
+
def _sort_and_validate_plugins(
|
|
194
|
+
self, plugins: list[CommonPlugin]
|
|
195
|
+
) -> tuple[RegistryPlugin, LoggingPlugin, list[TransportPlugin], list[UiPlugin]]:
|
|
196
|
+
"""
|
|
197
|
+
Utility function to ensure ``plugins`` contains enough plugins to run pntOS
|
|
198
|
+
Cobra. Then assigns and dispatches them to the relevant fields on the
|
|
199
|
+
controller. Raises a :class:`RuntimeError` if plugins are
|
|
200
|
+
not as expected.
|
|
201
|
+
"""
|
|
202
|
+
sorted_plugins: SortedPlugins = sort_plugins_dataclass(plugins)
|
|
203
|
+
|
|
204
|
+
if not validate_plugins(
|
|
205
|
+
sorted_plugins,
|
|
206
|
+
self._log,
|
|
207
|
+
registry_plugins=(1, 1),
|
|
208
|
+
logging_plugins=(1, 1),
|
|
209
|
+
transport_plugins=(1, 1000),
|
|
210
|
+
):
|
|
211
|
+
raise RuntimeError('Not enough plugins to run pntOS.')
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
sorted_plugins.registry_plugins[0],
|
|
215
|
+
sorted_plugins.logging_plugins[0],
|
|
216
|
+
sorted_plugins.transport_plugins,
|
|
217
|
+
sorted_plugins.ui_plugins,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _log(self, level: LoggingLevel, message: str) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Utility logging method for controller.
|
|
223
|
+
|
|
224
|
+
NOTE: This is only intended for log messages originating from the
|
|
225
|
+
controller.
|
|
226
|
+
"""
|
|
227
|
+
if self._logging_plugin is None:
|
|
228
|
+
print_message(level, ControllerPlugin.__name__, message)
|
|
229
|
+
else:
|
|
230
|
+
self._logging_plugin.log(ControllerPlugin, self.identifier, level, message)
|
|
231
|
+
|
|
232
|
+
def _main(self) -> None:
|
|
233
|
+
"""
|
|
234
|
+
The main control of pntOS Cobra.
|
|
235
|
+
"""
|
|
236
|
+
for transport in self._transport_plugins:
|
|
237
|
+
transport.start_listening()
|
|
238
|
+
|
|
239
|
+
# Run main thread from UI plugin, if needed
|
|
240
|
+
ui_needing_main_thrd = [p for p in self._ui_plugins if p.requires_main_thread()]
|
|
241
|
+
if len(ui_needing_main_thrd) > 1:
|
|
242
|
+
self._log(
|
|
243
|
+
LoggingLevel.ERROR,
|
|
244
|
+
f'Only 1 UiPlugin can require the main thread, but found {len(ui_needing_main_thrd)} needing the main thread. Cannot run pntOS.',
|
|
245
|
+
)
|
|
246
|
+
if ui_needing_main_thrd:
|
|
247
|
+
ui_needing_main_thrd[0].run_main_thread()
|
|
248
|
+
|
|
249
|
+
else: # wait for ctrl + c to exit
|
|
250
|
+
self._log(
|
|
251
|
+
LoggingLevel.INFO,
|
|
252
|
+
'Press Ctrl + C at any time to shut down pntOS...',
|
|
253
|
+
)
|
|
254
|
+
try:
|
|
255
|
+
BuscatMediator._logging_error_event.wait()
|
|
256
|
+
except KeyboardInterrupt:
|
|
257
|
+
self._log(LoggingLevel.INFO, 'Keyboard Interrupt Detected.')
|
|
258
|
+
|
|
259
|
+
self.shutdown_plugin()
|
|
260
|
+
if BuscatMediator._logging_error_event.is_set():
|
|
261
|
+
sys.exit(1)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from threading import Event
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from aspn23 import TypeTimestamp
|
|
5
|
+
from pntos.api import (
|
|
6
|
+
ControllerPlugin,
|
|
7
|
+
LoggingLevel,
|
|
8
|
+
LoggingPlugin,
|
|
9
|
+
Mediator,
|
|
10
|
+
Message,
|
|
11
|
+
PluginType,
|
|
12
|
+
Registry,
|
|
13
|
+
TransportPlugin,
|
|
14
|
+
)
|
|
15
|
+
from pntos.cobra.utils import print_message
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BuscatMediator(Mediator):
|
|
19
|
+
"""
|
|
20
|
+
This is a simple Buscat mediator implementation. It was designed to be used in conjunction with the
|
|
21
|
+
:class:`pntos.cobra.BuscatControllerPlugin` which is why this controller directly access private members.
|
|
22
|
+
It has one public member ``registry`` that other plugins are allowed to access.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_logging_plugin: LoggingPlugin | None = None
|
|
26
|
+
_transport_plugins: ClassVar[list[TransportPlugin]] = []
|
|
27
|
+
_controller_plugin: ControllerPlugin | None = None
|
|
28
|
+
_logging_error_event: Event = Event()
|
|
29
|
+
registry: Registry
|
|
30
|
+
_output_transports: tuple[str, ...]
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
attached_plugin_identifier: str,
|
|
35
|
+
attached_plugin_type: PluginType,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Buscat Mediator
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
attached_plugin_identifier (str): The identifier field of the plugin this
|
|
42
|
+
mediator is assigned to.
|
|
43
|
+
attached_plugin_type (PluginType | None): The abstract plugin type of the
|
|
44
|
+
plugin this mediator is assigned to.
|
|
45
|
+
"""
|
|
46
|
+
self._attached_plugin_type: PluginType = attached_plugin_type
|
|
47
|
+
self._attached_plugin_identifier: str = attached_plugin_identifier
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def filter_description_list(self) -> list[str]:
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
def request_solutions(
|
|
54
|
+
self,
|
|
55
|
+
solution_times: list[TypeTimestamp],
|
|
56
|
+
filter_description: str | None = None,
|
|
57
|
+
) -> list[Message | None] | None:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def process_pntos_message(self, message: Message) -> None:
|
|
61
|
+
# pass to designated transport plugin for broadcast
|
|
62
|
+
if message.source_identifier.startswith('/buscat/pntos'):
|
|
63
|
+
channel = message.source_identifier
|
|
64
|
+
else:
|
|
65
|
+
channel = '/buscat/pntos' + message.source_identifier
|
|
66
|
+
|
|
67
|
+
for output_transport in self._output_transports:
|
|
68
|
+
self.broadcast_aspn_message(
|
|
69
|
+
message,
|
|
70
|
+
transport=output_transport,
|
|
71
|
+
destination_identifier=channel,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def broadcast_aspn_message(
|
|
75
|
+
self,
|
|
76
|
+
message: Message,
|
|
77
|
+
transport: str | None = None,
|
|
78
|
+
destination_identifier: str | None = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
assert len(self._transport_plugins) != 0, (
|
|
81
|
+
'Transport plugin used before initialized and passed to mediator.'
|
|
82
|
+
)
|
|
83
|
+
sent = False
|
|
84
|
+
for transport_plugin in self._transport_plugins:
|
|
85
|
+
if transport is None or transport_plugin.identifier == transport:
|
|
86
|
+
transport_plugin.broadcast_message(message, destination_identifier)
|
|
87
|
+
sent = True
|
|
88
|
+
if not sent:
|
|
89
|
+
self._log_message(
|
|
90
|
+
LoggingLevel.WARN,
|
|
91
|
+
f'Transport "{transport}" not found. Unable to broadcast message.',
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def log_message(self, level: LoggingLevel, message: str) -> None:
|
|
95
|
+
self._log_message(level, message, self._attached_plugin_type)
|
|
96
|
+
|
|
97
|
+
def _log_message(
|
|
98
|
+
self,
|
|
99
|
+
level: LoggingLevel,
|
|
100
|
+
message: str,
|
|
101
|
+
plugin_type: PluginType = ControllerPlugin,
|
|
102
|
+
) -> None:
|
|
103
|
+
if self._logging_plugin is None:
|
|
104
|
+
print_message(level, plugin_type.__name__, message)
|
|
105
|
+
else:
|
|
106
|
+
self._logging_plugin.log(
|
|
107
|
+
plugin_type,
|
|
108
|
+
self._attached_plugin_identifier,
|
|
109
|
+
level,
|
|
110
|
+
message,
|
|
111
|
+
)
|
|
112
|
+
if level is LoggingLevel.ERROR and self._controller_plugin is not None:
|
|
113
|
+
self._logging_error_event.set()
|