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.
Files changed (136) hide show
  1. pntos/__init__.py +1 -0
  2. pntos/cobra/__init__.py +84 -0
  3. pntos/cobra/advanced_plugins/Aspn23RosTransportPlugin.py +149 -0
  4. pntos/cobra/advanced_plugins/buscat/BuscatControllerPlugin.py +261 -0
  5. pntos/cobra/advanced_plugins/buscat/BuscatMediator.py +113 -0
  6. pntos/cobra/advanced_plugins/ui/CobraUiLogPlayerPlugin.py +279 -0
  7. pntos/cobra/advanced_plugins/ui/ExperimentalCobraUiPlugin.py +261 -0
  8. pntos/cobra/advanced_plugins/ui/_static/dist/assets/Cobra_Logo-Standards_Horizontal-White-NoTagline_Vers1-BRfZe-iO.png +0 -0
  9. pntos/cobra/advanced_plugins/ui/_static/dist/assets/Cobra_Logo-Standards_Horizontal-White-Tagline_Vers1-ClwmN2Dp.png +0 -0
  10. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Black-Cuts-7cD.woff +0 -0
  11. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Black-os_tY5hn.woff2 +0 -0
  12. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Bold-BVFJaemX.woff +0 -0
  13. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Bold-DiAsa6G6.woff2 +0 -0
  14. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-BoldItalic-DUIV9VND.woff2 +0 -0
  15. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-BoldItalic-Dd9Vjcig.woff +0 -0
  16. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-ExtraBold-1GgzYkPj.woff2 +0 -0
  17. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-ExtraBold-CFEB4Nq7.woff +0 -0
  18. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Italic-BdaAcyO1.woff2 +0 -0
  19. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Italic-CvfvqgpG.woff +0 -0
  20. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Light-BiiL_zoC.woff2 +0 -0
  21. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Light-D_3u9Ke2.woff +0 -0
  22. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-LightItalic-BfN20AIg.woff +0 -0
  23. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-LightItalic-D8lCSEYW.woff2 +0 -0
  24. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Medium-DSEqpuun.woff2 +0 -0
  25. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Medium-Dkrd-Odz.woff +0 -0
  26. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-MediumItalic-CpB315hL.woff +0 -0
  27. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-MediumItalic-DKNIea0p.woff2 +0 -0
  28. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Regular-BLbITkWW.woff2 +0 -0
  29. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-Regular-Gz7hSEw8.woff +0 -0
  30. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBold-Bq0Ehvj3.woff +0 -0
  31. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBold-rrjtAbTs.woff2 +0 -0
  32. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBoldItalic-BrsCcVDJ.woff +0 -0
  33. pntos/cobra/advanced_plugins/ui/_static/dist/assets/HKGrotesk-SemiBoldItalic-C5HpS0vu.woff2 +0 -0
  34. pntos/cobra/advanced_plugins/ui/_static/dist/assets/ground_speed_dark-DXZwT6_z.svg +11 -0
  35. pntos/cobra/advanced_plugins/ui/_static/dist/assets/index-BfheAgQK.css +1 -0
  36. pntos/cobra/advanced_plugins/ui/_static/dist/assets/index-CZzgVHg8.js +21 -0
  37. pntos/cobra/advanced_plugins/ui/_static/dist/index.html +14 -0
  38. pntos/cobra/advanced_plugins/ui/_static/dist/purple-cobra-icon.ico +0 -0
  39. pntos/cobra/advanced_plugins/ui/models.py +88 -0
  40. pntos/cobra/advanced_plugins/ui/types.py +75 -0
  41. pntos/cobra/advanced_plugins/ui/utils.py +300 -0
  42. pntos/cobra/config/BaseConfig.py +17 -0
  43. pntos/cobra/config/ClockBiasStateBlockConfig.py +48 -0
  44. pntos/cobra/config/ConstantStateBlockConfig.py +41 -0
  45. pntos/cobra/config/ControllerConfig.py +41 -0
  46. pntos/cobra/config/FogmConfig.py +22 -0
  47. pntos/cobra/config/FusionEngineConfig.py +43 -0
  48. pntos/cobra/config/ImuConfig.py +53 -0
  49. pntos/cobra/config/InertialConfig.py +32 -0
  50. pntos/cobra/config/LcmTransportConfig.py +51 -0
  51. pntos/cobra/config/ManualAlignmentConfig.py +89 -0
  52. pntos/cobra/config/ManualHeadingAlignmentConfig.py +36 -0
  53. pntos/cobra/config/MountingConfig.py +23 -0
  54. pntos/cobra/config/OrchestrationConfig.py +790 -0
  55. pntos/cobra/config/PreprocessorConfig.py +167 -0
  56. pntos/cobra/config/StaticAlignmentConfig.py +25 -0
  57. pntos/cobra/config/VirtualStateBlockConfig.py +87 -0
  58. pntos/cobra/config/__init__.py +69 -0
  59. pntos/cobra/config/ui_config.py +56 -0
  60. pntos/cobra/config/utils.py +623 -0
  61. pntos/cobra/dummy_plugins/DummyControllerPlugin.py +92 -0
  62. pntos/cobra/dummy_plugins/DummyMediator.py +114 -0
  63. pntos/cobra/dummy_plugins/DummyMessageStreamConfig.py +70 -0
  64. pntos/cobra/dummy_plugins/DummyOrchestrationPlugin.py +72 -0
  65. pntos/cobra/dummy_plugins/DummyTransportPlugin.py +118 -0
  66. pntos/cobra/internal.py +125 -0
  67. pntos/cobra/py.typed +0 -0
  68. pntos/cobra/standard_plugins/DiagnosticLogPlugin.py +69 -0
  69. pntos/cobra/standard_plugins/EkfFusionStrategyPlugin.py +290 -0
  70. pntos/cobra/standard_plugins/LcmLogTransportPlugin.py +163 -0
  71. pntos/cobra/standard_plugins/LcmTransportPlugin.py +185 -0
  72. pntos/cobra/standard_plugins/ManualHeadingAlignInitializationPlugin.py +128 -0
  73. pntos/cobra/standard_plugins/StandardInertialPlugin.py +252 -0
  74. pntos/cobra/standard_plugins/StandardLoggingPlugin.py +85 -0
  75. pntos/cobra/standard_plugins/StandardOrchestrationPlugin.py +933 -0
  76. pntos/cobra/standard_plugins/StandardRegistryPlugin.py +619 -0
  77. pntos/cobra/standard_plugins/StaticAlignInitializationPlugin.py +120 -0
  78. pntos/cobra/standard_plugins/controller/StandardControllerPlugin.py +298 -0
  79. pntos/cobra/standard_plugins/controller/StandardMediator.py +177 -0
  80. pntos/cobra/standard_plugins/controller/StandardMessageStreamConfig.py +81 -0
  81. pntos/cobra/standard_plugins/fusion/StandardFusionPlugin.py +967 -0
  82. pntos/cobra/standard_plugins/fusion/VirtualStateBlockManager.py +372 -0
  83. pntos/cobra/standard_plugins/preprocessor/BarometerToAltitudePreprocessor.py +66 -0
  84. pntos/cobra/standard_plugins/preprocessor/DownsamplerPreprocessor.py +79 -0
  85. pntos/cobra/standard_plugins/preprocessor/ImuRotationPreprocessor.py +41 -0
  86. pntos/cobra/standard_plugins/preprocessor/OutagePreprocessor.py +70 -0
  87. pntos/cobra/standard_plugins/preprocessor/StandardPreprocessorPlugin.py +216 -0
  88. pntos/cobra/standard_plugins/preprocessor/TimeAdjusterPreprocessor.py +62 -0
  89. pntos/cobra/standard_plugins/preprocessor/TimeBiasPreprocessor.py +56 -0
  90. pntos/cobra/standard_plugins/state_modeling/AltitudeMeasurementProcessor.py +186 -0
  91. pntos/cobra/standard_plugins/state_modeling/ClockBiasStateBlock.py +116 -0
  92. pntos/cobra/standard_plugins/state_modeling/ConstantStateBlock.py +54 -0
  93. pntos/cobra/standard_plugins/state_modeling/Direction3DToPointsMeasurementProcessor.py +278 -0
  94. pntos/cobra/standard_plugins/state_modeling/FogmBlock.py +96 -0
  95. pntos/cobra/standard_plugins/state_modeling/Pinson15NedBlock.py +399 -0
  96. pntos/cobra/standard_plugins/state_modeling/PinsonBodyVelocityMeasurementProcessor.py +279 -0
  97. pntos/cobra/standard_plugins/state_modeling/PinsonPosVelMeasurementProcessor.py +239 -0
  98. pntos/cobra/standard_plugins/state_modeling/PinsonPositionMeasurementProcessor.py +256 -0
  99. pntos/cobra/standard_plugins/state_modeling/PinsonVelocityMeasurementProcessor.py +137 -0
  100. pntos/cobra/standard_plugins/state_modeling/PinsonWithLeverArmPositionMeasurementProcessor.py +282 -0
  101. pntos/cobra/standard_plugins/state_modeling/PinsonWithNedFogmPositionMeasurementProcessor.py +240 -0
  102. pntos/cobra/standard_plugins/state_modeling/PositionMeasurementProcessor.py +147 -0
  103. pntos/cobra/standard_plugins/state_modeling/StandardStateModelingPlugin.py +558 -0
  104. pntos/cobra/standard_plugins/state_modeling/virtual_state_blocks/PinsonErrorToStandard.py +216 -0
  105. pntos/cobra/standard_plugins/state_modeling/virtual_state_blocks/StateExtractor.py +113 -0
  106. pntos/cobra/tutorial_plugins/TutorialInitializationPlugin.py +176 -0
  107. pntos/cobra/tutorial_plugins/TutorialLcmTransportPlugin.py +109 -0
  108. pntos/cobra/tutorial_plugins/TutorialPosOrchestrationPlugin.py +346 -0
  109. pntos/cobra/tutorial_plugins/TutorialPosVelOrchestrationPlugin.py +358 -0
  110. pntos/cobra/tutorial_plugins/UiLogPlottingPlugin.py +118 -0
  111. pntos/cobra/tutorial_plugins/state_modeling/TutorialFogmBlock.py +71 -0
  112. pntos/cobra/tutorial_plugins/state_modeling/TutorialPinson15NedBlock.py +329 -0
  113. pntos/cobra/tutorial_plugins/state_modeling/TutorialPinsonVelocityMeasurementProcessor.py +75 -0
  114. pntos/cobra/tutorial_plugins/state_modeling/TutorialPinsonWithNedFogmPositionMeasurementProcessor.py +114 -0
  115. pntos/cobra/tutorial_plugins/state_modeling/TutorialPosInsStateModelingPlugin.py +207 -0
  116. pntos/cobra/utils/__init__.py +98 -0
  117. pntos/cobra/utils/apps.py +85 -0
  118. pntos/cobra/utils/arrays.py +140 -0
  119. pntos/cobra/utils/conversions.py +432 -0
  120. pntos/cobra/utils/hdf5.py +128 -0
  121. pntos/cobra/utils/lcm_utils.py +299 -0
  122. pntos/cobra/utils/logger.py +84 -0
  123. pntos/cobra/utils/logging.py +78 -0
  124. pntos/cobra/utils/navigation.py +380 -0
  125. pntos/cobra/utils/orchestration_utils.py +520 -0
  126. pntos/cobra/utils/plots.py +533 -0
  127. pntos/cobra/utils/plugins.py +219 -0
  128. pntos/cobra/utils/registry.py +286 -0
  129. pntos/cobra/utils/ros.py +84 -0
  130. pntos/cobra/utils/ui.py +390 -0
  131. pntos/py.typed +0 -0
  132. pntos_cobra-2.1.0.post0.dev0.dist-info/METADATA +34 -0
  133. pntos_cobra-2.1.0.post0.dev0.dist-info/RECORD +136 -0
  134. pntos_cobra-2.1.0.post0.dev0.dist-info/WHEEL +5 -0
  135. pntos_cobra-2.1.0.post0.dev0.dist-info/licenses/LICENSE +177 -0
  136. 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__)
@@ -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()