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.
- {conson_xp-1.24.0 → conson_xp-1.26.0}/PKG-INFO +1 -1
- {conson_xp-1.24.0 → conson_xp-1.26.0}/pyproject.toml +2 -2
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/__init__.py +1 -1
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/term_commands.py +2 -6
- conson_xp-1.26.0/src/xp/models/term/telegram_display.py +19 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/conbus_event_protocol.py +14 -1
- conson_xp-1.26.0/src/xp/services/term/__init__.py +5 -0
- conson_xp-1.26.0/src/xp/services/term/protocol_monitor_service.py +265 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/protocol.py +22 -58
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/help_menu.py +8 -7
- conson_xp-1.26.0/src/xp/term/widgets/protocol_log.py +81 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/status_footer.py +28 -4
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/dependencies.py +37 -0
- conson_xp-1.26.0/tests/unit/test_services/test_conbus_event_protocol.py +146 -0
- conson_xp-1.26.0/tests/unit/test_services/test_protocol_monitor_service.py +153 -0
- conson_xp-1.26.0/tests/unit/test_tui/test_protocol_log.py +81 -0
- conson_xp-1.24.0/src/xp/term/protocol.yml +0 -139
- conson_xp-1.24.0/src/xp/term/widgets/protocol_log.py +0 -309
- conson_xp-1.24.0/tests/unit/test_tui/test_protocol_log.py +0 -233
- {conson_xp-1.24.0 → conson_xp-1.26.0}/LICENSE +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/README.md +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/connection/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/connection/exceptions.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/server/xp33_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/logging.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/.coverage +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/conftest.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/test_connection_init.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_connection/test_exceptions.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.24.0 → conson_xp-1.26.0}/tests/unit/test_utils/test_time_utils.py +0 -0
|
@@ -22,7 +22,7 @@ dependencies = [
|
|
|
22
22
|
]
|
|
23
23
|
requires-python = ">=3.11"
|
|
24
24
|
readme = "README.md"
|
|
25
|
-
version = "1.
|
|
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 = [
|
|
@@ -23,9 +23,5 @@ def protocol_monitor(ctx: Context) -> None:
|
|
|
23
23
|
"""
|
|
24
24
|
from xp.term.protocol import ProtocolMonitorApp
|
|
25
25
|
|
|
26
|
-
# Resolve
|
|
27
|
-
|
|
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,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
|
-
|
|
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,
|
|
39
|
+
def __init__(self, protocol_service: Any) -> None:
|
|
42
40
|
"""Initialize the Protocol Monitor app.
|
|
43
41
|
|
|
44
42
|
Args:
|
|
45
|
-
|
|
43
|
+
protocol_service: ProtocolMonitorService for protocol operations.
|
|
46
44
|
"""
|
|
47
45
|
super().__init__()
|
|
48
|
-
self.
|
|
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(
|
|
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
|
-
|
|
63
|
+
service=self.protocol_service, id="help-menu"
|
|
76
64
|
)
|
|
77
65
|
yield self.help_menu
|
|
78
66
|
|
|
79
|
-
self.footer_widget = StatusFooterWidget(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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}")
|