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
@@ -0,0 +1,251 @@
1
+ import threading
2
+ from typing import Callable
3
+ from ekfsm.devices.utils import retry
4
+ from io4edge_client.binaryiotypeb import Pb
5
+ from ekfsm.devices.generic import Device
6
+ from ekfsm.devices.io4edge import GPIOArray
7
+ from ekfsm.log import ekfsm_logger
8
+ import io4edge_client.functionblock as fb
9
+
10
+ logger = ekfsm_logger(__name__)
11
+
12
+
13
+ class Button(Device):
14
+ """
15
+ Device class for handling a single button as part on array.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ name: str,
21
+ parent: Device,
22
+ children: list[Device] | None = None,
23
+ abort: bool = False,
24
+ channel_id: int = 0,
25
+ *args,
26
+ **kwargs,
27
+ ):
28
+ logger.debug(f"Initializing Button '{name}' on channel {channel_id}")
29
+
30
+ super().__init__(name, parent, children, abort, *args, **kwargs)
31
+
32
+ self.channel_id = channel_id
33
+ logger.debug(f"Button '{name}' assigned to channel {channel_id}")
34
+
35
+ self._handler: Callable | None = None
36
+ logger.info(f"Button '{name}' initialized on channel {channel_id}")
37
+
38
+ @property
39
+ def handler(self):
40
+ """
41
+ Handle button events with a callback function.
42
+ """
43
+ return self._handler
44
+
45
+ @handler.setter
46
+ def handler(self, func: Callable | None, *args, **kwargs):
47
+ """
48
+ Handle button events with a callback function.
49
+
50
+ Parameters
51
+ ----------
52
+ func : Callable | None
53
+ The function to call on button events. If None, no function is called.
54
+ """
55
+ if callable(func):
56
+ self._handler = func
57
+ logger.info(
58
+ f"Handler set for button '{self.name}' on channel {self.channel_id}"
59
+ )
60
+ logger.debug(
61
+ f"Handler function: {func.__name__ if hasattr(func, '__name__') else str(func)}"
62
+ )
63
+ else:
64
+ self._handler = None
65
+ logger.debug(
66
+ f"Handler cleared for button '{self.name}' on channel {self.channel_id}"
67
+ )
68
+
69
+ def __repr__(self):
70
+ return f"{self.name}; Channel ID: {self.channel_id}"
71
+
72
+
73
+ class ButtonArray(Device):
74
+ """
75
+ Device class for handling an io4edge gpio based button array.
76
+
77
+ To read button events, call the `read` method in a separate thread.
78
+
79
+ Note
80
+ ----
81
+ Button handlers are called in the context of the `read` method's thread and need to be set in the Button instances.
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ name: str,
87
+ parent: GPIOArray,
88
+ children: list[Device] | None = None,
89
+ abort: bool = False,
90
+ keepaliveInterval: int = 10000,
91
+ *args,
92
+ **kwargs,
93
+ ):
94
+ logger.debug(
95
+ f"Initializing ButtonArray '{name}' with parent device {parent.deviceId}"
96
+ )
97
+
98
+ super().__init__(name, parent, children, abort, *args, **kwargs)
99
+
100
+ self.name = name
101
+
102
+ self.service_addr = parent.service_addr
103
+ self.client = parent.client
104
+
105
+ logger.info(
106
+ f"ButtonArray '{name}' configured with service address: {self.service_addr}"
107
+ )
108
+
109
+ self.subscriptionType = Pb.SubscriptionType.BINARYIOTYPEB_ON_RISING_EDGE
110
+ self.stream_cfg = fb.Pb.StreamControlStart(
111
+ bucketSamples=1, # 1 sample per bucket, also ein event pro bucket
112
+ keepaliveInterval=keepaliveInterval,
113
+ bufferedSamples=2, # 2 samples werden gepuffert
114
+ low_latency_mode=True, # schickt soweit moeglich sofort die Events
115
+ )
116
+ logger.debug(
117
+ "Stream configuration initialized with rising edge subscription and low latency mode"
118
+ )
119
+
120
+ # Log button children count
121
+ button_count = sum(1 for child in (children or []) if isinstance(child, Button))
122
+ logger.info(f"ButtonArray '{name}' initialized with {button_count} button(s)")
123
+
124
+ def read(self, stop_event: threading.Event | None = None, timeout: float = 1):
125
+ """
126
+ Read all button events and dispatch to handlers.
127
+
128
+ Parameters
129
+ ----------
130
+ stop_event : threading.Event, optional
131
+ Event to signal stopping the reading loop. If None, the loop will run indefinitely.
132
+ timeout : float, optional
133
+ Timeout for reading from the stream in seconds. Default is 0.1 seconds.
134
+
135
+ Note
136
+ ----
137
+ This method blocks and should be run in a separate thread.
138
+ """
139
+ button_channels = [
140
+ button for button in self.children if isinstance(button, Button)
141
+ ]
142
+
143
+ if not button_channels:
144
+ logger.warning(
145
+ f"No button children found in ButtonArray '{self.name}', read operation will have no effect"
146
+ )
147
+ return
148
+
149
+ logger.info(
150
+ f"Starting button event reading for {len(button_channels)} buttons on '{self.name}'"
151
+ )
152
+ logger.debug(
153
+ f"Read timeout: {timeout}s, stop_event provided: {stop_event is not None}"
154
+ )
155
+ # Prepare subscription channels
156
+ subscribe_channels = tuple(
157
+ Pb.SubscribeChannel(
158
+ channel=button.channel_id,
159
+ subscriptionType=self.subscriptionType,
160
+ )
161
+ for button in button_channels
162
+ )
163
+
164
+ channel_ids = [button.channel_id for button in button_channels]
165
+
166
+ try:
167
+ self._button_event_handling(
168
+ stop_event,
169
+ timeout,
170
+ subscribe_channels,
171
+ button_channels,
172
+ channel_ids,
173
+ )
174
+ except Exception as e:
175
+ logger.error(
176
+ f"Failed to establish connection or start stream for ButtonArray '{self.name}': {e}"
177
+ )
178
+ raise
179
+
180
+ @retry()
181
+ def _button_event_handling(
182
+ self,
183
+ stop_event: threading.Event | None,
184
+ timeout: float,
185
+ subscribe_channels: tuple,
186
+ button_channels: list,
187
+ channel_ids: list,
188
+ ):
189
+ with self.client as client:
190
+ logger.debug(f"IO4Edge client connected to service: {self.service_addr}")
191
+
192
+ logger.debug(
193
+ f"Subscribing to {len(subscribe_channels)} button channels: {channel_ids}"
194
+ )
195
+
196
+ client.start_stream(
197
+ Pb.StreamControlStart(subscribeChannel=subscribe_channels),
198
+ self.stream_cfg,
199
+ )
200
+ logger.info(f"Button event stream started for ButtonArray '{self.name}'")
201
+
202
+ event_count = 0
203
+ try:
204
+ while not (stop_event and stop_event.is_set()):
205
+ try:
206
+ _, samples = client.read_stream(timeout=timeout)
207
+
208
+ for sample in samples.samples:
209
+ for button in button_channels:
210
+ pressed = bool(sample.inputs & (1 << button.channel_id))
211
+ if pressed:
212
+ event_count += 1
213
+ button_name = getattr(button, "name", "unnamed")
214
+ logger.debug(
215
+ f"Button press on channel {button.channel_id} ({button_name})"
216
+ )
217
+
218
+ if button.handler:
219
+ try:
220
+ logger.debug(
221
+ f"Calling handler for button on channel {button.channel_id}"
222
+ )
223
+ button.handler()
224
+ except Exception as e:
225
+ logger.error(
226
+ f"Error in button handler for channel {button.channel_id}: {e}"
227
+ )
228
+ else:
229
+ logger.debug(
230
+ f"No handler set for button on channel {button.channel_id}"
231
+ )
232
+
233
+ except TimeoutError:
234
+ # Timeout is expected during normal operation
235
+ continue
236
+ except Exception as e:
237
+ logger.error(f"Error reading button events from stream: {e}")
238
+ break
239
+
240
+ except KeyboardInterrupt:
241
+ logger.info(f"Button reading interrupted for ButtonArray '{self.name}'")
242
+ finally:
243
+ logger.info(
244
+ f"Button event reading stopped for '{self.name}' after processing {event_count} events"
245
+ )
246
+ if stop_event:
247
+ stop_event.clear()
248
+ logger.debug("Stop event cleared")
249
+
250
+ def __repr__(self):
251
+ return f"{self.name}; Service Address: {self.service_addr}"
@@ -0,0 +1,110 @@
1
+ from ekfsm.devices.generic import Device
2
+ from ekfsm.devices.ledArray import LEDArray
3
+ from ekfsm.devices.utils import retry
4
+ from ekfsm.log import ekfsm_logger
5
+ from io4edge_client.api.colorLED.python.colorLED.v1alpha1.colorLED_pb2 import Color
6
+
7
+ logger = ekfsm_logger(__name__)
8
+
9
+
10
+ class ColorLED(Device):
11
+ """
12
+ Device class for handling a color LED.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ name: str,
18
+ parent: LEDArray,
19
+ children: list[Device] | None = None,
20
+ abort: bool = False,
21
+ channel_id: int = 0,
22
+ *args,
23
+ **kwargs,
24
+ ):
25
+ logger.debug(f"Initializing ColorLED '{name}' on channel {channel_id}")
26
+
27
+ super().__init__(name, parent, children, abort, *args, **kwargs)
28
+
29
+ self.name = name
30
+ self.channel_id = channel_id
31
+
32
+ self.client = parent.client
33
+ logger.info(
34
+ f"ColorLED '{name}' initialized on channel {channel_id} with parent LEDArray"
35
+ )
36
+
37
+ @retry()
38
+ def describe(self):
39
+ pass
40
+
41
+ @retry()
42
+ def get(self) -> tuple[Color, bool]:
43
+ """
44
+ Get color LED state.
45
+
46
+ Returns
47
+ -------
48
+ Current color and blink state.
49
+
50
+ Raises
51
+ ------
52
+ RuntimeError
53
+ if the command fails
54
+ TimeoutError
55
+ if the command times out
56
+ """
57
+ logger.info(
58
+ "Getting color LED state for '%s' on channel %s", self.name, self.channel_id
59
+ )
60
+ try:
61
+ result = self.client.get(self.channel_id)
62
+ color, blink = result
63
+ logger.info(
64
+ "ColorLED '%s' state: color=%s, blink=%s", self.name, color, blink
65
+ )
66
+ return result
67
+ except Exception as e:
68
+ logger.error(
69
+ "Failed to get ColorLED '%s' state on channel %s: %s",
70
+ self.name,
71
+ self.channel_id,
72
+ e,
73
+ )
74
+ raise
75
+
76
+ @retry()
77
+ def set(self, color: Color, blink: bool) -> None:
78
+ """
79
+ Set the color of the color LED.
80
+
81
+ Parameters
82
+ ----------
83
+ color : Color
84
+ The color to set the LED to.
85
+ blink : bool
86
+ Whether to blink the LED.
87
+
88
+ Raises
89
+ ------
90
+ RuntimeError
91
+ if the command fails
92
+ TimeoutError
93
+ if the command times out
94
+ """
95
+ logger.info(
96
+ f"Setting ColorLED '{self.name}' on channel {self.channel_id}: color={color}, blink={blink}"
97
+ )
98
+ try:
99
+ self.client.set(self.channel_id, color, blink)
100
+ logger.debug(
101
+ f"ColorLED '{self.name}' successfully set to color={color}, blink={blink}"
102
+ )
103
+ except Exception as e:
104
+ logger.error(
105
+ f"Failed to set ColorLED '{self.name}' on channel {self.channel_id}: {e}"
106
+ )
107
+ raise
108
+
109
+ def __repr__(self):
110
+ return f"{self.name}; Channel ID: {self.channel_id}"
ekfsm/devices/coretemp.py CHANGED
@@ -1,13 +1,18 @@
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
10
+ from ekfsm.log import ekfsm_logger
11
+
12
+ logger = ekfsm_logger(__name__)
8
13
 
9
14
  # Path to the root of the HWMON sysfs filesystem
10
- HWMON_ROOT = SYSFS_ROOT / Path("class/hwmon")
15
+ HWMON_ROOT = sysfs_root() / Path("class/hwmon")
11
16
 
12
17
 
13
18
  def find_core_temp_dir(hwmon_dir) -> Path:
@@ -38,28 +43,33 @@ def find_core_temp_dir(hwmon_dir) -> Path:
38
43
 
39
44
  class CoreTemp(Device):
40
45
  """
41
- A class to represent the HWMON device.
42
-
43
- A HWMON device is a virtual device that is used to read hardware monitoring values from the sysfs filesystem.
46
+ This class provides an interface to read the CPU core temperature from the HWMON device.
44
47
 
45
48
  Note:
46
- Currently, only the CPU temperature is read from the HWMON device.
49
+ Currently, only the average temperature over all cores is read from the HWMON device.
47
50
  """
48
51
 
49
52
  def __init__(
50
53
  self,
51
54
  name: str,
52
55
  parent: Device,
56
+ children: list["Device"] | None = None,
57
+ abort: bool = False,
53
58
  *args,
54
59
  **kwargs,
55
60
  ):
56
- from ekfsm.core.sysfs import SysFSDevice, SYSFS_ROOT
61
+ logger.debug(f"Initializing CoreTemp device '{name}'")
57
62
 
58
- self.sysfs_device: SysFSDevice = SysFSDevice(
59
- find_core_temp_dir(SYSFS_ROOT / Path("class/hwmon"))
60
- )
63
+ try:
64
+ dir = find_core_temp_dir(sysfs_root() / Path("class/hwmon"))
65
+ logger.debug(f"Found coretemp directory: {dir}")
66
+ self.sysfs_device = ekfsm.core.sysfs.SysfsDevice(dir, False)
67
+ logger.info(f"CoreTemp '{name}' initialized with sysfs device at {dir}")
68
+ except FileNotFoundError as e:
69
+ logger.error(f"Failed to initialize CoreTemp '{name}': {e}")
70
+ raise
61
71
 
62
- super().__init__(name, parent, None, *args, **kwargs)
72
+ super().__init__(name, parent, None, abort, *args, **kwargs)
63
73
 
64
74
  def cputemp(self):
65
75
  """
@@ -70,4 +80,16 @@ class CoreTemp(Device):
70
80
  int
71
81
  The CPU temperature in degrees Celsius.
72
82
  """
73
- return int(self.sysfs_device.read_attr_utf8("temp1_input").strip()) / 1000
83
+ logger.debug(f"Reading CPU temperature for CoreTemp '{self.name}'")
84
+ try:
85
+ temp_raw = self.sysfs.read_float("temp1_input")
86
+ temp_celsius = temp_raw / 1000
87
+ logger.debug(
88
+ f"CoreTemp '{self.name}' raw reading: {temp_raw}, temperature: {temp_celsius}°C"
89
+ )
90
+ return temp_celsius
91
+ except Exception as e:
92
+ logger.error(
93
+ f"Failed to read CPU temperature for CoreTemp '{self.name}': {e}"
94
+ )
95
+ raise
ekfsm/devices/eeprom.py CHANGED
@@ -11,20 +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
16
 
17
- from ekfsm.core.components import SysTree
18
- from ekfsm.core.probe import ProbeableDevice
19
-
20
- from .generic import Device
21
- from .utils import compute_int_from_bytes, get_crc16_xmodem
22
- from ekfsm.exceptions import DataCorruptionError
23
17
  from hexdump import hexdump
24
18
 
19
+ from ekfsm.core.components import SysTree
20
+ from ekfsm.core.probe import ProbeableDevice
21
+ from ekfsm.exceptions import DataCorruptionError, DriverError, SysFSError
25
22
  from ekfsm.log import ekfsm_logger
26
23
 
27
- __all__ = ["EEPROM", "Validatable_EEPROM", "EKF_EEPROM", "validated"]
24
+ from .generic import Device
25
+ from .utils import get_crc16_xmodem
28
26
 
29
27
  logger = ekfsm_logger(__name__)
30
28
 
@@ -82,13 +80,19 @@ class EEPROM(Device):
82
80
  self,
83
81
  name: str,
84
82
  parent: SysTree | None = None,
83
+ children: list[Device] | None = None,
84
+ abort: bool = False,
85
85
  *args,
86
86
  **kwargs,
87
87
  ):
88
- super().__init__(name, parent, None, *args, **kwargs)
88
+ super().__init__(name, parent, None, abort, *args, **kwargs)
89
89
 
90
90
  self.addr = self.get_i2c_chip_addr()
91
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
+
92
96
  self._update_content()
93
97
 
94
98
  def _update_content(self) -> None:
@@ -119,20 +123,10 @@ class EEPROM(Device):
119
123
 
120
124
  Raises
121
125
  ------
122
- FileNotFoundError
123
- If the EEPROM sysfs file is not found.
124
- RuntimeError
125
- If the sysfs device is not found.
126
+ SysFSError
127
+ No sysfs device found for EEPROM or `eeprom` attribute does not exist
126
128
  """
127
- try:
128
- if self.sysfs_device:
129
- cnt = self.sysfs_device.read_attr_bytes("eeprom")
130
- else:
131
- raise RuntimeError("No sysfs device for EEPROM")
132
- except FileNotFoundError:
133
- raise FileNotFoundError("EEPROM not found")
134
-
135
- return cnt
129
+ return self.read_sysfs_bytes("eeprom")
136
130
 
137
131
  def write(self, data: bytes, offset: int = 0) -> None:
138
132
  """
@@ -157,15 +151,27 @@ class EEPROM(Device):
157
151
  Note
158
152
  ----
159
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!
160
160
  """
161
- try:
162
- if self.sysfs_device:
163
- logger.info(f"Writing {len(data)} bytes to EEPROM at offset {offset}")
164
- self.sysfs_device.write_attr("eeprom", data, offset)
165
- else:
166
- raise RuntimeError("No sysfs device for EEPROM")
167
- except FileNotFoundError:
168
- 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")
169
175
 
170
176
  self._update_content()
171
177
  written = self._content[offset : offset + len(data)]
@@ -204,6 +210,10 @@ class Validatable_EEPROM(EEPROM, ABC):
204
210
 
205
211
  def __init__(
206
212
  self,
213
+ name: str,
214
+ parent: SysTree | None = None,
215
+ children: list[Device] | None = None,
216
+ abort: bool = False,
207
217
  crc_pos: Literal["start", "end"] = "end",
208
218
  crc_length: int = 2,
209
219
  *args,
@@ -212,7 +222,7 @@ class Validatable_EEPROM(EEPROM, ABC):
212
222
  self._crc_length: int = crc_length
213
223
  self._crc_pos: str = crc_pos
214
224
 
215
- super().__init__(*args, **kwargs)
225
+ super().__init__(name, parent, children, abort, *args, **kwargs)
216
226
 
217
227
  self._crc_pos_start = len(self._data) if self._crc_pos == "end" else 0
218
228
  self._crc_pos_end = self._crc_pos_start + self._crc_length
@@ -390,13 +400,16 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
390
400
 
391
401
  def __init__(
392
402
  self,
403
+ name: str,
404
+ parent: SysTree | None = None,
405
+ children: list[Device] | None = None,
406
+ abort: bool = False,
393
407
  *args,
394
408
  **kwargs,
395
409
  ) -> None:
396
- super().__init__(*args, **kwargs)
410
+ super().__init__(name, parent, children, abort, *args, **kwargs)
397
411
 
398
412
  def _update_content(self) -> None:
399
-
400
413
  super()._update_content()
401
414
 
402
415
  # EKF EEPROM content is restricted to 128 bytes, so strip the rest!
@@ -426,7 +439,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
426
439
  The serial number of the root device.
427
440
  """
428
441
  area = self._content[self._sernum_index_start : self._sernum_index_end]
429
- sernum = compute_int_from_bytes(area[::-1])
442
+ sernum = int.from_bytes(area, byteorder="little")
430
443
  return str(sernum)
431
444
 
432
445
  def write_serial(self, serial: int) -> None:
@@ -463,7 +476,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
463
476
  area = self._content[
464
477
  self._customer_serial_index_start : self._customer_serial_index_end
465
478
  ]
466
- sernum = compute_int_from_bytes(area[::-1])
479
+ sernum = int.from_bytes(area, byteorder="little")
467
480
  return str(sernum)
468
481
 
469
482
  def write_custom_serial(self, serial: int) -> None:
@@ -507,8 +520,8 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
507
520
  The date the device was manufactured.
508
521
  """
509
522
  area = self._content[self._date_mft_index_start : self._date_mft_index_end]
510
- encoded_mft_date = area[::-1]
511
- return self._decode_date(encoded_mft_date)
523
+ # encoded_mft_date = area[::-1]
524
+ return self._decode_date(area)
512
525
 
513
526
  @validated
514
527
  def repaired_at(self) -> date:
@@ -520,8 +533,8 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
520
533
  The most recent date the device was repaired.
521
534
  """
522
535
  area = self._content[self._date_rep_index_start : self._date_rep_index_end]
523
- encoded_rep_date = area[::-1]
524
- return self._decode_date(encoded_rep_date)
536
+ # encoded_rep_date = area[::-1]
537
+ return self._decode_date(area)
525
538
 
526
539
  @validated
527
540
  def write_repaired_at(self, date: date) -> None:
@@ -692,8 +705,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
692
705
  date
693
706
  The decoded date.
694
707
  """
695
- encoded_date = encoded_date[::-1]
696
- bdate = compute_int_from_bytes(encoded_date)
708
+ bdate = int.from_bytes(encoded_date, byteorder="little")
697
709
 
698
710
  # Extract the day (bit 0-4)
699
711
  day = bdate & 0x1F # 0x1F is 00011111 in binary (5 bits)
@@ -715,7 +727,7 @@ class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
715
727
  ) # Handle invalid dates, e.g., 30th Feb
716
728
 
717
729
  @classmethod
718
- def _encode_date(self, date: date) -> bytes:
730
+ def _encode_date(cls, date: date) -> bytes:
719
731
  """
720
732
  Encode a date into a proprietary 2-byte format.
721
733
 
@@ -774,7 +786,6 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
774
786
  super().__init__(*args, **kwargs)
775
787
 
776
788
  def _update_content(self) -> None:
777
-
778
789
  super()._update_content()
779
790
 
780
791
  # CCU content is the raw content area of the EEPROM
@@ -935,7 +946,7 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
935
946
  area = self._content[
936
947
  self._cserial_index_start : self._cserial_index_start + self._cserial_length
937
948
  ]
938
- cserial = compute_int_from_bytes(area[::-1])
949
+ cserial = int.from_bytes(area, byteorder="little")
939
950
  return cserial
940
951
 
941
952
  def write_cserial(self, serial: int) -> None:
@@ -1000,7 +1011,7 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
1000
1011
  The unit number of the subsystem.
1001
1012
  """
1002
1013
  area = self._content[self._unit_index_start]
1003
- unit = compute_int_from_bytes([area])
1014
+ unit = int.from_bytes([area], byteorder="little")
1004
1015
  return unit
1005
1016
 
1006
1017
  @validated
@@ -1069,6 +1080,23 @@ class EKF_CCU_EEPROM(EKF_EEPROM):
1069
1080
  def write_customer_area(self, data: bytes) -> None:
1070
1081
  """
1071
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!'
1072
1100
  """
1073
1101
  if len(data) > self._customer_area_length:
1074
1102
  raise ValueError("Data exceeds customer area length")