pybrid-computing 0.10.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.
@@ -0,0 +1,726 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ import logging
6
+ import typing
7
+ from datetime import datetime
8
+ from functools import cache
9
+
10
+ import inflection
11
+ from pydantic import UUID4, BaseModel, Field
12
+
13
+ from ..entities import Path
14
+ from ..run import RunConfig, RunFlags, RunState, DAQConfig
15
+ from .types import SuccessInfo
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ # ██████ █████ ███████ ███████ ██████ ██ █████ ███████ ███████ ███████ ███████
21
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
22
+ # ██████ ███████ ███████ █████ ██ ██ ███████ ███████ ███████ █████ ███████
23
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
24
+ # ██████ ██ ██ ███████ ███████ ██████ ███████ ██ ██ ███████ ███████ ███████ ███████
25
+
26
+ TYPE_IDENTIFIER_MESSAGE_MAP = dict()
27
+
28
+
29
+ class Message(BaseModel):
30
+ """
31
+ Base class for all messages.
32
+
33
+ Serialization and deserialization is handled with the help of the :code:`pydantic` package.
34
+ """
35
+
36
+ @classmethod
37
+ def parse_obj(cls, **data: dict) -> 'Message':
38
+ """Parses data from `dict` or `json`-like and returns a message instance."""
39
+ ...
40
+
41
+ @classmethod
42
+ def register_callback(cls, callback: typing.Callable) -> typing.Callable:
43
+ """Register a callback for some :py:class:`Message` subclass, which is triggered when the respective message
44
+ is received.
45
+
46
+ :parameter callback: Function to register as callback
47
+ :returns: The original function
48
+ :Usage: Intended to be used as decorator:
49
+
50
+ .. code-block::
51
+
52
+ # Register callback triggered on e.g. an incoming RunStateChangeMessage
53
+ @RunStateChangeMessage.register_callback
54
+ def callback(self, msg: RunStateChangeMessage):
55
+ # do something with the message
56
+
57
+ """
58
+ ...
59
+
60
+ @classmethod
61
+ @cache
62
+ def get_type_identifier(cls):
63
+ pascal_case = cls.__name__.removesuffix("Message").removesuffix("Request").removesuffix("Response")
64
+ snake_case = inflection.underscore(pascal_case)
65
+ return snake_case
66
+
67
+ @staticmethod
68
+ def get_class_for_type_identifier(type_):
69
+ return TYPE_IDENTIFIER_MESSAGE_MAP[type_]
70
+
71
+ def __init_subclass__(cls, **kwargs):
72
+ super().__init_subclass__(**kwargs)
73
+ TYPE_IDENTIFIER_MESSAGE_MAP[cls.get_type_identifier()] = cls
74
+
75
+
76
+ REQUEST_RESPONSE_MAP = dict()
77
+
78
+
79
+ class Request(Message):
80
+ """
81
+ Base class for requests sent to the controller.
82
+
83
+ .. uml::
84
+
85
+ Client -> Controller: **Request(...)**
86
+ Controller -> Client: Response(...)
87
+
88
+ """
89
+ @classmethod
90
+ def get_expected_response_type(cls) -> typing.Type["Response"]:
91
+ """The :py:class:`Response` subclass expected for the answer to this request."""
92
+ return REQUEST_RESPONSE_MAP[cls]
93
+
94
+
95
+ class Response(Message):
96
+ """
97
+ Base class for responses to a previous request.
98
+
99
+ .. uml::
100
+
101
+ Client -> Controller: Request(...)
102
+ Controller -> Client: **Response(...)**
103
+
104
+ """
105
+ #: The :py:class:`Request` subclass to which this is the response
106
+ response_for: typing.ClassVar[Request]
107
+
108
+ @property
109
+ def successful(self) -> bool:
110
+ """Indicates whether the request was handled successfully"""
111
+ return not bool(self.first_error)
112
+
113
+ @property
114
+ def first_error(self) -> typing.Optional[str]:
115
+ """Error message of the first error that occurred when the request was handled.
116
+ `None` if there was no error."""
117
+ return None
118
+
119
+ def __init_subclass__(cls, **kwargs):
120
+ super().__init_subclass__(**kwargs)
121
+ REQUEST_RESPONSE_MAP[cls.response_for] = cls
122
+
123
+
124
+ # ██ ██ ████████ ██ ██ ██ ████████ ██ ██
125
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██
126
+ # ██ ██ ██ ██ ██ ██ ██ ████
127
+ # ██ ██ ██ ██ ██ ██ ██ ██
128
+ # ██████ ██ ██ ███████ ██ ██ ██
129
+
130
+
131
+ class PingRequest(Request):
132
+ """
133
+ A heartbeat request to check for controller status.
134
+ The controller replies with a :py:class:`PingResponse` message.
135
+
136
+ .. uml::
137
+
138
+ Client -> Controller: **PingRequest(...)**
139
+ Controller -> Client: PingResponse(...)
140
+
141
+ """
142
+ #: A timestamp used to synchronize client and controller clocks.
143
+ now: datetime = Field(default_factory=datetime.utcnow)
144
+
145
+
146
+ class PingResponse(Response):
147
+ """
148
+ A heartbeat response to an incoming :py:class:`PingRequest` message.
149
+
150
+ .. uml::
151
+
152
+ Client -> Controller: PingRequest(...)
153
+ Controller -> Client: **PingResponse(...)**
154
+
155
+ """
156
+ response_for = PingRequest
157
+ #: A timestamp used to synchronize client and controller clocks.
158
+ #: The controller returns its timestamp so the client can check if it was applied correctly.
159
+ now: datetime = Field(default_factory=datetime.utcnow)
160
+
161
+
162
+ class HackRequest(Request):
163
+ """
164
+ A message which may contain an arbitrary command and data,
165
+ intended for development purposes only!
166
+ """
167
+ command: str
168
+ data: typing.Any
169
+
170
+
171
+ class HackResponse(Response):
172
+ response_for = HackRequest
173
+ data: typing.Any
174
+
175
+
176
+ # ██ ███ ██ ██ ████████ ██ █████ ██ ██ ███████ █████ ████████ ██ ██████ ███ ██
177
+ # ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ████ ██
178
+ # ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ███ ███████ ██ ██ ██ ██ ██ ██ ██
179
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██
180
+ # ██ ██ ████ ██ ██ ██ ██ ██ ███████ ██ ███████ ██ ██ ██ ██ ██████ ██ ████
181
+
182
+
183
+ class ResetRequest(Request):
184
+ """
185
+ A request for the hybrid controller to reset its configuration.
186
+
187
+ Depending on the flags inside the message, only part of the configuration is reset.
188
+ The :attr:`sync` flag can be used to prevent actually writing the configuration to the hardware,
189
+ i.e. it is kept in memory only (useful if subsequent requests change it anyway).
190
+
191
+ .. uml::
192
+
193
+ Client -> Controller: **ResetRequest()**
194
+ activate Controller
195
+ note over Controller: resets system
196
+ Controller -> Client: ResetResponse()
197
+ deactivate Controller
198
+ """
199
+ #: Whether the calibration data should be kept.
200
+ keep_calibration: typing.Optional[bool] = True
201
+ #: Whether to immediately sync to hardware.
202
+ sync: typing.Optional[bool] = True
203
+
204
+
205
+ class ResetResponse(Response):
206
+ """
207
+ A response to a previous :class:`ResetRequest`.
208
+ """
209
+ response_for = ResetRequest
210
+
211
+
212
+ class GetEntitiesRequest(Request):
213
+ """
214
+ A request for the list of entity types (:py:class:`pybrid.redac.entities.EntityType`)
215
+ by their path (:class:`pybrid.redac.entities.Path`).
216
+ The controller responds with a :py:class:`GetEntitiesResponse` containing a tree-like representation of all entities.
217
+
218
+ The :class:`GetEntitiesResponse` tells you the current assembly structure of the analog computer.
219
+ Use a series of :class:`GetEntityConfiguration` messages if you also need to know the current configuration.
220
+
221
+ .. uml::
222
+
223
+ Client -> Controller: **GetEntitiesRequest()**
224
+ activate Controller
225
+ note over Controller: scans system
226
+ Controller -> Client: GetEntitiesResponse()
227
+ deactivate Controller
228
+
229
+ """
230
+ pass
231
+
232
+
233
+ class GetEntitiesResponse(Response):
234
+ """
235
+ A response containing the list of entities (:py:class:`pybrid.redac.entities.EntityType`)
236
+ currently in the analog computer.
237
+
238
+ .. uml::
239
+
240
+ Client -> Controller: GetEntitiesRequest()
241
+ activate Controller
242
+ note over Controller: scans system
243
+ Controller -> Client: **GetEntitiesResponse()**
244
+ deactivate Controller
245
+
246
+ """
247
+ response_for = GetEntitiesRequest
248
+ #: A tree-like dictionary structure containing entity type information by path.
249
+ entities: dict
250
+
251
+
252
+ # ███████ ███████ ███████ ███████ ██ ██████ ███ ██
253
+ # ██ ██ ██ ██ ██ ██ ██ ████ ██
254
+ # ███████ █████ ███████ ███████ ██ ██ ██ ██ ██ ██
255
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
256
+ # ███████ ███████ ███████ ███████ ██ ██████ ██ ████
257
+
258
+
259
+ class StartSessionRequest(Request):
260
+ """
261
+ Request to start a session for which the requested elements are reserved.
262
+ No other client can request those elements until the session is ended.
263
+
264
+ Starting and managing sessions is only necessary if your controller is configured to require it.
265
+
266
+ .. uml::
267
+
268
+ participant "Client A" as C1
269
+ participant "Client B" as C2
270
+ participant "Controller" as CTRL
271
+
272
+ C1 -> CTRL: StartSessionRequest(entities=[<X>, <Y>])
273
+ CTRL -> C1: StartSessionResponse(id_=<secret>, success=True)
274
+ ...during active session of Client A...
275
+ C2 -> CTRL: StartSessionRequest(entities=[<X>, <Z>])
276
+ CTRL -> C2: StartSessionResponse(success=False, error="X reserved.")
277
+ ...
278
+ C1 -> CTRL: EndSessionRequest(id_=<secret>)
279
+ CTRL -> C1: EndSessionResponse(success=True)
280
+
281
+ """
282
+ #: A list of analog entities to reserve for this session.
283
+ entities: list[Path]
284
+
285
+
286
+ class StartSessionResponse(Response):
287
+ """
288
+ Response to a prior :class:`StartSessionRequest`.
289
+
290
+ If the reservation was successful, a secret ID is returned.
291
+ This ID is used in subsequent configuration and run requests to authorize their usage of reserved entities.
292
+
293
+ If not all requested entities could be reserved for the new session, the session is not started.
294
+ """
295
+ response_for = StartSessionRequest
296
+ #: Secret session ID or None if the session could not be started.
297
+ id_: typing.Optional[UUID4]
298
+ #: Whether the session could be started and optional error info.
299
+ success: SuccessInfo
300
+
301
+
302
+ class EndSessionRequest(Request):
303
+ """
304
+ Request to end a session.
305
+
306
+ If there are any ongoing runs in the session, they are canceled first and any messages related to them are sent
307
+ first by the controller, before the corresponding :class:`EndSessionResponse` is sent.
308
+
309
+ Inactive sessions may be ended automatically depending on controller configuration.
310
+
311
+ .. uml::
312
+
313
+ participant "Client" as C
314
+ participant "Controller" as CTRL
315
+
316
+ C -> CTRL: EndSessionRequest(id_=<secret>)
317
+ activate CTRL
318
+ alt if ongoing requests
319
+ note over CTRL: cancels any ongoing runs in this session
320
+ CTRL -> C: RunStateChangeMessage(new=ERROR, ...)
321
+ end
322
+ CTRL -> C: EndSessionResponse(success=True)
323
+ deactivate CTRL
324
+
325
+ """
326
+ #: The secret session ID to end.
327
+ id_: UUID4
328
+
329
+
330
+ class EndSessionResponse(Response):
331
+ """
332
+ Response to a prior :class:`EndSessionRequest`.
333
+ """
334
+ response_for = EndSessionRequest
335
+ #: Whether the session could be ended and optional error info. Usually True.
336
+ success: SuccessInfo
337
+
338
+
339
+ class EntityReservationRequest(Request):
340
+ """
341
+ Request to reserve additional entities for an existing session.
342
+ """
343
+ #: Secret session ID
344
+ id_: UUID4
345
+ #: A list of analog entities to reserve for this session.
346
+ entities: list[Path]
347
+
348
+
349
+ class EntityReservationResponse(Response):
350
+ """
351
+ Response to a prior :class:`EntityReservationRequest`.
352
+ """
353
+ response_for = EntityReservationRequest
354
+ #: Whether the requested entities were reserved and error information if they were not.
355
+ success: SuccessInfo
356
+
357
+
358
+ # ██████ ██████ ███ ██ ███████ ██ ██████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██
359
+ # ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
360
+ # ██ ██ ██ ██ ██ ██ █████ ██ ██ ███ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ ██ ██
361
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
362
+ # ██████ ██████ ██ ████ ██ ██ ██████ ██████ ██ ██ ██ ██ ██ ██ ██████ ██ ████
363
+
364
+
365
+ class SetConfigRequest(Request):
366
+ """
367
+ A request to the controller to set a configuration for an entity.
368
+ The controller forwards the request to the carrier board on which the entity is located
369
+ and forwards its :class:`SetConfigResponse` response back.
370
+
371
+ .. uml::
372
+
373
+ participant "Client" as C
374
+ participant "Controller" as CTRL
375
+ participant "Carrier Board\\n(00:00:5e:00:53:af, )" as CB
376
+ participant "Entity\\n(00:00:5e:00:53:af, 7, 42)" as E
377
+
378
+ C -> CTRL: SetConfigRequest(\\n entity=(00:00:5e:00:53:af, 7, 42), ...\\n)
379
+ activate CTRL
380
+ CTRL -> CB: SetConfigRequest(entity=(7, 42), ...)
381
+ activate CB
382
+ CB <-> E: <entity specific data via SPI>
383
+ CTRL <- CB: SetConfigResponse(...)
384
+ deactivate CB
385
+ C <- CTRL: SetConfigResponse(...)
386
+ deactivate CTRL
387
+
388
+ Entities are arranged hierarchically in the REDAC.
389
+ The config dictionary is passed to the entity defined by :attr:`SetConfigRequest.entity`.
390
+ To allow the configuration of multiple entities, the :attr:`SetConfigRequest.config` dictionary
391
+ may contain keys starting with a slash ("/"), which are used to denote paths to sub-entities.
392
+ Such sub-entity path keys must denote a sub-config dictionary, which is again passed on.
393
+
394
+ The structure and content of the configuration message depend on the entities to be configured.
395
+ See :doc:`/redac/configurations` for details.
396
+
397
+ Example of a multi-entity :class:`SetConfigRequest` message.
398
+
399
+ .. code-block:: json
400
+
401
+ {
402
+ "_id": 42, "_type": "set_config", "msg": {
403
+ "entity": ["04-E9-E5-14-74-BF", "0"],
404
+ "config": {
405
+ "/U": {
406
+ "outputs": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
407
+ "alt-signals": [3, 8],
408
+ },
409
+ "/C": {
410
+ "elements": {0: 0.42, 3: -0.42}
411
+ }
412
+ }
413
+ }
414
+ }
415
+ """
416
+ #: The secret session ID for which the entity was reserved. Only required if session management is enabled.
417
+ session: typing.Optional[UUID4]
418
+ #: The entity to configure.
419
+ entity: Path
420
+ #: The configuration to apply.
421
+ #: May contain keys denoting paths to sub-entities (starting with a slash) and their config.
422
+ #: The data schema of the configuration depends on the type of entity,
423
+ #: see :doc:`/redac/configurations` for details.
424
+ config: dict
425
+
426
+ @classmethod
427
+ def make(cls, entity):
428
+ """Factory method to create a config request for some entity."""
429
+ ...
430
+
431
+
432
+ class SetConfigResponse(Response):
433
+ """A response to :py:class:`SetConfigRequest` conveying the success of the latter.
434
+
435
+ .. uml::
436
+
437
+ Client -> Controller: SetConfigRequest(...)
438
+ activate Controller
439
+ note over Controller: sets entity config
440
+ Controller -> Client: **SetConfigResponse**(...)
441
+ deactivate Controller
442
+ """
443
+ response_for = SetConfigRequest
444
+
445
+
446
+ class GetConfigRequest(Request):
447
+ """
448
+ A request to the controller to retrieve the configuration of an entity.
449
+ The controller responds with a :py:class:`GetConfigResponse`.
450
+ This configuration includes only the effective analog configuration
451
+ (e.g. the scalar factor of a digital potentiometer).
452
+ For any metadata (e.g. calibration) use :class:`GetMetadataRequest`.
453
+
454
+ .. uml::
455
+
456
+ Client -> Controller: **GetConfigRequest**(...)
457
+ activate Controller
458
+ note over Controller: gets entity config
459
+ Controller -> Client: GetConfigResponse(...)
460
+ deactivate Controller
461
+ """
462
+ #: Path to the entity of which the configuration should be returned.
463
+ entity: Path
464
+ #: Whether to retrieve the configuration of sub-entities recursively.
465
+ recursive: bool = True
466
+
467
+
468
+ class GetConfigResponse(Response):
469
+ """A response to :py:class:`GetConfigRequest` conveying the configuration of some entity.
470
+
471
+ .. uml::
472
+
473
+ Client -> Controller: GetConfigRequest(..)
474
+ activate Controller
475
+ note over Controller: gets entity config
476
+ Controller -> Client: **GetConfigResponse**(...)
477
+ deactivate Controller
478
+ """
479
+ response_for = GetConfigRequest
480
+ #: Path to the entity of which the configuration is returned.
481
+ entity: Path
482
+ #: The configuration of the entity.
483
+ #: The data schema of the configuration depends on the type of entity.
484
+ config: dict
485
+
486
+
487
+ class GetMetadataRequest(Request):
488
+ """
489
+ A request to the controller to retrieve the metadata of an entity.
490
+ The controller responds with a :py:class:`GetMetadataResponse`.
491
+ The metadata contains entity-specific information (e.g. type identifier, calibration, ...).
492
+
493
+ .. uml::
494
+
495
+ Client -> Controller: **GetMetadataRequest**(...)
496
+ activate Controller
497
+ note over Controller: gets entity metadata
498
+ Controller -> Client: GetMetadataResponse(...)
499
+ deactivate Controller
500
+ """
501
+ #: Path to the entity of which the metadata should be returned.
502
+ entity: Path
503
+
504
+
505
+ class GetMetadataResponse(Response):
506
+ """A response to :py:class:`GetMetadataRequest` conveying the metadata of some entity.
507
+
508
+ .. uml::
509
+
510
+ Client -> Controller: GetMetadataRequest(...)
511
+ activate Controller
512
+ note over Controller: gets entity metadata
513
+ Controller -> Client: **GetMetadataResponse**(...)
514
+ deactivate Controller
515
+ """
516
+ response_for = GetMetadataRequest
517
+ #: Path to the entity of which the metadata is returned.
518
+ entity: Path
519
+ #: The metadata of the entity.
520
+ #: The data schema of the metadata depends on the version included in config['sp_version'].
521
+ config: dict
522
+
523
+
524
+ class SetDAQRequest(Request):
525
+ """A request to the controller to set a :py:class:`DAQConfig` determining how and when data should be
526
+ acquired. The controller will respond with a :py:class:`SetDAQResponse`
527
+
528
+ .. uml::
529
+
530
+ Client -> Controller: **SetDAQRequest**(...)
531
+ Controller -> Client: SetDAQResponse(...)
532
+ """
533
+ #: The DAQ configuration to apply.
534
+ daq: DAQConfig
535
+ #: The secret session ID for which the entities were reserved. Only required if session management is enabled.
536
+ session: typing.Optional[UUID4]
537
+
538
+ class Config:
539
+ arbitrary_types_allowed = True
540
+
541
+
542
+ class SetDAQResponse(Response):
543
+ """A response to :py:class:`SetDAQRequest` conveying the success of the latter
544
+
545
+ .. uml::
546
+
547
+ Client -> Controller: SetDAQRequest(...)
548
+ Controller -> Client: **SetDAQResponse**(...)
549
+ """
550
+ response_for = SetDAQRequest
551
+ #: Whether the request was successful.
552
+ success: SuccessInfo
553
+
554
+
555
+ # ██████ ██ ██ ███ ██
556
+ # ██ ██ ██ ██ ████ ██
557
+ # ██████ ██ ██ ██ ██ ██
558
+ # ██ ██ ██ ██ ██ ██ ██
559
+ # ██ ██ ██████ ██ ████
560
+
561
+
562
+ class StartRunRequest(Request):
563
+ """
564
+ A request to start a run (computation).
565
+ After a run is started, the controller sends :class:`RunStateChangeMessage` notifications about its progress.
566
+
567
+ .. uml::
568
+
569
+ Client -> Controller: **StartRunRequest()**
570
+ alt run is accepted
571
+ Controller -> Client: StartRunResponse(success=True)
572
+ else run is not accepted (e.g. analog computer is busy or in failure mode)
573
+ Controller -> Client: StartRunResponse(success=False, error=<error info>)
574
+ end
575
+
576
+ """
577
+ #: The secret session ID in which the run should be started. Only required if session management is enabled.
578
+ session: typing.Optional[UUID4]
579
+ #: An ID that should be applied to the run.
580
+ id: UUID4
581
+ #: A :py:class:`pybrid.redac.run.RunConfig` that should be applied to the run.
582
+ config: RunConfig
583
+ #: A :py:class:`pybrid.redac.daq.DAQConfig` that should be applied to the run.
584
+ #: If None, the previous configuration is used.
585
+ daq_config: typing.Optional[DAQConfig]
586
+
587
+ @classmethod
588
+ def from_run(cls, run):
589
+ """
590
+ Generate a :py:class:`pybrid.redac.protocol.messages.StartRunRequest`
591
+ from a :py:class:`pybrid.redac.run.Run` instance.
592
+
593
+ :param run: A run
594
+ :return: A StartRunRequest instance
595
+ """
596
+ ...
597
+
598
+
599
+ class StartRunResponse(Response):
600
+ """
601
+ A response to a :py:class:`StartRunRequest` indicating whether the run was accepted.
602
+ """
603
+ response_for = StartRunRequest
604
+
605
+
606
+ class CancelRunRequest(Request):
607
+ """
608
+ A request to cancel an ongoing run.
609
+ Any caused :class:`RunStateChangeMessage` is sent first, before the :class:`CancelRunResponse` is sent.
610
+
611
+ .. uml::
612
+
613
+ Client -> Controller: StartRunRequest(...)
614
+ Controller -> Client: StartRunResponse(success=True)
615
+ ...
616
+ Client -> Controller: **CancelRunRequest**(...)
617
+ activate Controller
618
+ note over Controller: cancels run
619
+ Controller -> Client: RunStateChangeMessage(new=ERROR, ...)
620
+ Controller -> Client: CancelRunResponse(success=True)
621
+ deactivate Controller
622
+ """
623
+ #: The ID of the run to be canceled.
624
+ id_: UUID4
625
+
626
+
627
+ class CancelRunResponse(Response):
628
+ """
629
+ A response to a prior :class:`CancelRunRequest` indicating whether the run was successfully canceled.
630
+ """
631
+ response_for = CancelRunRequest
632
+ #: The ID of the run requested to be canceled.
633
+ id_: UUID4
634
+ #: Whether the run was successfully canceled and error information if not.
635
+ success: SuccessInfo
636
+
637
+
638
+ class RunStateChangeMessage(Message):
639
+ """
640
+ Notification that an ongoing :class:`Run` changed its :py:class:`RunState`.
641
+ A run is done once it enters :attr:`RunState.DONE` or :attr:`RuntState.ERROR`.
642
+
643
+ .. uml::
644
+
645
+ note over Client: starts a run
646
+ Client -> Controller: StartRunRequest()
647
+ Controller -> Client: StartRunResponse(accepted=True)
648
+ ...
649
+
650
+ note over Controller: controls run
651
+ Controller -> Client: **RunStateChangeMessage**(old=QUEUED, new=TAKE_OFF)
652
+ Controller -> Client: **RunStateChangeMessage**(old=TAKE_OFF, new=IC)
653
+ Controller -> Client: **RunStateChangeMessage**(old=IC, new=OP)
654
+ Controller -> Client: **RunStateChangeMessage**(old=OP, new=OP_END)
655
+ Controller -> Client: **RunStateChangeMessage**(old=OP_END, new=DONE)
656
+ ...
657
+ note over Client: knows run is done
658
+ Client -> Controller: StartRunRequest()
659
+ """
660
+ #: ID of the run
661
+ id: UUID4
662
+ #: Current time in microseconds
663
+ t: int
664
+ #: Previous state
665
+ old: RunState
666
+ #: New state
667
+ new: RunState
668
+ #: Any :class:`RunFlags` that the run has triggered (persistent across state changes).
669
+ run_flags: typing.Optional[RunFlags]
670
+
671
+
672
+ class RunDataMessage(Message):
673
+ """
674
+ Notification containing data sampled during a :class:`RunState`
675
+ according to the config set with :class:`SetDAQRequest`.
676
+ All data corresponding to a :class:`RunState` is sent out
677
+ before the state exit is indicated by a respective :class:`RunStateChangeMessage`.
678
+
679
+ .. uml::
680
+
681
+ Client -> Controller: StartRunRequest()
682
+ Controller -> Client: StartRunResponse(accepted=True)
683
+ ...
684
+ Controller -> Client: RunStateChangeMessage(old=IC, new=OP, ...)
685
+ activate Controller
686
+ loop until all data in RunState.OP is sent out
687
+ Controller -> Client: **RunDataMessage**(...)
688
+ end
689
+ Controller -> Client: RunStateChangeMessage(old=OP, new=OP_END, ...)
690
+ deactivate Controller
691
+ """
692
+ #: ID of the run
693
+ id: UUID4
694
+ #: Entity (cluster) that produced the data
695
+ entity: Path
696
+ #: Current state of the run
697
+ # state: RunState
698
+ #: Time of the first datapoint in `data` in microseconds
699
+ # t_0: int
700
+ #: Acquired data by entity path, normalized to [-1,+1]
701
+ data: list[list[float]]
702
+
703
+
704
+ class GetOverloadRequest(Request):
705
+ """
706
+ Request to get the overload status for all or some entities.
707
+
708
+ .. warning::
709
+ This message is intended to be used internally between the hybrid controller and the carrier boards.
710
+ As user, refer to the :attr:`RunStateChangeMessage.run_flags` field.
711
+ """
712
+ #: Optional path prefix to select only a subset of entities
713
+ entities: typing.Optional[Path]
714
+
715
+
716
+ class GetOverloadResponse(Response):
717
+ """
718
+ A response to a prior :class:`GetOverloadRequest`, containing all overloaded entities matching the requested prefix.
719
+
720
+ .. warning::
721
+ This message is intended to be used internally between the hybrid controller and the carrier boards.
722
+ As user, refer to the :attr:`RunStateChangeMessage.run_flags` field.
723
+ """
724
+ response_for = GetOverloadRequest
725
+ #: List of overloaded entities.
726
+ entities: list[Path]