ekfsm 1.3.0a26__py3-none-any.whl → 1.4.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/boards/oem/ekf/sq3-quartet.yaml +51 -37
- ekfsm/boards/oem/ekf/z1010.yaml +94 -0
- ekfsm/core/components.py +3 -4
- ekfsm/core/connections.py +19 -0
- ekfsm/core/slots.py +7 -2
- ekfsm/core/sysfs.py +32 -2
- ekfsm/core/utils.py +34 -10
- ekfsm/devices/__init__.py +16 -0
- ekfsm/devices/button.py +65 -0
- ekfsm/devices/buttonArray.py +206 -0
- ekfsm/devices/colorLed.py +101 -0
- ekfsm/devices/coretemp.py +26 -3
- ekfsm/devices/eeprom.py +4 -4
- ekfsm/devices/generic.py +41 -12
- ekfsm/devices/io4edge.py +110 -0
- ekfsm/devices/ledArray.py +54 -0
- ekfsm/devices/pixelDisplay.py +137 -0
- ekfsm/devices/pmbus.py +8 -4
- ekfsm/devices/smbios.py +20 -2
- ekfsm/devices/thermal_humidity.py +78 -0
- ekfsm/devices/watchdog.py +76 -0
- ekfsm/system.py +41 -12
- {ekfsm-1.3.0a26.dist-info → ekfsm-1.4.0.dist-info}/METADATA +5 -2
- {ekfsm-1.3.0a26.dist-info → ekfsm-1.4.0.dist-info}/RECORD +26 -16
- {ekfsm-1.3.0a26.dist-info → ekfsm-1.4.0.dist-info}/WHEEL +0 -0
- {ekfsm-1.3.0a26.dist-info → ekfsm-1.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,40 +2,54 @@ id: 69
|
|
|
2
2
|
name: "EKF SQ3-QUARTET"
|
|
3
3
|
slot_type: CPCI_S0_PER
|
|
4
4
|
children:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
5
|
+
- device_type: IO4Edge
|
|
6
|
+
name: "BMC"
|
|
7
|
+
children:
|
|
8
|
+
- device_type: PixelDisplay
|
|
9
|
+
name: "display"
|
|
10
|
+
- device_type: ButtonArray
|
|
11
|
+
name: "buttons"
|
|
12
|
+
children:
|
|
13
|
+
- device_type: Button
|
|
14
|
+
name: "up"
|
|
15
|
+
channel_id: 0
|
|
16
|
+
- device_type: Button
|
|
17
|
+
name: "down"
|
|
18
|
+
channel_id: 1
|
|
19
|
+
- device_type: I2CMux
|
|
20
|
+
name: "MUX"
|
|
21
|
+
addr: 0x70
|
|
22
|
+
slot_coding_mask: 0x07
|
|
23
|
+
children:
|
|
24
|
+
- device_type: MuxChannel
|
|
25
|
+
name: "CH00"
|
|
26
|
+
channel_id: 0
|
|
27
|
+
children:
|
|
28
|
+
- device_type: EKFIdentificationIOExpander
|
|
29
|
+
name: "GPIO"
|
|
30
|
+
addr: 0x3D
|
|
31
|
+
provides:
|
|
32
|
+
inventory:
|
|
33
|
+
- revision
|
|
34
|
+
- device_type: EKF_EEPROM
|
|
35
|
+
name: "EEPROM"
|
|
36
|
+
addr: 0x55
|
|
37
|
+
provides:
|
|
38
|
+
inventory:
|
|
39
|
+
- vendor
|
|
40
|
+
- serial
|
|
41
|
+
- model
|
|
42
|
+
- repaired_at
|
|
43
|
+
- manufactured_at
|
|
44
|
+
- device_type: MuxChannel
|
|
45
|
+
name: "CH01"
|
|
46
|
+
channel_id: 1
|
|
47
|
+
children:
|
|
48
|
+
- device_type: MuxChannel
|
|
49
|
+
name: "CH02"
|
|
50
|
+
channel_id: 2
|
|
51
|
+
children:
|
|
52
|
+
- device_type: MuxChannel
|
|
53
|
+
name: "CH03"
|
|
54
|
+
channel_id: 3
|
|
55
|
+
children:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
id: 112
|
|
2
|
+
name: "EKF Z1010"
|
|
3
|
+
slot_type: CPCI_S0_UTILITY
|
|
4
|
+
children:
|
|
5
|
+
- device_type: I2CMux
|
|
6
|
+
name: "MUX"
|
|
7
|
+
addr: 0x70
|
|
8
|
+
slot_coding_mask: 0x0
|
|
9
|
+
children:
|
|
10
|
+
- device_type: MuxChannel
|
|
11
|
+
name: "CH00"
|
|
12
|
+
channel_id: 0
|
|
13
|
+
children:
|
|
14
|
+
- device_type: EKFIdentificationIOExpander
|
|
15
|
+
name: "GPIO"
|
|
16
|
+
addr: 0x3D
|
|
17
|
+
provides:
|
|
18
|
+
inventory:
|
|
19
|
+
- revision
|
|
20
|
+
- device_type: EKF_EEPROM
|
|
21
|
+
name: "EEPROM"
|
|
22
|
+
addr: 0x55
|
|
23
|
+
provides:
|
|
24
|
+
inventory:
|
|
25
|
+
- vendor
|
|
26
|
+
- serial
|
|
27
|
+
- model
|
|
28
|
+
- repaired_at
|
|
29
|
+
- manufactured_at
|
|
30
|
+
# chassis_inventory:
|
|
31
|
+
# - revision: crevision
|
|
32
|
+
# - write_revision: write_crevision
|
|
33
|
+
# - vendor: cvendor
|
|
34
|
+
# - write_vendor: write_cvendor
|
|
35
|
+
# - serial: cserial
|
|
36
|
+
# - write_serial: write_cserial
|
|
37
|
+
# - model: cmodel
|
|
38
|
+
# - write_model: write_cmodel
|
|
39
|
+
# - unit
|
|
40
|
+
# - write_unit
|
|
41
|
+
# custom_eeprom:
|
|
42
|
+
# - write: write_customer_area
|
|
43
|
+
# - read: customer_area
|
|
44
|
+
- device_type: IO4Edge
|
|
45
|
+
name: "I4E"
|
|
46
|
+
provides:
|
|
47
|
+
management:
|
|
48
|
+
- identify_firmware
|
|
49
|
+
- load_firmware
|
|
50
|
+
- load_parameter
|
|
51
|
+
- get_parameter
|
|
52
|
+
- restart
|
|
53
|
+
children:
|
|
54
|
+
- device_type: LEDArray
|
|
55
|
+
name: "leds"
|
|
56
|
+
children:
|
|
57
|
+
- device_type: ColorLED
|
|
58
|
+
name: "led0"
|
|
59
|
+
channel_id: 0
|
|
60
|
+
- device_type: ColorLED
|
|
61
|
+
name: "led1"
|
|
62
|
+
channel_id: 1
|
|
63
|
+
- device_type: ColorLED
|
|
64
|
+
name: "led2"
|
|
65
|
+
channel_id: 2
|
|
66
|
+
- device_type: ColorLED
|
|
67
|
+
name: "led3"
|
|
68
|
+
channel_id: 3
|
|
69
|
+
- device_type: ColorLED
|
|
70
|
+
name: "led4"
|
|
71
|
+
channel_id: 4
|
|
72
|
+
- device_type: ColorLED
|
|
73
|
+
name: "led5"
|
|
74
|
+
channel_id: 5
|
|
75
|
+
- device_type: ColorLED
|
|
76
|
+
name: "led6"
|
|
77
|
+
channel_id: 6
|
|
78
|
+
- device_type: ColorLED
|
|
79
|
+
name: "led7"
|
|
80
|
+
channel_id: 7
|
|
81
|
+
- device_type: Watchdog
|
|
82
|
+
name: "watchdog"
|
|
83
|
+
provides:
|
|
84
|
+
sysstate:
|
|
85
|
+
- kick
|
|
86
|
+
- device_type: ThermalHumidity
|
|
87
|
+
name: "th"
|
|
88
|
+
- device_type: ButtonArray
|
|
89
|
+
name: "buttons"
|
|
90
|
+
service_suffix: "gpios"
|
|
91
|
+
children:
|
|
92
|
+
- device_type: Button
|
|
93
|
+
name: "eject"
|
|
94
|
+
channel_id: 2
|
ekfsm/core/components.py
CHANGED
|
@@ -51,7 +51,7 @@ class HWModule(SysTree):
|
|
|
51
51
|
*args,
|
|
52
52
|
**kwargs,
|
|
53
53
|
) -> None:
|
|
54
|
-
from ekfsm.core.utils import deserialize_hardware_tree
|
|
54
|
+
from ekfsm.core.utils import deserialize_hardware_tree, deserialize_module
|
|
55
55
|
|
|
56
56
|
from .slots import SlotType
|
|
57
57
|
|
|
@@ -59,9 +59,8 @@ class HWModule(SysTree):
|
|
|
59
59
|
self._slot: Slot = slot
|
|
60
60
|
self.config = config
|
|
61
61
|
|
|
62
|
-
self.id, self.board_type, slot_type
|
|
63
|
-
|
|
64
|
-
)
|
|
62
|
+
self.id, self.board_type, slot_type = deserialize_module(self.logger, config)
|
|
63
|
+
self.children = deserialize_hardware_tree(self.logger, self.config, parent=self)
|
|
65
64
|
|
|
66
65
|
self.slot_type = SlotType.from_string(slot_type)
|
|
67
66
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Connectable:
|
|
5
|
+
|
|
6
|
+
def __init__(self, client=None):
|
|
7
|
+
self._client = client
|
|
8
|
+
self._connected = False
|
|
9
|
+
|
|
10
|
+
@contextmanager
|
|
11
|
+
def connect(self):
|
|
12
|
+
if not self._connected:
|
|
13
|
+
client = self._client(self.service_addr, command_timeout=self.timeout)
|
|
14
|
+
self._connected = True
|
|
15
|
+
try:
|
|
16
|
+
yield client
|
|
17
|
+
finally:
|
|
18
|
+
client.close()
|
|
19
|
+
del client
|
ekfsm/core/slots.py
CHANGED
|
@@ -102,7 +102,9 @@ class Slot:
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
def __repr__(self) -> str:
|
|
105
|
-
return
|
|
105
|
+
return (
|
|
106
|
+
f"{self.__class__.__name__}(name={self._name}, slot_type={self.slot_type})"
|
|
107
|
+
)
|
|
106
108
|
|
|
107
109
|
@property
|
|
108
110
|
def name(self) -> str:
|
|
@@ -123,7 +125,10 @@ class Slot:
|
|
|
123
125
|
"""
|
|
124
126
|
Return True if the slot is populated with the desired hardware module type, False otherwise.
|
|
125
127
|
"""
|
|
126
|
-
return
|
|
128
|
+
return (
|
|
129
|
+
self.hwmodule is not None
|
|
130
|
+
and self.hwmodule.board_type.lower() == self._desired_hwmodule_type.lower()
|
|
131
|
+
)
|
|
127
132
|
|
|
128
133
|
|
|
129
134
|
class Slots(Munch):
|
ekfsm/core/sysfs.py
CHANGED
|
@@ -166,7 +166,7 @@ class SysfsDevice(MutableMapping):
|
|
|
166
166
|
|
|
167
167
|
def read_int(self, attr) -> int:
|
|
168
168
|
"""
|
|
169
|
-
Read a sysfs attribute as an integer
|
|
169
|
+
Read a sysfs attribute stored as a string as an integer
|
|
170
170
|
|
|
171
171
|
Parameters
|
|
172
172
|
----------
|
|
@@ -186,7 +186,7 @@ class SysfsDevice(MutableMapping):
|
|
|
186
186
|
"""
|
|
187
187
|
try:
|
|
188
188
|
value = self.read_attr_utf8(attr).strip()
|
|
189
|
-
return int(value
|
|
189
|
+
return int(value)
|
|
190
190
|
except StopIteration as e:
|
|
191
191
|
raise SysFSError(f"'{attr}' sysfs attribute does not exist") from e
|
|
192
192
|
except SysFSError:
|
|
@@ -194,6 +194,36 @@ class SysfsDevice(MutableMapping):
|
|
|
194
194
|
except ValueError as e:
|
|
195
195
|
raise ConversionError("Failed to convert sysfs value to int") from e
|
|
196
196
|
|
|
197
|
+
def read_hex(self, attr) -> int:
|
|
198
|
+
"""
|
|
199
|
+
Read a sysfs attribute stored as a hexadecimal integer as integer
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
attr: str
|
|
204
|
+
The sysfs attribute to read
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
The sysfs attribute as a hexadecimal integer
|
|
209
|
+
|
|
210
|
+
Raises
|
|
211
|
+
------
|
|
212
|
+
SysFSError
|
|
213
|
+
If the sysfs attribute does not exist
|
|
214
|
+
ConversionError
|
|
215
|
+
If the sysfs attribute could not be converted to a hexadecimal integer
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
value = self.read_attr_utf8(attr).strip()
|
|
219
|
+
return int(value, 16)
|
|
220
|
+
except StopIteration as e:
|
|
221
|
+
raise SysFSError(f"'{attr}' sysfs attribute does not exist") from e
|
|
222
|
+
except SysFSError:
|
|
223
|
+
raise
|
|
224
|
+
except ValueError as e:
|
|
225
|
+
raise ConversionError("Failed to convert sysfs value to hex int") from e
|
|
226
|
+
|
|
197
227
|
def read_utf8(self, attr, strip=True) -> str:
|
|
198
228
|
"""
|
|
199
229
|
Read a sysfs attribute as a UTF-8 encoded string
|
ekfsm/core/utils.py
CHANGED
|
@@ -55,10 +55,14 @@ def import_board(logger: logging.Logger, data, parent=None, abort: bool = False)
|
|
|
55
55
|
import_board(logger, data=child, parent=node, abort=abort)
|
|
56
56
|
except Exception as e:
|
|
57
57
|
if abort:
|
|
58
|
-
logger.error(
|
|
58
|
+
logger.error(
|
|
59
|
+
f"Failed to import sub device {pformat(child)}: {e}. Aborting."
|
|
60
|
+
)
|
|
59
61
|
raise e
|
|
60
62
|
else:
|
|
61
|
-
logger.error(
|
|
63
|
+
logger.error(
|
|
64
|
+
f"Failed to import sub device {pformat(child)}: {e}. Continuing anyway."
|
|
65
|
+
)
|
|
62
66
|
return node
|
|
63
67
|
|
|
64
68
|
|
|
@@ -69,7 +73,9 @@ def provides_validator(x: Any) -> Any:
|
|
|
69
73
|
key, value = next(iter(x.items()))
|
|
70
74
|
if isinstance(key, str) and isinstance(value, str):
|
|
71
75
|
return x
|
|
72
|
-
raise SchemaError(
|
|
76
|
+
raise SchemaError(
|
|
77
|
+
"Each provides item must be either a string or a dictionary with one string key/value pair"
|
|
78
|
+
)
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
device_schema = Schema({})
|
|
@@ -81,6 +87,8 @@ _device_structure = Schema(
|
|
|
81
87
|
Optional("addr"): int,
|
|
82
88
|
Optional("slot_coding_mask"): int,
|
|
83
89
|
Optional("channel_id"): int,
|
|
90
|
+
Optional("service_suffix"): str,
|
|
91
|
+
Optional("keepaliveInterval"): int,
|
|
84
92
|
Optional("provides"): {
|
|
85
93
|
Optional(str): [Use(provides_validator)],
|
|
86
94
|
},
|
|
@@ -103,30 +111,46 @@ module_schema = Schema(
|
|
|
103
111
|
)
|
|
104
112
|
|
|
105
113
|
|
|
114
|
+
def deserialize_module(logger: logging.Logger, data: dict) -> tuple[str, str, str]:
|
|
115
|
+
"""
|
|
116
|
+
docstring
|
|
117
|
+
"""
|
|
118
|
+
module_schema.validate(data)
|
|
119
|
+
|
|
120
|
+
id, name, slot_type = (data[key] for key in ["id", "name", "slot_type"])
|
|
121
|
+
logger.debug(colored(f"Importing top level module {pformat(name)}", "green"))
|
|
122
|
+
|
|
123
|
+
return id, name, slot_type
|
|
124
|
+
|
|
125
|
+
|
|
106
126
|
def deserialize_hardware_tree(
|
|
107
127
|
logger: logging.Logger, data: dict, parent: "HWModule"
|
|
108
|
-
) ->
|
|
128
|
+
) -> list["Device"]:
|
|
109
129
|
abort = parent.abort
|
|
110
130
|
|
|
111
131
|
module_schema.validate(data)
|
|
112
132
|
|
|
113
|
-
id, name, slot_type = (data.pop(key) for key in ["id", "name", "slot_type"])
|
|
114
|
-
|
|
115
133
|
children = data.pop("children", None)
|
|
116
134
|
if not children:
|
|
117
|
-
return
|
|
135
|
+
return []
|
|
118
136
|
|
|
119
137
|
devices = []
|
|
120
138
|
for child in children:
|
|
121
139
|
try:
|
|
122
|
-
logger.debug(
|
|
140
|
+
logger.debug(
|
|
141
|
+
colored(f"Importing top level device {pformat(child)}", "green")
|
|
142
|
+
)
|
|
123
143
|
|
|
124
144
|
node = import_board(logger, child, parent=parent, abort=abort)
|
|
125
145
|
devices.append(node)
|
|
126
146
|
except Exception as e:
|
|
127
|
-
logger.error(
|
|
147
|
+
logger.error(
|
|
148
|
+
colored(
|
|
149
|
+
f"Failed to import top-level device {pformat(child)}: {e}", "red"
|
|
150
|
+
)
|
|
151
|
+
)
|
|
128
152
|
logger.error(colored("Aborting." if abort else "Continuing anyway", "red"))
|
|
129
153
|
if abort:
|
|
130
154
|
raise
|
|
131
155
|
|
|
132
|
-
return
|
|
156
|
+
return devices
|
ekfsm/devices/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from ekfsm.devices.coretemp import CoreTemp
|
|
2
2
|
from ekfsm.devices.generic import Device
|
|
3
3
|
from ekfsm.devices.smbios import SMBIOS
|
|
4
|
+
from ekfsm.devices.thermal_humidity import ThermalHumidity
|
|
4
5
|
|
|
5
6
|
from .eeprom import EEPROM, EKF_CCU_EEPROM, EKF_EEPROM
|
|
6
7
|
from .ekf_ccu_uc import EKFCcuUc
|
|
@@ -9,6 +10,13 @@ from .gpio import GPIO, EKFIdentificationIOExpander, GPIOExpander
|
|
|
9
10
|
from .iio_thermal_humidity import IIOThermalHumidity
|
|
10
11
|
from .mux import I2CMux, MuxChannel
|
|
11
12
|
from .pmbus import PMBus, PSUStatus
|
|
13
|
+
from .io4edge import IO4Edge
|
|
14
|
+
from .pixelDisplay import PixelDisplay
|
|
15
|
+
from .buttonArray import ButtonArray
|
|
16
|
+
from .button import Button
|
|
17
|
+
from .colorLed import ColorLED
|
|
18
|
+
from .ledArray import LEDArray
|
|
19
|
+
from .watchdog import Watchdog
|
|
12
20
|
|
|
13
21
|
CLASS_MAP = {
|
|
14
22
|
"GenericDevice": Device,
|
|
@@ -27,4 +35,12 @@ CLASS_MAP = {
|
|
|
27
35
|
"HWMON": CoreTemp,
|
|
28
36
|
"EKFSurLed": EKFSurLed,
|
|
29
37
|
"IIOThermalHumidity": IIOThermalHumidity,
|
|
38
|
+
"ThermalHumidity": ThermalHumidity,
|
|
39
|
+
"IO4Edge": IO4Edge,
|
|
40
|
+
"PixelDisplay": PixelDisplay,
|
|
41
|
+
"ButtonArray": ButtonArray,
|
|
42
|
+
"Button": Button,
|
|
43
|
+
"ColorLED": ColorLED,
|
|
44
|
+
"LEDArray": LEDArray,
|
|
45
|
+
"Watchdog": Watchdog,
|
|
30
46
|
}
|
ekfsm/devices/button.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from ekfsm.devices.generic import Device
|
|
3
|
+
from ekfsm.log import ekfsm_logger
|
|
4
|
+
|
|
5
|
+
logger = ekfsm_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Button(Device):
|
|
9
|
+
"""
|
|
10
|
+
Device class for handling a single button as part on array.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
name: str,
|
|
16
|
+
parent: Device,
|
|
17
|
+
children: list[Device] | None = None,
|
|
18
|
+
abort: bool = False,
|
|
19
|
+
channel_id: int = 0,
|
|
20
|
+
*args,
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
logger.debug(f"Initializing Button '{name}' on channel {channel_id}")
|
|
24
|
+
|
|
25
|
+
super().__init__(name, parent, children, abort, *args, **kwargs)
|
|
26
|
+
|
|
27
|
+
self.channel_id = channel_id
|
|
28
|
+
logger.debug(f"Button '{name}' assigned to channel {channel_id}")
|
|
29
|
+
|
|
30
|
+
self._handler: Callable | None = None
|
|
31
|
+
logger.info(f"Button '{name}' initialized on channel {channel_id}")
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def handler(self):
|
|
35
|
+
"""
|
|
36
|
+
Handle button events with a callback function.
|
|
37
|
+
"""
|
|
38
|
+
return self._handler
|
|
39
|
+
|
|
40
|
+
@handler.setter
|
|
41
|
+
def handler(self, func: Callable | None, *args, **kwargs):
|
|
42
|
+
"""
|
|
43
|
+
Handle button events with a callback function.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
func : Callable | None
|
|
48
|
+
The function to call on button events. If None, no function is called.
|
|
49
|
+
"""
|
|
50
|
+
if callable(func):
|
|
51
|
+
self._handler = func
|
|
52
|
+
logger.info(
|
|
53
|
+
f"Handler set for button '{self.name}' on channel {self.channel_id}"
|
|
54
|
+
)
|
|
55
|
+
logger.debug(
|
|
56
|
+
f"Handler function: {func.__name__ if hasattr(func, '__name__') else str(func)}"
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
self._handler = None
|
|
60
|
+
logger.debug(
|
|
61
|
+
f"Handler cleared for button '{self.name}' on channel {self.channel_id}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
return f"{self.name}; Channel ID: {self.channel_id}"
|