ekfsm 0.13.0a183__py3-none-any.whl → 1.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.
Potentially problematic release.
This version of ekfsm might be problematic. Click here for more details.
- ekfsm/__init__.py +3 -14
- ekfsm/boards/oem/ekf/shu-shuttle.yaml +43 -0
- ekfsm/boards/oem/ekf/sq3-quartet.yaml +51 -37
- ekfsm/boards/oem/ekf/z1010.yaml +102 -0
- ekfsm/boards/oem/hitron/hdrc-300s.yaml +1 -1
- ekfsm/cli.py +32 -9
- ekfsm/config.py +14 -6
- ekfsm/core/__init__.py +13 -3
- ekfsm/core/components.py +7 -8
- ekfsm/core/connections.py +19 -0
- ekfsm/core/slots.py +6 -8
- ekfsm/core/sysfs.py +215 -25
- ekfsm/core/utils.py +128 -64
- ekfsm/devices/__init__.py +27 -7
- ekfsm/devices/buttons.py +251 -0
- ekfsm/devices/colorLed.py +110 -0
- ekfsm/devices/coretemp.py +35 -13
- ekfsm/devices/eeprom.py +73 -45
- ekfsm/devices/ekf_ccu_uc.py +76 -54
- ekfsm/devices/ekf_sur_led.py +6 -2
- ekfsm/devices/generic.py +200 -59
- ekfsm/devices/gpio.py +37 -27
- ekfsm/devices/iio.py +15 -31
- ekfsm/devices/iio_thermal_humidity.py +20 -13
- ekfsm/devices/imu.py +8 -4
- ekfsm/devices/io4edge.py +185 -0
- ekfsm/devices/ledArray.py +54 -0
- ekfsm/devices/mux.py +46 -8
- ekfsm/devices/pixelDisplay.py +141 -0
- ekfsm/devices/pmbus.py +74 -101
- ekfsm/devices/smbios.py +28 -8
- ekfsm/devices/smbus.py +1 -1
- ekfsm/devices/thermal_humidity.py +80 -0
- ekfsm/devices/toggles.py +90 -0
- ekfsm/devices/utils.py +52 -8
- ekfsm/devices/watchdog.py +79 -0
- ekfsm/exceptions.py +28 -7
- ekfsm/lock.py +48 -21
- ekfsm/simctrl.py +37 -83
- ekfsm/system.py +89 -73
- ekfsm/utils.py +44 -0
- {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/METADATA +12 -6
- ekfsm-1.5.0.dist-info/RECORD +57 -0
- ekfsm-0.13.0a183.dist-info/RECORD +0 -45
- {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/WHEEL +0 -0
- {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/entry_points.txt +0 -0
ekfsm/exceptions.py
CHANGED
|
@@ -3,6 +3,7 @@ from enum import Enum
|
|
|
3
3
|
|
|
4
4
|
class EkfSmException(Exception):
|
|
5
5
|
"""Base class for all exceptions in the EKFSM Library"""
|
|
6
|
+
|
|
6
7
|
pass
|
|
7
8
|
|
|
8
9
|
|
|
@@ -12,7 +13,7 @@ class ConfigError(EkfSmException):
|
|
|
12
13
|
pass
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class
|
|
16
|
+
class SysFSError(EkfSmException):
|
|
16
17
|
"""Error while handling sysfs pseudo file system"""
|
|
17
18
|
|
|
18
19
|
pass
|
|
@@ -31,9 +32,31 @@ class GPIOError(EkfSmException):
|
|
|
31
32
|
def __init__(self, error_type: ErrorType, details: str | None = None):
|
|
32
33
|
self.error_type = error_type
|
|
33
34
|
self.details = details
|
|
34
|
-
super().__init__(
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
super().__init__(f"{error_type.value}: {details}" if details else error_type.value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DriverError(EkfSmException):
|
|
39
|
+
"""No driver found for device"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HWMonError(EkfSmException):
|
|
45
|
+
"""No HwMon entry found for device"""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConversionError(EkfSmException):
|
|
51
|
+
"""Failed to convert"""
|
|
52
|
+
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UnsupportedModeError(EkfSmException):
|
|
57
|
+
"""Format not supported"""
|
|
58
|
+
|
|
59
|
+
pass
|
|
37
60
|
|
|
38
61
|
|
|
39
62
|
class FirmwareNodeError(EkfSmException):
|
|
@@ -47,9 +70,7 @@ class DataCorruptionError(EkfSmException):
|
|
|
47
70
|
|
|
48
71
|
def __init__(self, details: str | None = None):
|
|
49
72
|
self.details = details
|
|
50
|
-
super().__init__(
|
|
51
|
-
f"Data corruption: {details}" if details else "Data corruption"
|
|
52
|
-
)
|
|
73
|
+
super().__init__(f"Data corruption: {details}" if details else "Data corruption")
|
|
53
74
|
|
|
54
75
|
|
|
55
76
|
class AcquisitionError(EkfSmException):
|
ekfsm/lock.py
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Some devices or device functions don't allow concurrent access.
|
|
3
|
+
The locking mechanism is used to ensure that only one process/thread can
|
|
4
|
+
access the device (function) at a time.
|
|
5
|
+
|
|
6
|
+
Locking granularity is defined by the device.
|
|
7
|
+
It may be at the device level or function level.
|
|
8
|
+
|
|
9
|
+
Application can choose to use the locking mechanism or not.
|
|
10
|
+
By default, the locking mechanism is enabled and uses the default
|
|
11
|
+
lockfile root directory ``/var/lock/ekfsm``.
|
|
12
|
+
|
|
13
|
+
Use :func:`locking_configure` to enable or disable the locking mechanism or to change
|
|
14
|
+
the lockfile root directory.
|
|
15
|
+
"""
|
|
16
|
+
|
|
1
17
|
import fcntl
|
|
2
18
|
import os
|
|
3
|
-
from pathlib import Path
|
|
4
19
|
from contextlib import contextmanager
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
# Some devices or device functions don't allow concurrent access.
|
|
8
|
-
# The locking mechanism is used to ensure that only one process/thread can
|
|
9
|
-
# access the device (function) at a time.
|
|
10
|
-
#
|
|
11
|
-
# Locking granularity is defined by the device.
|
|
12
|
-
# It may be at the device level or function level.
|
|
13
|
-
#
|
|
14
|
-
# Application can choose to use the locking mechanism or not.
|
|
15
|
-
# By default, the locking mechanism is enabled and uses the default
|
|
16
|
-
# lockfile root directory /var/lock/ekfsm.
|
|
17
|
-
#
|
|
18
|
-
# Use locking_configure() to enable or disable the locking mechanism or to change
|
|
19
|
-
# the lockfile root directory.
|
|
20
|
+
from pathlib import Path
|
|
20
21
|
|
|
21
22
|
USE_LOCK = True
|
|
22
23
|
LOCKFILE_ROOT = "/var/lock/ekfsm"
|
|
@@ -27,8 +28,12 @@ def locking_configure(enable: bool, lockfile_root: str = LOCKFILE_ROOT):
|
|
|
27
28
|
"""
|
|
28
29
|
Configures the locking mechanism.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
enable
|
|
34
|
+
Whether to enable or disable locking.
|
|
35
|
+
lockfile_root
|
|
36
|
+
The root directory for lockfiles.
|
|
32
37
|
"""
|
|
33
38
|
global USE_LOCK, LOCKFILE_ROOT
|
|
34
39
|
USE_LOCK = enable
|
|
@@ -55,9 +60,12 @@ class Locker:
|
|
|
55
60
|
|
|
56
61
|
Example
|
|
57
62
|
-------
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
.. code-block:: python
|
|
64
|
+
|
|
65
|
+
with Locker("mysharedresourcename").lock():
|
|
66
|
+
# Access the shared resource here
|
|
67
|
+
pass
|
|
68
|
+
|
|
61
69
|
"""
|
|
62
70
|
|
|
63
71
|
def __init__(self, module: str):
|
|
@@ -72,6 +80,17 @@ class Locker:
|
|
|
72
80
|
ALL_LOCKERS.append(self)
|
|
73
81
|
|
|
74
82
|
def cleanup(self):
|
|
83
|
+
"""
|
|
84
|
+
Cleans up the lock file and closes the lock file descriptor.
|
|
85
|
+
|
|
86
|
+
Important
|
|
87
|
+
---------
|
|
88
|
+
This method should be called when the lock is no longer needed.
|
|
89
|
+
|
|
90
|
+
Note
|
|
91
|
+
----
|
|
92
|
+
It is automatically called when the context manager exits.
|
|
93
|
+
"""
|
|
75
94
|
if not USE_LOCK:
|
|
76
95
|
return
|
|
77
96
|
if self.lock_fd is not None:
|
|
@@ -84,6 +103,14 @@ class Locker:
|
|
|
84
103
|
|
|
85
104
|
@contextmanager
|
|
86
105
|
def lock(self):
|
|
106
|
+
"""
|
|
107
|
+
Locks the resource for exclusive access.
|
|
108
|
+
|
|
109
|
+
Note
|
|
110
|
+
----
|
|
111
|
+
This method is a context manager that locks the resource when entered
|
|
112
|
+
and releases the lock when exited.
|
|
113
|
+
"""
|
|
87
114
|
if not USE_LOCK:
|
|
88
115
|
yield
|
|
89
116
|
self.lock_fd = os.open(self.lockfile_path, os.O_RDWR)
|
ekfsm/simctrl.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import socket
|
|
2
2
|
import struct
|
|
3
|
-
from unittest.mock import patch
|
|
4
3
|
from pathlib import Path
|
|
4
|
+
from typing import List
|
|
5
|
+
from unittest.mock import patch
|
|
5
6
|
|
|
6
|
-
from
|
|
7
|
-
from ekfsm.devices.gpio import EKFIdSimGpio
|
|
8
|
-
from ekfsm.devices.gpio import SimGpio
|
|
9
|
-
from .core.sysfs import set_sysfs_root
|
|
10
|
-
from .core.components import SysTree
|
|
7
|
+
from smbus2 import SMBus
|
|
11
8
|
|
|
9
|
+
from ekfsm.devices.gpio import EKFIdSimGpio, SimGpio
|
|
10
|
+
from ekfsm.devices.smbus import SimSMBus
|
|
11
|
+
|
|
12
|
+
from .core.components import SysTree
|
|
13
|
+
from .core.sysfs import set_sysfs_root
|
|
12
14
|
from .devices import GPIO
|
|
13
|
-
from typing import List
|
|
14
|
-
from smbus2 import SMBus
|
|
15
15
|
|
|
16
16
|
GPIO_SIM_MAPPING: dict[str, SimGpio] = {}
|
|
17
|
-
SMBUS_SIM_MAPPING: dict[str,
|
|
17
|
+
SMBUS_SIM_MAPPING: dict[str, SimSMBus] = {}
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def register_gpio_sim(major: int, minor: int, sim_gpio: SimGpio) -> None:
|
|
@@ -31,14 +31,14 @@ def find_gpio_dev_with_major_minor(major: int, minor: int) -> SimGpio:
|
|
|
31
31
|
return GPIO_SIM_MAPPING[name]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def register_smbus_sim(bus_num: int, i2c_addr: int, sim_smbus:
|
|
34
|
+
def register_smbus_sim(bus_num: int, i2c_addr: int, sim_smbus: SimSMBus) -> None:
|
|
35
35
|
name = f"{bus_num}:{i2c_addr}"
|
|
36
36
|
if name in SMBUS_SIM_MAPPING:
|
|
37
37
|
raise ValueError(f"SMBUS_SIM_MAPPING already contains {name}")
|
|
38
38
|
SMBUS_SIM_MAPPING[name] = sim_smbus
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def find_smbus_dev(bus_num: int, i2c_addr: int) ->
|
|
41
|
+
def find_smbus_dev(bus_num: int, i2c_addr: int) -> SimSMBus:
|
|
42
42
|
name = f"{bus_num}:{i2c_addr}"
|
|
43
43
|
|
|
44
44
|
if name not in SMBUS_SIM_MAPPING:
|
|
@@ -46,7 +46,7 @@ def find_smbus_dev(bus_num: int, i2c_addr: int) -> SimSmbus:
|
|
|
46
46
|
return SMBUS_SIM_MAPPING[name]
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
class
|
|
49
|
+
class GPIOSimulator(GPIO):
|
|
50
50
|
def __init__(
|
|
51
51
|
self,
|
|
52
52
|
name: str,
|
|
@@ -80,7 +80,7 @@ class GpioSimulator(GPIO):
|
|
|
80
80
|
return f"GPIO_SIM({self.name})"
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
class
|
|
83
|
+
class SMBusSimulator:
|
|
84
84
|
def __init__(self, bus_num: int):
|
|
85
85
|
self.bus_num = bus_num
|
|
86
86
|
|
|
@@ -100,73 +100,12 @@ class SmbusSimulator:
|
|
|
100
100
|
find_smbus_dev(self.bus_num, i2c_addr).write_word_data(cmd, data)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
patched_methods.append(
|
|
110
|
-
patch.object(GPIO, "num_lines", new_callable=lambda: GpioSimulator.num_lines)
|
|
111
|
-
)
|
|
112
|
-
patched_methods.append(
|
|
113
|
-
patch.object(GPIO, "set_pin", new_callable=lambda: GpioSimulator.set_pin)
|
|
114
|
-
)
|
|
115
|
-
patched_methods.append(
|
|
116
|
-
patch.object(GPIO, "get_pin", new_callable=lambda: GpioSimulator.get_pin)
|
|
117
|
-
)
|
|
118
|
-
patched_methods.append(
|
|
119
|
-
patch.object(
|
|
120
|
-
GPIO, "set_direction", new_callable=lambda: GpioSimulator.set_direction
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
patched_methods.append(
|
|
124
|
-
patch.object(GPIO, "__str__", new_callable=lambda: GpioSimulator.__str__)
|
|
125
|
-
)
|
|
126
|
-
for pm in patched_methods:
|
|
127
|
-
pm.start()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def enable_smbus_simulation():
|
|
131
|
-
patched_methods = []
|
|
132
|
-
|
|
133
|
-
patched_methods.append(
|
|
134
|
-
patch.object(SMBus, "__init__", new_callable=lambda: SmbusSimulator.__init__)
|
|
135
|
-
)
|
|
136
|
-
patched_methods.append(
|
|
137
|
-
patch.object(
|
|
138
|
-
SMBus, "read_word_data", new_callable=lambda: SmbusSimulator.read_word_data
|
|
139
|
-
)
|
|
140
|
-
)
|
|
141
|
-
patched_methods.append(
|
|
142
|
-
patch.object(
|
|
143
|
-
SMBus,
|
|
144
|
-
"read_block_data",
|
|
145
|
-
new_callable=lambda: SmbusSimulator.read_block_data,
|
|
146
|
-
)
|
|
147
|
-
)
|
|
148
|
-
patched_methods.append(
|
|
149
|
-
patch.object(
|
|
150
|
-
SMBus,
|
|
151
|
-
"write_block_data",
|
|
152
|
-
new_callable=lambda: SmbusSimulator.write_block_data,
|
|
153
|
-
)
|
|
154
|
-
)
|
|
155
|
-
patched_methods.append(
|
|
156
|
-
patch.object(
|
|
157
|
-
SMBus, "write_byte", new_callable=lambda: SmbusSimulator.write_byte
|
|
158
|
-
)
|
|
159
|
-
)
|
|
160
|
-
patched_methods.append(
|
|
161
|
-
patch.object(
|
|
162
|
-
SMBus,
|
|
163
|
-
"write_word_data",
|
|
164
|
-
new_callable=lambda: SmbusSimulator.write_word_data,
|
|
165
|
-
)
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
for pm in patched_methods:
|
|
169
|
-
pm.start()
|
|
103
|
+
def patch_methods(cls, simulator, methods):
|
|
104
|
+
patched = []
|
|
105
|
+
for i, method in enumerate(methods):
|
|
106
|
+
if hasattr(cls, method) and hasattr(simulator, method):
|
|
107
|
+
patched.append(patch.object(cls, method, new_callable=lambda: getattr(simulator, method)))
|
|
108
|
+
patched[i].start()
|
|
170
109
|
|
|
171
110
|
|
|
172
111
|
def enable_simulation(sysfs_path: Path | str) -> None:
|
|
@@ -180,8 +119,23 @@ def enable_simulation(sysfs_path: Path | str) -> None:
|
|
|
180
119
|
sysfs_path = Path(sysfs_path)
|
|
181
120
|
|
|
182
121
|
set_sysfs_root(sysfs_path)
|
|
183
|
-
|
|
184
|
-
|
|
122
|
+
patch_methods(
|
|
123
|
+
GPIO,
|
|
124
|
+
GPIOSimulator,
|
|
125
|
+
["__init__", "num_lines", "set_pin", "get_pin", "set_direction", "__str__"],
|
|
126
|
+
)
|
|
127
|
+
patch_methods(
|
|
128
|
+
SMBus,
|
|
129
|
+
SMBusSimulator,
|
|
130
|
+
[
|
|
131
|
+
"__init__",
|
|
132
|
+
"read_word_data",
|
|
133
|
+
"read_block_data",
|
|
134
|
+
"write_block_data",
|
|
135
|
+
"write_byte",
|
|
136
|
+
"write_word_data",
|
|
137
|
+
],
|
|
138
|
+
)
|
|
185
139
|
|
|
186
140
|
|
|
187
141
|
def register_gpio_simulations():
|
|
@@ -189,7 +143,7 @@ def register_gpio_simulations():
|
|
|
189
143
|
register_gpio_sim(233, 2, EKFIdSimGpio(0x34, 0xA, 0x0, 0x1)) # CCU Rev 0
|
|
190
144
|
|
|
191
145
|
|
|
192
|
-
class SocketSmbus(
|
|
146
|
+
class SocketSmbus(SimSMBus):
|
|
193
147
|
def __init__(self, host: str, port: int) -> None:
|
|
194
148
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
195
149
|
self.sock.connect((host, port))
|
ekfsm/system.py
CHANGED
|
@@ -1,52 +1,19 @@
|
|
|
1
|
-
from typing import Tuple, Any, Generator
|
|
2
1
|
from pathlib import Path
|
|
3
|
-
from
|
|
2
|
+
from typing import Any, Tuple
|
|
4
3
|
|
|
5
|
-
from ekfsm.core.components import SysTree
|
|
6
4
|
import yaml
|
|
5
|
+
from munch import Munch, munchify
|
|
7
6
|
|
|
8
|
-
from .core.
|
|
7
|
+
from ekfsm.core.components import SysTree
|
|
8
|
+
from ekfsm.utils import all_board_cfg_files, find_board_config
|
|
9
9
|
|
|
10
10
|
from .config import load_config
|
|
11
|
-
from .core import
|
|
12
|
-
from .core.slots import Slots
|
|
11
|
+
from .core import HWModule
|
|
12
|
+
from .core.slots import Slot, Slots, SlotType
|
|
13
13
|
from .exceptions import ConfigError
|
|
14
14
|
from .log import ekfsm_logger
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
_CFG_DIR = Path(__file__).parent / "boards"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def find_board_config(module_type: str) -> Path | None:
|
|
21
|
-
"""
|
|
22
|
-
Find a matching board config in `boards/oem/` given the module type specified in
|
|
23
|
-
the system configuration file.
|
|
24
|
-
|
|
25
|
-
Parameters
|
|
26
|
-
----------
|
|
27
|
-
module_type
|
|
28
|
-
Board type specified in the system configuration for a slot.
|
|
29
|
-
It must consist of an OEM and the board type, separated by whitespace. Neither
|
|
30
|
-
part may contain any other whitespace.
|
|
31
|
-
"""
|
|
32
|
-
oem, board = module_type.split(maxsplit=1)
|
|
33
|
-
if (
|
|
34
|
-
path := _CFG_DIR / "oem" / oem.strip().lower() / f"{board.strip().lower()}.yaml"
|
|
35
|
-
).exists():
|
|
36
|
-
return path
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def all_board_cfg_files() -> Generator[Path, None, None]:
|
|
41
|
-
"""
|
|
42
|
-
Generator that recursively yields all *.yaml files in a directory
|
|
43
|
-
"""
|
|
44
|
-
path = Path(_CFG_DIR)
|
|
45
|
-
for item in path.rglob("*.yaml"):
|
|
46
|
-
if item.is_file():
|
|
47
|
-
yield item
|
|
48
|
-
|
|
49
|
-
|
|
50
17
|
class System(SysTree):
|
|
51
18
|
"""
|
|
52
19
|
A System represents a CPCI system.
|
|
@@ -57,15 +24,6 @@ class System(SysTree):
|
|
|
57
24
|
|
|
58
25
|
Visual representation of the system is shown as trees of HW Modules and attached devices.
|
|
59
26
|
|
|
60
|
-
Iterating over the system will iterate over all boards in the system.
|
|
61
|
-
|
|
62
|
-
Accessing boards
|
|
63
|
-
----------------
|
|
64
|
-
<board_name>
|
|
65
|
-
The board object can be accessed by its name.
|
|
66
|
-
<slot_number>
|
|
67
|
-
The board object can be accessed by its slot number.
|
|
68
|
-
|
|
69
27
|
Attributes
|
|
70
28
|
----------
|
|
71
29
|
name
|
|
@@ -81,11 +39,23 @@ class System(SysTree):
|
|
|
81
39
|
config
|
|
82
40
|
The system configuration.
|
|
83
41
|
|
|
42
|
+
|
|
43
|
+
Accessing boards
|
|
44
|
+
----------------
|
|
45
|
+
|
|
46
|
+
Iterating over the system will iterate over all boards in the system.
|
|
47
|
+
|
|
48
|
+
<board_name>
|
|
49
|
+
The board object can be accessed by its name.
|
|
50
|
+
<slot_number>
|
|
51
|
+
The board object can be accessed by its slot number.
|
|
52
|
+
|
|
53
|
+
|
|
84
54
|
Example
|
|
85
55
|
-------
|
|
86
56
|
>>> from ekfsm.system import System
|
|
87
57
|
>>> system = System("path/to/config.yaml")
|
|
88
|
-
>>> print(system) # Print the system configuration as trees of
|
|
58
|
+
>>> print(system) # Print the system configuration as trees of HWModules
|
|
89
59
|
>>> system.print() # same as above
|
|
90
60
|
>>> cpu = system.cpu # Access the CPU board by its name
|
|
91
61
|
>>> cpu = system[0] # Access the CPU board by its slot index (index as in configuration file)
|
|
@@ -101,6 +71,9 @@ class System(SysTree):
|
|
|
101
71
|
----------
|
|
102
72
|
config
|
|
103
73
|
Path to the config that specifies the system and how the slots are filled.
|
|
74
|
+
abort
|
|
75
|
+
If True, abort the program if a board cannot be created. If False, leave the slot empty.
|
|
76
|
+
Default is False.
|
|
104
77
|
"""
|
|
105
78
|
self.config_path = config
|
|
106
79
|
self.config = load_config(str(self.config_path))
|
|
@@ -116,7 +89,7 @@ class System(SysTree):
|
|
|
116
89
|
|
|
117
90
|
def _init_system(self, config: Path):
|
|
118
91
|
self.slots: Slots = Slots()
|
|
119
|
-
self.boards: list[
|
|
92
|
+
self.boards: list[HWModule] = []
|
|
120
93
|
|
|
121
94
|
self.master, self.master_slot_number = self._create_master()
|
|
122
95
|
if self.master is None:
|
|
@@ -125,7 +98,7 @@ class System(SysTree):
|
|
|
125
98
|
self.logger.info(f"Master board found in slot {self.master_slot_number}")
|
|
126
99
|
|
|
127
100
|
for i, slot_cfg in enumerate(self.config.system_config.slots):
|
|
128
|
-
hwmod:
|
|
101
|
+
hwmod: HWModule | Slot | None
|
|
129
102
|
if i == self.master_slot_number:
|
|
130
103
|
hwmod = self.master
|
|
131
104
|
else:
|
|
@@ -164,35 +137,44 @@ class System(SysTree):
|
|
|
164
137
|
"""
|
|
165
138
|
self.__init__(self.config_path)
|
|
166
139
|
|
|
167
|
-
def _create_master(self) -> Tuple[
|
|
140
|
+
def _create_master(self) -> Tuple[HWModule | None, int]:
|
|
168
141
|
for i, slot in enumerate(self.config.system_config.slots):
|
|
169
142
|
if "attributes" in slot:
|
|
170
143
|
if "is_master" in slot.attributes:
|
|
171
144
|
if slot.attributes.is_master:
|
|
172
145
|
master, _ = self.create_hwmodule(slot, i, None)
|
|
173
146
|
if master is not None:
|
|
174
|
-
master.master = master
|
|
147
|
+
master.master = master # ???
|
|
175
148
|
return master, i
|
|
176
149
|
else:
|
|
177
150
|
return None, -1
|
|
178
151
|
return None, -1 # ???
|
|
179
152
|
|
|
180
153
|
def create_hwmodule(
|
|
181
|
-
self, slot_entry: Munch, slot_number: int, master:
|
|
182
|
-
) -> Tuple[
|
|
154
|
+
self, slot_entry: Munch, slot_number: int, master: HWModule | None
|
|
155
|
+
) -> Tuple[HWModule | None, Slot]:
|
|
183
156
|
"""
|
|
184
|
-
Create
|
|
157
|
+
Create HWModule object for the slot.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
slot_entry
|
|
162
|
+
The slot entry config (usually part of the system configuration).
|
|
163
|
+
slot_number
|
|
164
|
+
The slot number of the slot.
|
|
165
|
+
master
|
|
166
|
+
The master board of the system.
|
|
185
167
|
|
|
186
168
|
Returns
|
|
187
169
|
-------
|
|
188
|
-
|
|
170
|
+
HWModule and Slot. HWodule is None if it cannot be created.
|
|
189
171
|
"""
|
|
190
172
|
slot = self._create_slot(slot_entry, slot_number, master)
|
|
191
173
|
board_type = slot_entry.desired_hwmodule_type
|
|
192
174
|
board_name = slot_entry.desired_hwmodule_name
|
|
193
175
|
|
|
194
176
|
self.logger.debug(
|
|
195
|
-
f"Creating
|
|
177
|
+
f"Creating HWModule {board_type} (desired name: {board_name}) in slot {slot.name}"
|
|
196
178
|
)
|
|
197
179
|
|
|
198
180
|
if board_type != "":
|
|
@@ -210,12 +192,12 @@ class System(SysTree):
|
|
|
210
192
|
except Exception as e:
|
|
211
193
|
if self.abort:
|
|
212
194
|
self.logger.error(
|
|
213
|
-
f"failed to create desired
|
|
195
|
+
f"failed to create desired HWModule {board_type} (as {board_name}): {e}. Aborting!"
|
|
214
196
|
)
|
|
215
197
|
raise e
|
|
216
198
|
else:
|
|
217
199
|
self.logger.error(
|
|
218
|
-
f"failed to create desired
|
|
200
|
+
f"failed to create desired HWModule {board_type} (as {board_name}): {e}. Leaving slot empty!"
|
|
219
201
|
)
|
|
220
202
|
return None, slot
|
|
221
203
|
|
|
@@ -239,7 +221,7 @@ class System(SysTree):
|
|
|
239
221
|
continue
|
|
240
222
|
except Exception as e:
|
|
241
223
|
self.logger.debug(
|
|
242
|
-
f"failed to create
|
|
224
|
+
f"failed to create HWmodule {path} for slot {slot.name}: {e}"
|
|
243
225
|
)
|
|
244
226
|
continue
|
|
245
227
|
|
|
@@ -252,7 +234,7 @@ class System(SysTree):
|
|
|
252
234
|
return None, slot
|
|
253
235
|
|
|
254
236
|
def _create_slot(
|
|
255
|
-
self, slot_entry: Munch, slot_number: int, master:
|
|
237
|
+
self, slot_entry: Munch, slot_number: int, master: HWModule | None
|
|
256
238
|
) -> Slot:
|
|
257
239
|
attributes = None
|
|
258
240
|
if "attributes" in slot_entry:
|
|
@@ -271,14 +253,14 @@ class System(SysTree):
|
|
|
271
253
|
|
|
272
254
|
def _create_hwmodule_from_cfg_file(
|
|
273
255
|
self, slot: Slot, board_name: str, path: Path
|
|
274
|
-
) ->
|
|
256
|
+
) -> HWModule:
|
|
275
257
|
"""
|
|
276
|
-
Try to create a
|
|
258
|
+
Try to create a HWModule object from a board config file.
|
|
277
259
|
It does not probe the hardware.
|
|
278
260
|
|
|
279
261
|
Returns
|
|
280
262
|
-------
|
|
281
|
-
|
|
263
|
+
HWModule object.
|
|
282
264
|
|
|
283
265
|
Raises
|
|
284
266
|
------
|
|
@@ -299,7 +281,7 @@ class System(SysTree):
|
|
|
299
281
|
f"Slot type mismatch for slot {slot.name}: {cfg.slot_type} != {slot.slot_type}"
|
|
300
282
|
)
|
|
301
283
|
|
|
302
|
-
hwmod =
|
|
284
|
+
hwmod = HWModule(
|
|
303
285
|
instance_name=board_name,
|
|
304
286
|
config=yaml_data,
|
|
305
287
|
slot=slot,
|
|
@@ -309,7 +291,21 @@ class System(SysTree):
|
|
|
309
291
|
|
|
310
292
|
return hwmod
|
|
311
293
|
|
|
312
|
-
def get_module_in_slot(self, idx: int) ->
|
|
294
|
+
def get_module_in_slot(self, idx: int) -> HWModule | None:
|
|
295
|
+
"""
|
|
296
|
+
Get the HWModule in the given slot.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
idx
|
|
301
|
+
The slot index.
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
HWModule
|
|
305
|
+
The HWModule in the given slot.
|
|
306
|
+
None
|
|
307
|
+
If no HWModule is present in the given slot.
|
|
308
|
+
"""
|
|
313
309
|
return next(
|
|
314
310
|
(
|
|
315
311
|
v.hwmodule
|
|
@@ -319,16 +315,36 @@ class System(SysTree):
|
|
|
319
315
|
None,
|
|
320
316
|
)
|
|
321
317
|
|
|
322
|
-
def get_module_by_name(self, name: str) ->
|
|
318
|
+
def get_module_by_name(self, name: str) -> HWModule | None:
|
|
319
|
+
"""
|
|
320
|
+
Get the HWModule by its name.
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
name
|
|
325
|
+
The name of the HWModule.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
HWModule
|
|
330
|
+
The HWModule with the given name.
|
|
331
|
+
None
|
|
332
|
+
If no HWModule is present with the given name.
|
|
333
|
+
"""
|
|
323
334
|
return next(
|
|
324
|
-
(
|
|
335
|
+
(
|
|
336
|
+
b
|
|
337
|
+
for b in self.boards
|
|
338
|
+
if getattr(b, "instance_name", None) is not None
|
|
339
|
+
and getattr(b, "instance_name").lower() == name.lower()
|
|
340
|
+
),
|
|
325
341
|
None,
|
|
326
342
|
)
|
|
327
343
|
|
|
328
344
|
def __iter__(self):
|
|
329
345
|
return iter(self.boards)
|
|
330
346
|
|
|
331
|
-
def __getitem__(self, key) ->
|
|
347
|
+
def __getitem__(self, key) -> HWModule:
|
|
332
348
|
if isinstance(key, int):
|
|
333
349
|
value = self.get_module_in_slot(key)
|
|
334
350
|
else:
|
|
@@ -342,9 +358,9 @@ class System(SysTree):
|
|
|
342
358
|
def __getattr__(self, name: str) -> Any:
|
|
343
359
|
"""Access board by attribute using dot notation"""
|
|
344
360
|
# This fixes mypy error: "... has no object ..."
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
361
|
+
if (hw_module := self.get_module_by_name(name)) is not None:
|
|
362
|
+
return hw_module
|
|
363
|
+
|
|
348
364
|
raise AttributeError(
|
|
349
365
|
f"'{type(self).__name__}' object has no board with name '{name}'"
|
|
350
366
|
)
|
ekfsm/utils.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
_CFG_DIR = Path(__file__).parent / "boards"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_board_config(module_type: str) -> Path | None:
|
|
8
|
+
"""
|
|
9
|
+
Find a matching board config in `boards/oem/` given the module type specified in
|
|
10
|
+
the system configuration file.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
module_type
|
|
15
|
+
Board type specified in the system configuration for a slot.
|
|
16
|
+
It must consist of an OEM and the board type, separated by whitespace. Neither
|
|
17
|
+
part may contain any other whitespace.
|
|
18
|
+
"""
|
|
19
|
+
oem, board = module_type.split(maxsplit=1)
|
|
20
|
+
if (path := _CFG_DIR / "oem" / oem.strip().lower() / f"{board.strip().lower()}.yaml").exists():
|
|
21
|
+
return path
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def all_board_cfg_files() -> Generator[Path, None, None]:
|
|
26
|
+
"""
|
|
27
|
+
Generator that recursively yields all *.yaml files in the config directory.
|
|
28
|
+
|
|
29
|
+
Yields
|
|
30
|
+
------
|
|
31
|
+
item: Path
|
|
32
|
+
Path to a config file.
|
|
33
|
+
"""
|
|
34
|
+
path = Path(_CFG_DIR)
|
|
35
|
+
for item in path.rglob("*.yaml"):
|
|
36
|
+
if item.is_file():
|
|
37
|
+
yield item
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def next_or_raise(it, exc):
|
|
41
|
+
value = next(it, None)
|
|
42
|
+
if value is None:
|
|
43
|
+
raise exc
|
|
44
|
+
return value
|