ekfsm 1.1.0a15.post1__py3-none-any.whl → 1.3.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.

ekfsm/devices/coretemp.py CHANGED
@@ -1,13 +1,15 @@
1
1
  from __future__ import annotations
2
- import os
2
+
3
3
  import glob
4
+ import os
4
5
  from pathlib import Path
5
6
 
6
- from ekfsm.core.sysfs import SYSFS_ROOT
7
+ import ekfsm.core
8
+ from ekfsm.core.sysfs import sysfs_root
7
9
  from ekfsm.devices.generic import Device
8
10
 
9
11
  # Path to the root of the HWMON sysfs filesystem
10
- HWMON_ROOT = SYSFS_ROOT / Path("class/hwmon")
12
+ HWMON_ROOT = sysfs_root() / Path("class/hwmon")
11
13
 
12
14
 
13
15
  def find_core_temp_dir(hwmon_dir) -> Path:
@@ -48,16 +50,15 @@ class CoreTemp(Device):
48
50
  self,
49
51
  name: str,
50
52
  parent: Device,
53
+ children: list["Device"] | None = None,
54
+ abort: bool = False,
51
55
  *args,
52
56
  **kwargs,
53
57
  ):
54
- from ekfsm.core.sysfs import SysFSDevice, SYSFS_ROOT
55
-
56
- self.sysfs_device: SysFSDevice = SysFSDevice(
57
- find_core_temp_dir(SYSFS_ROOT / Path("class/hwmon"))
58
- )
58
+ dir = find_core_temp_dir(sysfs_root() / Path("class/hwmon"))
59
+ self.sysfs_device = ekfsm.core.sysfs.SysfsDevice(dir, False)
59
60
 
60
- super().__init__(name, parent, None, *args, **kwargs)
61
+ super().__init__(name, parent, None, abort, *args, **kwargs)
61
62
 
62
63
  def cputemp(self):
63
64
  """
@@ -68,4 +69,4 @@ class CoreTemp(Device):
68
69
  int
69
70
  The CPU temperature in degrees Celsius.
70
71
  """
71
- return int(self.sysfs_device.read_attr_utf8("temp1_input").strip()) / 1000
72
+ return self.sysfs.read_int("temp1_input") / 1000
ekfsm/devices/eeprom.py CHANGED
@@ -11,18 +11,18 @@ Routine Listings
11
11
 
12
12
  from abc import ABC, abstractmethod
13
13
  from datetime import date
14
- from typing import Any, Callable, Literal, Sequence
15
14
  from functools import wraps
15
+ from typing import Any, Callable, Literal, Sequence
16
+
17
+ from hexdump import hexdump
16
18
 
17
19
  from ekfsm.core.components import SysTree
18
20
  from ekfsm.core.probe import ProbeableDevice
21
+ from ekfsm.exceptions import DataCorruptionError, DriverError, SysFSError
22
+ from ekfsm.log import ekfsm_logger
19
23
 
20
24
  from .generic import Device
21
- from .utils import compute_int_from_bytes, get_crc16_xmodem
22
- from ekfsm.exceptions import DataCorruptionError
23
- from hexdump import hexdump
24
-
25
- from ekfsm.log import ekfsm_logger
25
+ from .utils import get_crc16_xmodem
26
26
 
27
27
  logger = ekfsm_logger(__name__)
28
28
 
@@ -80,13 +80,19 @@ class EEPROM(Device):
80
80
  self,
81
81
  name: str,
82
82
  parent: SysTree | None = None,
83
+ children: list[Device] | None = None,
84
+ abort: bool = False,
83
85
  *args,
84
86
  **kwargs,
85
87
  ):
86
- super().__init__(name, parent, None, *args, **kwargs)
88
+ super().__init__(name, parent, None, abort, *args, **kwargs)
87
89
 
88
90
  self.addr = self.get_i2c_chip_addr()
89
91
  self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
92
+
93
+ if not self.sysfs_device.get_driver():
94
+ raise DriverError("No driver attached to device {self.name}")
95
+
90
96
  self._update_content()
91
97
 
92
98
  def _update_content(self) -> None:
@@ -117,20 +123,10 @@ class EEPROM(Device):
117
123
 
118
124
  Raises
119
125
  ------
120
- FileNotFoundError
121
- If the EEPROM sysfs file is not found.
122
- RuntimeError
123
- If the sysfs device is not found.
126
+ SysFSError
127
+ No sysfs device found for EEPROM or `eeprom` attribute does not exist
124
128
  """
125
- try:
126
- if self.sysfs_device:
127
- cnt = self.sysfs_device.read_attr_bytes("eeprom")
128
- else:
129
- raise RuntimeError("No sysfs device for EEPROM")
130
- except FileNotFoundError:
131
- raise FileNotFoundError("EEPROM not found")
132
-
133
- return cnt
129
+ return self.read_sysfs_bytes("eeprom")
134
130
 
135
131
  def write(self, data: bytes, offset: int = 0) -> None:
136
132
  """
@@ -155,15 +151,27 @@ class EEPROM(Device):
155
151
  Note
156
152
  ----
157
153
  Operation is checked for data corruption by reading back the written data.
154
+
155
+ Important
156
+ ---------
157
+ The offset parameter is only supported if the EEPROM driver is bin_attribute enabled.
158
+
159
+ For almost any other native sysfs attribute, this is NOT the case!
158
160
  """
159
- try:
160
- if self.sysfs_device:
161
- logger.info(f"Writing {len(data)} bytes to EEPROM at offset {offset}")
162
- self.sysfs_device.write_attr("eeprom", data, offset)
163
- else:
164
- raise RuntimeError("No sysfs device for EEPROM")
165
- except FileNotFoundError:
166
- raise FileNotFoundError("EEPROM not found")
161
+
162
+ if self.sysfs_device:
163
+ attr = next(x for x in self.sysfs_device.attributes if x.name == "eeprom")
164
+ logger.info(f"Writing {len(data)} bytes to EEPROM at offset {offset}")
165
+ if attr.is_sysfs_attr() and data is not None:
166
+ mode = "r+" if isinstance(data, str) else "rb+"
167
+ try:
168
+ with open(attr.path, mode) as f:
169
+ f.seek(offset)
170
+ f.write(data)
171
+ except OSError as e:
172
+ raise SysFSError("Error accessing SysFS attribute") from e
173
+ else:
174
+ raise RuntimeError("No sysfs device for EEPROM")
167
175
 
168
176
  self._update_content()
169
177
  written = self._content[offset : offset + len(data)]
@@ -202,6 +210,10 @@ class Validatable_EEPROM(EEPROM, ABC):
202
210
 
203
211
  def __init__(
204
212
  self,
213
+ name: str,
214
+ parent: SysTree | None = None,
215
+ children: list[Device] | None = None,
216
+ abort: bool = False,
205
217
  crc_pos: Literal["start", "end"] = "end",
206
218
  crc_length: int = 2,
207
219
  *args,
@@ -210,7 +222,7 @@ class Validatable_EEPROM(EEPROM, ABC):
210
222
  self._crc_length: int = crc_length
211
223
  self._crc_pos: str = crc_pos
212
224
 
213
- super().__init__(*args, **kwargs)
225
+ super().__init__(name, parent, children, abort, *args, **kwargs)
214
226
 
215
227
  self._crc_pos_start = len(self._data) if self._crc_pos == "end" else 0
216
228
  self._crc_pos_end = self._crc_pos_start + self._crc_length
@@ -388,13 +400,16 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
388
400
 
389
401
  def __init__(
390
402
  self,
403
+ name: str,
404
+ parent: SysTree | None = None,
405
+ children: list[Device] | None = None,
406
+ abort: bool = False,
391
407
  *args,
392
408
  **kwargs,
393
409
  ) -> None:
394
- super().__init__(*args, **kwargs)
410
+ super().__init__(name, parent, children, abort, *args, **kwargs)
395
411
 
396
412
  def _update_content(self) -> None:
397
-
398
413
  super()._update_content()
399
414
 
400
415
  # EKF EEPROM content is restricted to 128 bytes, so strip the rest!
@@ -424,7 +439,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
424
439
  The serial number of the root device.
425
440
  """
426
441
  area = self._content[self._sernum_index_start : self._sernum_index_end]
427
- sernum = compute_int_from_bytes(area[::-1])
442
+ sernum = int.from_bytes(area, byteorder="little")
428
443
  return str(sernum)
429
444
 
430
445
  def write_serial(self, serial: int) -> None:
@@ -461,7 +476,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
461
476
  area = self._content[
462
477
  self._customer_serial_index_start : self._customer_serial_index_end
463
478
  ]
464
- sernum = compute_int_from_bytes(area[::-1])
479
+ sernum = int.from_bytes(area, byteorder="little")
465
480
  return str(sernum)
466
481
 
467
482
  def write_custom_serial(self, serial: int) -> None:
@@ -690,8 +705,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
690
705
  date
691
706
  The decoded date.
692
707
  """
693
- encoded_date = encoded_date[::-1]
694
- bdate = compute_int_from_bytes(encoded_date)
708
+ bdate = int.from_bytes(encoded_date, byteorder="little")
695
709
 
696
710
  # Extract the day (bit 0-4)
697
711
  day = bdate & 0x1F # 0x1F is 00011111 in binary (5 bits)
@@ -713,7 +727,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
713
727
  ) # Handle invalid dates, e.g., 30th Feb
714
728
 
715
729
  @classmethod
716
- def _encode_date(self, date: date) -> bytes:
730
+ def _encode_date(cls, date: date) -> bytes:
717
731
  """
718
732
  Encode a date into a proprietary 2-byte format.
719
733
 
@@ -772,7 +786,6 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
772
786
  super().__init__(*args, **kwargs)
773
787
 
774
788
  def _update_content(self) -> None:
775
-
776
789
  super()._update_content()
777
790
 
778
791
  # CCU content is the raw content area of the EEPROM
@@ -933,7 +946,7 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
933
946
  area = self._content[
934
947
  self._cserial_index_start : self._cserial_index_start + self._cserial_length
935
948
  ]
936
- cserial = compute_int_from_bytes(area[::-1])
949
+ cserial = int.from_bytes(area, byteorder="little")
937
950
  return cserial
938
951
 
939
952
  def write_cserial(self, serial: int) -> None:
@@ -998,7 +1011,7 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
998
1011
  The unit number of the subsystem.
999
1012
  """
1000
1013
  area = self._content[self._unit_index_start]
1001
- unit = compute_int_from_bytes([area])
1014
+ unit = int.from_bytes([area], byteorder="little")
1002
1015
  return unit
1003
1016
 
1004
1017
  @validated
@@ -1067,6 +1080,23 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
1067
1080
  def write_customer_area(self, data: bytes) -> None:
1068
1081
  """
1069
1082
  Write data to CCU EEPROM customer area.
1083
+
1084
+ Parameters
1085
+ ----------
1086
+ data
1087
+ The data to write to the customer area of the CCU EEPROM.
1088
+
1089
+ Raises
1090
+ ------
1091
+ ValueError
1092
+ If the data exceeds the customer area length.
1093
+
1094
+ Example
1095
+ -------
1096
+ >>> eeprom = EKF_CCU_EEPROM()
1097
+ >>> eeprom.write_customer_area(b"Hello, World!")
1098
+ >>> eeprom.customer_area()
1099
+ b'Hello, World!'
1070
1100
  """
1071
1101
  if len(data) > self._customer_area_length:
1072
1102
  raise ValueError("Data exceeds customer area length")
@@ -1,12 +1,15 @@
1
- from .generic import Device
2
- from smbus2 import SMBus
1
+ import struct
3
2
  from enum import Enum
4
- from typing import Tuple
3
+ from typing import Any, Tuple
4
+
5
+ from smbus2 import SMBus
6
+
5
7
  from ekfsm.core.components import SysTree
8
+
6
9
  from ..exceptions import AcquisitionError
7
10
  from ..lock import Locker
11
+ from .generic import Device
8
12
  from .imu import ImuSample
9
- import struct
10
13
 
11
14
 
12
15
  class CcuCommands(Enum):
@@ -37,10 +40,13 @@ class EKFCcuUc(Device):
37
40
  self,
38
41
  name: str,
39
42
  parent: SysTree | None,
43
+ children: list["Device"] | None = None,
44
+ abort: bool = False,
45
+ debug: Any = None, # XXX: What is this?
40
46
  *args,
41
47
  **kwargs,
42
48
  ):
43
- super().__init__(name, parent, None, *args, **kwargs)
49
+ super().__init__(name, parent, None, abort, *args, **kwargs)
44
50
  self._i2c_addr = self.get_i2c_chip_addr()
45
51
  self._i2c_bus = self.get_i2c_bus_number()
46
52
  self._smbus = SMBus(self._i2c_bus)
@@ -54,7 +60,10 @@ class EKFCcuUc(Device):
54
60
  def temperature(self) -> float:
55
61
  """
56
62
  Get the temperature from the CCU thermal/humidity sensor.
57
- The temperature is read once per second.
63
+
64
+ Note
65
+ ----
66
+ The CCU reads the temperature once per second.
58
67
 
59
68
  Returns
60
69
  -------
@@ -66,10 +75,7 @@ class EKFCcuUc(Device):
66
75
  AcquisitionError
67
76
  If the temperature cannot be read, for example, because the sensor is not working.
68
77
  """
69
- return (
70
- self._get_signed_word_data(CcuCommands.CCU_TEMPERATURE.value, "temperature")
71
- / 10.0
72
- )
78
+ return self._get_signed_word_data(CcuCommands.CCU_TEMPERATURE.value, "temperature") / 10.0
73
79
 
74
80
  def humidity(self) -> float:
75
81
  """
@@ -86,10 +92,7 @@ class EKFCcuUc(Device):
86
92
  AcquisitionError
87
93
  If the humidity cannot be read, for example, because the sensor is not working.
88
94
  """
89
- return (
90
- self._get_signed_word_data(CcuCommands.CCU_HUMIDITY.value, "humidity")
91
- / 10.0
92
- )
95
+ return self._get_signed_word_data(CcuCommands.CCU_HUMIDITY.value, "humidity") / 10.0
93
96
 
94
97
  def vin_voltage(self) -> float:
95
98
  """
@@ -106,15 +109,14 @@ class EKFCcuUc(Device):
106
109
  AcquisitionError
107
110
  If the voltage cannot be read, for example, because the ADC is not working.
108
111
  """
109
- return (
110
- self._get_signed_word_data(CcuCommands.VIN_VOLTAGE.value, "VIN voltage")
111
- / 10.0
112
- )
112
+ return self._get_signed_word_data(CcuCommands.VIN_VOLTAGE.value, "VIN voltage") / 10.0
113
113
 
114
114
  def _get_signed_word_data(self, cmd: int, what: str) -> int:
115
115
  v = self._smbus.read_word_data(self._i2c_addr, cmd)
116
+
116
117
  if v == 0x8000:
117
- raise AcquisitionError(f"cannot read {what}")
118
+ raise AcquisitionError(f"Cannot read {what}")
119
+
118
120
  return struct.unpack("<h", struct.pack("<H", v))[0]
119
121
 
120
122
  def fan_status(self, fan: int) -> Tuple[float, float, int]:
@@ -168,9 +170,7 @@ class EKFCcuUc(Device):
168
170
  if fan == -1:
169
171
  fan = 0xFF
170
172
  data = struct.pack("<Bh", fan, int(temp * 10))
171
- self._smbus.write_block_data(
172
- self._i2c_addr, CcuCommands.PUSH_TEMPERATURE.value, list(data)
173
- )
173
+ self._smbus.write_block_data(self._i2c_addr, CcuCommands.PUSH_TEMPERATURE.value, list(data))
174
174
 
175
175
  def imu_sample(self) -> Tuple[ImuSample | None, bool]:
176
176
  """
@@ -197,15 +197,11 @@ class EKFCcuUc(Device):
197
197
  True if more samples are available in the FIFO, False otherwise.
198
198
  """
199
199
  more_samples = False
200
- _data = self._smbus.read_block_data(
201
- self._i2c_addr, CcuCommands.IMU_SAMPLES.value
202
- )
200
+ _data = self._smbus.read_block_data(self._i2c_addr, CcuCommands.IMU_SAMPLES.value)
203
201
  data = bytes(_data)
204
202
  if len(data) < 14:
205
203
  return None, False # No data available
206
- diag, fsr, acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z = struct.unpack(
207
- "<BBhhhhhh", data
208
- )
204
+ diag, fsr, acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z = struct.unpack("<BBhhhhhh", data)
209
205
  imu_data = ImuSample(
210
206
  [
211
207
  self._scale_imu_accel(acc_x, fsr),
@@ -271,15 +267,11 @@ class EKFCcuUc(Device):
271
267
  version: str
272
268
  The firmware version.
273
269
  """
274
- title = bytes(
275
- self._smbus.read_block_data(
276
- self._i2c_addr, CcuCommands.IDENTIFY_FIRMWARE_TITLE.value
277
- )
278
- ).decode("utf-8")
270
+ title = bytes(self._smbus.read_block_data(self._i2c_addr, CcuCommands.IDENTIFY_FIRMWARE_TITLE.value)).decode(
271
+ "utf-8"
272
+ )
279
273
  version = bytes(
280
- self._smbus.read_block_data(
281
- self._i2c_addr, CcuCommands.IDENTIFY_FIRMWARE_VERSION.value
282
- )
274
+ self._smbus.read_block_data(self._i2c_addr, CcuCommands.IDENTIFY_FIRMWARE_VERSION.value)
283
275
  ).decode("utf-8")
284
276
  return title, version
285
277
 
@@ -304,27 +296,37 @@ class EKFCcuUc(Device):
304
296
  progress_callback
305
297
  A callback function that is called with the current progress in bytes.
306
298
 
299
+ Example
300
+ -------
301
+ >>> from ekfsm.devices import EkfCcuUc
302
+ >>> ccu = EkfCcuUc("ccu")
303
+ >>> firmware = open("fw-ccu-1.0.0.bin", "rb").read()
304
+ >>> # Load firmware with progress callback
305
+ >>> ccu.load_firmware(firmware, progress_callback=lambda x: print(f"Progress: {x} bytes"))
307
306
  """
308
307
  with Locker(self.name + "-load_firmware").lock():
309
308
  offset = 0
310
309
  max_chunk_len = 28
310
+
311
311
  while len(firmware) > 0:
312
312
  chunk, firmware = firmware[:max_chunk_len], firmware[max_chunk_len:]
313
313
  self._load_firmware_chunk(offset, len(firmware) == 0, chunk)
314
314
  offset += len(chunk)
315
+
315
316
  if len(firmware) != 0:
316
317
  self._nop()
318
+
317
319
  if progress_callback is not None:
318
320
  progress_callback(offset)
319
321
 
320
322
  def _load_firmware_chunk(self, offset: int, is_last: bool, data: bytes) -> None:
321
323
  if is_last:
322
324
  offset |= 0x80000000
325
+
323
326
  hdr = struct.pack("<I", offset)
324
327
  data = hdr + data
325
- self._smbus.write_block_data(
326
- self._i2c_addr, CcuCommands.LOAD_FIRMWARE_CHUNK.value, list(data)
327
- )
328
+
329
+ self._smbus.write_block_data(self._i2c_addr, CcuCommands.LOAD_FIRMWARE_CHUNK.value, list(data))
328
330
 
329
331
  def get_parameterset(self) -> str:
330
332
  """
@@ -382,6 +384,7 @@ class EKFCcuUc(Device):
382
384
  with Locker(self.name + "-parameterset").lock():
383
385
  json = b""
384
386
  begin = True
387
+
385
388
  while True:
386
389
  chunk = self._get_parameterset_chunk(begin)
387
390
  if len(chunk) < 32:
@@ -391,16 +394,13 @@ class EKFCcuUc(Device):
391
394
  chunk = chunk[:-1]
392
395
  json += chunk
393
396
  begin = False
397
+
394
398
  return json.decode("utf-8")
395
399
 
396
400
  def _get_parameterset_chunk(self, begin: bool) -> bytes:
397
401
  data = self._smbus.read_block_data(
398
402
  self._i2c_addr,
399
- (
400
- CcuCommands.GET_PARAMETERSET_BEGIN.value
401
- if begin
402
- else CcuCommands.GET_PARAMETERSET_FOLLOW.value
403
- ),
403
+ (CcuCommands.GET_PARAMETERSET_BEGIN.value if begin else CcuCommands.GET_PARAMETERSET_FOLLOW.value),
404
404
  )
405
405
  return bytes(data)
406
406
 
@@ -423,6 +423,8 @@ class EKFCcuUc(Device):
423
423
  This would load a parameterset with just one parameter, the default fan speed. All other parameters will
424
424
  be set to their default values.
425
425
 
426
+ Important
427
+ ---------
426
428
  In order to apply the parameterset, the CCU must be restarted.
427
429
 
428
430
  Parameters
@@ -430,11 +432,20 @@ class EKFCcuUc(Device):
430
432
  _cfg
431
433
  The parameterset in JSON format.
432
434
 
435
+ Example
436
+ -------
437
+ >>> from ekfsm.devices import EkfCcuUc
438
+ >>> ccu = EkfCcuUc("ccu")
439
+ >>> # Load parameterset
440
+ >>> ccu.load_parameterset('{"version": "1.0.0", "parameters": {"fan-defrpm": "6000"}}')
441
+ >>> # Restart CCU to apply parameterset
442
+ >>> ccu.restart()
433
443
  """
434
444
  with Locker(self.name + "-parameterset").lock():
435
445
  cfg = _cfg.encode("utf-8")
436
446
  offset = 0
437
447
  max_chunk_len = 28
448
+
438
449
  while len(cfg) > 0:
439
450
  chunk, cfg = cfg[:max_chunk_len], cfg[max_chunk_len:]
440
451
  self._load_parameterset_chunk(offset, len(cfg) == 0, chunk)
@@ -444,11 +455,11 @@ class EKFCcuUc(Device):
444
455
  def _load_parameterset_chunk(self, offset: int, is_last: bool, data: bytes) -> None:
445
456
  if is_last:
446
457
  offset |= 0x80000000
458
+
447
459
  hdr = struct.pack("<I", offset)
448
460
  data = hdr + data
449
- self._smbus.write_block_data(
450
- self._i2c_addr, CcuCommands.LOAD_PARAMETERSET.value, list(data)
451
- )
461
+
462
+ self._smbus.write_block_data(self._i2c_addr, CcuCommands.LOAD_PARAMETERSET.value, list(data))
452
463
 
453
464
  def restart(self) -> None:
454
465
  """
@@ -1,5 +1,7 @@
1
- from .gpio import GPIOExpander
2
1
  from ekfsm.core.components import SysTree
2
+ from ekfsm.devices.generic import Device
3
+
4
+ from .gpio import GPIOExpander
3
5
 
4
6
 
5
7
  class EKFSurLed(GPIOExpander):
@@ -11,10 +13,12 @@ class EKFSurLed(GPIOExpander):
11
13
  self,
12
14
  name: str,
13
15
  parent: SysTree | None,
16
+ children: list["Device"] | None = None,
17
+ abort: bool = False,
14
18
  *args,
15
19
  **kwargs,
16
20
  ):
17
- super().__init__(name, parent, None, *args, **kwargs)
21
+ super().__init__(name, parent, None, abort, *args, **kwargs)
18
22
 
19
23
  def __str__(self) -> str:
20
24
  return (