ekfsm 0.11.0b1.post3__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 +13 -0
- ekfsm/boards/oem/ekf/ccu.yaml +68 -0
- ekfsm/boards/oem/ekf/sc5-festival.yaml +30 -0
- ekfsm/boards/oem/ekf/sc9-toccata.yaml +31 -0
- ekfsm/boards/oem/ekf/spv-mystic.yaml +68 -0
- ekfsm/boards/oem/ekf/sq1-track.yaml +41 -0
- ekfsm/boards/oem/ekf/srf-fan.yaml +48 -0
- ekfsm/boards/oem/ekf/sur-uart.yaml +72 -0
- ekfsm/boards/oem/hitron/hdrc-300.yaml +20 -0
- ekfsm/cli.py +111 -0
- ekfsm/config.py +37 -0
- ekfsm/core/__init__.py +4 -0
- ekfsm/core/components.py +120 -0
- ekfsm/core/probe.py +10 -0
- ekfsm/core/slots.py +201 -0
- ekfsm/core/sysfs.py +91 -0
- ekfsm/core/utils.py +77 -0
- ekfsm/devices/__init__.py +28 -0
- ekfsm/devices/eeprom.py +1054 -0
- ekfsm/devices/ekf_ccu_uc.py +390 -0
- ekfsm/devices/ekf_sur_led.py +67 -0
- ekfsm/devices/generic.py +245 -0
- ekfsm/devices/gpio.py +340 -0
- ekfsm/devices/hwmon.py +71 -0
- ekfsm/devices/iio.py +58 -0
- ekfsm/devices/iio_thermal_humidity.py +41 -0
- ekfsm/devices/mux.py +39 -0
- ekfsm/devices/pmbus.py +65 -0
- ekfsm/devices/smbios.py +38 -0
- ekfsm/devices/utils.py +16 -0
- ekfsm/exceptions.py +58 -0
- ekfsm/log.py +28 -0
- ekfsm/py.typed +2 -0
- ekfsm/simctrl.py +241 -0
- ekfsm/system.py +326 -0
- ekfsm-0.11.0b1.post3.dist-info/METADATA +86 -0
- ekfsm-0.11.0b1.post3.dist-info/RECORD +39 -0
- ekfsm-0.11.0b1.post3.dist-info/WHEEL +4 -0
- ekfsm-0.11.0b1.post3.dist-info/entry_points.txt +2 -0
ekfsm/devices/eeprom.py
ADDED
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module containing classes to represent EEPROM devices.
|
|
3
|
+
|
|
4
|
+
Routine Listings
|
|
5
|
+
----------------
|
|
6
|
+
:py:class:`EEPROM`
|
|
7
|
+
:py:class:`Validatable_EEPROM`
|
|
8
|
+
:py:class:`EKF_EEPROM`
|
|
9
|
+
:py:class:`validated`
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from datetime import date
|
|
14
|
+
from typing import Any, Callable, Literal, Sequence
|
|
15
|
+
from functools import wraps
|
|
16
|
+
|
|
17
|
+
from ekfsm.core.components import SystemComponent
|
|
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
|
+
from hexdump import hexdump
|
|
24
|
+
|
|
25
|
+
from ekfsm.log import ekfsm_logger
|
|
26
|
+
|
|
27
|
+
__all__ = ["EEPROM", "Validatable_EEPROM", "EKF_EEPROM", "validated"]
|
|
28
|
+
|
|
29
|
+
logger = ekfsm_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validated(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
33
|
+
"""
|
|
34
|
+
A decorator to validate the CRC of the EEPROM content before executing a method.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
func
|
|
39
|
+
The method to validate.
|
|
40
|
+
|
|
41
|
+
Note
|
|
42
|
+
----
|
|
43
|
+
This decorator should be used on methods that read data from an EEPROM.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@wraps(func)
|
|
47
|
+
def validate(self, *args, **kwargs):
|
|
48
|
+
logger.debug(f"Validating EEPROM content for {self.name}")
|
|
49
|
+
if not self.valid:
|
|
50
|
+
raise DataCorruptionError("CRC validation failed")
|
|
51
|
+
logger.debug(f"EEPROM content is valid for {self.name}")
|
|
52
|
+
return func(self, *args, **kwargs)
|
|
53
|
+
|
|
54
|
+
return validate
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class EEPROM(Device):
|
|
58
|
+
"""
|
|
59
|
+
A class used to represent a generic EEPROM device.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
name
|
|
64
|
+
The name of the EEPROM device.
|
|
65
|
+
parent
|
|
66
|
+
The parent device of the EEPROM in the :py:class:`~.generic.Device` tree.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Caution
|
|
70
|
+
-------
|
|
71
|
+
The following conditions must be met for this class to work properly:
|
|
72
|
+
- EEPROM must be I2C accessable
|
|
73
|
+
- EEPROM must have a sysfs device
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
Note
|
|
77
|
+
----
|
|
78
|
+
This class should be inherited by classes representing specific EEPROM devices and defining custom storage schemes.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
name: str,
|
|
84
|
+
parent: SystemComponent | None = None,
|
|
85
|
+
*args,
|
|
86
|
+
**kwargs,
|
|
87
|
+
):
|
|
88
|
+
super().__init__(name, parent, None, *args, **kwargs)
|
|
89
|
+
|
|
90
|
+
self.addr = self.get_i2c_chip_addr()
|
|
91
|
+
self.sysfs_device = self.get_i2c_sysfs_device(self.addr)
|
|
92
|
+
self._update_content()
|
|
93
|
+
|
|
94
|
+
def _update_content(self) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Update the content of the EEPROM device.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
Note
|
|
100
|
+
----
|
|
101
|
+
- This method should be called whenever the content of the EEPROM is updated (after each write op).
|
|
102
|
+
- Inheriting classes should call this method before updating their own attributes.
|
|
103
|
+
"""
|
|
104
|
+
logger.debug("Reading data")
|
|
105
|
+
try:
|
|
106
|
+
data = self.read()
|
|
107
|
+
self._content = data
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Error reading data, {e}")
|
|
110
|
+
self._content = b""
|
|
111
|
+
|
|
112
|
+
def read(self) -> bytes:
|
|
113
|
+
"""
|
|
114
|
+
Read the content of the EEPROM.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
The content of the EEPROM.
|
|
119
|
+
|
|
120
|
+
Raises
|
|
121
|
+
------
|
|
122
|
+
FileNotFoundError
|
|
123
|
+
If the EEPROM sysfs file is not found.
|
|
124
|
+
RuntimeError
|
|
125
|
+
If the sysfs device is not found.
|
|
126
|
+
"""
|
|
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
|
|
136
|
+
|
|
137
|
+
def write(self, data: bytes, offset: int = 0) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Write data to the EEPROM.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
data
|
|
144
|
+
The data to write to the EEPROM.
|
|
145
|
+
offset
|
|
146
|
+
The offset at which to start writing the data.
|
|
147
|
+
|
|
148
|
+
Raises
|
|
149
|
+
------
|
|
150
|
+
RuntimeError
|
|
151
|
+
If the sysfs device is not found.
|
|
152
|
+
FileNotFoundError
|
|
153
|
+
If the EEPROM sysfs file is not found.
|
|
154
|
+
DataCorruptionError
|
|
155
|
+
If an error occurs during the write operation.
|
|
156
|
+
|
|
157
|
+
Note
|
|
158
|
+
----
|
|
159
|
+
Operation is checked for data corruption by reading back the written data.
|
|
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")
|
|
169
|
+
|
|
170
|
+
self._update_content()
|
|
171
|
+
written = self._content[offset : offset + len(data)]
|
|
172
|
+
if not written == data:
|
|
173
|
+
raise DataCorruptionError(
|
|
174
|
+
"Error during EEPROM write, data is not the same as read back"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def print(self):
|
|
178
|
+
hexdump(self._content)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class Validatable_EEPROM(EEPROM, ABC):
|
|
182
|
+
"""
|
|
183
|
+
Abstract class used to represent an EEPROM device using CRC to validate its content.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
crc_pos
|
|
188
|
+
The position of the CRC value in the EEPROM content (`'start'` or `'end'`).
|
|
189
|
+
crc_length
|
|
190
|
+
The length of the CRC value in number of bytes (defaults to 2).
|
|
191
|
+
|
|
192
|
+
Note
|
|
193
|
+
----
|
|
194
|
+
- Derived classes must implement a method to compute the CRC value of the EEPROM content.
|
|
195
|
+
- If the CRC position differs from the shipped schema `('start' | 'end')`,
|
|
196
|
+
the derived class must override the :meth:`~Validatable_EEPROM._update_content` method.
|
|
197
|
+
- Validity of individual content fields stored/returned by attributes, methods or properties
|
|
198
|
+
can be achieved by using the :py:func:`validated` decorator.
|
|
199
|
+
|
|
200
|
+
See Also
|
|
201
|
+
--------
|
|
202
|
+
Validatable_EEPROM._compute_crc : Method to compute the CRC value of the EEPROM content.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
crc_pos: Literal["start", "end"] = "end",
|
|
208
|
+
crc_length: int = 2,
|
|
209
|
+
*args,
|
|
210
|
+
**kwargs,
|
|
211
|
+
) -> None:
|
|
212
|
+
self._crc_length: int = crc_length
|
|
213
|
+
self._crc_pos: str = crc_pos
|
|
214
|
+
|
|
215
|
+
super().__init__(*args, **kwargs)
|
|
216
|
+
|
|
217
|
+
self._crc_pos_start = len(self._data) if self._crc_pos == "end" else 0
|
|
218
|
+
self._crc_pos_end = self._crc_pos_start + self._crc_length
|
|
219
|
+
|
|
220
|
+
def _update_content(self) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Update the content of the EEPROM device (checksum excluded).
|
|
223
|
+
"""
|
|
224
|
+
super()._update_content()
|
|
225
|
+
|
|
226
|
+
# Firmware data without CRC
|
|
227
|
+
self._data: bytes = (
|
|
228
|
+
self._content[self._crc_length :]
|
|
229
|
+
if self._crc_pos == "start"
|
|
230
|
+
else self._content[: -self._crc_length :]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def _update_crc(self) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Update the CRC value of the EEPROM content.
|
|
236
|
+
"""
|
|
237
|
+
self.crc = self._compute_crc()
|
|
238
|
+
|
|
239
|
+
def _get_crc_value(self) -> int:
|
|
240
|
+
return int.from_bytes(
|
|
241
|
+
self._content[self._crc_pos_start : self._crc_pos_end], byteorder="little"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def crc(self) -> int:
|
|
246
|
+
"""
|
|
247
|
+
Gets or sets the CRC value of the EEPROM content.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
value: optional
|
|
252
|
+
The CRC value to write to the EEPROM.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
int
|
|
257
|
+
The CRC value currently stored in the EEPROM if used as *getter*.
|
|
258
|
+
None
|
|
259
|
+
If used as *setter*.
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
Caution
|
|
263
|
+
-------
|
|
264
|
+
The *setter* actually writes the CRC value to the EEPROM.
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
Warning
|
|
268
|
+
-------
|
|
269
|
+
The *setter* method should be used with caution as it can lead to data corruption if the CRC value is not correct!
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
Note
|
|
273
|
+
----
|
|
274
|
+
The *setter* is usually triggered automatically after a successful write operation
|
|
275
|
+
and in most cases, there is no need to call it manually.
|
|
276
|
+
"""
|
|
277
|
+
return self._get_crc_value()
|
|
278
|
+
|
|
279
|
+
@crc.setter
|
|
280
|
+
def crc(self, value: int) -> None:
|
|
281
|
+
crc_bytes = value.to_bytes(self._crc_length, byteorder="little")
|
|
282
|
+
logger.debug(f"Writing CRC value {value} to EEPROM")
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
super().write(crc_bytes, self._crc_pos_start)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Error writing CRC value to EEPROM, {e}")
|
|
288
|
+
|
|
289
|
+
# self._update_content()
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def valid(self) -> bool:
|
|
293
|
+
"""
|
|
294
|
+
Checks if the EEPROM content is valid by comparing the stored CRC value with the computed CRC value.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
bool
|
|
299
|
+
`True` if the EEPROM content is valid, `False` otherwise.
|
|
300
|
+
"""
|
|
301
|
+
return self._compute_crc() == self.crc
|
|
302
|
+
|
|
303
|
+
@abstractmethod
|
|
304
|
+
def _compute_crc(self) -> int:
|
|
305
|
+
"""
|
|
306
|
+
This method should be implemented by derived classes to compute the CRC value of the EEPROM content.
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
The computed CRC value.
|
|
311
|
+
"""
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
def write(self, data: bytes, offset: int = 0) -> None:
|
|
315
|
+
try:
|
|
316
|
+
super().write(data, offset)
|
|
317
|
+
self._update_crc()
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Error writing to EEPROM, {e}")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class EKF_EEPROM(Validatable_EEPROM, ProbeableDevice):
|
|
323
|
+
"""
|
|
324
|
+
A class used to represent an EKF EEPROM device.
|
|
325
|
+
|
|
326
|
+
Structure
|
|
327
|
+
---------
|
|
328
|
+
The EKF_EEPROM content is structured as follows:
|
|
329
|
+
|
|
330
|
+
- `Serial number` (4 bytes, starts at pos 8):
|
|
331
|
+
The serial number of the device.
|
|
332
|
+
- `Manufactured at` (2 bytes, starts at pos 12):
|
|
333
|
+
The date the device was manufactured.
|
|
334
|
+
- `Repaired at` (2 bytes, starts at pos 14):
|
|
335
|
+
The date the device was repaired.
|
|
336
|
+
- `Customer serial number` (4 bytes, starts at pos 32):
|
|
337
|
+
The customer serial number of the device.
|
|
338
|
+
- `Customer configuration block offset pointer` (4 bytes, starts at pos 36):
|
|
339
|
+
The offset pointer to the customer configuration block.
|
|
340
|
+
- `String array` (78 bytes, starts at pos 48):
|
|
341
|
+
An array of strings containing the model, manufacturer, and custom board data of the device.
|
|
342
|
+
- `CRC` (2 bytes, starts at pos 126):
|
|
343
|
+
The CRC value of the EEPROM content.
|
|
344
|
+
- `Raw content` (80 bytes, starts at pos 128):
|
|
345
|
+
Free customizable content for other purposes.
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
Note
|
|
349
|
+
----
|
|
350
|
+
- As the CRC value is stored at the end of the OEM data space, just before the customer configuration block,
|
|
351
|
+
the CRC position is manually set and the :meth:`~ekfsm.devices.eeprom.Validatable_EEPROM._update_content` method is
|
|
352
|
+
overridden.
|
|
353
|
+
- The CRC value is computed using the `CRC-16/XMODEM <https://en.wikipedia.org/wiki/Cyclic_redundancy_check>`_ algorithm.
|
|
354
|
+
- Dates are stored in a proprietary format (2 bytes) and must be decoded using the :meth:`~EKF_EEPROM._decode_date` method.
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
See Also
|
|
358
|
+
--------
|
|
359
|
+
`crcmod <https://crcmod.sourceforge.net/>`_
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
Important
|
|
363
|
+
---------
|
|
364
|
+
All data read from the EEPROM should be validated using the @validated decorator.
|
|
365
|
+
This decorator ensures that the data is not corrupted by checking the CRC value.
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
Raises
|
|
369
|
+
------
|
|
370
|
+
DataCorruptionError
|
|
371
|
+
If the CRC validation fails.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
_sernum_index_start = 8
|
|
375
|
+
_sernum_index_end = 12
|
|
376
|
+
|
|
377
|
+
_date_mft_index_start = 12
|
|
378
|
+
_date_mft_index_end = 14
|
|
379
|
+
|
|
380
|
+
_date_rep_index_start = 14
|
|
381
|
+
_date_rep_index_end = 16
|
|
382
|
+
|
|
383
|
+
_customer_serial_index_start = 32
|
|
384
|
+
_customer_serial_index_end = 36
|
|
385
|
+
|
|
386
|
+
_customer_config_block_offset_pointer_index = 36
|
|
387
|
+
|
|
388
|
+
_str_array_start_offset = 48
|
|
389
|
+
_str_array_end_offset = 126
|
|
390
|
+
|
|
391
|
+
def __init__(
|
|
392
|
+
self,
|
|
393
|
+
*args,
|
|
394
|
+
**kwargs,
|
|
395
|
+
) -> None:
|
|
396
|
+
super().__init__(*args, **kwargs)
|
|
397
|
+
|
|
398
|
+
def _update_content(self) -> None:
|
|
399
|
+
|
|
400
|
+
super()._update_content()
|
|
401
|
+
|
|
402
|
+
# EKF EEPROM content is restricted to 128 bytes, so strip the rest!
|
|
403
|
+
self._firmware_content = self._content[:128]
|
|
404
|
+
|
|
405
|
+
# The rest is raw content available for other purposes
|
|
406
|
+
self._raw_content: bytes = self._content[128:]
|
|
407
|
+
|
|
408
|
+
# Firmware data without CRC needs to be overriden
|
|
409
|
+
self._data: bytes = (
|
|
410
|
+
self._firmware_content[self._crc_length :]
|
|
411
|
+
if self._crc_pos == "start"
|
|
412
|
+
else self._firmware_content[: -self._crc_length :]
|
|
413
|
+
)
|
|
414
|
+
self._str_list = self._get_string_array()
|
|
415
|
+
|
|
416
|
+
self._crc_pos_start = len(self._data)
|
|
417
|
+
self._crc_pos_end = self._crc_pos_start + self._crc_length
|
|
418
|
+
|
|
419
|
+
@validated
|
|
420
|
+
def serial(self) -> str:
|
|
421
|
+
"""
|
|
422
|
+
Get the serial number of the device to which the EEPROM is attached (the root device).
|
|
423
|
+
|
|
424
|
+
Returns
|
|
425
|
+
-------
|
|
426
|
+
The serial number of the root device.
|
|
427
|
+
"""
|
|
428
|
+
area = self._content[self._sernum_index_start : self._sernum_index_end]
|
|
429
|
+
sernum = compute_int_from_bytes(area[::-1])
|
|
430
|
+
return str(sernum)
|
|
431
|
+
|
|
432
|
+
def write_serial(self, serial: int) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Write serial number of the root device to EEPROM.
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
serial
|
|
439
|
+
The serial number to write to the EEPROM.
|
|
440
|
+
"""
|
|
441
|
+
# Check serial number is within bounds
|
|
442
|
+
unsigned_upper_bound = (2**32) - 1
|
|
443
|
+
if serial < 0 or serial > unsigned_upper_bound:
|
|
444
|
+
raise ValueError(
|
|
445
|
+
f"Serial number must be between 0 and {unsigned_upper_bound}"
|
|
446
|
+
)
|
|
447
|
+
serial_bytes = serial.to_bytes(4, byteorder="little")
|
|
448
|
+
self.write(serial_bytes, self._sernum_index_start)
|
|
449
|
+
|
|
450
|
+
@validated
|
|
451
|
+
def custom_serial(self) -> str:
|
|
452
|
+
"""
|
|
453
|
+
Get the customer serial number of the device to which the EEPROM is attached (the root device).
|
|
454
|
+
|
|
455
|
+
Attention
|
|
456
|
+
---------
|
|
457
|
+
This is a custom - non-OEM - serial number that can be set by the user.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
The customer serial number of the root device.
|
|
462
|
+
"""
|
|
463
|
+
area = self._content[
|
|
464
|
+
self._customer_serial_index_start : self._customer_serial_index_end
|
|
465
|
+
]
|
|
466
|
+
sernum = compute_int_from_bytes(area[::-1])
|
|
467
|
+
return str(sernum)
|
|
468
|
+
|
|
469
|
+
def write_custom_serial(self, serial: int) -> None:
|
|
470
|
+
"""
|
|
471
|
+
Write customer serial number of the root device to EEPROM.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
serial
|
|
476
|
+
The customer serial number to write to the EEPROM.
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
Raises
|
|
480
|
+
------
|
|
481
|
+
ValueError
|
|
482
|
+
If the serial number is not within the bounds of a 32-bit unsigned integer.
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
Note
|
|
486
|
+
----
|
|
487
|
+
Due to space restrictions on storage, the serial number must be a 32-bit unsigned integer.
|
|
488
|
+
"""
|
|
489
|
+
# Check serial number is within bounds
|
|
490
|
+
unsigned_upper_bound = (2**32) - 1
|
|
491
|
+
if serial < 0 or serial > unsigned_upper_bound:
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Serial number must be between 0 and {unsigned_upper_bound}"
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
serial_bytes = serial.to_bytes(4, byteorder="little")
|
|
497
|
+
logger.debug(f"Writing customer serial {serial}")
|
|
498
|
+
self.write(serial_bytes, self._customer_serial_index_start)
|
|
499
|
+
|
|
500
|
+
@validated
|
|
501
|
+
def manufactured_at(self) -> date:
|
|
502
|
+
"""
|
|
503
|
+
Get the date the device was manufactured.
|
|
504
|
+
|
|
505
|
+
Returns
|
|
506
|
+
-------
|
|
507
|
+
The date the device was manufactured.
|
|
508
|
+
"""
|
|
509
|
+
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)
|
|
512
|
+
|
|
513
|
+
@validated
|
|
514
|
+
def repaired_at(self) -> date:
|
|
515
|
+
"""
|
|
516
|
+
Get the date the device was repaired.
|
|
517
|
+
|
|
518
|
+
Returns
|
|
519
|
+
-------
|
|
520
|
+
The most recent date the device was repaired.
|
|
521
|
+
"""
|
|
522
|
+
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)
|
|
525
|
+
|
|
526
|
+
@validated
|
|
527
|
+
def write_repaired_at(self, date: date) -> None:
|
|
528
|
+
"""
|
|
529
|
+
Write the date the device was repaired to EEPROM.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
date
|
|
534
|
+
The date the device was repaired.
|
|
535
|
+
|
|
536
|
+
Note
|
|
537
|
+
----
|
|
538
|
+
The date year must be within the range of 1980-2079.
|
|
539
|
+
|
|
540
|
+
Attention
|
|
541
|
+
---------
|
|
542
|
+
The date is stored in a proprietary 2-byte format.
|
|
543
|
+
|
|
544
|
+
Raises
|
|
545
|
+
------
|
|
546
|
+
ValueError
|
|
547
|
+
If the year is not within the range of 1980-2079.
|
|
548
|
+
"""
|
|
549
|
+
if date.year < 1980 or date.year > 2079:
|
|
550
|
+
raise ValueError("Year must be within the range of 1980-2079")
|
|
551
|
+
rep_date_bytes = self._encode_date(date)
|
|
552
|
+
logger.debug(f"Writing repair date {date} to EEPROM")
|
|
553
|
+
self.write(rep_date_bytes, self._date_rep_index_start)
|
|
554
|
+
|
|
555
|
+
@validated
|
|
556
|
+
def model(self) -> str | None:
|
|
557
|
+
"""
|
|
558
|
+
Get the model name of the device to which the EEPROM is attached to (the root device).
|
|
559
|
+
|
|
560
|
+
Returns
|
|
561
|
+
-------
|
|
562
|
+
The model name of the device.
|
|
563
|
+
"""
|
|
564
|
+
return self._str_list[0] if len(self._str_list) > 0 else None
|
|
565
|
+
|
|
566
|
+
@validated
|
|
567
|
+
def vendor(self) -> str | None:
|
|
568
|
+
"""
|
|
569
|
+
Get the vendor/manufacturer of the device to which the EEPROM is attached to (the root device).
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
The name of the vendor/manufacturer of the device.
|
|
574
|
+
"""
|
|
575
|
+
return self._str_list[1] if len(self._str_list) > 1 else None
|
|
576
|
+
|
|
577
|
+
@validated
|
|
578
|
+
def custom_board_data(self) -> str | None:
|
|
579
|
+
"""
|
|
580
|
+
Get the custom board data of the device.
|
|
581
|
+
|
|
582
|
+
Note
|
|
583
|
+
----
|
|
584
|
+
This is a custom field that can be set by the user.
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
Attention
|
|
588
|
+
---------
|
|
589
|
+
This field is optional and may not be present in the EEPROM content.
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
Returns
|
|
593
|
+
-------
|
|
594
|
+
The custom board data of the device as a string, or `None` if the field is not present.
|
|
595
|
+
"""
|
|
596
|
+
return None if len(self._str_list) < 3 else self._str_list[2]
|
|
597
|
+
|
|
598
|
+
def write_custom_board_data(self, data: str) -> None:
|
|
599
|
+
"""
|
|
600
|
+
Write custom board data to EEPROM.
|
|
601
|
+
|
|
602
|
+
Important
|
|
603
|
+
---------
|
|
604
|
+
Due to size limitations, the custom board data should only contain expressive,
|
|
605
|
+
short content like serials, variants or specific codes.
|
|
606
|
+
|
|
607
|
+
Parameters
|
|
608
|
+
----------
|
|
609
|
+
data
|
|
610
|
+
The custom board data to write to the EEPROM.
|
|
611
|
+
|
|
612
|
+
Attention
|
|
613
|
+
---------
|
|
614
|
+
The model and vendor fields are mandatory and must be set before writing custom board data.
|
|
615
|
+
|
|
616
|
+
Raises
|
|
617
|
+
------
|
|
618
|
+
ValueError
|
|
619
|
+
If the model and vendor fields are not set before writing custom board data.
|
|
620
|
+
"""
|
|
621
|
+
data_bytes = data.encode("utf-8")
|
|
622
|
+
data_offset = 0
|
|
623
|
+
for s in self._str_list[:2]:
|
|
624
|
+
if s is None:
|
|
625
|
+
raise ValueError(
|
|
626
|
+
"Model and vendor fields must be set before writing custom board data"
|
|
627
|
+
)
|
|
628
|
+
if isinstance(s, str):
|
|
629
|
+
data_offset += len(s) + 1
|
|
630
|
+
logger.info(f"Writing custom board data {data} to EEPROM")
|
|
631
|
+
self.write(data_bytes, self._str_array_start_offset + data_offset)
|
|
632
|
+
|
|
633
|
+
def custom_raw_data(self) -> bytes:
|
|
634
|
+
"""
|
|
635
|
+
Get the raw content area data stored in the EEPROM.
|
|
636
|
+
|
|
637
|
+
Returns
|
|
638
|
+
-------
|
|
639
|
+
The data contained in the raw content block of the EEPROM.
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
Note
|
|
643
|
+
----
|
|
644
|
+
This area is free for custom data storage and is not included during crc calculations and validations.
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
Important
|
|
648
|
+
---------
|
|
649
|
+
If custom raw data should be stored on EEPROM and
|
|
650
|
+
if it should be protected against corruption, it has to be validated manually.
|
|
651
|
+
"""
|
|
652
|
+
return self._raw_content
|
|
653
|
+
|
|
654
|
+
def write_custom_raw_data(self, data: bytes) -> None:
|
|
655
|
+
"""
|
|
656
|
+
Write custom data to the raw content area of the EEPROM.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
data
|
|
661
|
+
The data to write to the raw content area of the EEPROM.
|
|
662
|
+
"""
|
|
663
|
+
logger.info(f"Writing {len(data)} bytes to raw content area of EEPROM")
|
|
664
|
+
self.write(data, 128)
|
|
665
|
+
|
|
666
|
+
def _get_string_array(self) -> list[str | None]:
|
|
667
|
+
str_array = self._content[
|
|
668
|
+
self._str_array_start_offset : self._str_array_end_offset
|
|
669
|
+
].split(b"\x00")
|
|
670
|
+
try:
|
|
671
|
+
return [s.decode("utf-8") for s in str_array if s]
|
|
672
|
+
except UnicodeDecodeError:
|
|
673
|
+
return [None, None, None]
|
|
674
|
+
|
|
675
|
+
@classmethod
|
|
676
|
+
def _decode_date(cls, encoded_date: Sequence[int]) -> date:
|
|
677
|
+
"""
|
|
678
|
+
Decode a date from a proprietary 2-byte format.
|
|
679
|
+
|
|
680
|
+
Parameters
|
|
681
|
+
----------
|
|
682
|
+
encoded_date
|
|
683
|
+
The date to decode.
|
|
684
|
+
|
|
685
|
+
Raises
|
|
686
|
+
------
|
|
687
|
+
ValueError
|
|
688
|
+
If the date is invalid (e.g., 30th Feb).
|
|
689
|
+
|
|
690
|
+
Returns
|
|
691
|
+
-------
|
|
692
|
+
date
|
|
693
|
+
The decoded date.
|
|
694
|
+
"""
|
|
695
|
+
encoded_date = encoded_date[::-1]
|
|
696
|
+
bdate = compute_int_from_bytes(encoded_date)
|
|
697
|
+
|
|
698
|
+
# Extract the day (bit 0-4)
|
|
699
|
+
day = bdate & 0x1F # 0x1F is 00011111 in binary (5 bits)
|
|
700
|
+
|
|
701
|
+
# Extract the month (bit 5-8)
|
|
702
|
+
month = (bdate >> 5) & 0x0F # Shift right by 5 and mask with 0x0F (4 bits)
|
|
703
|
+
|
|
704
|
+
# Extract the year since 1980 (bit 9-15)
|
|
705
|
+
year = (bdate >> 9) & 0x7F # Shift right by 9 and mask with 0x7F (7 bits)
|
|
706
|
+
year += 1980 # Add base year (1980)
|
|
707
|
+
|
|
708
|
+
# Return a datetime object with the extracted year, month, and day
|
|
709
|
+
try:
|
|
710
|
+
decoded_date = date(year, month, day)
|
|
711
|
+
return decoded_date
|
|
712
|
+
except ValueError:
|
|
713
|
+
raise ValueError(
|
|
714
|
+
f"Invalid date: {day}/{month}/{year}"
|
|
715
|
+
) # Handle invalid dates, e.g., 30th Feb
|
|
716
|
+
|
|
717
|
+
@classmethod
|
|
718
|
+
def _encode_date(self, date: date) -> bytes:
|
|
719
|
+
"""
|
|
720
|
+
Encode a date into a proprietary 2-byte format.
|
|
721
|
+
|
|
722
|
+
Parameters
|
|
723
|
+
----------
|
|
724
|
+
date
|
|
725
|
+
The date to encode.
|
|
726
|
+
|
|
727
|
+
Returns
|
|
728
|
+
-------
|
|
729
|
+
bytes
|
|
730
|
+
The encoded date.
|
|
731
|
+
"""
|
|
732
|
+
year = date.year - 1980
|
|
733
|
+
month = date.month
|
|
734
|
+
day = date.day
|
|
735
|
+
|
|
736
|
+
encoded_date = year << 9 | month << 5 | day
|
|
737
|
+
return encoded_date.to_bytes(2, byteorder="little")
|
|
738
|
+
|
|
739
|
+
def _compute_crc(self) -> int:
|
|
740
|
+
return get_crc16_xmodem(self._data)
|
|
741
|
+
|
|
742
|
+
def probe(self, *args, **kwargs):
|
|
743
|
+
return self.root.id == self.model()
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
class EKF_CCU_EEPROM(EKF_EEPROM):
|
|
747
|
+
"""
|
|
748
|
+
EKF CCU EEPROM - uses the second part of the EEPROM for chassis inventory and customer area
|
|
749
|
+
"""
|
|
750
|
+
|
|
751
|
+
_cvendor_index_start = 128
|
|
752
|
+
_cvendor_length = 24
|
|
753
|
+
|
|
754
|
+
_cmodel_index_start = 152
|
|
755
|
+
_cmodel_length = 24
|
|
756
|
+
|
|
757
|
+
_crevision_index_start = 176
|
|
758
|
+
_crevision_length = 9
|
|
759
|
+
|
|
760
|
+
_cserial_index_start = 185
|
|
761
|
+
_cserial_length = 4
|
|
762
|
+
|
|
763
|
+
_unit_index_start = 189
|
|
764
|
+
_unit_length = 1
|
|
765
|
+
|
|
766
|
+
_customer_area_start = 190
|
|
767
|
+
_customer_area_length = 64
|
|
768
|
+
|
|
769
|
+
def __init__(
|
|
770
|
+
self,
|
|
771
|
+
*args,
|
|
772
|
+
**kwargs,
|
|
773
|
+
):
|
|
774
|
+
super().__init__(*args, **kwargs)
|
|
775
|
+
|
|
776
|
+
def _update_content(self) -> None:
|
|
777
|
+
|
|
778
|
+
super()._update_content()
|
|
779
|
+
|
|
780
|
+
# CCU content is the raw content area of the EEPROM
|
|
781
|
+
self._ccu_content: bytes = self._raw_content
|
|
782
|
+
|
|
783
|
+
# CCU Firmware data without CRC needs to be overriden
|
|
784
|
+
self._cdata: bytes = (
|
|
785
|
+
self._ccu_content[self._crc_length :]
|
|
786
|
+
if self._crc_pos == "start"
|
|
787
|
+
else self._ccu_content[: -self._crc_length :]
|
|
788
|
+
)
|
|
789
|
+
self._ccrc_pos_start = len(self._cdata) + 128
|
|
790
|
+
self._ccrc_pos_end = self._ccrc_pos_start + self._crc_length
|
|
791
|
+
|
|
792
|
+
def _update_ccrc(self) -> None:
|
|
793
|
+
"""
|
|
794
|
+
Update the CRC value of the EEPROM content.
|
|
795
|
+
"""
|
|
796
|
+
self.ccrc = self._compute_ccrc()
|
|
797
|
+
|
|
798
|
+
def _get_ccrc_value(self) -> int:
|
|
799
|
+
return int.from_bytes(
|
|
800
|
+
self._content[self._ccrc_pos_start : self._ccrc_pos_end], byteorder="little"
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
@property
|
|
804
|
+
def ccrc(self) -> int:
|
|
805
|
+
"""
|
|
806
|
+
Gets or sets the CRC value of the EEPROM content.
|
|
807
|
+
|
|
808
|
+
Parameters
|
|
809
|
+
----------
|
|
810
|
+
value: optional
|
|
811
|
+
The CRC value to write to the EEPROM.
|
|
812
|
+
|
|
813
|
+
Returns
|
|
814
|
+
-------
|
|
815
|
+
int
|
|
816
|
+
The CRC value currently stored in the EEPROM if used as *getter*.
|
|
817
|
+
None
|
|
818
|
+
If used as *setter*.
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
Caution
|
|
822
|
+
-------
|
|
823
|
+
The *setter* actually writes the CRC value to the EEPROM.
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
Warning
|
|
827
|
+
-------
|
|
828
|
+
The *setter* method should be used with caution as it can lead to data corruption if the CRC value is not correct!
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
Note
|
|
832
|
+
----
|
|
833
|
+
The *setter* is usually triggered automatically after a successful write operation
|
|
834
|
+
and in most cases, there is no need to call it manually.
|
|
835
|
+
"""
|
|
836
|
+
return self._get_ccrc_value()
|
|
837
|
+
|
|
838
|
+
@ccrc.setter
|
|
839
|
+
def ccrc(self, value: int) -> None:
|
|
840
|
+
ccrc_bytes = value.to_bytes(self._crc_length, byteorder="little")
|
|
841
|
+
logger.debug(f"Writing chassis CRC value {value}")
|
|
842
|
+
try:
|
|
843
|
+
super(Validatable_EEPROM, self).write(ccrc_bytes, self._ccrc_pos_start)
|
|
844
|
+
except Exception as e:
|
|
845
|
+
logger.error(f"Error writing CRC value, error: {e}")
|
|
846
|
+
|
|
847
|
+
@property
|
|
848
|
+
def valid(self) -> bool:
|
|
849
|
+
"""
|
|
850
|
+
Checks if the EEPROM content is valid by comparing the stored CRC value with the computed CRC value.
|
|
851
|
+
|
|
852
|
+
Returns
|
|
853
|
+
-------
|
|
854
|
+
bool
|
|
855
|
+
`True` if the EEPROM content is valid, `False` otherwise.
|
|
856
|
+
"""
|
|
857
|
+
return self._compute_ccrc() == self.ccrc and super().valid
|
|
858
|
+
|
|
859
|
+
@validated
|
|
860
|
+
def cvendor(self) -> str:
|
|
861
|
+
"""
|
|
862
|
+
Get the chassis vendor.
|
|
863
|
+
|
|
864
|
+
Returns
|
|
865
|
+
-------
|
|
866
|
+
The vendor of the chassis.
|
|
867
|
+
"""
|
|
868
|
+
return (
|
|
869
|
+
self._content[
|
|
870
|
+
self._cvendor_index_start : self._cvendor_index_start
|
|
871
|
+
+ self._cvendor_length
|
|
872
|
+
]
|
|
873
|
+
.strip(b"\x00")
|
|
874
|
+
.decode("utf-8")
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
def write_cvendor(self, vendor: str) -> None:
|
|
878
|
+
"""
|
|
879
|
+
Write the vendor of the chassis to EEPROM.
|
|
880
|
+
|
|
881
|
+
Parameters
|
|
882
|
+
----------
|
|
883
|
+
vendor
|
|
884
|
+
The vendor of the chassis.
|
|
885
|
+
"""
|
|
886
|
+
vendor_bytes = vendor.encode("utf-8")
|
|
887
|
+
vendor_fill = b"\x00" * (self._cvendor_length - len(vendor_bytes))
|
|
888
|
+
vendor_bytes += vendor_fill
|
|
889
|
+
logger.info(f"Writing vendor {vendor}")
|
|
890
|
+
self.write(vendor_bytes, self._cvendor_index_start)
|
|
891
|
+
|
|
892
|
+
@validated
|
|
893
|
+
def cmodel(self) -> str:
|
|
894
|
+
"""
|
|
895
|
+
Get the chassis model.
|
|
896
|
+
|
|
897
|
+
Returns
|
|
898
|
+
-------
|
|
899
|
+
The model of the chassis.
|
|
900
|
+
"""
|
|
901
|
+
return (
|
|
902
|
+
self._content[
|
|
903
|
+
self._cmodel_index_start : self._cmodel_index_start
|
|
904
|
+
+ self._cmodel_length
|
|
905
|
+
]
|
|
906
|
+
.strip(b"\x00")
|
|
907
|
+
.decode("utf-8")
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
def write_cmodel(self, model: str) -> None:
|
|
911
|
+
"""
|
|
912
|
+
Write the model of the chassis to EEPROM.
|
|
913
|
+
|
|
914
|
+
Parameters
|
|
915
|
+
----------
|
|
916
|
+
model
|
|
917
|
+
The model of the chassis.
|
|
918
|
+
"""
|
|
919
|
+
model_bytes = model.encode("utf-8")
|
|
920
|
+
model_fill = b"\x00" * (self._cmodel_length - len(model_bytes))
|
|
921
|
+
model_bytes += model_fill
|
|
922
|
+
logger.info(f"Writing model {model}")
|
|
923
|
+
self.write(model_bytes, self._cmodel_index_start)
|
|
924
|
+
|
|
925
|
+
@validated
|
|
926
|
+
def cserial(self) -> int:
|
|
927
|
+
"""
|
|
928
|
+
Get the chassis serial number.
|
|
929
|
+
|
|
930
|
+
Returns
|
|
931
|
+
-------
|
|
932
|
+
The serial number of the chassis.
|
|
933
|
+
"""
|
|
934
|
+
area = self._content[
|
|
935
|
+
self._cserial_index_start : self._cserial_index_start + self._cserial_length
|
|
936
|
+
]
|
|
937
|
+
cserial = compute_int_from_bytes(area[::-1])
|
|
938
|
+
return cserial
|
|
939
|
+
|
|
940
|
+
def write_cserial(self, serial: int) -> None:
|
|
941
|
+
"""
|
|
942
|
+
Write the serial number of the chassis to EEPROM.
|
|
943
|
+
|
|
944
|
+
Parameters
|
|
945
|
+
----------
|
|
946
|
+
serial
|
|
947
|
+
The serial number of the chassis.
|
|
948
|
+
"""
|
|
949
|
+
# Check serial number is within bounds
|
|
950
|
+
unsigned_upper_bound = (2**32) - 1
|
|
951
|
+
if serial < 0 or serial > unsigned_upper_bound:
|
|
952
|
+
raise ValueError(
|
|
953
|
+
f"Serial number must be between 0 and {unsigned_upper_bound}"
|
|
954
|
+
)
|
|
955
|
+
serial_bytes = serial.to_bytes(4, byteorder="little")
|
|
956
|
+
logger.info(f"Writing chassis serial {serial}")
|
|
957
|
+
self.write(serial_bytes, self._cserial_index_start)
|
|
958
|
+
|
|
959
|
+
@validated
|
|
960
|
+
def crevision(self) -> str:
|
|
961
|
+
"""
|
|
962
|
+
Get the revision of the chassis.
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
The revision of the chassis.
|
|
967
|
+
"""
|
|
968
|
+
return (
|
|
969
|
+
self._content[
|
|
970
|
+
self._crevision_index_start : self._crevision_index_start
|
|
971
|
+
+ self._crevision_length
|
|
972
|
+
]
|
|
973
|
+
.strip(b"\x00")
|
|
974
|
+
.decode("utf-8")
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def write_crevision(self, revision: str) -> None:
|
|
978
|
+
"""
|
|
979
|
+
Write the chassis revision.
|
|
980
|
+
|
|
981
|
+
Parameters
|
|
982
|
+
----------
|
|
983
|
+
revision
|
|
984
|
+
The revision of the chassis.
|
|
985
|
+
"""
|
|
986
|
+
revision_bytes = revision.encode("utf-8")
|
|
987
|
+
revision_fill = b"\x00" * (self._crevision_length - len(revision_bytes))
|
|
988
|
+
revision_bytes += revision_fill
|
|
989
|
+
logger.info(f"Writing chassis revision {revision}")
|
|
990
|
+
self.write(revision_bytes, self._crevision_index_start)
|
|
991
|
+
|
|
992
|
+
@validated
|
|
993
|
+
def unit(self) -> int:
|
|
994
|
+
"""
|
|
995
|
+
Get the subsystem unit number.
|
|
996
|
+
|
|
997
|
+
Returns
|
|
998
|
+
-------
|
|
999
|
+
The unit number of the subsystem.
|
|
1000
|
+
"""
|
|
1001
|
+
area = self._content[self._unit_index_start]
|
|
1002
|
+
unit = compute_int_from_bytes([area])
|
|
1003
|
+
return unit
|
|
1004
|
+
|
|
1005
|
+
def write_unit(self, unit: int) -> None:
|
|
1006
|
+
"""
|
|
1007
|
+
Write the subsystem unit number.
|
|
1008
|
+
|
|
1009
|
+
Parameters
|
|
1010
|
+
----------
|
|
1011
|
+
unit
|
|
1012
|
+
The unit number of the subsystem.
|
|
1013
|
+
"""
|
|
1014
|
+
unit_bytes = unit.to_bytes(1, byteorder="little")
|
|
1015
|
+
logger.info(f"Writing unit {unit}")
|
|
1016
|
+
self.write(unit_bytes, self._unit_index_start)
|
|
1017
|
+
|
|
1018
|
+
@validated
|
|
1019
|
+
def customer_area(self) -> bytes:
|
|
1020
|
+
"""
|
|
1021
|
+
Get the customer area of the CCU EEPROM.
|
|
1022
|
+
|
|
1023
|
+
Returns
|
|
1024
|
+
-------
|
|
1025
|
+
The customer area of the CCU EEPROM.
|
|
1026
|
+
"""
|
|
1027
|
+
return self._content[
|
|
1028
|
+
self._customer_area_start : self._customer_area_start
|
|
1029
|
+
+ self._customer_area_length
|
|
1030
|
+
]
|
|
1031
|
+
|
|
1032
|
+
def write_customer_area(self, data: bytes) -> None:
|
|
1033
|
+
"""
|
|
1034
|
+
Write data to CCU EEPROM customer area.
|
|
1035
|
+
"""
|
|
1036
|
+
if len(data) > self._customer_area_length:
|
|
1037
|
+
raise ValueError("Data exceeds customer area length")
|
|
1038
|
+
self.write(data, self._customer_area_start)
|
|
1039
|
+
|
|
1040
|
+
def _compute_ccrc(self) -> int:
|
|
1041
|
+
return get_crc16_xmodem(self._cdata)
|
|
1042
|
+
|
|
1043
|
+
def write(self, data: bytes, offset: int = 0) -> None:
|
|
1044
|
+
try:
|
|
1045
|
+
super(Validatable_EEPROM, self).write(data, offset)
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
logger.error(f"Error writing to EEPROM, {e}")
|
|
1048
|
+
self._update_ccrc()
|
|
1049
|
+
|
|
1050
|
+
def custom_raw_data(self) -> bytes:
|
|
1051
|
+
raise NotImplementedError("CCU EEPROM does not have a raw content area")
|
|
1052
|
+
|
|
1053
|
+
def write_custom_raw_data(self, data: bytes) -> None:
|
|
1054
|
+
raise NotImplementedError("CCU EEPROM does not have a raw content area")
|