conson-xp 1.51.1__py3-none-any.whl → 2.0.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.
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/METADATA +1 -11
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/RECORD +20 -38
- xp/__init__.py +1 -1
- xp/cli/commands/__init__.py +0 -4
- xp/cli/commands/term/term_commands.py +1 -1
- xp/cli/main.py +0 -3
- xp/models/homekit/homekit_config.py +4 -0
- xp/models/protocol/conbus_protocol.py +30 -25
- xp/models/term/accessory_state.py +1 -1
- xp/services/protocol/__init__.py +2 -3
- xp/services/protocol/conbus_event_protocol.py +5 -5
- xp/services/term/homekit_accessory_driver.py +171 -0
- xp/services/term/homekit_service.py +187 -10
- xp/term/homekit.py +146 -14
- xp/term/homekit.tcss +4 -4
- xp/term/widgets/room_list.py +61 -3
- xp/utils/dependencies.py +34 -154
- xp/cli/commands/homekit/__init__.py +0 -3
- xp/cli/commands/homekit/homekit.py +0 -120
- xp/cli/commands/homekit/homekit_start_commands.py +0 -44
- xp/services/homekit/__init__.py +0 -1
- xp/services/homekit/homekit_cache_service.py +0 -313
- xp/services/homekit/homekit_conbus_service.py +0 -99
- xp/services/homekit/homekit_config_validator.py +0 -327
- xp/services/homekit/homekit_conson_validator.py +0 -130
- xp/services/homekit/homekit_dimminglight.py +0 -189
- xp/services/homekit/homekit_dimminglight_service.py +0 -155
- xp/services/homekit/homekit_hap_service.py +0 -351
- xp/services/homekit/homekit_lightbulb.py +0 -125
- xp/services/homekit/homekit_lightbulb_service.py +0 -91
- xp/services/homekit/homekit_module_service.py +0 -60
- xp/services/homekit/homekit_outlet.py +0 -175
- xp/services/homekit/homekit_outlet_service.py +0 -127
- xp/services/homekit/homekit_service.py +0 -371
- xp/services/protocol/protocol_factory.py +0 -84
- xp/services/protocol/telegram_protocol.py +0 -270
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"""Bubus cache service for caching datapoint responses."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any, TypedDict, Union
|
|
8
|
-
|
|
9
|
-
from bubus import EventBus
|
|
10
|
-
|
|
11
|
-
from xp.models.protocol.conbus_protocol import (
|
|
12
|
-
LightLevelReceivedEvent,
|
|
13
|
-
OutputStateReceivedEvent,
|
|
14
|
-
ReadDatapointEvent,
|
|
15
|
-
ReadDatapointFromProtocolEvent,
|
|
16
|
-
)
|
|
17
|
-
from xp.models.telegram.datapoint_type import DataPointType
|
|
18
|
-
|
|
19
|
-
# Cache file configuration
|
|
20
|
-
CACHE_DIR = Path(".cache")
|
|
21
|
-
CACHE_FILE = CACHE_DIR / "homekit_cache.json"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class CacheEntry(TypedDict):
|
|
25
|
-
"""
|
|
26
|
-
Cache entry type definition.
|
|
27
|
-
|
|
28
|
-
Attributes:
|
|
29
|
-
event: The cached event (OutputStateReceivedEvent or LightLevelReceivedEvent).
|
|
30
|
-
timestamp: When the event was cached.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
event: Union[OutputStateReceivedEvent, LightLevelReceivedEvent]
|
|
34
|
-
timestamp: datetime
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class HomeKitCacheService:
|
|
38
|
-
"""
|
|
39
|
-
Cache service that intercepts bubus protocol messages to reduce redundant queries.
|
|
40
|
-
|
|
41
|
-
Caches OutputStateReceivedEvent and LightLevelReceivedEvent responses.
|
|
42
|
-
When a ReadDatapointEvent is received, checks cache and either:
|
|
43
|
-
- Returns cached response if available (cache hit)
|
|
44
|
-
- Forwards to protocol via ReadDatapointFromProtocolEvent (cache miss)
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(self, event_bus: EventBus, enable_persistence: bool = True):
|
|
48
|
-
"""
|
|
49
|
-
Initialize the HomeKit cache service.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
event_bus: Event bus for inter-service communication.
|
|
53
|
-
enable_persistence: Whether to persist cache to disk.
|
|
54
|
-
"""
|
|
55
|
-
self.logger = logging.getLogger(__name__)
|
|
56
|
-
self.event_bus = event_bus
|
|
57
|
-
self.cache: dict[tuple[str, DataPointType], CacheEntry] = {}
|
|
58
|
-
self.enable_persistence = enable_persistence
|
|
59
|
-
|
|
60
|
-
# Load cache from disk
|
|
61
|
-
if self.enable_persistence:
|
|
62
|
-
self._load_cache()
|
|
63
|
-
|
|
64
|
-
# Register event handlers
|
|
65
|
-
# Note: These must be registered BEFORE HomeKitConbusService registers its handlers
|
|
66
|
-
self.event_bus.on(ReadDatapointEvent, self.handle_read_datapoint_event)
|
|
67
|
-
self.event_bus.on(
|
|
68
|
-
OutputStateReceivedEvent, self.handle_output_state_received_event
|
|
69
|
-
)
|
|
70
|
-
self.event_bus.on(
|
|
71
|
-
LightLevelReceivedEvent, self.handle_light_level_received_event
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
self.logger.info(
|
|
75
|
-
f"HomeKitCacheService initialized with {len(self.cache)} cached entries"
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
def _serialize_cache_key(self, key: tuple[str, DataPointType]) -> str:
|
|
79
|
-
"""Serialize cache key to JSON-compatible string."""
|
|
80
|
-
serial_number, datapoint_type = key
|
|
81
|
-
return f"{serial_number}.{datapoint_type.value}"
|
|
82
|
-
|
|
83
|
-
def _deserialize_cache_key(self, key_str: str) -> tuple[str, DataPointType]:
|
|
84
|
-
"""Deserialize cache key from JSON string."""
|
|
85
|
-
serial_number, datapoint_type_str = key_str.rsplit(".", 1)
|
|
86
|
-
return (serial_number, DataPointType(datapoint_type_str))
|
|
87
|
-
|
|
88
|
-
def _serialize_cache(self) -> dict[str, Any]:
|
|
89
|
-
"""Serialize cache to JSON-compatible dict."""
|
|
90
|
-
serialized = {}
|
|
91
|
-
for key, entry in self.cache.items():
|
|
92
|
-
key_str = self._serialize_cache_key(key)
|
|
93
|
-
serialized[key_str] = {
|
|
94
|
-
"event": entry["event"].model_dump(mode="json"),
|
|
95
|
-
"timestamp": entry["timestamp"].isoformat(),
|
|
96
|
-
}
|
|
97
|
-
return serialized
|
|
98
|
-
|
|
99
|
-
def _deserialize_cache(
|
|
100
|
-
self, data: dict[str, Any]
|
|
101
|
-
) -> dict[tuple[str, DataPointType], CacheEntry]:
|
|
102
|
-
"""Deserialize cache from JSON dict."""
|
|
103
|
-
cache: dict[tuple[str, DataPointType], CacheEntry] = {}
|
|
104
|
-
for key_str, entry_data in data.items():
|
|
105
|
-
try:
|
|
106
|
-
key = self._deserialize_cache_key(key_str)
|
|
107
|
-
event_data = entry_data["event"]
|
|
108
|
-
|
|
109
|
-
# Reconstruct event based on datapoint_type
|
|
110
|
-
if key[1] == DataPointType.MODULE_OUTPUT_STATE:
|
|
111
|
-
event = OutputStateReceivedEvent(**event_data)
|
|
112
|
-
elif key[1] == DataPointType.MODULE_LIGHT_LEVEL:
|
|
113
|
-
event = LightLevelReceivedEvent(**event_data)
|
|
114
|
-
else:
|
|
115
|
-
self.logger.warning(f"Unknown datapoint type in cache: {key[1]}")
|
|
116
|
-
continue
|
|
117
|
-
|
|
118
|
-
cache[key] = {
|
|
119
|
-
"event": event,
|
|
120
|
-
"timestamp": datetime.fromisoformat(entry_data["timestamp"]),
|
|
121
|
-
}
|
|
122
|
-
except Exception as e:
|
|
123
|
-
self.logger.warning(f"Failed to deserialize cache entry {key_str}: {e}")
|
|
124
|
-
continue
|
|
125
|
-
|
|
126
|
-
return cache
|
|
127
|
-
|
|
128
|
-
def _load_cache(self) -> None:
|
|
129
|
-
"""Load cache from disk."""
|
|
130
|
-
if not CACHE_FILE.exists():
|
|
131
|
-
self.logger.debug("No cache file found, starting with empty cache")
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
with CACHE_FILE.open("r") as f:
|
|
136
|
-
data = json.load(f)
|
|
137
|
-
|
|
138
|
-
self.cache = self._deserialize_cache(data)
|
|
139
|
-
self.logger.info(f"Loaded {len(self.cache)} entries from cache file")
|
|
140
|
-
except Exception as e:
|
|
141
|
-
self.logger.error(f"Failed to load cache from disk: {e}")
|
|
142
|
-
self.cache = {}
|
|
143
|
-
|
|
144
|
-
def _save_cache(self) -> None:
|
|
145
|
-
"""Save cache to disk atomically."""
|
|
146
|
-
if not self.enable_persistence:
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
# Ensure cache directory exists
|
|
151
|
-
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
152
|
-
|
|
153
|
-
# Atomic write: write to temp file, then rename
|
|
154
|
-
temp_file = CACHE_FILE.with_suffix(".tmp")
|
|
155
|
-
with temp_file.open("w") as f:
|
|
156
|
-
json.dump(self._serialize_cache(), f, indent=2)
|
|
157
|
-
|
|
158
|
-
# Atomic rename
|
|
159
|
-
temp_file.replace(CACHE_FILE)
|
|
160
|
-
|
|
161
|
-
self.logger.debug(f"Saved {len(self.cache)} entries to cache file")
|
|
162
|
-
except Exception as e:
|
|
163
|
-
self.logger.error(f"Failed to save cache to disk: {e}")
|
|
164
|
-
|
|
165
|
-
def _get_cache_key(
|
|
166
|
-
self, serial_number: str, datapoint_type: DataPointType
|
|
167
|
-
) -> tuple[str, DataPointType]:
|
|
168
|
-
"""Generate cache key from serial number and datapoint type."""
|
|
169
|
-
return (serial_number, datapoint_type)
|
|
170
|
-
|
|
171
|
-
def _cache_event(
|
|
172
|
-
self, event: Union[OutputStateReceivedEvent, LightLevelReceivedEvent]
|
|
173
|
-
) -> None:
|
|
174
|
-
"""Store an event in the cache."""
|
|
175
|
-
cache_key = self._get_cache_key(event.serial_number, event.datapoint_type)
|
|
176
|
-
cache_entry: CacheEntry = {
|
|
177
|
-
"event": event,
|
|
178
|
-
"timestamp": datetime.now(),
|
|
179
|
-
}
|
|
180
|
-
self.cache[cache_key] = cache_entry
|
|
181
|
-
self.logger.debug(
|
|
182
|
-
f"Cached event: "
|
|
183
|
-
f"serial={event.serial_number}, "
|
|
184
|
-
f"type={event.datapoint_type}, "
|
|
185
|
-
f"value={event.data_value}"
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
# Persist to disk
|
|
189
|
-
self._save_cache()
|
|
190
|
-
|
|
191
|
-
def _get_cached_event(
|
|
192
|
-
self, serial_number: str, datapoint_type: DataPointType
|
|
193
|
-
) -> Union[OutputStateReceivedEvent, LightLevelReceivedEvent, None]:
|
|
194
|
-
"""Retrieve an event from the cache if it exists."""
|
|
195
|
-
cache_key = self._get_cache_key(serial_number, datapoint_type)
|
|
196
|
-
cache_entry = self.cache.get(cache_key)
|
|
197
|
-
|
|
198
|
-
if cache_entry:
|
|
199
|
-
self.logger.debug(
|
|
200
|
-
f"Cache hit: " f"serial={serial_number}, " f"type={datapoint_type}"
|
|
201
|
-
)
|
|
202
|
-
return cache_entry["event"]
|
|
203
|
-
|
|
204
|
-
self.logger.debug(f"Cache miss: serial={serial_number}, type={datapoint_type}")
|
|
205
|
-
return None
|
|
206
|
-
|
|
207
|
-
def handle_read_datapoint_event(self, event: ReadDatapointEvent) -> None:
|
|
208
|
-
"""
|
|
209
|
-
Handle ReadDatapointEvent by checking cache or refresh flag.
|
|
210
|
-
|
|
211
|
-
On refresh_cache=True: invalidate cache and force protocol query
|
|
212
|
-
On cache hit: dispatch cached response event
|
|
213
|
-
On cache miss: forward to protocol via ReadDatapointFromProtocolEvent
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
event: Read datapoint event with serial number, datapoint type, and refresh flag.
|
|
217
|
-
"""
|
|
218
|
-
self.logger.debug(
|
|
219
|
-
f"Handling ReadDatapointEvent: "
|
|
220
|
-
f"serial={event.serial_number}, "
|
|
221
|
-
f"type={event.datapoint_type}, "
|
|
222
|
-
f"refresh_cache={event.refresh_cache}"
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
# Check if cache refresh requested
|
|
226
|
-
if event.refresh_cache:
|
|
227
|
-
self.logger.info(
|
|
228
|
-
f"Cache refresh requested: "
|
|
229
|
-
f"serial={event.serial_number}, "
|
|
230
|
-
f"type={event.datapoint_type}"
|
|
231
|
-
)
|
|
232
|
-
# Invalidate cache entry
|
|
233
|
-
cache_key = self._get_cache_key(event.serial_number, event.datapoint_type)
|
|
234
|
-
if cache_key in self.cache:
|
|
235
|
-
del self.cache[cache_key]
|
|
236
|
-
self.logger.debug(f"Invalidated cache entry: {cache_key}")
|
|
237
|
-
# Persist invalidation
|
|
238
|
-
self._save_cache()
|
|
239
|
-
|
|
240
|
-
# Normal cache lookup flow
|
|
241
|
-
cached_event = self._get_cached_event(event.serial_number, event.datapoint_type)
|
|
242
|
-
|
|
243
|
-
if cached_event:
|
|
244
|
-
# Cache hit - dispatch the cached event
|
|
245
|
-
self.logger.debug(
|
|
246
|
-
f"Returning cached response: "
|
|
247
|
-
f"serial={event.serial_number}, "
|
|
248
|
-
f"type={event.datapoint_type}"
|
|
249
|
-
)
|
|
250
|
-
self.event_bus.dispatch(cached_event)
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
# Cache miss - forward to protocol
|
|
254
|
-
self.logger.debug(
|
|
255
|
-
f"Forwarding to protocol: "
|
|
256
|
-
f"serial={event.serial_number}, "
|
|
257
|
-
f"type={event.datapoint_type}"
|
|
258
|
-
)
|
|
259
|
-
self.event_bus.dispatch(
|
|
260
|
-
ReadDatapointFromProtocolEvent(
|
|
261
|
-
serial_number=event.serial_number,
|
|
262
|
-
datapoint_type=event.datapoint_type,
|
|
263
|
-
)
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
def handle_output_state_received_event(
|
|
267
|
-
self, event: OutputStateReceivedEvent
|
|
268
|
-
) -> None:
|
|
269
|
-
"""
|
|
270
|
-
Cache OutputStateReceivedEvent for future queries.
|
|
271
|
-
|
|
272
|
-
Args:
|
|
273
|
-
event: Output state received event to cache.
|
|
274
|
-
"""
|
|
275
|
-
self.logger.debug(
|
|
276
|
-
f"Caching OutputStateReceivedEvent: "
|
|
277
|
-
f"serial={event.serial_number}, "
|
|
278
|
-
f"type={event.datapoint_type}, "
|
|
279
|
-
f"value={event.data_value}"
|
|
280
|
-
)
|
|
281
|
-
self._cache_event(event)
|
|
282
|
-
|
|
283
|
-
def handle_light_level_received_event(self, event: LightLevelReceivedEvent) -> None:
|
|
284
|
-
"""
|
|
285
|
-
Cache LightLevelReceivedEvent for future queries.
|
|
286
|
-
|
|
287
|
-
Args:
|
|
288
|
-
event: Light level received event to cache.
|
|
289
|
-
"""
|
|
290
|
-
self.logger.debug(
|
|
291
|
-
f"Caching LightLevelReceivedEvent: "
|
|
292
|
-
f"serial={event.serial_number}, "
|
|
293
|
-
f"type={event.datapoint_type}, "
|
|
294
|
-
f"value={event.data_value}"
|
|
295
|
-
)
|
|
296
|
-
self._cache_event(event)
|
|
297
|
-
|
|
298
|
-
def clear_cache(self) -> None:
|
|
299
|
-
"""Clear all cached entries."""
|
|
300
|
-
self.logger.info("Clearing cache")
|
|
301
|
-
self.cache.clear()
|
|
302
|
-
self._save_cache()
|
|
303
|
-
|
|
304
|
-
def get_cache_stats(self) -> dict[str, int]:
|
|
305
|
-
"""
|
|
306
|
-
Get cache statistics.
|
|
307
|
-
|
|
308
|
-
Returns:
|
|
309
|
-
Dictionary with cache statistics including total_entries.
|
|
310
|
-
"""
|
|
311
|
-
return {
|
|
312
|
-
"total_entries": len(self.cache),
|
|
313
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HomeKit Conbus Service for protocol communication.
|
|
3
|
-
|
|
4
|
-
This module bridges HomeKit events with the Conbus protocol for device control.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
from bubus import EventBus
|
|
10
|
-
|
|
11
|
-
from xp.models.protocol.conbus_protocol import (
|
|
12
|
-
ReadDatapointFromProtocolEvent,
|
|
13
|
-
SendActionEvent,
|
|
14
|
-
SendWriteConfigEvent,
|
|
15
|
-
)
|
|
16
|
-
from xp.models.telegram.datapoint_type import DataPointType
|
|
17
|
-
from xp.models.telegram.system_function import SystemFunction
|
|
18
|
-
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class HomeKitConbusService:
|
|
22
|
-
"""
|
|
23
|
-
Service for bridging HomeKit events with Conbus protocol.
|
|
24
|
-
|
|
25
|
-
Attributes:
|
|
26
|
-
event_bus: Event bus for inter-service communication.
|
|
27
|
-
telegram_protocol: Protocol for sending telegrams.
|
|
28
|
-
logger: Logger instance.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
event_bus: EventBus
|
|
32
|
-
|
|
33
|
-
def __init__(self, event_bus: EventBus, telegram_protocol: TelegramProtocol):
|
|
34
|
-
"""
|
|
35
|
-
Initialize the HomeKit Conbus service.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
event_bus: Event bus instance.
|
|
39
|
-
telegram_protocol: Telegram protocol instance.
|
|
40
|
-
"""
|
|
41
|
-
self.logger = logging.getLogger(__name__)
|
|
42
|
-
self.event_bus = event_bus
|
|
43
|
-
self.telegram_protocol = telegram_protocol
|
|
44
|
-
|
|
45
|
-
# Register event handlers
|
|
46
|
-
self.event_bus.on(
|
|
47
|
-
ReadDatapointFromProtocolEvent, self.handle_read_datapoint_request
|
|
48
|
-
)
|
|
49
|
-
self.event_bus.on(SendActionEvent, self.handle_send_action_event)
|
|
50
|
-
self.event_bus.on(SendWriteConfigEvent, self.handle_send_write_config_event)
|
|
51
|
-
|
|
52
|
-
def handle_read_datapoint_request(
|
|
53
|
-
self, event: ReadDatapointFromProtocolEvent
|
|
54
|
-
) -> None:
|
|
55
|
-
"""
|
|
56
|
-
Handle request to read datapoint from protocol.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
event: Read datapoint event with serial number and datapoint type.
|
|
60
|
-
"""
|
|
61
|
-
self.logger.debug(f"read_datapoint_request {event}")
|
|
62
|
-
|
|
63
|
-
system_function = SystemFunction.READ_DATAPOINT.value
|
|
64
|
-
datapoint_value = event.datapoint_type.value
|
|
65
|
-
telegram = f"S{event.serial_number}F{system_function}D{datapoint_value}"
|
|
66
|
-
self.telegram_protocol.sendFrame(telegram.encode())
|
|
67
|
-
|
|
68
|
-
def handle_send_write_config_event(self, event: SendWriteConfigEvent) -> None:
|
|
69
|
-
"""
|
|
70
|
-
Handle send write config event.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
event: Write config event with configuration data.
|
|
74
|
-
"""
|
|
75
|
-
self.logger.debug(f"send_write_config_event {event}")
|
|
76
|
-
|
|
77
|
-
# Format data as output_number:level (e.g., "02:050")
|
|
78
|
-
system_function = SystemFunction.WRITE_CONFIG.value
|
|
79
|
-
datapoint_type = DataPointType.MODULE_LIGHT_LEVEL.value
|
|
80
|
-
config_data = f"{event.output_number:02d}:{event.value:03d}"
|
|
81
|
-
telegram = (
|
|
82
|
-
f"S{event.serial_number}F{system_function}D{datapoint_type}{config_data}"
|
|
83
|
-
)
|
|
84
|
-
self.telegram_protocol.sendFrame(telegram.encode())
|
|
85
|
-
|
|
86
|
-
def handle_send_action_event(self, event: SendActionEvent) -> None:
|
|
87
|
-
"""
|
|
88
|
-
Handle send action event.
|
|
89
|
-
|
|
90
|
-
Args:
|
|
91
|
-
event: Send action event with action data.
|
|
92
|
-
"""
|
|
93
|
-
self.logger.debug(f"send_action_event {event}")
|
|
94
|
-
|
|
95
|
-
telegram = event.on_action if event.value else event.off_action
|
|
96
|
-
telegram_make = f"{telegram}M"
|
|
97
|
-
telegram_break = f"{telegram}B"
|
|
98
|
-
self.telegram_protocol.sendFrame(telegram_make.encode())
|
|
99
|
-
self.telegram_protocol.sendFrame(telegram_break.encode())
|