ekfsm 1.0.2__py3-none-any.whl → 1.2.0a7__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/cli.py +26 -1
- ekfsm/core/__init__.py +10 -0
- ekfsm/core/slots.py +2 -2
- ekfsm/core/sysfs.py +3 -13
- ekfsm/devices/coretemp.py +2 -4
- ekfsm/devices/eeprom.py +17 -2
- ekfsm/devices/ekf_ccu_uc.py +39 -8
- ekfsm/devices/generic.py +55 -0
- ekfsm/devices/iio_thermal_humidity.py +9 -1
- ekfsm/devices/imu.py +8 -4
- ekfsm/devices/mux.py +30 -0
- ekfsm/devices/pmbus.py +23 -2
- ekfsm/lock.py +47 -20
- ekfsm/system.py +57 -44
- ekfsm/utils.py +41 -0
- {ekfsm-1.0.2.dist-info → ekfsm-1.2.0a7.dist-info}/METADATA +1 -1
- {ekfsm-1.0.2.dist-info → ekfsm-1.2.0a7.dist-info}/RECORD +20 -19
- {ekfsm-1.0.2.dist-info → ekfsm-1.2.0a7.dist-info}/WHEEL +0 -0
- {ekfsm-1.0.2.dist-info → ekfsm-1.2.0a7.dist-info}/entry_points.txt +0 -0
ekfsm/__init__.py
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
from .core.slots import Slot
|
|
2
1
|
from .system import System
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
5
|
-
from .exceptions import ConfigError
|
|
6
|
-
from .lock import locking_cleanup, locking_configure
|
|
2
|
+
from .config import load_config
|
|
3
|
+
from .log import ekfsm_logger
|
|
7
4
|
|
|
8
|
-
__all__ = (
|
|
9
|
-
"System",
|
|
10
|
-
"ConfigError",
|
|
11
|
-
"HwModule",
|
|
12
|
-
"Slot",
|
|
13
|
-
"SlotType",
|
|
14
|
-
"locking_cleanup",
|
|
15
|
-
"locking_configure",
|
|
16
|
-
)
|
|
5
|
+
__all__ = ("System", "load_config", "ekfsm_logger")
|
ekfsm/cli.py
CHANGED
|
@@ -16,6 +16,8 @@ logger = ekfsm_logger(__name__)
|
|
|
16
16
|
|
|
17
17
|
sm: System | None = None
|
|
18
18
|
|
|
19
|
+
__all__ = ("cli", "main", "write", "show")
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
@click.group()
|
|
21
23
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
|
@@ -37,7 +39,30 @@ sm: System | None = None
|
|
|
37
39
|
)
|
|
38
40
|
def cli(verbose, debug, sysfs, config):
|
|
39
41
|
global sm
|
|
40
|
-
"""POSIX-compliant CLI tool with subcommands
|
|
42
|
+
"""POSIX-compliant CLI tool with subcommands
|
|
43
|
+
for the EKF System Management (EKFSM) library.
|
|
44
|
+
|
|
45
|
+
This tool provides a command-line interface for managing and
|
|
46
|
+
interacting with the EKF System Management library. It allows
|
|
47
|
+
users to perform various operations related to system management,
|
|
48
|
+
including reading and writing data to the system, and displaying
|
|
49
|
+
information about the system.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
verbose
|
|
54
|
+
Enable verbose output. If set, the logging level will be set to INFO.
|
|
55
|
+
debug
|
|
56
|
+
Enable debug output. If set, the logging level will be set to DEBUG.
|
|
57
|
+
sysfs
|
|
58
|
+
Use custom sysfs directory for simulation mode. If set, the
|
|
59
|
+
simulation mode will be enabled and the specified directory
|
|
60
|
+
will be used for sysfs operations.
|
|
61
|
+
config
|
|
62
|
+
Path to the configuration file. This file is required for
|
|
63
|
+
initializing the system. The path should point to a valid
|
|
64
|
+
configuration file in YAML format.
|
|
65
|
+
"""
|
|
41
66
|
if verbose:
|
|
42
67
|
logging.getLogger().setLevel(logging.INFO)
|
|
43
68
|
logger.info("Verbose output enabled")
|
ekfsm/core/__init__.py
CHANGED
|
@@ -2,3 +2,13 @@ from .slots import Slot, SlotType, Slots # noqa: F401
|
|
|
2
2
|
from .sysfs import SysFSDevice, SysFSAttribute # noqa: F401
|
|
3
3
|
from .components import HwModule # noqa: F401
|
|
4
4
|
from .probe import ProbeableDevice # noqa: F401
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Slot",
|
|
8
|
+
"SlotType",
|
|
9
|
+
"Slots",
|
|
10
|
+
"SysFSDevice",
|
|
11
|
+
"SysFSAttribute",
|
|
12
|
+
"HwModule",
|
|
13
|
+
"ProbeableDevice",
|
|
14
|
+
]
|
ekfsm/core/slots.py
CHANGED
|
@@ -142,7 +142,7 @@ class Slots(Munch):
|
|
|
142
142
|
|
|
143
143
|
Example
|
|
144
144
|
-------
|
|
145
|
-
>>> from ekfsm.
|
|
145
|
+
>>> from ekfsm..core.slots import Slot, Slots, SlotType
|
|
146
146
|
>>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
|
|
147
147
|
>>> slots = Slots((slotA.name, slotA))
|
|
148
148
|
>>> print(slots[name])
|
|
@@ -192,7 +192,7 @@ class Slots(Munch):
|
|
|
192
192
|
|
|
193
193
|
Example
|
|
194
194
|
-------
|
|
195
|
-
>>> from ekfsm.
|
|
195
|
+
>>> from ekfsm.core.slots import Slot, Slots, SlotType
|
|
196
196
|
>>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
|
|
197
197
|
>>> slots = Slots()
|
|
198
198
|
>>> slots.add(slotA) # add slotA to the collection
|
ekfsm/core/sysfs.py
CHANGED
|
@@ -22,18 +22,8 @@ class SysFSAttribute:
|
|
|
22
22
|
|
|
23
23
|
Attributes
|
|
24
24
|
----------
|
|
25
|
-
path
|
|
26
|
-
|
|
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
|
-
|
|
25
|
+
path
|
|
26
|
+
Path to the underlying file for the SysFSAttribute instance.
|
|
37
27
|
"""
|
|
38
28
|
|
|
39
29
|
def __init__(self, path: Path):
|
|
@@ -51,7 +41,7 @@ class SysFSAttribute:
|
|
|
51
41
|
|
|
52
42
|
def write(self, data: str | bytes | None, offset: int = 0) -> None:
|
|
53
43
|
if self.is_sysfs_attr() and data is not None:
|
|
54
|
-
mode =
|
|
44
|
+
mode = "r+" if isinstance(data, str) else "rb+"
|
|
55
45
|
with open(self.path, mode) as f:
|
|
56
46
|
f.seek(offset)
|
|
57
47
|
f.write(data)
|
ekfsm/devices/coretemp.py
CHANGED
|
@@ -38,12 +38,10 @@ def find_core_temp_dir(hwmon_dir) -> Path:
|
|
|
38
38
|
|
|
39
39
|
class CoreTemp(Device):
|
|
40
40
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
A HWMON device is a virtual device that is used to read hardware monitoring values from the sysfs filesystem.
|
|
41
|
+
This class provides an interface to read the CPU core temperature from the HWMON device.
|
|
44
42
|
|
|
45
43
|
Note:
|
|
46
|
-
Currently, only the
|
|
44
|
+
Currently, only the average temperature over all cores is read from the HWMON device.
|
|
47
45
|
"""
|
|
48
46
|
|
|
49
47
|
def __init__(
|
ekfsm/devices/eeprom.py
CHANGED
|
@@ -24,8 +24,6 @@ from hexdump import hexdump
|
|
|
24
24
|
|
|
25
25
|
from ekfsm.log import ekfsm_logger
|
|
26
26
|
|
|
27
|
-
__all__ = ["EEPROM", "Validatable_EEPROM", "EKF_EEPROM", "validated"]
|
|
28
|
-
|
|
29
27
|
logger = ekfsm_logger(__name__)
|
|
30
28
|
|
|
31
29
|
|
|
@@ -1069,6 +1067,23 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
|
|
|
1069
1067
|
def write_customer_area(self, data: bytes) -> None:
|
|
1070
1068
|
"""
|
|
1071
1069
|
Write data to CCU EEPROM customer area.
|
|
1070
|
+
|
|
1071
|
+
Parameters
|
|
1072
|
+
----------
|
|
1073
|
+
data
|
|
1074
|
+
The data to write to the customer area of the CCU EEPROM.
|
|
1075
|
+
|
|
1076
|
+
Raises
|
|
1077
|
+
------
|
|
1078
|
+
ValueError
|
|
1079
|
+
If the data exceeds the customer area length.
|
|
1080
|
+
|
|
1081
|
+
Example
|
|
1082
|
+
-------
|
|
1083
|
+
>>> eeprom = EKF_CCU_EEPROM()
|
|
1084
|
+
>>> eeprom.write_customer_area(b"Hello, World!")
|
|
1085
|
+
>>> eeprom.customer_area()
|
|
1086
|
+
b'Hello, World!'
|
|
1072
1087
|
"""
|
|
1073
1088
|
if len(data) > self._customer_area_length:
|
|
1074
1089
|
raise ValueError("Data exceeds customer area length")
|
ekfsm/devices/ekf_ccu_uc.py
CHANGED
|
@@ -54,7 +54,10 @@ class EKFCcuUc(Device):
|
|
|
54
54
|
def temperature(self) -> float:
|
|
55
55
|
"""
|
|
56
56
|
Get the temperature from the CCU thermal/humidity sensor.
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
Note
|
|
59
|
+
----
|
|
60
|
+
The CCU reads the temperature once per second.
|
|
58
61
|
|
|
59
62
|
Returns
|
|
60
63
|
-------
|
|
@@ -128,9 +131,16 @@ class EKFCcuUc(Device):
|
|
|
128
131
|
|
|
129
132
|
Returns
|
|
130
133
|
-------
|
|
131
|
-
|
|
132
|
-
The desired speed
|
|
133
|
-
|
|
134
|
+
desired: float
|
|
135
|
+
The desired speed.
|
|
136
|
+
actual: float
|
|
137
|
+
The actual speed.
|
|
138
|
+
diag: int
|
|
139
|
+
The diagnostic value.
|
|
140
|
+
|
|
141
|
+
Note
|
|
142
|
+
----
|
|
143
|
+
The diagnostic value is a bitfield with the following meaning:
|
|
134
144
|
|
|
135
145
|
- bit 0: 0 = fan status is invalid, 1 = fan status is valid
|
|
136
146
|
- bit 1: 0 = no error detected, 1 = fan is stuck
|
|
@@ -184,8 +194,10 @@ class EKFCcuUc(Device):
|
|
|
184
194
|
|
|
185
195
|
Returns
|
|
186
196
|
-------
|
|
187
|
-
|
|
188
|
-
The IMU sample
|
|
197
|
+
imu_data: ImuSample | None
|
|
198
|
+
The IMU sample, or None if no sample is available.
|
|
199
|
+
more_samples: bool
|
|
200
|
+
True if more samples are available in the FIFO, False otherwise.
|
|
189
201
|
"""
|
|
190
202
|
more_samples = False
|
|
191
203
|
_data = self._smbus.read_block_data(
|
|
@@ -257,8 +269,10 @@ class EKFCcuUc(Device):
|
|
|
257
269
|
|
|
258
270
|
Returns
|
|
259
271
|
-------
|
|
260
|
-
|
|
261
|
-
The firmware title
|
|
272
|
+
title: str
|
|
273
|
+
The firmware title.
|
|
274
|
+
version: str
|
|
275
|
+
The firmware version.
|
|
262
276
|
"""
|
|
263
277
|
title = bytes(
|
|
264
278
|
self._smbus.read_block_data(
|
|
@@ -293,6 +307,13 @@ class EKFCcuUc(Device):
|
|
|
293
307
|
progress_callback
|
|
294
308
|
A callback function that is called with the current progress in bytes.
|
|
295
309
|
|
|
310
|
+
Example
|
|
311
|
+
-------
|
|
312
|
+
>>> from ekfsm.devices import EkfCcuUc
|
|
313
|
+
>>> ccu = EkfCcuUc("ccu")
|
|
314
|
+
>>> firmware = open("fw-ccu-1.0.0.bin", "rb").read()
|
|
315
|
+
>>> # Load firmware with progress callback
|
|
316
|
+
>>> ccu.load_firmware(firmware, progress_callback=lambda x: print(f"Progress: {x} bytes"))
|
|
296
317
|
"""
|
|
297
318
|
with Locker(self.name + "-load_firmware").lock():
|
|
298
319
|
offset = 0
|
|
@@ -412,6 +433,8 @@ class EKFCcuUc(Device):
|
|
|
412
433
|
This would load a parameterset with just one parameter, the default fan speed. All other parameters will
|
|
413
434
|
be set to their default values.
|
|
414
435
|
|
|
436
|
+
Important
|
|
437
|
+
---------
|
|
415
438
|
In order to apply the parameterset, the CCU must be restarted.
|
|
416
439
|
|
|
417
440
|
Parameters
|
|
@@ -419,6 +442,14 @@ class EKFCcuUc(Device):
|
|
|
419
442
|
_cfg
|
|
420
443
|
The parameterset in JSON format.
|
|
421
444
|
|
|
445
|
+
Example
|
|
446
|
+
-------
|
|
447
|
+
>>> from ekfsm.devices import EkfCcuUc
|
|
448
|
+
>>> ccu = EkfCcuUc("ccu")
|
|
449
|
+
>>> # Load parameterset
|
|
450
|
+
>>> ccu.load_parameterset('{"version": "1.0.0", "parameters": {"fan-defrpm": "6000"}}')
|
|
451
|
+
>>> # Restart CCU to apply parameterset
|
|
452
|
+
>>> ccu.restart()
|
|
422
453
|
"""
|
|
423
454
|
with Locker(self.name + "-parameterset").lock():
|
|
424
455
|
cfg = _cfg.encode("utf-8")
|
ekfsm/devices/generic.py
CHANGED
|
@@ -77,22 +77,77 @@ class Device(SysTree):
|
|
|
77
77
|
return provides
|
|
78
78
|
|
|
79
79
|
def read_sysfs_attr_bytes(self, attr: str) -> bytes | None:
|
|
80
|
+
"""
|
|
81
|
+
Read a sysfs attribute as bytes.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
attr
|
|
86
|
+
The sysfs attribute to read.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
content: bytes
|
|
91
|
+
The contents of the sysfs attribute as bytes.
|
|
92
|
+
None:
|
|
93
|
+
If the sysfs device is not set or the attribute does not exist.
|
|
94
|
+
"""
|
|
80
95
|
if self.sysfs_device and len(attr) != 0:
|
|
81
96
|
return self.sysfs_device.read_attr_bytes(attr)
|
|
82
97
|
return None
|
|
83
98
|
|
|
84
99
|
def read_sysfs_attr_utf8(self, attr: str) -> str | None:
|
|
100
|
+
"""
|
|
101
|
+
Read a sysfs attribute as UTF-8 string.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
attr
|
|
106
|
+
The sysfs attribute to read.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
content: str
|
|
111
|
+
The contents of the sysfs attribute as UTF-8 string.
|
|
112
|
+
None:
|
|
113
|
+
If the sysfs device is not set or the attribute does not exist.
|
|
114
|
+
"""
|
|
85
115
|
if self.sysfs_device and len(attr) != 0:
|
|
86
116
|
return self.sysfs_device.read_attr_utf8(attr)
|
|
87
117
|
return None
|
|
88
118
|
|
|
89
119
|
def write_sysfs_attr(self, attr: str, data: str | bytes) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Write data to a sysfs attribute.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
attr
|
|
126
|
+
The sysfs attribute to write to.
|
|
127
|
+
data
|
|
128
|
+
The data to write to the sysfs attribute.
|
|
129
|
+
"""
|
|
90
130
|
if self.sysfs_device and len(attr) != 0:
|
|
91
131
|
return self.sysfs_device.write_attr(attr, data)
|
|
92
132
|
return None
|
|
93
133
|
|
|
94
134
|
@property
|
|
95
135
|
def hw_module(self) -> "HwModule":
|
|
136
|
+
"""
|
|
137
|
+
Get or set the HwModule instance that this device belongs to.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
hw_module: optional
|
|
142
|
+
The HwModule instance to set.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
HwModule
|
|
147
|
+
The HwModule instance that this device belongs to.
|
|
148
|
+
None
|
|
149
|
+
If used as a setter.
|
|
150
|
+
"""
|
|
96
151
|
from ekfsm.core.components import HwModule
|
|
97
152
|
|
|
98
153
|
if isinstance(self._hw_module, HwModule):
|
|
@@ -13,6 +13,14 @@ class IIOThermalHumidity(Device):
|
|
|
13
13
|
"""
|
|
14
14
|
Device for IIO thermal and/or humidity sensors.
|
|
15
15
|
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
name
|
|
19
|
+
The name of the device.
|
|
20
|
+
parent
|
|
21
|
+
The parent device of the IIOThermalHumidity device. If None, no parent is created.
|
|
22
|
+
children
|
|
23
|
+
The children of the IIOThermalHumidity device. If None, no children are created.
|
|
16
24
|
"""
|
|
17
25
|
|
|
18
26
|
def __init__(
|
|
@@ -24,7 +32,7 @@ class IIOThermalHumidity(Device):
|
|
|
24
32
|
**kwargs,
|
|
25
33
|
):
|
|
26
34
|
self.logger = ekfsm_logger("IIOThermalHumidity:" + name)
|
|
27
|
-
super().__init__(name, parent, children, **kwargs)
|
|
35
|
+
super().__init__(name, parent, children, *args, **kwargs)
|
|
28
36
|
self.addr = self.get_i2c_chip_addr()
|
|
29
37
|
self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
|
|
30
38
|
|
ekfsm/devices/imu.py
CHANGED
|
@@ -2,10 +2,14 @@ class ImuSample:
|
|
|
2
2
|
"""
|
|
3
3
|
Class to store IMU data sample
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Parameters
|
|
6
|
+
----------
|
|
7
|
+
accel
|
|
8
|
+
Accelerometer data in m/s^2, [x, y, z]
|
|
9
|
+
gyro
|
|
10
|
+
Gyroscope data in degrees/s, [x, y, z]
|
|
11
|
+
lost
|
|
12
|
+
True if data was lost before that sample
|
|
9
13
|
"""
|
|
10
14
|
|
|
11
15
|
def __init__(self, accel: list[float], gyro: list[float], lost: bool):
|
ekfsm/devices/mux.py
CHANGED
|
@@ -5,6 +5,23 @@ from .generic import Device
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class MuxChannel(Device):
|
|
8
|
+
"""
|
|
9
|
+
A MuxChannel is a device that represents a channel on an I2C multiplexer.
|
|
10
|
+
It is a child of the I2CMux device.
|
|
11
|
+
The MuxChannel device is used to access the I2C bus on the channel.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
name
|
|
16
|
+
The name of the device.
|
|
17
|
+
channel_id
|
|
18
|
+
The channel ID of the device.
|
|
19
|
+
parent
|
|
20
|
+
The parent device of the MuxChannel.
|
|
21
|
+
children
|
|
22
|
+
The children of the MuxChannel device. If None, no children are created.
|
|
23
|
+
"""
|
|
24
|
+
|
|
8
25
|
def __init__(
|
|
9
26
|
self,
|
|
10
27
|
name: str,
|
|
@@ -25,6 +42,19 @@ class MuxChannel(Device):
|
|
|
25
42
|
|
|
26
43
|
|
|
27
44
|
class I2CMux(Device):
|
|
45
|
+
"""
|
|
46
|
+
This class represents an I2C multiplexer device.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
name
|
|
51
|
+
The name of the device.
|
|
52
|
+
parent
|
|
53
|
+
The parent device of the I2CMux device. If None, no parent is created.
|
|
54
|
+
children
|
|
55
|
+
The children of the I2CMux device. If None, no children are created.
|
|
56
|
+
"""
|
|
57
|
+
|
|
28
58
|
def __init__(
|
|
29
59
|
self,
|
|
30
60
|
name: str,
|
ekfsm/devices/pmbus.py
CHANGED
|
@@ -76,9 +76,27 @@ class PsuStatus(IntFlag):
|
|
|
76
76
|
"""
|
|
77
77
|
Represents the status of a PSU according to STATUS_BYTE register.
|
|
78
78
|
|
|
79
|
-
See
|
|
79
|
+
See also
|
|
80
80
|
--------
|
|
81
|
+
External Documentation:
|
|
81
82
|
`PMBus Power System Management Protocol Specification - Part II - Revision 1.4, Fig. 60 <https://pmbus.org/>`_
|
|
83
|
+
|
|
84
|
+
Example
|
|
85
|
+
-------
|
|
86
|
+
>>> from ekfsm.devices.pmbus import PsuStatus
|
|
87
|
+
>>> status = PsuStatus(0x1F)
|
|
88
|
+
>>> status
|
|
89
|
+
<PsuStatus.OUTPUT_OVERCURRENT|INPUT_UNDERVOLTAGE|TEMP_ANORMALY|COMMUNICATION_ERROR|ERROR: 31>
|
|
90
|
+
>>> PsuStatus.OUTPUT_OVERCURRENT in status
|
|
91
|
+
True
|
|
92
|
+
>>> # OK is always present
|
|
93
|
+
>>> PsuStatus.OK in status
|
|
94
|
+
True
|
|
95
|
+
>>> # Instead, check if status is OK
|
|
96
|
+
>>> status == PsuStatus(0x00)
|
|
97
|
+
False
|
|
98
|
+
>>> PsuStatus.OUTPUT_OVERCURRENT in status
|
|
99
|
+
False
|
|
82
100
|
"""
|
|
83
101
|
|
|
84
102
|
OUTPUT_OVERVOLTAGE = 0x20
|
|
@@ -91,6 +109,9 @@ class PsuStatus(IntFlag):
|
|
|
91
109
|
|
|
92
110
|
|
|
93
111
|
class PmBus(Device, ProbeableDevice):
|
|
112
|
+
"""
|
|
113
|
+
This class represents a PMBus device (e.g. a PSU).
|
|
114
|
+
"""
|
|
94
115
|
|
|
95
116
|
def __init__(
|
|
96
117
|
self,
|
|
@@ -100,7 +121,7 @@ class PmBus(Device, ProbeableDevice):
|
|
|
100
121
|
*args,
|
|
101
122
|
**kwargs,
|
|
102
123
|
):
|
|
103
|
-
super().__init__(name, parent, children, **kwargs)
|
|
124
|
+
super().__init__(name, parent, children, *args, **kwargs)
|
|
104
125
|
self.addr = self.get_i2c_chip_addr()
|
|
105
126
|
self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
|
|
106
127
|
|
ekfsm/lock.py
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
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
19
|
from pathlib import Path
|
|
4
20
|
from contextlib import contextmanager
|
|
5
21
|
|
|
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
|
-
|
|
21
22
|
USE_LOCK = True
|
|
22
23
|
LOCKFILE_ROOT = "/var/lock/ekfsm"
|
|
23
24
|
ALL_LOCKERS: list["Locker"] = [] # List of all locker instances
|
|
@@ -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/system.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
from typing import Tuple, Any
|
|
1
|
+
from typing import Tuple, Any
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from munch import Munch, munchify
|
|
4
4
|
|
|
5
5
|
from ekfsm.core.components import SysTree
|
|
6
6
|
import yaml
|
|
7
7
|
|
|
8
|
+
from ekfsm.utils import all_board_cfg_files, find_board_config
|
|
9
|
+
|
|
8
10
|
from .core.slots import Slot, SlotType
|
|
9
11
|
|
|
10
12
|
from .config import load_config
|
|
@@ -14,39 +16,6 @@ from .exceptions import ConfigError
|
|
|
14
16
|
from .log import ekfsm_logger
|
|
15
17
|
|
|
16
18
|
|
|
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
19
|
class System(SysTree):
|
|
51
20
|
"""
|
|
52
21
|
A System represents a CPCI system.
|
|
@@ -57,15 +26,6 @@ class System(SysTree):
|
|
|
57
26
|
|
|
58
27
|
Visual representation of the system is shown as trees of HW Modules and attached devices.
|
|
59
28
|
|
|
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
29
|
Attributes
|
|
70
30
|
----------
|
|
71
31
|
name
|
|
@@ -81,6 +41,18 @@ class System(SysTree):
|
|
|
81
41
|
config
|
|
82
42
|
The system configuration.
|
|
83
43
|
|
|
44
|
+
|
|
45
|
+
Accessing boards
|
|
46
|
+
----------------
|
|
47
|
+
|
|
48
|
+
Iterating over the system will iterate over all boards in the system.
|
|
49
|
+
|
|
50
|
+
<board_name>
|
|
51
|
+
The board object can be accessed by its name.
|
|
52
|
+
<slot_number>
|
|
53
|
+
The board object can be accessed by its slot number.
|
|
54
|
+
|
|
55
|
+
|
|
84
56
|
Example
|
|
85
57
|
-------
|
|
86
58
|
>>> from ekfsm.system import System
|
|
@@ -101,6 +73,9 @@ class System(SysTree):
|
|
|
101
73
|
----------
|
|
102
74
|
config
|
|
103
75
|
Path to the config that specifies the system and how the slots are filled.
|
|
76
|
+
abort
|
|
77
|
+
If True, abort the program if a board cannot be created. If False, leave the slot empty.
|
|
78
|
+
Default is False.
|
|
104
79
|
"""
|
|
105
80
|
self.config_path = config
|
|
106
81
|
self.config = load_config(str(self.config_path))
|
|
@@ -171,7 +146,7 @@ class System(SysTree):
|
|
|
171
146
|
if slot.attributes.is_master:
|
|
172
147
|
master, _ = self.create_hwmodule(slot, i, None)
|
|
173
148
|
if master is not None:
|
|
174
|
-
master.master = master
|
|
149
|
+
master.master = master # ???
|
|
175
150
|
return master, i
|
|
176
151
|
else:
|
|
177
152
|
return None, -1
|
|
@@ -183,6 +158,15 @@ class System(SysTree):
|
|
|
183
158
|
"""
|
|
184
159
|
Create HwModule object for the slot.
|
|
185
160
|
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
slot_entry
|
|
164
|
+
The slot entry config (usually part of the system configuration).
|
|
165
|
+
slot_number
|
|
166
|
+
The slot number of the slot.
|
|
167
|
+
master
|
|
168
|
+
The master board of the system.
|
|
169
|
+
|
|
186
170
|
Returns
|
|
187
171
|
-------
|
|
188
172
|
HwModule and Slot. HwModule is None if it cannot be created.
|
|
@@ -310,6 +294,20 @@ class System(SysTree):
|
|
|
310
294
|
return hwmod
|
|
311
295
|
|
|
312
296
|
def get_module_in_slot(self, idx: int) -> HwModule | None:
|
|
297
|
+
"""
|
|
298
|
+
Get the hwmodule in the given slot.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
idx
|
|
303
|
+
The slot index.
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
HwModule
|
|
307
|
+
The hwmodule in the given slot.
|
|
308
|
+
None
|
|
309
|
+
If no hwmodule is present in the given slot.
|
|
310
|
+
"""
|
|
313
311
|
return next(
|
|
314
312
|
(
|
|
315
313
|
v.hwmodule
|
|
@@ -320,6 +318,21 @@ class System(SysTree):
|
|
|
320
318
|
)
|
|
321
319
|
|
|
322
320
|
def get_module_by_name(self, name: str) -> HwModule | None:
|
|
321
|
+
"""
|
|
322
|
+
Get the hwmodule by its name.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
name
|
|
327
|
+
The name of the hwmodule.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
HwModule
|
|
332
|
+
The hwmodule with the given name.
|
|
333
|
+
None
|
|
334
|
+
If no hwmodule is present with the given name.
|
|
335
|
+
"""
|
|
323
336
|
return next(
|
|
324
337
|
(b for b in self.boards if getattr(b, "instance_name", None) == name),
|
|
325
338
|
None,
|
ekfsm/utils.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
_CFG_DIR = Path(__file__).parent / "boards"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_board_config(module_type: str) -> Path | None:
|
|
10
|
+
"""
|
|
11
|
+
Find a matching board config in `boards/oem/` given the module type specified in
|
|
12
|
+
the system configuration file.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
module_type
|
|
17
|
+
Board type specified in the system configuration for a slot.
|
|
18
|
+
It must consist of an OEM and the board type, separated by whitespace. Neither
|
|
19
|
+
part may contain any other whitespace.
|
|
20
|
+
"""
|
|
21
|
+
oem, board = module_type.split(maxsplit=1)
|
|
22
|
+
if (
|
|
23
|
+
path := _CFG_DIR / "oem" / oem.strip().lower() / f"{board.strip().lower()}.yaml"
|
|
24
|
+
).exists():
|
|
25
|
+
return path
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def all_board_cfg_files() -> Generator[Path, None, None]:
|
|
30
|
+
"""
|
|
31
|
+
Generator that recursively yields all *.yaml files in the config directory.
|
|
32
|
+
|
|
33
|
+
Yields
|
|
34
|
+
------
|
|
35
|
+
item: Path
|
|
36
|
+
Path to a config file.
|
|
37
|
+
"""
|
|
38
|
+
path = Path(_CFG_DIR)
|
|
39
|
+
for item in path.rglob("*.yaml"):
|
|
40
|
+
if item.is_file():
|
|
41
|
+
yield item
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ekfsm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0a7
|
|
4
4
|
Summary: The EKF System Management Library (ekfsm) is a sensor monitoring suite for Compact PCI Serial devices.
|
|
5
5
|
Author-email: Klaus Popp <klaus.popp@ci4rail.com>, Jan Jansen <jan@ekf.de>, Felix Päßler <fp@ekf.de>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
ekfsm/__init__.py,sha256=
|
|
2
|
-
ekfsm/cli.py,sha256=
|
|
1
|
+
ekfsm/__init__.py,sha256=R12o_igFRTqlHUD9jyrOIPq2nBpTUWuvIPgCytBlVzk,142
|
|
2
|
+
ekfsm/cli.py,sha256=JmTO1TyxrTlGTkQo8ndSR7nj0grSGuQdh9ugXx1IeQE,3919
|
|
3
3
|
ekfsm/config.py,sha256=FTk47f3qA05Zv6Cy_L_5XlGmmbC9z_kPdQJKpVoHkWs,924
|
|
4
4
|
ekfsm/exceptions.py,sha256=25P677GxfBTdBWHRngPce0bmhcLg7MFeTtuAvguxd90,1310
|
|
5
|
-
ekfsm/lock.py,sha256=
|
|
5
|
+
ekfsm/lock.py,sha256=XWLu2yhgIs16oSyc_4jdVUobbxyYlYOjZi1EFgHsXVU,3236
|
|
6
6
|
ekfsm/log.py,sha256=_GC8Y7a4fFV4_DNicbwQ-5rRzNQU6WSotXd2etXSrZk,866
|
|
7
7
|
ekfsm/py.typed,sha256=1gNRtmxvYcVqDDEyAzBLnD8dAOweUfYxW2ZPdJzN1fg,102
|
|
8
8
|
ekfsm/simctrl.py,sha256=NkmjvqOym9Wruh0f14Od6gHfEgPMAkxUzMQ-nvzcM3Q,6681
|
|
9
|
-
ekfsm/system.py,sha256=
|
|
9
|
+
ekfsm/system.py,sha256=kBukgVyPJYTh_ilauEEANG2KKqHWOrQBYLhVdG679HM,12028
|
|
10
|
+
ekfsm/utils.py,sha256=IKQrGr8UzUJpqj365uQqIWDRTAgpVC0OUmqIKzt_9T0,1081
|
|
10
11
|
ekfsm/boards/oem/ekf/ccu.yaml,sha256=qgr7YZO0kEddD9K6tv6222NyozkRNuF7NFw6hyX0XgE,2094
|
|
11
12
|
ekfsm/boards/oem/ekf/sc5-festival.yaml,sha256=_0kS5GegfyOt5CTJc9kY6HJbr9yZo4i18sVo6F4KE9c,772
|
|
12
13
|
ekfsm/boards/oem/ekf/sc9-toccata.yaml,sha256=btLgQMSsW0tRipnUYUkVQSIsjzxfKq0NXaQ1fMMyBRI,771
|
|
@@ -18,28 +19,28 @@ ekfsm/boards/oem/ekf/sq3-quartet.yaml,sha256=pBB7Tv0IWLkFUYBs3tFvZriA-uqPuPIgzja
|
|
|
18
19
|
ekfsm/boards/oem/ekf/srf-fan.yaml,sha256=Mcu1Q8B1Ih10hoc_hbkGlppBmbOFcufsVUR42iW4Rwc,1368
|
|
19
20
|
ekfsm/boards/oem/ekf/sur-uart.yaml,sha256=VaNP2BSlNTi1lDco16Ma9smPEAMaVKvx-ZNDcm3QptM,1890
|
|
20
21
|
ekfsm/boards/oem/hitron/hdrc-300s.yaml,sha256=E567QsRPvyAEXpXOeF1OvX2AoQK8BTxI0C7QVBFlT_k,548
|
|
21
|
-
ekfsm/core/__init__.py,sha256=
|
|
22
|
+
ekfsm/core/__init__.py,sha256=NVgbM5fBDbPhP5HtzMTKfloe9u5xutm47DM9Vt_kbRU,348
|
|
22
23
|
ekfsm/core/components.py,sha256=dh71jM_OJ8QhTzou-5L6RaF3abXgE470KJfxQRqq1CY,3836
|
|
23
24
|
ekfsm/core/probe.py,sha256=DgJvkvMjVk09n0Rzn13ybRvidrmFn_D2PD56XS-KgxU,262
|
|
24
|
-
ekfsm/core/slots.py,sha256=
|
|
25
|
-
ekfsm/core/sysfs.py,sha256=
|
|
25
|
+
ekfsm/core/slots.py,sha256=pKBkPUxv-Lz9GxCrlnZu4gR7f59tQ0GW1MBs7nblqaI,6582
|
|
26
|
+
ekfsm/core/sysfs.py,sha256=CVGxUEdhmJjRw8ZfnjL3hUwm6oWsp3ZKPG2qGUFYIiY,2283
|
|
26
27
|
ekfsm/core/utils.py,sha256=EoPOuRmLlvHvGneDcKjP7Qbjfsc4XlMbLeaSFRObt_E,3145
|
|
27
28
|
ekfsm/devices/__init__.py,sha256=UtFLCtAdpDJ3OaY7fedk13bx90NMsfxhyVAHV13t2U4,936
|
|
28
|
-
ekfsm/devices/coretemp.py,sha256=
|
|
29
|
-
ekfsm/devices/eeprom.py,sha256=
|
|
30
|
-
ekfsm/devices/ekf_ccu_uc.py,sha256=
|
|
29
|
+
ekfsm/devices/coretemp.py,sha256=oco909zpXw9BqkLWV3dJma1_Q23M2EUGsgdcs4QtFjU,1966
|
|
30
|
+
ekfsm/devices/eeprom.py,sha256=4UjydeDxZT7w2zXkhZu7GUuHYu8sAidT8xuVtkt4OJw,32389
|
|
31
|
+
ekfsm/devices/ekf_ccu_uc.py,sha256=bVdxaiXtrJC2-tSRv1rGUzm4wOxL20QfTL4NIKdRJE8,16092
|
|
31
32
|
ekfsm/devices/ekf_sur_led.py,sha256=dY2EIqUx4-LV7ipCEIg8DfXWlGxpspRmEtEhVfHY6LI,1871
|
|
32
|
-
ekfsm/devices/generic.py,sha256=
|
|
33
|
+
ekfsm/devices/generic.py,sha256=9FDj8hhiCQAFmA5tt3lv1JPIZyAb3XCCriDIoR61kcE,10821
|
|
33
34
|
ekfsm/devices/gpio.py,sha256=hlJ3yJ8jSGUIkPpjymani0EcxYJX2iEybGbnV8N0O5E,9552
|
|
34
35
|
ekfsm/devices/iio.py,sha256=gMOJGdg2PvFbAvlBpLfDTIc_-9i1Z3OLpuabTke6N3I,1480
|
|
35
|
-
ekfsm/devices/iio_thermal_humidity.py,sha256=
|
|
36
|
-
ekfsm/devices/imu.py,sha256=
|
|
37
|
-
ekfsm/devices/mux.py,sha256=
|
|
38
|
-
ekfsm/devices/pmbus.py,sha256=
|
|
36
|
+
ekfsm/devices/iio_thermal_humidity.py,sha256=gALZ1B4f1ePGAEL7Q5VxbubgC3CgtC9RBM9F4YWuH4E,1478
|
|
37
|
+
ekfsm/devices/imu.py,sha256=GwJrnC-WztpE5oGnkIp-laFkSp3_gbSp9YpJ8jBsUOs,423
|
|
38
|
+
ekfsm/devices/mux.py,sha256=C2h5w8eXrL4RXXebCSnNY-VShTgeqmYDMvGSunapBqk,1830
|
|
39
|
+
ekfsm/devices/pmbus.py,sha256=5nIvuUkY20YwXTIpd3lebdu2dU-he_IJ7wLz1EUTrzc,7078
|
|
39
40
|
ekfsm/devices/smbios.py,sha256=gDDYtfCd7njzqxJBKJxIR2BMCK6-uZxUVxC9y0hnz4g,1009
|
|
40
41
|
ekfsm/devices/smbus.py,sha256=zD9b6PehipMw-xoaOMC-oorDVxBoo30hc98gyFxFLs0,500
|
|
41
42
|
ekfsm/devices/utils.py,sha256=4-0Kmvy4ou8R71afNj6RqdBTjLW4SMWPHqVrzB2RUZw,397
|
|
42
|
-
ekfsm-1.
|
|
43
|
-
ekfsm-1.
|
|
44
|
-
ekfsm-1.
|
|
45
|
-
ekfsm-1.
|
|
43
|
+
ekfsm-1.2.0a7.dist-info/METADATA,sha256=VUyieRTVS_l9ZWkP1ctAYgFAK5s9tv3V_mUOTBXkePM,6543
|
|
44
|
+
ekfsm-1.2.0a7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
45
|
+
ekfsm-1.2.0a7.dist-info/entry_points.txt,sha256=WhUR4FzuxPoGrbTOKLsNJO7NAnk2qd4T30fqzN1yLw8,45
|
|
46
|
+
ekfsm-1.2.0a7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|