aqpxlib 0.1.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.
- aqpxlib/__init__.py +8 -0
- aqpxlib/abstract/__init__.py +13 -0
- aqpxlib/abstract/abstract_bus.py +551 -0
- aqpxlib/abstract/abstract_device.py +291 -0
- aqpxlib/abstract/abstract_instance.py +69 -0
- aqpxlib/channels.py +183 -0
- aqpxlib/event.py +25 -0
- aqpxlib/exceptions.py +24 -0
- aqpxlib/i2c/__init__.py +25 -0
- aqpxlib/i2c/i2c_32bit_register_target.py +83 -0
- aqpxlib/i2c/i2c_base_controller.py +301 -0
- aqpxlib/i2c/i2c_bus.py +132 -0
- aqpxlib/i3c/__init__.py +31 -0
- aqpxlib/i3c/ccc.py +9 -0
- aqpxlib/i3c/i3c_32bit_register_target.py +324 -0
- aqpxlib/i3c/i3c_base_controller.py +1618 -0
- aqpxlib/i3c/i3c_bus.py +142 -0
- aqpxlib/i3c/i3c_device_conf.py +6 -0
- aqpxlib/message/.gitignore +1 -0
- aqpxlib/message/__init__.py +1477 -0
- aqpxlib/sessions.py +202 -0
- aqpxlib/spi/__init__.py +25 -0
- aqpxlib/spi/spi_32bit_register_target.py +43 -0
- aqpxlib/spi/spi_base_controller.py +161 -0
- aqpxlib/spi/spi_bus.py +144 -0
- aqpxlib/toplevel.py +167 -0
- aqpxlib/uart/__init__.py +17 -0
- aqpxlib/uart/_uart_message.py +11 -0
- aqpxlib/uart/uart_bus.py +113 -0
- aqpxlib/uart/uart_rx.py +54 -0
- aqpxlib/uart/uart_tx.py +60 -0
- aqpxlib-0.1.0.dist-info/METADATA +46 -0
- aqpxlib-0.1.0.dist-info/RECORD +35 -0
- aqpxlib-0.1.0.dist-info/WHEEL +4 -0
- aqpxlib-0.1.0.dist-info/licenses/LICENSE +28 -0
aqpxlib/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright 2025 Acute Technology Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
"""Acute Protocol Exerciser Bus Instances."""
|
|
4
|
+
|
|
5
|
+
from aqpxlib.abstract.abstract_bus import PxAbstractBus
|
|
6
|
+
from aqpxlib.abstract.abstract_device import PxAbstractController, PxAbstractDevice, PxAbstractTarget
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"PxAbstractBus",
|
|
10
|
+
"PxAbstractController",
|
|
11
|
+
"PxAbstractDevice",
|
|
12
|
+
"PxAbstractTarget",
|
|
13
|
+
]
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# Copyright 2025 Acute Technology Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
"""Abstract Bus Class for all bus types."""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
10
|
+
|
|
11
|
+
import betterproto
|
|
12
|
+
|
|
13
|
+
import aqpxlib.message as pxmsg
|
|
14
|
+
from aqpxlib.abstract.abstract_device import PxAbstractDevice
|
|
15
|
+
from aqpxlib.abstract.abstract_instance import PxAbstractInstance
|
|
16
|
+
from aqpxlib.exceptions import PxError
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from aqpxlib.toplevel import AqProtocolExerciser
|
|
20
|
+
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PxAbstractBus(PxAbstractInstance):
|
|
25
|
+
"""Abstract Bus Class for all bus types."""
|
|
26
|
+
|
|
27
|
+
_available_devices: ClassVar[list[type[PxAbstractDevice]]] = []
|
|
28
|
+
|
|
29
|
+
controller_class: type[PxAbstractDevice]
|
|
30
|
+
|
|
31
|
+
@cached_property
|
|
32
|
+
def available_devices_by_protobuf_key(self):
|
|
33
|
+
"""A mapping of device protobuf field name to device classes."""
|
|
34
|
+
return {cls.protobuf_key: cls for cls in self._available_devices}
|
|
35
|
+
|
|
36
|
+
@cached_property
|
|
37
|
+
def available_devices_by_identifier(self):
|
|
38
|
+
"""A mapping of device identifier (from abstract instance) to device classes."""
|
|
39
|
+
return {cls.identifier: cls for cls in self._available_devices}
|
|
40
|
+
|
|
41
|
+
def __init__(self, exerciser: "AqProtocolExerciser") -> None:
|
|
42
|
+
"""Initialize an abstract bus."""
|
|
43
|
+
super().__init__(exerciser)
|
|
44
|
+
self._handle_index: int = -1
|
|
45
|
+
|
|
46
|
+
def _get_electrical_config(self) -> pxmsg.PxElectricalConfig | None:
|
|
47
|
+
"""Get the electrical configuration of the bus."""
|
|
48
|
+
assert self._handle_index != -1, "Bus handle index is not set"
|
|
49
|
+
|
|
50
|
+
_LOGGER.info("Getting electrical config for bus %d", self._handle_index)
|
|
51
|
+
msg = pxmsg.PxProtobufMsg(
|
|
52
|
+
get_electrical_config_request=pxmsg.PxElectricalConfigRequest(
|
|
53
|
+
handle_idx=self._handle_index,
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
response = self.exerciser.send_request(msg)
|
|
58
|
+
assert response is not None
|
|
59
|
+
return response.get_electrical_config_request.config
|
|
60
|
+
|
|
61
|
+
def _set_electrical_config(self, config: pxmsg.PxElectricalConfig) -> bool:
|
|
62
|
+
"""Set the electrical configuration of the bus."""
|
|
63
|
+
assert self._handle_index != -1, "Bus handle index is not set"
|
|
64
|
+
|
|
65
|
+
msg = pxmsg.PxProtobufMsg(
|
|
66
|
+
set_electrical_config_request=pxmsg.PxElectricalConfigRequest(
|
|
67
|
+
handle_idx=self._handle_index,
|
|
68
|
+
config=config,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
_ = self.exerciser.send_request(msg)
|
|
74
|
+
except PxError as e:
|
|
75
|
+
_LOGGER.exception("Failed to set electrical config: %s", e)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def handle_index(self) -> int:
|
|
82
|
+
"""The index of the bus interface."""
|
|
83
|
+
return self._handle_index
|
|
84
|
+
|
|
85
|
+
@handle_index.setter
|
|
86
|
+
def handle_index(self, handle_index: int):
|
|
87
|
+
"""Set the index of the bus interface."""
|
|
88
|
+
self._handle_index = handle_index
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def voltage(self) -> int | None:
|
|
92
|
+
"""The voltage of the bus."""
|
|
93
|
+
electrical_config = self._get_electrical_config()
|
|
94
|
+
if electrical_config is None:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
return electrical_config.voltage
|
|
98
|
+
|
|
99
|
+
@voltage.setter
|
|
100
|
+
def voltage(self, voltage: int):
|
|
101
|
+
"""Set the voltage of the bus."""
|
|
102
|
+
if voltage < 0:
|
|
103
|
+
err_msg = f"Invalid voltage value: {voltage}"
|
|
104
|
+
raise ValueError(err_msg)
|
|
105
|
+
|
|
106
|
+
config = pxmsg.PxElectricalConfig(
|
|
107
|
+
voltage=voltage,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if not self._set_electrical_config(config):
|
|
111
|
+
err_msg = f"Failed to set voltage: {voltage}"
|
|
112
|
+
raise ValueError(err_msg)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def la_threshold_voltage(self) -> int | None:
|
|
116
|
+
"""The la threshold voltage of the bus."""
|
|
117
|
+
electrical_config = self._get_electrical_config()
|
|
118
|
+
if electrical_config is None:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
return electrical_config.la_threshold_voltage
|
|
122
|
+
|
|
123
|
+
@la_threshold_voltage.setter
|
|
124
|
+
def la_threshold_voltage(self, voltage: int):
|
|
125
|
+
"""Set the la threshold voltage of the bus."""
|
|
126
|
+
if voltage < 0:
|
|
127
|
+
err_msg = f"Invalid voltage value: {voltage}"
|
|
128
|
+
raise ValueError(err_msg)
|
|
129
|
+
|
|
130
|
+
config = pxmsg.PxElectricalConfig(
|
|
131
|
+
la_threshold_voltage=voltage,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if not self._set_electrical_config(config):
|
|
135
|
+
err_msg = f"Failed to set la threshold voltage: {voltage}"
|
|
136
|
+
raise ValueError(err_msg)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def pullup_resistance(self) -> int | None:
|
|
140
|
+
"""The pullup resistance of the bus."""
|
|
141
|
+
electrical_config = self._get_electrical_config()
|
|
142
|
+
if electrical_config is None:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
return electrical_config.pullup_resistance
|
|
146
|
+
|
|
147
|
+
@pullup_resistance.setter
|
|
148
|
+
def pullup_resistance(self, resistance: int):
|
|
149
|
+
"""Set the pullup resistance of the bus."""
|
|
150
|
+
if resistance < 0:
|
|
151
|
+
err_msg = f"Invalid resistance value: {resistance}"
|
|
152
|
+
raise ValueError(err_msg)
|
|
153
|
+
|
|
154
|
+
config = pxmsg.PxElectricalConfig(
|
|
155
|
+
pullup_resistance=resistance,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not self._set_electrical_config(config):
|
|
159
|
+
err_msg = f"Failed to set pullup resistance: {resistance}"
|
|
160
|
+
raise ValueError(err_msg)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def pulldown_resistance(self) -> int | None:
|
|
164
|
+
"""The pulldown resistance of the bus."""
|
|
165
|
+
electrical_config = self._get_electrical_config()
|
|
166
|
+
if electrical_config is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
return electrical_config.pulldown_resistance
|
|
170
|
+
|
|
171
|
+
@pulldown_resistance.setter
|
|
172
|
+
def pulldown_resistance(self, resistance: int):
|
|
173
|
+
"""Set the pulldown resistance of the bus."""
|
|
174
|
+
if resistance < 0:
|
|
175
|
+
err_msg = f"Invalid resistance value: {resistance}"
|
|
176
|
+
raise ValueError(err_msg)
|
|
177
|
+
|
|
178
|
+
config = pxmsg.PxElectricalConfig(
|
|
179
|
+
pulldown_resistance=resistance,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if not self._set_electrical_config(config):
|
|
183
|
+
err_msg = f"Failed to set pulldown resistance: {resistance}"
|
|
184
|
+
raise ValueError(err_msg)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def bus_hold_enable(self) -> bool | None:
|
|
188
|
+
"""The bus hold enable of the bus."""
|
|
189
|
+
electrical_config = self._get_electrical_config()
|
|
190
|
+
if electrical_config is None:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
return electrical_config.bus_hold_enable
|
|
194
|
+
|
|
195
|
+
@bus_hold_enable.setter
|
|
196
|
+
def bus_hold_enable(self, enable: bool):
|
|
197
|
+
"""Set the bus hold enable of the bus."""
|
|
198
|
+
config = pxmsg.PxElectricalConfig(
|
|
199
|
+
bus_hold_enable=enable,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if not self._set_electrical_config(config):
|
|
203
|
+
err_msg = f"Failed to set bus hold enable: {enable}"
|
|
204
|
+
raise ValueError(err_msg)
|
|
205
|
+
|
|
206
|
+
def get_config(self) -> pxmsg.PxHandle | None:
|
|
207
|
+
"""Get the configuration of the bus."""
|
|
208
|
+
assert self._handle_index != -1, "Bus handle index is not set"
|
|
209
|
+
|
|
210
|
+
handle_config = self.exerciser.get_handle(self._handle_index)
|
|
211
|
+
|
|
212
|
+
bus_config = handle_config
|
|
213
|
+
bus_key, bus_config = betterproto.which_one_of(bus_config, "protocol")
|
|
214
|
+
if bus_key == self.protobuf_key:
|
|
215
|
+
return handle_config
|
|
216
|
+
_LOGGER.exception("Bus configuration is not supported")
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
def get_target_pb_msg(
|
|
220
|
+
self,
|
|
221
|
+
target: PxAbstractDevice,
|
|
222
|
+
) -> betterproto.Message | None:
|
|
223
|
+
"""Get the configuration of the target from the bus."""
|
|
224
|
+
config = self.get_config()
|
|
225
|
+
if config is None:
|
|
226
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
for target_config in config.targets:
|
|
230
|
+
if target_config.id == target.device_id:
|
|
231
|
+
return target_config
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def get_controller_pb_msg(
|
|
235
|
+
self,
|
|
236
|
+
controller: PxAbstractDevice,
|
|
237
|
+
) -> betterproto.Message | None:
|
|
238
|
+
"""Get the configuration of the controller from the bus."""
|
|
239
|
+
controllers_config = self.get_config().controllers
|
|
240
|
+
for controller_config in controllers_config:
|
|
241
|
+
if controller_config.id == controller.device_id:
|
|
242
|
+
return controller_config
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def remove_controller(self, controller: PxAbstractDevice):
|
|
246
|
+
"""Remove a controller from the bus."""
|
|
247
|
+
config = self.get_config()
|
|
248
|
+
if config is None:
|
|
249
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
config.controllers = [
|
|
253
|
+
controller_pb_msg
|
|
254
|
+
for controller_pb_msg in config.controllers
|
|
255
|
+
if controller_pb_msg.id != controller.device_id
|
|
256
|
+
]
|
|
257
|
+
self.set_config(config)
|
|
258
|
+
|
|
259
|
+
def remove_target(self, target: PxAbstractDevice):
|
|
260
|
+
"""Remove a target from the bus."""
|
|
261
|
+
config = self.get_config()
|
|
262
|
+
if config is None:
|
|
263
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
config.targets = [target_pb_msg for target_pb_msg in config.targets if target_pb_msg.id != target.device_id]
|
|
267
|
+
|
|
268
|
+
self.set_config(config)
|
|
269
|
+
|
|
270
|
+
def remove_device(self, device: PxAbstractDevice):
|
|
271
|
+
"""Remove a device from the bus."""
|
|
272
|
+
if self.controller_class is not None and isinstance(
|
|
273
|
+
device,
|
|
274
|
+
self.controller_class,
|
|
275
|
+
):
|
|
276
|
+
self.remove_controller(device)
|
|
277
|
+
else:
|
|
278
|
+
self.remove_target(device)
|
|
279
|
+
|
|
280
|
+
def get_device_state(
|
|
281
|
+
self,
|
|
282
|
+
device: PxAbstractDevice,
|
|
283
|
+
) -> betterproto.Message | None:
|
|
284
|
+
"""Get the state of the device from the bus."""
|
|
285
|
+
if type(device) not in self._available_devices:
|
|
286
|
+
err_msg = f"Device {device} is not supported on {self} bus."
|
|
287
|
+
raise ValueError(err_msg)
|
|
288
|
+
|
|
289
|
+
msg = pxmsg.PxProtobufMsg(
|
|
290
|
+
px_device_operation=pxmsg.PxDeviceOperationRequest(
|
|
291
|
+
handle_idx=self.handle_index,
|
|
292
|
+
device_id=device.device_id,
|
|
293
|
+
op=pxmsg.PxDeviceOperation(
|
|
294
|
+
px_device_get_state=pxmsg.PxDeviceState(),
|
|
295
|
+
),
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
response = self.exerciser.send_request(
|
|
300
|
+
msg,
|
|
301
|
+
)
|
|
302
|
+
assert response is not None
|
|
303
|
+
dev_state = response.px_device_operation.op.px_device_get_state
|
|
304
|
+
_, state = betterproto.which_one_of(dev_state, "state")
|
|
305
|
+
return state
|
|
306
|
+
|
|
307
|
+
def set_device_state(self, device: PxAbstractDevice, state: betterproto.Message):
|
|
308
|
+
"""Set the state of the device on the bus."""
|
|
309
|
+
if type(device) not in self._available_devices:
|
|
310
|
+
err_msg = f"Device {device} is not supported on {self} bus."
|
|
311
|
+
raise ValueError(err_msg)
|
|
312
|
+
|
|
313
|
+
msg = pxmsg.PxProtobufMsg(
|
|
314
|
+
px_device_operation=pxmsg.PxDeviceOperationRequest(
|
|
315
|
+
handle_idx=self.handle_index,
|
|
316
|
+
device_id=device.device_id,
|
|
317
|
+
op=pxmsg.PxDeviceOperation(
|
|
318
|
+
px_device_set_state=pxmsg.PxDeviceState(
|
|
319
|
+
**{
|
|
320
|
+
device.protobuf_key: state,
|
|
321
|
+
},
|
|
322
|
+
),
|
|
323
|
+
),
|
|
324
|
+
),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
_ = self.exerciser.send_request(msg)
|
|
328
|
+
|
|
329
|
+
def get_device_pb_msg(
|
|
330
|
+
self,
|
|
331
|
+
device: PxAbstractDevice,
|
|
332
|
+
) -> betterproto.Message | None:
|
|
333
|
+
"""Get the configuration of the device from the bus."""
|
|
334
|
+
if isinstance(device, self.controller_class):
|
|
335
|
+
return self.get_controller_pb_msg(device)
|
|
336
|
+
return self.get_target_pb_msg(device)
|
|
337
|
+
|
|
338
|
+
def set_config(self, config: pxmsg.PxHandle) -> pxmsg.PxHandle:
|
|
339
|
+
"""Set the configuration of the bus."""
|
|
340
|
+
assert self._handle_index != -1, "Bus handle index is not set"
|
|
341
|
+
|
|
342
|
+
return self.exerciser.set_handle(self._handle_index, config)
|
|
343
|
+
|
|
344
|
+
def get_controllers(self) -> list[PxAbstractDevice]:
|
|
345
|
+
"""Get the controllers of the bus."""
|
|
346
|
+
config = self.get_config()
|
|
347
|
+
if config is None:
|
|
348
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
349
|
+
return []
|
|
350
|
+
|
|
351
|
+
if hasattr(config, "controllers"):
|
|
352
|
+
# TODO: Handle buses that have more than 1 controller
|
|
353
|
+
for controller_config in config.controllers:
|
|
354
|
+
if self.controller_class is not None:
|
|
355
|
+
controller = self.controller_class(
|
|
356
|
+
self.exerciser,
|
|
357
|
+
bus=self,
|
|
358
|
+
device_id=controller_config.id,
|
|
359
|
+
)
|
|
360
|
+
return [controller]
|
|
361
|
+
|
|
362
|
+
_LOGGER.info("Can not find any controllers")
|
|
363
|
+
return []
|
|
364
|
+
|
|
365
|
+
def attach_controller(self, controller: PxAbstractDevice):
|
|
366
|
+
"""Attach a controller to the bus."""
|
|
367
|
+
config = self.get_config()
|
|
368
|
+
if config is None:
|
|
369
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
370
|
+
return
|
|
371
|
+
if hasattr(config, "controller") and betterproto.serialized_on_wire(
|
|
372
|
+
config.controller,
|
|
373
|
+
):
|
|
374
|
+
err_msg = f"Bus already has a controller, config = {config}"
|
|
375
|
+
raise ValueError(err_msg)
|
|
376
|
+
|
|
377
|
+
config.controllers.append(
|
|
378
|
+
pxmsg.PxControllerDevice.from_dict(
|
|
379
|
+
{
|
|
380
|
+
"id": 0,
|
|
381
|
+
"name": controller.name,
|
|
382
|
+
controller.protobuf_key: controller.config.to_dict(),
|
|
383
|
+
},
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
controller.device_id = self.set_config(config).controllers[len(config.controllers) - 1].id
|
|
387
|
+
controller.bus = self
|
|
388
|
+
|
|
389
|
+
def attach_target(self, target: PxAbstractDevice):
|
|
390
|
+
"""Attach a target to the bus."""
|
|
391
|
+
config = self.get_config()
|
|
392
|
+
if config is None:
|
|
393
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
394
|
+
return
|
|
395
|
+
config.targets.append(
|
|
396
|
+
pxmsg.PxTargetDevice.from_dict(
|
|
397
|
+
{
|
|
398
|
+
"id": 0,
|
|
399
|
+
"name": target.name,
|
|
400
|
+
target.protobuf_key: target.config.to_dict(),
|
|
401
|
+
},
|
|
402
|
+
),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
target.device_id = self.set_config(config).targets[len(config.targets) - 1].id
|
|
406
|
+
target.bus = self
|
|
407
|
+
|
|
408
|
+
def get_targets(self) -> list[PxAbstractDevice]:
|
|
409
|
+
"""Get the targets of the bus."""
|
|
410
|
+
config = self.get_config()
|
|
411
|
+
if config is None:
|
|
412
|
+
_LOGGER.exception("Failed to get bus configuration")
|
|
413
|
+
return []
|
|
414
|
+
|
|
415
|
+
targets = []
|
|
416
|
+
for target_pb_msg in config.targets:
|
|
417
|
+
pb_key, _ = betterproto.which_one_of(target_pb_msg, "config")
|
|
418
|
+
target_class = self.available_devices_by_protobuf_key.get(pb_key, None)
|
|
419
|
+
if target_class is not None:
|
|
420
|
+
target = target_class(
|
|
421
|
+
self.exerciser,
|
|
422
|
+
bus=self,
|
|
423
|
+
device_id=target_pb_msg.id,
|
|
424
|
+
)
|
|
425
|
+
targets.append(target)
|
|
426
|
+
return targets
|
|
427
|
+
|
|
428
|
+
def get_devices(self) -> list[PxAbstractDevice]:
|
|
429
|
+
"""Get the devices of the bus."""
|
|
430
|
+
return self.get_controllers() + self.get_targets()
|
|
431
|
+
|
|
432
|
+
def create_device(
|
|
433
|
+
self,
|
|
434
|
+
exerciser: "AqProtocolExerciser",
|
|
435
|
+
device_identifier: str,
|
|
436
|
+
*args,
|
|
437
|
+
**kwargs,
|
|
438
|
+
) -> PxAbstractDevice:
|
|
439
|
+
"""Create a new device on the bus.
|
|
440
|
+
|
|
441
|
+
A new device interface will be created and attached to the bus.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
exerciser (AqProtocolExerciser): An exerciser instance which is used to send request to server
|
|
445
|
+
device_identifier (str): The unique identifier of the device to create
|
|
446
|
+
*args: Additional arguments to pass to the device constructor
|
|
447
|
+
**kwargs: Additional keyword arguments to pass to the device constructor
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
PxAbstractDevice: The interface of the created device
|
|
451
|
+
|
|
452
|
+
Raises:
|
|
453
|
+
ValueError: If the `device_identifier` is not supported
|
|
454
|
+
|
|
455
|
+
"""
|
|
456
|
+
if device_identifier not in self.available_devices_by_identifier:
|
|
457
|
+
err_msg = f"Device {device_identifier} not supported"
|
|
458
|
+
raise ValueError(err_msg)
|
|
459
|
+
|
|
460
|
+
dev = self.available_devices_by_identifier[device_identifier](
|
|
461
|
+
exerciser,
|
|
462
|
+
*args,
|
|
463
|
+
**kwargs,
|
|
464
|
+
)
|
|
465
|
+
dev.attach_to_bus(self)
|
|
466
|
+
|
|
467
|
+
return dev
|
|
468
|
+
|
|
469
|
+
@abstractmethod
|
|
470
|
+
def _to_protobuf_handle_config(self, *args, **kwargs) -> pxmsg.PxHandle:
|
|
471
|
+
"""Convert the bus to a protobuf handle configuration.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
*args: Additional arguments to pass to the bus constructor
|
|
475
|
+
**kwargs: Additional keyword arguments to pass to the bus constructor
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
pxmsg.PxHandle: The protobuf structure of the bus
|
|
479
|
+
|
|
480
|
+
Raises:
|
|
481
|
+
NotImplementedError: If the bus subclass does not implement this method
|
|
482
|
+
|
|
483
|
+
"""
|
|
484
|
+
err_msg = "Bus subclass must implement this method"
|
|
485
|
+
raise NotImplementedError(err_msg)
|
|
486
|
+
|
|
487
|
+
def __eq__(self, other: object) -> bool:
|
|
488
|
+
"""Check if the bus is equal to another bus.
|
|
489
|
+
|
|
490
|
+
This method is used to check if the bus is equal to another bus.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
other (Any): The other bus to compare with or a protobuf structure of the bus
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
NotImplementedError: If the bus subclass does not implement this method
|
|
497
|
+
|
|
498
|
+
"""
|
|
499
|
+
err_msg = "Bus subclass must implement this method"
|
|
500
|
+
raise NotImplementedError(err_msg)
|
|
501
|
+
|
|
502
|
+
def _find_or_create_bus(self, *args, **kwargs) -> int:
|
|
503
|
+
"""Find or create a bus interface.
|
|
504
|
+
|
|
505
|
+
This method is used to find an available handle slot and create a new bus interface.
|
|
506
|
+
It the bus handle does not exist, a new bus interface will be created.
|
|
507
|
+
Otherwise, the existing bus interface will be returned.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
*args: Additional arguments to pass to the bus constructor
|
|
511
|
+
**kwargs: Additional keyword arguments to pass to the bus constructor
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
int: The index of the bus interface if found, otherwise -1
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
ValueError: If no empty slot is found
|
|
518
|
+
|
|
519
|
+
"""
|
|
520
|
+
handles = self.exerciser.get_handles()
|
|
521
|
+
if handles is None:
|
|
522
|
+
_LOGGER.exception("Failed to get handles")
|
|
523
|
+
return -1
|
|
524
|
+
|
|
525
|
+
available_handle_slot = -1
|
|
526
|
+
for idx, handle in enumerate(handles):
|
|
527
|
+
name, current_handle = betterproto.which_one_of(handle, "protocol")
|
|
528
|
+
if name == self.protobuf_key:
|
|
529
|
+
if self == current_handle:
|
|
530
|
+
return idx
|
|
531
|
+
elif name in ["resv", ""]:
|
|
532
|
+
# Compare with reserved handle and unset handles
|
|
533
|
+
available_handle_slot = idx if available_handle_slot == -1 else available_handle_slot
|
|
534
|
+
|
|
535
|
+
_LOGGER.info("No existing bus found, creating a new bus")
|
|
536
|
+
if available_handle_slot == -1:
|
|
537
|
+
_LOGGER.exception("No empty slot found, cannot create a new bus")
|
|
538
|
+
return -1
|
|
539
|
+
|
|
540
|
+
# Update topology
|
|
541
|
+
new_bus = self._to_protobuf_handle_config(*args, **kwargs)
|
|
542
|
+
self.exerciser.set_handle(available_handle_slot, new_bus)
|
|
543
|
+
|
|
544
|
+
_LOGGER.info("New bus created at index %d", available_handle_slot)
|
|
545
|
+
|
|
546
|
+
return available_handle_slot
|
|
547
|
+
|
|
548
|
+
def add_event_handler(self, event: str, handler: Callable[[Any], Any]):
|
|
549
|
+
"""Add an event handler to the current bus instance."""
|
|
550
|
+
err_msg = "Bus subclass must implement this method"
|
|
551
|
+
raise NotImplementedError(err_msg)
|