conson-xp 1.20.0__py3-none-any.whl → 1.22.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.20.0.dist-info → conson_xp-1.22.0.dist-info}/METADATA +32 -17
- {conson_xp-1.20.0.dist-info → conson_xp-1.22.0.dist-info}/RECORD +20 -14
- xp/__init__.py +1 -1
- xp/cli/commands/term/term_commands.py +1 -1
- xp/models/term/__init__.py +11 -0
- xp/models/term/protocol_keys_config.py +45 -0
- xp/services/protocol/conbus_event_protocol.py +8 -0
- xp/term/protocol.py +127 -0
- xp/term/protocol.tcss +135 -0
- xp/term/protocol.yml +139 -0
- xp/term/widgets/__init__.py +7 -0
- xp/term/widgets/help_menu.py +55 -0
- xp/{tui → term}/widgets/protocol_log.py +157 -76
- xp/term/widgets/status_footer.py +53 -0
- xp/utils/logging.py +16 -5
- xp/utils/state_machine.py +81 -0
- xp/tui/app.py +0 -72
- xp/tui/protocol.tcss +0 -50
- xp/tui/widgets/__init__.py +0 -1
- {conson_xp-1.20.0.dist-info → conson_xp-1.22.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.20.0.dist-info → conson_xp-1.22.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.20.0.dist-info → conson_xp-1.22.0.dist-info}/licenses/LICENSE +0 -0
- /xp/{tui → term}/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: conson-xp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.22.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
[](https://opensource.org/licenses/MIT)
|
|
61
61
|
[](https://mypy-lang.org/)
|
|
62
62
|
|
|
63
|
-
> **A powerful Python CLI
|
|
63
|
+
> **A powerful Python CLI toolkit for CONSON XP Protocol operations**
|
|
64
64
|
|
|
65
65
|
Control and communicate with XP devices through console bus (Conbus), parse telegrams in real-time, and integrate with smart home systems like Apple HomeKit.
|
|
66
66
|
|
|
@@ -81,7 +81,7 @@ Bridge XP devices to Apple HomeKit for seamless smart home control
|
|
|
81
81
|
Automatically discover XP servers and scan connected modules on your network
|
|
82
82
|
|
|
83
83
|
⚡ **Modern Architecture**
|
|
84
|
-
|
|
84
|
+
Comprehensive type safety and robust error handling
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
@@ -96,9 +96,6 @@ xp telegram parse "<E14L00I02MAK>"
|
|
|
96
96
|
|
|
97
97
|
# Discover XP servers on your network
|
|
98
98
|
xp conbus discover
|
|
99
|
-
|
|
100
|
-
# Start the REST API server
|
|
101
|
-
xp api start
|
|
102
99
|
```
|
|
103
100
|
|
|
104
101
|
## 📦 Installation
|
|
@@ -167,7 +164,32 @@ xp module search "push button"
|
|
|
167
164
|
xp module list --group-by-category
|
|
168
165
|
```
|
|
169
166
|
|
|
167
|
+
### 🖥️ Terminal UI (TUI)
|
|
168
|
+
|
|
169
|
+
**Real-time Protocol Monitor**
|
|
170
|
+
|
|
171
|
+
Launch an interactive terminal interface for live protocol monitoring and control:
|
|
170
172
|
|
|
173
|
+
```bash
|
|
174
|
+
# Start the protocol monitor TUI
|
|
175
|
+
xp term protocol
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Features:**
|
|
179
|
+
- 📊 **Live Telegram Stream**: Real-time RX/TX telegram monitoring from Conbus server
|
|
180
|
+
- ⌨️ **Keyboard Shortcuts**: Quick access controls for common operations
|
|
181
|
+
- `Q` - Quit application
|
|
182
|
+
- `C` - Toggle connection (connect/disconnect)
|
|
183
|
+
- `R` - Reset and clear log
|
|
184
|
+
- `0-9, a-q` - Send predefined protocol telegrams
|
|
185
|
+
- 🎨 **Visual Status Indicators**: Color-coded connection states
|
|
186
|
+
- 🟢 Green - Connected
|
|
187
|
+
- 🟡 Yellow - Connecting/Disconnecting
|
|
188
|
+
- 🔴 Red - Failed
|
|
189
|
+
- ⚪ White - Disconnected
|
|
190
|
+
- 📝 **Interactive Display**: Scrollable telegram log with detailed parsing information
|
|
191
|
+
|
|
192
|
+
The TUI provides a convenient way to monitor and interact with XP devices without juggling multiple terminal commands.
|
|
171
193
|
|
|
172
194
|
### 🔧 Advanced Features
|
|
173
195
|
|
|
@@ -201,13 +223,7 @@ xp checksum calculate "E14L00I02M" --algorithm crc32
|
|
|
201
223
|
```
|
|
202
224
|
</details>
|
|
203
225
|
|
|
204
|
-
### 🌐
|
|
205
|
-
|
|
206
|
-
**REST API Server**
|
|
207
|
-
```bash
|
|
208
|
-
# Start API server with interactive docs at /docs
|
|
209
|
-
xp api start
|
|
210
|
-
```
|
|
226
|
+
### 🌐 Integration
|
|
211
227
|
|
|
212
228
|
**HomeKit Smart Home Bridge**
|
|
213
229
|
```bash
|
|
@@ -232,7 +248,7 @@ xp reverse-proxy start
|
|
|
232
248
|
|
|
233
249
|
**Layered Design**
|
|
234
250
|
```
|
|
235
|
-
CLI Layer →
|
|
251
|
+
CLI Layer → Services → Models → Connection Layer
|
|
236
252
|
```
|
|
237
253
|
|
|
238
254
|
**Key Components**: Telegram processing • Real-time Conbus communication • HomeKit bridge • Multiple XP server support • Configuration management
|
|
@@ -256,9 +272,8 @@ pdm run check
|
|
|
256
272
|
<details>
|
|
257
273
|
<summary><b>Project Structure</b></summary>
|
|
258
274
|
|
|
259
|
-
```
|
|
275
|
+
```
|
|
260
276
|
src/xp/
|
|
261
|
-
├── api/ # FastAPI REST endpoints
|
|
262
277
|
├── cli/ # Command-line interface
|
|
263
278
|
├── models/ # Core data models
|
|
264
279
|
├── services/ # Business logic
|
|
@@ -407,7 +422,7 @@ xp term protocol
|
|
|
407
422
|
```
|
|
408
423
|
</details>
|
|
409
424
|
|
|
410
|
-
**Requirements**: Python 3.10+ •
|
|
425
|
+
**Requirements**: Python 3.10+ • Pydantic • Click • HAP-python
|
|
411
426
|
|
|
412
427
|
## License
|
|
413
428
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
conson_xp-1.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
1
|
+
conson_xp-1.22.0.dist-info/METADATA,sha256=SHz732F4Z0CbbWIukCpttstkgiNLFBgqYAZQg94OkEk,10298
|
|
2
|
+
conson_xp-1.22.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.22.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.22.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=cGDFz-p2G9nzESEviCX7b8voSRj-fYSsRjnhBPsDwSE,181
|
|
6
6
|
xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
|
|
7
7
|
xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
|
|
8
8
|
xp/cli/commands/__init__.py,sha256=noh8fdZAWq-ihJEboP8WugbIgq4LJ3jUWMRA7720xWE,4909
|
|
@@ -42,7 +42,7 @@ xp/cli/commands/telegram/telegram_parse_commands.py,sha256=_OYOso1hS4f_ox96qlkYL
|
|
|
42
42
|
xp/cli/commands/telegram/telegram_version_commands.py,sha256=WQyx1-B9yJ8V9WrFyBpOvULJ-jq12GoZZDDoRbM7eyw,1553
|
|
43
43
|
xp/cli/commands/term/__init__.py,sha256=1NNST_8YJfj5LCujQISwQflK6LyEn7mDmZpMpvI9d-o,116
|
|
44
44
|
xp/cli/commands/term/term.py,sha256=gjvsv2OE-F_KNWQrWi04fXQ5cGo0l8P-Ortbb5KTA-A,309
|
|
45
|
-
xp/cli/commands/term/term_commands.py,sha256=
|
|
45
|
+
xp/cli/commands/term/term_commands.py,sha256=ccBdvvyxjh2-cptmI9ohVIa02OOfG0dzO1JFb4KTowQ,744
|
|
46
46
|
xp/cli/main.py,sha256=ap5jU0DrSnrCKDKqGXcz9N-sngZodyyN-5ReWE8Fh1s,1817
|
|
47
47
|
xp/cli/utils/__init__.py,sha256=gTGIj60Uai0iE7sr9_TtEpl04fD7krtTzbbigXUsUVU,46
|
|
48
48
|
xp/cli/utils/click_tree.py,sha256=ilmM2IMa_c-TqUMsv2alrZXuS0BNhvVlrBlSfyN8lzM,1670
|
|
@@ -104,6 +104,8 @@ xp/models/telegram/system_telegram.py,sha256=9FNQ4Mf47mRK7wGrTg2GzziVsrEWCE5ZkZp
|
|
|
104
104
|
xp/models/telegram/telegram.py,sha256=IJUxHX6ftLcET9C1pjvLhUO5Db5JO6W7rUItzdEW30I,842
|
|
105
105
|
xp/models/telegram/telegram_type.py,sha256=GhqKP63oNMyh2tIvCPcsC5RFp4s4JjhmEqCLCC-8XMk,423
|
|
106
106
|
xp/models/telegram/timeparam_type.py,sha256=Ar8xvSfPmOAgR2g2Je0FgvP01SL7bPvZn5_HrVDpmJM,1137
|
|
107
|
+
xp/models/term/__init__.py,sha256=c1AMtVitYk80o9K_zWjYNzZYpFDASqM8S1Djm1PD4Qo,192
|
|
108
|
+
xp/models/term/protocol_keys_config.py,sha256=CTujcfI2_NOeltjvHy_cnsHzxLSVsGFXieMZlD-zj0Q,1204
|
|
107
109
|
xp/models/write_config_type.py,sha256=T2RaO52RpzoJ4782uMHE-fX7Ymx3CaIQAEwByydXq1M,881
|
|
108
110
|
xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
|
|
109
111
|
xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
|
|
@@ -149,7 +151,7 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
|
|
|
149
151
|
xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
|
|
150
152
|
xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
|
|
151
153
|
xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
|
|
152
|
-
xp/services/protocol/conbus_event_protocol.py,sha256=
|
|
154
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=t_ovcLbwXays-y8u-EqFpDSfo2Xc_BNl3jAj9PqxRwg,13885
|
|
153
155
|
xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
|
|
154
156
|
xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
|
|
155
157
|
xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
|
|
@@ -173,16 +175,20 @@ xp/services/telegram/telegram_link_number_service.py,sha256=1_c-_QCRPTHYn3BmMElr
|
|
|
173
175
|
xp/services/telegram/telegram_output_service.py,sha256=UaUv_14fR8o5K2PxQBXrCzx-Hohnk-gzbev_oLw_Clc,10799
|
|
174
176
|
xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmXDOsU4Xl8BlY,13237
|
|
175
177
|
xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
|
|
176
|
-
xp/
|
|
177
|
-
xp/
|
|
178
|
-
xp/
|
|
179
|
-
xp/
|
|
180
|
-
xp/
|
|
178
|
+
xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
|
|
179
|
+
xp/term/protocol.py,sha256=ERntzYvNMyI-VDCYW7EnhpPu-V6WPmyEk0xcQfm7LFM,4399
|
|
180
|
+
xp/term/protocol.tcss,sha256=biaIv6X-bmD1tbXENFUNsXOF8ABkuoRAUgeSDqwudtQ,2126
|
|
181
|
+
xp/term/protocol.yml,sha256=kiTe_QSMPmLvLA0ZyIhNaDPwBdi6khh5C1NSR7I9TN0,2124
|
|
182
|
+
xp/term/widgets/__init__.py,sha256=ftWmN_fmjxy2E8Qfm-YSRmzKfgL0KTBCTpgvYWCPbUY,274
|
|
183
|
+
xp/term/widgets/help_menu.py,sha256=bdT5AYRdtKt_tvZTVbG7-DPMb1mj78kggtjjsa-95BA,1780
|
|
184
|
+
xp/term/widgets/protocol_log.py,sha256=yuSfc61azgQmWQxLvBcmVik3cJAW1Hocj2Sk8qr3hSg,14343
|
|
185
|
+
xp/term/widgets/status_footer.py,sha256=VTN5owCprMbYmNiEbNWS_4CE8yxysDi8IBCECuU9EQY,1663
|
|
181
186
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
182
187
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
183
188
|
xp/utils/dependencies.py,sha256=ECS6p0eXzocM5INLwJeckHXn_Dim18uOjXTJ29qQvkQ,22001
|
|
184
189
|
xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
|
|
185
|
-
xp/utils/logging.py,sha256=
|
|
190
|
+
xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
|
|
186
191
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
192
|
+
xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
|
|
187
193
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
188
|
-
conson_xp-1.
|
|
194
|
+
conson_xp-1.22.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -21,7 +21,7 @@ def protocol_monitor(ctx: Context) -> None:
|
|
|
21
21
|
\b
|
|
22
22
|
xp term protocol
|
|
23
23
|
"""
|
|
24
|
-
from xp.
|
|
24
|
+
from xp.term.protocol import ProtocolMonitorApp
|
|
25
25
|
|
|
26
26
|
# Resolve ServiceContainer from context
|
|
27
27
|
container = ctx.obj.get("container").get_container()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Protocol keys configuration model."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProtocolKeyConfig(BaseModel):
|
|
11
|
+
"""Configuration for a single protocol key.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
name: Human-readable command name.
|
|
15
|
+
telegrams: List of raw telegram strings to send (without angle brackets).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name: str = Field(..., description="Human-readable command name")
|
|
19
|
+
telegrams: list[str] = Field(..., description="List of raw telegram strings")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProtocolKeysConfig(BaseModel):
|
|
23
|
+
"""Protocol keys configuration.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
protocol: Dictionary mapping key to protocol configuration.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
protocol: Dict[str, ProtocolKeyConfig] = Field(
|
|
30
|
+
default_factory=dict, description="Protocol key mappings"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_yaml(cls, config_path: Path) -> "ProtocolKeysConfig":
|
|
35
|
+
"""Load protocol keys from YAML file.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config_path: Path to YAML configuration file.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
ProtocolKeysConfig instance.
|
|
42
|
+
"""
|
|
43
|
+
with config_path.open("r") as f:
|
|
44
|
+
data = yaml.safe_load(f)
|
|
45
|
+
return cls(**data)
|
|
@@ -209,6 +209,14 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
209
209
|
f"F{system_function.value}"
|
|
210
210
|
f"D{data_value}"
|
|
211
211
|
)
|
|
212
|
+
self.send_raw_telegram(payload)
|
|
213
|
+
|
|
214
|
+
def send_raw_telegram(self, payload: str) -> None:
|
|
215
|
+
"""Send telegram with specified parameters.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
payload: Telegram to send.
|
|
219
|
+
"""
|
|
212
220
|
self.telegram_queue.put_nowait(payload.encode())
|
|
213
221
|
self.call_later(0.0, self.start_queue_manager)
|
|
214
222
|
|
xp/term/protocol.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Protocol Monitor TUI Application."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from textual.app import App, ComposeResult
|
|
7
|
+
from textual.containers import Horizontal
|
|
8
|
+
|
|
9
|
+
from xp.models.term import ProtocolKeysConfig
|
|
10
|
+
from xp.term.widgets.help_menu import HelpMenuWidget
|
|
11
|
+
from xp.term.widgets.protocol_log import ProtocolLogWidget
|
|
12
|
+
from xp.term.widgets.status_footer import StatusFooterWidget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProtocolMonitorApp(App[None]):
|
|
16
|
+
"""Textual app for real-time protocol monitoring.
|
|
17
|
+
|
|
18
|
+
Displays live RX/TX telegram stream from Conbus server in an interactive
|
|
19
|
+
terminal interface with keyboard shortcuts for control.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
container: ServiceContainer for dependency injection.
|
|
23
|
+
CSS_PATH: Path to CSS stylesheet file.
|
|
24
|
+
BINDINGS: Keyboard bindings for app actions.
|
|
25
|
+
TITLE: Application title displayed in header.
|
|
26
|
+
ENABLE_COMMAND_PALETTE: Disable Textual's command palette feature.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
CSS_PATH = Path(__file__).parent / "protocol.tcss"
|
|
30
|
+
TITLE = "Protocol Monitor"
|
|
31
|
+
ENABLE_COMMAND_PALETTE = False
|
|
32
|
+
|
|
33
|
+
BINDINGS = [
|
|
34
|
+
("Q", "quit", "Quit"),
|
|
35
|
+
("C", "toggle_connection", "Connect"),
|
|
36
|
+
("R", "reset", "Reset"),
|
|
37
|
+
("0-9,a-q", "protocol_keys", "Keys"),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
def __init__(self, container: Any) -> None:
|
|
41
|
+
"""Initialize the Protocol Monitor app.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
container: ServiceContainer for resolving services.
|
|
45
|
+
"""
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.container = container
|
|
48
|
+
self.protocol_widget: Optional[ProtocolLogWidget] = None
|
|
49
|
+
self.help_menu: Optional[HelpMenuWidget] = None
|
|
50
|
+
self.footer_widget: Optional[StatusFooterWidget] = None
|
|
51
|
+
self.protocol_keys = self._load_protocol_keys()
|
|
52
|
+
|
|
53
|
+
def _load_protocol_keys(self) -> ProtocolKeysConfig:
|
|
54
|
+
"""Load protocol keys from YAML config file.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
ProtocolKeysConfig instance.
|
|
58
|
+
"""
|
|
59
|
+
config_path = Path(__file__).parent / "protocol.yml"
|
|
60
|
+
return ProtocolKeysConfig.from_yaml(config_path)
|
|
61
|
+
|
|
62
|
+
def compose(self) -> ComposeResult:
|
|
63
|
+
"""Compose the app layout with widgets.
|
|
64
|
+
|
|
65
|
+
Yields:
|
|
66
|
+
ProtocolLogWidget and Footer widgets.
|
|
67
|
+
"""
|
|
68
|
+
with Horizontal(id="main-container"):
|
|
69
|
+
self.protocol_widget = ProtocolLogWidget(container=self.container)
|
|
70
|
+
yield self.protocol_widget
|
|
71
|
+
|
|
72
|
+
# Help menu (hidden by default)
|
|
73
|
+
self.help_menu = HelpMenuWidget(
|
|
74
|
+
protocol_keys=self.protocol_keys, id="help-menu"
|
|
75
|
+
)
|
|
76
|
+
yield self.help_menu
|
|
77
|
+
|
|
78
|
+
self.footer_widget = StatusFooterWidget(id="footer-container")
|
|
79
|
+
yield self.footer_widget
|
|
80
|
+
|
|
81
|
+
def action_toggle_connection(self) -> None:
|
|
82
|
+
"""Toggle connection on 'c' key press.
|
|
83
|
+
|
|
84
|
+
Connects if disconnected/failed, disconnects if connected/connecting.
|
|
85
|
+
"""
|
|
86
|
+
if self.protocol_widget:
|
|
87
|
+
from xp.term.widgets.protocol_log import ConnectionState
|
|
88
|
+
|
|
89
|
+
state = self.protocol_widget.connection_state
|
|
90
|
+
if state in (ConnectionState.CONNECTED, ConnectionState.CONNECTING):
|
|
91
|
+
self.protocol_widget.disconnect()
|
|
92
|
+
else:
|
|
93
|
+
self.protocol_widget.connect()
|
|
94
|
+
|
|
95
|
+
def action_reset(self) -> None:
|
|
96
|
+
"""Reset and clear protocol widget on 'r' key press."""
|
|
97
|
+
if self.protocol_widget:
|
|
98
|
+
self.protocol_widget.clear_log()
|
|
99
|
+
|
|
100
|
+
def on_key(self, event: Any) -> None:
|
|
101
|
+
"""Handle key press events for protocol keys.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
event: Key press event from Textual.
|
|
105
|
+
"""
|
|
106
|
+
if event.key in self.protocol_keys.protocol and self.protocol_widget:
|
|
107
|
+
key_config = self.protocol_keys.protocol[event.key]
|
|
108
|
+
for telegram in key_config.telegrams:
|
|
109
|
+
self.protocol_widget.send_telegram(key_config.name, telegram)
|
|
110
|
+
|
|
111
|
+
def on_mount(self) -> None:
|
|
112
|
+
"""Set up status line updates when app mounts."""
|
|
113
|
+
if self.protocol_widget:
|
|
114
|
+
self.protocol_widget.watch(
|
|
115
|
+
self.protocol_widget,
|
|
116
|
+
"connection_state",
|
|
117
|
+
self._update_status,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _update_status(self, state: Any) -> None:
|
|
121
|
+
"""Update status line with connection state.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
state: Current connection state.
|
|
125
|
+
"""
|
|
126
|
+
if self.footer_widget:
|
|
127
|
+
self.footer_widget.update_status(state)
|
xp/term/protocol.tcss
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/* Protocol Monitor TUI Styling */
|
|
2
|
+
|
|
3
|
+
/* App-level styling */
|
|
4
|
+
Screen {
|
|
5
|
+
background: $background;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Protocol Log Widget */
|
|
9
|
+
ProtocolLogWidget {
|
|
10
|
+
border: solid $success;
|
|
11
|
+
width: 1fr;
|
|
12
|
+
height: 1fr;
|
|
13
|
+
background: $background;
|
|
14
|
+
padding: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ProtocolLogWidget > RichLog {
|
|
18
|
+
background: $background !important;
|
|
19
|
+
scrollbar-background: $background;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ProtocolLogWidget > RichLog:focus {
|
|
23
|
+
background: $background !important;
|
|
24
|
+
background-tint: transparent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ProtocolLogWidget > .connection-status {
|
|
28
|
+
color: $text;
|
|
29
|
+
text-align: center;
|
|
30
|
+
padding: 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ProtocolLogWidget > .connection-status.connecting {
|
|
34
|
+
color: $warning;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ProtocolLogWidget > .connection-status.connected {
|
|
38
|
+
color: $success;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ProtocolLogWidget > .connection-status.failed {
|
|
42
|
+
color: $error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Message display styling */
|
|
46
|
+
.message-tx {
|
|
47
|
+
color: $success;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.message-rx {
|
|
51
|
+
color: $success;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.message-frame {
|
|
55
|
+
color: $text-muted;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Main container */
|
|
59
|
+
#main-container {
|
|
60
|
+
height: 1fr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Help menu styling */
|
|
64
|
+
#help-menu {
|
|
65
|
+
width: 35;
|
|
66
|
+
height: 1fr;
|
|
67
|
+
background: $background;
|
|
68
|
+
border: solid $success;
|
|
69
|
+
padding: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#help-menu:focus {
|
|
73
|
+
border: solid $success;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#help-title {
|
|
77
|
+
color: $success;
|
|
78
|
+
text-align: center;
|
|
79
|
+
text-style: bold;
|
|
80
|
+
margin-bottom: 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#help-table {
|
|
84
|
+
background: $background;
|
|
85
|
+
height: auto;
|
|
86
|
+
color: $success;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DataTable {
|
|
90
|
+
background: $background;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
DataTable > .datatable--header {
|
|
94
|
+
background: $background;
|
|
95
|
+
color: $success;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
DataTable > .datatable--cursor {
|
|
99
|
+
background: $background;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
DataTable:focus > .datatable--cursor {
|
|
103
|
+
background: $background;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Footer styling */
|
|
107
|
+
#footer-container {
|
|
108
|
+
dock: bottom;
|
|
109
|
+
height: 1;
|
|
110
|
+
background: $background;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Footer {
|
|
114
|
+
width: auto;
|
|
115
|
+
background: $background;
|
|
116
|
+
color: $text;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#status-text {
|
|
120
|
+
dock: right;
|
|
121
|
+
width: auto;
|
|
122
|
+
padding: 0 1;
|
|
123
|
+
background: $background;
|
|
124
|
+
color: $text;
|
|
125
|
+
text-align: right;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#status-line {
|
|
129
|
+
dock: right;
|
|
130
|
+
width: auto;
|
|
131
|
+
padding: 0 1;
|
|
132
|
+
background: $background;
|
|
133
|
+
color: $text;
|
|
134
|
+
text-align: right;
|
|
135
|
+
}
|
xp/term/protocol.yml
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
protocol:
|
|
2
|
+
"1":
|
|
3
|
+
name: "Discover"
|
|
4
|
+
telegrams:
|
|
5
|
+
- S0000000000F01D00
|
|
6
|
+
"2":
|
|
7
|
+
name: "Error Code"
|
|
8
|
+
telegrams:
|
|
9
|
+
- S0020044966F02D10
|
|
10
|
+
"3":
|
|
11
|
+
name: "Module Type"
|
|
12
|
+
telegrams:
|
|
13
|
+
- S0020044966F02D00
|
|
14
|
+
"4":
|
|
15
|
+
name: "Auto Report"
|
|
16
|
+
telegrams:
|
|
17
|
+
- S0020044966F02D21
|
|
18
|
+
"5":
|
|
19
|
+
name: "Link Number"
|
|
20
|
+
telegrams:
|
|
21
|
+
- S0020044966F02D04
|
|
22
|
+
"6":
|
|
23
|
+
name: "Blink On"
|
|
24
|
+
telegrams:
|
|
25
|
+
- S0020044966F01D01
|
|
26
|
+
"7":
|
|
27
|
+
name: "Blink Off"
|
|
28
|
+
telegrams:
|
|
29
|
+
- S0020044966F01D00
|
|
30
|
+
"8":
|
|
31
|
+
name: "Output 1 On"
|
|
32
|
+
telegrams:
|
|
33
|
+
- S0020044966F02101
|
|
34
|
+
"9":
|
|
35
|
+
name: "Output 1 Off"
|
|
36
|
+
telegrams:
|
|
37
|
+
- S0020044966F02100
|
|
38
|
+
"0":
|
|
39
|
+
name: "Output State"
|
|
40
|
+
telegrams:
|
|
41
|
+
- S0020044966F02D09
|
|
42
|
+
"a":
|
|
43
|
+
name: "Module State"
|
|
44
|
+
telegrams:
|
|
45
|
+
- S0020044966F02D09
|
|
46
|
+
"b":
|
|
47
|
+
name: "All Off"
|
|
48
|
+
telegrams:
|
|
49
|
+
- E02L00I00M
|
|
50
|
+
- E02L00I00B
|
|
51
|
+
"c":
|
|
52
|
+
name: "All On"
|
|
53
|
+
telegrams:
|
|
54
|
+
- E02L00I08M
|
|
55
|
+
- E02L00I08B
|
|
56
|
+
|
|
57
|
+
"d":
|
|
58
|
+
name: "Link 1 On"
|
|
59
|
+
telegrams:
|
|
60
|
+
- E02L01I08M
|
|
61
|
+
- E02L01I08B
|
|
62
|
+
|
|
63
|
+
"e":
|
|
64
|
+
name: "Link 1 Off"
|
|
65
|
+
telegrams:
|
|
66
|
+
- E02L01I00M
|
|
67
|
+
- E02L01I00B
|
|
68
|
+
|
|
69
|
+
"f":
|
|
70
|
+
name: "Link 2 On"
|
|
71
|
+
telegrams:
|
|
72
|
+
- E02L02I08M
|
|
73
|
+
- E02L02I08B
|
|
74
|
+
|
|
75
|
+
"g":
|
|
76
|
+
name: "Link 2 Off"
|
|
77
|
+
telegrams:
|
|
78
|
+
- E02L02I00M
|
|
79
|
+
- E02L02I00B
|
|
80
|
+
|
|
81
|
+
"h":
|
|
82
|
+
name: "Link 3 On"
|
|
83
|
+
telegrams:
|
|
84
|
+
- E02L03I08M
|
|
85
|
+
- E02L03I08B
|
|
86
|
+
|
|
87
|
+
"i":
|
|
88
|
+
name: "Link 3 Off"
|
|
89
|
+
telegrams:
|
|
90
|
+
- E02L03I00M
|
|
91
|
+
- E02L03I00B
|
|
92
|
+
|
|
93
|
+
"j":
|
|
94
|
+
name: "Link 4 On"
|
|
95
|
+
telegrams:
|
|
96
|
+
- E02L04I08M
|
|
97
|
+
- E02L04I08B
|
|
98
|
+
|
|
99
|
+
"k":
|
|
100
|
+
name: "Link 4 Off"
|
|
101
|
+
telegrams:
|
|
102
|
+
- E02L04I00M
|
|
103
|
+
- E02L04I00B
|
|
104
|
+
|
|
105
|
+
"l":
|
|
106
|
+
name: "Link 5 On"
|
|
107
|
+
telegrams:
|
|
108
|
+
- E02L05I08M
|
|
109
|
+
- E02L05I08B
|
|
110
|
+
|
|
111
|
+
"m":
|
|
112
|
+
name: "Link 5 Off"
|
|
113
|
+
telegrams:
|
|
114
|
+
- E02L05I00M
|
|
115
|
+
- E02L05I00B
|
|
116
|
+
|
|
117
|
+
"n":
|
|
118
|
+
name: "Link 6 On"
|
|
119
|
+
telegrams:
|
|
120
|
+
- E02L06I08M
|
|
121
|
+
- E02L06I08B
|
|
122
|
+
|
|
123
|
+
"o":
|
|
124
|
+
name: "Link 6 Off"
|
|
125
|
+
telegrams:
|
|
126
|
+
- E02L06I00M
|
|
127
|
+
- E02L06I00B
|
|
128
|
+
|
|
129
|
+
"p":
|
|
130
|
+
name: "Link 7 On"
|
|
131
|
+
telegrams:
|
|
132
|
+
- E02L07I08M
|
|
133
|
+
- E02L07I08B
|
|
134
|
+
|
|
135
|
+
"q":
|
|
136
|
+
name: "Link 7 Off"
|
|
137
|
+
telegrams:
|
|
138
|
+
- E02L07I00M
|
|
139
|
+
- E02L07I00B
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""TUI widgets package."""
|
|
2
|
+
|
|
3
|
+
from xp.term.widgets.help_menu import HelpMenuWidget
|
|
4
|
+
from xp.term.widgets.protocol_log import ProtocolLogWidget
|
|
5
|
+
from xp.term.widgets.status_footer import StatusFooterWidget
|
|
6
|
+
|
|
7
|
+
__all__ = ["HelpMenuWidget", "ProtocolLogWidget", "StatusFooterWidget"]
|