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/core/slots.py ADDED
@@ -0,0 +1,201 @@
1
+ from enum import Enum
2
+ from ekfsm.core.components import HwModule
3
+
4
+
5
+ from munch import Munch
6
+
7
+
8
+ from typing import Any
9
+
10
+
11
+ class SlotType(Enum):
12
+ """
13
+ Define the types of slots that can be found in a chassis.
14
+
15
+ The following slot types are defined:
16
+ - CPCI_S0_UTILITY: CompactPCI Serial Utility Connector
17
+ - CPCI_S0_SYS: CompactPCI Serial System Slot
18
+ - CPCI_S0_PER: CompactPCI Serial Peripheral Slot
19
+ - CPCI_S0_PSU: CompactPCI Serial Power Supply Slot
20
+ """
21
+
22
+ CPCI_S0_UTILITY = 1 # CompactPCI Serial Utility Connector
23
+ CPCI_S0_SYS = 2 # CompactPCI Serial System Slot
24
+ CPCI_S0_PER = 3 # CompactPCI Serial Peripheral Slot
25
+ CPCI_S0_PSU = 4 # CompactPCI Serial Power Supply Slot
26
+
27
+ @classmethod
28
+ def from_string(cls, name: str) -> "SlotType":
29
+ try:
30
+ return cls[name]
31
+ except KeyError:
32
+ raise ValueError(f"Invalid {cls.__name__}: {name}")
33
+
34
+ def to_string(self) -> str:
35
+ return self.name
36
+
37
+
38
+ class Slot:
39
+ """
40
+ A slot represents a physical slot in a chassis.
41
+
42
+ Parameters
43
+ ----------
44
+ name
45
+ The name of the slot, e.g. "SlotA" or "CPCI-SYSTEMSLOT"
46
+ slot_type
47
+ The type of the slot, e.g. SlotType.CPCI_S0_SYS
48
+ desired_hwmodule_type
49
+ The desired type of the hardware module that should be in the slot (currently unused)
50
+ desired_hwmodule_name
51
+ The name to be used for the hardware module instance in the slot. (currently unused)
52
+ master
53
+ The master board of the system
54
+ hwmodule
55
+ The hardware module that is in the slot
56
+ number
57
+ The number of the slot
58
+ attributes
59
+ Additional attributes
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ name: str,
65
+ slot_type: SlotType,
66
+ desired_hwmodule_type: str,
67
+ desired_hwmodule_name: str,
68
+ number: int,
69
+ hwmodule: HwModule | None = None,
70
+ master: HwModule | None = None,
71
+ attributes: Munch | None = None,
72
+ ) -> None:
73
+ self._name = name
74
+ self.slot_type = slot_type
75
+ self._desired_hwmodule_type = desired_hwmodule_type
76
+ self._desired_hwmodule_name = desired_hwmodule_name
77
+ self.number = number
78
+ self.hwmodule = hwmodule
79
+ self.master = master
80
+ self.attributes = attributes
81
+
82
+ def info(self) -> dict[str, Any]:
83
+ """
84
+ Returns a dictionary with information about the slot.
85
+
86
+ - name (str): The name of the slot
87
+ - slot_type (str): The type of the slot
88
+ - number (int): The number of the slot
89
+ - desired_hwmodule_type (str): The desired type of the hardware module
90
+ - actual_hwmodule_type (str): The actual type of the hardware module
91
+ - desired_hwmodule_name (str): The desired name of the hardware module
92
+ - is_populated (bool): Is the slot populated?
93
+ - is_correctly_populated (bool): Is the slot correctly populated?
94
+ """
95
+ return {
96
+ "name": self._name,
97
+ "slot_type": self.slot_type.to_string(),
98
+ "number": self.number,
99
+ "desired_hwmodule_type": self._desired_hwmodule_type,
100
+ "actual_hwmodule_type": self.hwmodule.board_type if self.hwmodule else None,
101
+ "desired_hwmodule_name": self._desired_hwmodule_name,
102
+ "is_populated": self.is_populated,
103
+ "is_correctly_populated": self.is_correctly_populated,
104
+ }
105
+
106
+ def __repr__(self) -> str:
107
+ return (
108
+ f"{self.__class__.__name__}(name={self._name}, slot_type={self.slot_type})"
109
+ )
110
+
111
+ @property
112
+ def name(self) -> str:
113
+ """
114
+ Return the name of the slot.
115
+ """
116
+ return self._name
117
+
118
+ @property
119
+ def is_populated(self) -> bool:
120
+ """
121
+ Return True if the slot is populated, False otherwise.
122
+ """
123
+ return self.hwmodule is not None
124
+
125
+ @property
126
+ def is_correctly_populated(self) -> bool:
127
+ """
128
+ Return True if the slot is populated with the desired hardware module type, False otherwise.
129
+ """
130
+ return (
131
+ self.hwmodule is not None
132
+ and self.hwmodule.board_type.lower() == self._desired_hwmodule_type.lower()
133
+ )
134
+
135
+
136
+ class Slots(Munch):
137
+ """
138
+ A collection of slots.
139
+
140
+ Slots are stored in a dictionary-like object, where the key is the slot name and the value is the Slot object.
141
+ Slots can be accessed by name, by number or via an attribute access matching the key.
142
+
143
+ Example
144
+ -------
145
+ >>> from ekfsm.slot import Slot, Slots, SlotType
146
+ >>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
147
+ >>> slots = Slots((slotA.name, slotA))
148
+ >>> print(slots[name])
149
+ >>> print(slots.slotA) # attribute access, same as slots[name]
150
+ >>> print(slots[3]) # number access, same as slots.slotA
151
+ """
152
+
153
+ def __init__(self, *args, **kwargs) -> None:
154
+ super().__init__(*args, **kwargs)
155
+
156
+ def __getitem__(self, key: int | str) -> Slot:
157
+ """
158
+ Get a slot by name, number or attribute access.
159
+ """
160
+ if isinstance(key, int):
161
+ return next(slot for slot in self.values() if slot.number == key)
162
+
163
+ return super().__getitem__(key)
164
+
165
+ def __setitem__(self, key, value):
166
+ """
167
+ Add a Slot object to the collection.
168
+
169
+ Raises
170
+ ------
171
+ ValueError
172
+ if:
173
+ - the value is not a Slot object
174
+ - the key does not match the slot name
175
+ - the slot already exists in collection
176
+ - or the slot number is not unique
177
+ """
178
+ if not isinstance(value, Slot):
179
+ raise ValueError("Only Slot instances can be added to a Slots collection.")
180
+ elif key != value.name:
181
+ raise ValueError("Slot name must match key.")
182
+ elif value in self.values():
183
+ raise ValueError("Slot already exists in collection.")
184
+ elif value.number in [slot.number for slot in self.values()]:
185
+ raise ValueError("Slot number must be unique.")
186
+
187
+ return super().__setitem__(key, value)
188
+
189
+ def add(self, slot: Slot) -> None:
190
+ """
191
+ Add a Slot object to the collection, where the name of the slot object is used as the key.
192
+
193
+ Example
194
+ -------
195
+ >>> from ekfsm.slot import Slot, Slots, SlotType
196
+ >>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
197
+ >>> slots = Slots()
198
+ >>> slots.add(slotA) # add slotA to the collection
199
+ >>> print(slots.SlotA) # attribute access, same as slots["SlotA"]
200
+ """
201
+ self[slot.name] = slot
ekfsm/core/sysfs.py ADDED
@@ -0,0 +1,91 @@
1
+ from pathlib import Path
2
+
3
+ SYSFS_ROOT = Path("/sys")
4
+
5
+
6
+ def sysfs_root() -> Path:
7
+ return SYSFS_ROOT
8
+
9
+
10
+ def set_sysfs_root(path: Path) -> None:
11
+ global SYSFS_ROOT
12
+ SYSFS_ROOT = path
13
+
14
+
15
+ def file_is_sysfs_attr(path: Path) -> bool:
16
+ return path.is_file() and not path.stat().st_mode & 0o111
17
+
18
+
19
+ class SysFSAttribute:
20
+ """
21
+ A SysFSAttribute is a singular sysfs attribute located somewhere in */sys*.
22
+
23
+ Attributes
24
+ ----------
25
+ path : Path
26
+ path to the underlying file for the SysFSAttribute instance
27
+ name : str
28
+ attribute name, or the last part of self.path
29
+
30
+ Methods
31
+ -------
32
+ read() -> Optional[str]
33
+ Reads the file attribute if it exists
34
+ write(data: Union[str, int]) -> None
35
+ Writes data to the file attribute
36
+
37
+ """
38
+
39
+ def __init__(self, path: Path):
40
+ self.path = path
41
+ if not path.exists() or not path.is_file():
42
+ raise FileNotFoundError("Invalid sysfs attribute path")
43
+
44
+ self.name: str = path.name
45
+
46
+ def read_utf8(self) -> str:
47
+ return self.path.read_text()
48
+
49
+ def read_bytes(self) -> bytes:
50
+ return self.path.read_bytes()
51
+
52
+ def write(self, data: str | bytes | None, offset: int = 0) -> None:
53
+ if self.is_sysfs_attr() and data is not None:
54
+ mode = 'r+' if isinstance(data, str) else 'rb+'
55
+ with open(self.path, mode) as f:
56
+ f.seek(offset)
57
+ f.write(data)
58
+
59
+ def is_sysfs_attr(self) -> bool:
60
+ return file_is_sysfs_attr(self.path)
61
+
62
+ def __repr__(self):
63
+ return f"SysFSAttribute: {self.name}"
64
+
65
+
66
+ def list_sysfs_attributes(path: Path) -> list[SysFSAttribute]:
67
+ if not path.exists() or not path.is_dir():
68
+ raise FileNotFoundError(f"Invalid sysfs directory: {path}")
69
+
70
+ return [SysFSAttribute(item) for item in path.iterdir() if file_is_sysfs_attr(item)]
71
+
72
+
73
+ class SysFSDevice:
74
+ def __init__(self, base_dir: Path):
75
+ self.path: Path = base_dir
76
+ self.attributes: list[SysFSAttribute] = list_sysfs_attributes(self.path)
77
+
78
+ def pre(self) -> None:
79
+ pass
80
+
81
+ def post(self) -> None:
82
+ pass
83
+
84
+ def write_attr(self, attr: str, data: str | bytes, offset: int = 0) -> None:
85
+ next(x for x in self.attributes if x.name == attr).write(data, offset)
86
+
87
+ def read_attr_utf8(self, attr: str) -> str:
88
+ return next(x for x in self.attributes if x.name == attr).read_utf8()
89
+
90
+ def read_attr_bytes(self, attr: str) -> bytes:
91
+ return next(x for x in self.attributes if x.name == attr).read_bytes()
ekfsm/core/utils.py ADDED
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+ from anytree import AnyNode
5
+
6
+ from ekfsm.devices import CLASS_MAP
7
+ from ekfsm.exceptions import ConfigError
8
+
9
+ if TYPE_CHECKING:
10
+ from .components import HwModule
11
+ from ekfsm.devices.generic import Device
12
+
13
+
14
+ class BoardDictImporter:
15
+
16
+ def __init__(self, nodecls=AnyNode):
17
+ self.nodecls = nodecls
18
+
19
+ def import_(self, logger: logging.Logger, data, parent=None):
20
+ """Import tree from `data`."""
21
+ return self.__import(logger, data, parent=parent)
22
+
23
+ def __import(self, logger: logging.Logger, data, parent=None):
24
+ from .components import HwModule
25
+
26
+ device_type = data.get("device_type")
27
+ nodecls = CLASS_MAP.get(device_type)
28
+ if nodecls is None:
29
+ raise ConfigError(f"Unknown device type: {device_type}")
30
+
31
+ children = data.pop("children", [])
32
+ if parent is not None and isinstance(parent, HwModule):
33
+ pass
34
+
35
+ node = nodecls(parent=parent, **data)
36
+
37
+ if children is not None:
38
+ for child in children:
39
+ try:
40
+ logger.debug(f"Importing sub device {child}")
41
+ self.__import(logger, child, parent=node)
42
+ except Exception as e:
43
+ logger.error(
44
+ f"Failed to import sub device {child}: {e}. continue anyway"
45
+ )
46
+ return node
47
+
48
+
49
+ def deserialize_hardware_tree(
50
+ logger: logging.Logger, data: dict, parent: "HwModule"
51
+ ) -> tuple[str, str, str, list["Device"]]:
52
+ importer = BoardDictImporter()
53
+
54
+ id = data.pop("id", None)
55
+ if id is None:
56
+ raise ConfigError("Board configuration must contain `id`")
57
+ name = data.pop("name", None)
58
+ if name is None:
59
+ raise ConfigError("Board configuration must contain `name`")
60
+ slot_type = data.pop("slot_type")
61
+ if slot_type is None:
62
+ raise ConfigError("Board configuration must contain `board_type`")
63
+
64
+ children = data.pop("children", None)
65
+ devices = []
66
+ if children:
67
+ for child in children:
68
+ try:
69
+ logger.debug(f"Importing top level device {child}")
70
+ node = importer.import_(logger, child, parent=parent)
71
+ devices.append(node)
72
+ except Exception as e:
73
+ logger.error(
74
+ f"Failed to import top level device {child}: {e}. continue anyway"
75
+ )
76
+
77
+ return id, name, slot_type, devices
@@ -0,0 +1,28 @@
1
+ from ekfsm.devices.generic import Device
2
+ from ekfsm.devices.hwmon import HWMON
3
+ from ekfsm.devices.smbios import SMBIOS
4
+ from .eeprom import EEPROM, EKF_EEPROM, EKF_CCU_EEPROM
5
+ from .pmbus import PmBus
6
+ from .gpio import GPIO, EKFIdentificationIOExpander, GPIOExpander
7
+ from .ekf_sur_led import EKFSurLed
8
+ from .ekf_ccu_uc import EKFCcuUc
9
+ from .iio_thermal_humidity import IIOThermalHumidity
10
+ from .mux import I2CMux, MuxChannel
11
+
12
+ CLASS_MAP = {
13
+ "GenericDevice": Device,
14
+ "I2CMux": I2CMux,
15
+ "MuxChannel": MuxChannel,
16
+ "GPIO": GPIO,
17
+ "GPIOExpander": GPIOExpander,
18
+ "EKFIdentificationIOExpander": EKFIdentificationIOExpander,
19
+ "EEPROM": EEPROM,
20
+ "EKF_EEPROM": EKF_EEPROM,
21
+ "EKF_CCU_EEPROM": EKF_CCU_EEPROM,
22
+ "EKFCcuUc": EKFCcuUc,
23
+ "PmBus": PmBus,
24
+ "SMBIOS": SMBIOS,
25
+ "HWMON": HWMON,
26
+ "EKFSurLed": EKFSurLed,
27
+ "IIOThermalHumidity": IIOThermalHumidity,
28
+ }