conson-xp 1.21.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.21.0
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
61
61
  [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
62
62
 
63
- > **A powerful Python CLI and API toolkit for CONSON XP Protocol operations**
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
- FastAPI REST endpoints and comprehensive type safety
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
- ### 🌐 API & Integration
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 → API Layer → Services → Models → Connection 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+ • FastAPI • Pydantic • Click • HAP-python
425
+ **Requirements**: Python 3.10+ • Pydantic • Click • HAP-python
411
426
 
412
427
  ## License
413
428
 
@@ -1,8 +1,8 @@
1
- conson_xp-1.21.0.dist-info/METADATA,sha256=HK1EyLvZ5dYaq4J30bnPy8dCvOHt7Zg7eZEI_6yjAbk,9584
2
- conson_xp-1.21.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-1.21.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.21.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=XRUlxeaLq3T5GvQqiQm4B3VDGuqGInrFa511pFvemqI,181
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=5LwxOp1oHqki3iWPHXm6enPCF4De6uDg5N1QDVxjROs,739
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
@@ -176,11 +176,13 @@ xp/services/telegram/telegram_output_service.py,sha256=UaUv_14fR8o5K2PxQBXrCzx-H
176
176
  xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmXDOsU4Xl8BlY,13237
177
177
  xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
178
178
  xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
179
- xp/term/app.py,sha256=vrC5VJbw1tlCvKnLR0n9NvGWaJx44yz0gG6k3--BaNk,5652
179
+ xp/term/protocol.py,sha256=ERntzYvNMyI-VDCYW7EnhpPu-V6WPmyEk0xcQfm7LFM,4399
180
180
  xp/term/protocol.tcss,sha256=biaIv6X-bmD1tbXENFUNsXOF8ABkuoRAUgeSDqwudtQ,2126
181
- xp/term/protocol.yml,sha256=vPtTsMdasWiruj8iqqQX4f4ZtQdJ5wA39IJ-et3qiZc,2110
182
- xp/term/widgets/__init__.py,sha256=Ewiza9u6k5K50zZRIMD7jjOHY1IvGhoX1ViwlqhdGms,27
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
183
184
  xp/term/widgets/protocol_log.py,sha256=yuSfc61azgQmWQxLvBcmVik3cJAW1Hocj2Sk8qr3hSg,14343
185
+ xp/term/widgets/status_footer.py,sha256=VTN5owCprMbYmNiEbNWS_4CE8yxysDi8IBCECuU9EQY,1663
184
186
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
185
187
  xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
186
188
  xp/utils/dependencies.py,sha256=ECS6p0eXzocM5INLwJeckHXn_Dim18uOjXTJ29qQvkQ,22001
@@ -189,4 +191,4 @@ xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
189
191
  xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
190
192
  xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
191
193
  xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
192
- conson_xp-1.21.0.dist-info/RECORD,,
194
+ conson_xp-1.22.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.21.0"
6
+ __version__ = "1.22.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -21,7 +21,7 @@ def protocol_monitor(ctx: Context) -> None:
21
21
  \b
22
22
  xp term protocol
23
23
  """
24
- from xp.term.app import ProtocolMonitorApp
24
+ from xp.term.protocol import ProtocolMonitorApp
25
25
 
26
26
  # Resolve ServiceContainer from context
27
27
  container = ctx.obj.get("container").get_container()
@@ -4,11 +4,12 @@ from pathlib import Path
4
4
  from typing import Any, Optional
5
5
 
6
6
  from textual.app import App, ComposeResult
7
- from textual.containers import Horizontal, Vertical
8
- from textual.widgets import DataTable, Footer, Static
7
+ from textual.containers import Horizontal
9
8
 
10
9
  from xp.models.term import ProtocolKeysConfig
10
+ from xp.term.widgets.help_menu import HelpMenuWidget
11
11
  from xp.term.widgets.protocol_log import ProtocolLogWidget
12
+ from xp.term.widgets.status_footer import StatusFooterWidget
12
13
 
13
14
 
14
15
  class ProtocolMonitorApp(App[None]):
@@ -45,9 +46,8 @@ class ProtocolMonitorApp(App[None]):
45
46
  super().__init__()
46
47
  self.container = container
47
48
  self.protocol_widget: Optional[ProtocolLogWidget] = None
48
- self.status_widget: Optional[Static] = None
49
- self.status_text_widget: Optional[Static] = None
50
- self.help_table: Optional[DataTable] = None
49
+ self.help_menu: Optional[HelpMenuWidget] = None
50
+ self.footer_widget: Optional[StatusFooterWidget] = None
51
51
  self.protocol_keys = self._load_protocol_keys()
52
52
 
53
53
  def _load_protocol_keys(self) -> ProtocolKeysConfig:
@@ -70,18 +70,13 @@ class ProtocolMonitorApp(App[None]):
70
70
  yield self.protocol_widget
71
71
 
72
72
  # Help menu (hidden by default)
73
- help_container = Vertical(id="help-menu")
74
- help_container.border_title = "Help menu"
75
- help_container.can_focus = False
76
- with help_container:
77
- self.help_table = DataTable(id="help-table", show_header=False)
78
- self.help_table.can_focus = False
79
- yield self.help_table
80
-
81
- with Horizontal(id="footer-container"):
82
- yield Footer()
83
- self.status_widget = Static("○", id="status-line")
84
- yield self.status_widget
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
85
80
 
86
81
  def action_toggle_connection(self) -> None:
87
82
  """Toggle connection on 'c' key press.
@@ -122,37 +117,11 @@ class ProtocolMonitorApp(App[None]):
122
117
  self._update_status,
123
118
  )
124
119
 
125
- # Initialize help table
126
- if self.help_table:
127
- self.help_table.add_columns("Key", "Command")
128
- for key, config in self.protocol_keys.protocol.items():
129
- self.help_table.add_row(key, config.name)
130
-
131
120
  def _update_status(self, state: Any) -> None:
132
121
  """Update status line with connection state.
133
122
 
134
123
  Args:
135
124
  state: Current connection state.
136
125
  """
137
- if self.status_widget:
138
- # Map states to colored dots
139
- status_map = {
140
- "CONNECTED": "[green]●[/green]",
141
- "CONNECTING": "[yellow]●[/yellow]",
142
- "DISCONNECTING": "[yellow]●[/yellow]",
143
- "FAILED": "[red]●[/red]",
144
- "DISCONNECTED": "○",
145
- }
146
- dot = status_map.get(state.value, "○")
147
- self.status_widget.update(dot)
148
-
149
- def on_protocol_log_widget_status_message_changed(
150
- self, message: ProtocolLogWidget.StatusMessageChanged
151
- ) -> None:
152
- """Handle status message changes from protocol widget.
153
-
154
- Args:
155
- message: Message containing the status text.
156
- """
157
- if self.status_text_widget:
158
- self.status_text_widget.update(message.message)
126
+ if self.footer_widget:
127
+ self.footer_widget.update_status(state)
xp/term/protocol.yml CHANGED
@@ -55,85 +55,85 @@ protocol:
55
55
  - E02L00I08B
56
56
 
57
57
  "d":
58
- name: "All 1 On"
58
+ name: "Link 1 On"
59
59
  telegrams:
60
60
  - E02L01I08M
61
61
  - E02L01I08B
62
62
 
63
63
  "e":
64
- name: "All 1 Off"
64
+ name: "Link 1 Off"
65
65
  telegrams:
66
66
  - E02L01I00M
67
67
  - E02L01I00B
68
68
 
69
69
  "f":
70
- name: "All 2 On"
70
+ name: "Link 2 On"
71
71
  telegrams:
72
72
  - E02L02I08M
73
73
  - E02L02I08B
74
74
 
75
75
  "g":
76
- name: "All 2 Off"
76
+ name: "Link 2 Off"
77
77
  telegrams:
78
78
  - E02L02I00M
79
79
  - E02L02I00B
80
80
 
81
81
  "h":
82
- name: "All 3 On"
82
+ name: "Link 3 On"
83
83
  telegrams:
84
84
  - E02L03I08M
85
85
  - E02L03I08B
86
86
 
87
87
  "i":
88
- name: "All 3 Off"
88
+ name: "Link 3 Off"
89
89
  telegrams:
90
90
  - E02L03I00M
91
91
  - E02L03I00B
92
92
 
93
93
  "j":
94
- name: "All 4 On"
94
+ name: "Link 4 On"
95
95
  telegrams:
96
96
  - E02L04I08M
97
97
  - E02L04I08B
98
98
 
99
99
  "k":
100
- name: "All 4 Off"
100
+ name: "Link 4 Off"
101
101
  telegrams:
102
102
  - E02L04I00M
103
103
  - E02L04I00B
104
104
 
105
105
  "l":
106
- name: "All 5 On"
106
+ name: "Link 5 On"
107
107
  telegrams:
108
108
  - E02L05I08M
109
109
  - E02L05I08B
110
110
 
111
111
  "m":
112
- name: "All 5 Off"
112
+ name: "Link 5 Off"
113
113
  telegrams:
114
114
  - E02L05I00M
115
115
  - E02L05I00B
116
116
 
117
117
  "n":
118
- name: "All 6 On"
118
+ name: "Link 6 On"
119
119
  telegrams:
120
120
  - E02L06I08M
121
121
  - E02L06I08B
122
122
 
123
123
  "o":
124
- name: "All 6 Off"
124
+ name: "Link 6 Off"
125
125
  telegrams:
126
126
  - E02L06I00M
127
127
  - E02L06I00B
128
128
 
129
129
  "p":
130
- name: "All 7 On"
130
+ name: "Link 7 On"
131
131
  telegrams:
132
132
  - E02L07I08M
133
133
  - E02L07I08B
134
134
 
135
135
  "q":
136
- name: "All 7 Off"
136
+ name: "Link 7 Off"
137
137
  telegrams:
138
138
  - E02L07I00M
139
139
  - E02L07I00B
@@ -1 +1,7 @@
1
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"]
@@ -0,0 +1,55 @@
1
+ """Help Menu Widget for displaying keyboard shortcuts and protocol keys."""
2
+
3
+ from typing import Any
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Vertical
7
+ from textual.widgets import DataTable
8
+
9
+ from xp.models.term import ProtocolKeysConfig
10
+
11
+
12
+ class HelpMenuWidget(Vertical):
13
+ """Help menu widget displaying keyboard shortcuts and protocol keys.
14
+
15
+ Displays a table of available keyboard shortcuts mapped to their
16
+ corresponding protocol commands.
17
+
18
+ Attributes:
19
+ protocol_keys: Configuration of protocol keys and their telegrams.
20
+ help_table: DataTable widget for displaying key mappings.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ protocol_keys: ProtocolKeysConfig,
26
+ *args: Any,
27
+ **kwargs: Any,
28
+ ) -> None:
29
+ """Initialize the Help Menu widget.
30
+
31
+ Args:
32
+ protocol_keys: Configuration containing protocol key mappings.
33
+ args: Additional positional arguments for Vertical.
34
+ kwargs: Additional keyword arguments for Vertical.
35
+ """
36
+ super().__init__(*args, **kwargs)
37
+ self.protocol_keys = protocol_keys
38
+ self.help_table: DataTable = DataTable(id="help-table", show_header=False)
39
+ self.help_table.can_focus = False
40
+ self.border_title = "Help menu"
41
+ self.can_focus = False
42
+
43
+ def compose(self) -> ComposeResult:
44
+ """Compose the help menu layout.
45
+
46
+ Yields:
47
+ DataTable widget with key mappings.
48
+ """
49
+ yield self.help_table
50
+
51
+ def on_mount(self) -> None:
52
+ """Populate help table when widget mounts."""
53
+ self.help_table.add_columns("Key", "Command")
54
+ for key, config in self.protocol_keys.protocol.items():
55
+ self.help_table.add_row(key, config.name)
@@ -0,0 +1,53 @@
1
+ """Status Footer Widget for displaying app footer with connection status."""
2
+
3
+ from typing import Any
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Horizontal
7
+ from textual.widgets import Footer, Static
8
+
9
+
10
+ class StatusFooterWidget(Horizontal):
11
+ """Footer widget with connection status indicator.
12
+
13
+ Combines the Textual Footer with a status indicator dot that shows
14
+ the current connection state.
15
+
16
+ Attributes:
17
+ status_widget: Static widget displaying colored status dot.
18
+ """
19
+
20
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
21
+ """Initialize the Status Footer widget.
22
+
23
+ Args:
24
+ args: Additional positional arguments for Horizontal.
25
+ kwargs: Additional keyword arguments for Horizontal.
26
+ """
27
+ super().__init__(*args, **kwargs)
28
+ self.status_widget: Static = Static("○", id="status-line")
29
+
30
+ def compose(self) -> ComposeResult:
31
+ """Compose the footer layout.
32
+
33
+ Yields:
34
+ Footer and status indicator widgets.
35
+ """
36
+ yield Footer()
37
+ yield self.status_widget
38
+
39
+ def update_status(self, state: Any) -> None:
40
+ """Update status indicator with connection state.
41
+
42
+ Args:
43
+ state: Current connection state (ConnectionState enum).
44
+ """
45
+ # Map states to colored dots
46
+ dot = {
47
+ "CONNECTED": "[green]●[/green]",
48
+ "CONNECTING": "[yellow]●[/yellow]",
49
+ "DISCONNECTING": "[yellow]●[/yellow]",
50
+ "FAILED": "[red]●[/red]",
51
+ "DISCONNECTED": "○",
52
+ }.get(state.value, "○")
53
+ self.status_widget.update(dot)