runtimepy 5.14.2__py3-none-any.whl → 5.15.0__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.
- runtimepy/__init__.py +2 -2
- runtimepy/channel/__init__.py +1 -4
- runtimepy/channel/environment/__init__.py +93 -2
- runtimepy/channel/environment/create.py +16 -1
- runtimepy/channel/environment/sample.py +10 -2
- runtimepy/channel/registry.py +2 -3
- runtimepy/codec/protocol/base.py +34 -14
- runtimepy/codec/protocol/json.py +5 -3
- runtimepy/codec/system/__init__.py +6 -2
- runtimepy/control/source.py +1 -1
- runtimepy/data/404.md +16 -0
- runtimepy/data/base.yaml +3 -0
- runtimepy/data/css/bootstrap_extra.css +59 -44
- runtimepy/data/css/main.css +23 -4
- runtimepy/data/dummy_load.yaml +2 -2
- runtimepy/data/factories.yaml +1 -0
- runtimepy/data/js/classes/App.js +54 -2
- runtimepy/data/js/classes/ChannelTable.js +6 -8
- runtimepy/data/js/classes/Plot.js +9 -4
- runtimepy/data/js/classes/TabFilter.js +47 -9
- runtimepy/data/js/classes/TabInterface.js +106 -11
- runtimepy/data/js/classes/WindowHashManager.js +30 -15
- runtimepy/data/js/init.js +18 -1
- runtimepy/data/js/markdown_page.js +10 -0
- runtimepy/data/schemas/BitFields.yaml +9 -0
- runtimepy/data/schemas/RuntimeEnum.yaml +6 -0
- runtimepy/data/schemas/StructConfig.yaml +9 -1
- runtimepy/data/static/css/bootstrap-icons.min.css +4 -3
- runtimepy/data/static/css/bootstrap.min.css +3 -4
- runtimepy/data/static/css/fonts/bootstrap-icons.woff +0 -0
- runtimepy/data/static/css/fonts/bootstrap-icons.woff2 +0 -0
- runtimepy/data/static/js/bootstrap.bundle.min.js +5 -4
- runtimepy/data/static/js/webglplot.umd.min.js +2 -1
- runtimepy/data/static/svg/outline-dark.svg +22 -0
- runtimepy/data/static/svg/outline-light.svg +22 -0
- runtimepy/enum/__init__.py +13 -1
- runtimepy/enum/registry.py +13 -1
- runtimepy/message/__init__.py +3 -3
- runtimepy/mixins/logging.py +6 -1
- runtimepy/net/__init__.py +0 -2
- runtimepy/net/arbiter/info.py +36 -4
- runtimepy/net/arbiter/struct/__init__.py +3 -2
- runtimepy/net/connection.py +4 -5
- runtimepy/net/html/__init__.py +29 -11
- runtimepy/net/html/bootstrap/__init__.py +2 -2
- runtimepy/net/html/bootstrap/elements.py +44 -24
- runtimepy/net/html/bootstrap/tabs.py +18 -11
- runtimepy/net/http/__init__.py +3 -3
- runtimepy/net/http/request_target.py +3 -3
- runtimepy/net/mixin.py +4 -2
- runtimepy/net/server/__init__.py +16 -9
- runtimepy/net/server/app/__init__.py +1 -0
- runtimepy/net/server/app/create.py +3 -3
- runtimepy/net/server/app/env/__init__.py +28 -4
- runtimepy/net/server/app/env/settings.py +4 -7
- runtimepy/net/server/app/env/tab/controls.py +141 -27
- runtimepy/net/server/app/env/tab/html.py +68 -26
- runtimepy/net/server/app/env/widgets.py +115 -61
- runtimepy/net/server/app/landing_page.py +1 -1
- runtimepy/net/server/html.py +2 -2
- runtimepy/net/server/json.py +1 -1
- runtimepy/net/server/markdown.py +18 -12
- runtimepy/net/server/mux.py +29 -0
- runtimepy/net/stream/__init__.py +6 -5
- runtimepy/net/stream/base.py +4 -2
- runtimepy/net/tcp/connection.py +5 -3
- runtimepy/net/tcp/http/__init__.py +10 -9
- runtimepy/net/tcp/protocol.py +2 -2
- runtimepy/net/tcp/scpi/__init__.py +5 -2
- runtimepy/net/tcp/telnet/__init__.py +2 -1
- runtimepy/net/udp/connection.py +10 -6
- runtimepy/net/udp/protocol.py +5 -6
- runtimepy/net/udp/queue.py +5 -2
- runtimepy/net/udp/tftp/base.py +2 -1
- runtimepy/net/websocket/connection.py +50 -8
- runtimepy/primitives/array/__init__.py +7 -5
- runtimepy/primitives/base.py +3 -2
- runtimepy/primitives/field/__init__.py +35 -2
- runtimepy/primitives/field/fields.py +11 -2
- runtimepy/primitives/field/manager/base.py +19 -2
- runtimepy/primitives/serializable/base.py +5 -2
- runtimepy/primitives/serializable/fixed.py +5 -2
- runtimepy/primitives/serializable/prefixed.py +4 -1
- runtimepy/primitives/types/base.py +4 -1
- runtimepy/primitives/types/bounds.py +10 -4
- runtimepy/registry/__init__.py +20 -0
- runtimepy/registry/name.py +6 -0
- runtimepy/requirements.txt +2 -2
- runtimepy/ui/controls.py +20 -1
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/METADATA +6 -6
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/RECORD +95 -92
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/WHEEL +1 -1
- runtimepy/data/404.html +0 -7
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/licenses/LICENSE +0 -0
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/top_level.txt +0 -0
runtimepy/net/udp/connection.py
CHANGED
|
@@ -12,9 +12,11 @@ from typing import Any as _Any
|
|
|
12
12
|
from typing import Optional as _Optional
|
|
13
13
|
from typing import TypeVar as _TypeVar
|
|
14
14
|
|
|
15
|
+
# third-party
|
|
16
|
+
from vcorelib.io import BinaryMessage
|
|
17
|
+
|
|
15
18
|
# internal
|
|
16
19
|
from runtimepy.net import IpHost, get_free_socket, normalize_host
|
|
17
|
-
from runtimepy.net.connection import BinaryMessage as _BinaryMessage
|
|
18
20
|
from runtimepy.net.connection import Connection as _Connection
|
|
19
21
|
from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
20
22
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
@@ -86,11 +88,13 @@ class UdpConnection(_Connection, _TransportMixin):
|
|
|
86
88
|
|
|
87
89
|
@_abstractmethod
|
|
88
90
|
async def process_datagram(
|
|
89
|
-
self, data:
|
|
91
|
+
self, data: BinaryMessage, addr: tuple[str, int]
|
|
90
92
|
) -> bool:
|
|
91
93
|
"""Process a datagram."""
|
|
92
94
|
|
|
93
|
-
def sendto(
|
|
95
|
+
def sendto(
|
|
96
|
+
self, data: BinaryMessage, addr: IpHostTuplelike = None
|
|
97
|
+
) -> None:
|
|
94
98
|
"""Send to a specific address."""
|
|
95
99
|
|
|
96
100
|
try:
|
|
@@ -108,7 +112,7 @@ class UdpConnection(_Connection, _TransportMixin):
|
|
|
108
112
|
"""Enqueue a text message to send."""
|
|
109
113
|
self.sendto(data.encode(), addr=self.remote_address)
|
|
110
114
|
|
|
111
|
-
def send_binary(self, data:
|
|
115
|
+
def send_binary(self, data: BinaryMessage) -> None:
|
|
112
116
|
"""Enqueue a binary message to send."""
|
|
113
117
|
self.sendto(data, addr=self.remote_address)
|
|
114
118
|
|
|
@@ -216,7 +220,7 @@ class EchoUdpConnection(UdpConnection, _EchoConnection):
|
|
|
216
220
|
"""An echo connection for UDP."""
|
|
217
221
|
|
|
218
222
|
async def process_datagram(
|
|
219
|
-
self, data:
|
|
223
|
+
self, data: BinaryMessage, addr: tuple[str, int]
|
|
220
224
|
) -> bool:
|
|
221
225
|
"""Process a datagram."""
|
|
222
226
|
|
|
@@ -228,7 +232,7 @@ class NullUdpConnection(UdpConnection, _NullConnection):
|
|
|
228
232
|
"""A null UDP connection."""
|
|
229
233
|
|
|
230
234
|
async def process_datagram(
|
|
231
|
-
self, data:
|
|
235
|
+
self, data: BinaryMessage, addr: tuple[str, int]
|
|
232
236
|
) -> bool:
|
|
233
237
|
"""Process a datagram."""
|
|
234
238
|
return True
|
runtimepy/net/udp/protocol.py
CHANGED
|
@@ -6,14 +6,13 @@ A module implementing a DatagramProtocol for UdpConnection.
|
|
|
6
6
|
import asyncio as _asyncio
|
|
7
7
|
from asyncio import DatagramProtocol as _DatagramProtocol
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Tuple as _Tuple
|
|
10
9
|
|
|
11
10
|
# third-party
|
|
11
|
+
from vcorelib.io import BinaryMessage
|
|
12
12
|
from vcorelib.logging import LoggerMixin, LoggerType
|
|
13
13
|
from vcorelib.math import RateLimiter
|
|
14
14
|
|
|
15
15
|
# internal
|
|
16
|
-
from runtimepy.net.connection import BinaryMessage as _BinaryMessage
|
|
17
16
|
from runtimepy.net.connection import Connection as _Connection
|
|
18
17
|
|
|
19
18
|
|
|
@@ -26,13 +25,13 @@ class UdpQueueProtocol(_DatagramProtocol):
|
|
|
26
25
|
def __init__(self) -> None:
|
|
27
26
|
"""Initialize this protocol."""
|
|
28
27
|
|
|
29
|
-
self.queue: _asyncio.Queue[
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
self.queue: _asyncio.Queue[tuple[BinaryMessage, tuple[str, int]]] = (
|
|
29
|
+
_asyncio.Queue()
|
|
30
|
+
)
|
|
32
31
|
|
|
33
32
|
self.log_limiter = RateLimiter.from_s(1.0)
|
|
34
33
|
|
|
35
|
-
def datagram_received(self, data: bytes, addr:
|
|
34
|
+
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
|
36
35
|
"""Handle incoming data."""
|
|
37
36
|
self.queue.put_nowait((data, addr))
|
|
38
37
|
|
runtimepy/net/udp/queue.py
CHANGED
|
@@ -5,10 +5,13 @@ A module implementing a simple queue-based UDP interface.
|
|
|
5
5
|
# built-in
|
|
6
6
|
from asyncio import Queue
|
|
7
7
|
|
|
8
|
+
# third-party
|
|
9
|
+
from vcorelib.io import BinaryMessage
|
|
10
|
+
|
|
8
11
|
# internal
|
|
9
12
|
from runtimepy.net.udp.connection import UdpConnection
|
|
10
13
|
|
|
11
|
-
DatagramQueue = Queue[tuple[
|
|
14
|
+
DatagramQueue = Queue[tuple[BinaryMessage, tuple[str, int]]]
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class QueueUdpConnection(UdpConnection):
|
|
@@ -21,7 +24,7 @@ class QueueUdpConnection(UdpConnection):
|
|
|
21
24
|
self.datagrams = Queue()
|
|
22
25
|
|
|
23
26
|
async def process_datagram(
|
|
24
|
-
self, data:
|
|
27
|
+
self, data: BinaryMessage, addr: tuple[str, int]
|
|
25
28
|
) -> bool:
|
|
26
29
|
"""Process a datagram."""
|
|
27
30
|
self.datagrams.put_nowait((data, addr))
|
runtimepy/net/udp/tftp/base.py
CHANGED
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import BinaryIO, Callable
|
|
12
12
|
|
|
13
13
|
# third-party
|
|
14
|
+
from vcorelib.io import BinaryMessage
|
|
14
15
|
from vcorelib.math import metrics_time_ns
|
|
15
16
|
|
|
16
17
|
# internal
|
|
@@ -353,7 +354,7 @@ class BaseTftpConnection(UdpConnection):
|
|
|
353
354
|
self.endpoint(addr).handle_error(error_code, message)
|
|
354
355
|
|
|
355
356
|
async def process_datagram(
|
|
356
|
-
self, data:
|
|
357
|
+
self, data: BinaryMessage, addr: tuple[str, int]
|
|
357
358
|
) -> bool:
|
|
358
359
|
"""Process a datagram."""
|
|
359
360
|
|
|
@@ -20,6 +20,7 @@ from typing import Union as _Union
|
|
|
20
20
|
|
|
21
21
|
# third-party
|
|
22
22
|
from vcorelib.asyncio import log_exceptions as _log_exceptions
|
|
23
|
+
from vcorelib.io import BinaryMessage
|
|
23
24
|
import websockets
|
|
24
25
|
from websockets.asyncio.client import ClientConnection as _ClientConnection
|
|
25
26
|
from websockets.asyncio.server import Server as _Server
|
|
@@ -29,7 +30,7 @@ from websockets.exceptions import ConnectionClosed as _ConnectionClosed
|
|
|
29
30
|
|
|
30
31
|
# internal
|
|
31
32
|
from runtimepy.net import sockname as _sockname
|
|
32
|
-
from runtimepy.net.connection import
|
|
33
|
+
from runtimepy.net.connection import Connection
|
|
33
34
|
from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
34
35
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
35
36
|
from runtimepy.net.manager import ConnectionManager as _ConnectionManager
|
|
@@ -41,6 +42,17 @@ V = _TypeVar("V")
|
|
|
41
42
|
LOG = _getLogger(__name__)
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
async def websocket_connect(uri: str, **kwargs) -> _ClientConnection:
|
|
46
|
+
"""Attempt to connect a websocket interface."""
|
|
47
|
+
|
|
48
|
+
# Defaults.
|
|
49
|
+
kwargs.setdefault("use_ssl", uri.startswith("wss"))
|
|
50
|
+
|
|
51
|
+
return await getattr(websockets, "connect")( # type: ignore
|
|
52
|
+
uri, **handle_possible_ssl(**kwargs)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
44
56
|
class WebsocketConnection(Connection):
|
|
45
57
|
"""A simple websocket connection interface."""
|
|
46
58
|
|
|
@@ -54,6 +66,10 @@ class WebsocketConnection(Connection):
|
|
|
54
66
|
self.protocol = protocol
|
|
55
67
|
super().__init__(self.protocol.logger, **kwargs)
|
|
56
68
|
|
|
69
|
+
# Store connection-instantiation arguments (for connection restarting).
|
|
70
|
+
self._uri: str = ""
|
|
71
|
+
self._conn_kwargs: dict[str, _Any] = {}
|
|
72
|
+
|
|
57
73
|
async def _handle_connection_closed(
|
|
58
74
|
self, task: _Awaitable[V]
|
|
59
75
|
) -> _Optional[V]:
|
|
@@ -81,7 +97,9 @@ class WebsocketConnection(Connection):
|
|
|
81
97
|
|
|
82
98
|
async def _send_binay_message(self, data: BinaryMessage) -> None:
|
|
83
99
|
"""Send a binary message."""
|
|
84
|
-
await self._handle_connection_closed(
|
|
100
|
+
await self._handle_connection_closed(
|
|
101
|
+
self.protocol.send(data), # type: ignore
|
|
102
|
+
)
|
|
85
103
|
self.metrics.tx.increment(len(data))
|
|
86
104
|
|
|
87
105
|
async def close(self) -> None:
|
|
@@ -94,12 +112,30 @@ class WebsocketConnection(Connection):
|
|
|
94
112
|
) -> T:
|
|
95
113
|
"""Connect a client to an endpoint."""
|
|
96
114
|
|
|
97
|
-
|
|
115
|
+
inst = cls(await websocket_connect(uri, **kwargs), markdown=markdown)
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
# Stored for connection restart capability.
|
|
118
|
+
inst._uri = uri
|
|
119
|
+
inst._conn_kwargs = {**kwargs}
|
|
120
|
+
|
|
121
|
+
return inst
|
|
122
|
+
|
|
123
|
+
async def restart(self) -> bool:
|
|
124
|
+
"""
|
|
125
|
+
Reset necessary underlying state for this connection to 'process'
|
|
126
|
+
again.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
result = False
|
|
130
|
+
|
|
131
|
+
if self._uri:
|
|
132
|
+
with _suppress(TimeoutError, OSError):
|
|
133
|
+
self.protocol = await websocket_connect(
|
|
134
|
+
self._uri, **self._conn_kwargs
|
|
135
|
+
)
|
|
136
|
+
result = True
|
|
137
|
+
|
|
138
|
+
return result
|
|
103
139
|
|
|
104
140
|
@classmethod
|
|
105
141
|
@_asynccontextmanager
|
|
@@ -113,7 +149,13 @@ class WebsocketConnection(Connection):
|
|
|
113
149
|
async with getattr(websockets, "connect")(
|
|
114
150
|
uri, **handle_possible_ssl(**kwargs)
|
|
115
151
|
) as protocol:
|
|
116
|
-
|
|
152
|
+
inst = cls(protocol, markdown=markdown)
|
|
153
|
+
|
|
154
|
+
# Stored for connection restart capability.
|
|
155
|
+
inst._uri = uri
|
|
156
|
+
inst._conn_kwargs = {**kwargs}
|
|
157
|
+
|
|
158
|
+
yield inst
|
|
117
159
|
|
|
118
160
|
@classmethod
|
|
119
161
|
def server_handler(
|
|
@@ -9,6 +9,9 @@ from struct import unpack as _unpack
|
|
|
9
9
|
from typing import NamedTuple
|
|
10
10
|
from typing import cast as _cast
|
|
11
11
|
|
|
12
|
+
# third-party
|
|
13
|
+
from vcorelib.io import BinaryMessage
|
|
14
|
+
|
|
12
15
|
# internal
|
|
13
16
|
from runtimepy.primitives import AnyPrimitive as _AnyPrimitive
|
|
14
17
|
from runtimepy.primitives import Primitivelike as _Primitivelike
|
|
@@ -42,8 +45,6 @@ class PrimitiveArray(Serializable):
|
|
|
42
45
|
"""Initialize this primitive array."""
|
|
43
46
|
|
|
44
47
|
self._primitives: list[_AnyPrimitive] = []
|
|
45
|
-
self.byte_order = byte_order
|
|
46
|
-
self._format: str = self.byte_order.fmt
|
|
47
48
|
|
|
48
49
|
# Keep track of a quick lookup for converting between element indices
|
|
49
50
|
# and byte indices.
|
|
@@ -53,12 +54,13 @@ class PrimitiveArray(Serializable):
|
|
|
53
54
|
self.size = 0
|
|
54
55
|
self.chain = None
|
|
55
56
|
|
|
57
|
+
super().__init__(byte_order=byte_order, chain=chain)
|
|
58
|
+
self._format: str = self.byte_order.fmt
|
|
59
|
+
|
|
56
60
|
# Add initial items.
|
|
57
61
|
for item in primitives:
|
|
58
62
|
self.add(item)
|
|
59
63
|
|
|
60
|
-
super().__init__(byte_order=self.byte_order, chain=chain)
|
|
61
|
-
|
|
62
64
|
self._fragments: list["PrimitiveArray"] = []
|
|
63
65
|
self._fragment_specs: list[ArrayFragmentSpec] = []
|
|
64
66
|
|
|
@@ -236,7 +238,7 @@ class PrimitiveArray(Serializable):
|
|
|
236
238
|
"""Get bytes from a fragment."""
|
|
237
239
|
return bytes(self._fragments[index])
|
|
238
240
|
|
|
239
|
-
def update(self, data:
|
|
241
|
+
def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
|
|
240
242
|
"""Update primitive values from a bytes instance."""
|
|
241
243
|
|
|
242
244
|
for primitive, item in zip(
|
runtimepy/primitives/base.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import Iterator as _Iterator
|
|
|
13
13
|
from typing import TypeVar as _TypeVar
|
|
14
14
|
|
|
15
15
|
# third-party
|
|
16
|
+
from vcorelib.io import BinaryMessage
|
|
16
17
|
from vcorelib.math import default_time_ns, nano_str
|
|
17
18
|
from vcorelib.math.keeper import TimeSource
|
|
18
19
|
|
|
@@ -243,7 +244,7 @@ class Primitive(_Generic[T]):
|
|
|
243
244
|
stream.write(self.binary(byte_order=byte_order))
|
|
244
245
|
return self.kind.size
|
|
245
246
|
|
|
246
|
-
def update(self, data:
|
|
247
|
+
def update(self, data: BinaryMessage, byte_order: _ByteOrder = None) -> T:
|
|
247
248
|
"""Update this primitive from a bytes object."""
|
|
248
249
|
|
|
249
250
|
if byte_order is None:
|
|
@@ -269,7 +270,7 @@ class Primitive(_Generic[T]):
|
|
|
269
270
|
return cls.kind.encode(value, byte_order=byte_order)
|
|
270
271
|
|
|
271
272
|
@classmethod
|
|
272
|
-
def decode(cls, data:
|
|
273
|
+
def decode(cls, data: BinaryMessage, byte_order: _ByteOrder = None) -> T:
|
|
273
274
|
"""Decode a primitive of this type from provided data."""
|
|
274
275
|
|
|
275
276
|
if byte_order is None:
|
|
@@ -3,6 +3,7 @@ A module implementing bit flags and fields.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# built-in
|
|
6
|
+
from typing import Optional as _Optional
|
|
6
7
|
from typing import cast as _cast
|
|
7
8
|
|
|
8
9
|
# third-party
|
|
@@ -14,6 +15,12 @@ from runtimepy.mixins.regex import CHANNEL_PATTERN as _CHANNEL_PATTERN
|
|
|
14
15
|
from runtimepy.mixins.regex import RegexMixin as _RegexMixin
|
|
15
16
|
from runtimepy.primitives.int import UnsignedInt as _UnsignedInt
|
|
16
17
|
from runtimepy.registry.name import RegistryKey as _RegistryKey
|
|
18
|
+
from runtimepy.ui.controls import (
|
|
19
|
+
Controls,
|
|
20
|
+
Controlslike,
|
|
21
|
+
bit_slider,
|
|
22
|
+
normalize_controls,
|
|
23
|
+
)
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
class BitFieldBase:
|
|
@@ -26,6 +33,8 @@ class BitFieldBase:
|
|
|
26
33
|
width: int,
|
|
27
34
|
commandable: bool = False,
|
|
28
35
|
description: str = None,
|
|
36
|
+
controls: Controlslike = None,
|
|
37
|
+
default: _Optional[int | bool | str] = None,
|
|
29
38
|
) -> None:
|
|
30
39
|
"""Initialize this bit-field."""
|
|
31
40
|
|
|
@@ -34,6 +43,11 @@ class BitFieldBase:
|
|
|
34
43
|
self.commandable = commandable
|
|
35
44
|
self.description = description
|
|
36
45
|
|
|
46
|
+
if controls:
|
|
47
|
+
controls = normalize_controls(controls)
|
|
48
|
+
self.controls: _Optional[Controls] = controls # type: ignore
|
|
49
|
+
self.default = default
|
|
50
|
+
|
|
37
51
|
# Compute a bit-mask for this field.
|
|
38
52
|
self.width = width
|
|
39
53
|
self.mask = (2**self.width) - 1
|
|
@@ -46,7 +60,7 @@ class BitFieldBase:
|
|
|
46
60
|
|
|
47
61
|
result = ""
|
|
48
62
|
|
|
49
|
-
for idx in range(self.raw.size * 8):
|
|
63
|
+
for idx in reversed(range(self.raw.size * 8)):
|
|
50
64
|
val = 1 << idx
|
|
51
65
|
if val & self.shifted_mask:
|
|
52
66
|
result += "^"
|
|
@@ -96,11 +110,17 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
|
|
|
96
110
|
enum: _RegistryKey = None,
|
|
97
111
|
commandable: bool = False,
|
|
98
112
|
description: str = None,
|
|
113
|
+
**kwargs,
|
|
99
114
|
) -> None:
|
|
100
115
|
"""Initialize this bit-field."""
|
|
101
116
|
|
|
102
117
|
super().__init__(
|
|
103
|
-
raw,
|
|
118
|
+
raw,
|
|
119
|
+
index,
|
|
120
|
+
width,
|
|
121
|
+
commandable=commandable,
|
|
122
|
+
description=description,
|
|
123
|
+
**kwargs,
|
|
104
124
|
)
|
|
105
125
|
|
|
106
126
|
# Verify bit-field parameters.
|
|
@@ -118,6 +138,13 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
|
|
|
118
138
|
self.name = name
|
|
119
139
|
self._enum = enum
|
|
120
140
|
|
|
141
|
+
def set_slider(self) -> "BitField":
|
|
142
|
+
"""Set slider controls for this bit field."""
|
|
143
|
+
|
|
144
|
+
assert self.controls is None
|
|
145
|
+
self.controls = bit_slider(self.width, False)
|
|
146
|
+
return self
|
|
147
|
+
|
|
121
148
|
def asdict(self) -> _JsonObject:
|
|
122
149
|
"""Get this field as a dictionary."""
|
|
123
150
|
|
|
@@ -131,6 +158,10 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
|
|
|
131
158
|
result["enum"] = self.enum
|
|
132
159
|
if self.commandable:
|
|
133
160
|
result["commandable"] = True
|
|
161
|
+
if self.controls:
|
|
162
|
+
result["controls"] = self.controls # type: ignore
|
|
163
|
+
if self.default is not None:
|
|
164
|
+
result["default"] = self.default
|
|
134
165
|
return result
|
|
135
166
|
|
|
136
167
|
|
|
@@ -145,6 +176,7 @@ class BitFlag(BitField):
|
|
|
145
176
|
enum: _RegistryKey = None,
|
|
146
177
|
commandable: bool = False,
|
|
147
178
|
description: str = None,
|
|
179
|
+
**kwargs,
|
|
148
180
|
) -> None:
|
|
149
181
|
"""Initialize this bit flag."""
|
|
150
182
|
|
|
@@ -156,6 +188,7 @@ class BitFlag(BitField):
|
|
|
156
188
|
enum=enum,
|
|
157
189
|
commandable=commandable,
|
|
158
190
|
description=description,
|
|
191
|
+
**kwargs,
|
|
159
192
|
)
|
|
160
193
|
|
|
161
194
|
def clear(self) -> None:
|
|
@@ -73,6 +73,8 @@ class BitFields(_RuntimepyDictCodec):
|
|
|
73
73
|
enum=enum,
|
|
74
74
|
description=desc,
|
|
75
75
|
commandable=commandable,
|
|
76
|
+
controls=item.get("controls"),
|
|
77
|
+
default=item.get("default"),
|
|
76
78
|
)
|
|
77
79
|
flag(value)
|
|
78
80
|
item["index"] = flag.index
|
|
@@ -84,6 +86,8 @@ class BitFields(_RuntimepyDictCodec):
|
|
|
84
86
|
enum=enum,
|
|
85
87
|
description=desc,
|
|
86
88
|
commandable=commandable,
|
|
89
|
+
controls=item.get("controls"),
|
|
90
|
+
default=item.get("default"),
|
|
87
91
|
)
|
|
88
92
|
field(value)
|
|
89
93
|
item["index"] = field.index
|
|
@@ -229,10 +233,15 @@ class BitFields(_RuntimepyDictCodec):
|
|
|
229
233
|
|
|
230
234
|
@classmethod
|
|
231
235
|
def new(
|
|
232
|
-
cls: type[T], value: _Primitivelike | _AnyPrimitive = "uint8"
|
|
236
|
+
cls: type[T], value: _Primitivelike | _AnyPrimitive = "uint8", **kwargs
|
|
233
237
|
) -> T:
|
|
234
238
|
"""Create a new bit-field storage entity."""
|
|
235
239
|
|
|
236
240
|
return cls.create(
|
|
237
|
-
{
|
|
241
|
+
{
|
|
242
|
+
"type": _cast(str, value),
|
|
243
|
+
"fields": [],
|
|
244
|
+
"finalized": False,
|
|
245
|
+
**kwargs,
|
|
246
|
+
}
|
|
238
247
|
)
|
|
@@ -14,6 +14,7 @@ from typing import cast as _cast
|
|
|
14
14
|
from vcorelib.io import ARBITER as _ARBITER
|
|
15
15
|
from vcorelib.io.types import EncodeResult as _EncodeResult
|
|
16
16
|
from vcorelib.io.types import JsonObject as _JsonObject
|
|
17
|
+
from vcorelib.namespace import Namespace
|
|
17
18
|
from vcorelib.paths import Pathlike as _Pathlike
|
|
18
19
|
|
|
19
20
|
# internal
|
|
@@ -86,7 +87,12 @@ class BitFieldsManagerBase:
|
|
|
86
87
|
"""Encode this bit-fields manager to a file."""
|
|
87
88
|
return fields_to_file(path, self.fields, **kwargs)
|
|
88
89
|
|
|
89
|
-
def add(
|
|
90
|
+
def add(
|
|
91
|
+
self,
|
|
92
|
+
fields: _BitFields,
|
|
93
|
+
namespace: Namespace = None,
|
|
94
|
+
track: bool = False,
|
|
95
|
+
) -> int:
|
|
90
96
|
"""Add new bit-fields to manage."""
|
|
91
97
|
|
|
92
98
|
# Ensure that new fields can't be added after the current fields
|
|
@@ -97,7 +103,12 @@ class BitFieldsManagerBase:
|
|
|
97
103
|
self.fields.append(fields)
|
|
98
104
|
|
|
99
105
|
# Register fields into the lookup structure.
|
|
106
|
+
to_add = {}
|
|
100
107
|
for name, field in fields.fields.items():
|
|
108
|
+
if namespace is not None:
|
|
109
|
+
name = namespace.namespace(name=name, track=track)
|
|
110
|
+
to_add[name] = field
|
|
111
|
+
|
|
101
112
|
ident = self.registry.register_name(name)
|
|
102
113
|
assert ident is not None, "Couldn't register bit-field '{name}'!"
|
|
103
114
|
assert name not in self.lookup, name
|
|
@@ -105,7 +116,13 @@ class BitFieldsManagerBase:
|
|
|
105
116
|
|
|
106
117
|
# Also store the enum mapping.
|
|
107
118
|
if field.is_enum:
|
|
108
|
-
|
|
119
|
+
runtime = self.enums[field.enum]
|
|
120
|
+
self.enum_lookup[name] = runtime
|
|
121
|
+
if runtime.default:
|
|
122
|
+
self.set(name, runtime.default)
|
|
123
|
+
|
|
124
|
+
# Add possible namespaced-name mappings.
|
|
125
|
+
fields.fields.update(to_add)
|
|
109
126
|
|
|
110
127
|
return index
|
|
111
128
|
|
|
@@ -11,6 +11,7 @@ from typing import TypeVar
|
|
|
11
11
|
|
|
12
12
|
# third-party
|
|
13
13
|
from vcorelib import DEFAULT_ENCODING
|
|
14
|
+
from vcorelib.io import BinaryMessage
|
|
14
15
|
|
|
15
16
|
# internal
|
|
16
17
|
from runtimepy.primitives.byte_order import (
|
|
@@ -133,7 +134,7 @@ class Serializable(ABC):
|
|
|
133
134
|
)
|
|
134
135
|
|
|
135
136
|
@abstractmethod
|
|
136
|
-
def update(self, data:
|
|
137
|
+
def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
|
|
137
138
|
"""Update this serializable from a bytes instance."""
|
|
138
139
|
|
|
139
140
|
def update_str(self, data: str, timestamp_ns: int = None) -> int:
|
|
@@ -158,7 +159,9 @@ class Serializable(ABC):
|
|
|
158
159
|
|
|
159
160
|
return result
|
|
160
161
|
|
|
161
|
-
def update_chain(
|
|
162
|
+
def update_chain(
|
|
163
|
+
self, data: BinaryMessage, timestamp_ns: int = None
|
|
164
|
+
) -> int:
|
|
162
165
|
"""Update this serializable from a bytes instance."""
|
|
163
166
|
|
|
164
167
|
with _BytesIO(data) as stream:
|
|
@@ -5,6 +5,9 @@ A module implementing a fixed-size bytes serializable.
|
|
|
5
5
|
# built-in
|
|
6
6
|
from copy import copy as _copy
|
|
7
7
|
|
|
8
|
+
# third-party
|
|
9
|
+
from vcorelib.io import BinaryMessage
|
|
10
|
+
|
|
8
11
|
# internal
|
|
9
12
|
from runtimepy.primitives.serializable.base import Serializable
|
|
10
13
|
|
|
@@ -31,11 +34,11 @@ class FixedChunk(Serializable):
|
|
|
31
34
|
"""Get this serializable as a bytes instance."""
|
|
32
35
|
return self.data
|
|
33
36
|
|
|
34
|
-
def update(self, data:
|
|
37
|
+
def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
|
|
35
38
|
"""Update this serializable from a bytes instance."""
|
|
36
39
|
|
|
37
40
|
del timestamp_ns
|
|
38
41
|
|
|
39
|
-
self.data = data
|
|
42
|
+
self.data = bytes(data)
|
|
40
43
|
self.size = len(self.data)
|
|
41
44
|
return self.size
|
|
@@ -7,6 +7,9 @@ primitive prefix to determine the size of the chunk portion.
|
|
|
7
7
|
from typing import BinaryIO as _BinaryIO
|
|
8
8
|
from typing import TypeVar
|
|
9
9
|
|
|
10
|
+
# third-party
|
|
11
|
+
from vcorelib.io import BinaryMessage
|
|
12
|
+
|
|
10
13
|
# internal
|
|
11
14
|
from runtimepy.primitives import Primitivelike, UnsignedInt, create
|
|
12
15
|
from runtimepy.primitives.byte_order import (
|
|
@@ -44,7 +47,7 @@ class PrefixedChunk(Serializable):
|
|
|
44
47
|
"""Get this chunk as a string."""
|
|
45
48
|
return str(self.chunk)
|
|
46
49
|
|
|
47
|
-
def update(self, data:
|
|
50
|
+
def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
|
|
48
51
|
"""Update this serializable from a bytes instance."""
|
|
49
52
|
|
|
50
53
|
size = self.chunk.update(data, timestamp_ns=timestamp_ns)
|
|
@@ -14,6 +14,9 @@ from typing import TypeVar as _TypeVar
|
|
|
14
14
|
from typing import Union as _Union
|
|
15
15
|
from typing import cast as _cast
|
|
16
16
|
|
|
17
|
+
# third-party
|
|
18
|
+
from vcorelib.io import BinaryMessage
|
|
19
|
+
|
|
17
20
|
# internal
|
|
18
21
|
from runtimepy.primitives.byte_order import (
|
|
19
22
|
DEFAULT_BYTE_ORDER as _DEFAULT_BYTE_ORDER,
|
|
@@ -134,7 +137,7 @@ class PrimitiveType(_Generic[T]):
|
|
|
134
137
|
return _pack(byte_order.fmt + self.format, value)
|
|
135
138
|
|
|
136
139
|
def decode(
|
|
137
|
-
self, data:
|
|
140
|
+
self, data: BinaryMessage, byte_order: _ByteOrder = _DEFAULT_BYTE_ORDER
|
|
138
141
|
) -> PythonPrimitive:
|
|
139
142
|
"""Decode primitive based on this type."""
|
|
140
143
|
return _unpack(byte_order.fmt + self.format, data)[0] # type: ignore
|
|
@@ -31,9 +31,15 @@ class IntegerBounds(NamedTuple):
|
|
|
31
31
|
return max(self.min, min(val, self.max))
|
|
32
32
|
|
|
33
33
|
@staticmethod
|
|
34
|
-
def
|
|
34
|
+
def create_bit(bit_count: int, signed: bool) -> "IntegerBounds":
|
|
35
35
|
"""Compute maximum and minimum values given size and signedness."""
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
return IntegerBounds(
|
|
38
|
+
0 if not signed else -1 * (2 ** (bit_count - 1)),
|
|
39
|
+
(2 ** (bit_count if not signed else bit_count - 1)) - 1,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def create(byte_count: int, signed: bool) -> "IntegerBounds":
|
|
44
|
+
"""Compute maximum and minimum values given size and signedness."""
|
|
45
|
+
return IntegerBounds.create_bit(8 * byte_count, signed)
|
runtimepy/registry/__init__.py
CHANGED
|
@@ -20,6 +20,7 @@ from vcorelib.io.types import JsonValue as _JsonValue
|
|
|
20
20
|
from runtimepy.registry.item import RegistryItem as _RegistryItem
|
|
21
21
|
from runtimepy.registry.name import NameRegistry as _NameRegistry
|
|
22
22
|
from runtimepy.registry.name import RegistryKey as _RegistryKey
|
|
23
|
+
from runtimepy.registry.name import is_registry_key as _is_registry_key
|
|
23
24
|
from runtimepy.schemas import RuntimepyDictCodec as _RuntimepyDictCodec
|
|
24
25
|
|
|
25
26
|
T = _TypeVar("T", bound=_RegistryItem)
|
|
@@ -66,6 +67,15 @@ class Registry(_RuntimepyDictCodec, _Generic[T]):
|
|
|
66
67
|
for name, item in self.items.items()
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
def register_from_other(self, other: "Registry[T]") -> None:
|
|
71
|
+
"""Register missing elements from another registry."""
|
|
72
|
+
|
|
73
|
+
for name, instance in other.items.items():
|
|
74
|
+
if name in self.items:
|
|
75
|
+
assert self.items[name].id == instance.id, (name, instance)
|
|
76
|
+
else:
|
|
77
|
+
assert self.register(name, instance), (name, instance)
|
|
78
|
+
|
|
69
79
|
def register(self, name: str, item: T) -> bool:
|
|
70
80
|
"""Attempt to register a new item."""
|
|
71
81
|
|
|
@@ -100,6 +110,16 @@ class Registry(_RuntimepyDictCodec, _Generic[T]):
|
|
|
100
110
|
result = self.items[name]
|
|
101
111
|
return result
|
|
102
112
|
|
|
113
|
+
def registry_normalize(self, key: _RegistryKey | T) -> T:
|
|
114
|
+
"""Attempt to get an item from a registry key."""
|
|
115
|
+
|
|
116
|
+
if _is_registry_key(key):
|
|
117
|
+
result: T = self[_cast(str, key)]
|
|
118
|
+
else:
|
|
119
|
+
result = _cast(T, key)
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
|
|
103
123
|
def __getitem__(self, key: _RegistryKey) -> T:
|
|
104
124
|
"""Get a registry item."""
|
|
105
125
|
|
runtimepy/registry/name.py
CHANGED
|
@@ -3,6 +3,7 @@ A simple name-to-identifier registry interface.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# built-in
|
|
6
|
+
from typing import Any as _Any
|
|
6
7
|
from typing import MutableMapping as _MutableMapping
|
|
7
8
|
from typing import Optional as _Optional
|
|
8
9
|
from typing import Union as _Union
|
|
@@ -15,6 +16,11 @@ KeyToName = _MutableMapping[int, str]
|
|
|
15
16
|
NameToKey = _MutableMapping[str, int]
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def is_registry_key(data: _Any) -> bool:
|
|
20
|
+
"""Determine if this data is a registry key."""
|
|
21
|
+
return isinstance(data, (int, str))
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
class NameRegistry(_TwoWayNameMapping[int]):
|
|
19
25
|
"""A simple class for keeping track of name-to-identifier mappings."""
|
|
20
26
|
|