bec-widgets 2.3.0__py3-none-any.whl → 2.5.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.
- .github/ISSUE_TEMPLATE/bug_report.md +26 -0
- .github/ISSUE_TEMPLATE/feature_request.md +48 -0
- .github/workflows/check_pr.yml +28 -0
- .github/workflows/ci.yml +36 -0
- .github/workflows/end2end-conda.yml +48 -0
- .github/workflows/formatter.yml +61 -0
- .github/workflows/generate-cli-check.yml +49 -0
- .github/workflows/pytest-matrix.yml +49 -0
- .github/workflows/pytest.yml +65 -0
- .github/workflows/semantic_release.yml +103 -0
- CHANGELOG.md +1726 -1546
- LICENSE +1 -1
- PKG-INFO +2 -1
- README.md +11 -0
- bec_widgets/cli/client.py +346 -0
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +8 -8
- bec_widgets/tests/utils.py +3 -3
- bec_widgets/utils/entry_validator.py +13 -3
- bec_widgets/utils/side_panel.py +65 -39
- bec_widgets/utils/toolbar.py +79 -0
- bec_widgets/widgets/containers/layout_manager/layout_manager.py +34 -1
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +1 -1
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +27 -31
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +1 -1
- bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +1 -1
- bec_widgets/widgets/editors/dict_backed_table.py +7 -0
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +1 -0
- bec_widgets/widgets/editors/web_console/register_web_console.py +15 -0
- bec_widgets/widgets/editors/web_console/web_console.py +230 -0
- bec_widgets/widgets/editors/web_console/web_console.pyproject +1 -0
- bec_widgets/widgets/editors/web_console/web_console_plugin.py +54 -0
- bec_widgets/widgets/plots/image/image.py +90 -0
- bec_widgets/widgets/plots/roi/__init__.py +0 -0
- bec_widgets/widgets/plots/roi/image_roi.py +867 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +11 -46
- bec_widgets/widgets/utility/visual/color_button_native/__init__.py +0 -0
- bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py +58 -0
- bec_widgets/widgets/utility/visual/color_button_native/color_button_native.pyproject +1 -0
- bec_widgets/widgets/utility/visual/color_button_native/color_button_native_plugin.py +56 -0
- bec_widgets/widgets/utility/visual/color_button_native/register_color_button_native.py +17 -0
- {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/METADATA +2 -1
- {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/RECORD +46 -25
- {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/licenses/LICENSE +1 -1
- pyproject.toml +17 -5
- {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/entry_points.txt +0 -0
LICENSE
CHANGED
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.5.0
|
4
4
|
Summary: BEC Widgets
|
5
5
|
Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
|
6
6
|
Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
|
@@ -25,6 +25,7 @@ Requires-Dist: coverage~=7.0; extra == 'dev'
|
|
25
25
|
Requires-Dist: fakeredis>=2.23.2,~=2.23; extra == 'dev'
|
26
26
|
Requires-Dist: isort>=5.13.2,~=5.13; extra == 'dev'
|
27
27
|
Requires-Dist: pytest-bec-e2e<=4.0,>=2.21.4; extra == 'dev'
|
28
|
+
Requires-Dist: pytest-cov~=6.1.1; extra == 'dev'
|
28
29
|
Requires-Dist: pytest-qt~=4.4; extra == 'dev'
|
29
30
|
Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
|
30
31
|
Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
|
README.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# BEC Widgets
|
2
2
|
|
3
|
+
|
4
|
+
[](https://github.com/bec-project/bec_widgets/actions/workflows/ci.yml)
|
5
|
+
[](https://pypi.org/project/bec-widgets/)
|
6
|
+
[](./LICENSE)
|
7
|
+
[](https://github.com/psf/black)
|
8
|
+
[](https://www.python.org)
|
9
|
+
[](https://doc.qt.io/qtforpython/)
|
10
|
+
[](https://conventionalcommits.org)
|
11
|
+
[](https://codecov.io/gh/bec-project/bec_widgets)
|
12
|
+
|
13
|
+
|
3
14
|
**⚠️ Important Notice:**
|
4
15
|
|
5
16
|
🚨 **PyQt6 is no longer supported** due to incompatibilities with Qt Designer. Please use **PySide6** instead. 🚨
|
bec_widgets/cli/client.py
CHANGED
@@ -55,6 +55,7 @@ _Widgets = {
|
|
55
55
|
"TextBox": "TextBox",
|
56
56
|
"VSCodeEditor": "VSCodeEditor",
|
57
57
|
"Waveform": "Waveform",
|
58
|
+
"WebConsole": "WebConsole",
|
58
59
|
"WebsiteWidget": "WebsiteWidget",
|
59
60
|
}
|
60
61
|
|
@@ -503,6 +504,204 @@ class BECStatusBox(RPCBase):
|
|
503
504
|
"""
|
504
505
|
|
505
506
|
|
507
|
+
class BaseROI(RPCBase):
|
508
|
+
"""Base class for all Region of Interest (ROI) implementations."""
|
509
|
+
|
510
|
+
@property
|
511
|
+
@rpc_call
|
512
|
+
def label(self) -> "str":
|
513
|
+
"""
|
514
|
+
Gets the display name of this ROI.
|
515
|
+
|
516
|
+
Returns:
|
517
|
+
str: The current name of the ROI.
|
518
|
+
"""
|
519
|
+
|
520
|
+
@label.setter
|
521
|
+
@rpc_call
|
522
|
+
def label(self) -> "str":
|
523
|
+
"""
|
524
|
+
Gets the display name of this ROI.
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
str: The current name of the ROI.
|
528
|
+
"""
|
529
|
+
|
530
|
+
@property
|
531
|
+
@rpc_call
|
532
|
+
def line_color(self) -> "str":
|
533
|
+
"""
|
534
|
+
Gets the current line color of the ROI.
|
535
|
+
|
536
|
+
Returns:
|
537
|
+
str: The current line color as a string (e.g., hex color code).
|
538
|
+
"""
|
539
|
+
|
540
|
+
@line_color.setter
|
541
|
+
@rpc_call
|
542
|
+
def line_color(self) -> "str":
|
543
|
+
"""
|
544
|
+
Gets the current line color of the ROI.
|
545
|
+
|
546
|
+
Returns:
|
547
|
+
str: The current line color as a string (e.g., hex color code).
|
548
|
+
"""
|
549
|
+
|
550
|
+
@property
|
551
|
+
@rpc_call
|
552
|
+
def line_width(self) -> "int":
|
553
|
+
"""
|
554
|
+
Gets the current line width of the ROI.
|
555
|
+
|
556
|
+
Returns:
|
557
|
+
int: The current line width in pixels.
|
558
|
+
"""
|
559
|
+
|
560
|
+
@line_width.setter
|
561
|
+
@rpc_call
|
562
|
+
def line_width(self) -> "int":
|
563
|
+
"""
|
564
|
+
Gets the current line width of the ROI.
|
565
|
+
|
566
|
+
Returns:
|
567
|
+
int: The current line width in pixels.
|
568
|
+
"""
|
569
|
+
|
570
|
+
@rpc_call
|
571
|
+
def get_coordinates(self):
|
572
|
+
"""
|
573
|
+
Gets the coordinates that define this ROI's position and shape.
|
574
|
+
|
575
|
+
This is an abstract method that must be implemented by subclasses.
|
576
|
+
Implementations should return either a dictionary with descriptive keys
|
577
|
+
or a tuple of coordinates, depending on the value of self.description.
|
578
|
+
|
579
|
+
Returns:
|
580
|
+
dict or tuple: The coordinates defining the ROI's position and shape.
|
581
|
+
|
582
|
+
Raises:
|
583
|
+
NotImplementedError: This method must be implemented by subclasses.
|
584
|
+
"""
|
585
|
+
|
586
|
+
@rpc_call
|
587
|
+
def get_data_from_image(
|
588
|
+
self, image_item: "pg.ImageItem | None" = None, returnMappedCoords: "bool" = False, **kwargs
|
589
|
+
):
|
590
|
+
"""
|
591
|
+
Wrapper around `pyqtgraph.ROI.getArrayRegion`.
|
592
|
+
|
593
|
+
Args:
|
594
|
+
image_item (pg.ImageItem or None): The ImageItem to sample. If None, auto-detects
|
595
|
+
the first `ImageItem` in the same GraphicsScene as this ROI.
|
596
|
+
returnMappedCoords (bool): If True, also returns the coordinate array generated by
|
597
|
+
*getArrayRegion*.
|
598
|
+
**kwargs: Additional keyword arguments passed to *getArrayRegion* or *affineSlice*,
|
599
|
+
such as `axes`, `order`, `shape`, etc.
|
600
|
+
|
601
|
+
Returns:
|
602
|
+
ndarray: Pixel data inside the ROI, or (data, coords) if *returnMappedCoords* is True.
|
603
|
+
"""
|
604
|
+
|
605
|
+
|
606
|
+
class CircularROI(RPCBase):
|
607
|
+
"""Circular Region of Interest with center/diameter tracking and auto-labeling."""
|
608
|
+
|
609
|
+
@property
|
610
|
+
@rpc_call
|
611
|
+
def label(self) -> "str":
|
612
|
+
"""
|
613
|
+
Gets the display name of this ROI.
|
614
|
+
|
615
|
+
Returns:
|
616
|
+
str: The current name of the ROI.
|
617
|
+
"""
|
618
|
+
|
619
|
+
@label.setter
|
620
|
+
@rpc_call
|
621
|
+
def label(self) -> "str":
|
622
|
+
"""
|
623
|
+
Gets the display name of this ROI.
|
624
|
+
|
625
|
+
Returns:
|
626
|
+
str: The current name of the ROI.
|
627
|
+
"""
|
628
|
+
|
629
|
+
@property
|
630
|
+
@rpc_call
|
631
|
+
def line_color(self) -> "str":
|
632
|
+
"""
|
633
|
+
Gets the current line color of the ROI.
|
634
|
+
|
635
|
+
Returns:
|
636
|
+
str: The current line color as a string (e.g., hex color code).
|
637
|
+
"""
|
638
|
+
|
639
|
+
@line_color.setter
|
640
|
+
@rpc_call
|
641
|
+
def line_color(self) -> "str":
|
642
|
+
"""
|
643
|
+
Gets the current line color of the ROI.
|
644
|
+
|
645
|
+
Returns:
|
646
|
+
str: The current line color as a string (e.g., hex color code).
|
647
|
+
"""
|
648
|
+
|
649
|
+
@property
|
650
|
+
@rpc_call
|
651
|
+
def line_width(self) -> "int":
|
652
|
+
"""
|
653
|
+
Gets the current line width of the ROI.
|
654
|
+
|
655
|
+
Returns:
|
656
|
+
int: The current line width in pixels.
|
657
|
+
"""
|
658
|
+
|
659
|
+
@line_width.setter
|
660
|
+
@rpc_call
|
661
|
+
def line_width(self) -> "int":
|
662
|
+
"""
|
663
|
+
Gets the current line width of the ROI.
|
664
|
+
|
665
|
+
Returns:
|
666
|
+
int: The current line width in pixels.
|
667
|
+
"""
|
668
|
+
|
669
|
+
@rpc_call
|
670
|
+
def get_coordinates(self, typed: "bool | None" = None) -> "dict | tuple":
|
671
|
+
"""
|
672
|
+
Calculates and returns the coordinates and size of an object, either as a
|
673
|
+
typed dictionary or as a tuple.
|
674
|
+
|
675
|
+
Args:
|
676
|
+
typed (bool | None): If True, returns coordinates as a dictionary. Defaults
|
677
|
+
to None, which utilizes the object's description value.
|
678
|
+
|
679
|
+
Returns:
|
680
|
+
dict: A dictionary with keys 'center_x', 'center_y', 'diameter', and 'radius'
|
681
|
+
if `typed` is True.
|
682
|
+
tuple: A tuple containing (center_x, center_y, diameter, radius) if `typed` is False.
|
683
|
+
"""
|
684
|
+
|
685
|
+
@rpc_call
|
686
|
+
def get_data_from_image(
|
687
|
+
self, image_item: "pg.ImageItem | None" = None, returnMappedCoords: "bool" = False, **kwargs
|
688
|
+
):
|
689
|
+
"""
|
690
|
+
Wrapper around `pyqtgraph.ROI.getArrayRegion`.
|
691
|
+
|
692
|
+
Args:
|
693
|
+
image_item (pg.ImageItem or None): The ImageItem to sample. If None, auto-detects
|
694
|
+
the first `ImageItem` in the same GraphicsScene as this ROI.
|
695
|
+
returnMappedCoords (bool): If True, also returns the coordinate array generated by
|
696
|
+
*getArrayRegion*.
|
697
|
+
**kwargs: Additional keyword arguments passed to *getArrayRegion* or *affineSlice*,
|
698
|
+
such as `axes`, `order`, `shape`, etc.
|
699
|
+
|
700
|
+
Returns:
|
701
|
+
ndarray: Pixel data inside the ROI, or (data, coords) if *returnMappedCoords* is True.
|
702
|
+
"""
|
703
|
+
|
704
|
+
|
506
705
|
class Curve(RPCBase):
|
507
706
|
@rpc_call
|
508
707
|
def remove(self):
|
@@ -1214,6 +1413,44 @@ class Image(RPCBase):
|
|
1214
1413
|
Access the main image item.
|
1215
1414
|
"""
|
1216
1415
|
|
1416
|
+
@rpc_call
|
1417
|
+
def add_roi(
|
1418
|
+
self,
|
1419
|
+
kind: "Literal['rect', 'circle']" = "rect",
|
1420
|
+
name: "str | None" = None,
|
1421
|
+
line_width: "int | None" = 10,
|
1422
|
+
pos: "tuple[float, float] | None" = (10, 10),
|
1423
|
+
size: "tuple[float, float] | None" = (50, 50),
|
1424
|
+
**pg_kwargs,
|
1425
|
+
) -> "RectangularROI | CircularROI":
|
1426
|
+
"""
|
1427
|
+
Add a ROI to the image.
|
1428
|
+
|
1429
|
+
Args:
|
1430
|
+
kind(str): The type of ROI to add. Options are "rect" or "circle".
|
1431
|
+
name(str): The name of the ROI.
|
1432
|
+
line_width(int): The line width of the ROI.
|
1433
|
+
pos(tuple): The position of the ROI.
|
1434
|
+
size(tuple): The size of the ROI.
|
1435
|
+
**pg_kwargs: Additional arguments for the ROI.
|
1436
|
+
|
1437
|
+
Returns:
|
1438
|
+
RectangularROI | CircularROI: The created ROI object.
|
1439
|
+
"""
|
1440
|
+
|
1441
|
+
@rpc_call
|
1442
|
+
def remove_roi(self, roi: "int | str"):
|
1443
|
+
"""
|
1444
|
+
Remove an ROI by index or label via the ROIController.
|
1445
|
+
"""
|
1446
|
+
|
1447
|
+
@property
|
1448
|
+
@rpc_call
|
1449
|
+
def rois(self) -> "list[BaseROI]":
|
1450
|
+
"""
|
1451
|
+
Get the list of ROIs.
|
1452
|
+
"""
|
1453
|
+
|
1217
1454
|
|
1218
1455
|
class ImageItem(RPCBase):
|
1219
1456
|
@property
|
@@ -2317,6 +2554,105 @@ class PositionerGroup(RPCBase):
|
|
2317
2554
|
"""
|
2318
2555
|
|
2319
2556
|
|
2557
|
+
class RectangularROI(RPCBase):
|
2558
|
+
"""Defines a rectangular Region of Interest (ROI) with additional functionality."""
|
2559
|
+
|
2560
|
+
@property
|
2561
|
+
@rpc_call
|
2562
|
+
def label(self) -> "str":
|
2563
|
+
"""
|
2564
|
+
Gets the display name of this ROI.
|
2565
|
+
|
2566
|
+
Returns:
|
2567
|
+
str: The current name of the ROI.
|
2568
|
+
"""
|
2569
|
+
|
2570
|
+
@label.setter
|
2571
|
+
@rpc_call
|
2572
|
+
def label(self) -> "str":
|
2573
|
+
"""
|
2574
|
+
Gets the display name of this ROI.
|
2575
|
+
|
2576
|
+
Returns:
|
2577
|
+
str: The current name of the ROI.
|
2578
|
+
"""
|
2579
|
+
|
2580
|
+
@property
|
2581
|
+
@rpc_call
|
2582
|
+
def line_color(self) -> "str":
|
2583
|
+
"""
|
2584
|
+
Gets the current line color of the ROI.
|
2585
|
+
|
2586
|
+
Returns:
|
2587
|
+
str: The current line color as a string (e.g., hex color code).
|
2588
|
+
"""
|
2589
|
+
|
2590
|
+
@line_color.setter
|
2591
|
+
@rpc_call
|
2592
|
+
def line_color(self) -> "str":
|
2593
|
+
"""
|
2594
|
+
Gets the current line color of the ROI.
|
2595
|
+
|
2596
|
+
Returns:
|
2597
|
+
str: The current line color as a string (e.g., hex color code).
|
2598
|
+
"""
|
2599
|
+
|
2600
|
+
@property
|
2601
|
+
@rpc_call
|
2602
|
+
def line_width(self) -> "int":
|
2603
|
+
"""
|
2604
|
+
Gets the current line width of the ROI.
|
2605
|
+
|
2606
|
+
Returns:
|
2607
|
+
int: The current line width in pixels.
|
2608
|
+
"""
|
2609
|
+
|
2610
|
+
@line_width.setter
|
2611
|
+
@rpc_call
|
2612
|
+
def line_width(self) -> "int":
|
2613
|
+
"""
|
2614
|
+
Gets the current line width of the ROI.
|
2615
|
+
|
2616
|
+
Returns:
|
2617
|
+
int: The current line width in pixels.
|
2618
|
+
"""
|
2619
|
+
|
2620
|
+
@rpc_call
|
2621
|
+
def get_coordinates(self, typed: "bool | None" = None) -> "dict | tuple":
|
2622
|
+
"""
|
2623
|
+
Returns the coordinates of a rectangle's corners. Supports returning them
|
2624
|
+
as either a dictionary with descriptive keys or a tuple of coordinates.
|
2625
|
+
|
2626
|
+
Args:
|
2627
|
+
typed (bool | None): If True, returns coordinates as a dictionary with
|
2628
|
+
descriptive keys. If False, returns them as a tuple. Defaults to
|
2629
|
+
the value of `self.description`.
|
2630
|
+
|
2631
|
+
Returns:
|
2632
|
+
dict | tuple: The rectangle's corner coordinates, where the format
|
2633
|
+
depends on the `typed` parameter.
|
2634
|
+
"""
|
2635
|
+
|
2636
|
+
@rpc_call
|
2637
|
+
def get_data_from_image(
|
2638
|
+
self, image_item: "pg.ImageItem | None" = None, returnMappedCoords: "bool" = False, **kwargs
|
2639
|
+
):
|
2640
|
+
"""
|
2641
|
+
Wrapper around `pyqtgraph.ROI.getArrayRegion`.
|
2642
|
+
|
2643
|
+
Args:
|
2644
|
+
image_item (pg.ImageItem or None): The ImageItem to sample. If None, auto-detects
|
2645
|
+
the first `ImageItem` in the same GraphicsScene as this ROI.
|
2646
|
+
returnMappedCoords (bool): If True, also returns the coordinate array generated by
|
2647
|
+
*getArrayRegion*.
|
2648
|
+
**kwargs: Additional keyword arguments passed to *getArrayRegion* or *affineSlice*,
|
2649
|
+
such as `axes`, `order`, `shape`, etc.
|
2650
|
+
|
2651
|
+
Returns:
|
2652
|
+
ndarray: Pixel data inside the ROI, or (data, coords) if *returnMappedCoords* is True.
|
2653
|
+
"""
|
2654
|
+
|
2655
|
+
|
2320
2656
|
class ResetButton(RPCBase):
|
2321
2657
|
"""A button that resets the scan queue."""
|
2322
2658
|
|
@@ -3501,6 +3837,16 @@ class Waveform(RPCBase):
|
|
3501
3837
|
"""
|
3502
3838
|
|
3503
3839
|
|
3840
|
+
class WebConsole(RPCBase):
|
3841
|
+
"""A simple widget to display a website"""
|
3842
|
+
|
3843
|
+
@rpc_call
|
3844
|
+
def remove(self):
|
3845
|
+
"""
|
3846
|
+
Cleanup the BECConnector
|
3847
|
+
"""
|
3848
|
+
|
3849
|
+
|
3504
3850
|
class WebsiteWidget(RPCBase):
|
3505
3851
|
"""A simple widget to display a website"""
|
3506
3852
|
|
@@ -43,7 +43,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
43
43
|
"pg": pg,
|
44
44
|
"wh": wh,
|
45
45
|
"dock": self.dock,
|
46
|
-
|
46
|
+
"im": self.im,
|
47
47
|
# "mi": self.mi,
|
48
48
|
# "mm": self.mm,
|
49
49
|
# "lm": self.lm,
|
@@ -112,13 +112,13 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
112
112
|
# tab_widget.addTab(fifth_tab, "Waveform Next Gen")
|
113
113
|
# tab_widget.setCurrentIndex(4)
|
114
114
|
#
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
115
|
+
sixth_tab = QWidget()
|
116
|
+
sixth_tab_layout = QVBoxLayout(sixth_tab)
|
117
|
+
self.im = Image(popups=False)
|
118
|
+
self.mi = self.im.main_image
|
119
|
+
sixth_tab_layout.addWidget(self.im)
|
120
|
+
tab_widget.addTab(sixth_tab, "Image Next Gen")
|
121
|
+
tab_widget.setCurrentIndex(1)
|
122
122
|
#
|
123
123
|
# seventh_tab = QWidget()
|
124
124
|
# seventh_tab_layout = QVBoxLayout(seventh_tab)
|
bec_widgets/tests/utils.py
CHANGED
@@ -96,9 +96,9 @@ class FakePositioner(BECPositioner):
|
|
96
96
|
}
|
97
97
|
self._info = {
|
98
98
|
"signals": {
|
99
|
-
"readback": {"kind_str": "
|
100
|
-
"setpoint": {"kind_str": "
|
101
|
-
"velocity": {"kind_str": "
|
99
|
+
"readback": {"kind_str": "hinted"}, # hinted
|
100
|
+
"setpoint": {"kind_str": "normal"}, # normal
|
101
|
+
"velocity": {"kind_str": "config"}, # config
|
102
102
|
}
|
103
103
|
}
|
104
104
|
self.signals = {
|
@@ -17,13 +17,23 @@ class EntryValidator:
|
|
17
17
|
raise ValueError(f"Device '{name}' not found in current BEC session")
|
18
18
|
|
19
19
|
device = self.devices[name]
|
20
|
-
|
20
|
+
|
21
|
+
# Build list of available signal entries from device._info['signals']
|
22
|
+
signals_dict = getattr(device, "_info", {}).get("signals", {})
|
23
|
+
available_entries = [
|
24
|
+
sig.get("obj_name") for sig in signals_dict.values() if sig.get("obj_name")
|
25
|
+
]
|
26
|
+
|
27
|
+
# If no signals are found, means device is a signal, use the device name as the entry
|
28
|
+
if not available_entries:
|
29
|
+
available_entries = [name]
|
21
30
|
|
22
31
|
if entry is None or entry == "":
|
23
32
|
entry = next(iter(device._hints), name) if hasattr(device, "_hints") else name
|
24
|
-
if entry not in
|
33
|
+
if entry not in available_entries:
|
25
34
|
raise ValueError(
|
26
|
-
f"Entry '{entry}' not found in device '{name}' signals.
|
35
|
+
f"Entry '{entry}' not found in device '{name}' signals. "
|
36
|
+
f"Available signals: '{available_entries}'"
|
27
37
|
)
|
28
38
|
|
29
39
|
return entry
|