rocket-welder-sdk 1.1.0__py3-none-any.whl → 1.1.26__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.
@@ -20,6 +20,9 @@ if TYPE_CHECKING:
20
20
  # Type alias for OpenCV Mat
21
21
  Mat = np.ndarray[Any, Any]
22
22
 
23
+ # Module logger
24
+ logger = logging.getLogger(__name__)
25
+
23
26
 
24
27
  class RocketWelderClient:
25
28
  """
@@ -28,20 +31,18 @@ class RocketWelderClient:
28
31
  Provides a unified interface for different connection types and protocols.
29
32
  """
30
33
 
31
- def __init__(self, connection: str | ConnectionString, logger: logging.Logger | None = None):
34
+ def __init__(self, connection: str | ConnectionString):
32
35
  """
33
36
  Initialize the RocketWelder client.
34
37
 
35
38
  Args:
36
39
  connection: Connection string or ConnectionString object
37
- logger: Optional logger instance
38
40
  """
39
41
  if isinstance(connection, str):
40
42
  self._connection = ConnectionString.parse(connection)
41
43
  else:
42
44
  self._connection = connection
43
45
 
44
- self._logger = logger or logging.getLogger(__name__)
45
46
  self._controller: IController | None = None
46
47
  self._lock = threading.Lock()
47
48
 
@@ -93,15 +94,15 @@ class RocketWelderClient:
93
94
  # Create appropriate controller based on connection
94
95
  if self._connection.protocol == Protocol.SHM:
95
96
  if self._connection.connection_mode == ConnectionMode.DUPLEX:
96
- self._controller = DuplexShmController(self._connection, self._logger)
97
+ self._controller = DuplexShmController(self._connection)
97
98
  else:
98
- self._controller = OneWayShmController(self._connection, self._logger)
99
+ self._controller = OneWayShmController(self._connection)
99
100
  else:
100
101
  raise ValueError(f"Unsupported protocol: {self._connection.protocol}")
101
102
 
102
103
  # Start the controller
103
104
  self._controller.start(on_frame, cancellation_token) # type: ignore[arg-type]
104
- self._logger.info("RocketWelder client started with %s", self._connection)
105
+ logger.info("RocketWelder client started with %s", self._connection)
105
106
 
106
107
  def stop(self) -> None:
107
108
  """Stop the client and clean up resources."""
@@ -109,7 +110,7 @@ class RocketWelderClient:
109
110
  if self._controller:
110
111
  self._controller.stop()
111
112
  self._controller = None
112
- self._logger.info("RocketWelder client stopped")
113
+ logger.info("RocketWelder client stopped")
113
114
 
114
115
  def __enter__(self) -> RocketWelderClient:
115
116
  """Context manager entry."""
@@ -125,7 +126,6 @@ class RocketWelderClient:
125
126
  buffer_name: str,
126
127
  buffer_size: str = "256MB",
127
128
  metadata_size: str = "4KB",
128
- logger: logging.Logger | None = None,
129
129
  ) -> RocketWelderClient:
130
130
  """
131
131
  Create a one-way shared memory client.
@@ -134,7 +134,6 @@ class RocketWelderClient:
134
134
  buffer_name: Name of the shared memory buffer
135
135
  buffer_size: Size of the buffer (e.g., "256MB")
136
136
  metadata_size: Size of metadata buffer (e.g., "4KB")
137
- logger: Optional logger
138
137
 
139
138
  Returns:
140
139
  Configured RocketWelderClient instance
@@ -142,7 +141,7 @@ class RocketWelderClient:
142
141
  connection_str = (
143
142
  f"shm://{buffer_name}?size={buffer_size}&metadata={metadata_size}&mode=OneWay"
144
143
  )
145
- return cls(connection_str, logger)
144
+ return cls(connection_str)
146
145
 
147
146
  @classmethod
148
147
  def create_duplex_shm(
@@ -150,7 +149,6 @@ class RocketWelderClient:
150
149
  buffer_name: str,
151
150
  buffer_size: str = "256MB",
152
151
  metadata_size: str = "4KB",
153
- logger: logging.Logger | None = None,
154
152
  ) -> RocketWelderClient:
155
153
  """
156
154
  Create a duplex shared memory client.
@@ -159,7 +157,6 @@ class RocketWelderClient:
159
157
  buffer_name: Name of the shared memory buffer
160
158
  buffer_size: Size of the buffer (e.g., "256MB")
161
159
  metadata_size: Size of metadata buffer (e.g., "4KB")
162
- logger: Optional logger
163
160
 
164
161
  Returns:
165
162
  Configured RocketWelderClient instance
@@ -167,4 +164,4 @@ class RocketWelderClient:
167
164
  connection_str = (
168
165
  f"shm://{buffer_name}?size={buffer_size}&metadata={metadata_size}&mode=Duplex"
169
166
  )
170
- return cls(connection_str, logger)
167
+ return cls(connection_str)
@@ -0,0 +1,48 @@
1
+ """UI module for RocketWelder SDK."""
2
+
3
+ from rocket_welder_sdk.external_controls.contracts import ArrowDirection
4
+
5
+ from .controls import (
6
+ ArrowGridControl,
7
+ ControlBase,
8
+ IconButtonControl,
9
+ LabelControl,
10
+ )
11
+ from .icons import Custom, Icons, Material
12
+ from .ui_events_projection import UiEventsProjection
13
+ from .ui_service import (
14
+ ItemsControl,
15
+ UiControlFactory,
16
+ UiService,
17
+ )
18
+ from .value_types import (
19
+ Color,
20
+ ControlType,
21
+ RegionName,
22
+ Size,
23
+ Typography,
24
+ )
25
+
26
+ __all__ = [
27
+ "ArrowDirection",
28
+ "ArrowGridControl",
29
+ "Color",
30
+ # Controls
31
+ "ControlBase",
32
+ # Enums
33
+ "ControlType",
34
+ "Custom",
35
+ "IconButtonControl",
36
+ # Icons
37
+ "Icons",
38
+ "ItemsControl",
39
+ "LabelControl",
40
+ "Material",
41
+ "RegionName",
42
+ "Size",
43
+ "Typography",
44
+ "UiControlFactory",
45
+ "UiEventsProjection",
46
+ # Services
47
+ "UiService",
48
+ ]
@@ -0,0 +1,362 @@
1
+ """UI Control base classes and implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar
7
+
8
+ from rocket_welder_sdk.external_controls.contracts import (
9
+ ArrowDirection,
10
+ ButtonDown,
11
+ ButtonUp,
12
+ KeyDown,
13
+ KeyUp,
14
+ )
15
+
16
+ from .value_types import Color, ControlType, Size, Typography
17
+
18
+ if TYPE_CHECKING:
19
+ from .ui_service import UiService
20
+
21
+
22
+ class ControlBase(ABC):
23
+ """Base class for all UI controls."""
24
+
25
+ def __init__(
26
+ self,
27
+ control_id: str,
28
+ control_type: ControlType,
29
+ ui_service: UiService,
30
+ properties: dict[str, str] | None = None,
31
+ ) -> None:
32
+ """
33
+ Initialize base control.
34
+
35
+ Args:
36
+ control_id: Unique identifier for the control
37
+ control_type: Type of the control
38
+ ui_service: Reference to parent UiService
39
+ properties: Initial properties
40
+ """
41
+ self.id: str = control_id
42
+ self.control_type: ControlType = control_type
43
+ self._ui_service: UiService = ui_service
44
+ self._properties: dict[str, str] = properties or {}
45
+ self._changed: dict[str, str] = {}
46
+ self._is_disposed: bool = False
47
+
48
+ @property
49
+ def is_dirty(self) -> bool:
50
+ """Check if control has uncommitted changes."""
51
+ return bool(self._changed)
52
+
53
+ @property
54
+ def changed(self) -> dict[str, str]:
55
+ """Get pending changes."""
56
+ return self._changed.copy()
57
+
58
+ @property
59
+ def properties(self) -> dict[str, str]:
60
+ """Get current properties including changes."""
61
+ props = self._properties.copy()
62
+ props.update(self._changed)
63
+ return props
64
+
65
+ def set_property(self, name: str, value: Any) -> None:
66
+ """
67
+ Set a property value.
68
+
69
+ Args:
70
+ name: Property name
71
+ value: Property value (will be converted to string)
72
+ """
73
+ str_value = str(value) if value is not None else ""
74
+ if self._properties.get(name) != str_value:
75
+ self._changed[name] = str_value
76
+
77
+ def commit_changes(self) -> None:
78
+ """Commit pending changes to properties."""
79
+ self._properties.update(self._changed)
80
+ self._changed.clear()
81
+
82
+ @abstractmethod
83
+ def handle_event(self, event: Any) -> None:
84
+ """
85
+ Handle an event for this control.
86
+
87
+ Args:
88
+ event: Event to handle
89
+ """
90
+ pass
91
+
92
+ def dispose(self) -> None:
93
+ """Dispose of the control."""
94
+ if not self._is_disposed:
95
+ self._is_disposed = True
96
+ self._ui_service.schedule_delete(self.id)
97
+
98
+
99
+ class IconButtonControl(ControlBase):
100
+ """Icon button control with click events."""
101
+
102
+ def __init__(
103
+ self,
104
+ control_id: str,
105
+ ui_service: UiService,
106
+ icon: str,
107
+ properties: dict[str, str] | None = None,
108
+ ) -> None:
109
+ """
110
+ Initialize icon button control.
111
+
112
+ Args:
113
+ control_id: Unique identifier
114
+ ui_service: Parent UiService
115
+ icon: SVG path for the icon
116
+ properties: Additional properties
117
+ """
118
+ props = properties or {}
119
+ props["Icon"] = icon
120
+ super().__init__(control_id, ControlType.ICON_BUTTON, ui_service, props)
121
+
122
+ # Event handlers
123
+ self.on_button_down: Callable[[IconButtonControl], None] | None = None
124
+ self.on_button_up: Callable[[IconButtonControl], None] | None = None
125
+
126
+ @property
127
+ def icon(self) -> str:
128
+ """Get icon SVG path."""
129
+ return self.properties.get("Icon", "")
130
+
131
+ @icon.setter
132
+ def icon(self, value: str) -> None:
133
+ """Set icon SVG path."""
134
+ self.set_property("Icon", value)
135
+
136
+ @property
137
+ def text(self) -> str | None:
138
+ """Get button text."""
139
+ return self.properties.get("Text")
140
+
141
+ @text.setter
142
+ def text(self, value: str | None) -> None:
143
+ """Set button text."""
144
+ if value is not None:
145
+ self.set_property("Text", value)
146
+
147
+ @property
148
+ def color(self) -> Color:
149
+ """Get button color."""
150
+ color_str = self.properties.get("Color", Color.PRIMARY.value)
151
+ try:
152
+ return Color(color_str)
153
+ except ValueError:
154
+ return Color.PRIMARY
155
+
156
+ @color.setter
157
+ def color(self, value: Color | str) -> None:
158
+ """Set button color."""
159
+ if isinstance(value, Color):
160
+ self.set_property("Color", value.value)
161
+ else:
162
+ # Try to find matching enum
163
+ for color in Color:
164
+ if color.value == value:
165
+ self.set_property("Color", value)
166
+ return
167
+ raise ValueError(f"Invalid color value: {value}")
168
+
169
+ @property
170
+ def size(self) -> Size:
171
+ """Get button size."""
172
+ size_str = self.properties.get("Size", Size.MEDIUM.value)
173
+ try:
174
+ return Size(size_str)
175
+ except ValueError:
176
+ return Size.MEDIUM
177
+
178
+ @size.setter
179
+ def size(self, value: Size | str) -> None:
180
+ """Set button size."""
181
+ if isinstance(value, Size):
182
+ self.set_property("Size", value.value)
183
+ else:
184
+ # Try to find matching enum
185
+ for size in Size:
186
+ if size.value == value:
187
+ self.set_property("Size", value)
188
+ return
189
+ raise ValueError(f"Invalid size value: {value}")
190
+
191
+ def handle_event(self, event: Any) -> None:
192
+ """Handle button events."""
193
+ if isinstance(event, ButtonDown) and self.on_button_down:
194
+ self.on_button_down(self)
195
+ elif isinstance(event, ButtonUp) and self.on_button_up:
196
+ self.on_button_up(self)
197
+
198
+
199
+ class ArrowGridControl(ControlBase):
200
+ """Arrow grid control for directional input."""
201
+
202
+ # Mapping from key codes to arrow directions
203
+ KEY_TO_DIRECTION: ClassVar[dict[str, ArrowDirection]] = {
204
+ "ArrowUp": ArrowDirection.UP,
205
+ "ArrowDown": ArrowDirection.DOWN,
206
+ "ArrowLeft": ArrowDirection.LEFT,
207
+ "ArrowRight": ArrowDirection.RIGHT,
208
+ }
209
+
210
+ def __init__(
211
+ self, control_id: str, ui_service: UiService, properties: dict[str, str] | None = None
212
+ ) -> None:
213
+ """
214
+ Initialize arrow grid control.
215
+
216
+ Args:
217
+ control_id: Unique identifier
218
+ ui_service: Parent UiService
219
+ properties: Additional properties
220
+ """
221
+ super().__init__(control_id, ControlType.ARROW_GRID, ui_service, properties)
222
+
223
+ # Event handlers
224
+ self.on_arrow_down: Callable[[ArrowGridControl, ArrowDirection], None] | None = None
225
+ self.on_arrow_up: Callable[[ArrowGridControl, ArrowDirection], None] | None = None
226
+
227
+ @property
228
+ def size(self) -> Size:
229
+ """Get grid size."""
230
+ size_str = self.properties.get("Size", Size.MEDIUM.value)
231
+ try:
232
+ return Size(size_str)
233
+ except ValueError:
234
+ return Size.MEDIUM
235
+
236
+ @size.setter
237
+ def size(self, value: Size | str) -> None:
238
+ """Set grid size."""
239
+ if isinstance(value, Size):
240
+ self.set_property("Size", value.value)
241
+ else:
242
+ # Try to find matching enum
243
+ for size in Size:
244
+ if size.value == value:
245
+ self.set_property("Size", value)
246
+ return
247
+ raise ValueError(f"Invalid size value: {value}")
248
+
249
+ @property
250
+ def color(self) -> Color:
251
+ """Get grid color."""
252
+ color_str = self.properties.get("Color", Color.PRIMARY.value)
253
+ try:
254
+ return Color(color_str)
255
+ except ValueError:
256
+ return Color.PRIMARY
257
+
258
+ @color.setter
259
+ def color(self, value: Color | str) -> None:
260
+ """Set grid color."""
261
+ if isinstance(value, Color):
262
+ self.set_property("Color", value.value)
263
+ else:
264
+ # Try to find matching enum
265
+ for color in Color:
266
+ if color.value == value:
267
+ self.set_property("Color", value)
268
+ return
269
+ raise ValueError(f"Invalid color value: {value}")
270
+
271
+ def handle_event(self, event: Any) -> None:
272
+ """Handle keyboard events and translate to arrow events."""
273
+ if isinstance(event, KeyDown):
274
+ direction = self.KEY_TO_DIRECTION.get(event.code)
275
+ if direction and self.on_arrow_down:
276
+ self.on_arrow_down(self, direction)
277
+ elif isinstance(event, KeyUp):
278
+ direction = self.KEY_TO_DIRECTION.get(event.code)
279
+ if direction and self.on_arrow_up:
280
+ self.on_arrow_up(self, direction)
281
+
282
+
283
+ class LabelControl(ControlBase):
284
+ """Label control for displaying text."""
285
+
286
+ def __init__(
287
+ self,
288
+ control_id: str,
289
+ ui_service: UiService,
290
+ text: str,
291
+ properties: dict[str, str] | None = None,
292
+ ) -> None:
293
+ """
294
+ Initialize label control.
295
+
296
+ Args:
297
+ control_id: Unique identifier
298
+ ui_service: Parent UiService
299
+ text: Label text
300
+ properties: Additional properties
301
+ """
302
+ props = properties or {}
303
+ props["Text"] = text
304
+ super().__init__(control_id, ControlType.LABEL, ui_service, props)
305
+
306
+ @property
307
+ def text(self) -> str:
308
+ """Get label text."""
309
+ return self.properties.get("Text", "")
310
+
311
+ @text.setter
312
+ def text(self, value: str) -> None:
313
+ """Set label text."""
314
+ self.set_property("Text", value)
315
+
316
+ @property
317
+ def typography(self) -> Typography:
318
+ """Get label typography."""
319
+ typo_str = self.properties.get("Typography", Typography.BODY1.value)
320
+ try:
321
+ return Typography(typo_str)
322
+ except ValueError:
323
+ return Typography.BODY1
324
+
325
+ @typography.setter
326
+ def typography(self, value: Typography | str) -> None:
327
+ """Set label typography."""
328
+ if isinstance(value, Typography):
329
+ self.set_property("Typography", value.value)
330
+ else:
331
+ # Try to find matching enum
332
+ for typo in Typography:
333
+ if typo.value == value:
334
+ self.set_property("Typography", value)
335
+ return
336
+ raise ValueError(f"Invalid typography value: {value}")
337
+
338
+ @property
339
+ def color(self) -> Color:
340
+ """Get label color."""
341
+ color_str = self.properties.get("Color", Color.TEXT_PRIMARY.value)
342
+ try:
343
+ return Color(color_str)
344
+ except ValueError:
345
+ return Color.TEXT_PRIMARY
346
+
347
+ @color.setter
348
+ def color(self, value: Color | str) -> None:
349
+ """Set label color."""
350
+ if isinstance(value, Color):
351
+ self.set_property("Color", value.value)
352
+ else:
353
+ # Try to find matching enum
354
+ for color in Color:
355
+ if color.value == value:
356
+ self.set_property("Color", value)
357
+ return
358
+ raise ValueError(f"Invalid color value: {value}")
359
+
360
+ def handle_event(self, event: Any) -> None:
361
+ """Labels typically don't handle events."""
362
+ pass