pntos-api 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 +3 -0
- pntos/api/__init__.py +79 -0
- pntos/api/plugins/__init__.py +1 -0
- pntos/api/plugins/common.py +1046 -0
- pntos/api/plugins/controller.py +99 -0
- pntos/api/plugins/fusion.py +572 -0
- pntos/api/plugins/fusion_strategy.py +306 -0
- pntos/api/plugins/inertial.py +414 -0
- pntos/api/plugins/initialization.py +278 -0
- pntos/api/plugins/logging.py +68 -0
- pntos/api/plugins/orchestration.py +253 -0
- pntos/api/plugins/platform_integration.py +81 -0
- pntos/api/plugins/preprocessor.py +88 -0
- pntos/api/plugins/registry.py +46 -0
- pntos/api/plugins/state_modeling.py +581 -0
- pntos/api/plugins/transport.py +50 -0
- pntos/api/plugins/ui.py +43 -0
- pntos/api/plugins/utility.py +17 -0
- pntos/py.typed +0 -0
- pntos_api-2.1.0.post0.dev0.dist-info/METADATA +21 -0
- pntos_api-2.1.0.post0.dev0.dist-info/RECORD +24 -0
- pntos_api-2.1.0.post0.dev0.dist-info/WHEEL +5 -0
- pntos_api-2.1.0.post0.dev0.dist-info/licenses/LICENSE +177 -0
- pntos_api-2.1.0.post0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
"""Python API of pntOS."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable, Iterator
|
|
5
|
+
from contextlib import AbstractContextManager
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from types import TracebackType
|
|
9
|
+
from typing import TypeVar, final
|
|
10
|
+
|
|
11
|
+
from aspn23 import AspnBase, TypeTimestamp
|
|
12
|
+
from numpy import float64
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Message:
|
|
18
|
+
"""
|
|
19
|
+
A container for an ASPN message.
|
|
20
|
+
|
|
21
|
+
This container may contain either proper ASPN messages which are part of the ASPN data model, or
|
|
22
|
+
extension messages specific to pntOS which augment ASPN.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
wrapped_message (AspnBase): Either an ASPN message or a pntOS ASPN Extension message.
|
|
26
|
+
source_identifier (str): Indicates where this message came from. If the message originated
|
|
27
|
+
from a :class:`pntos.api.TransportPlugin` and the underlying transport has the concept of a
|
|
28
|
+
channel or topic, this field should be populated by the channel or topic. Otherwise, the
|
|
29
|
+
identifier is populated in a plugin-specific manner by the originating plugin that
|
|
30
|
+
created the message.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
wrapped_message: AspnBase
|
|
34
|
+
source_identifier: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EstimateWithCovarianceType(IntEnum):
|
|
38
|
+
"""Describes how the fields in :class:`pntos.api.EstimateWithCovariance` are used."""
|
|
39
|
+
|
|
40
|
+
EWC_GENERIC = 0
|
|
41
|
+
"""
|
|
42
|
+
Contains a mean (estimate) and covariance describing a multivariate Gaussian distribution.
|
|
43
|
+
|
|
44
|
+
- :attr:`.EstimateWithCovariance.estimate` is size Nx1 where N is the length field.
|
|
45
|
+
- :attr:`.EstimateWithCovariance.covariance` is size NxN where N is the length field.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
EWC_ATTITUDE_QUAT = 1
|
|
49
|
+
"""
|
|
50
|
+
Contains a mean (estimate) and covariance describing a rotation modeled by a multivariate
|
|
51
|
+
Gaussian distribution, but the estimate is in quaternion form and the covariance is in tilt
|
|
52
|
+
error form.
|
|
53
|
+
|
|
54
|
+
- :attr:`.EstimateWithCovariance.estimate` is size 4x1.
|
|
55
|
+
- :attr:`.EstimateWithCovariance.covariance` is size 3x3, in :math:`\\text{radians}^2`.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class EstimateWithCovariance:
|
|
61
|
+
"""
|
|
62
|
+
A container for holding an estimate and covariance.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
type (EstimateWithCovarianceType): Describes how the fields in this struct are used.
|
|
66
|
+
estimate (NDArray[float64]): An array of doubles representing an estimate vector. Usage depends on
|
|
67
|
+
the ``type`` field.
|
|
68
|
+
covariance (NDArray[float64]): An array of doubles representing a square covariance matrix. Data is
|
|
69
|
+
stored in row major form. Usage depends on the ``type`` field.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
type: EstimateWithCovarianceType
|
|
73
|
+
estimate: NDArray[float64]
|
|
74
|
+
"""An estimate vector."""
|
|
75
|
+
|
|
76
|
+
covariance: NDArray[float64]
|
|
77
|
+
"""A covariance matrix, describing the errors in the estimate."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LoggingLevel(IntEnum):
|
|
81
|
+
"""An enumeration of the types of log outs that are available in pntOS."""
|
|
82
|
+
|
|
83
|
+
ERROR = 0
|
|
84
|
+
"""
|
|
85
|
+
This output indicates the program has entered an error state, and
|
|
86
|
+
likely needs to be inspected to discover what went wrong.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
WARN = 1
|
|
90
|
+
"""
|
|
91
|
+
This output is designed to warn of a possibly unintended state that may
|
|
92
|
+
be harmless or be indicative of a bug.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
INFO = 2
|
|
96
|
+
"""
|
|
97
|
+
This output is designed to be informational, and may indicate correct
|
|
98
|
+
operation.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
DEBUG = 3
|
|
102
|
+
"""
|
|
103
|
+
This output is designed to assist in debugging plugins by providing
|
|
104
|
+
additional information about state and behavior which would be
|
|
105
|
+
otherwise unnecessary.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class KeyValueStoreDataFormat(IntEnum):
|
|
110
|
+
"""
|
|
111
|
+
The format of data returned or expected.
|
|
112
|
+
|
|
113
|
+
An enum that specifies the format of data returned/expected in the :meth:`pntos.api.KeyValueStore.get_raw`
|
|
114
|
+
and :meth:`pntos.api.KeyValueStore.set_raw` methods. This value is otherwise unused when querying a
|
|
115
|
+
key-value store.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
INI = 0
|
|
119
|
+
"""
|
|
120
|
+
Keys and their corresponding values are returned according to the INI
|
|
121
|
+
file format specification.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
UNSPECIFIED = 1
|
|
125
|
+
"""
|
|
126
|
+
An opaque type that is undefined by the implementer.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
RegistryValueType = TypeVar(
|
|
131
|
+
'RegistryValueType',
|
|
132
|
+
str,
|
|
133
|
+
list[str],
|
|
134
|
+
int,
|
|
135
|
+
bool,
|
|
136
|
+
float,
|
|
137
|
+
NDArray[float64],
|
|
138
|
+
Message,
|
|
139
|
+
)
|
|
140
|
+
"""
|
|
141
|
+
A ``TypeVar`` of the types allowed in :class:`pntos.api.KeyValueStore`.
|
|
142
|
+
|
|
143
|
+
A ``TypeVar`` is particularly for cases where a method needs to guarantee that
|
|
144
|
+
the type on an input is the same as the returned type.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
For example, :meth:`pntos.api.KeyValueStore.get_value` needs to guarantee that
|
|
148
|
+
the input and the return types are the same. Thus, :meth:`pntos.api.KeyValueStore.get_value` would
|
|
149
|
+
be a good place to use :class:`pntos.api.RegistryValueType` in the type description::
|
|
150
|
+
|
|
151
|
+
def get_value(
|
|
152
|
+
self, key: str, value_type: type[RegistryValueType]
|
|
153
|
+
) -> RegistryValueType | None
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
RegistryValueTypeUnion = (
|
|
157
|
+
str | list[str] | int | bool | float | NDArray[float64] | Message
|
|
158
|
+
)
|
|
159
|
+
"""
|
|
160
|
+
This is a union of all types allowed in :class:`pntos.api.KeyValueStore`.
|
|
161
|
+
|
|
162
|
+
This is particularly for cases where a method does not need to guarantee that
|
|
163
|
+
the type on an input is the same as the returned type.
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
For example, :meth:`pntos.api.KeyValueStore.set_value` does not need to guarantee that
|
|
167
|
+
the input and the return type are the same since it returns `None`. Thus,
|
|
168
|
+
:meth:`pntos.api.KeyValueStore.set_value` would be a good place to use :class:`RegistryValueTypeUnion`
|
|
169
|
+
in the type description::
|
|
170
|
+
|
|
171
|
+
def set_value(self, key: str, value: RegistryValueTypeUnion) -> None
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class KeyValueStore(AbstractContextManager['KeyValueStore']):
|
|
176
|
+
"""
|
|
177
|
+
A key-value store implemented with a string-pair key.
|
|
178
|
+
|
|
179
|
+
This key-value store is intended to function as an expanded python
|
|
180
|
+
dictionary.
|
|
181
|
+
|
|
182
|
+
Each value can be looked up by an associated key (string). Values can be of
|
|
183
|
+
any type specified by :class:`RegistryValueType`/:class:`RegistryValueTypeUnion`.
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
For example, to store a string value "foo" in the key-value store under the key "k1", one
|
|
187
|
+
would write either of the following::
|
|
188
|
+
|
|
189
|
+
store.set_value("k1", "foo")
|
|
190
|
+
store["k1"] = "foo"
|
|
191
|
+
|
|
192
|
+
At this point, the key-value store would have recorded the value into its
|
|
193
|
+
internal data storage. Later a user could call either of these lines::
|
|
194
|
+
|
|
195
|
+
foo = store.get_value("k1", str)
|
|
196
|
+
foo = store["k1"]
|
|
197
|
+
|
|
198
|
+
to retrieve the value at key "k1".
|
|
199
|
+
|
|
200
|
+
The advantage of ``get_value`` is that if the conversion is possible, the
|
|
201
|
+
user can specify a different type to return than the type that was
|
|
202
|
+
originally stored in the key-value store. For example, these two lines
|
|
203
|
+
demonstrate a user saving an integer to the key-value store and then
|
|
204
|
+
retrieving it as a string::
|
|
205
|
+
|
|
206
|
+
store["k2"] = 42
|
|
207
|
+
val = store.get_value("k2", str) # val now equals "42"
|
|
208
|
+
|
|
209
|
+
In general, a :class:`pntos.api.KeyValueStore` is generated by a :class:`pntos.api.Registry` and not directly by
|
|
210
|
+
other code. The :class:`pntos.api.Registry` will return key/value stores on demand, utilizing the data
|
|
211
|
+
backing store chosen by the plugin to store data (either ephemerally in memory or permanently in
|
|
212
|
+
persistent storage). In general, it is only valid to call the getters/setters on a
|
|
213
|
+
:class:`pntos.api.KeyValueStore` during a batch operation. See :class:`pntos.api.Registry` for more information.
|
|
214
|
+
|
|
215
|
+
Attributes:
|
|
216
|
+
data_format (KeyValueStoreDataFormat): The data format that is used by the :meth:`set_raw`
|
|
217
|
+
and :meth:`get_raw` methods.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
@abstractmethod
|
|
221
|
+
def keys(self) -> list[str] | None:
|
|
222
|
+
"""
|
|
223
|
+
Get the array of keys which currently exist in this store.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
list[str] | None: Returns the keys in the store or ``None`` if no keys are present.
|
|
227
|
+
"""
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def __contains__(self, key: str) -> bool:
|
|
232
|
+
"""
|
|
233
|
+
Check whether or not a given key exists in the store.
|
|
234
|
+
|
|
235
|
+
Particularly, this function allows the
|
|
236
|
+
user to use python "if in" statements::
|
|
237
|
+
|
|
238
|
+
if key in store:
|
|
239
|
+
...
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
key (str)
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
bool
|
|
246
|
+
"""
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
@abstractmethod
|
|
250
|
+
def get_value(
|
|
251
|
+
self, key: str, value_type: type[RegistryValueType]
|
|
252
|
+
) -> RegistryValueType | None:
|
|
253
|
+
"""
|
|
254
|
+
Get the value stored at ``key`` with return type ``value_type``.
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
For example, to access altitude in a :class:`pntos.api.KeyValueStore` named ``kv_store`` as an
|
|
258
|
+
integer::
|
|
259
|
+
|
|
260
|
+
altitude = kv_store.get_value("altitude", int)
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
key (str)
|
|
264
|
+
value_type (type[RegistryValueType])
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
:class:`pntos.api.RegistryValueType` | None: Returns ``None`` if the key is not available. The return is
|
|
268
|
+
guaranteed to not be ``None`` if called with a valid key (which can be checked with
|
|
269
|
+
:meth:`pntos.api.KeyValueStore.__contains__`) and if the store can convert the value to the requested type.
|
|
270
|
+
"""
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
@abstractmethod
|
|
274
|
+
def __getitem__(self, key: str) -> RegistryValueTypeUnion | None:
|
|
275
|
+
"""
|
|
276
|
+
Gets an item in the key-value store.
|
|
277
|
+
|
|
278
|
+
This function enables python bracket indexing to return a value from the
|
|
279
|
+
store for a given key.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
For example::
|
|
283
|
+
|
|
284
|
+
if key in store:
|
|
285
|
+
foo = store[key] # foo is now the value stored at key
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
key (str)
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
:class:`RegistryValueTypeUnion` | None: This is guaranteed to return a value
|
|
292
|
+
of the same type as :meth:`get_type` returns for the given key, if
|
|
293
|
+
the key exists. If :meth:`get_type` returns ``None`` (indicating
|
|
294
|
+
type information is not available), then this method will only
|
|
295
|
+
return values as strings or Messages.
|
|
296
|
+
"""
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
@abstractmethod
|
|
300
|
+
def get_raw(self, key: str | None = None) -> bytes | None:
|
|
301
|
+
"""
|
|
302
|
+
Get the value for the given ``key`` as an array of bytes.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
key (str | None, optional)
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
bytes | None: The return format will conform to the definition in
|
|
309
|
+
:attr:`pntos.api.KeyValueStore.data_format`. Returns ``None`` if the given key is not available.
|
|
310
|
+
The return is guaranteed to not be ``None`` if called with a valid key, which can be
|
|
311
|
+
checked with :meth:`pntos.api.KeyValueStore.__contains__`::
|
|
312
|
+
|
|
313
|
+
if key in store:
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
If ``key`` is ``None``, then this function will return all
|
|
317
|
+
of the keys and values in the group passed to :meth:`pntos.api.Registry.batch_start` and will be
|
|
318
|
+
formatted to conform to keys and values as defined in :attr:`pntos.api.KeyValueStore.data_format`.
|
|
319
|
+
"""
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
@abstractmethod
|
|
323
|
+
def set_value(self, key: str, value: RegistryValueTypeUnion) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Set the given key to the provided value.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
key (str)
|
|
329
|
+
value (RegistryValueTypeUnion): Can be of any type specified by
|
|
330
|
+
:class:`pntos.api.RegistryValueTypeUnion`.
|
|
331
|
+
"""
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
@abstractmethod
|
|
335
|
+
def __setitem__(self, key: str, value: RegistryValueTypeUnion) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Set an item in the key-value store.
|
|
338
|
+
|
|
339
|
+
Same functionality as :meth:`set_value`, but allows python bracket
|
|
340
|
+
indexing to set values in the store.
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
For example::
|
|
344
|
+
|
|
345
|
+
store["key"] = 42
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
key (str)
|
|
349
|
+
value (RegistryValueTypeUnion): Can be of any type specified by
|
|
350
|
+
:class:`pntos.api.RegistryValueTypeUnion`.
|
|
351
|
+
"""
|
|
352
|
+
pass
|
|
353
|
+
|
|
354
|
+
@abstractmethod
|
|
355
|
+
def set_raw(self, key: str | None, bytes: bytes) -> None:
|
|
356
|
+
"""
|
|
357
|
+
Set the given key to the provided value.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
key (str | None): If ``key`` is ``None``, then the contents of ``bytes`` must include
|
|
361
|
+
both keys and values and must be formatted to conform to
|
|
362
|
+
:attr:`pntos.api.KeyValueStore.data_format`. ``bytes`` will then be used to set the
|
|
363
|
+
corresponding keys and values in the group passed to :meth:`pntos.api.Registry.batch_start`.
|
|
364
|
+
bytes (bytes): Must be formatted to conform to the definition of a value in
|
|
365
|
+
:attr:`pntos.api.KeyValueStore.data_format`.
|
|
366
|
+
"""
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
@abstractmethod
|
|
370
|
+
def remove_key(self, key: str) -> bool:
|
|
371
|
+
"""
|
|
372
|
+
Remove the given key from the registry.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
key (str)
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
bool: ``True`` if ``key`` is successfully removed, and ``False`` otherwise. Keys may
|
|
379
|
+
fail to be removed if the key does not currently exist, or the backend is unable to
|
|
380
|
+
remove the key.
|
|
381
|
+
"""
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
@abstractmethod
|
|
385
|
+
def batch_end(self) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Ends a batch operation.
|
|
388
|
+
|
|
389
|
+
Ends a batch operation started with a :meth:`pntos.api.Registry.batch_start` call. After calling this,
|
|
390
|
+
the user should not use the :class:`pntos.api.KeyValueStore` they received from
|
|
391
|
+
:meth:`pntos.api.Registry.batch_start` again without calling :meth:`pntos.api.KeyValueStore.batch_restart` on the
|
|
392
|
+
:class:`pntos.api.KeyValueStore`.
|
|
393
|
+
|
|
394
|
+
If keys in the batch were acted upon with :meth:`set_permanent` turned on, and the plugin
|
|
395
|
+
supports permanent storage, this call will save changes to permanent storage if
|
|
396
|
+
:meth:`set_permanent` is ``True`` during the call to :meth:`batch_end`. Enacts equivalent of
|
|
397
|
+
``set_permanent(self,false)`` before return. If any :meth:`request_notify` observers have
|
|
398
|
+
been added, they will be processed prior to this call returning.
|
|
399
|
+
|
|
400
|
+
Example:
|
|
401
|
+
Example 1: Flushing to permanent storage on :meth:`batch_end`::
|
|
402
|
+
|
|
403
|
+
store = registry.batch_start("group")
|
|
404
|
+
...work...
|
|
405
|
+
store.set_permanent(true) # if not disabled, flush on batch_end
|
|
406
|
+
...work...
|
|
407
|
+
store.batch_end() # will flush values
|
|
408
|
+
|
|
409
|
+
Example 2: Not flushing to permanent storage on :meth:`batch_end`::
|
|
410
|
+
|
|
411
|
+
store = registry.batch_start("group")
|
|
412
|
+
...work...
|
|
413
|
+
store.set_permanent(true) # tag some values
|
|
414
|
+
...work...
|
|
415
|
+
store.set_permanent(false) # do not flush on batch_end
|
|
416
|
+
store.batch_end() # will not flush values
|
|
417
|
+
|
|
418
|
+
In the second example above, values set with "set" methods after the initial
|
|
419
|
+
:meth:`set_permanent` call are still stored for potential saving to permanent storage.
|
|
420
|
+
"""
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
@abstractmethod
|
|
424
|
+
def batch_restart(self) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Restarts a batch.
|
|
427
|
+
|
|
428
|
+
Restarts a batch that was previously started with :meth:`pntos.api.Registry.batch_start` and
|
|
429
|
+
subsequently ended with :meth:`pntos.api.KeyValueStore.batch_end`. This method is likely
|
|
430
|
+
much more efficient than :meth:`pntos.api.Registry.batch_start` (depending on the registry
|
|
431
|
+
implementation) as the :meth:`pntos.api.Registry.batch_start` method must find the store
|
|
432
|
+
again given the group name.
|
|
433
|
+
|
|
434
|
+
Note:
|
|
435
|
+
While a batch is active, access to the store may be denied to other users. Thus a user
|
|
436
|
+
should endeavour to call :meth:`batch_end` as soon as possible after they are done
|
|
437
|
+
getting/setting values in the returned :class:`pntos.api.KeyValueStore`.
|
|
438
|
+
"""
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
@abstractmethod
|
|
442
|
+
def request_notify(
|
|
443
|
+
self,
|
|
444
|
+
key: str | None,
|
|
445
|
+
callback: Callable[[str, list[str], 'KeyValueStore'], None],
|
|
446
|
+
) -> bool:
|
|
447
|
+
"""
|
|
448
|
+
Register a callback which gets called each time a key in the store is updated.
|
|
449
|
+
|
|
450
|
+
Allows plugins to respond asynchronously to parameter updates. Returns ``True`` if the
|
|
451
|
+
notifier was successfully registered, and ``False`` if the store is unable to notify the
|
|
452
|
+
requester. If ``key`` is ``None``, then the callback will be invoked when any key in the
|
|
453
|
+
batch's group is modified. Otherwise, the callback will only be invoked when the given key
|
|
454
|
+
is modified.
|
|
455
|
+
|
|
456
|
+
Note:
|
|
457
|
+
The callback must not attempt to set any values inside the :class:`pntos.api.KeyValueStore`, as
|
|
458
|
+
the callback is likely being invoked during the processing of another operation. The
|
|
459
|
+
callback should endeavour to store off the updated keys/values as quickly as possible
|
|
460
|
+
and return, leaving the processing of the updates to another context or thread when
|
|
461
|
+
possible. Calling :class:`pntos.api.Mediator` within the callback may be disallowed by the
|
|
462
|
+
controller implementation and lead to undefined behavior.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
key (str | None)
|
|
466
|
+
callback (Callable[[str, list[str], KeyValueStore], None]): A
|
|
467
|
+
function with a function definition compatible with::
|
|
468
|
+
|
|
469
|
+
def my_callback(group: str, modified_keys: list[str], kv: KeyValueStore) -> None:
|
|
470
|
+
...
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
bool: ``True`` if the notifier was successfully registered, and ``False`` if the store
|
|
474
|
+
is unable to notify the requester.
|
|
475
|
+
"""
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
@abstractmethod
|
|
479
|
+
def remove_notify(
|
|
480
|
+
self,
|
|
481
|
+
key: str | None,
|
|
482
|
+
callback: Callable[[str, list[str], 'KeyValueStore'], None],
|
|
483
|
+
) -> bool:
|
|
484
|
+
"""
|
|
485
|
+
Removes a notification as requested by :meth:`request_notify`.
|
|
486
|
+
|
|
487
|
+
The group and callback must match the parameters passed to :meth:`request_notify`
|
|
488
|
+
in order to successfully remove a callback.
|
|
489
|
+
|
|
490
|
+
Note:
|
|
491
|
+
This will remove all matching callbacks that have a matching group and callback. If a
|
|
492
|
+
user registers the same callback twice this will remove both.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
key (str | None)
|
|
496
|
+
callback (Callable[[str, list[str], KeyValueStore], None])
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
bool: ``True`` if removal was successful and ``False`` if it was not. ``False`` will be
|
|
500
|
+
returned if a callback did not exist for the group.
|
|
501
|
+
"""
|
|
502
|
+
pass
|
|
503
|
+
|
|
504
|
+
@abstractmethod
|
|
505
|
+
def __delitem__(self, key: str) -> None:
|
|
506
|
+
"""
|
|
507
|
+
Delete the item with the specified key.
|
|
508
|
+
|
|
509
|
+
Must remove the given key and the associated value from the store, along
|
|
510
|
+
with any callbacks or permanency settings.
|
|
511
|
+
|
|
512
|
+
This enables the python ``del`` operator.
|
|
513
|
+
|
|
514
|
+
Example:
|
|
515
|
+
For example::
|
|
516
|
+
|
|
517
|
+
for key in keys_to_remove:
|
|
518
|
+
del store[key]
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
key (str)
|
|
522
|
+
"""
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
@abstractmethod
|
|
526
|
+
def __iter__(self) -> Iterator[str]:
|
|
527
|
+
"""
|
|
528
|
+
Return an iterator over the keys in the store.
|
|
529
|
+
|
|
530
|
+
This allows for easy iteration over the key-value store.
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
For example::
|
|
534
|
+
|
|
535
|
+
for key in store:
|
|
536
|
+
...
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Iterator
|
|
540
|
+
"""
|
|
541
|
+
pass
|
|
542
|
+
|
|
543
|
+
@abstractmethod
|
|
544
|
+
def __len__(self) -> int:
|
|
545
|
+
"""
|
|
546
|
+
Return the number of items in the store.
|
|
547
|
+
|
|
548
|
+
This allows the use of python's ``len`` function.
|
|
549
|
+
|
|
550
|
+
Example:
|
|
551
|
+
For example::
|
|
552
|
+
|
|
553
|
+
num_elements = len(key_val_store)
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
int
|
|
557
|
+
"""
|
|
558
|
+
pass
|
|
559
|
+
|
|
560
|
+
@abstractmethod
|
|
561
|
+
def clear(self) -> None:
|
|
562
|
+
"""Remove all items from key-value store."""
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
@abstractmethod
|
|
566
|
+
def set_permanent(self, permanent: bool) -> bool:
|
|
567
|
+
"""
|
|
568
|
+
Tag values modified with "set" methods as permanently stored.
|
|
569
|
+
|
|
570
|
+
Configure the :class:`pntos.api.KeyValueStore` to tag values modified with "set" methods as
|
|
571
|
+
permanently stored (as opposed to ephemerally stored in memory). Only values acted upon with
|
|
572
|
+
"set" methods while :meth:`set_permanent` is ``True`` will be tagged. Values will be flushed
|
|
573
|
+
according to registry configuration settings or per :meth:`batch_end` API. Returns the value
|
|
574
|
+
of the permanent storage configuration. Callers should check this to verify if the set was
|
|
575
|
+
successful.
|
|
576
|
+
|
|
577
|
+
Example:
|
|
578
|
+
Tagging specific keys to be permanently stored::
|
|
579
|
+
|
|
580
|
+
store: PntosKeyValueStore = registry.batch_start("group")
|
|
581
|
+
store.set_value("key1",1234.56) # does not tag this value as permanently stored
|
|
582
|
+
store.set_permanent(True) # start tagging set calls as permanently stored
|
|
583
|
+
store.set_value("key1",987.65)
|
|
584
|
+
store.set_value("key2",123) # both key1 and key2 values tagged
|
|
585
|
+
store.set_permanent(False) # disable permanent storage
|
|
586
|
+
store.set_value("key1",456.78) # key1 = 456.78 is value of key1 in store
|
|
587
|
+
# key1 = 987.65 tagged to be permanently stored
|
|
588
|
+
# key2 = 123 tagged to be permanently stored
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
permanent (bool)
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
bool: The value of the permanent storage configuration. Callers
|
|
595
|
+
should check this to verify if the set was successful.
|
|
596
|
+
"""
|
|
597
|
+
pass
|
|
598
|
+
|
|
599
|
+
@abstractmethod
|
|
600
|
+
def values(self) -> list[RegistryValueTypeUnion]:
|
|
601
|
+
"""
|
|
602
|
+
Returns a list of the key-value store's values.
|
|
603
|
+
|
|
604
|
+
This method is useful for unloading all values from a key-value store.
|
|
605
|
+
|
|
606
|
+
Example:
|
|
607
|
+
For example::
|
|
608
|
+
|
|
609
|
+
def get_mean_of_kv_store(kv: KeyValueStore) -> float | None:
|
|
610
|
+
'''Returns the mean of a key-value store if all values are int or float'''
|
|
611
|
+
values = kv.values()
|
|
612
|
+
if all(isinstance(x, (int, float)) for x in values):
|
|
613
|
+
return sum(values) / len(values)
|
|
614
|
+
return None
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
list[RegistryValueTypeUnion]: A list of all values in the store.
|
|
618
|
+
|
|
619
|
+
"""
|
|
620
|
+
pass
|
|
621
|
+
|
|
622
|
+
@abstractmethod
|
|
623
|
+
def items(self) -> list[tuple[str, RegistryValueTypeUnion]]:
|
|
624
|
+
"""
|
|
625
|
+
Returns a list of the items (key-value pairs) in the store.
|
|
626
|
+
|
|
627
|
+
This method is useful for when you need to iterate over both keys and
|
|
628
|
+
values in a dictionary.
|
|
629
|
+
|
|
630
|
+
Example:
|
|
631
|
+
For example::
|
|
632
|
+
|
|
633
|
+
for key, value in my_dict.items()
|
|
634
|
+
printf('Key: {key}, Value: {value}')
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
list[tuple[str, RegistryValueTypeUnion]]: A list of the key-value pairs (as
|
|
638
|
+
tuples) in the store.
|
|
639
|
+
"""
|
|
640
|
+
pass
|
|
641
|
+
|
|
642
|
+
@abstractmethod
|
|
643
|
+
def get_type(self, key: str) -> type[RegistryValueTypeUnion] | None:
|
|
644
|
+
"""
|
|
645
|
+
Returns the type of a value in the KeyValueStore.
|
|
646
|
+
|
|
647
|
+
This is helpful for situations when it is advantageous to know the
|
|
648
|
+
type of a value in the store without need for the value itself.
|
|
649
|
+
|
|
650
|
+
Example:
|
|
651
|
+
For example, it might be useful for these situations where different
|
|
652
|
+
callbacks are needed depending on what type is in the store::
|
|
653
|
+
|
|
654
|
+
if key in kv_store:
|
|
655
|
+
val_type = kv_store.get_type(key)
|
|
656
|
+
if val_type is int:
|
|
657
|
+
kv_store.request_notify(key, int_callback)
|
|
658
|
+
elif val_type is Message:
|
|
659
|
+
kv_store.request_notify(key, message_callback)
|
|
660
|
+
else:
|
|
661
|
+
kv_store.request_notify(key, generic_callback)
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
key (str)
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
type[RegistryValueTypeUnion] | None: If key exists in registry,
|
|
668
|
+
this is guaranteed to return the return type of :meth:`pntos.api.KeyValueStore.__getitem__`
|
|
669
|
+
for the given key. Else, it returns ``None`` if type information is
|
|
670
|
+
not available or key does not exist in registry.
|
|
671
|
+
"""
|
|
672
|
+
pass
|
|
673
|
+
|
|
674
|
+
data_format: KeyValueStoreDataFormat
|
|
675
|
+
"""Defines the underlying format which the data in the key-value store will be stored as.
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
############# BEGIN FINAL METHODS - THESE SHOULD NOT BE OVERRIDDEN #################
|
|
679
|
+
|
|
680
|
+
@final
|
|
681
|
+
def __enter__(self) -> 'KeyValueStore':
|
|
682
|
+
"""
|
|
683
|
+
Allows for use of the ``Registry`` through Python ``with`` statements.
|
|
684
|
+
|
|
685
|
+
Example:
|
|
686
|
+
Here is an example of how the registry can work using a ``with`` statement:
|
|
687
|
+
|
|
688
|
+
with registry.batch_start('my_group') as store:
|
|
689
|
+
store['my_val'] = 42
|
|
690
|
+
# store.batch_end() called automatically when leaving 'with' block
|
|
691
|
+
store['my_val'] = 73 # Invalid: store use outside of batch
|
|
692
|
+
|
|
693
|
+
This is hard-coded context behavior - do not implement this method in child
|
|
694
|
+
classes.
|
|
695
|
+
"""
|
|
696
|
+
return super().__enter__()
|
|
697
|
+
|
|
698
|
+
@final
|
|
699
|
+
def __exit__(
|
|
700
|
+
self,
|
|
701
|
+
exc_type: type[BaseException] | None,
|
|
702
|
+
exc: BaseException | None,
|
|
703
|
+
tb: TracebackType | None,
|
|
704
|
+
) -> bool | None:
|
|
705
|
+
"""
|
|
706
|
+
Allows for use of the ``Registry`` through Python ``with`` statements.
|
|
707
|
+
|
|
708
|
+
Example:
|
|
709
|
+
Here is an example of how the registry can work using a ``with`` statement:
|
|
710
|
+
|
|
711
|
+
with registry.batch_start('my_group') as store:
|
|
712
|
+
store['my_val'] = 42
|
|
713
|
+
# store.batch_end() called automatically when leaving 'with' block
|
|
714
|
+
store['my_val'] = 73 # Invalid: store use outside of batch
|
|
715
|
+
|
|
716
|
+
This is hard-coded context behavior - do not implement this method in child
|
|
717
|
+
classes.
|
|
718
|
+
"""
|
|
719
|
+
self.batch_end()
|
|
720
|
+
return None
|
|
721
|
+
|
|
722
|
+
################################ END FINAL METHODS #################################
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
class Registry(ABC):
|
|
726
|
+
"""
|
|
727
|
+
A registry of key/value data which is organized by (string) groups.
|
|
728
|
+
|
|
729
|
+
In order to get/set a key in the registry, one must call :meth:`pntos.api.Registry.batch_start` with the
|
|
730
|
+
group the key is stored under and then use the resulting :class:`pntos.api.KeyValueStore` to get/set the
|
|
731
|
+
key/value pair. When one is done accessing keys in the :class:`pntos.api.KeyValueStore`, they must call
|
|
732
|
+
:meth:`pntos.api.KeyValueStore.batch_end`. It is not permitted to access any member inside the
|
|
733
|
+
:class:`pntos.api.KeyValueStore` after a batch has ended. If a user has ended a batch and then desires to
|
|
734
|
+
access the :class:`pntos.api.KeyValueStore` again, they may use the :meth:`pntos.api.KeyValueStore.batch_restart`
|
|
735
|
+
method.
|
|
736
|
+
"""
|
|
737
|
+
|
|
738
|
+
@abstractmethod
|
|
739
|
+
def batch_start(self, group: str) -> KeyValueStore:
|
|
740
|
+
"""
|
|
741
|
+
Begin a batch get/set operation.
|
|
742
|
+
|
|
743
|
+
Begin a batch get/set operation wherein the user may make any number of modifications to the
|
|
744
|
+
keys/values in the ``group``. The registry implementation may wait to batch these requests
|
|
745
|
+
until :meth:`pntos.api.KeyValueStore.batch_end` is called for better performance. For example, a lock
|
|
746
|
+
may be obtained at the beginning of a :meth:`batch_start` and not released until a
|
|
747
|
+
:meth:`pntos.api.KeyValueStore.batch_end` call is encountered. Thus, a plugin that calls
|
|
748
|
+
:meth:`batch_start` should endeavour to make its calls to the ``set_``, ``get_``, and
|
|
749
|
+
``register`` methods as quickly as possible and call :meth:`pntos.api.KeyValueStore.batch_end`
|
|
750
|
+
immediately, as doing otherwise may be locking other plugins out of access to the registry
|
|
751
|
+
(depending on the registry plugin implementation). If a plugin supports
|
|
752
|
+
:meth:`pntos.api.KeyValueStore.request_notify`, then notifications of updates may be suspended until
|
|
753
|
+
the batch ends. After a batch is ended, the returned :class:`pntos.api.KeyValueStore` can still be
|
|
754
|
+
used to access the store via :meth:`pntos.api.KeyValueStore.batch_restart`.
|
|
755
|
+
|
|
756
|
+
Note:
|
|
757
|
+
While a batch is active, access to the store may be denied to other users. Thus a user
|
|
758
|
+
should endeavour to call :meth:`pntos.api.KeyValueStore.batch_end` as soon as possible after they
|
|
759
|
+
are done getting/setting values in the returned :class:`pntos.api.KeyValueStore`.
|
|
760
|
+
|
|
761
|
+
Args:
|
|
762
|
+
group (str)
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
KeyValueStore
|
|
766
|
+
"""
|
|
767
|
+
pass
|
|
768
|
+
|
|
769
|
+
@property
|
|
770
|
+
@abstractmethod
|
|
771
|
+
def group_array(self) -> list[str] | None:
|
|
772
|
+
"""
|
|
773
|
+
Get the array of groups which currently exist.
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
list[str] | None: The array of groups which currently exists. Returns ``None`` if no groups
|
|
777
|
+
exist.
|
|
778
|
+
"""
|
|
779
|
+
pass
|
|
780
|
+
|
|
781
|
+
@abstractmethod
|
|
782
|
+
def has_group(self, group: str) -> bool:
|
|
783
|
+
"""
|
|
784
|
+
Checks whether or not a given group has had any values added to it (for any key).
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
group (str)
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
bool
|
|
791
|
+
"""
|
|
792
|
+
pass
|
|
793
|
+
|
|
794
|
+
@abstractmethod
|
|
795
|
+
def request_notify_new_group(self, callback: Callable[[str], None]) -> bool:
|
|
796
|
+
"""
|
|
797
|
+
Register a callback which gets called each time a new group is made in the registry.
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
callback (Callable[[str], None])
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
bool: ``True`` if the notifier was successfully registered, and ``False`` if the
|
|
804
|
+
registry is unable to notify the requester.
|
|
805
|
+
"""
|
|
806
|
+
pass
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
class Mediator(ABC):
|
|
810
|
+
"""
|
|
811
|
+
A set of callbacks which are handed to a pntOS plugin upon initialization.
|
|
812
|
+
|
|
813
|
+
When a plugin is first initialized into pntOS, it is guaranteed that the plugin will be passed
|
|
814
|
+
an instance of this struct via an invocation of :meth:`pntos.api.CommonPlugin.init_plugin` (See
|
|
815
|
+
:class:`pntos.api.CommonPlugin` for more information). The plugin may then use the set of function calls
|
|
816
|
+
in this class to make requests of the :class:`pntos.api.ControllerPlugin`.
|
|
817
|
+
|
|
818
|
+
All of the functions on this class (and any returned values from those functions) are guaranteed
|
|
819
|
+
to be thread-safe for use by all plugins. Thus, after a pntOS plugin has received a copy of a
|
|
820
|
+
:class:`pntos.api.Mediator` it can freely call the functions contained therein without doing any explicit
|
|
821
|
+
locking. This thread safety is implemented by the :class:`pntos.api.ControllerPlugin` when it creates the
|
|
822
|
+
mediator before passing them to other plugins.
|
|
823
|
+
|
|
824
|
+
Callers must still take care to only call functions in :class:`pntos.api.Mediator` which they are not
|
|
825
|
+
themselves responsible for implementing. The details of which plugins are used in the
|
|
826
|
+
implementation of any particular function on this struct is decided by the
|
|
827
|
+
:class:`pntos.api.ControllerPlugin`, and thus is implementation specific to the :class:`pntos.api.ControllerPlugin`
|
|
828
|
+
used.
|
|
829
|
+
|
|
830
|
+
Attributes:
|
|
831
|
+
registry (Registry): A :class:`pntos.api.Registry` object that can be used to update keys/values in
|
|
832
|
+
the pntOS global registry.
|
|
833
|
+
"""
|
|
834
|
+
|
|
835
|
+
@property
|
|
836
|
+
@abstractmethod
|
|
837
|
+
def filter_description_list(self) -> list[str]:
|
|
838
|
+
"""
|
|
839
|
+
Request a list of strings describing the solutions available.
|
|
840
|
+
|
|
841
|
+
One of these description strings may be used when calling :meth:`request_solutions`. For
|
|
842
|
+
consistency, these strings should adhere to the following conventions:
|
|
843
|
+
|
|
844
|
+
- Strings should be upper case and have words and acronyms separated by underscores
|
|
845
|
+
(``UPPER_SNAKE_CASE``).
|
|
846
|
+
- Strings should contain the substring ``BEST`` when they represent the primary solution.
|
|
847
|
+
- Strings should contain the substring ``DEAD_RECKONING`` when they represent a solution
|
|
848
|
+
suitable for estimating relative motion or rotation over a period of time. This solution
|
|
849
|
+
may drift more than ``BEST`` solutions, as the goal is to allow a user to get an estimate
|
|
850
|
+
of the relative motion between different times. In the calculation of this solution, some
|
|
851
|
+
sensor measurement might be excluded. For example, a system with an IMU might provide a
|
|
852
|
+
``DEAD_RECKONING`` solution which is the solution from its free-running inertial
|
|
853
|
+
mechanization, with resets disabled during the time intervals between ``solution_times``
|
|
854
|
+
(but resets applied before all of the ``solution_times``).
|
|
855
|
+
- Strings should include a substring indicating the type of solution returned. This
|
|
856
|
+
substring should contain the string-equivalent to the corresponding ASPN message class
|
|
857
|
+
name, converted to UPPER_SNAKE_CASE, followed by the string ``_ESTIMATE``. This allows the
|
|
858
|
+
user to perform substring matching without a risk of getting a false positive match from a
|
|
859
|
+
type whose string would be a subset of another type.
|
|
860
|
+
|
|
861
|
+
Example:
|
|
862
|
+
If the primary solution is an ASPN PVA then the string
|
|
863
|
+
``MY_BEST_ASPN_MEASUREMENT_POSITION_VELOCITY_ATTITUDE_ESTIMATE`` would fulfill the
|
|
864
|
+
convention.
|
|
865
|
+
|
|
866
|
+
These conventions allow the user to identify their desired type of solution using substring
|
|
867
|
+
matching.
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
list[str]: A list of strings describing the solutions available.
|
|
871
|
+
"""
|
|
872
|
+
pass
|
|
873
|
+
|
|
874
|
+
@abstractmethod
|
|
875
|
+
def request_solutions(
|
|
876
|
+
self,
|
|
877
|
+
solution_times: list[TypeTimestamp],
|
|
878
|
+
filter_description: str | None = None,
|
|
879
|
+
) -> list[Message | None] | None:
|
|
880
|
+
"""
|
|
881
|
+
Request filtering solutions at the times specified in the array ``solution_times``.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
solution_times (list[TypeTimestamp]): The times at which to return solutions.
|
|
885
|
+
filter_description (str | None, optional): To select which filter(s) to request
|
|
886
|
+
solutions from, enter a valid filter description string in ``filter_description``.
|
|
887
|
+
Valid filter description strings can be obtained by calling
|
|
888
|
+
:attr:`filter_description_list`. Passing in ``None`` will provide a result
|
|
889
|
+
specific to a particular implementation. When ``filter_description`` is ``None``,
|
|
890
|
+
the implementation should endeavor to return its best solution.
|
|
891
|
+
|
|
892
|
+
Returns:
|
|
893
|
+
list[Message | None] | None: An array of messages containing the filter solutions for the requested
|
|
894
|
+
``solution_times``. Some entries may be ``None`` if they are unavailable at the corresponding time
|
|
895
|
+
in ``solution_times``. The returned :class:`pntos.api.Message` array may be ``None`` if
|
|
896
|
+
``filter_description`` is invalid.
|
|
897
|
+
"""
|
|
898
|
+
pass
|
|
899
|
+
|
|
900
|
+
@abstractmethod
|
|
901
|
+
def process_pntos_message(self, message: Message) -> None:
|
|
902
|
+
"""
|
|
903
|
+
Send a new message to the system for arbitrary processing.
|
|
904
|
+
|
|
905
|
+
For example, this function is useful for plugins who have just received new
|
|
906
|
+
sensor data that they wish to relay to the system to be used in a sensor fusion
|
|
907
|
+
solution.
|
|
908
|
+
|
|
909
|
+
Args:
|
|
910
|
+
message (Message)
|
|
911
|
+
"""
|
|
912
|
+
pass
|
|
913
|
+
|
|
914
|
+
@abstractmethod
|
|
915
|
+
def broadcast_aspn_message(
|
|
916
|
+
self,
|
|
917
|
+
message: Message,
|
|
918
|
+
transport: str | None = None,
|
|
919
|
+
destination_identifier: str | None = None,
|
|
920
|
+
) -> None:
|
|
921
|
+
"""
|
|
922
|
+
Request that pntOS broadcast the provided message out to the network.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
message (Message)
|
|
926
|
+
transport (str | None, optional): The identifier of a transport plugin that the message
|
|
927
|
+
should be routed to. The transport parameter should match the
|
|
928
|
+
:attr:`pntos.api.CommonPlugin.identifier` string of a
|
|
929
|
+
:class:`pntos.api.TransportPlugin` active in the system. If the transport parameter
|
|
930
|
+
is ``None``, this indicates that the message should be broadcast to all available
|
|
931
|
+
transports.
|
|
932
|
+
destination_identifier (str | None, optional): A transport-specific identifier that
|
|
933
|
+
allows transports to determine how to route the message. If the destination
|
|
934
|
+
transport has the concept of a channel or topic, ``destination_identifier`` should
|
|
935
|
+
be populated by the channel or topic. Otherwise, the identifier is populated in a
|
|
936
|
+
plugin-specific manner defined by the destination transport. If
|
|
937
|
+
``destination_identifier`` is ``None``, then the transport should output the message
|
|
938
|
+
in the "default" output channel/topic and route being used by pntOS.
|
|
939
|
+
"""
|
|
940
|
+
pass
|
|
941
|
+
|
|
942
|
+
@abstractmethod
|
|
943
|
+
def log_message(self, level: LoggingLevel, message: str) -> None:
|
|
944
|
+
"""
|
|
945
|
+
Log a message.
|
|
946
|
+
|
|
947
|
+
Send a loggable message to the system, to be logged through the current
|
|
948
|
+
logging infrastructure enabled (e.g. the console, a logfile, etc.).
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
level (LoggingLevel)
|
|
952
|
+
message (str)
|
|
953
|
+
"""
|
|
954
|
+
pass
|
|
955
|
+
|
|
956
|
+
registry: Registry
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
class CommonPlugin(ABC):
|
|
960
|
+
"""
|
|
961
|
+
Common definitions that all plugins must provide.
|
|
962
|
+
|
|
963
|
+
This structure should not be used directly (except in the case of a utility plugin), but instead
|
|
964
|
+
is composed as the first field on all of the concrete pntOS plugin structures. For example, the
|
|
965
|
+
transport plugin is specified as::
|
|
966
|
+
|
|
967
|
+
class TransportPlugin(CommonPlugin, ABC):
|
|
968
|
+
|
|
969
|
+
def init_plugin(...):
|
|
970
|
+
...init_plugin implementation...
|
|
971
|
+
|
|
972
|
+
def ...other function implementations...
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
Thus this class defines a set of functions and variables that all plugins have. The
|
|
976
|
+
:meth:`pntos.api.CommonPlugin.init_plugin` function is guaranteed to be called by pntOS when the plugin is
|
|
977
|
+
first loaded into memory by the system.
|
|
978
|
+
|
|
979
|
+
Example:
|
|
980
|
+
When defining a new transport plugin, the plugin writer is responsible for implementing all
|
|
981
|
+
fields on the :class:`pntos.api.TransportPlugin` class. Thus, the fields of the :class:`pntos.api.CommonPlugin`
|
|
982
|
+
nested on the :class:`pntos.api.TransportPlugin` are implemented by the plugin writer.
|
|
983
|
+
|
|
984
|
+
Attributes:
|
|
985
|
+
identifier (str): A string identifier uniquely identifying this plugin. This string will be
|
|
986
|
+
used to determine the unique space this plugin receives in the system config.
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
@abstractmethod
|
|
990
|
+
def init_plugin(
|
|
991
|
+
self,
|
|
992
|
+
plugin_resources_location: str | None = None,
|
|
993
|
+
mediator: Mediator | None = None,
|
|
994
|
+
) -> None:
|
|
995
|
+
"""
|
|
996
|
+
The first plugin method called; initializes the plugin.
|
|
997
|
+
|
|
998
|
+
A function that will be called by pntOS once and only once when it first initializes the
|
|
999
|
+
plugin before any other functions on the plugin are called. Here the plugin may do dynamic
|
|
1000
|
+
runtime initialization of its members, and is given the full path to the location of a data
|
|
1001
|
+
folder specific to the plugin, in case it needs to acquire additional files. An
|
|
1002
|
+
instance of :class:`pntos.api.Mediator` will be passed which the plugin should save off for later use.
|
|
1003
|
+
Whenever the plugin needs to make a request of pntOS, it should use one of the fields in the
|
|
1004
|
+
:class:`pntos.api.Mediator` instance received by the plugin in this function call.
|
|
1005
|
+
|
|
1006
|
+
Note:
|
|
1007
|
+
Implementation note: This inversion of control allows the controller to implement the
|
|
1008
|
+
:class:`pntos.api.Mediator` class, and abstracts away the return communication channel from the
|
|
1009
|
+
plugin to the rest of the system. Thus, the plugin need only implement :class:`pntos.api.Mediator`
|
|
1010
|
+
by simply saving a copy of the functions that the controller passes into it. Then, when
|
|
1011
|
+
the plugin later needs to make requests of the system, it may call a function in its
|
|
1012
|
+
copy of :class:`pntos.api.Mediator`, without needing any knowledge of how the controller
|
|
1013
|
+
implemented :class:`pntos.api.Mediator`. This allows controllers to implement arbitrary
|
|
1014
|
+
concurrency models, including single-threaded, multi-threaded, multi-process, and
|
|
1015
|
+
distributed computing.
|
|
1016
|
+
|
|
1017
|
+
Args:
|
|
1018
|
+
plugin_resources_location (str | None, optional): Specifies the location of the plugin's
|
|
1019
|
+
resources. The location is determined by the controller plugin, and therefore is
|
|
1020
|
+
controller implementation specific. Plugin implementers wishing to provide a
|
|
1021
|
+
resource to their plugin should consult the documentation of the controller to
|
|
1022
|
+
determine which location scheme will be passed into this function.
|
|
1023
|
+
mediator (Mediator | None, optional): ``None``-able if the plugin type being initialized
|
|
1024
|
+
is a :class:`pntos.api.ControllerPlugin`. Non-controller plugins may assume that the mediator
|
|
1025
|
+
parameter is not ``None``.
|
|
1026
|
+
"""
|
|
1027
|
+
pass
|
|
1028
|
+
|
|
1029
|
+
@abstractmethod
|
|
1030
|
+
def shutdown_plugin(self) -> None:
|
|
1031
|
+
"""
|
|
1032
|
+
A function that will be called by pntOS when it is done using the plugin.
|
|
1033
|
+
|
|
1034
|
+
Here the plugin should release any resources it has acquired. When this function call
|
|
1035
|
+
returns pntOS may only call the destructor function (it will not call any other functions of
|
|
1036
|
+
this plugin). The plugin may not call any function on any other plugin, mediator, or use any
|
|
1037
|
+
resource that was given to it by pntOS after it returns from this function.
|
|
1038
|
+
"""
|
|
1039
|
+
pass
|
|
1040
|
+
|
|
1041
|
+
identifier: str
|
|
1042
|
+
""" A string identifier uniquely identifying this plugin.
|
|
1043
|
+
|
|
1044
|
+
This string will be used to determine the unique space this plugin receives in the system
|
|
1045
|
+
config.
|
|
1046
|
+
"""
|