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.
@@ -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
+ """