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