fastcs-pandablocks 0.2.0a3__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,1034 @@
1
+ import asyncio
2
+ import enum
3
+ from collections.abc import Generator
4
+
5
+ import numpy as np
6
+ from fastcs.attributes import Attribute, AttributeIO, AttrR, AttrRW, AttrW
7
+ from fastcs.controllers import BaseController
8
+ from fastcs.datatypes import Bool, Enum, Float, Int, String, Table
9
+ from numpy.typing import DTypeLike
10
+ from pandablocks.commands import TableFieldDetails
11
+ from pandablocks.responses import (
12
+ BitMuxFieldInfo,
13
+ BitOutFieldInfo,
14
+ EnumFieldInfo,
15
+ ExtOutBitsFieldInfo,
16
+ ExtOutFieldInfo,
17
+ FieldInfo,
18
+ PosMuxFieldInfo,
19
+ PosOutFieldInfo,
20
+ ScalarFieldInfo,
21
+ SubtypeTimeFieldInfo,
22
+ TableFieldInfo,
23
+ TimeFieldInfo,
24
+ UintFieldInfo,
25
+ )
26
+ from pandablocks.utils import words_to_table
27
+
28
+ from fastcs_pandablocks.panda.client_wrapper import RawPanda
29
+ from fastcs_pandablocks.panda.io.arm import ArmCommand, ArmIORef
30
+ from fastcs_pandablocks.panda.io.bits import BitGroupOnUpdate
31
+ from fastcs_pandablocks.panda.io.default import DefaultFieldIORef
32
+ from fastcs_pandablocks.panda.io.table import TableFieldIORef
33
+ from fastcs_pandablocks.panda.io.units import TimeUnit, UnitsIORef
34
+ from fastcs_pandablocks.panda.utils import panda_value_to_attribute_value
35
+ from fastcs_pandablocks.types import (
36
+ PandaName,
37
+ RawInitialValuesType,
38
+ ResponseType,
39
+ WidgetGroup,
40
+ )
41
+
42
+ from .block_controller import BlockController, BlockControllerVector
43
+ from .data import DataController, DatasetAttributes
44
+ from .versions import VersionController
45
+
46
+
47
+ class Blocks:
48
+ """A wrapper that handles creating controllers and attributes from introspected
49
+ panda data.
50
+
51
+ Unfortunately attributes and names need to be cached throughout the introspection
52
+ process so having this all in one (huge) file is the nicest way to handle this.
53
+ """
54
+
55
+ def __init__(self, raw_panda: RawPanda, ios: list[AttributeIO]):
56
+ self._raw_panda = raw_panda
57
+ #: The controllers which should be registered by `PandaController` and are
58
+ #: acccessible by panda name.
59
+ self._introspected_controllers: dict[PandaName, BlockController] = {}
60
+
61
+ #: For controllers we add on the fastcs side which aren't accessible
62
+ #: by panda name.
63
+ self._additional_controllers: dict[str, BaseController] = {}
64
+
65
+ #: For keeping track of ext out bits so that updates in the group can be linked
66
+ self._bits_group_names: list[tuple[PandaName, list[PandaName]]] = []
67
+
68
+ #: For keeping track of dataset records so the :Data:Datasets table
69
+ #: can be updated.
70
+ self._dataset_attributes: dict[PandaName, DatasetAttributes] = {}
71
+
72
+ self._ios = ios
73
+
74
+ def get_attribute(self, panda_name: PandaName) -> Attribute:
75
+ return self._introspected_controllers[
76
+ panda_name.up_to_block()
77
+ ].panda_name_to_attribute[panda_name]
78
+
79
+ def controllers(self) -> Generator[tuple[str, BaseController], None, None]:
80
+ for (
81
+ panda_name,
82
+ introspected_controller,
83
+ ) in self._introspected_controllers.items():
84
+ yield panda_name.attribute_name, introspected_controller
85
+
86
+ yield from self._additional_controllers.items()
87
+
88
+ # ==================================================================================
89
+ # ====== FOR LINKING AND GENERATING POST INTROSPECTION =============================
90
+ # ==================================================================================
91
+
92
+ async def setup_post_introspection(self):
93
+ await asyncio.gather(
94
+ self._link_bits_groups(),
95
+ self._add_version_block(),
96
+ self._add_pcap_arm(),
97
+ self._add_data_block(),
98
+ )
99
+
100
+ async def _link_bits_groups(self):
101
+ for group_panda_name, bit_panda_names in self._bits_group_names:
102
+ group_attribute = self.get_attribute(group_panda_name)
103
+
104
+ bit_attributes: list[AttrRW] = [
105
+ self.get_attribute(panda_name) for panda_name in bit_panda_names
106
+ ] # type: ignore
107
+
108
+ assert isinstance(group_attribute, AttrRW)
109
+ update_callback = BitGroupOnUpdate(group_attribute, bit_attributes)
110
+ group_attribute.add_on_update_callback(update_callback)
111
+
112
+ for bit_attribute in bit_attributes:
113
+ bit_attribute.description = (
114
+ "Whether this field is set for capture in "
115
+ f"the `{group_panda_name.up_to_field()}` group."
116
+ )
117
+ bit_attribute.add_on_update_callback(update_callback)
118
+
119
+ # To match all bits before the p4p transport starts.
120
+ await update_callback(group_attribute.get())
121
+
122
+ async def _add_version_block(self):
123
+ idn_response = await self._raw_panda.get("*IDN")
124
+ assert isinstance(idn_response, str)
125
+ self._additional_controllers["Versions"] = VersionController(idn_response)
126
+
127
+ async def _add_pcap_arm(self):
128
+ pcap_name = PandaName("PCAP")
129
+ pcap_block = self._introspected_controllers.get(pcap_name, None)
130
+ if pcap_block is None:
131
+ raise ValueError("Did not receive a PCAP block during introspection.")
132
+
133
+ pcap_block.add_attribute(
134
+ pcap_name + PandaName(field="Arm"),
135
+ AttrRW(
136
+ Enum(ArmCommand),
137
+ description="Arm/Disarm the PandA.",
138
+ io_ref=ArmIORef(self._raw_panda.arm, self._raw_panda.disarm),
139
+ group=WidgetGroup.CAPTURE.value,
140
+ ),
141
+ )
142
+
143
+ async def _add_data_block(self):
144
+ self._additional_controllers["Data"] = DataController(
145
+ self._raw_panda.data, self._dataset_attributes
146
+ )
147
+
148
+ # ==================================================================================
149
+ # ====== FOR PARSING INTROSPECTED DATA =============================================
150
+ # ==================================================================================
151
+
152
+ async def parse_introspected_data(self):
153
+ (
154
+ raw_blocks,
155
+ raw_field_infos,
156
+ raw_labels,
157
+ raw_initial_values,
158
+ ) = await self._raw_panda.introspect()
159
+
160
+ for (block_name, block_info), field_info in zip(
161
+ raw_blocks.items(), raw_field_infos, strict=True
162
+ ):
163
+ numbered_block_names = (
164
+ [block_name]
165
+ if block_info.number in (None, 1)
166
+ else [
167
+ block_name + PandaName(block_number=number)
168
+ for number in range(1, block_info.number + 1)
169
+ ]
170
+ )
171
+ numbered_block_controllers: dict[int, BlockController] = {}
172
+ for number, numbered_block_name in enumerate(numbered_block_names):
173
+ block_initial_values = {
174
+ key: value
175
+ for key, value in raw_initial_values.items()
176
+ if key in numbered_block_name
177
+ }
178
+ label = raw_labels.get(numbered_block_name, None)
179
+ block = BlockController(
180
+ numbered_block_name,
181
+ self._raw_panda.put_value_to_panda,
182
+ label=block_info.description or label,
183
+ ios=self._ios,
184
+ )
185
+ numbered_block_controllers[number + 1] = block
186
+ self.fill_block(block, field_info, block_initial_values)
187
+ self._introspected_controllers[numbered_block_name] = block
188
+
189
+ # If there are numbered controllers, add a ControllerVector
190
+ if len(numbered_block_names) > 1:
191
+ self._additional_controllers[str(block_name)] = BlockControllerVector(
192
+ numbered_block_controllers
193
+ )
194
+
195
+ def fill_block(
196
+ self,
197
+ block: BlockController,
198
+ field_infos: dict[PandaName, ResponseType],
199
+ initial_values: RawInitialValuesType,
200
+ ):
201
+ for field_panda_name, field_info in field_infos.items():
202
+ full_field_name = block.panda_name + field_panda_name
203
+ field_initial_values = {
204
+ key: value
205
+ for key, value in initial_values.items()
206
+ if key in field_panda_name
207
+ }
208
+ self.add_field_to_block(
209
+ block, full_field_name, field_info, field_initial_values
210
+ )
211
+
212
+ def add_field_to_block(
213
+ self,
214
+ parent_block: BlockController,
215
+ field_panda_name: PandaName,
216
+ field_info: ResponseType,
217
+ initial_values: RawInitialValuesType,
218
+ ):
219
+ match field_info:
220
+ case TableFieldInfo():
221
+ return self._make_table_field(
222
+ parent_block, field_panda_name, field_info, initial_values
223
+ )
224
+ case TimeFieldInfo(subtype=None):
225
+ self._make_time_param(
226
+ parent_block, field_panda_name, field_info, initial_values
227
+ )
228
+ case SubtypeTimeFieldInfo(type="param"):
229
+ self._make_time_param(
230
+ parent_block, field_panda_name, field_info, initial_values
231
+ )
232
+ case SubtypeTimeFieldInfo(subtype="read"):
233
+ self._make_time_read(
234
+ parent_block, field_panda_name, field_info, initial_values
235
+ )
236
+ case SubtypeTimeFieldInfo(subtype="write"):
237
+ self._make_time_write(parent_block, field_panda_name, field_info)
238
+
239
+ case BitOutFieldInfo():
240
+ self._make_bit_out(
241
+ parent_block, field_panda_name, field_info, initial_values
242
+ )
243
+ case ExtOutBitsFieldInfo(subtype="timestamp"):
244
+ self._make_ext_out(
245
+ parent_block, field_panda_name, field_info, initial_values
246
+ )
247
+ case ExtOutBitsFieldInfo():
248
+ self._make_ext_out_bits(
249
+ parent_block, field_panda_name, field_info, initial_values
250
+ )
251
+ case ExtOutFieldInfo():
252
+ self._make_ext_out(
253
+ parent_block, field_panda_name, field_info, initial_values
254
+ )
255
+ case BitMuxFieldInfo():
256
+ self._make_bit_mux(
257
+ parent_block, field_panda_name, field_info, initial_values
258
+ )
259
+ case FieldInfo(type="param", subtype="bit"):
260
+ self._make_bit_param(
261
+ parent_block, field_panda_name, field_info, initial_values
262
+ )
263
+ case FieldInfo(type="read", subtype="bit"):
264
+ self._make_bit_read(
265
+ parent_block, field_panda_name, field_info, initial_values
266
+ )
267
+ case FieldInfo(type="write", subtype="bit"):
268
+ self._make_bit_write(parent_block, field_panda_name, field_info)
269
+
270
+ case PosOutFieldInfo():
271
+ self._make_pos_out(
272
+ parent_block, field_panda_name, field_info, initial_values
273
+ )
274
+ case PosMuxFieldInfo():
275
+ self._make_pos_mux(
276
+ parent_block, field_panda_name, field_info, initial_values
277
+ )
278
+
279
+ # TODO: Add scaled as an option to fastcs int so we can have a uint32
280
+ case UintFieldInfo(type="param"):
281
+ self._make_uint_param(
282
+ parent_block, field_panda_name, field_info, initial_values
283
+ )
284
+ case UintFieldInfo(type="read"):
285
+ self._make_uint_read(
286
+ parent_block, field_panda_name, field_info, initial_values
287
+ )
288
+ case UintFieldInfo(type="write"):
289
+ self._make_uint_write(parent_block, field_panda_name, field_info)
290
+
291
+ # Scalar types
292
+ case ScalarFieldInfo(subtype="param"):
293
+ self._make_scalar_param(
294
+ parent_block, field_panda_name, field_info, initial_values
295
+ )
296
+ case ScalarFieldInfo(type="read"):
297
+ self._make_scalar_read(
298
+ parent_block, field_panda_name, field_info, initial_values
299
+ )
300
+ case ScalarFieldInfo(type="write"):
301
+ self._make_scalar_write(parent_block, field_panda_name, field_info)
302
+
303
+ # Int types
304
+ case FieldInfo(type="param", subtype="int"):
305
+ self._make_int_param(
306
+ parent_block, field_panda_name, field_info, initial_values
307
+ )
308
+ case FieldInfo(type="read", subtype="int"):
309
+ self._make_int_read(
310
+ parent_block, field_panda_name, field_info, initial_values
311
+ )
312
+ case FieldInfo(type="write", subtype="int"):
313
+ self._make_int_write(parent_block, field_panda_name, field_info)
314
+
315
+ # Action types
316
+ case FieldInfo(
317
+ type="write",
318
+ subtype="action",
319
+ ):
320
+ self._make_action_write(parent_block, field_panda_name, field_info)
321
+
322
+ # Lut types
323
+ case FieldInfo(type="param", subtype="lut"):
324
+ self._make_lut_param(
325
+ parent_block, field_panda_name, field_info, initial_values
326
+ )
327
+ case FieldInfo(type="read", subtype="lut"):
328
+ self._make_lut_read(
329
+ parent_block, field_panda_name, field_info, initial_values
330
+ )
331
+ case FieldInfo(type="write", subtype="lut"):
332
+ self._make_lut_write(parent_block, field_panda_name, field_info)
333
+
334
+ # Enum types
335
+ case EnumFieldInfo(type="param"):
336
+ self._make_enum_param(
337
+ parent_block, field_panda_name, field_info, initial_values
338
+ )
339
+ case EnumFieldInfo(type="read"):
340
+ self._make_enum_read(
341
+ parent_block, field_panda_name, field_info, initial_values
342
+ )
343
+ case EnumFieldInfo(type="write"):
344
+ self._make_enum_write(parent_block, field_panda_name, field_info)
345
+ case _:
346
+ raise ValueError(f"Unknown field type: {type(field_info).__name__}.")
347
+
348
+ def _table_datatypes_from_table_field_details(
349
+ self,
350
+ details: TableFieldDetails,
351
+ ) -> DTypeLike:
352
+ match details:
353
+ case TableFieldDetails(subtype="int"):
354
+ return np.int32
355
+ case TableFieldDetails(subtype="uint"):
356
+ return np.uint32
357
+ case TableFieldDetails(subtype="enum"):
358
+ # TODO: replace with string once
359
+ # https://github.com/epics-base/p4p/issues/168
360
+ # is fixed.
361
+ return "U16"
362
+ case _:
363
+ raise RuntimeError("Received unknown datatype for table in panda.")
364
+
365
+ def _make_table_field(
366
+ self,
367
+ parent_block: BlockController,
368
+ panda_name: PandaName,
369
+ field_info: TableFieldInfo,
370
+ initial_values: RawInitialValuesType,
371
+ ):
372
+ structured_datatype = [
373
+ (name.lower(), self._table_datatypes_from_table_field_details(details))
374
+ for name, details in field_info.fields.items()
375
+ ]
376
+
377
+ initial_value = panda_value_to_attribute_value(
378
+ fastcs_datatype=Table(structured_datatype),
379
+ value=words_to_table(
380
+ words=initial_values[panda_name],
381
+ table_field_info=field_info,
382
+ convert_enum_indices=True,
383
+ ),
384
+ )
385
+
386
+ # TODO: Add units IO to update the units field and value of this one PV
387
+ # https://github.com/PandABlocks/PandABlocks-ioc/blob/c1e8056abf3f680fa3840493eb4ac6ca2be31313/src/pandablocks_ioc/ioc.py#L750-L769
388
+ attribute = AttrRW(
389
+ Table(structured_datatype),
390
+ io_ref=TableFieldIORef(
391
+ panda_name, field_info, self._raw_panda.put_value_to_panda
392
+ ),
393
+ initial_value=initial_value,
394
+ )
395
+ parent_block.add_attribute(panda_name, attribute)
396
+
397
+ def _make_time_param(
398
+ self,
399
+ parent_block: BlockController,
400
+ panda_name: PandaName,
401
+ field_info: SubtypeTimeFieldInfo | TimeFieldInfo,
402
+ initial_values: RawInitialValuesType,
403
+ ):
404
+ # TODO: add an IO for units to scale to get seconds
405
+ # The units come through in the same *CHANGES so there shouldn't
406
+ # be a race condition here.
407
+ # PandaName(block='PULSE', block_number=2, field='WIDTH', sub_field=None): '0',
408
+ # PandaName(..., sub_field='UNITS'): 's',
409
+
410
+ attribute = AttrRW(
411
+ Float(units="s", prec=5),
412
+ io_ref=DefaultFieldIORef(panda_name, self._raw_panda.put_value_to_panda),
413
+ description=field_info.description,
414
+ group=WidgetGroup.PARAMETERS.value,
415
+ initial_value=float(initial_values[panda_name]),
416
+ )
417
+ parent_block.add_attribute(panda_name, attribute)
418
+
419
+ units_enum = Enum(TimeUnit)
420
+ units_name = panda_name + PandaName(sub_field="UNITS")
421
+ units_attribute = AttrRW(
422
+ units_enum,
423
+ io_ref=UnitsIORef(
424
+ attribute, TimeUnit.s, units_name, self._raw_panda.put_value_to_panda
425
+ ),
426
+ description=field_info.description,
427
+ group=WidgetGroup.PARAMETERS.value,
428
+ initial_value=TimeUnit.s,
429
+ )
430
+ parent_block.add_attribute(units_name, units_attribute)
431
+
432
+ def _make_time_read(
433
+ self,
434
+ parent_block: BlockController,
435
+ panda_name: PandaName,
436
+ field_info: SubtypeTimeFieldInfo,
437
+ initial_values: RawInitialValuesType,
438
+ ):
439
+ attribute = AttrR(
440
+ Float(units="s"),
441
+ description=field_info.description,
442
+ group=WidgetGroup.OUTPUTS.value,
443
+ initial_value=float(initial_values[panda_name]),
444
+ )
445
+ parent_block.add_attribute(panda_name, attribute)
446
+
447
+ def _make_time_write(
448
+ self,
449
+ parent_block: BlockController,
450
+ panda_name: PandaName,
451
+ field_info: SubtypeTimeFieldInfo,
452
+ ):
453
+ attribute = AttrW(
454
+ Float(units="s"),
455
+ io_ref=DefaultFieldIORef(panda_name, self._raw_panda.put_value_to_panda),
456
+ description=field_info.description,
457
+ group=WidgetGroup.OUTPUTS.value,
458
+ )
459
+ parent_block.add_attribute(panda_name, attribute)
460
+
461
+ def _make_bit_out(
462
+ self,
463
+ parent_block: BlockController,
464
+ panda_name: PandaName,
465
+ field_info: BitOutFieldInfo,
466
+ initial_values: RawInitialValuesType,
467
+ ):
468
+ parent_block.add_attribute(
469
+ panda_name,
470
+ AttrR(
471
+ Bool(),
472
+ description=field_info.description,
473
+ group=WidgetGroup.OUTPUTS.value,
474
+ initial_value=bool(int(initial_values[panda_name])),
475
+ ),
476
+ )
477
+
478
+ capture_name = panda_name + PandaName(sub_field="CAPTURE")
479
+ parent_block.add_attribute(
480
+ capture_name,
481
+ AttrRW(
482
+ Bool(),
483
+ group=WidgetGroup.CAPTURE.value,
484
+ initial_value=False,
485
+ ),
486
+ )
487
+
488
+ def _make_pos_out(
489
+ self,
490
+ parent_block: BlockController,
491
+ panda_name: PandaName,
492
+ field_info: PosOutFieldInfo,
493
+ initial_values: RawInitialValuesType,
494
+ ):
495
+ pos_out = AttrR(
496
+ Int(),
497
+ description=field_info.description,
498
+ group=WidgetGroup.OUTPUTS.value,
499
+ initial_value=int(initial_values[panda_name]),
500
+ )
501
+ parent_block.add_attribute(panda_name, pos_out)
502
+
503
+ scale_panda_name = panda_name + PandaName(sub_field="SCALE")
504
+ scale = AttrRW(
505
+ Float(),
506
+ group=WidgetGroup.CAPTURE.value,
507
+ io_ref=DefaultFieldIORef(
508
+ scale_panda_name, self._raw_panda.put_value_to_panda
509
+ ),
510
+ initial_value=float(initial_values[scale_panda_name]),
511
+ )
512
+ parent_block.add_attribute(scale_panda_name, scale)
513
+
514
+ offset_panda_name = panda_name + PandaName(sub_field="OFFSET")
515
+ offset = AttrRW(
516
+ Float(),
517
+ group=WidgetGroup.CAPTURE.value,
518
+ io_ref=DefaultFieldIORef(
519
+ offset_panda_name, self._raw_panda.put_value_to_panda
520
+ ),
521
+ initial_value=float(initial_values[offset_panda_name]),
522
+ )
523
+ parent_block.add_attribute(offset_panda_name, offset)
524
+
525
+ scaled_panda_name = panda_name + PandaName(sub_field="SCALED")
526
+ scaled = AttrR(
527
+ Float(),
528
+ group=WidgetGroup.CAPTURE.value,
529
+ description="Value with scaling applied.",
530
+ initial_value=scale.get() * pos_out.get() + offset.get(),
531
+ )
532
+ parent_block.add_attribute(scaled_panda_name, scaled)
533
+
534
+ async def updated_scaled_on_offset_change(*_):
535
+ await scaled.update(scale.get() * pos_out.get() + offset.get())
536
+
537
+ offset.add_on_update_callback(updated_scaled_on_offset_change)
538
+ scale.add_on_update_callback(updated_scaled_on_offset_change)
539
+ pos_out.add_on_update_callback(updated_scaled_on_offset_change)
540
+
541
+ capture_enum = Enum(enum.Enum("Capture", field_info.capture_labels))
542
+
543
+ capture_panda_name = panda_name + PandaName(sub_field="CAPTURE")
544
+ capture_attribute = AttrRW(
545
+ capture_enum,
546
+ description=field_info.description,
547
+ group=WidgetGroup.CAPTURE.value,
548
+ initial_value=capture_enum.members[
549
+ capture_enum.names.index(initial_values[capture_panda_name])
550
+ ],
551
+ )
552
+ parent_block.add_attribute(
553
+ capture_panda_name,
554
+ capture_attribute,
555
+ )
556
+ dataset_attribute = AttrRW(
557
+ String(),
558
+ description=(
559
+ "Used to adjust the dataset name to one more scientifically relevant"
560
+ ),
561
+ group=WidgetGroup.CAPTURE.value,
562
+ initial_value="",
563
+ )
564
+ parent_block.add_attribute(
565
+ panda_name + PandaName(sub_field="DATASET"),
566
+ dataset_attribute,
567
+ )
568
+ self._dataset_attributes[panda_name] = DatasetAttributes(
569
+ dataset_attribute, capture_attribute
570
+ )
571
+
572
+ def _make_ext_out(
573
+ self,
574
+ parent_block: BlockController,
575
+ panda_name: PandaName,
576
+ field_info: ExtOutFieldInfo,
577
+ initial_values: RawInitialValuesType,
578
+ ):
579
+ """Returns the capture attribtute so we can add a callback in ext out bits.
580
+
581
+ For an ext out bits, we set one capture in the group, wait for the panda
582
+ response that the group is being captutured, and then update all the elements
583
+ in the group.
584
+ """
585
+
586
+ capture_enum = Enum(enum.Enum("Capture", field_info.capture_labels))
587
+ capture_panda_name = panda_name + PandaName(sub_field="CAPTURE")
588
+ capture_attribute = AttrRW(
589
+ capture_enum,
590
+ description=field_info.description,
591
+ group=WidgetGroup.CAPTURE.value,
592
+ initial_value=capture_enum.enum_cls[initial_values[capture_panda_name]],
593
+ )
594
+
595
+ parent_block.add_attribute(capture_panda_name, capture_attribute)
596
+
597
+ dataset_attribute = AttrRW(
598
+ String(),
599
+ description=(
600
+ "Used to adjust the dataset name to one more scientifically relevant"
601
+ ),
602
+ group=WidgetGroup.CAPTURE.value,
603
+ initial_value="",
604
+ )
605
+ parent_block.add_attribute(
606
+ panda_name + PandaName(sub_field="DATASET"),
607
+ dataset_attribute,
608
+ )
609
+ self._dataset_attributes[panda_name] = DatasetAttributes(
610
+ dataset_attribute, capture_attribute
611
+ )
612
+
613
+ def _make_ext_out_bits(
614
+ self,
615
+ parent_block: BlockController,
616
+ panda_name: PandaName,
617
+ field_info: ExtOutBitsFieldInfo,
618
+ initial_values: RawInitialValuesType,
619
+ ):
620
+ self._make_ext_out(parent_block, panda_name, field_info, initial_values)
621
+ capture_group_members = [
622
+ PandaName.from_string(label) + PandaName(sub_field="CAPTURE")
623
+ for label in field_info.bits
624
+ if label != ""
625
+ ]
626
+
627
+ self._bits_group_names.append(
628
+ (
629
+ panda_name + PandaName(sub_field="CAPTURE"),
630
+ capture_group_members,
631
+ )
632
+ )
633
+
634
+ def _make_bit_mux(
635
+ self,
636
+ parent_block: BlockController,
637
+ panda_name: PandaName,
638
+ bit_mux_field_info: BitMuxFieldInfo,
639
+ initial_values: RawInitialValuesType,
640
+ ):
641
+ enum_type = enum.Enum("Labels", bit_mux_field_info.labels)
642
+ parent_block.add_attribute(
643
+ panda_name,
644
+ AttrRW(
645
+ Enum(enum_type),
646
+ description=bit_mux_field_info.description,
647
+ io_ref=DefaultFieldIORef(
648
+ panda_name, self._raw_panda.put_value_to_panda
649
+ ),
650
+ group=WidgetGroup.INPUTS.value,
651
+ initial_value=enum_type[initial_values[panda_name]],
652
+ ),
653
+ )
654
+
655
+ delay_panda_name = panda_name + PandaName(sub_field="DELAY")
656
+ parent_block.add_attribute(
657
+ delay_panda_name,
658
+ AttrRW(
659
+ Int(min=0, max=bit_mux_field_info.max_delay),
660
+ description="Clock delay on input.",
661
+ io_ref=DefaultFieldIORef(
662
+ delay_panda_name, self._raw_panda.put_value_to_panda
663
+ ),
664
+ group=WidgetGroup.INPUTS.value,
665
+ initial_value=int(initial_values[delay_panda_name]),
666
+ ),
667
+ )
668
+
669
+ def _make_pos_mux(
670
+ self,
671
+ parent_block: BlockController,
672
+ panda_name: PandaName,
673
+ pos_mux_field_info: PosMuxFieldInfo,
674
+ initial_values: RawInitialValuesType,
675
+ ):
676
+ enum_type = enum.Enum("Labels", pos_mux_field_info.labels)
677
+ parent_block.add_attribute(
678
+ panda_name,
679
+ AttrRW(
680
+ Enum(enum_type),
681
+ description=pos_mux_field_info.description,
682
+ io_ref=DefaultFieldIORef(
683
+ panda_name, self._raw_panda.put_value_to_panda
684
+ ),
685
+ group=WidgetGroup.INPUTS.value,
686
+ initial_value=enum_type[initial_values[panda_name]],
687
+ ),
688
+ )
689
+
690
+ def _make_uint_param(
691
+ self,
692
+ parent_block: BlockController,
693
+ panda_name: PandaName,
694
+ uint_param_field_info: UintFieldInfo,
695
+ initial_values: RawInitialValuesType,
696
+ ):
697
+ parent_block.add_attribute(
698
+ panda_name,
699
+ AttrRW(
700
+ Int(min=0, max=uint_param_field_info.max_val),
701
+ description=uint_param_field_info.description,
702
+ io_ref=DefaultFieldIORef(
703
+ panda_name, self._raw_panda.put_value_to_panda
704
+ ),
705
+ group=WidgetGroup.PARAMETERS.value,
706
+ initial_value=int(initial_values[panda_name]),
707
+ ),
708
+ )
709
+
710
+ def _make_uint_read(
711
+ self,
712
+ parent_block: BlockController,
713
+ panda_name: PandaName,
714
+ uint_read_field_info: UintFieldInfo,
715
+ initial_values: RawInitialValuesType,
716
+ ):
717
+ parent_block.add_attribute(
718
+ panda_name,
719
+ AttrR(
720
+ Int(min=0, max=uint_read_field_info.max_val),
721
+ description=uint_read_field_info.description,
722
+ group=WidgetGroup.READBACKS.value,
723
+ initial_value=int(initial_values[panda_name]),
724
+ ),
725
+ )
726
+
727
+ def _make_uint_write(
728
+ self,
729
+ parent_block: BlockController,
730
+ panda_name: PandaName,
731
+ uint_write_field_info: UintFieldInfo,
732
+ ):
733
+ parent_block.add_attribute(
734
+ panda_name,
735
+ AttrW(
736
+ Int(min=0, max=uint_write_field_info.max_val),
737
+ description=uint_write_field_info.description,
738
+ io_ref=DefaultFieldIORef(
739
+ panda_name, self._raw_panda.put_value_to_panda
740
+ ),
741
+ group=WidgetGroup.OUTPUTS.value,
742
+ ),
743
+ )
744
+
745
+ def _make_int_param(
746
+ self,
747
+ parent_block: BlockController,
748
+ panda_name: PandaName,
749
+ int_param_field_info: FieldInfo,
750
+ initial_values: RawInitialValuesType,
751
+ ):
752
+ parent_block.add_attribute(
753
+ panda_name,
754
+ AttrRW(
755
+ Int(),
756
+ description=int_param_field_info.description,
757
+ io_ref=DefaultFieldIORef(
758
+ panda_name, self._raw_panda.put_value_to_panda
759
+ ),
760
+ group=WidgetGroup.PARAMETERS.value,
761
+ initial_value=int(initial_values[panda_name]),
762
+ ),
763
+ )
764
+
765
+ def _make_int_read(
766
+ self,
767
+ parent_block: BlockController,
768
+ panda_name: PandaName,
769
+ int_read_field_info: FieldInfo,
770
+ initial_values: RawInitialValuesType,
771
+ ):
772
+ parent_block.add_attribute(
773
+ panda_name,
774
+ AttrR(
775
+ Int(),
776
+ description=int_read_field_info.description,
777
+ group=WidgetGroup.READBACKS.value,
778
+ initial_value=int(initial_values[panda_name]),
779
+ ),
780
+ )
781
+
782
+ def _make_int_write(
783
+ self,
784
+ parent_block: BlockController,
785
+ panda_name: PandaName,
786
+ int_write_field_info: FieldInfo,
787
+ ):
788
+ parent_block.add_attribute(
789
+ panda_name,
790
+ AttrW(
791
+ Int(),
792
+ description=int_write_field_info.description,
793
+ io_ref=DefaultFieldIORef(
794
+ panda_name, self._raw_panda.put_value_to_panda
795
+ ),
796
+ group=WidgetGroup.PARAMETERS.value,
797
+ ),
798
+ )
799
+
800
+ def _make_scalar_param(
801
+ self,
802
+ parent_block: BlockController,
803
+ panda_name: PandaName,
804
+ scalar_param_field_info: ScalarFieldInfo,
805
+ initial_values: RawInitialValuesType,
806
+ ):
807
+ parent_block.add_attribute(
808
+ panda_name,
809
+ AttrRW(
810
+ Float(units=scalar_param_field_info.units),
811
+ description=scalar_param_field_info.description,
812
+ io_ref=DefaultFieldIORef(
813
+ panda_name, self._raw_panda.put_value_to_panda
814
+ ),
815
+ group=WidgetGroup.PARAMETERS.value,
816
+ initial_value=float(initial_values[panda_name]),
817
+ ),
818
+ )
819
+
820
+ def _make_scalar_read(
821
+ self,
822
+ parent_block: BlockController,
823
+ panda_name: PandaName,
824
+ scalar_read_field_info: ScalarFieldInfo,
825
+ initial_values: RawInitialValuesType,
826
+ ):
827
+ parent_block.add_attribute(
828
+ panda_name,
829
+ AttrR(
830
+ Float(),
831
+ description=scalar_read_field_info.description,
832
+ group=WidgetGroup.READBACKS.value,
833
+ initial_value=float(initial_values[panda_name]),
834
+ ),
835
+ )
836
+
837
+ def _make_scalar_write(
838
+ self,
839
+ parent_block: BlockController,
840
+ panda_name: PandaName,
841
+ scalar_write_field_info: ScalarFieldInfo,
842
+ ):
843
+ parent_block.add_attribute(
844
+ panda_name,
845
+ AttrR(
846
+ Float(),
847
+ description=scalar_write_field_info.description,
848
+ group=WidgetGroup.PARAMETERS.value,
849
+ ),
850
+ )
851
+
852
+ def _make_bit_param(
853
+ self,
854
+ parent_block: BlockController,
855
+ panda_name: PandaName,
856
+ bit_param_field_info: FieldInfo,
857
+ initial_values: RawInitialValuesType,
858
+ ):
859
+ parent_block.add_attribute(
860
+ panda_name,
861
+ AttrRW(
862
+ Bool(),
863
+ description=bit_param_field_info.description,
864
+ io_ref=DefaultFieldIORef(
865
+ panda_name, self._raw_panda.put_value_to_panda
866
+ ),
867
+ group=WidgetGroup.PARAMETERS.value,
868
+ initial_value=bool(int(initial_values[panda_name])),
869
+ ),
870
+ )
871
+
872
+ def _make_bit_read(
873
+ self,
874
+ parent_block: BlockController,
875
+ panda_name: PandaName,
876
+ bit_read_field_info: FieldInfo,
877
+ initial_values: RawInitialValuesType,
878
+ ):
879
+ parent_block.add_attribute(
880
+ panda_name,
881
+ AttrR(
882
+ Bool(),
883
+ description=bit_read_field_info.description,
884
+ group=WidgetGroup.READBACKS.value,
885
+ initial_value=bool(int(initial_values[panda_name])),
886
+ ),
887
+ )
888
+
889
+ def _make_bit_write(
890
+ self,
891
+ parent_block: BlockController,
892
+ panda_name: PandaName,
893
+ bit_write_field_info: FieldInfo,
894
+ ):
895
+ parent_block.add_attribute(
896
+ panda_name,
897
+ AttrW(
898
+ Bool(),
899
+ description=bit_write_field_info.description,
900
+ io_ref=DefaultFieldIORef(
901
+ panda_name, self._raw_panda.put_value_to_panda
902
+ ),
903
+ group=WidgetGroup.OUTPUTS.value,
904
+ ),
905
+ )
906
+
907
+ def _make_action_write(
908
+ self,
909
+ parent_block: BlockController,
910
+ panda_name: PandaName,
911
+ action_write_field_info: FieldInfo,
912
+ ):
913
+ parent_block.add_attribute(
914
+ panda_name,
915
+ AttrW(
916
+ Bool(),
917
+ description=action_write_field_info.description,
918
+ io_ref=DefaultFieldIORef(
919
+ panda_name, self._raw_panda.put_value_to_panda
920
+ ),
921
+ group=WidgetGroup.OUTPUTS.value,
922
+ ),
923
+ )
924
+
925
+ def _make_lut_param(
926
+ self,
927
+ parent_block: BlockController,
928
+ panda_name: PandaName,
929
+ lut_param_field_info: FieldInfo,
930
+ initial_values: RawInitialValuesType,
931
+ ):
932
+ parent_block.add_attribute(
933
+ panda_name,
934
+ AttrRW(
935
+ String(),
936
+ description=lut_param_field_info.description,
937
+ io_ref=DefaultFieldIORef(
938
+ panda_name, self._raw_panda.put_value_to_panda
939
+ ),
940
+ group=WidgetGroup.PARAMETERS.value,
941
+ initial_value=initial_values[panda_name],
942
+ ),
943
+ )
944
+
945
+ def _make_lut_read(
946
+ self,
947
+ parent_block: BlockController,
948
+ panda_name: PandaName,
949
+ lut_read_field_info: FieldInfo,
950
+ initial_values: RawInitialValuesType,
951
+ ):
952
+ parent_block.add_attribute(
953
+ panda_name,
954
+ AttrR(
955
+ String(),
956
+ description=lut_read_field_info.description,
957
+ group=WidgetGroup.READBACKS.value,
958
+ initial_value=initial_values[panda_name],
959
+ ),
960
+ )
961
+
962
+ def _make_lut_write(
963
+ self,
964
+ parent_block: BlockController,
965
+ panda_name: PandaName,
966
+ lut_read_field_info: FieldInfo,
967
+ ):
968
+ parent_block.add_attribute(
969
+ panda_name,
970
+ AttrR(
971
+ String(),
972
+ description=lut_read_field_info.description,
973
+ group=WidgetGroup.OUTPUTS.value,
974
+ ),
975
+ )
976
+
977
+ def _make_enum_param(
978
+ self,
979
+ parent_block: BlockController,
980
+ panda_name: PandaName,
981
+ enum_param_field_info: EnumFieldInfo,
982
+ initial_values: RawInitialValuesType,
983
+ ):
984
+ enum_type = enum.Enum("Labels", enum_param_field_info.labels)
985
+ parent_block.add_attribute(
986
+ panda_name,
987
+ AttrRW(
988
+ Enum(enum_type),
989
+ description=enum_param_field_info.description,
990
+ io_ref=DefaultFieldIORef(
991
+ panda_name, self._raw_panda.put_value_to_panda
992
+ ),
993
+ group=WidgetGroup.PARAMETERS.value,
994
+ initial_value=enum_type[initial_values[panda_name]],
995
+ ),
996
+ )
997
+
998
+ def _make_enum_read(
999
+ self,
1000
+ parent_block: BlockController,
1001
+ panda_name: PandaName,
1002
+ enum_read_field_info: EnumFieldInfo,
1003
+ initial_values: RawInitialValuesType,
1004
+ ):
1005
+ enum_type = enum.Enum("Labels", enum_read_field_info.labels)
1006
+
1007
+ parent_block.add_attribute(
1008
+ panda_name,
1009
+ AttrR(
1010
+ Enum(enum_type),
1011
+ description=enum_read_field_info.description,
1012
+ group=WidgetGroup.READBACKS.value,
1013
+ initial_value=enum_type[initial_values[panda_name]],
1014
+ ),
1015
+ )
1016
+
1017
+ def _make_enum_write(
1018
+ self,
1019
+ parent_block: BlockController,
1020
+ panda_name: PandaName,
1021
+ enum_write_field_info: EnumFieldInfo,
1022
+ ):
1023
+ enum_type = enum.Enum("Labels", enum_write_field_info.labels)
1024
+ parent_block.add_attribute(
1025
+ panda_name,
1026
+ AttrW(
1027
+ Enum(enum_type),
1028
+ description=enum_write_field_info.description,
1029
+ io_ref=DefaultFieldIORef(
1030
+ panda_name, self._raw_panda.put_value_to_panda
1031
+ ),
1032
+ group=WidgetGroup.OUTPUTS.value,
1033
+ ),
1034
+ )