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.
Files changed (99) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/__init__.py +1 -4
  3. runtimepy/channel/environment/__init__.py +93 -2
  4. runtimepy/channel/environment/create.py +16 -1
  5. runtimepy/channel/environment/sample.py +10 -2
  6. runtimepy/channel/registry.py +2 -3
  7. runtimepy/codec/protocol/base.py +34 -14
  8. runtimepy/codec/protocol/json.py +5 -3
  9. runtimepy/codec/system/__init__.py +6 -2
  10. runtimepy/control/source.py +1 -1
  11. runtimepy/data/404.md +16 -0
  12. runtimepy/data/base.yaml +3 -0
  13. runtimepy/data/css/bootstrap_extra.css +59 -44
  14. runtimepy/data/css/main.css +23 -4
  15. runtimepy/data/dummy_load.yaml +5 -2
  16. runtimepy/data/factories.yaml +1 -0
  17. runtimepy/data/js/classes/App.js +54 -2
  18. runtimepy/data/js/classes/ChannelTable.js +6 -8
  19. runtimepy/data/js/classes/Plot.js +9 -4
  20. runtimepy/data/js/classes/TabFilter.js +47 -9
  21. runtimepy/data/js/classes/TabInterface.js +106 -11
  22. runtimepy/data/js/classes/WindowHashManager.js +30 -15
  23. runtimepy/data/js/init.js +18 -1
  24. runtimepy/data/js/markdown_page.js +10 -0
  25. runtimepy/data/js/sample.js +1 -0
  26. runtimepy/data/schemas/BitFields.yaml +9 -0
  27. runtimepy/data/schemas/RuntimeEnum.yaml +6 -0
  28. runtimepy/data/schemas/StructConfig.yaml +9 -1
  29. runtimepy/data/static/css/bootstrap-icons.min.css +4 -3
  30. runtimepy/data/static/css/bootstrap.min.css +3 -4
  31. runtimepy/data/static/css/fonts/bootstrap-icons.woff +0 -0
  32. runtimepy/data/static/css/fonts/bootstrap-icons.woff2 +0 -0
  33. runtimepy/data/static/js/bootstrap.bundle.min.js +5 -4
  34. runtimepy/data/static/js/webglplot.umd.min.js +2 -1
  35. runtimepy/data/static/svg/outline-dark.svg +22 -0
  36. runtimepy/data/static/svg/outline-light.svg +22 -0
  37. runtimepy/enum/__init__.py +13 -1
  38. runtimepy/enum/registry.py +13 -1
  39. runtimepy/message/__init__.py +3 -3
  40. runtimepy/mixins/logging.py +6 -1
  41. runtimepy/net/__init__.py +0 -2
  42. runtimepy/net/arbiter/info.py +36 -4
  43. runtimepy/net/arbiter/struct/__init__.py +3 -2
  44. runtimepy/net/connection.py +6 -7
  45. runtimepy/net/html/__init__.py +29 -11
  46. runtimepy/net/html/bootstrap/__init__.py +2 -2
  47. runtimepy/net/html/bootstrap/elements.py +44 -24
  48. runtimepy/net/html/bootstrap/tabs.py +18 -11
  49. runtimepy/net/http/__init__.py +3 -3
  50. runtimepy/net/http/request_target.py +3 -3
  51. runtimepy/net/mixin.py +4 -2
  52. runtimepy/net/server/__init__.py +16 -9
  53. runtimepy/net/server/app/__init__.py +1 -0
  54. runtimepy/net/server/app/create.py +3 -3
  55. runtimepy/net/server/app/env/__init__.py +30 -4
  56. runtimepy/net/server/app/env/settings.py +4 -7
  57. runtimepy/net/server/app/env/tab/base.py +2 -1
  58. runtimepy/net/server/app/env/tab/controls.py +141 -27
  59. runtimepy/net/server/app/env/tab/html.py +68 -26
  60. runtimepy/net/server/app/env/widgets.py +115 -61
  61. runtimepy/net/server/app/landing_page.py +1 -1
  62. runtimepy/net/server/app/tab.py +12 -3
  63. runtimepy/net/server/html.py +2 -2
  64. runtimepy/net/server/json.py +1 -1
  65. runtimepy/net/server/markdown.py +29 -12
  66. runtimepy/net/server/mux.py +29 -0
  67. runtimepy/net/stream/__init__.py +6 -5
  68. runtimepy/net/stream/base.py +4 -2
  69. runtimepy/net/tcp/connection.py +5 -3
  70. runtimepy/net/tcp/http/__init__.py +10 -9
  71. runtimepy/net/tcp/protocol.py +2 -2
  72. runtimepy/net/tcp/scpi/__init__.py +5 -2
  73. runtimepy/net/tcp/telnet/__init__.py +2 -1
  74. runtimepy/net/udp/connection.py +10 -6
  75. runtimepy/net/udp/protocol.py +5 -6
  76. runtimepy/net/udp/queue.py +5 -2
  77. runtimepy/net/udp/tftp/base.py +2 -1
  78. runtimepy/net/websocket/connection.py +58 -9
  79. runtimepy/primitives/array/__init__.py +7 -5
  80. runtimepy/primitives/base.py +3 -2
  81. runtimepy/primitives/field/__init__.py +35 -2
  82. runtimepy/primitives/field/fields.py +11 -2
  83. runtimepy/primitives/field/manager/base.py +19 -2
  84. runtimepy/primitives/serializable/base.py +5 -2
  85. runtimepy/primitives/serializable/fixed.py +5 -2
  86. runtimepy/primitives/serializable/prefixed.py +4 -1
  87. runtimepy/primitives/types/base.py +4 -1
  88. runtimepy/primitives/types/bounds.py +10 -4
  89. runtimepy/registry/__init__.py +20 -0
  90. runtimepy/registry/name.py +6 -0
  91. runtimepy/requirements.txt +2 -2
  92. runtimepy/ui/controls.py +20 -1
  93. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/METADATA +6 -6
  94. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/RECORD +98 -94
  95. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/WHEEL +1 -1
  96. runtimepy/data/404.html +0 -7
  97. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/entry_points.txt +0 -0
  98. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/licenses/LICENSE +0 -0
  99. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/top_level.txt +0 -0
@@ -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: bytes, addr: tuple[str, int] = None
63
+ self, data: BinaryMessage, addr: tuple[str, int] = None
62
64
  ) -> bool:
63
65
  """Process an incoming message."""
64
66
 
@@ -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[_BinaryMessage, str]]:
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: _BinaryMessage) -> None:
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[bytes | AsyncResponse]
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[bytes],
29
+ # request_data: Optional[bytearray],
29
30
  # ) -> HttpResult:
30
31
  # """Sample handler."""
31
32
  #
32
33
  HttpRequestHandler = Callable[
33
- [ResponseHeader, RequestHeader, Optional[bytes]],
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[bytes],
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[bytes],
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[bytes] = None,
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[bytes] = None
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[bytes] = None
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: bytes) -> bool:
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(
@@ -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: _BinaryMessage) -> None:
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: bytes) -> bool:
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: bytes) -> bool:
69
+ async def process_binary(self, data: BinaryMessage) -> bool:
69
70
  """Process a binary frame."""
70
71
 
71
72
  result = True
@@ -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: bytes, addr: tuple[str, int]
91
+ self, data: BinaryMessage, addr: tuple[str, int]
90
92
  ) -> bool:
91
93
  """Process a datagram."""
92
94
 
93
- def sendto(self, data: bytes, addr: IpHostTuplelike = None) -> None:
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: _BinaryMessage) -> None:
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: bytes, addr: tuple[str, int]
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: bytes, addr: tuple[str, int]
235
+ self, data: BinaryMessage, addr: tuple[str, int]
232
236
  ) -> bool:
233
237
  """Process a datagram."""
234
238
  return True
@@ -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
- _Tuple[_BinaryMessage, _Tuple[str, int]]
31
- ] = _asyncio.Queue()
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: _Tuple[str, int]) -> None:
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
 
@@ -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[bytes, tuple[str, int]]]
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: bytes, addr: tuple[str, int]
27
+ self, data: BinaryMessage, addr: tuple[str, int]
25
28
  ) -> bool:
26
29
  """Process a datagram."""
27
30
  self.datagrams.put_nowait((data, addr))
@@ -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: bytes, addr: tuple[str, int]
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 BinaryMessage, Connection
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__(self.protocol.logger, **kwargs)
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(self.protocol.send(data))
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
- kwargs.setdefault("use_ssl", uri.startswith("wss"))
122
+ inst = cls(await websocket_connect(uri, **kwargs), markdown=markdown)
98
123
 
99
- protocol = await getattr(websockets, "connect")(
100
- uri, **handle_possible_ssl(**kwargs)
101
- )
102
- return cls(protocol, markdown=markdown)
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
- yield cls(protocol, markdown=markdown)
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: bytes, timestamp_ns: int = None) -> int:
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(
@@ -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: bytes, byte_order: _ByteOrder = None) -> T:
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: bytes, byte_order: _ByteOrder = None) -> T:
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, index, width, commandable=commandable, description=description
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
- {"type": _cast(str, value), "fields": [], "finalized": False}
241
+ {
242
+ "type": _cast(str, value),
243
+ "fields": [],
244
+ "finalized": False,
245
+ **kwargs,
246
+ }
238
247
  )