runtimepy 5.14.2__py3-none-any.whl → 5.15.1__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 +5 -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/js/sample.js +1 -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 +6 -7
- 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 +30 -4
- runtimepy/net/server/app/env/settings.py +4 -7
- runtimepy/net/server/app/env/tab/base.py +2 -1
- 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/app/tab.py +12 -3
- runtimepy/net/server/html.py +2 -2
- runtimepy/net/server/json.py +1 -1
- runtimepy/net/server/markdown.py +29 -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 +58 -9
- 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.1.dist-info}/METADATA +6 -6
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/RECORD +98 -94
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/WHEEL +1 -1
- runtimepy/data/404.html +0 -7
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/licenses/LICENSE +0 -0
- {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/top_level.txt +0 -0
runtimepy/net/stream/base.py
CHANGED
|
@@ -6,9 +6,11 @@ A module implementing a base, stream-oriented connection interface.
|
|
|
6
6
|
from io import BytesIO as _BytesIO
|
|
7
7
|
from typing import BinaryIO as _BinaryIO
|
|
8
8
|
|
|
9
|
+
# third-party
|
|
10
|
+
from vcorelib.io import BinaryMessage
|
|
11
|
+
|
|
9
12
|
# internal
|
|
10
13
|
from runtimepy.message import MessageProcessor
|
|
11
|
-
from runtimepy.net.connection import BinaryMessage
|
|
12
14
|
from runtimepy.net.connection import Connection as _Connection
|
|
13
15
|
|
|
14
16
|
|
|
@@ -58,7 +60,7 @@ class PrefixedMessageConnection(_Connection):
|
|
|
58
60
|
return True
|
|
59
61
|
|
|
60
62
|
async def process_binary(
|
|
61
|
-
self, data:
|
|
63
|
+
self, data: BinaryMessage, addr: tuple[str, int] = None
|
|
62
64
|
) -> bool:
|
|
63
65
|
"""Process an incoming message."""
|
|
64
66
|
|
runtimepy/net/tcp/connection.py
CHANGED
|
@@ -17,10 +17,12 @@ from typing import Optional as _Optional
|
|
|
17
17
|
from typing import TypeVar as _TypeVar
|
|
18
18
|
from typing import Union as _Union
|
|
19
19
|
|
|
20
|
+
# third-party
|
|
21
|
+
from vcorelib.io import BinaryMessage
|
|
22
|
+
|
|
20
23
|
# internal
|
|
21
24
|
from runtimepy.net import sockname as _sockname
|
|
22
25
|
from runtimepy.net.backoff import ExponentialBackoff
|
|
23
|
-
from runtimepy.net.connection import BinaryMessage as _BinaryMessage
|
|
24
26
|
from runtimepy.net.connection import Connection as _Connection
|
|
25
27
|
from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
26
28
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
@@ -91,7 +93,7 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
91
93
|
"""Determine if this connection uses SSL."""
|
|
92
94
|
return self._transport.get_extra_info("sslcontext") is not None
|
|
93
95
|
|
|
94
|
-
async def _await_message(self) -> _Optional[_Union[
|
|
96
|
+
async def _await_message(self) -> _Optional[_Union[BinaryMessage, str]]:
|
|
95
97
|
"""Await the next message. Return None on error or failure."""
|
|
96
98
|
|
|
97
99
|
data = await self._protocol.queue.get()
|
|
@@ -103,7 +105,7 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
103
105
|
"""Enqueue a text message to send."""
|
|
104
106
|
self.send_binary(data.encode())
|
|
105
107
|
|
|
106
|
-
def send_binary(self, data:
|
|
108
|
+
def send_binary(self, data: BinaryMessage) -> None:
|
|
107
109
|
"""Enqueue a binary message tos end."""
|
|
108
110
|
self._transport.write(data)
|
|
109
111
|
self.metrics.tx.increment(len(data))
|
|
@@ -11,6 +11,7 @@ from typing import Any, Awaitable, Callable, Optional, Tuple, Union, cast
|
|
|
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 import PKG_NAME, VERSION
|
|
@@ -19,18 +20,18 @@ from runtimepy.net.http.header import RequestHeader
|
|
|
19
20
|
from runtimepy.net.http.response import AsyncResponse, ResponseHeader
|
|
20
21
|
from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
|
|
21
22
|
|
|
22
|
-
HttpResult = Optional[
|
|
23
|
+
HttpResult = Optional[BinaryMessage | AsyncResponse]
|
|
23
24
|
|
|
24
25
|
#
|
|
25
26
|
# async def handler(
|
|
26
27
|
# response: ResponseHeader,
|
|
27
28
|
# request: RequestHeader,
|
|
28
|
-
# request_data: Optional[
|
|
29
|
+
# request_data: Optional[bytearray],
|
|
29
30
|
# ) -> HttpResult:
|
|
30
31
|
# """Sample handler."""
|
|
31
32
|
#
|
|
32
33
|
HttpRequestHandler = Callable[
|
|
33
|
-
[ResponseHeader, RequestHeader, Optional[
|
|
34
|
+
[ResponseHeader, RequestHeader, Optional[bytearray]],
|
|
34
35
|
Awaitable[HttpResult],
|
|
35
36
|
]
|
|
36
37
|
HttpResponse = Tuple[ResponseHeader, HttpResult]
|
|
@@ -99,7 +100,7 @@ class HttpConnection(_TcpConnection):
|
|
|
99
100
|
self,
|
|
100
101
|
response: ResponseHeader,
|
|
101
102
|
request: RequestHeader,
|
|
102
|
-
request_data: Optional[
|
|
103
|
+
request_data: Optional[bytearray],
|
|
103
104
|
) -> HttpResult:
|
|
104
105
|
"""Sample handler."""
|
|
105
106
|
|
|
@@ -107,7 +108,7 @@ class HttpConnection(_TcpConnection):
|
|
|
107
108
|
self,
|
|
108
109
|
response: ResponseHeader,
|
|
109
110
|
request: RequestHeader,
|
|
110
|
-
request_data: Optional[
|
|
111
|
+
request_data: Optional[bytearray],
|
|
111
112
|
) -> HttpResult:
|
|
112
113
|
"""Sample handler."""
|
|
113
114
|
|
|
@@ -115,7 +116,7 @@ class HttpConnection(_TcpConnection):
|
|
|
115
116
|
self,
|
|
116
117
|
response: ResponseHeader,
|
|
117
118
|
request_header: RequestHeader,
|
|
118
|
-
request_data: Optional[
|
|
119
|
+
request_data: Optional[bytearray] = None,
|
|
119
120
|
) -> HttpResult:
|
|
120
121
|
"""Process an individual request."""
|
|
121
122
|
|
|
@@ -141,7 +142,7 @@ class HttpConnection(_TcpConnection):
|
|
|
141
142
|
return result
|
|
142
143
|
|
|
143
144
|
async def request(
|
|
144
|
-
self, request: RequestHeader, data: Optional[
|
|
145
|
+
self, request: RequestHeader, data: Optional[BinaryMessage] = None
|
|
145
146
|
) -> HttpResponse:
|
|
146
147
|
"""Make an HTTP request."""
|
|
147
148
|
|
|
@@ -157,7 +158,7 @@ class HttpConnection(_TcpConnection):
|
|
|
157
158
|
return result
|
|
158
159
|
|
|
159
160
|
async def request_json(
|
|
160
|
-
self, request: RequestHeader, data: Optional[
|
|
161
|
+
self, request: RequestHeader, data: Optional[BinaryMessage] = None
|
|
161
162
|
) -> Any:
|
|
162
163
|
"""
|
|
163
164
|
Perform a request and convert the response to a data structure by
|
|
@@ -191,7 +192,7 @@ class HttpConnection(_TcpConnection):
|
|
|
191
192
|
|
|
192
193
|
header.log(self.logger, True)
|
|
193
194
|
|
|
194
|
-
async def process_binary(self, data:
|
|
195
|
+
async def process_binary(self, data: BinaryMessage) -> bool:
|
|
195
196
|
"""Process a binary frame."""
|
|
196
197
|
|
|
197
198
|
for header, payload in self.processor.ingest(
|
runtimepy/net/tcp/protocol.py
CHANGED
|
@@ -9,10 +9,10 @@ from logging import getLogger as _getLogger
|
|
|
9
9
|
from typing import Optional as _Optional
|
|
10
10
|
|
|
11
11
|
# third-party
|
|
12
|
+
from vcorelib.io import BinaryMessage
|
|
12
13
|
from vcorelib.logging import LoggerType as _LoggerType
|
|
13
14
|
|
|
14
15
|
# internal
|
|
15
|
-
from runtimepy.net.connection import BinaryMessage as _BinaryMessage
|
|
16
16
|
from runtimepy.net.connection import Connection as _Connection
|
|
17
17
|
from runtimepy.net.mixin import (
|
|
18
18
|
BinaryMessageQueueMixin as _BinaryMessageQueueMixin,
|
|
@@ -26,7 +26,7 @@ class QueueProtocol(_BinaryMessageQueueMixin, _Protocol):
|
|
|
26
26
|
logger: _LoggerType
|
|
27
27
|
conn: _Connection
|
|
28
28
|
|
|
29
|
-
def data_received(self, data:
|
|
29
|
+
def data_received(self, data: BinaryMessage) -> None:
|
|
30
30
|
"""Handle incoming data."""
|
|
31
31
|
self.queue.put_nowait(data)
|
|
32
32
|
|
|
@@ -5,6 +5,9 @@ A module implementing an SCPI interface.
|
|
|
5
5
|
# built-in
|
|
6
6
|
import asyncio
|
|
7
7
|
|
|
8
|
+
# third-party
|
|
9
|
+
from vcorelib.io import BinaryMessage
|
|
10
|
+
|
|
8
11
|
# internal
|
|
9
12
|
from runtimepy.net.arbiter.tcp import TcpConnectionFactory
|
|
10
13
|
from runtimepy.net.tcp import TcpConnection
|
|
@@ -34,9 +37,9 @@ class ScpiConnection(TcpConnection):
|
|
|
34
37
|
|
|
35
38
|
return True
|
|
36
39
|
|
|
37
|
-
async def process_binary(self, data:
|
|
40
|
+
async def process_binary(self, data: BinaryMessage) -> bool:
|
|
38
41
|
"""Process a binary frame."""
|
|
39
|
-
return await self.process_text(data.decode())
|
|
42
|
+
return await self.process_text(bytes(data).decode())
|
|
40
43
|
|
|
41
44
|
async def send_command(
|
|
42
45
|
self,
|
|
@@ -10,6 +10,7 @@ from typing import BinaryIO as _BinaryIO
|
|
|
10
10
|
|
|
11
11
|
# third-party
|
|
12
12
|
from vcorelib import DEFAULT_ENCODING
|
|
13
|
+
from vcorelib.io import BinaryMessage
|
|
13
14
|
|
|
14
15
|
# internal
|
|
15
16
|
from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
|
|
@@ -65,7 +66,7 @@ class Telnet(_TcpConnection):
|
|
|
65
66
|
) -> None:
|
|
66
67
|
"""Process a telnet option."""
|
|
67
68
|
|
|
68
|
-
async def process_binary(self, data:
|
|
69
|
+
async def process_binary(self, data: BinaryMessage) -> bool:
|
|
69
70
|
"""Process a binary frame."""
|
|
70
71
|
|
|
71
72
|
result = True
|
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
|
|
@@ -28,8 +29,9 @@ from websockets.asyncio.server import serve as _serve
|
|
|
28
29
|
from websockets.exceptions import ConnectionClosed as _ConnectionClosed
|
|
29
30
|
|
|
30
31
|
# internal
|
|
32
|
+
from runtimepy.net import normalize_host as _normalize_host
|
|
31
33
|
from runtimepy.net import sockname as _sockname
|
|
32
|
-
from runtimepy.net.connection import
|
|
34
|
+
from runtimepy.net.connection import Connection
|
|
33
35
|
from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
34
36
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
35
37
|
from runtimepy.net.manager import ConnectionManager as _ConnectionManager
|
|
@@ -41,6 +43,17 @@ V = _TypeVar("V")
|
|
|
41
43
|
LOG = _getLogger(__name__)
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
async def websocket_connect(uri: str, **kwargs) -> _ClientConnection:
|
|
47
|
+
"""Attempt to connect a websocket interface."""
|
|
48
|
+
|
|
49
|
+
# Defaults.
|
|
50
|
+
kwargs.setdefault("use_ssl", uri.startswith("wss"))
|
|
51
|
+
|
|
52
|
+
return await getattr(websockets, "connect")( # type: ignore
|
|
53
|
+
uri, **handle_possible_ssl(**kwargs)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
44
57
|
class WebsocketConnection(Connection):
|
|
45
58
|
"""A simple websocket connection interface."""
|
|
46
59
|
|
|
@@ -52,7 +65,17 @@ class WebsocketConnection(Connection):
|
|
|
52
65
|
"""Initialize this connection."""
|
|
53
66
|
|
|
54
67
|
self.protocol = protocol
|
|
55
|
-
super().__init__(
|
|
68
|
+
super().__init__(
|
|
69
|
+
_getLogger(
|
|
70
|
+
f"W {_normalize_host(*self.protocol.local_address)} -> "
|
|
71
|
+
f"{_normalize_host(*self.protocol.remote_address)}"
|
|
72
|
+
),
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Store connection-instantiation arguments (for connection restarting).
|
|
77
|
+
self._uri: str = ""
|
|
78
|
+
self._conn_kwargs: dict[str, _Any] = {}
|
|
56
79
|
|
|
57
80
|
async def _handle_connection_closed(
|
|
58
81
|
self, task: _Awaitable[V]
|
|
@@ -81,7 +104,9 @@ class WebsocketConnection(Connection):
|
|
|
81
104
|
|
|
82
105
|
async def _send_binay_message(self, data: BinaryMessage) -> None:
|
|
83
106
|
"""Send a binary message."""
|
|
84
|
-
await self._handle_connection_closed(
|
|
107
|
+
await self._handle_connection_closed(
|
|
108
|
+
self.protocol.send(data), # type: ignore
|
|
109
|
+
)
|
|
85
110
|
self.metrics.tx.increment(len(data))
|
|
86
111
|
|
|
87
112
|
async def close(self) -> None:
|
|
@@ -94,12 +119,30 @@ class WebsocketConnection(Connection):
|
|
|
94
119
|
) -> T:
|
|
95
120
|
"""Connect a client to an endpoint."""
|
|
96
121
|
|
|
97
|
-
|
|
122
|
+
inst = cls(await websocket_connect(uri, **kwargs), markdown=markdown)
|
|
98
123
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
124
|
+
# Stored for connection restart capability.
|
|
125
|
+
inst._uri = uri
|
|
126
|
+
inst._conn_kwargs = {**kwargs}
|
|
127
|
+
|
|
128
|
+
return inst
|
|
129
|
+
|
|
130
|
+
async def restart(self) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Reset necessary underlying state for this connection to 'process'
|
|
133
|
+
again.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
result = False
|
|
137
|
+
|
|
138
|
+
if self._uri:
|
|
139
|
+
with _suppress(TimeoutError, OSError):
|
|
140
|
+
self.protocol = await websocket_connect(
|
|
141
|
+
self._uri, **self._conn_kwargs
|
|
142
|
+
)
|
|
143
|
+
result = True
|
|
144
|
+
|
|
145
|
+
return result
|
|
103
146
|
|
|
104
147
|
@classmethod
|
|
105
148
|
@_asynccontextmanager
|
|
@@ -113,7 +156,13 @@ class WebsocketConnection(Connection):
|
|
|
113
156
|
async with getattr(websockets, "connect")(
|
|
114
157
|
uri, **handle_possible_ssl(**kwargs)
|
|
115
158
|
) as protocol:
|
|
116
|
-
|
|
159
|
+
inst = cls(protocol, markdown=markdown)
|
|
160
|
+
|
|
161
|
+
# Stored for connection restart capability.
|
|
162
|
+
inst._uri = uri
|
|
163
|
+
inst._conn_kwargs = {**kwargs}
|
|
164
|
+
|
|
165
|
+
yield inst
|
|
117
166
|
|
|
118
167
|
@classmethod
|
|
119
168
|
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
|
)
|