conson-xp 1.18.0__py3-none-any.whl → 1.20.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.18.0
3
+ Version: 1.20.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -48,6 +48,7 @@ Requires-Dist: punq>=0.7.0
48
48
  Requires-Dist: twisted>=25.5.0
49
49
  Requires-Dist: bubus>=1.5.6
50
50
  Requires-Dist: psygnal>=0.15.0
51
+ Requires-Dist: textual>=1.0.0
51
52
  Description-Content-Type: text/markdown
52
53
 
53
54
  # 🔌 XP Protocol Communication Tool
@@ -306,6 +307,7 @@ xp conbus datapoint query
306
307
  xp conbus discover
307
308
 
308
309
  xp conbus event
310
+ xp conbus event list
309
311
  xp conbus event raw
310
312
 
311
313
 
@@ -397,6 +399,10 @@ xp telegram parse
397
399
  xp telegram validate
398
400
  xp telegram version
399
401
 
402
+
403
+ xp term
404
+ xp term protocol
405
+
400
406
  <!-- END CLI HELP -->
401
407
  ```
402
408
  </details>
@@ -1,11 +1,11 @@
1
- conson_xp-1.18.0.dist-info/METADATA,sha256=tn61tFoUuWRz3TkdNXxuq2b26S0ZtqqjYAmHYL6Cwqg,9506
2
- conson_xp-1.18.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- conson_xp-1.18.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.18.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=F3DFS5EiPFPNOhbV20K8kGI6O9d6SQGEopDRegtFQZI,181
1
+ conson_xp-1.20.0.dist-info/METADATA,sha256=vAvQzvNII8d7oxEEbo1rSLyswX3lROWxh7cERQ-y3_Y,9584
2
+ conson_xp-1.20.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-1.20.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.20.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=T304nC2FLiJmOCyBS3xq0x0Bb4Nv_XAMMRN4xZtqUec,181
6
6
  xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
7
7
  xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
8
- xp/cli/commands/__init__.py,sha256=wvo9Z5viwpjvO2432E7YP5HWjLLiW1IFpyXLc5puuGY,4766
8
+ xp/cli/commands/__init__.py,sha256=noh8fdZAWq-ihJEboP8WugbIgq4LJ3jUWMRA7720xWE,4909
9
9
  xp/cli/commands/conbus/__init__.py,sha256=gE3K5OEoXkkZX8UOc2v3nreQQzwkOQi7n0VZ-Z2juXA,495
10
10
  xp/cli/commands/conbus/conbus.py,sha256=eqdY8ArapvD08Z4p7Xk7eh4z0dESHuMSw7PKtwTJRYU,3021
11
11
  xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=cdjLV9cnm7teEOlu5Jf1MS_aL7lNy8KiDIyjCQa5Nzw,7138
@@ -15,14 +15,14 @@ xp/cli/commands/conbus/conbus_config_commands.py,sha256=BugIbgNX6_s4MySGvI6tWZkw
15
15
  xp/cli/commands/conbus/conbus_custom_commands.py,sha256=lICT93ijMdhVRm8KjNMLo7kQ2BLlnOZvMPbR3SxSmZ4,1692
16
16
  xp/cli/commands/conbus/conbus_datapoint_commands.py,sha256=r36OuTjREtbGKL-bskAGa0-WLw7x06td6woZn3GYJNA,3630
17
17
  xp/cli/commands/conbus/conbus_discover_commands.py,sha256=-y3TDgOnw1_cjvxvgyfQ1GQE2_WmYq-l8Md7DsdTXmo,1719
18
- xp/cli/commands/conbus/conbus_event_commands.py,sha256=nhWnPIIWTD9EFp_WiyaUAMlr8q20WW3SvyJ0LiqTu88,2373
18
+ xp/cli/commands/conbus/conbus_event_commands.py,sha256=7URf-2u8Kzcy0chLYShbZfCbKawf--i-8U88AjhxleQ,3177
19
19
  xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=FpCwogdxa7yFUjlrxM7e8Q2Ut32tKAHabngQQChvtJI,6763
20
20
  xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=KitaGDM5HpwVUz8rLpO8VZUypUTcAg3Bzl0DVm6gnSk,3391
21
21
  xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=L7-6y3rDllOjQ9g6Bk_RiTKIhAOHVPLdxWif9exkngs,3463
22
22
  xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=fb9MQ4O04H0Dinpt7vSF5GtfntTZHelQ5TuUmSBbCTg,2899
23
23
  xp/cli/commands/conbus/conbus_output_commands.py,sha256=zdRVbHzVhMbZpG2x5WXtujc3wKTsoQUV4IgkVIbJbCc,5019
24
24
  xp/cli/commands/conbus/conbus_raw_commands.py,sha256=8BKUarwvHgz-sxML7n99YVsb8B1HJNExjQpRsuY_tQw,1829
25
- xp/cli/commands/conbus/conbus_receive_commands.py,sha256=WdH7fYcbjweIGxD2uxrTRD8lJzViMSVdsaHTvSDJNCQ,1757
25
+ xp/cli/commands/conbus/conbus_receive_commands.py,sha256=2lZP0a3dte3Q_Vp28xYkqLAoxnvArS9SsQdeedOHcQw,1788
26
26
  xp/cli/commands/conbus/conbus_scan_commands.py,sha256=JfXucOwOadvLEKT_fW9fwvqWKHaEODOojLjnO8JV_00,1730
27
27
  xp/cli/commands/file_commands.py,sha256=GV102X7FRZDUNKLlzvSsIGcoXAaofOzmjCp3HUpE9lw,5532
28
28
  xp/cli/commands/homekit/__init__.py,sha256=qqwY8ulxTx1S74Mzpb6EKjBLT6fWTNdf9PQ3HKuERKY,50
@@ -40,7 +40,10 @@ xp/cli/commands/telegram/telegram_discover_commands.py,sha256=0UArJinw1eWFbee5EG
40
40
  xp/cli/commands/telegram/telegram_linknumber_commands.py,sha256=7j0-E5Moqqga4NrKDch82C6glaFDFMQn5_3hMwie7BQ,2511
41
41
  xp/cli/commands/telegram/telegram_parse_commands.py,sha256=_OYOso1hS4f_ox96qlkYL2SuFnmimpAvqqdYlLzX9yo,2232
42
42
  xp/cli/commands/telegram/telegram_version_commands.py,sha256=WQyx1-B9yJ8V9WrFyBpOvULJ-jq12GoZZDDoRbM7eyw,1553
43
- xp/cli/main.py,sha256=3TY4wZoKMK8kQBgOn0WshTsag4J4ofoGoGPgg12wueM,2810
43
+ xp/cli/commands/term/__init__.py,sha256=1NNST_8YJfj5LCujQISwQflK6LyEn7mDmZpMpvI9d-o,116
44
+ xp/cli/commands/term/term.py,sha256=gjvsv2OE-F_KNWQrWi04fXQ5cGo0l8P-Ortbb5KTA-A,309
45
+ xp/cli/commands/term/term_commands.py,sha256=3qwiGlEEgMIz5AG7fq5U_9SZSWSDfrKwYggHbiv5kRk,738
46
+ xp/cli/main.py,sha256=ap5jU0DrSnrCKDKqGXcz9N-sngZodyyN-5ReWE8Fh1s,1817
44
47
  xp/cli/utils/__init__.py,sha256=gTGIj60Uai0iE7sr9_TtEpl04fD7krtTzbbigXUsUVU,46
45
48
  xp/cli/utils/click_tree.py,sha256=ilmM2IMa_c-TqUMsv2alrZXuS0BNhvVlrBlSfyN8lzM,1670
46
49
  xp/cli/utils/datapoint_type_choice.py,sha256=HcydhlqxZ7YyorEeTjFGkypF2JnYNPvOzkl1rhZ93Fc,1666
@@ -53,7 +56,7 @@ xp/cli/utils/system_function_choice.py,sha256=0J02EMgAQcsrE-9rEkv6YHelBoBkZ73T8V
53
56
  xp/cli/utils/xp_module_type.py,sha256=qSFJBRceqPi_cUFPxAWtLUNq37-KwUEjo9ekYOj7kLQ,1471
54
57
  xp/connection/__init__.py,sha256=ClJsVWALYZgAGYZK_Jznd3YKLrHDu17kBfwugjuPfu0,209
55
58
  xp/connection/exceptions.py,sha256=7CcRUzkyay5zA6Z9-5dIDRzua806v5N7pCcJazP_1dE,365
56
- xp/models/__init__.py,sha256=UaUiuvWevneh9gPzKNaVsuy6rxM7YlZg4mi8VlEJpfg,1210
59
+ xp/models/__init__.py,sha256=lROqr559DGd8WpJJUtfPT95VERCwMZHpBDEc96QSxQ0,1312
57
60
  xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
58
61
  xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
59
62
  xp/models/actiontable/msactiontable_xp20.py,sha256=C_lYYIQagEFap0S5S40_S7AhLO2UZG2EmXjjeem7uw8,1967
@@ -63,14 +66,16 @@ xp/models/conbus/__init__.py,sha256=VIusMWQdBtlwDgj7oSj06wQkklihTp4oWFShvP_JUgA,
63
66
  xp/models/conbus/conbus.py,sha256=mZQzKPfrdttT-qUnYUSyrEYyc_eHs8z301E5ejeiyvk,2689
64
67
  xp/models/conbus/conbus_autoreport.py,sha256=lKotDfxRBb7h2Z1d4qI3KhhLJhFDwKqLbSdG5Makm8Y,2289
65
68
  xp/models/conbus/conbus_blink.py,sha256=XEAPtA-O76ulX6Zh1oYzsWF6L4css6xJBuUTwNcDQKc,2911
66
- xp/models/conbus/conbus_client_config.py,sha256=fWPmHM-OVUzSASKq667JzP7e9_Qp9ZUyYcTaijWkVlY,1484
69
+ xp/models/conbus/conbus_client_config.py,sha256=MliIffoP0Ku1FxpNznMJPBrpdz9OVKZcs9IJ4g1Z3dE,1485
67
70
  xp/models/conbus/conbus_connection_status.py,sha256=iGbmtBaAMwV6UD7XG3H3tnB0fl2MR8rJhpjrLH2KjsE,1097
68
71
  xp/models/conbus/conbus_custom.py,sha256=8H2sPR6_LIlksuOvL7-8bPkzAJLR0rpYiiwfYYFVjEo,1965
69
72
  xp/models/conbus/conbus_datapoint.py,sha256=4ncR-vB2lRzRBAA30rYn8eguyTxsZoOKrrXtjGmPpWg,3396
70
73
  xp/models/conbus/conbus_discover.py,sha256=nxxUEKfEsH1kd0BF8ovMs7zLujRhrq1oL9ZJtysPr5o,2238
74
+ xp/models/conbus/conbus_event_list.py,sha256=M8aHRHVB5VDIjqMzjO86xlERt7AMdfjIjt1b70RF52Y,958
71
75
  xp/models/conbus/conbus_event_raw.py,sha256=i5gc7z-0yeunWOZ4rw3AiBt4MANezmhBQKjOOQk3oDc,1567
72
76
  xp/models/conbus/conbus_lightlevel.py,sha256=GQGhzrCBEJROosNHInXIzBy6MD2AskEIMoFEGgZ60-0,1695
73
77
  xp/models/conbus/conbus_linknumber.py,sha256=uFzKzfB06oIzZEKCb5X2JEI80JjMPFuYglsT1W1k8j4,1815
78
+ xp/models/conbus/conbus_logger_config.py,sha256=cFWjWn8tc_hPPI2kQAib_Akddar8O-3zkoj6wLBsdUo,3328
74
79
  xp/models/conbus/conbus_output.py,sha256=q7QKsD_CWT7YOk-V3otKWD1VM7qThrSLIUOunntMrMc,1953
75
80
  xp/models/conbus/conbus_raw.py,sha256=xqvYao6IE1SXum7JBgZpSuWXm9x_QZquS9N_3_r0Hjs,1460
76
81
  xp/models/conbus/conbus_receive.py,sha256=-1u1qK-texfKCNZV-GYf_9KyLtJdIrx7HuZsKzu26Ow,1322
@@ -120,10 +125,11 @@ xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFx
120
125
  xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=p9R02cVimhdJILHQ6BoeZj8Hog4oRpqBnMo3t4R8ecY,6816
121
126
  xp/services/conbus/conbus_datapoint_service.py,sha256=SYhHj9RmTmaJ750tyZ1IW2kl7tgDQ1xm_EM1zUjk1aQ,6421
122
127
  xp/services/conbus/conbus_discover_service.py,sha256=sSCSDNWWGtx5QOShwJfcbG54WCYH-BxWvgE10ghibN4,12326
128
+ xp/services/conbus/conbus_event_list_service.py,sha256=0xyXXNU44epN5bFkU6oiZMyhxfUguul3evqClvPJDcA,3618
123
129
  xp/services/conbus/conbus_event_raw_service.py,sha256=FZFu-LNLInrTKTpiGLyootozvyIF5Si5FMrxNk2ALD0,7000
124
130
  xp/services/conbus/conbus_output_service.py,sha256=mHFOAPx2zo0TStZ3pokp6v94AQjIamcwZDeg5YH_-eo,7240
125
131
  xp/services/conbus/conbus_raw_service.py,sha256=4yZLLTIAOxpgByUTWZXw1ihGa6Xtl98ckj9T7VfprDI,4335
126
- xp/services/conbus/conbus_receive_service.py,sha256=frXrS0OyKKvYYQTWdma21Kd0BKw5aSuHn3ZXTTqOaj0,3953
132
+ xp/services/conbus/conbus_receive_service.py,sha256=38lAZ0tc2AjBfcqI7qje-ES_QHiHZ3Ayybrp1ZC8ceM,5412
127
133
  xp/services/conbus/conbus_scan_service.py,sha256=tHJ5qaxcNXxAZb2D2F1v6IrzydfxjJOYllM6Txt1eBE,5176
128
134
  xp/services/conbus/write_config_service.py,sha256=6feNdixI_Nli4MRLe15nea-7gTEXMUwZIvTqv_1OqHI,7157
129
135
  xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
@@ -143,7 +149,7 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
143
149
  xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
144
150
  xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
145
151
  xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
146
- xp/services/protocol/conbus_event_protocol.py,sha256=6ihDsWj5k08Hb3OpYd3xBZCS-yPa16FfWtFSxJknIwo,12852
152
+ xp/services/protocol/conbus_event_protocol.py,sha256=48KCTkJLDHV1ijVXHf0TraY663Nk3_dEV3lkZpvduDo,13671
147
153
  xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
148
154
  xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
149
155
  xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
@@ -167,10 +173,16 @@ xp/services/telegram/telegram_link_number_service.py,sha256=1_c-_QCRPTHYn3BmMElr
167
173
  xp/services/telegram/telegram_output_service.py,sha256=UaUv_14fR8o5K2PxQBXrCzx-Hohnk-gzbev_oLw_Clc,10799
168
174
  xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmXDOsU4Xl8BlY,13237
169
175
  xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
176
+ xp/tui/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
177
+ xp/tui/app.py,sha256=NBCdFCgftckey5TgxDFn-SeOLSFggapshIwKDcLM5QY,2227
178
+ xp/tui/protocol.tcss,sha256=njMgFgz4oD4Qjw3dyoX1SfCuvlfGg6QrrkZ2COFZ0yM,768
179
+ xp/tui/widgets/__init__.py,sha256=Ewiza9u6k5K50zZRIMD7jjOHY1IvGhoX1ViwlqhdGms,27
180
+ xp/tui/widgets/protocol_log.py,sha256=0twJHiOTNcOOw3eQtYkjb1x37adecypPPPT1fuWb2q4,11541
170
181
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
171
182
  xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
172
- xp/utils/dependencies.py,sha256=QsZfPDMdlrQK01YQ4PRQ8Q59ZF9w22h1evWX3J4xCjE,20930
183
+ xp/utils/dependencies.py,sha256=ECS6p0eXzocM5INLwJeckHXn_Dim18uOjXTJ29qQvkQ,22001
173
184
  xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
185
+ xp/utils/logging.py,sha256=5ol2JrnFtjs9QtBYW4KeKYzcFzCxbCn8BsIq3aIF4H4,3395
174
186
  xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
175
187
  xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
176
- conson_xp-1.18.0.dist-info/RECORD,,
188
+ conson_xp-1.20.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.5)
2
+ Generator: pdm-backend (2.4.6)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
xp/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.18.0"
6
+ __version__ = "1.20.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -86,6 +86,8 @@ from xp.cli.commands.telegram.telegram_parse_commands import (
86
86
  validate_telegram,
87
87
  )
88
88
  from xp.cli.commands.telegram.telegram_version_commands import generate_version_request
89
+ from xp.cli.commands.term.term import term
90
+ from xp.cli.commands.term.term_commands import protocol_monitor
89
91
 
90
92
  __all__ = [
91
93
  # Main command groups (conbus excluded to avoid module shadowing)
@@ -109,7 +111,9 @@ __all__ = [
109
111
  "checksum",
110
112
  "homekit",
111
113
  "homekit_start",
114
+ "term",
112
115
  # Individual command functions
116
+ "protocol_monitor",
113
117
  "conbus_download_msactiontable",
114
118
  "conbus_download_actiontable",
115
119
  "send_blink_on_telegram",
@@ -8,6 +8,7 @@ from xp.cli.commands.conbus.conbus import conbus
8
8
  from xp.cli.utils.decorators import connection_command
9
9
  from xp.cli.utils.module_type_choice import MODULE_TYPE
10
10
  from xp.models import ConbusEventRawResponse
11
+ from xp.services.conbus.conbus_event_list_service import ConbusEventListService
11
12
  from xp.services.conbus.conbus_event_raw_service import ConbusEventRawService
12
13
 
13
14
 
@@ -17,6 +18,30 @@ def conbus_event() -> None:
17
18
  pass
18
19
 
19
20
 
21
+ @conbus_event.command("list")
22
+ @click.pass_context
23
+ def list_events(ctx: click.Context) -> None:
24
+ r"""List configured event telegrams from module action tables.
25
+
26
+ Reads conson.yml configuration, parses action tables, and groups
27
+ modules by their event keys to show which modules are assigned to
28
+ each event (button configuration).
29
+
30
+ Output is sorted by module count (most frequently used events first).
31
+
32
+ Args:
33
+ ctx: Click context object.
34
+
35
+ Examples:
36
+ \b
37
+ xp conbus event list
38
+ """
39
+ service: ConbusEventListService = (
40
+ ctx.obj.get("container").get_container().resolve(ConbusEventListService)
41
+ )
42
+ click.echo(json.dumps(service.list_events().to_dict(), indent=2))
43
+
44
+
20
45
  @conbus_event.command("raw")
21
46
  @click.argument("module_type", type=MODULE_TYPE)
22
47
  @click.argument("link_number", type=click.IntRange(0, 99))
@@ -56,4 +56,5 @@ def receive_telegrams(ctx: Context, timeout: float) -> None:
56
56
  ctx.obj.get("container").get_container().resolve(ConbusReceiveService)
57
57
  )
58
58
  with service:
59
- service.start(progress, on_finish, timeout)
59
+ service.init(progress, on_finish, timeout)
60
+ service.start_reactor()
@@ -0,0 +1,5 @@
1
+ """Term CLI commands package for TUI interfaces."""
2
+
3
+ from xp.cli.commands.term.term import term
4
+
5
+ __all__ = ["term"]
@@ -0,0 +1,12 @@
1
+ """Term CLI group definition for TUI commands."""
2
+
3
+ import click
4
+ from click_help_colors import HelpColorsGroup
5
+
6
+
7
+ @click.group(
8
+ cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
9
+ )
10
+ def term() -> None:
11
+ """Terminal UI commands for interactive monitoring and control."""
12
+ pass
@@ -0,0 +1,31 @@
1
+ """Term protocol CLI command for TUI monitoring."""
2
+
3
+ import click
4
+ from click import Context
5
+
6
+ from xp.cli.commands.term.term import term
7
+
8
+
9
+ @term.command("protocol")
10
+ @click.pass_context
11
+ def protocol_monitor(ctx: Context) -> None:
12
+ r"""Start TUI for real-time protocol monitoring.
13
+
14
+ Displays live RX/TX telegram stream from Conbus server
15
+ in an interactive terminal interface.
16
+
17
+ Args:
18
+ ctx: Click context object.
19
+
20
+ Examples:
21
+ \b
22
+ xp term protocol
23
+ """
24
+ from xp.tui.app import ProtocolMonitorApp
25
+
26
+ # Resolve ServiceContainer from context
27
+ container = ctx.obj.get("container").get_container()
28
+
29
+ # Initialize and run Textual app
30
+ app = ProtocolMonitorApp(container=container)
31
+ app.run()
xp/cli/main.py CHANGED
@@ -1,7 +1,5 @@
1
1
  """XP CLI tool entry point with modular command structure."""
2
2
 
3
- import logging
4
-
5
3
  import click
6
4
  from click_help_colors import HelpColorsGroup
7
5
 
@@ -16,8 +14,10 @@ from xp.cli.commands.server.server_commands import server
16
14
 
17
15
  # Import command groups from modular structure
18
16
  from xp.cli.commands.telegram.telegram_parse_commands import telegram
17
+ from xp.cli.commands.term.term import term
19
18
  from xp.cli.utils.click_tree import add_tree_command
20
19
  from xp.utils.dependencies import ServiceContainer
20
+ from xp.utils.logging import LoggerService
21
21
 
22
22
 
23
23
  @click.group(
@@ -31,44 +31,15 @@ def cli(ctx: click.Context) -> None:
31
31
  Args:
32
32
  ctx: Click context object for passing state between commands.
33
33
  """
34
- # Configure logging with thread information
35
- log_format = "%(asctime)s - [%(threadName)s-%(thread)d] - %(levelname)s - %(name)s - %(message)s"
36
- date_format = "%H:%M:%S"
37
-
38
- # Force format on root logger and all handlers
39
- formatter = logging.Formatter(log_format, datefmt=date_format)
40
- root_logger = logging.getLogger()
41
- root_logger.setLevel(logging.DEBUG)
42
-
43
- # Update all existing handlers or create new one
44
- if root_logger.handlers:
45
- for handler in root_logger.handlers:
46
- handler.setFormatter(formatter)
47
- else:
48
- handler = logging.StreamHandler()
49
- handler.setFormatter(formatter)
50
- root_logger.addHandler(handler)
51
-
52
- # Suppress pyhap.hap_protocol logs
53
-
54
- # bubus
55
- logging.getLogger("bubus").setLevel(logging.WARNING)
56
-
57
- # xp
58
- logging.getLogger("xp").setLevel(logging.DEBUG)
59
- logging.getLogger("xp.services.homekit").setLevel(logging.DEBUG)
60
-
61
- # pyhap
62
- logging.getLogger("pyhap").setLevel(logging.WARNING)
63
- logging.getLogger("pyhap.hap_handler").setLevel(logging.WARNING)
64
- logging.getLogger("pyhap.hap_protocol").setLevel(logging.WARNING)
65
- # logging.getLogger('pyhap.accessory_driver').setLevel(logging.WARNING)
34
+ container = ServiceContainer()
35
+ logger_config = container.get_container().resolve(LoggerService)
36
+ logger_config.setup()
66
37
 
67
38
  # Initialize the service container and store it in the context
68
39
  ctx.ensure_object(dict)
69
40
  # Only create a new container if one wasn't provided (e.g., for testing)
70
41
  if "container" not in ctx.obj:
71
- ctx.obj["container"] = ServiceContainer()
42
+ ctx.obj["container"] = container
72
43
 
73
44
 
74
45
  # Register all command groups
@@ -79,6 +50,7 @@ cli.add_command(module)
79
50
  cli.add_command(file)
80
51
  cli.add_command(server)
81
52
  cli.add_command(reverse_proxy)
53
+ cli.add_command(term)
82
54
 
83
55
  # Add the tree command
84
56
  add_tree_command(cli)
xp/models/__init__.py CHANGED
@@ -5,6 +5,7 @@ from xp.models.conbus.conbus_client_config import ConbusClientConfig
5
5
  from xp.models.conbus.conbus_connection_status import ConbusConnectionStatus
6
6
  from xp.models.conbus.conbus_datapoint import ConbusDatapointResponse
7
7
  from xp.models.conbus.conbus_discover import ConbusDiscoverResponse
8
+ from xp.models.conbus.conbus_event_list import ConbusEventListResponse
8
9
  from xp.models.conbus.conbus_event_raw import ConbusEventRawResponse
9
10
  from xp.models.log_entry import LogEntry
10
11
  from xp.models.telegram.event_telegram import EventTelegram
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "ConbusResponse",
32
33
  "ConbusDatapointResponse",
33
34
  "ConbusDiscoverResponse",
35
+ "ConbusEventListResponse",
34
36
  "ConbusEventRawResponse",
35
37
  "ConbusConnectionStatus",
36
38
  ]
@@ -49,6 +49,7 @@ class ConbusClientConfig(BaseModel):
49
49
  except FileNotFoundError:
50
50
  logger.error(f"File {file_path} does not exist, loading default")
51
51
  return cls()
52
+
52
53
  except yaml.YAMLError:
53
54
  logger.error(f"File {file_path} is not valid")
54
55
  # Return default config if YAML parsing fails
@@ -0,0 +1,34 @@
1
+ """Conbus event list response model."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Any, Dict, Optional
6
+
7
+
8
+ @dataclass
9
+ class ConbusEventListResponse:
10
+ """Represents a response from Conbus event list operation.
11
+
12
+ Attributes:
13
+ events: Dict mapping event keys to list of module names.
14
+ timestamp: Timestamp of the response.
15
+ """
16
+
17
+ events: Dict[str, list[str]]
18
+ timestamp: Optional[datetime] = None
19
+
20
+ def __post_init__(self) -> None:
21
+ """Initialize timestamp if not provided."""
22
+ if self.timestamp is None:
23
+ self.timestamp = datetime.now()
24
+
25
+ def to_dict(self) -> Dict[str, Any]:
26
+ """Convert to dictionary for JSON serialization.
27
+
28
+ Returns:
29
+ Dictionary representation of the response.
30
+ """
31
+ return {
32
+ "events": self.events,
33
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
34
+ }
@@ -0,0 +1,107 @@
1
+ """Logger configuration models for XP application."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Dict, Union
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field, field_validator
9
+
10
+
11
+ class LoggingConfig(BaseModel):
12
+ """Logging configuration.
13
+
14
+ Attributes:
15
+ path: log folder.
16
+ default_level: DEBUG, WARNING, INFO, ERROR, CRITICAL.
17
+ levels: Per-module log level overrides.
18
+ max_bytes: Maximum size in bytes before rotating (default: 1MB).
19
+ backup_count: Number of backup files to keep (default: 365).
20
+ log_format: Log message format string.
21
+ date_format: Date format string for timestamps.
22
+ """
23
+
24
+ path: str = "log"
25
+ default_level: str = "DEBUG"
26
+ levels: Dict[str, int] = {
27
+ "xp": logging.DEBUG,
28
+ "xp.services.homekit": logging.WARNING,
29
+ "xp.services.server": logging.WARNING,
30
+ }
31
+ max_bytes: int = 1024 * 1024 # 1MB
32
+ backup_count: int = 365
33
+ log_format: str = (
34
+ "%(asctime)s - [%(threadName)s-%(thread)d] - %(levelname)s - %(name)s - %(message)s"
35
+ )
36
+ date_format: str = "%H:%M:%S"
37
+
38
+ @field_validator("levels", mode="before")
39
+ @classmethod
40
+ def convert_level_names(cls, v: Dict[str, Union[str, int]]) -> Dict[str, int]:
41
+ """Convert string level names to numeric values.
42
+
43
+ Args:
44
+ v: Dictionary with string or int log levels.
45
+
46
+ Returns:
47
+ Dictionary with numeric log levels.
48
+
49
+ Raises:
50
+ ValueError: If an invalid log level name is provided.
51
+ """
52
+ level_map = {
53
+ "DEBUG": logging.DEBUG,
54
+ "INFO": logging.INFO,
55
+ "WARNING": logging.WARNING,
56
+ "ERROR": logging.ERROR,
57
+ "CRITICAL": logging.CRITICAL,
58
+ }
59
+
60
+ result = {}
61
+ for module, level in v.items():
62
+ if isinstance(level, str):
63
+ level_upper = level.upper()
64
+ if level_upper not in level_map:
65
+ raise ValueError(
66
+ f"Invalid log level '{level}' for module '{module}'. "
67
+ f"Must be one of: {', '.join(level_map.keys())}"
68
+ )
69
+ result[module] = level_map[level_upper]
70
+ else:
71
+ result[module] = level
72
+ return result
73
+
74
+
75
+ class ConbusLoggerConfig(BaseModel):
76
+ """Logging configuration.
77
+
78
+ Attributes:
79
+ log: LoggingConfig instance for logging settings.
80
+ """
81
+
82
+ log: LoggingConfig = Field(default_factory=LoggingConfig)
83
+
84
+ @classmethod
85
+ def from_yaml(cls, file_path: str) -> "ConbusLoggerConfig":
86
+ """Load configuration from YAML file.
87
+
88
+ Args:
89
+ file_path: Path to the YAML configuration file.
90
+
91
+ Returns:
92
+ ConbusClientConfig instance loaded from file or default config.
93
+ """
94
+ logger = logging.getLogger(__name__)
95
+ try:
96
+ with Path(file_path).open("r") as file:
97
+ data = yaml.safe_load(file)
98
+ return cls(**data)
99
+
100
+ except FileNotFoundError:
101
+ logger.error(f"File {file_path} does not exist, loading default")
102
+ return cls()
103
+
104
+ except yaml.YAMLError:
105
+ logger.error(f"File {file_path} is not valid")
106
+ # Return default config if YAML parsing fails
107
+ return cls()
@@ -0,0 +1,91 @@
1
+ """Conbus Event List Service for listing configured event telegrams.
2
+
3
+ This service parses action tables from conson.yml and groups events
4
+ by button configuration to show which modules are assigned to each event.
5
+ """
6
+
7
+ import logging
8
+ from collections import defaultdict
9
+ from typing import Dict, List
10
+
11
+ from xp.models import ConbusEventListResponse
12
+ from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
13
+ from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
14
+
15
+
16
+ class ConbusEventListService:
17
+ """Service for listing configured event telegrams from action tables.
18
+
19
+ Parses action tables from conson.yml configuration and groups modules
20
+ by their event keys to identify common button configurations.
21
+
22
+ Attributes:
23
+ conson_config: Configuration containing module action tables.
24
+ logger: Logger instance for the service.
25
+ """
26
+
27
+ def __init__(self, conson_config: ConsonModuleListConfig) -> None:
28
+ """Initialize the Conbus event list service.
29
+
30
+ Args:
31
+ conson_config: ConsonModuleListConfig instance with module action tables.
32
+ """
33
+ self.conson_config = conson_config
34
+ self.logger = logging.getLogger(__name__)
35
+
36
+ def list_events(self) -> ConbusEventListResponse:
37
+ """List all configured events from module action tables.
38
+
39
+ Parses action tables, extracts event information (module_type, link, input),
40
+ groups modules by event key, and sorts by usage count.
41
+
42
+ Returns:
43
+ ConbusEventListResponse with events dict mapping event keys to module names.
44
+ """
45
+ # Dict to track which modules are assigned to each event
46
+ # event_key -> set of module names (using set for automatic deduplication)
47
+ event_modules: Dict[str, set[str]] = defaultdict(set)
48
+
49
+ # Process each module's action table
50
+ for module in self.conson_config.root:
51
+ # Skip modules without action table
52
+ if not module.action_table:
53
+ continue
54
+
55
+ # Process each action in the module's action table
56
+ for action in module.action_table:
57
+ try:
58
+ # Use existing ActionTableSerializer to parse action
59
+ entry = ActionTableSerializer.parse_action_string(action)
60
+
61
+ # Extract event data from parsed entry
62
+ module_type_name = entry.module_type.name
63
+ link = entry.link_number
64
+ input_num = entry.module_input
65
+
66
+ # Create event key (space-separated format)
67
+ event_key = f"{module_type_name} {link:02d} {input_num:02d}"
68
+
69
+ # Add this module to the event (set automatically deduplicates)
70
+ event_modules[event_key].add(
71
+ f"{module.serial_number}:{entry.module_output}"
72
+ )
73
+
74
+ except ValueError as e:
75
+ # Invalid action format - log warning and skip
76
+ self.logger.warning(
77
+ f"Invalid action '{action}' in module '{module.serial_number}': {e}"
78
+ )
79
+ continue
80
+
81
+ # Convert sets to sorted lists and sort events by module count (descending)
82
+ events_dict: Dict[str, List[str]] = {
83
+ event_key: sorted(list(modules))
84
+ for event_key, modules in sorted(
85
+ event_modules.items(),
86
+ key=lambda item: len(item[1]),
87
+ reverse=True,
88
+ )
89
+ }
90
+
91
+ return ConbusEventListResponse(events=events_dict)