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/generic.py CHANGED
@@ -1,12 +1,14 @@
1
- from typing import TYPE_CHECKING
1
+ from pathlib import Path
2
+ from typing import TYPE_CHECKING, Callable
3
+
2
4
  from munch import Munch
5
+
3
6
  from ekfsm.core.components import SysTree
4
- from ekfsm.core.sysfs import SysFSDevice, sysfs_root
5
- from ekfsm.exceptions import ConfigError
6
- from pathlib import Path
7
+ from ekfsm.core.sysfs import SysfsDevice, sysfs_root
8
+ from ekfsm.exceptions import ConfigError, SysFSError, UnsupportedModeError
7
9
 
8
10
  if TYPE_CHECKING:
9
- from ekfsm.core.components import HwModule
11
+ from ekfsm.core.components import HWModule
10
12
 
11
13
 
12
14
  class Device(SysTree):
@@ -26,26 +28,25 @@ class Device(SysTree):
26
28
  super().__init__(name, abort=abort)
27
29
  self.parent = parent
28
30
  self.device_args = kwargs
29
- self.logger.debug(f"Device: {name} {kwargs}")
30
31
 
31
32
  if children:
32
33
  self.children = children
33
34
 
34
- # needs to be set during init because root will be changed after tree is complete
35
- self.hw_module = self.root
35
+ # Needs to be set during init because root will be changed after tree is complete
36
+ self.hw_module = self.root # pyright: ignore[reportAttributeAccessIssue]
36
37
 
37
- # i2c initialization
38
+ # I2C initialization
38
39
  if not hasattr(self, "sysfs_device") or self.sysfs_device is None:
39
- self.sysfs_device: SysFSDevice | None = None
40
+ self.sysfs_device: SysfsDevice | None = None
40
41
 
41
- # post init
42
+ # Post-initialization steps
42
43
  self._provides_attrs = kwargs.get("provides", {})
43
- self.provides = self.__post_init__(Munch(self._provides_attrs))
44
+ self.provides = self.__post_init__(Munch(self._provides_attrs), abort)
44
45
 
45
- def __post_init__(self, provides: Munch) -> Munch:
46
+ def __post_init__(self, provides: Munch, abort) -> Munch:
46
47
  for key, fields in provides.items():
47
48
  if isinstance(fields, dict):
48
- provides[key] = self.__post_init__(Munch(fields))
49
+ provides[key] = self.__post_init__(Munch(fields), abort)
49
50
  elif isinstance(fields, list | str):
50
51
  provides[key] = Munch()
51
52
 
@@ -53,59 +54,182 @@ class Device(SysTree):
53
54
  fields = [fields]
54
55
 
55
56
  while fields:
56
- iface = fields.pop()
57
- if isinstance(iface, dict):
58
- name = list(iface.keys())[0]
57
+ interface = fields.pop()
58
+
59
+ # TODO: Check if this is superfluous after schema validation
60
+ if isinstance(interface, dict):
61
+ name = list(interface.keys())[0]
62
+
59
63
  try:
60
- func = list(iface.values())[0]
64
+ func = list(interface.values())[0]
61
65
  except IndexError:
62
66
  raise ConfigError(
63
67
  f"{self.name}: No function given for interface {name}."
64
68
  )
69
+
65
70
  if not hasattr(self, func):
66
- raise NotImplementedError(
67
- f"{self.name}: Function {func} for interface {name} not implemented."
68
- )
71
+ if abort:
72
+ raise NotImplementedError(
73
+ f"{self.name}: Function {func} for interface {name} not implemented."
74
+ )
75
+ continue
76
+
69
77
  provides[key].update({name: getattr(self, func)})
70
78
  else:
71
- if not hasattr(self, iface):
72
- raise NotImplementedError(
73
- f"{self.name}: Function {iface} for provider {key} not implemented."
74
- )
75
- provides[key].update({iface: getattr(self, iface)})
79
+ if not hasattr(self, interface):
80
+ if abort:
81
+ raise NotImplementedError(
82
+ f"{self.name}: Function {interface} for provider {key} not implemented."
83
+ )
84
+ continue
85
+
86
+ provides[key].update({interface: getattr(self, interface)})
76
87
 
77
88
  return provides
78
89
 
90
+ @property
91
+ def sysfs(self):
92
+ """
93
+ Access sysfs device for device
94
+
95
+ Returns
96
+ -------
97
+ Sysfs device for device
98
+
99
+ Raises
100
+ ------
101
+ SysFSError
102
+ If the SysFSDevice does not exist
103
+ """
104
+ if self.sysfs_device is None:
105
+ raise SysFSError(f"No sysfs device attached to device {self.name}")
106
+
107
+ return self.sysfs_device
108
+
109
+ def read_attr(self, attr: str, mode: str = "utf", strip: bool = True):
110
+ match mode:
111
+ case "utf":
112
+ return self.sysfs.read_utf8(attr, strip)
113
+ case "bytes":
114
+ return self.sysfs.read_bytes(attr)
115
+ case "float":
116
+ return self.sysfs.read_float(attr)
117
+ case "int":
118
+ return self.sysfs.read_int(attr)
119
+ case "hex":
120
+ return self.sysfs.read_hex(attr)
121
+ case _:
122
+ raise UnsupportedModeError(f"Mode {mode} is not supported")
123
+
124
+ def read_attr_or_default(
125
+ self, attr: str, mode: str = "utf", strip: bool = True, default=None
126
+ ):
127
+ try:
128
+ return self.read_attr(attr, mode, strip)
129
+ except UnsupportedModeError:
130
+ raise
131
+ except Exception:
132
+ if default is not None:
133
+ return default
134
+ return None
135
+
136
+ def read_sysfs_bytes(self, attr) -> bytes:
137
+ if len(attr) <= 0:
138
+ raise SysFSError("sysfs attribute name too short")
139
+
140
+ return self.sysfs.read_bytes(attr)
141
+
79
142
  def read_sysfs_attr_bytes(self, attr: str) -> bytes | None:
80
- if self.sysfs_device and len(attr) != 0:
143
+ """
144
+ Read a sysfs attribute as bytes.
145
+
146
+ Parameters
147
+ ----------
148
+ attr
149
+ The sysfs attribute to read.
150
+
151
+ Returns
152
+ -------
153
+ content: bytes
154
+ The contents of the sysfs attribute as bytes.
155
+ None:
156
+ If the sysfs device is not set or the attribute does not exist.
157
+ """
158
+ if (
159
+ self.sysfs_device is not None
160
+ and len(attr) != 0
161
+ and attr in [x.name for x in self.sysfs_device.attributes]
162
+ ):
81
163
  return self.sysfs_device.read_attr_bytes(attr)
82
164
  return None
83
165
 
84
166
  def read_sysfs_attr_utf8(self, attr: str) -> str | None:
85
- if self.sysfs_device and len(attr) != 0:
86
- return self.sysfs_device.read_attr_utf8(attr)
87
- return None
167
+ """
168
+ Read a sysfs attribute as UTF-8 string.
169
+
170
+ Parameters
171
+ ----------
172
+ attr
173
+ The sysfs attribute to read.
174
+
175
+ Returns
176
+ -------
177
+ content: str
178
+ The contents of the sysfs attribute as UTF-8 string.
179
+ None:
180
+ If the sysfs device is not set or the attribute does not exist.
181
+ """
182
+ try:
183
+ return self.sysfs.read_utf8(attr)
184
+ except Exception:
185
+ return None
88
186
 
89
187
  def write_sysfs_attr(self, attr: str, data: str | bytes) -> None:
188
+ """
189
+ Write data to a sysfs attribute.
190
+
191
+ Parameters
192
+ ----------
193
+ attr
194
+ The sysfs attribute to write to.
195
+ data
196
+ The data to write to the sysfs attribute.
197
+ """
90
198
  if self.sysfs_device and len(attr) != 0:
91
199
  return self.sysfs_device.write_attr(attr, data)
92
200
  return None
93
201
 
94
202
  @property
95
- def hw_module(self) -> "HwModule":
96
- from ekfsm.core.components import HwModule
203
+ def hw_module(self) -> "HWModule":
204
+ """
205
+ Get or set the HWModule instance that this device belongs to.
206
+
207
+ Parameters
208
+ ----------
209
+ hw_module: optional
210
+ The HWModule instance to set.
211
+
212
+ Returns
213
+ -------
214
+ HWModule
215
+ The HWModule instance that this device belongs to.
216
+ None
217
+ If used as a setter.
218
+ """
219
+ from ekfsm.core.components import HWModule
97
220
 
98
- if isinstance(self._hw_module, HwModule):
221
+ if isinstance(self._hw_module, HWModule):
99
222
  return self._hw_module
100
223
  else:
101
- raise RuntimeError("Device is not a child of HwModule")
224
+ raise RuntimeError("Device is not a child of HWModule")
102
225
 
103
226
  @hw_module.setter
104
- def hw_module(self, hw_module: "HwModule") -> None:
227
+ def hw_module(self, hw_module: "HWModule") -> None:
105
228
  self._hw_module = hw_module
106
229
 
107
230
  def get_i2c_chip_addr(self) -> int:
108
- assert self.parent is not None
231
+ if self.parent is None:
232
+ raise ConfigError(f"{self.name}: Device must have a parent to get I2C chip address")
109
233
 
110
234
  chip_addr = self.device_args.get("addr")
111
235
  if chip_addr is None:
@@ -139,21 +263,27 @@ class Device(SysTree):
139
263
 
140
264
  return chip_addr
141
265
 
142
- def get_i2c_sysfs_device(self, addr: int) -> SysFSDevice:
143
- from ekfsm.core.components import HwModule
266
+ def get_i2c_sysfs_device(
267
+ self, addr: int, driver_required=True, find_driver: Callable | None = None
268
+ ) -> SysfsDevice:
269
+ from ekfsm.core.components import HWModule
144
270
 
145
271
  parent = self.parent
146
- assert parent is not None
272
+ if parent is None:
273
+ raise ConfigError(f"{self.name}: Device must have a parent to get I2C sysfs device")
147
274
 
148
- # if parent is a HwModule, we can get the i2c bus from the master device
149
- if isinstance(parent, HwModule):
150
- i2c_bus_path = self._master_i2c_bus()
275
+ # If parent is a HWModule, we can get the i2c bus from the master device
276
+ # XXX: Does this still hold true after refactoring?
277
+ if isinstance(parent, HWModule):
278
+ i2c_bus_path = self.__master_i2c_bus()
151
279
  else:
152
280
  # otherwise the parent must be a MuxChannel
153
281
  from ekfsm.devices.mux import MuxChannel
154
282
 
155
- assert isinstance(parent, MuxChannel)
156
- assert parent.sysfs_device is not None
283
+ if not isinstance(parent, MuxChannel):
284
+ raise ConfigError(f"{self.name}: Parent must be MuxChannel when not a HWModule")
285
+ if parent.sysfs_device is None:
286
+ raise ConfigError(f"{self.name}: Parent MuxChannel must have a sysfs_device")
157
287
  i2c_bus_path = parent.sysfs_device.path
158
288
 
159
289
  # search for device with addr
@@ -163,29 +293,34 @@ class Device(SysTree):
163
293
  and not (entry / "new_device").exists() # skip bus entries
164
294
  and (entry / "name").exists()
165
295
  ):
166
- # for PRP devices, address is contained in firmware_node/description
296
+ # PRP devices unfortunately do not readily expose the underlying I2C address of the device like
297
+ # regular I2C devices that follow the `${I2C_BUS}-${ADDR}` pattern. To address this issue, we
298
+ # initialize the ACPI _STR object for each PRP device with the necessary information, which is
299
+ # accessible in the `${DEVICE_SYSFS_PATH}/firmware_node/description` file.
167
300
  if (entry / "firmware_node").exists() and (
168
301
  entry / "firmware_node" / "description"
169
302
  ).exists():
170
303
  description = (
171
304
  (entry / "firmware_node/description").read_text().strip()
172
305
  )
173
- got_addr = int(description.split(" - ")[0], 16)
174
- if got_addr == addr:
175
- return SysFSDevice(entry)
306
+ acpi_addr = int(description.split(" - ")[0], 16)
307
+
308
+ if acpi_addr == addr:
309
+ return SysfsDevice(entry, driver_required, find_driver)
176
310
 
177
- # for non-PRP devices, address is contained in the directory name (e.g. 2-0018)
311
+ # For regular non-PRP devices, the address is contained in the directory name (e.g. 2-0018).
178
312
  else:
179
- got_addr = int(entry.name.split("-")[1], 16)
180
- if got_addr == addr:
181
- return SysFSDevice(entry)
313
+ acpi_addr = int(entry.name.split("-")[1], 16)
314
+
315
+ if acpi_addr == addr:
316
+ return SysfsDevice(entry, driver_required, find_driver)
182
317
 
183
318
  raise FileNotFoundError(
184
319
  f"Device with address 0x{addr:x} not found in {i2c_bus_path}"
185
320
  )
186
321
 
187
322
  @staticmethod
188
- def _master_i2c_get_config(master: "HwModule") -> dict:
323
+ def __master_i2c_get_config(master: "HWModule") -> dict:
189
324
  if (
190
325
  master.config.get("bus_masters") is not None
191
326
  and master.config["bus_masters"].get("i2c") is not None
@@ -194,12 +329,13 @@ class Device(SysTree):
194
329
  else:
195
330
  raise ConfigError("Master definition incomplete")
196
331
 
197
- def _master_i2c_bus(self) -> Path:
332
+ def __master_i2c_bus(self) -> Path:
198
333
  if self.hw_module.is_master:
199
334
  # we are the master
200
335
  master = self.hw_module
201
336
  master_key = "MASTER_LOCAL_DEFAULT"
202
337
  override_master_key = self.device_args.get("i2c_master", None)
338
+
203
339
  if override_master_key is not None:
204
340
  master_key = override_master_key
205
341
  else:
@@ -212,18 +348,20 @@ class Device(SysTree):
212
348
  master = self.hw_module.slot.master
213
349
  master_key = self.hw_module.slot.slot_type.name
214
350
 
215
- i2c_masters = self._master_i2c_get_config(master)
351
+ i2c_masters = self.__master_i2c_get_config(master)
216
352
 
217
353
  if i2c_masters.get(master_key) is not None:
218
354
  dir = sysfs_root() / Path(i2c_masters[master_key])
219
355
  bus_dirs = list(dir.glob("i2c-*"))
356
+
220
357
  if len(bus_dirs) == 1:
221
358
  return bus_dirs[0]
222
359
  elif len(bus_dirs) > 1:
223
- raise ConfigError(f"Multiple master i2c buses found for {master_key}")
224
- raise ConfigError(f"No master i2c bus found for {master_key}")
360
+ raise ConfigError(f"Multiple master I2C buses found for {master_key}")
361
+
362
+ raise ConfigError(f"No master I2C bus found for {master_key}")
225
363
  else:
226
- raise ConfigError(f"Master i2c bus not found for {master_key}")
364
+ raise ConfigError(f"Master I2C bus not found for {master_key}")
227
365
 
228
366
  def get_i2c_bus_number(self) -> int:
229
367
  """
@@ -240,11 +378,14 @@ class Device(SysTree):
240
378
  parent_path = self.parent.sysfs_device.path
241
379
  else:
242
380
  parent_path = self.sysfs_device.path.parent
381
+
243
382
  if parent_path.is_symlink():
244
383
  parent_path = parent_path.readlink()
384
+
245
385
  bus_number = parent_path.name.split("-")[1]
386
+
246
387
  return int(bus_number)
247
388
 
248
389
  def __repr__(self) -> str:
249
390
  sysfs_path = getattr(self.sysfs_device, "path", "")
250
- return f"{self.name}; sysfs_path: {sysfs_path}"
391
+ return f"{self.name}; Path: {sysfs_path}"
ekfsm/devices/gpio.py CHANGED
@@ -1,19 +1,19 @@
1
- from abc import ABC, abstractmethod
2
1
  import os
3
2
  import re
3
+ from abc import ABC, abstractmethod
4
4
  from pathlib import Path
5
5
 
6
6
  import gpiod
7
- from gpiod.chip import LineSettings
8
7
  from gpiod.line import Direction, Value
8
+ from gpiod.line_settings import LineSettings
9
9
  from more_itertools import first_true
10
10
 
11
11
  from ekfsm.core.components import SysTree
12
- from ekfsm.exceptions import GPIOError
12
+ from ekfsm.exceptions import ConfigError, GPIOError
13
13
  from ekfsm.log import ekfsm_logger
14
14
 
15
- from .generic import Device
16
15
  from ..core.probe import ProbeableDevice
16
+ from .generic import Device
17
17
 
18
18
  gpio_pat = re.compile(r"gpiochip\d+")
19
19
 
@@ -24,7 +24,13 @@ def get_gpio_major_minor(path: Path) -> tuple[int, int]:
24
24
  dev = d / "dev"
25
25
  if dev.exists():
26
26
  content = dev.read_text().strip()
27
- major, minor = map(int, content.split(":"))
27
+ try:
28
+ major, minor = map(int, content.split(":"))
29
+ except Exception as e:
30
+ raise GPIOError(
31
+ GPIOError.ErrorType.NO_MAJOR_MINOR,
32
+ f"No minor/major number found for GPIO device at {path}",
33
+ ) from e
28
34
  return major, minor
29
35
 
30
36
  raise GPIOError(
@@ -51,11 +57,12 @@ def find_gpio_dev_with_major_minor(major: int, minor: int) -> Path | None:
51
57
 
52
58
 
53
59
  class GPIO(Device):
54
-
55
60
  def __init__(
56
61
  self,
57
62
  name: str,
58
63
  parent: SysTree | None = None,
64
+ children: Device | None = None,
65
+ abort: bool = False,
59
66
  *args,
60
67
  **kwargs,
61
68
  ):
@@ -63,6 +70,7 @@ class GPIO(Device):
63
70
  name,
64
71
  parent,
65
72
  None,
73
+ abort,
66
74
  *args,
67
75
  **kwargs,
68
76
  )
@@ -70,23 +78,18 @@ class GPIO(Device):
70
78
  major, minor = self._find_gpio_dev(parent, *args, **kwargs)
71
79
  self.gpio = find_gpio_dev_with_major_minor(major, minor)
72
80
 
73
- assert self.gpio is not None
81
+ if self.gpio is None:
82
+ raise GPIOError(GPIOError.ErrorType.NO_MATCHING_DEVICE, f"{self.name}: GPIO device not found")
74
83
  match = re.search(r"\d+", self.gpio.name)
75
- if match:
76
- self.number: int = int(match.group().strip())
77
- else:
78
- raise GPIOError(
79
- GPIOError.ErrorType.NO_MATCHING_DEVICE, "Failed to find matching device"
80
- )
84
+
85
+ if not match:
86
+ raise GPIOError(GPIOError.ErrorType.NO_MATCHING_DEVICE, "Failed to find matching device")
87
+
88
+ self.number: int = int(match.group().strip())
81
89
 
82
90
  self.init_dev()
83
91
 
84
- def _find_gpio_dev(
85
- self,
86
- parent: SysTree | None = None,
87
- *args,
88
- **kwargs,
89
- ) -> tuple[int, int]:
92
+ def _find_gpio_dev(self, parent: SysTree | None = None, *args, **kwargs) -> tuple[int, int]:
90
93
  self.addr = self.get_i2c_chip_addr()
91
94
  self.logger.debug(f"GPIO: {self.addr}")
92
95
  self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
@@ -97,9 +100,13 @@ class GPIO(Device):
97
100
  try:
98
101
  self.dev = gpiod.Chip(str(self.gpio))
99
102
  self.initialized = True
103
+ return
100
104
  except FileNotFoundError:
105
+ self.initialized = False
101
106
  raise FileNotFoundError(f"{self.gpio} does not exist")
102
107
 
108
+ self.initialized = False
109
+
103
110
  def num_lines(self) -> int:
104
111
  """
105
112
  Get number of GPIO lines available on the device.
@@ -153,9 +160,7 @@ class GPIO(Device):
153
160
  default=None,
154
161
  )
155
162
  ) is not None:
156
- raise GPIOError(
157
- GPIOError.ErrorType.INVALID_PIN, f"GPIO {invalid_pin} is invalid."
158
- )
163
+ raise GPIOError(GPIOError.ErrorType.INVALID_PIN, f"GPIO {invalid_pin} is invalid.")
159
164
 
160
165
  def set_direction(self, pin: int, direction: bool) -> None:
161
166
  """
@@ -187,10 +192,12 @@ class GPIOExpander(GPIO):
187
192
  self,
188
193
  name: str,
189
194
  parent: SysTree | None,
195
+ children: Device | None = None,
196
+ abort: bool = False,
190
197
  *args,
191
198
  **kwargs,
192
199
  ):
193
- super().__init__(name, parent, None, *args, **kwargs)
200
+ super().__init__(name, parent, None, abort, *args, **kwargs)
194
201
 
195
202
  def __str__(self) -> str:
196
203
  return (
@@ -204,15 +211,18 @@ class EKFIdentificationIOExpander(GPIOExpander, ProbeableDevice):
204
211
  self,
205
212
  name: str,
206
213
  parent: SysTree | None,
214
+ children: Device | None = None,
215
+ abort: bool = False,
207
216
  *args,
208
217
  **kwargs,
209
218
  ):
210
- super().__init__(name, parent, None, *args, **kwargs)
219
+ super().__init__(name, parent, None, abort, *args, **kwargs)
211
220
 
212
221
  def probe(self, *args, **kwargs) -> bool:
213
- from ekfsm.core import HwModule
222
+ from ekfsm.core import HWModule
214
223
 
215
- assert isinstance(self.hw_module, HwModule)
224
+ if not isinstance(self.hw_module, HWModule):
225
+ raise ConfigError(f"{self.name}: hw_module must be a HWModule instance")
216
226
  id, _ = self.read_board_id_rev()
217
227
  self.logger.debug(f"Probing EKFIdentificationIOExpander: {id}")
218
228
 
@@ -245,7 +255,7 @@ class EKFIdentificationIOExpander(GPIOExpander, ProbeableDevice):
245
255
  )
246
256
 
247
257
  def revision(self) -> str:
248
- id, rev = self.read_board_id_rev()
258
+ _, rev = self.read_board_id_rev()
249
259
  return str(rev)
250
260
 
251
261
  def read_id_gpio_inputs(self) -> int:
ekfsm/devices/iio.py CHANGED
@@ -1,7 +1,9 @@
1
- from .generic import SysFSDevice
1
+ from ekfsm.exceptions import ConversionError, SysFSError
2
2
 
3
+ from .generic import SysfsDevice
3
4
 
4
- def iio_get_in_value(dev: SysFSDevice, attrset: str) -> float:
5
+
6
+ def iio_get_in_value(dev: SysfsDevice, attrset: str) -> float:
5
7
  """
6
8
  Calculate a value from an IIO in_* attribute set, using the _raw, _scale and _offset attributes (if present).
7
9
  Typical name of attrset are "in_temp" or "in_voltage0".
@@ -26,33 +28,15 @@ def iio_get_in_value(dev: SysFSDevice, attrset: str) -> float:
26
28
  if the neiter _input nor _raw attribute is found
27
29
 
28
30
  """
29
- # check if _input exists
30
- try:
31
- content = dev.read_attr_utf8(f"{attrset}_input")
32
- return float(content)
33
- except StopIteration:
34
- pass
35
-
36
- # check if _raw exists
37
31
  try:
38
- content = dev.read_attr_utf8(f"{attrset}_raw")
39
- except StopIteration:
40
- raise FileNotFoundError(f"{attrset}_raw not found")
41
-
42
- raw = float(content)
43
-
44
- # use offset if present
45
- try:
46
- content = dev.read_attr_utf8(f"{attrset}_offset")
47
- raw += float(content)
48
- except StopIteration:
49
- pass
50
-
51
- # use scale if present
52
- try:
53
- content = dev.read_attr_utf8(f"{attrset}_scale")
54
- raw *= float(content)
55
- except StopIteration:
56
- pass
57
-
58
- return raw
32
+ content = dev.read_float(f"{attrset}_input")
33
+ except (SysFSError, ConversionError):
34
+ try:
35
+ raw = dev.read_float(f"{attrset}_raw")
36
+ offset = dev.read_float(f"{attrset}_offset")
37
+ scale = dev.read_float(f"{attrset}_scale")
38
+ content = (raw + offset) * scale
39
+ except (SysFSError, ConversionError) as e:
40
+ raise FileNotFoundError from e
41
+
42
+ return content
@@ -1,10 +1,8 @@
1
- from pathlib import Path
2
-
3
1
  from ekfsm.core.components import SysTree
4
2
  from ekfsm.log import ekfsm_logger
3
+ from ekfsm.utils import next_or_raise
5
4
 
6
- from ..core.sysfs import SysFSDevice
7
-
5
+ from ..core.sysfs import list_sysfs_attributes
8
6
  from .generic import Device
9
7
  from .iio import iio_get_in_value
10
8
 
@@ -13,6 +11,14 @@ class IIOThermalHumidity(Device):
13
11
  """
14
12
  Device for IIO thermal and/or humidity sensors.
15
13
 
14
+ Parameters
15
+ ----------
16
+ name
17
+ The name of the device.
18
+ parent
19
+ The parent device of the IIOThermalHumidity device. If None, no parent is created.
20
+ children
21
+ The children of the IIOThermalHumidity device. If None, no children are created.
16
22
  """
17
23
 
18
24
  def __init__(
@@ -20,22 +26,23 @@ class IIOThermalHumidity(Device):
20
26
  name: str,
21
27
  parent: SysTree | None = None,
22
28
  children: list[Device] | None = None,
29
+ abort: bool = False,
23
30
  *args,
24
31
  **kwargs,
25
32
  ):
26
33
  self.logger = ekfsm_logger("IIOThermalHumidity:" + name)
27
- super().__init__(name, parent, children, **kwargs)
34
+ super().__init__(name, parent, children, abort, *args, **kwargs)
28
35
  self.addr = self.get_i2c_chip_addr()
29
- self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
36
+ self.sysfs_device = self.get_i2c_sysfs_device(self.addr, False)
37
+
38
+ # TODO: We can just search the attributes directly
39
+ iio_dir = self.sysfs.path.glob("iio:device*")
40
+ attrs = next_or_raise(iio_dir, FileNotFoundError("IIO entry not found"))
30
41
 
31
- dir = list(Path(self.sysfs_device.path).glob("iio:device*"))
32
- if len(dir) == 0:
33
- raise FileNotFoundError("iio entry not found")
34
- self.iio_sysfs = SysFSDevice(dir[0])
35
- self.logger.debug(f"iio: {self.iio_sysfs.path}")
42
+ self.sysfs_device.extend_attributes(list_sysfs_attributes(attrs))
36
43
 
37
44
  def temperature(self) -> float:
38
- return iio_get_in_value(self.iio_sysfs, "in_temp") / 1000.0
45
+ return iio_get_in_value(self.sysfs, "in_temp") / 1000.0
39
46
 
40
47
  def humidity(self) -> float:
41
- return iio_get_in_value(self.iio_sysfs, "in_humidityrelative") / 1000.0
48
+ return iio_get_in_value(self.sysfs, "in_humidityrelative") / 1000.0