ekfsm 0.11.0b1.post3__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 +13 -0
- ekfsm/boards/oem/ekf/ccu.yaml +68 -0
- ekfsm/boards/oem/ekf/sc5-festival.yaml +30 -0
- ekfsm/boards/oem/ekf/sc9-toccata.yaml +31 -0
- ekfsm/boards/oem/ekf/spv-mystic.yaml +68 -0
- ekfsm/boards/oem/ekf/sq1-track.yaml +41 -0
- ekfsm/boards/oem/ekf/srf-fan.yaml +48 -0
- ekfsm/boards/oem/ekf/sur-uart.yaml +72 -0
- ekfsm/boards/oem/hitron/hdrc-300.yaml +20 -0
- ekfsm/cli.py +111 -0
- ekfsm/config.py +37 -0
- ekfsm/core/__init__.py +4 -0
- ekfsm/core/components.py +120 -0
- ekfsm/core/probe.py +10 -0
- ekfsm/core/slots.py +201 -0
- ekfsm/core/sysfs.py +91 -0
- ekfsm/core/utils.py +77 -0
- ekfsm/devices/__init__.py +28 -0
- ekfsm/devices/eeprom.py +1054 -0
- ekfsm/devices/ekf_ccu_uc.py +390 -0
- ekfsm/devices/ekf_sur_led.py +67 -0
- ekfsm/devices/generic.py +245 -0
- ekfsm/devices/gpio.py +340 -0
- ekfsm/devices/hwmon.py +71 -0
- ekfsm/devices/iio.py +58 -0
- ekfsm/devices/iio_thermal_humidity.py +41 -0
- ekfsm/devices/mux.py +39 -0
- ekfsm/devices/pmbus.py +65 -0
- ekfsm/devices/smbios.py +38 -0
- ekfsm/devices/utils.py +16 -0
- ekfsm/exceptions.py +58 -0
- ekfsm/log.py +28 -0
- ekfsm/py.typed +2 -0
- ekfsm/simctrl.py +241 -0
- ekfsm/system.py +326 -0
- ekfsm-0.11.0b1.post3.dist-info/METADATA +86 -0
- ekfsm-0.11.0b1.post3.dist-info/RECORD +39 -0
- ekfsm-0.11.0b1.post3.dist-info/WHEEL +4 -0
- ekfsm-0.11.0b1.post3.dist-info/entry_points.txt +2 -0
ekfsm/exceptions.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EkfSmException(Exception):
|
|
5
|
+
"""Base class for all exceptions in the EKFSM Library"""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigError(EkfSmException):
|
|
10
|
+
"""Error in configuration"""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SYSFSError(EkfSmException):
|
|
16
|
+
"""Error while handling sysfs pseudo file system"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GPIOError(EkfSmException):
|
|
22
|
+
"""Error while handling GPIO"""
|
|
23
|
+
|
|
24
|
+
class ErrorType(Enum):
|
|
25
|
+
INVALID_PIN = "Pin not found"
|
|
26
|
+
NO_MATCHING_DEVICE = "No matching device found"
|
|
27
|
+
NO_MAJOR_MINOR = "No major/minor number found"
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def __init__(self, error_type: ErrorType, details: str | None = None):
|
|
32
|
+
self.error_type = error_type
|
|
33
|
+
self.details = details
|
|
34
|
+
super().__init__(
|
|
35
|
+
f"{error_type.value}: {details}" if details else error_type.value
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FirmwareNodeError(EkfSmException):
|
|
40
|
+
"""Error while handlig firmware node"""
|
|
41
|
+
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DataCorruptionError(EkfSmException):
|
|
46
|
+
"""Error while handling data corruption"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, details: str | None = None):
|
|
49
|
+
self.details = details
|
|
50
|
+
super().__init__(
|
|
51
|
+
f"Data corruption: {details}" if details else "Data corruption"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AcquisitionError(EkfSmException):
|
|
56
|
+
"""Error while handling data acquisition"""
|
|
57
|
+
|
|
58
|
+
pass
|
ekfsm/log.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# We follow the recommendations from https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
|
|
5
|
+
#
|
|
6
|
+
# By default, if the application does not configure logging, the logging module will log
|
|
7
|
+
# only messages with level WARNING or above and is using the default formatting, i.e.
|
|
8
|
+
# only the message is printed.
|
|
9
|
+
#
|
|
10
|
+
# To get a more verbose output, the application should call, for example
|
|
11
|
+
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ekfsm_logger(name: str) -> logging.Logger:
|
|
15
|
+
"""
|
|
16
|
+
Create a logger with the name 'ekfsm:name'
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
logging.Logger
|
|
21
|
+
The logger object.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
name
|
|
26
|
+
The name of the module, class or object that is using the logger.
|
|
27
|
+
"""
|
|
28
|
+
return logging.getLogger("ekfsm:" + name)
|
ekfsm/py.typed
ADDED
ekfsm/simctrl.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import socket
|
|
3
|
+
import struct
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
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 SystemComponent
|
|
11
|
+
|
|
12
|
+
from .devices import GPIO
|
|
13
|
+
from typing import List
|
|
14
|
+
from smbus2 import SMBus
|
|
15
|
+
|
|
16
|
+
GPIO_SIM_MAPPING = {}
|
|
17
|
+
SMBUS_SIM_MAPPING = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SimSmbus(ABC):
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def read_word_data(self, cmd: int) -> int:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def read_block_data(self, cmd: int) -> List[int]:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def write_block_data(self, cmd: int, data: List[int]):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def write_byte(self, cmd: int):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def write_word_data(self, cmd: int, data: int):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_gpio_sim(major: int, minor: int, sim_gpio: SimGpio) -> None:
|
|
43
|
+
name = f"{major}:{minor}"
|
|
44
|
+
if name in GPIO_SIM_MAPPING:
|
|
45
|
+
raise ValueError(f"GPIO_SIM_MAPPING already contains {name}")
|
|
46
|
+
GPIO_SIM_MAPPING[name] = sim_gpio
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def find_gpio_dev_with_major_minor(major: int, minor: int) -> SimGpio:
|
|
50
|
+
name = f"{major}:{minor}"
|
|
51
|
+
if name not in GPIO_SIM_MAPPING:
|
|
52
|
+
raise ValueError(f"GPIO_SIM_MAPPING does not contain {name}")
|
|
53
|
+
return GPIO_SIM_MAPPING[name]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def register_smbus_sim(bus_num: int, i2c_addr: int, sim_smbus: SimSmbus) -> None:
|
|
57
|
+
name = f"{bus_num}:{i2c_addr}"
|
|
58
|
+
if name in SMBUS_SIM_MAPPING:
|
|
59
|
+
raise ValueError(f"SMBUS_SIM_MAPPING already contains {name}")
|
|
60
|
+
SMBUS_SIM_MAPPING[name] = sim_smbus
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def find_smbus_dev(bus_num: int, i2c_addr: int) -> SimSmbus:
|
|
64
|
+
name = f"{bus_num}:{i2c_addr}"
|
|
65
|
+
|
|
66
|
+
if name not in SMBUS_SIM_MAPPING:
|
|
67
|
+
raise ValueError(f"SMBUS_SIM_MAPPING does not contain {name}")
|
|
68
|
+
return SMBUS_SIM_MAPPING[name]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GpioSimulator(GPIO):
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
name: str,
|
|
75
|
+
parent: SystemComponent | None = None,
|
|
76
|
+
*args,
|
|
77
|
+
**kwargs,
|
|
78
|
+
):
|
|
79
|
+
super(GPIO, self).__init__(
|
|
80
|
+
name,
|
|
81
|
+
parent,
|
|
82
|
+
*args,
|
|
83
|
+
**kwargs,
|
|
84
|
+
)
|
|
85
|
+
major, minor = self._find_gpio_dev(parent, *args, **kwargs)
|
|
86
|
+
self._sim_gpio = find_gpio_dev_with_major_minor(major, minor)
|
|
87
|
+
self.number = minor
|
|
88
|
+
|
|
89
|
+
def num_lines(self) -> int:
|
|
90
|
+
return self._sim_gpio.num_lines()
|
|
91
|
+
|
|
92
|
+
def set_pin(self, pin: int, value: bool) -> None:
|
|
93
|
+
self._sim_gpio.set_pin(pin, value)
|
|
94
|
+
|
|
95
|
+
def get_pin(self, pin: int) -> bool:
|
|
96
|
+
return self._sim_gpio.get_pin(pin)
|
|
97
|
+
|
|
98
|
+
def set_direction(self, pin: int, direction: bool) -> None:
|
|
99
|
+
self._sim_gpio.set_direction(pin, direction)
|
|
100
|
+
|
|
101
|
+
def __str__(self) -> str:
|
|
102
|
+
return f"GPIO_SIM({self.name})"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SmbusSimulator:
|
|
106
|
+
def __init__(self, bus_num: int):
|
|
107
|
+
self.bus_num = bus_num
|
|
108
|
+
|
|
109
|
+
def read_word_data(self, i2c_addr: int, cmd: int) -> int:
|
|
110
|
+
return find_smbus_dev(self.bus_num, i2c_addr).read_word_data(cmd)
|
|
111
|
+
|
|
112
|
+
def read_block_data(self, i2c_addr: int, cmd: int) -> List[int]:
|
|
113
|
+
return find_smbus_dev(self.bus_num, i2c_addr).read_block_data(cmd)
|
|
114
|
+
|
|
115
|
+
def write_block_data(self, i2c_addr: int, cmd: int, data: List[int]):
|
|
116
|
+
find_smbus_dev(self.bus_num, i2c_addr).write_block_data(cmd, data)
|
|
117
|
+
|
|
118
|
+
def write_byte(self, i2c_addr: int, cmd: int):
|
|
119
|
+
find_smbus_dev(self.bus_num, i2c_addr).write_byte(cmd)
|
|
120
|
+
|
|
121
|
+
def write_word_data(self, i2c_addr: int, cmd: int, data: int):
|
|
122
|
+
find_smbus_dev(self.bus_num, i2c_addr).write_word_data(cmd, data)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def enable_gpio_simulation():
|
|
126
|
+
patched_methods = []
|
|
127
|
+
|
|
128
|
+
patched_methods.append(
|
|
129
|
+
patch.object(GPIO, "__init__", new_callable=lambda: GpioSimulator.__init__)
|
|
130
|
+
)
|
|
131
|
+
patched_methods.append(
|
|
132
|
+
patch.object(GPIO, "num_lines", new_callable=lambda: GpioSimulator.num_lines)
|
|
133
|
+
)
|
|
134
|
+
patched_methods.append(
|
|
135
|
+
patch.object(GPIO, "set_pin", new_callable=lambda: GpioSimulator.set_pin)
|
|
136
|
+
)
|
|
137
|
+
patched_methods.append(
|
|
138
|
+
patch.object(GPIO, "get_pin", new_callable=lambda: GpioSimulator.get_pin)
|
|
139
|
+
)
|
|
140
|
+
patched_methods.append(
|
|
141
|
+
patch.object(
|
|
142
|
+
GPIO, "set_direction", new_callable=lambda: GpioSimulator.set_direction
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
patched_methods.append(
|
|
146
|
+
patch.object(GPIO, "__str__", new_callable=lambda: GpioSimulator.__str__)
|
|
147
|
+
)
|
|
148
|
+
for pm in patched_methods:
|
|
149
|
+
pm.start()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def enable_smbus_simulation():
|
|
153
|
+
patched_methods = []
|
|
154
|
+
|
|
155
|
+
patched_methods.append(
|
|
156
|
+
patch.object(SMBus, "__init__", new_callable=lambda: SmbusSimulator.__init__)
|
|
157
|
+
)
|
|
158
|
+
patched_methods.append(
|
|
159
|
+
patch.object(
|
|
160
|
+
SMBus, "read_word_data", new_callable=lambda: SmbusSimulator.read_word_data
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
patched_methods.append(
|
|
164
|
+
patch.object(
|
|
165
|
+
SMBus,
|
|
166
|
+
"read_block_data",
|
|
167
|
+
new_callable=lambda: SmbusSimulator.read_block_data,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
patched_methods.append(
|
|
171
|
+
patch.object(
|
|
172
|
+
SMBus,
|
|
173
|
+
"write_block_data",
|
|
174
|
+
new_callable=lambda: SmbusSimulator.write_block_data,
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
patched_methods.append(
|
|
178
|
+
patch.object(
|
|
179
|
+
SMBus, "write_byte", new_callable=lambda: SmbusSimulator.write_byte
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
patched_methods.append(
|
|
183
|
+
patch.object(
|
|
184
|
+
SMBus,
|
|
185
|
+
"write_word_data",
|
|
186
|
+
new_callable=lambda: SmbusSimulator.write_word_data,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
for pm in patched_methods:
|
|
191
|
+
pm.start()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def enable_simulation(sysfs_path: Path):
|
|
195
|
+
global GPIO_SIM_MAPPING
|
|
196
|
+
GPIO_SIM_MAPPING = {}
|
|
197
|
+
|
|
198
|
+
global SMBUS_SIM_MAPPING
|
|
199
|
+
SMBUS_SIM_MAPPING = {}
|
|
200
|
+
|
|
201
|
+
set_sysfs_root(sysfs_path)
|
|
202
|
+
enable_gpio_simulation()
|
|
203
|
+
enable_smbus_simulation()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def register_gpio_simulations():
|
|
207
|
+
register_gpio_sim(233, 1, EKFIdSimGpio(0x38, 0x1, 0x0, 0x6)) # SRF Rev 0
|
|
208
|
+
register_gpio_sim(233, 2, EKFIdSimGpio(0x34, 0xA, 0x0, 0x1)) # CCU Rev 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class SocketSmbus(SimSmbus):
|
|
212
|
+
def __init__(self, host: str, port: int) -> None:
|
|
213
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
214
|
+
self.sock.connect((host, port))
|
|
215
|
+
|
|
216
|
+
def read_word_data(self, cmd: int) -> int:
|
|
217
|
+
req = struct.pack("BB", 0x4, cmd)
|
|
218
|
+
self.sock.send(req)
|
|
219
|
+
data = self.sock.recv(2)
|
|
220
|
+
return struct.unpack("<H", data)[0]
|
|
221
|
+
|
|
222
|
+
def read_block_data(self, cmd: int) -> List[int]:
|
|
223
|
+
req = struct.pack("BB", 0x1, cmd)
|
|
224
|
+
self.sock.send(req)
|
|
225
|
+
_count = self.sock.recv(1)
|
|
226
|
+
count = struct.unpack("B", _count)[0]
|
|
227
|
+
data = self.sock.recv(count)
|
|
228
|
+
return [int(data[i]) for i in range(0, len(data), 1)]
|
|
229
|
+
|
|
230
|
+
def write_block_data(self, cmd: int, data: List[int]):
|
|
231
|
+
_data = bytes(data)
|
|
232
|
+
hdr = struct.pack("BBB", 0x2, cmd, len(_data))
|
|
233
|
+
self.sock.send(hdr + _data)
|
|
234
|
+
|
|
235
|
+
def write_byte(self, cmd: int):
|
|
236
|
+
hdr = struct.pack("BB", 0x3, cmd)
|
|
237
|
+
self.sock.send(hdr)
|
|
238
|
+
|
|
239
|
+
def write_word_data(self, cmd: int, data: int):
|
|
240
|
+
hdr = struct.pack("BBH", 0x5, cmd, data)
|
|
241
|
+
self.sock.send(hdr)
|
ekfsm/system.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from typing import Tuple, Any, Generator
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from munch import Munch, munchify
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from .core.slots import Slot, SlotType
|
|
8
|
+
|
|
9
|
+
from .config import load_config
|
|
10
|
+
from .core import HwModule
|
|
11
|
+
from .core.slots import Slots
|
|
12
|
+
from .exceptions import ConfigError
|
|
13
|
+
from .log import ekfsm_logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_CFG_DIR = Path(__file__).parent / "boards"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def find_board_config(module_type: str) -> Path | None:
|
|
20
|
+
"""
|
|
21
|
+
Find a matching board config in `boards/oem/` given the module type specified in
|
|
22
|
+
the system configuration file.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
module_type
|
|
27
|
+
Board type specified in the system configuration for a slot.
|
|
28
|
+
It must consist of an OEM and the board type, separated by whitespace. Neither
|
|
29
|
+
part may contain any other whitespace.
|
|
30
|
+
"""
|
|
31
|
+
oem, board = module_type.split(maxsplit=1)
|
|
32
|
+
if (
|
|
33
|
+
path := _CFG_DIR / "oem" / oem.strip().lower() / f"{board.strip().lower()}.yaml"
|
|
34
|
+
).exists():
|
|
35
|
+
return path
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def all_board_cfg_files() -> Generator[Path, None, None]:
|
|
40
|
+
"""
|
|
41
|
+
Generator that recursively yields all *.yaml files in a directory
|
|
42
|
+
"""
|
|
43
|
+
path = Path(_CFG_DIR)
|
|
44
|
+
for item in path.rglob("*.yaml"):
|
|
45
|
+
if item.is_file():
|
|
46
|
+
yield item
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class System:
|
|
50
|
+
"""
|
|
51
|
+
A System represents a CPCI system.
|
|
52
|
+
|
|
53
|
+
Once initialised, it will create:
|
|
54
|
+
- a list of boards that are present in the system which can be accessed either by name or by slot number.
|
|
55
|
+
- a list of slots that are present in the system which can be accessed under the slots attribute.
|
|
56
|
+
|
|
57
|
+
Visual representation of the system is shown as trees of HW Modules and attached devices.
|
|
58
|
+
|
|
59
|
+
Iterating over the system will iterate over all boards in the system.
|
|
60
|
+
|
|
61
|
+
Accessing boards
|
|
62
|
+
----------------
|
|
63
|
+
<board_name>
|
|
64
|
+
The board object can be accessed by its name.
|
|
65
|
+
<slot_number>
|
|
66
|
+
The board object can be accessed by its slot number.
|
|
67
|
+
|
|
68
|
+
Attributes
|
|
69
|
+
----------
|
|
70
|
+
name
|
|
71
|
+
The name of the system.
|
|
72
|
+
slots
|
|
73
|
+
A dictionary-like object that contains all slots in the system.
|
|
74
|
+
boards
|
|
75
|
+
A list of all boards in the system.
|
|
76
|
+
master
|
|
77
|
+
The master board of the system.
|
|
78
|
+
master_slot_number
|
|
79
|
+
The slot number of the master board.
|
|
80
|
+
config
|
|
81
|
+
The system configuration.
|
|
82
|
+
|
|
83
|
+
Example
|
|
84
|
+
-------
|
|
85
|
+
>>> from ekfsm.system import System
|
|
86
|
+
>>> system = System("path/to/config.yaml")
|
|
87
|
+
>>> print(system) # Print the system configuration as trees of HwModules
|
|
88
|
+
>>> system.print() # same as above
|
|
89
|
+
>>> cpu = system.cpu # Access the CPU board by its name
|
|
90
|
+
>>> cpu = system[0] # Access the CPU board by its slot index (index as in configuration file)
|
|
91
|
+
>>> print(system.slots) # Print all slots in the system
|
|
92
|
+
>>> print(system.boards) # Print all boards in the system
|
|
93
|
+
>>> for b in system: # Iterate over all boards in the system
|
|
94
|
+
>>> print(b.name + b.slot.name) # Print the name of the board and the slot it is in
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, config: Path) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
config
|
|
102
|
+
Path to the config that specifies the system and how the slots are filled.
|
|
103
|
+
"""
|
|
104
|
+
self.logger = ekfsm_logger(__name__)
|
|
105
|
+
self._init_system(config)
|
|
106
|
+
self._init_slot_attrs()
|
|
107
|
+
|
|
108
|
+
def _init_system(self, config: Path):
|
|
109
|
+
self.config_path = config
|
|
110
|
+
self.config = load_config(str(self.config_path))
|
|
111
|
+
self.name = self.config.system_config.name
|
|
112
|
+
self.slots: Slots = Slots()
|
|
113
|
+
self.boards: list[HwModule] = []
|
|
114
|
+
|
|
115
|
+
self.master, self.master_slot_number = self._create_master()
|
|
116
|
+
if self.master is None:
|
|
117
|
+
raise ConfigError("No master board found in system configuration!")
|
|
118
|
+
|
|
119
|
+
self.logger.info(f"Master board found in slot {self.master_slot_number}")
|
|
120
|
+
|
|
121
|
+
for i, slot_cfg in enumerate(self.config.system_config.slots):
|
|
122
|
+
hwmod: HwModule | Slot | None
|
|
123
|
+
if i == self.master_slot_number:
|
|
124
|
+
hwmod = self.master
|
|
125
|
+
else:
|
|
126
|
+
hwmod, slot = self.create_hwmodule(slot_cfg, i, self.master)
|
|
127
|
+
|
|
128
|
+
if hwmod is not None:
|
|
129
|
+
hwmod.slot.hwmodule = hwmod
|
|
130
|
+
self.boards.append(hwmod)
|
|
131
|
+
self.slots.add(hwmod.slot)
|
|
132
|
+
else:
|
|
133
|
+
self.slots.add(slot)
|
|
134
|
+
|
|
135
|
+
def _init_slot_attrs(self):
|
|
136
|
+
for board in self.boards:
|
|
137
|
+
setattr(self, board.instance_name.lower(), board)
|
|
138
|
+
|
|
139
|
+
def reload(self):
|
|
140
|
+
"""
|
|
141
|
+
Reload the current system configuration.
|
|
142
|
+
|
|
143
|
+
Important
|
|
144
|
+
---------
|
|
145
|
+
This will rebuild all system objects and reinitialize the system tree.
|
|
146
|
+
"""
|
|
147
|
+
self._init_system(self.config_path)
|
|
148
|
+
|
|
149
|
+
def _create_master(self) -> Tuple[HwModule | None, int]:
|
|
150
|
+
for i, slot in enumerate(self.config.system_config.slots):
|
|
151
|
+
if "attributes" in slot:
|
|
152
|
+
if "is_master" in slot.attributes:
|
|
153
|
+
if slot.attributes.is_master:
|
|
154
|
+
master, _ = self.create_hwmodule(slot, i, None)
|
|
155
|
+
if master is not None:
|
|
156
|
+
master.master = master
|
|
157
|
+
return master, i
|
|
158
|
+
else:
|
|
159
|
+
return None, -1
|
|
160
|
+
return None, -1 # ???
|
|
161
|
+
|
|
162
|
+
def create_hwmodule(
|
|
163
|
+
self, slot_entry: Munch, slot_number: int, master: HwModule | None
|
|
164
|
+
) -> Tuple[HwModule | None, Slot]:
|
|
165
|
+
"""
|
|
166
|
+
Create HwModule object for the slot.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
HwModule and Slot. HwModule is None if it cannot be created.
|
|
171
|
+
"""
|
|
172
|
+
slot = self._create_slot(slot_entry, slot_number, master)
|
|
173
|
+
board_type = slot_entry.desired_hwmodule_type
|
|
174
|
+
board_name = slot_entry.desired_hwmodule_name
|
|
175
|
+
|
|
176
|
+
self.logger.debug(
|
|
177
|
+
f"Creating hwmodule {board_type} (desired name: {board_name}) in slot {slot.name}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if board_type != "":
|
|
181
|
+
# try to create first the desired board
|
|
182
|
+
path = find_board_config(board_type)
|
|
183
|
+
if path is None:
|
|
184
|
+
self.logger.error(
|
|
185
|
+
f"No board config found for {board_type} (desired name: {board_name})"
|
|
186
|
+
)
|
|
187
|
+
return None, slot
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
hwmod = self._create_hwmodule_from_cfg_file(slot, board_name, path)
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
self.logger.error(
|
|
194
|
+
f"failed to create desired hwmodule {board_type} (as {board_name}): {e}. Leaving slot empty!"
|
|
195
|
+
)
|
|
196
|
+
return None, slot
|
|
197
|
+
|
|
198
|
+
# try to probe desired board type
|
|
199
|
+
if hwmod.probe():
|
|
200
|
+
self.logger.info(
|
|
201
|
+
f"Found desired board type {hwmod.board_type} for slot {slot.name}"
|
|
202
|
+
)
|
|
203
|
+
return hwmod, slot
|
|
204
|
+
|
|
205
|
+
# try all other boards types. Maybe someone inserted the wrong board
|
|
206
|
+
self.logger.info(
|
|
207
|
+
f"Probing failed. Trying all other board types for slot {slot.name}"
|
|
208
|
+
)
|
|
209
|
+
for path in all_board_cfg_files():
|
|
210
|
+
try:
|
|
211
|
+
hwmod = self._create_hwmodule_from_cfg_file(slot, board_name, path)
|
|
212
|
+
except ConfigError:
|
|
213
|
+
# slot type not matching, ignore
|
|
214
|
+
continue
|
|
215
|
+
except Exception as e:
|
|
216
|
+
self.logger.debug(
|
|
217
|
+
f"failed to create hwmodule {path} for slot {slot.name}: {e}"
|
|
218
|
+
)
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
if hwmod.probe():
|
|
222
|
+
self.logger.info(
|
|
223
|
+
f"Found other board type {hwmod.board_type} for slot {slot.name}"
|
|
224
|
+
)
|
|
225
|
+
return hwmod, slot
|
|
226
|
+
|
|
227
|
+
return None, slot
|
|
228
|
+
|
|
229
|
+
def _create_slot(
|
|
230
|
+
self, slot_entry: Munch, slot_number: int, master: HwModule | None
|
|
231
|
+
) -> Slot:
|
|
232
|
+
attributes = None
|
|
233
|
+
if "attributes" in slot_entry:
|
|
234
|
+
attributes = slot_entry.attributes
|
|
235
|
+
|
|
236
|
+
return Slot(
|
|
237
|
+
slot_entry.name,
|
|
238
|
+
SlotType.from_string(slot_entry.slot_type),
|
|
239
|
+
slot_entry.desired_hwmodule_type,
|
|
240
|
+
slot_entry.desired_hwmodule_name,
|
|
241
|
+
slot_number,
|
|
242
|
+
None,
|
|
243
|
+
master,
|
|
244
|
+
attributes,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def _create_hwmodule_from_cfg_file(
|
|
248
|
+
self, slot: Slot, board_name: str, path: Path
|
|
249
|
+
) -> HwModule:
|
|
250
|
+
"""
|
|
251
|
+
Try to create a HwModule object from a board config file.
|
|
252
|
+
It does not probe the hardware.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
HwModule object.
|
|
257
|
+
|
|
258
|
+
Raises
|
|
259
|
+
------
|
|
260
|
+
FileNotFoundError
|
|
261
|
+
If the board config file does not exist.
|
|
262
|
+
ConfigError
|
|
263
|
+
If the slot type in the config file does not match the slot type.
|
|
264
|
+
Exception
|
|
265
|
+
If something else went wrong.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
with open(path) as file:
|
|
269
|
+
yaml_data = yaml.safe_load(file)
|
|
270
|
+
cfg = munchify(yaml_data)
|
|
271
|
+
# only instantiate if slot type matches
|
|
272
|
+
if cfg.slot_type != slot.slot_type.to_string():
|
|
273
|
+
raise ConfigError(
|
|
274
|
+
f"Slot type mismatch for slot {slot.name}: {cfg.slot_type} != {slot.slot_type}"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return HwModule(instance_name=board_name, config=yaml_data, slot=slot)
|
|
278
|
+
|
|
279
|
+
def get_module_in_slot(self, idx: int) -> HwModule | None:
|
|
280
|
+
return next(
|
|
281
|
+
(
|
|
282
|
+
v.hwmodule
|
|
283
|
+
for k, v in self.slots.items()
|
|
284
|
+
if getattr(v, "number", None) == idx
|
|
285
|
+
),
|
|
286
|
+
None,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def get_module_by_name(self, name: str) -> HwModule | None:
|
|
290
|
+
return next(
|
|
291
|
+
(b for b in self.boards if getattr(b, "instance_name", None) == name),
|
|
292
|
+
None,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def __iter__(self):
|
|
296
|
+
return iter(self.boards)
|
|
297
|
+
|
|
298
|
+
def __getitem__(self, key) -> HwModule:
|
|
299
|
+
if isinstance(key, int):
|
|
300
|
+
value = self.get_module_in_slot(key)
|
|
301
|
+
else:
|
|
302
|
+
value = self.get_module_by_name(key)
|
|
303
|
+
|
|
304
|
+
if value is None:
|
|
305
|
+
raise KeyError(f"Board {key} not found in system!")
|
|
306
|
+
|
|
307
|
+
return value
|
|
308
|
+
|
|
309
|
+
def __getattr__(self, name: str) -> Any:
|
|
310
|
+
"""Access board by attribute using dot notation"""
|
|
311
|
+
# This fixes mypy error: "... has no object ..."
|
|
312
|
+
hwModule = self.get_module_by_name(name)
|
|
313
|
+
if hwModule is not None:
|
|
314
|
+
return hwModule
|
|
315
|
+
raise AttributeError(
|
|
316
|
+
f"'{type(self).__name__}' object has no board with name '{name}'"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def __str__(self) -> str:
|
|
320
|
+
output = ""
|
|
321
|
+
for b in self.boards:
|
|
322
|
+
output += b._render_tree()
|
|
323
|
+
return output
|
|
324
|
+
|
|
325
|
+
def print(self) -> None:
|
|
326
|
+
print(self)
|