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.

Files changed (46) hide show
  1. ekfsm/__init__.py +3 -14
  2. ekfsm/boards/oem/ekf/shu-shuttle.yaml +43 -0
  3. ekfsm/boards/oem/ekf/sq3-quartet.yaml +51 -37
  4. ekfsm/boards/oem/ekf/z1010.yaml +102 -0
  5. ekfsm/boards/oem/hitron/hdrc-300s.yaml +1 -1
  6. ekfsm/cli.py +32 -9
  7. ekfsm/config.py +14 -6
  8. ekfsm/core/__init__.py +13 -3
  9. ekfsm/core/components.py +7 -8
  10. ekfsm/core/connections.py +19 -0
  11. ekfsm/core/slots.py +6 -8
  12. ekfsm/core/sysfs.py +215 -25
  13. ekfsm/core/utils.py +128 -64
  14. ekfsm/devices/__init__.py +27 -7
  15. ekfsm/devices/buttons.py +251 -0
  16. ekfsm/devices/colorLed.py +110 -0
  17. ekfsm/devices/coretemp.py +35 -13
  18. ekfsm/devices/eeprom.py +73 -45
  19. ekfsm/devices/ekf_ccu_uc.py +76 -54
  20. ekfsm/devices/ekf_sur_led.py +6 -2
  21. ekfsm/devices/generic.py +200 -59
  22. ekfsm/devices/gpio.py +37 -27
  23. ekfsm/devices/iio.py +15 -31
  24. ekfsm/devices/iio_thermal_humidity.py +20 -13
  25. ekfsm/devices/imu.py +8 -4
  26. ekfsm/devices/io4edge.py +185 -0
  27. ekfsm/devices/ledArray.py +54 -0
  28. ekfsm/devices/mux.py +46 -8
  29. ekfsm/devices/pixelDisplay.py +141 -0
  30. ekfsm/devices/pmbus.py +74 -101
  31. ekfsm/devices/smbios.py +28 -8
  32. ekfsm/devices/smbus.py +1 -1
  33. ekfsm/devices/thermal_humidity.py +80 -0
  34. ekfsm/devices/toggles.py +90 -0
  35. ekfsm/devices/utils.py +52 -8
  36. ekfsm/devices/watchdog.py +79 -0
  37. ekfsm/exceptions.py +28 -7
  38. ekfsm/lock.py +48 -21
  39. ekfsm/simctrl.py +37 -83
  40. ekfsm/system.py +89 -73
  41. ekfsm/utils.py +44 -0
  42. {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/METADATA +12 -6
  43. ekfsm-1.5.0.dist-info/RECORD +57 -0
  44. ekfsm-0.13.0a183.dist-info/RECORD +0 -45
  45. {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/WHEEL +0 -0
  46. {ekfsm-0.13.0a183.dist-info → ekfsm-1.5.0.dist-info}/entry_points.txt +0 -0
ekfsm/devices/imu.py CHANGED
@@ -2,10 +2,14 @@ class ImuSample:
2
2
  """
3
3
  Class to store IMU data sample
4
4
 
5
- * accel: list[float] - Accelerometer data in m/s^2, [x, y, z]
6
- * gyro: list[float] - Gyroscope data in degrees/s, [x, y, z]
7
- * lost: bool - True if data was lost before that sample
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):
@@ -0,0 +1,185 @@
1
+ from typing import Callable, Optional
2
+ from ekfsm.core.components import HWModule
3
+ from ekfsm.devices.generic import Device
4
+ from ekfsm.log import ekfsm_logger
5
+ import io4edge_client.core.coreclient as CClient
6
+ from io4edge_client.binaryiotypeb import Client
7
+
8
+ from re import sub
9
+
10
+ logger = ekfsm_logger(__name__)
11
+
12
+
13
+ class IO4Edge(Device):
14
+ """
15
+ Device class for handling IO4Edge devices.
16
+
17
+ See https://docs.ci4rail.com/user-docs/io4edge/ for more information.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ name: str,
23
+ parent: HWModule | None = None,
24
+ children: list[Device] | None = None,
25
+ abort: bool = False,
26
+ *args,
27
+ **kwargs,
28
+ ):
29
+ logger.debug("Initializing IO4Edge device '%s'", name)
30
+
31
+ super().__init__(name, parent, children, abort, *args, **kwargs)
32
+
33
+ attr = self.hw_module.slot.attributes
34
+ if (
35
+ attr is None
36
+ or not hasattr(attr, "slot_coding")
37
+ or getattr(attr, "slot_coding") is None
38
+ ):
39
+ logger.error(
40
+ "Slot attributes for %s are not set or do not contain 'slot_coding'",
41
+ self.hw_module.slot.name,
42
+ )
43
+ raise ValueError(
44
+ f"Slot attributes for {self.hw_module.slot.name} are not set or do not contain 'slot_coding'."
45
+ )
46
+ else:
47
+ geoaddr = int(attr.slot_coding)
48
+ self._geoaddr = geoaddr
49
+ logger.debug("IO4Edge '%s' geo address: %s", name, geoaddr)
50
+
51
+ _, module_name = sub(r"-.*$", "", self.hw_module.board_type).split(maxsplit=1)
52
+ self._module_name = module_name
53
+ logger.debug("IO4Edge '%s' module name: %s", name, module_name)
54
+
55
+ try:
56
+ self.client = CClient.new_core_client(self.deviceId)
57
+ logger.info(
58
+ "IO4Edge '%s' initialized with device ID: %s", name, self.deviceId
59
+ )
60
+ except Exception as e:
61
+ logger.error("Failed to create IO4Edge core client for '%s': %s", name, e)
62
+ raise
63
+
64
+ @property
65
+ def deviceId(self) -> str:
66
+ """
67
+ Returns the device ID for the IO4Edge device.
68
+ The device ID is a combination of the module name and the geo address.
69
+ """
70
+ return f"{self._module_name}-geo_addr{self._geoaddr:02d}"
71
+
72
+ def identify_firmware(self) -> tuple[str, str]:
73
+ """
74
+ Identify the firmware on the IO4Edge device.
75
+
76
+ Returns
77
+ -------
78
+ A tuple containing the firmware title and version.
79
+ """
80
+ response = self.client.identify_firmware()
81
+ return (
82
+ response.title,
83
+ response.version,
84
+ )
85
+
86
+ def load_firmware(
87
+ self, cfg: bytes, progress_callback: Optional[Callable[[float], None]] = None
88
+ ) -> None:
89
+ """
90
+ Load firmware onto the IO4Edge device.
91
+
92
+ cfg
93
+ Firmware configuration bytes.
94
+ progress_callback
95
+ Optional callback for progress updates.
96
+ """
97
+ self.client.load_firmware(cfg, progress_callback)
98
+
99
+ def restart(self) -> None:
100
+ """
101
+ Restart the IO4Edge device.
102
+
103
+ Important
104
+ ---------
105
+ This will disconnect the client from the device.
106
+ """
107
+ self.client.restart()
108
+
109
+ def load_parameter(self, name: str, value: str) -> None:
110
+ """
111
+ Set a parameter onto the IO4Edge device.
112
+
113
+ cfg
114
+ The name of the parameter to load.
115
+ value
116
+ The value to set for the parameter.
117
+ """
118
+ self.client.set_persistent_parameter(name, value)
119
+
120
+ def get_parameter(self, name: str) -> str:
121
+ """
122
+ Get a parameter value from the IO4Edge device.
123
+
124
+ Returns
125
+ The value of the requested parameter.
126
+ """
127
+ return self.client.get_persistent_parameter(name)
128
+
129
+ def __repr__(self):
130
+ return f"{self.name}; DeviceId: {self.deviceId}"
131
+
132
+
133
+ class GPIOArray(Device):
134
+ """
135
+ Device class for handling an io4edge GPIO array.
136
+ """
137
+
138
+ def __init__(
139
+ self,
140
+ name: str,
141
+ parent: IO4Edge,
142
+ children: list[Device] | None = None,
143
+ abort: bool = False,
144
+ service_suffix: str | None = None,
145
+ keepaliveInterval: int = 10000,
146
+ *args,
147
+ **kwargs,
148
+ ):
149
+ logger.debug(
150
+ "Initializing GPIOArray '%s' with parent device %s", name, parent.deviceId
151
+ )
152
+
153
+ super().__init__(name, parent, children, abort, *args, **kwargs)
154
+
155
+ self.name = name
156
+ if service_suffix is not None:
157
+ self.service_suffix = service_suffix
158
+ logger.debug("Using custom service suffix: %s", service_suffix)
159
+ else:
160
+ self.service_suffix = name
161
+ logger.debug("Using default service suffix: %s", name)
162
+
163
+ self.deviceId = parent.deviceId
164
+ self.service_addr = f"{self.deviceId}-{self.service_suffix}"
165
+ self.timeout = int(keepaliveInterval / 1000 + 5)
166
+
167
+ logger.info(
168
+ "GPIOArray '%s' configured with service address: %s",
169
+ name,
170
+ self.service_addr,
171
+ )
172
+
173
+ try:
174
+ self.client = Client(
175
+ self.service_addr, command_timeout=self.timeout, connect=False
176
+ )
177
+ logger.debug("IO4Edge client created for service: %s", self.service_addr)
178
+ except Exception as e:
179
+ logger.error(
180
+ "Failed to create IO4Edge client for %s: %s", self.service_addr, e
181
+ )
182
+ raise
183
+
184
+ def __repr__(self):
185
+ return f"{self.name}; Service Address: {self.service_addr}"
@@ -0,0 +1,54 @@
1
+ from ekfsm.devices.generic import Device
2
+ from ekfsm.devices.io4edge import IO4Edge
3
+ from ekfsm.log import ekfsm_logger
4
+ from io4edge_client.colorLED import Client
5
+
6
+ logger = ekfsm_logger(__name__)
7
+
8
+
9
+ class LEDArray(Device):
10
+ """
11
+ Device class for handling a LED array.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ name: str,
17
+ parent: IO4Edge,
18
+ children: list[Device] | None = None,
19
+ abort: bool = False,
20
+ service_suffix: str | None = None,
21
+ *args,
22
+ **kwargs,
23
+ ):
24
+ logger.debug(
25
+ f"Initializing LEDArray '{name}' with parent device {parent.deviceId}"
26
+ )
27
+
28
+ super().__init__(name, parent, children, abort, *args, **kwargs)
29
+
30
+ self.name = name
31
+
32
+ if service_suffix is not None:
33
+ self.service_suffix = service_suffix
34
+ logger.debug(f"Using custom service suffix: {service_suffix}")
35
+ else:
36
+ self.service_suffix = name
37
+ logger.debug(f"Using default service suffix: {name}")
38
+
39
+ self.service_addr = f"{parent.deviceId}-{self.service_suffix}"
40
+ logger.info(
41
+ f"LEDArray '{name}' configured with service address: {self.service_addr}"
42
+ )
43
+
44
+ try:
45
+ self.client = Client(self.service_addr)
46
+ logger.debug(f"LEDArray client created for service: {self.service_addr}")
47
+ except Exception as e:
48
+ logger.error(
49
+ f"Failed to create LEDArray client for {self.service_addr}: {e}"
50
+ )
51
+ raise
52
+
53
+ def __repr__(self):
54
+ return f"{self.name}; Service Address: {self.service_addr}"
ekfsm/devices/mux.py CHANGED
@@ -1,39 +1,77 @@
1
1
  from ekfsm.core.components import SysTree
2
+ from ekfsm.exceptions import ConfigError
3
+ from ekfsm.log import ekfsm_logger
2
4
 
3
- from ..core.sysfs import SysFSDevice
5
+ from ..core.sysfs import SysfsDevice
4
6
  from .generic import Device
5
7
 
8
+ logger = ekfsm_logger(__name__)
9
+
6
10
 
7
11
  class MuxChannel(Device):
12
+ """
13
+ A MuxChannel is a device that represents a channel on an I2C multiplexer.
14
+ It is a child of the I2CMux device.
15
+ The MuxChannel device is used to access the I2C bus on the channel.
16
+
17
+ Parameters
18
+ ----------
19
+ name
20
+ The name of the device.
21
+ channel_id
22
+ The channel ID of the device.
23
+ parent
24
+ The parent device of the MuxChannel.
25
+ children
26
+ The children of the MuxChannel device. If None, no children are created.
27
+ """
28
+
8
29
  def __init__(
9
30
  self,
10
31
  name: str,
11
- channel_id: int,
12
32
  parent: "I2CMux",
13
33
  children: list[Device] | None = None,
34
+ abort=False,
35
+ channel_id: int | None = None,
14
36
  *args,
15
37
  **kwargs,
16
38
  ) -> None:
17
- super().__init__(name=name, parent=parent, children=children)
39
+ super().__init__(name, parent, children, abort, *args, **kwargs)
18
40
  self.channel_id = channel_id
19
41
 
20
- assert parent.sysfs_device is not None
21
- assert isinstance(self.parent, I2CMux)
42
+ if parent.sysfs_device is None:
43
+ raise ConfigError(f"{self.name}: Parent I2CMux must have a sysfs_device")
44
+ if not isinstance(self.parent, I2CMux):
45
+ raise ConfigError(f"{self.name}: Parent must be an I2CMux instance")
22
46
 
23
47
  path = parent.sysfs_device.path / f"channel-{self.channel_id}"
24
- self.sysfs_device = SysFSDevice(path)
48
+ self.sysfs_device = SysfsDevice(path, False)
25
49
 
26
50
 
27
51
  class I2CMux(Device):
52
+ """
53
+ This class represents an I2C multiplexer device.
54
+
55
+ Parameters
56
+ ----------
57
+ name
58
+ The name of the device.
59
+ parent
60
+ The parent device of the I2CMux device. If None, no parent is created.
61
+ children
62
+ The children of the I2CMux device. If None, no children are created.
63
+ """
64
+
28
65
  def __init__(
29
66
  self,
30
67
  name: str,
31
68
  parent: SysTree | None = None,
32
- children: list[MuxChannel] | None = None,
69
+ children: list[Device] | None = None,
70
+ abort=False,
33
71
  *args,
34
72
  **kwargs,
35
73
  ):
36
- super().__init__(name, parent, children, **kwargs)
74
+ super().__init__(name, parent, children, abort, *args, **kwargs)
37
75
 
38
76
  self.addr = self.get_i2c_chip_addr()
39
77
  self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
@@ -0,0 +1,141 @@
1
+ from ekfsm.devices.generic import Device
2
+ from ekfsm.devices.io4edge import IO4Edge
3
+ from ekfsm.devices.utils import retry
4
+ from ekfsm.log import ekfsm_logger
5
+ from io4edge_client.pixelDisplay import Client
6
+ from PIL import Image
7
+
8
+ logger = ekfsm_logger(__name__)
9
+
10
+
11
+ class PixelDisplay(Device):
12
+ """
13
+ Device class for handling a pixel display.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ name: str,
19
+ parent: IO4Edge,
20
+ children: list[Device] | None = None,
21
+ abort: bool = False,
22
+ service_suffix: str | None = None,
23
+ *args,
24
+ **kwargs,
25
+ ):
26
+ logger.debug(
27
+ f"Initializing PixelDisplay '{name}' with parent device {parent.deviceId}"
28
+ )
29
+
30
+ super().__init__(name, parent, children, abort, *args, **kwargs)
31
+
32
+ self.name = name
33
+
34
+ if service_suffix is not None:
35
+ self.service_suffix = service_suffix
36
+ logger.debug(f"Using custom service suffix: {service_suffix}")
37
+ else:
38
+ self.service_suffix = name
39
+ logger.debug(f"Using default service suffix: {name}")
40
+
41
+ self.service_addr = f"{parent.deviceId}-{self.service_suffix}"
42
+ logger.info(
43
+ f"PixelDisplay '{name}' configured with service address: {self.service_addr}"
44
+ )
45
+
46
+ try:
47
+ self.client = Client(self.service_addr, connect=False)
48
+ logger.debug(
49
+ f"PixelDisplay client created for service: {self.service_addr}"
50
+ )
51
+ except Exception as e:
52
+ logger.error(
53
+ f"Failed to create PixelDisplay client for {self.service_addr}: {e}"
54
+ )
55
+ raise
56
+
57
+ @retry()
58
+ def describe(self) -> dict:
59
+ """
60
+ Returns a description of the pixel display.
61
+ """
62
+ logger.debug(f"Getting PixelDisplay description for '{self.name}'")
63
+ try:
64
+ describe = self.client.describe()
65
+ desc = {
66
+ "height": describe.height_pixel,
67
+ "width": describe.width_pixel,
68
+ "max_num_of_pixel": describe.max_num_of_pixel,
69
+ }
70
+ logger.debug(f"PixelDisplay '{self.name}' description: {desc}")
71
+ return desc
72
+ except Exception as e:
73
+ logger.error(
74
+ f"Failed to get PixelDisplay description for '{self.name}': {e}"
75
+ )
76
+ raise
77
+
78
+ @property
79
+ def height(self) -> int:
80
+ """
81
+ Returns the height of the pixel display in pixels.
82
+ """
83
+ return self.describe()["height"]
84
+
85
+ @property
86
+ def width(self) -> int:
87
+ """
88
+ Returns the width of the pixel display in pixels.
89
+ """
90
+ return self.describe()["width"]
91
+
92
+ @retry()
93
+ def off(self) -> None:
94
+ """
95
+ Turn off the pixel display.
96
+ @raises RuntimeError: if the command fails
97
+ @raises TimeoutError: if the command times out
98
+ """
99
+ logger.info(f"Turning off PixelDisplay '{self.name}'")
100
+ try:
101
+ self.client.set_display_off()
102
+ logger.debug(f"PixelDisplay '{self.name}' successfully turned off")
103
+ except Exception as e:
104
+ logger.error(f"Failed to turn off PixelDisplay '{self.name}': {e}")
105
+ raise
106
+
107
+ @retry()
108
+ def display_image(self, path: str) -> None:
109
+ """
110
+ Display an image on the pixel display.
111
+ @raises RuntimeError: if the command fails
112
+ @raises TimeoutError: if the command times out
113
+ """
114
+ logger.info(f"Displaying image '{path}' on PixelDisplay '{self.name}'")
115
+ try:
116
+ with Image.open(path) as img:
117
+ img = img.convert("RGB")
118
+ pix = img.load()
119
+ logger.debug(
120
+ f"Image '{path}' loaded and converted to RGB for PixelDisplay '{self.name}'"
121
+ )
122
+
123
+ with self.client as client:
124
+ logger.debug(f"Sending pixel data to PixelDisplay '{self.name}'")
125
+ for i in range(0, 320, 16):
126
+ pix_area = []
127
+ for k in range(0, 16):
128
+ for j in range(0, 240):
129
+ pix_area.append(pix[j, i + k])
130
+ client.set_pixel_area(0, i, 239, pix_area)
131
+ logger.debug(
132
+ f"Image successfully displayed on PixelDisplay '{self.name}'"
133
+ )
134
+ except Exception as e:
135
+ logger.error(
136
+ f"Failed to display image '{path}' on PixelDisplay '{self.name}': {e}"
137
+ )
138
+ raise
139
+
140
+ def __repr__(self):
141
+ return f"{self.name}; Service Address: {self.service_addr}"