conson-xp 1.24.0__tar.gz → 1.26.0__tar.gz

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.
Files changed (305) hide show
  1. {conson_xp-1.24.0 → conson_xp-1.26.0}/PKG-INFO +1 -1
  2. {conson_xp-1.24.0 → conson_xp-1.26.0}/pyproject.toml +2 -2
  3. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/__init__.py +1 -1
  4. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/term_commands.py +2 -6
  5. conson_xp-1.26.0/src/xp/models/term/telegram_display.py +19 -0
  6. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/conbus_event_protocol.py +14 -1
  7. conson_xp-1.26.0/src/xp/services/term/__init__.py +5 -0
  8. conson_xp-1.26.0/src/xp/services/term/protocol_monitor_service.py +265 -0
  9. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/protocol.py +22 -58
  10. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/help_menu.py +8 -7
  11. conson_xp-1.26.0/src/xp/term/widgets/protocol_log.py +81 -0
  12. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/status_footer.py +28 -4
  13. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/dependencies.py +37 -0
  14. conson_xp-1.26.0/tests/unit/test_services/test_conbus_event_protocol.py +146 -0
  15. conson_xp-1.26.0/tests/unit/test_services/test_protocol_monitor_service.py +153 -0
  16. conson_xp-1.26.0/tests/unit/test_tui/test_protocol_log.py +81 -0
  17. conson_xp-1.24.0/src/xp/term/protocol.yml +0 -139
  18. conson_xp-1.24.0/src/xp/term/widgets/protocol_log.py +0 -309
  19. conson_xp-1.24.0/tests/unit/test_tui/test_protocol_log.py +0 -233
  20. {conson_xp-1.24.0 → conson_xp-1.26.0}/LICENSE +0 -0
  21. {conson_xp-1.24.0 → conson_xp-1.26.0}/README.md +0 -0
  22. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/__init__.py +0 -0
  23. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/__main__.py +0 -0
  24. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/__init__.py +0 -0
  25. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
  26. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
  27. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
  28. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
  29. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
  30. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
  31. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
  32. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
  33. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
  34. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
  35. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
  36. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
  37. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
  38. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
  39. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
  40. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
  41. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
  42. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
  43. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/file_commands.py +0 -0
  44. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
  45. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
  46. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
  47. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/module_commands.py +0 -0
  48. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
  49. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/server/__init__.py +0 -0
  50. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/server/server_commands.py +0 -0
  51. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
  52. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
  53. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
  54. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
  55. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
  56. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
  57. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
  58. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
  59. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/__init__.py +0 -0
  60. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/term.py +0 -0
  61. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/main.py +0 -0
  62. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/__init__.py +0 -0
  63. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/click_tree.py +0 -0
  64. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
  65. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/decorators.py +0 -0
  66. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/error_handlers.py +0 -0
  67. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/formatters.py +0 -0
  68. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/module_type_choice.py +0 -0
  69. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/serial_number_type.py +0 -0
  70. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/system_function_choice.py +0 -0
  71. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/xp_module_type.py +0 -0
  72. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/connection/__init__.py +0 -0
  73. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/connection/exceptions.py +0 -0
  74. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/__init__.py +0 -0
  75. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/__init__.py +0 -0
  76. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/actiontable.py +0 -0
  77. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
  78. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
  79. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
  80. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/__init__.py +0 -0
  81. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus.py +0 -0
  82. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
  83. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_blink.py +0 -0
  84. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
  85. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
  86. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_custom.py +0 -0
  87. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
  88. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_discover.py +0 -0
  89. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
  90. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
  91. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
  92. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
  93. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
  94. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_output.py +0 -0
  95. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_raw.py +0 -0
  96. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_receive.py +0 -0
  97. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
  98. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/__init__.py +0 -0
  99. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
  100. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_config.py +0 -0
  101. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
  102. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/log_entry.py +0 -0
  103. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/protocol/__init__.py +0 -0
  104. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
  105. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/response.py +0 -0
  106. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/__init__.py +0 -0
  107. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/action_type.py +0 -0
  108. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/datapoint_type.py +0 -0
  109. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/event_telegram.py +0 -0
  110. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/event_type.py +0 -0
  111. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/input_action_type.py +0 -0
  112. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/input_type.py +0 -0
  113. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/module_type.py +0 -0
  114. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/module_type_code.py +0 -0
  115. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/output_telegram.py +0 -0
  116. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/reply_telegram.py +0 -0
  117. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/system_function.py +0 -0
  118. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/system_telegram.py +0 -0
  119. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/telegram.py +0 -0
  120. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/telegram_type.py +0 -0
  121. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/timeparam_type.py +0 -0
  122. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/__init__.py +0 -0
  123. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/connection_state.py +0 -0
  124. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/protocol_keys_config.py +0 -0
  125. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/status_message.py +0 -0
  126. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/write_config_type.py +0 -0
  127. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/__init__.py +0 -0
  128. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/__init__.py +0 -0
  129. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
  130. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
  131. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
  132. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
  133. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
  134. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/__init__.py +0 -0
  135. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
  136. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
  137. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
  138. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
  139. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
  140. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
  141. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
  142. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
  143. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
  144. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
  145. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
  146. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
  147. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
  148. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
  149. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
  150. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
  151. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
  152. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
  153. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/write_config_service.py +0 -0
  154. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/__init__.py +0 -0
  155. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
  156. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
  157. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
  158. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
  159. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
  160. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
  161. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
  162. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
  163. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
  164. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
  165. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
  166. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
  167. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_service.py +0 -0
  168. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/log_file_service.py +0 -0
  169. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/module_type_service.py +0 -0
  170. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/__init__.py +0 -0
  171. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
  172. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/protocol_factory.py +0 -0
  173. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
  174. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/reverse_proxy_service.py +0 -0
  175. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/__init__.py +0 -0
  176. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/base_server_service.py +0 -0
  177. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/cp20_server_service.py +0 -0
  178. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/device_service_factory.py +0 -0
  179. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/server_service.py +0 -0
  180. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp130_server_service.py +0 -0
  181. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp20_server_service.py +0 -0
  182. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp230_server_service.py +0 -0
  183. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp24_server_service.py +0 -0
  184. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp33_server_service.py +0 -0
  185. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/__init__.py +0 -0
  186. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
  187. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
  188. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
  189. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
  190. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
  191. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
  192. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_service.py +0 -0
  193. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
  194. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/__init__.py +0 -0
  195. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/protocol.tcss +0 -0
  196. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/__init__.py +0 -0
  197. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/__init__.py +0 -0
  198. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/checksum.py +0 -0
  199. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/event_helper.py +0 -0
  200. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/logging.py +0 -0
  201. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/serialization.py +0 -0
  202. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/state_machine.py +0 -0
  203. {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/time_utils.py +0 -0
  204. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/.coverage +0 -0
  205. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/__init__.py +0 -0
  206. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/conftest.py +0 -0
  207. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/.coverage +0 -0
  208. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/__init__.py +0 -0
  209. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/telegram_test_data.py +0 -0
  210. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_actiontable_integration.py +0 -0
  211. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_api/.coverage +0 -0
  212. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_api/__init__.py +0 -0
  213. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_blink_integration.py +0 -0
  214. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_checksum_integration.py +0 -0
  215. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_blink_integration.py +0 -0
  216. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
  217. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_raw_integration.py +0 -0
  218. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_receive_integration.py +0 -0
  219. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_discovery_integration.py +0 -0
  220. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_event_telegram_integration.py +0 -0
  221. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_homekit_config_integration.py +0 -0
  222. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_link_number_integration.py +0 -0
  223. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_module_integration.py +0 -0
  224. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_output_integration.py +0 -0
  225. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
  226. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
  227. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_term_logging_integration.py +0 -0
  228. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_version_integration.py +0 -0
  229. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
  230. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
  231. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/__init__.py +0 -0
  232. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_api/__init__.py +0 -0
  233. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/__init__.py +0 -0
  234. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_click_tree.py +0 -0
  235. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
  236. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
  237. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
  238. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_decorators.py +0 -0
  239. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
  240. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_formatters.py +0 -0
  241. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
  242. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
  243. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_term_commands.py +0 -0
  244. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/__init__.py +0 -0
  245. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/test_connection_init.py +0 -0
  246. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/test_exceptions.py +0 -0
  247. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_encoding/__init__.py +0 -0
  248. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
  249. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/__init__.py +0 -0
  250. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus.py +0 -0
  251. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
  252. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
  253. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
  254. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_event_telegram.py +0 -0
  255. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_log_entry.py +0 -0
  256. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_logger_config.py +0 -0
  257. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_module_type.py +0 -0
  258. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
  259. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_system_telegram.py +0 -0
  260. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
  261. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_version_telegram.py +0 -0
  262. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_write_config_type.py +0 -0
  263. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
  264. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
  265. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
  266. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/__init__.py +0 -0
  267. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
  268. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
  269. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
  270. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_base_server_service.py +0 -0
  271. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_blink_service.py +0 -0
  272. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_checksum_service.py +0 -0
  273. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
  274. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
  275. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
  276. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
  277. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
  278. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
  279. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
  280. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_discovery_service.py +0 -0
  281. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
  282. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
  283. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
  284. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_services.py +0 -0
  285. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_log_file_service.py +0 -0
  286. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_module_type_service.py +0 -0
  287. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_protocol.py +0 -0
  288. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_server_service.py +0 -0
  289. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
  290. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
  291. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_service.py +0 -0
  292. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_version_service.py +0 -0
  293. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
  294. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
  295. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
  296. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
  297. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
  298. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
  299. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_tui/__init__.py +0 -0
  300. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/__init__.py +0 -0
  301. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_checksum.py +0 -0
  302. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_event_helper.py +0 -0
  303. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_logging.py +0 -0
  304. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_serialization.py +0 -0
  305. {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_time_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.24.0
3
+ Version: 1.26.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -22,7 +22,7 @@ dependencies = [
22
22
  ]
23
23
  requires-python = ">=3.11"
24
24
  readme = "README.md"
25
- version = "1.24.0"
25
+ version = "1.26.0"
26
26
 
27
27
  [project.license]
28
28
  file = "LICENSE"
@@ -209,7 +209,7 @@ composite = [
209
209
  ]
210
210
 
211
211
  [tool.pdm.scripts.clean]
212
- shell = "rm -rf .coverage coverage.xml htmlcov/ .pytest_cache/ conbus.log.* && find . -type d -name '__pycache__' -exec rm -rf {} +"
212
+ shell = "rm -rf .coverage coverage.xml htmlcov/ .pytest_cache/ log/conbus.log.* && find . -type d -name '__pycache__' -exec rm -rf {} +"
213
213
 
214
214
  [tool.vulture]
215
215
  exclude = [
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.24.0"
6
+ __version__ = "1.26.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -23,9 +23,5 @@ def protocol_monitor(ctx: Context) -> None:
23
23
  """
24
24
  from xp.term.protocol import ProtocolMonitorApp
25
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()
26
+ # Resolve ProtocolMonitorApp from container and run
27
+ ctx.obj.get("container").get_container().resolve(ProtocolMonitorApp).run()
@@ -0,0 +1,19 @@
1
+ """Domain models for telegram display in terminal interface."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal, Optional
5
+
6
+
7
+ @dataclass
8
+ class TelegramDisplayEvent:
9
+ """Event containing telegram data for display in TUI.
10
+
11
+ Attributes:
12
+ direction: Direction of telegram ("RX" for received, "TX" for transmitted).
13
+ telegram: Formatted telegram string.
14
+ timestamp: Optional timestamp of the event.
15
+ """
16
+
17
+ direction: Literal["RX", "TX"]
18
+ telegram: str
19
+ timestamp: Optional[float] = None
@@ -325,10 +325,23 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
325
325
  self._reactor.stop()
326
326
 
327
327
  def connect(self) -> None:
328
- """Connect to TCP server."""
328
+ """Connect to TCP server.
329
+
330
+ Automatically detects and integrates with running asyncio event loop if present.
331
+ """
329
332
  self.logger.info(
330
333
  f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
331
334
  )
335
+
336
+ # Auto-detect and integrate with asyncio event loop if available
337
+ try:
338
+ event_loop = asyncio.get_running_loop()
339
+ self.logger.debug(f"Detected running event loop: {event_loop}")
340
+ self.set_event_loop(event_loop)
341
+ except RuntimeError:
342
+ # No running event loop - that's fine for non-async contexts
343
+ self.logger.debug("No running event loop detected - using reactor only")
344
+
332
345
  self._reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
333
346
 
334
347
  def disconnect(self) -> None:
@@ -0,0 +1,5 @@
1
+ """Terminal interface services."""
2
+
3
+ from xp.services.term.protocol_monitor_service import ProtocolMonitorService
4
+
5
+ __all__ = ["ProtocolMonitorService"]
@@ -0,0 +1,265 @@
1
+ """Protocol Monitor Service for terminal interface."""
2
+
3
+ import logging
4
+ from typing import Any, ItemsView, Optional
5
+
6
+ from psygnal import Signal
7
+ from twisted.python.failure import Failure
8
+
9
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
10
+ from xp.models.term.connection_state import ConnectionState
11
+ from xp.models.term.protocol_keys_config import ProtocolKeyConfig, ProtocolKeysConfig
12
+ from xp.models.term.telegram_display import TelegramDisplayEvent
13
+ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
14
+
15
+
16
+ class ProtocolMonitorService:
17
+ """Service for protocol monitoring in terminal interface.
18
+
19
+ Wraps ConbusEventProtocol and provides high-level operations
20
+ for the TUI without exposing protocol implementation details.
21
+
22
+ Attributes:
23
+ _conbus_protocol: Protocol instance for Conbus communication.
24
+ _protocol_keys: Configuration for protocol keyboard shortcuts.
25
+ connection_state: Current connection state (read-only property).
26
+ server_info: Server connection info as "IP:port" (read-only property).
27
+ on_connection_state_changed: Signal emitted when connection state changes.
28
+ on_telegram_display: Signal emitted when telegram should be displayed.
29
+ on_status_message: Signal emitted for status updates.
30
+ """
31
+
32
+ on_connection_state_changed: Signal = Signal(ConnectionState)
33
+ on_telegram_display: Signal = Signal(TelegramDisplayEvent)
34
+ on_status_message: Signal = Signal(str)
35
+
36
+ def __init__(
37
+ self,
38
+ conbus_protocol: ConbusEventProtocol,
39
+ protocol_keys: ProtocolKeysConfig,
40
+ ) -> None:
41
+ """Initialize the Protocol Monitor service.
42
+
43
+ Args:
44
+ conbus_protocol: ConbusEventProtocol instance.
45
+ protocol_keys: Protocol keys configuration.
46
+ """
47
+ self.logger = logging.getLogger(__name__)
48
+ self._conbus_protocol = conbus_protocol
49
+ self._connection_state = ConnectionState.DISCONNECTED
50
+ self._state_machine = ConnectionState.create_state_machine()
51
+ self._protocol_keys = protocol_keys
52
+
53
+ # Connect to protocol signals
54
+ self._connect_signals()
55
+
56
+ def _connect_signals(self) -> None:
57
+ """Connect to protocol signals."""
58
+ self._conbus_protocol.on_connection_made.connect(self._on_connection_made)
59
+ self._conbus_protocol.on_connection_failed.connect(self._on_connection_failed)
60
+ self._conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
61
+ self._conbus_protocol.on_telegram_sent.connect(self._on_telegram_sent)
62
+ self._conbus_protocol.on_timeout.connect(self._on_timeout)
63
+ self._conbus_protocol.on_failed.connect(self._on_failed)
64
+
65
+ def _disconnect_signals(self) -> None:
66
+ """Disconnect from protocol signals."""
67
+ self._conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
68
+ self._conbus_protocol.on_connection_failed.disconnect(
69
+ self._on_connection_failed
70
+ )
71
+ self._conbus_protocol.on_telegram_received.disconnect(
72
+ self._on_telegram_received
73
+ )
74
+ self._conbus_protocol.on_telegram_sent.disconnect(self._on_telegram_sent)
75
+ self._conbus_protocol.on_timeout.disconnect(self._on_timeout)
76
+ self._conbus_protocol.on_failed.disconnect(self._on_failed)
77
+
78
+ @property
79
+ def connection_state(self) -> ConnectionState:
80
+ """Get current connection state.
81
+
82
+ Returns:
83
+ Current connection state.
84
+ """
85
+ return self._connection_state
86
+
87
+ @property
88
+ def server_info(self) -> str:
89
+ """Get server connection info (IP:port).
90
+
91
+ Returns:
92
+ Server address in format "IP:port".
93
+ """
94
+ return f"{self._conbus_protocol.cli_config.ip}:{self._conbus_protocol.cli_config.port}"
95
+
96
+ def _connect(self) -> None:
97
+ """Initiate connection to server."""
98
+ if not self._state_machine.can_transition("connect"):
99
+ self.logger.warning(
100
+ f"Cannot connect: current state is {self._connection_state.value}"
101
+ )
102
+ return
103
+
104
+ if self._state_machine.transition("connecting", ConnectionState.CONNECTING):
105
+ self._connection_state = ConnectionState.CONNECTING
106
+ self.on_connection_state_changed.emit(self._connection_state)
107
+ self.on_status_message.emit(f"Connecting to {self.server_info}...")
108
+
109
+ self._conbus_protocol.connect()
110
+
111
+ def _disconnect(self) -> None:
112
+ """Disconnect from server."""
113
+ if not self._state_machine.can_transition("disconnect"):
114
+ self.logger.warning(
115
+ f"Cannot disconnect: current state is {self._connection_state.value}"
116
+ )
117
+ return
118
+
119
+ if self._state_machine.transition(
120
+ "disconnecting", ConnectionState.DISCONNECTING
121
+ ):
122
+ self._connection_state = ConnectionState.DISCONNECTING
123
+ self.on_connection_state_changed.emit(self._connection_state)
124
+ self.on_status_message.emit("Disconnecting...")
125
+
126
+ self._conbus_protocol.disconnect()
127
+
128
+ if self._state_machine.transition("disconnected", ConnectionState.DISCONNECTED):
129
+ self._connection_state = ConnectionState.DISCONNECTED
130
+ self.on_connection_state_changed.emit(self._connection_state)
131
+ self.on_status_message.emit("Disconnected")
132
+
133
+ def toggle_connection(self) -> None:
134
+ """Toggle connection state between connected and disconnected.
135
+
136
+ Disconnects if currently connected or connecting.
137
+ Connects if currently disconnected or failed.
138
+ """
139
+ if self._connection_state in (
140
+ ConnectionState.CONNECTED,
141
+ ConnectionState.CONNECTING,
142
+ ):
143
+ self._disconnect()
144
+ else:
145
+ self._connect()
146
+
147
+ def _send_telegram(self, name: str, telegram: str) -> None:
148
+ """Send a raw telegram.
149
+
150
+ Args:
151
+ name: Display name for the telegram.
152
+ telegram: Raw telegram string.
153
+ """
154
+ try:
155
+ self._conbus_protocol.send_raw_telegram(telegram)
156
+ self.on_status_message.emit(f"{name} sent.")
157
+ except Exception as e:
158
+ self.logger.error(f"Failed to send telegram: {e}")
159
+ self.on_status_message.emit(f"Failed: {e}")
160
+
161
+ def handle_key_press(self, key: str) -> bool:
162
+ """Handle protocol key press.
163
+
164
+ Args:
165
+ key: Key that was pressed.
166
+
167
+ Returns:
168
+ True if key was handled, False otherwise.
169
+ """
170
+ if key in self._protocol_keys.protocol:
171
+ key_config = self._protocol_keys.protocol[key]
172
+ for telegram in key_config.telegrams:
173
+ self._send_telegram(key_config.name, telegram)
174
+ return True
175
+ return False
176
+
177
+ # Protocol signal handlers
178
+
179
+ def _on_connection_made(self) -> None:
180
+ """Handle connection established."""
181
+ if self._state_machine.transition("connected", ConnectionState.CONNECTED):
182
+ self._connection_state = ConnectionState.CONNECTED
183
+ self.on_connection_state_changed.emit(self._connection_state)
184
+ self.on_status_message.emit(f"Connected to {self.server_info}")
185
+
186
+ def _on_connection_failed(self, failure: Failure) -> None:
187
+ """Handle connection failed.
188
+
189
+ Args:
190
+ failure: Twisted failure object with error details.
191
+ """
192
+ if self._state_machine.transition("disconnected", ConnectionState.DISCONNECTED):
193
+ self._connection_state = ConnectionState.DISCONNECTED
194
+ self.on_connection_state_changed.emit(self._connection_state)
195
+ self.on_status_message.emit(failure.getErrorMessage())
196
+
197
+ def _on_telegram_received(self, event: TelegramReceivedEvent) -> None:
198
+ """Handle telegram received.
199
+
200
+ Args:
201
+ event: Telegram received event with frame data.
202
+ """
203
+ display_event = TelegramDisplayEvent(direction="RX", telegram=event.frame)
204
+ self.on_telegram_display.emit(display_event)
205
+
206
+ def _on_telegram_sent(self, telegram: str) -> None:
207
+ """Handle telegram sent.
208
+
209
+ Args:
210
+ telegram: Sent telegram string.
211
+ """
212
+ display_event = TelegramDisplayEvent(direction="TX", telegram=telegram)
213
+ self.on_telegram_display.emit(display_event)
214
+
215
+ def _on_timeout(self) -> None:
216
+ """Handle timeout."""
217
+ self.logger.debug("Timeout occurred (continuous monitoring)")
218
+
219
+ def _on_failed(self, error: str) -> None:
220
+ """Handle connection failed.
221
+
222
+ Args:
223
+ error: Error message describing the failure.
224
+ """
225
+ if self._state_machine.transition("failed", ConnectionState.FAILED):
226
+ self._connection_state = ConnectionState.FAILED
227
+ self.on_connection_state_changed.emit(self._connection_state)
228
+ self.on_status_message.emit(f"Failed: {error}")
229
+
230
+ def cleanup(self) -> None:
231
+ """Clean up service resources."""
232
+ self._disconnect_signals()
233
+ if self._conbus_protocol.transport:
234
+ self._disconnect()
235
+
236
+ def get_keys(self) -> ItemsView[str, ProtocolKeyConfig]:
237
+ """Get protocol key mappings.
238
+
239
+ Returns:
240
+ Dictionary items view of key to ProtocolKeyConfig mappings.
241
+ """
242
+ return self._protocol_keys.protocol.items()
243
+
244
+ def __enter__(self) -> "ProtocolMonitorService":
245
+ """Enter context manager.
246
+
247
+ Returns:
248
+ Self for context management.
249
+ """
250
+ return self
251
+
252
+ def __exit__(
253
+ self,
254
+ _exc_type: Optional[type],
255
+ _exc_val: Optional[BaseException],
256
+ _exc_tb: Optional[Any],
257
+ ) -> None:
258
+ """Exit context manager and clean up resources.
259
+
260
+ Args:
261
+ _exc_type: Exception type if any.
262
+ _exc_val: Exception value if any.
263
+ _exc_tb: Exception traceback if any.
264
+ """
265
+ self.cleanup()
@@ -6,8 +6,6 @@ from typing import Any, Optional
6
6
  from textual.app import App, ComposeResult
7
7
  from textual.containers import Horizontal
8
8
 
9
- from xp.models.term import ProtocolKeysConfig
10
- from xp.models.term.status_message import StatusMessageChanged
11
9
  from xp.term.widgets.help_menu import HelpMenuWidget
12
10
  from xp.term.widgets.protocol_log import ProtocolLogWidget
13
11
  from xp.term.widgets.status_footer import StatusFooterWidget
@@ -20,7 +18,7 @@ class ProtocolMonitorApp(App[None]):
20
18
  terminal interface with keyboard shortcuts for control.
21
19
 
22
20
  Attributes:
23
- container: ServiceContainer for dependency injection.
21
+ protocol_service: ProtocolMonitorService for protocol operations.
24
22
  CSS_PATH: Path to CSS stylesheet file.
25
23
  BINDINGS: Keyboard bindings for app actions.
26
24
  TITLE: Application title displayed in header.
@@ -38,27 +36,17 @@ class ProtocolMonitorApp(App[None]):
38
36
  ("0-9,a-q", "protocol_keys", "Keys"),
39
37
  ]
40
38
 
41
- def __init__(self, container: Any) -> None:
39
+ def __init__(self, protocol_service: Any) -> None:
42
40
  """Initialize the Protocol Monitor app.
43
41
 
44
42
  Args:
45
- container: ServiceContainer for resolving services.
43
+ protocol_service: ProtocolMonitorService for protocol operations.
46
44
  """
47
45
  super().__init__()
48
- self.container = container
46
+ self.protocol_service = protocol_service
49
47
  self.protocol_widget: Optional[ProtocolLogWidget] = None
50
48
  self.help_menu: Optional[HelpMenuWidget] = None
51
49
  self.footer_widget: Optional[StatusFooterWidget] = None
52
- self.protocol_keys = self._load_protocol_keys()
53
-
54
- def _load_protocol_keys(self) -> ProtocolKeysConfig:
55
- """Load protocol keys from YAML config file.
56
-
57
- Returns:
58
- ProtocolKeysConfig instance.
59
- """
60
- config_path = Path(__file__).parent / "protocol.yml"
61
- return ProtocolKeysConfig.from_yaml(config_path)
62
50
 
63
51
  def compose(self) -> ComposeResult:
64
52
  """Compose the app layout with widgets.
@@ -67,31 +55,37 @@ class ProtocolMonitorApp(App[None]):
67
55
  ProtocolLogWidget and Footer widgets.
68
56
  """
69
57
  with Horizontal(id="main-container"):
70
- self.protocol_widget = ProtocolLogWidget(container=self.container)
58
+ self.protocol_widget = ProtocolLogWidget(service=self.protocol_service)
71
59
  yield self.protocol_widget
72
60
 
73
61
  # Help menu (hidden by default)
74
62
  self.help_menu = HelpMenuWidget(
75
- protocol_keys=self.protocol_keys, id="help-menu"
63
+ service=self.protocol_service, id="help-menu"
76
64
  )
77
65
  yield self.help_menu
78
66
 
79
- self.footer_widget = StatusFooterWidget(id="footer-container")
67
+ self.footer_widget = StatusFooterWidget(
68
+ service=self.protocol_service, id="footer-container"
69
+ )
80
70
  yield self.footer_widget
81
71
 
72
+ async def on_mount(self) -> None:
73
+ """Initialize app after UI is mounted.
74
+
75
+ Delays connection by 0.5s to let UI render first.
76
+ """
77
+ import asyncio
78
+
79
+ # Delay connection to let UI render
80
+ await asyncio.sleep(0.5)
81
+ self.protocol_service.connect()
82
+
82
83
  def action_toggle_connection(self) -> None:
83
84
  """Toggle connection on 'c' key press.
84
85
 
85
86
  Connects if disconnected/failed, disconnects if connected/connecting.
86
87
  """
87
- if self.protocol_widget:
88
- from xp.term.widgets.protocol_log import ConnectionState
89
-
90
- state = self.protocol_widget.connection_state
91
- if state in (ConnectionState.CONNECTED, ConnectionState.CONNECTING):
92
- self.protocol_widget.disconnect()
93
- else:
94
- self.protocol_widget.connect()
88
+ self.protocol_service.toggle_connection()
95
89
 
96
90
  def action_reset(self) -> None:
97
91
  """Reset and clear protocol widget on 'r' key press."""
@@ -104,34 +98,4 @@ class ProtocolMonitorApp(App[None]):
104
98
  Args:
105
99
  event: Key press event from Textual.
106
100
  """
107
- if event.key in self.protocol_keys.protocol and self.protocol_widget:
108
- key_config = self.protocol_keys.protocol[event.key]
109
- for telegram in key_config.telegrams:
110
- self.protocol_widget.send_telegram(key_config.name, telegram)
111
-
112
- def on_mount(self) -> None:
113
- """Set up status line updates when app mounts."""
114
- if self.protocol_widget:
115
- self.protocol_widget.watch(
116
- self.protocol_widget,
117
- "connection_state",
118
- self._update_status,
119
- )
120
-
121
- def _update_status(self, state: Any) -> None:
122
- """Update status line with connection state.
123
-
124
- Args:
125
- state: Current connection state.
126
- """
127
- if self.footer_widget:
128
- self.footer_widget.update_status(state)
129
-
130
- def on_status_message_changed(self, message: StatusMessageChanged) -> None:
131
- """Handle status message changes from protocol widget.
132
-
133
- Args:
134
- message: Message containing the status text.
135
- """
136
- if self.footer_widget:
137
- self.footer_widget.update_message(message.message)
101
+ self.protocol_service.handle_key_press(event.key)
@@ -1,12 +1,13 @@
1
1
  """Help Menu Widget for displaying keyboard shortcuts and protocol keys."""
2
2
 
3
- from typing import Any
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from textual.app import ComposeResult
6
6
  from textual.containers import Vertical
7
7
  from textual.widgets import DataTable
8
8
 
9
- from xp.models.term import ProtocolKeysConfig
9
+ if TYPE_CHECKING:
10
+ from xp.services.term.protocol_monitor_service import ProtocolMonitorService
10
11
 
11
12
 
12
13
  class HelpMenuWidget(Vertical):
@@ -16,25 +17,25 @@ class HelpMenuWidget(Vertical):
16
17
  corresponding protocol commands.
17
18
 
18
19
  Attributes:
19
- protocol_keys: Configuration of protocol keys and their telegrams.
20
+ service: ProtocolMonitorService for accessing protocol keys.
20
21
  help_table: DataTable widget for displaying key mappings.
21
22
  """
22
23
 
23
24
  def __init__(
24
25
  self,
25
- protocol_keys: ProtocolKeysConfig,
26
+ service: "ProtocolMonitorService",
26
27
  *args: Any,
27
28
  **kwargs: Any,
28
29
  ) -> None:
29
30
  """Initialize the Help Menu widget.
30
31
 
31
32
  Args:
32
- protocol_keys: Configuration containing protocol key mappings.
33
+ service: ProtocolMonitorService instance.
33
34
  args: Additional positional arguments for Vertical.
34
35
  kwargs: Additional keyword arguments for Vertical.
35
36
  """
36
37
  super().__init__(*args, **kwargs)
37
- self.protocol_keys = protocol_keys
38
+ self.service: ProtocolMonitorService = service
38
39
  self.help_table: DataTable = DataTable(id="help-table", show_header=False)
39
40
  self.help_table.can_focus = False
40
41
  self.border_title = "Help menu"
@@ -51,5 +52,5 @@ class HelpMenuWidget(Vertical):
51
52
  def on_mount(self) -> None:
52
53
  """Populate help table when widget mounts."""
53
54
  self.help_table.add_columns("Key", "Command")
54
- for key, config in self.protocol_keys.protocol.items():
55
+ for key, config in self.service.get_keys():
55
56
  self.help_table.add_row(key, config.name)
@@ -0,0 +1,81 @@
1
+ """Protocol Log Widget for displaying telegram stream."""
2
+
3
+ import logging
4
+ from typing import Any, Optional
5
+
6
+ from textual.widget import Widget
7
+ from textual.widgets import RichLog
8
+
9
+ from xp.models.term.telegram_display import TelegramDisplayEvent
10
+ from xp.services.term.protocol_monitor_service import ProtocolMonitorService
11
+
12
+
13
+ class ProtocolLogWidget(Widget):
14
+ """Widget for displaying protocol telegram stream.
15
+
16
+ Displays live RX/TX telegram stream with color-coded direction markers
17
+ via ProtocolMonitorService.
18
+
19
+ Attributes:
20
+ service: ProtocolMonitorService for protocol operations.
21
+ logger: Logger instance for this widget.
22
+ log_widget: RichLog widget for displaying messages.
23
+ """
24
+
25
+ def __init__(self, service: ProtocolMonitorService) -> None:
26
+ """Initialize the Protocol Log widget.
27
+
28
+ Args:
29
+ service: ProtocolMonitorService instance for protocol operations.
30
+ """
31
+ super().__init__()
32
+ self.border_title = "Protocol"
33
+ self.service = service
34
+ self.logger = logging.getLogger(__name__)
35
+ self.log_widget: Optional[RichLog] = None
36
+
37
+ def compose(self) -> Any:
38
+ """Compose the widget layout.
39
+
40
+ Yields:
41
+ RichLog widget for message display.
42
+ """
43
+ self.log_widget = RichLog(highlight=False, markup=True)
44
+ yield self.log_widget
45
+
46
+ def on_mount(self) -> None:
47
+ """Initialize widget when mounted.
48
+
49
+ Connects to service signals for telegram display.
50
+ """
51
+ # Connect to service signals
52
+ self.service.on_telegram_display.connect(self._on_telegram_display)
53
+
54
+ def _on_telegram_display(self, event: TelegramDisplayEvent) -> None:
55
+ """Handle telegram display event from service.
56
+
57
+ Args:
58
+ event: Telegram display event with direction and telegram data.
59
+ """
60
+ if self.log_widget:
61
+ color = "bold #00ff00" if event.direction == "TX" else "#00ff00"
62
+ self.log_widget.write(
63
+ f"[{color}]\\[{event.direction}] {event.telegram}[/{color}]"
64
+ )
65
+
66
+ def clear_log(self) -> None:
67
+ """Clear the protocol log widget."""
68
+ if self.log_widget:
69
+ self.log_widget.clear()
70
+
71
+ def on_unmount(self) -> None:
72
+ """Clean up when widget unmounts.
73
+
74
+ Disconnects signals from service.
75
+ """
76
+ try:
77
+ # Disconnect service signals
78
+ self.service.on_telegram_display.disconnect(self._on_telegram_display)
79
+
80
+ except Exception as e:
81
+ self.logger.error(f"Error during cleanup: {e}")