conson-xp 1.27.0__tar.gz → 1.28.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.27.0 → conson_xp-1.28.0}/PKG-INFO +2 -1
- {conson_xp-1.27.0 → conson_xp-1.28.0}/README.md +1 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/pyproject.toml +1 -1
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/__init__.py +1 -1
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/term/term_commands.py +21 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/term/__init__.py +2 -0
- conson_xp-1.28.0/src/xp/models/term/module_state.py +28 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/xp24_server_service.py +1 -4
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/xp33_server_service.py +1 -4
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_output_service.py +33 -0
- conson_xp-1.28.0/src/xp/services/term/__init__.py +6 -0
- conson_xp-1.28.0/src/xp/services/term/state_monitor_service.py +313 -0
- conson_xp-1.28.0/src/xp/term/state.py +97 -0
- conson_xp-1.28.0/src/xp/term/state.tcss +86 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/widgets/help_menu.py +3 -3
- conson_xp-1.28.0/src/xp/term/widgets/modules_list.py +217 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/widgets/status_footer.py +5 -4
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/dependencies.py +20 -0
- conson_xp-1.28.0/tests/unit/test_services/test_telegram_output_service.py +83 -0
- conson_xp-1.27.0/src/xp/services/term/__init__.py +0 -5
- {conson_xp-1.27.0 → conson_xp-1.28.0}/LICENSE +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/connection/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/connection/exceptions.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/term/telegram_display.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/protocol/conbus_event_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/term/widgets/protocol_log.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/logging.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/.coverage +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/conftest.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_connection/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_connection/test_connection_init.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_connection/test_exceptions.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.27.0 → conson_xp-1.28.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.
|
|
3
|
+
Version: 1.28.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -417,6 +417,7 @@ xp telegram version
|
|
|
417
417
|
|
|
418
418
|
xp term
|
|
419
419
|
xp term protocol
|
|
420
|
+
xp term state
|
|
420
421
|
|
|
421
422
|
<!-- END CLI HELP -->
|
|
422
423
|
```
|
|
@@ -25,3 +25,24 @@ def protocol_monitor(ctx: Context) -> None:
|
|
|
25
25
|
|
|
26
26
|
# Resolve ProtocolMonitorApp from container and run
|
|
27
27
|
ctx.obj.get("container").get_container().resolve(ProtocolMonitorApp).run()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@term.command("state")
|
|
31
|
+
@click.pass_context
|
|
32
|
+
def state_monitor(ctx: Context) -> None:
|
|
33
|
+
r"""Start TUI for module state monitoring.
|
|
34
|
+
|
|
35
|
+
Displays module states from Conson configuration with real-time
|
|
36
|
+
updates in an interactive terminal interface.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
ctx: Click context object.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
\b
|
|
43
|
+
xp term state
|
|
44
|
+
"""
|
|
45
|
+
from xp.term.state import StateMonitorApp
|
|
46
|
+
|
|
47
|
+
# Resolve StateMonitorApp from container and run
|
|
48
|
+
ctx.obj.get("container").get_container().resolve(StateMonitorApp).run()
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Terminal UI models."""
|
|
2
2
|
|
|
3
|
+
from xp.models.term.module_state import ModuleState
|
|
3
4
|
from xp.models.term.protocol_keys_config import (
|
|
4
5
|
ProtocolKeyConfig,
|
|
5
6
|
ProtocolKeysConfig,
|
|
6
7
|
)
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
10
|
+
"ModuleState",
|
|
9
11
|
"ProtocolKeyConfig",
|
|
10
12
|
"ProtocolKeysConfig",
|
|
11
13
|
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Module state data model."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ModuleState:
|
|
10
|
+
"""State of a Conson module for TUI display.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
name: Module name/identifier (e.g., A01, A02).
|
|
14
|
+
serial_number: Module serial number.
|
|
15
|
+
module_type: Module type designation (e.g., XP130, XP230, XP24).
|
|
16
|
+
outputs: Output states as space-separated binary values. Empty string for modules without outputs.
|
|
17
|
+
auto_report: Auto-report enabled status (Y/N).
|
|
18
|
+
error_status: Module status ("OK" or error code like "E10").
|
|
19
|
+
last_update: Last communication timestamp. None if never updated.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
name: str
|
|
23
|
+
serial_number: str
|
|
24
|
+
module_type: str
|
|
25
|
+
outputs: str
|
|
26
|
+
auto_report: bool
|
|
27
|
+
error_status: str
|
|
28
|
+
last_update: Optional[datetime]
|
|
@@ -161,10 +161,7 @@ class XP24ServerService(BaseServerService):
|
|
|
161
161
|
|
|
162
162
|
data_value = handler()
|
|
163
163
|
data_part = (
|
|
164
|
-
f"R{self.serial_number}"
|
|
165
|
-
f"F02D{datapoint_type.value}"
|
|
166
|
-
f"{self.module_type_code.value:02}"
|
|
167
|
-
f"{data_value}"
|
|
164
|
+
f"R{self.serial_number}" f"F02D{datapoint_type.value}" f"{data_value}"
|
|
168
165
|
)
|
|
169
166
|
telegram = self._build_response_telegram(data_part)
|
|
170
167
|
|
|
@@ -226,10 +226,7 @@ class XP33ServerService(BaseServerService):
|
|
|
226
226
|
|
|
227
227
|
data_value = handler()
|
|
228
228
|
data_part = (
|
|
229
|
-
f"R{self.serial_number}"
|
|
230
|
-
f"F02D{datapoint_type.value}"
|
|
231
|
-
f"{self.module_type_code.value:02}"
|
|
232
|
-
f"{data_value}"
|
|
229
|
+
f"R{self.serial_number}" f"F02D{datapoint_type.value}" f"{data_value}"
|
|
233
230
|
)
|
|
234
231
|
telegram = self._build_response_telegram(data_part)
|
|
235
232
|
|
|
@@ -320,3 +320,36 @@ class TelegramOutputService:
|
|
|
320
320
|
f"Timestamp: {telegram.timestamp}\n"
|
|
321
321
|
f"Checksum: {telegram.checksum}{checksum_status}"
|
|
322
322
|
)
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def format_output_state(data_value: str) -> str:
|
|
326
|
+
"""Format module output state data value for display.
|
|
327
|
+
|
|
328
|
+
Algorithm:
|
|
329
|
+
1. Remove 'x' characters
|
|
330
|
+
2. Format to 4 chars with space padding on the right
|
|
331
|
+
3. Invert order
|
|
332
|
+
4. Add spaces between characters
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
data_value: Raw data value from module output state datapoint (e.g., "xxxx0101", "xx1110").
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Formatted output string with spaces (e.g., "1 0 1 0", "0 1 1 1").
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
>>> TelegramOutputService.format_output_state("xxxx0101")
|
|
342
|
+
"1 0 1 0"
|
|
343
|
+
>>> TelegramOutputService.format_output_state("xx1110")
|
|
344
|
+
"0 1 1 1"
|
|
345
|
+
>>> TelegramOutputService.format_output_state("xxxx01")
|
|
346
|
+
" 1 0"
|
|
347
|
+
"""
|
|
348
|
+
# Remove 'x' characters
|
|
349
|
+
cleaned = data_value.replace("x", "").replace("X", "")
|
|
350
|
+
# Format to 4 chars with space padding on the right
|
|
351
|
+
padded = cleaned.ljust(4)[:4]
|
|
352
|
+
# Invert order
|
|
353
|
+
inverted = padded[::-1]
|
|
354
|
+
# Add spaces between characters
|
|
355
|
+
return " ".join(inverted)
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""State Monitor Service for terminal interface."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from psygnal import Signal
|
|
8
|
+
|
|
9
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
10
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
11
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
12
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
13
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
14
|
+
from xp.models.term.connection_state import ConnectionState
|
|
15
|
+
from xp.models.term.module_state import ModuleState
|
|
16
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
17
|
+
from xp.services.telegram.telegram_output_service import TelegramOutputService
|
|
18
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StateMonitorService:
|
|
22
|
+
"""Service for module state monitoring in terminal interface.
|
|
23
|
+
|
|
24
|
+
Wraps ConbusEventProtocol and ConsonModuleListConfig to provide
|
|
25
|
+
high-level module state tracking for the TUI.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
on_connection_state_changed: Signal emitted when connection state changes.
|
|
29
|
+
on_module_list_updated: Signal emitted when module list refreshed from config.
|
|
30
|
+
on_module_state_changed: Signal emitted when individual module state updates.
|
|
31
|
+
on_module_error: Signal emitted when module error occurs.
|
|
32
|
+
on_status_message: Signal emitted for status messages.
|
|
33
|
+
connection_state: Property returning current connection state.
|
|
34
|
+
server_info: Property returning server connection info (IP:port).
|
|
35
|
+
module_states: Property returning list of all module states.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
on_connection_state_changed: Signal = Signal(ConnectionState)
|
|
39
|
+
on_module_list_updated: Signal = Signal(list)
|
|
40
|
+
on_module_state_changed: Signal = Signal(ModuleState)
|
|
41
|
+
on_module_error: Signal = Signal(str, str)
|
|
42
|
+
on_status_message: Signal = Signal(str)
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
conbus_protocol: ConbusEventProtocol,
|
|
47
|
+
conson_config: ConsonModuleListConfig,
|
|
48
|
+
telegram_service: TelegramService,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Initialize the State Monitor service.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
conbus_protocol: ConbusEventProtocol instance.
|
|
54
|
+
conson_config: ConsonModuleListConfig for module configuration.
|
|
55
|
+
telegram_service: TelegramService for parsing telegrams.
|
|
56
|
+
"""
|
|
57
|
+
self.logger = logging.getLogger(__name__)
|
|
58
|
+
self._conbus_protocol = conbus_protocol
|
|
59
|
+
self._conson_config = conson_config
|
|
60
|
+
self._telegram_service = telegram_service
|
|
61
|
+
self._connection_state = ConnectionState.DISCONNECTED
|
|
62
|
+
self._state_machine = ConnectionState.create_state_machine()
|
|
63
|
+
self._module_states: Dict[str, ModuleState] = {}
|
|
64
|
+
|
|
65
|
+
# Connect to protocol signals
|
|
66
|
+
self._connect_signals()
|
|
67
|
+
|
|
68
|
+
# Initialize module states from config
|
|
69
|
+
self._initialize_module_states()
|
|
70
|
+
|
|
71
|
+
def _initialize_module_states(self) -> None:
|
|
72
|
+
"""Initialize module states from ConsonModuleListConfig."""
|
|
73
|
+
for module_config in self._conson_config.root:
|
|
74
|
+
# Map auto_report_status: PP → True, others → False
|
|
75
|
+
auto_report = module_config.auto_report_status == "PP"
|
|
76
|
+
|
|
77
|
+
module_state = ModuleState(
|
|
78
|
+
name=module_config.name,
|
|
79
|
+
serial_number=module_config.serial_number,
|
|
80
|
+
module_type=module_config.module_type,
|
|
81
|
+
outputs="", # Empty initially
|
|
82
|
+
auto_report=auto_report,
|
|
83
|
+
error_status="OK",
|
|
84
|
+
last_update=None, # Not updated yet
|
|
85
|
+
)
|
|
86
|
+
self._module_states[module_config.serial_number] = module_state
|
|
87
|
+
|
|
88
|
+
def _connect_signals(self) -> None:
|
|
89
|
+
"""Connect to protocol signals."""
|
|
90
|
+
self._conbus_protocol.on_connection_made.connect(self._on_connection_made)
|
|
91
|
+
self._conbus_protocol.on_connection_failed.connect(self._on_connection_failed)
|
|
92
|
+
self._conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
|
|
93
|
+
self._conbus_protocol.on_timeout.connect(self._on_timeout)
|
|
94
|
+
self._conbus_protocol.on_failed.connect(self._on_failed)
|
|
95
|
+
|
|
96
|
+
def _disconnect_signals(self) -> None:
|
|
97
|
+
"""Disconnect from protocol signals."""
|
|
98
|
+
self._conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
|
|
99
|
+
self._conbus_protocol.on_connection_failed.disconnect(
|
|
100
|
+
self._on_connection_failed
|
|
101
|
+
)
|
|
102
|
+
self._conbus_protocol.on_telegram_received.disconnect(
|
|
103
|
+
self._on_telegram_received
|
|
104
|
+
)
|
|
105
|
+
self._conbus_protocol.on_timeout.disconnect(self._on_timeout)
|
|
106
|
+
self._conbus_protocol.on_failed.disconnect(self._on_failed)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def connection_state(self) -> ConnectionState:
|
|
110
|
+
"""Get current connection state.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Current connection state.
|
|
114
|
+
"""
|
|
115
|
+
return self._connection_state
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def server_info(self) -> str:
|
|
119
|
+
"""Get server connection info (IP:port).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Server address in format "IP:port".
|
|
123
|
+
"""
|
|
124
|
+
return f"{self._conbus_protocol.cli_config.ip}:{self._conbus_protocol.cli_config.port}"
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def module_states(self) -> List[ModuleState]:
|
|
128
|
+
"""Get all module states.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of all module states.
|
|
132
|
+
"""
|
|
133
|
+
return list(self._module_states.values())
|
|
134
|
+
|
|
135
|
+
def connect(self) -> None:
|
|
136
|
+
"""Initiate connection to server."""
|
|
137
|
+
if not self._state_machine.can_transition("connect"):
|
|
138
|
+
self.logger.warning(
|
|
139
|
+
f"Cannot connect: current state is {self._connection_state.value}"
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if self._state_machine.transition("connecting", ConnectionState.CONNECTING):
|
|
144
|
+
self._connection_state = ConnectionState.CONNECTING
|
|
145
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
146
|
+
self.on_status_message.emit(f"Connecting to {self.server_info}...")
|
|
147
|
+
|
|
148
|
+
self._conbus_protocol.connect()
|
|
149
|
+
|
|
150
|
+
def disconnect(self) -> None:
|
|
151
|
+
"""Disconnect from server."""
|
|
152
|
+
if not self._state_machine.can_transition("disconnect"):
|
|
153
|
+
self.logger.warning(
|
|
154
|
+
f"Cannot disconnect: current state is {self._connection_state.value}"
|
|
155
|
+
)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
if self._state_machine.transition(
|
|
159
|
+
"disconnecting", ConnectionState.DISCONNECTING
|
|
160
|
+
):
|
|
161
|
+
self._connection_state = ConnectionState.DISCONNECTING
|
|
162
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
163
|
+
self.on_status_message.emit("Disconnecting...")
|
|
164
|
+
|
|
165
|
+
self._conbus_protocol.disconnect()
|
|
166
|
+
|
|
167
|
+
if self._state_machine.transition("disconnected", ConnectionState.DISCONNECTED):
|
|
168
|
+
self._connection_state = ConnectionState.DISCONNECTED
|
|
169
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
170
|
+
self.on_status_message.emit("Disconnected")
|
|
171
|
+
|
|
172
|
+
def toggle_connection(self) -> None:
|
|
173
|
+
"""Toggle connection state between connected and disconnected.
|
|
174
|
+
|
|
175
|
+
Disconnects if currently connected or connecting.
|
|
176
|
+
Connects if currently disconnected or failed.
|
|
177
|
+
"""
|
|
178
|
+
if self._connection_state in (
|
|
179
|
+
ConnectionState.CONNECTED,
|
|
180
|
+
ConnectionState.CONNECTING,
|
|
181
|
+
):
|
|
182
|
+
self.disconnect()
|
|
183
|
+
else:
|
|
184
|
+
self.connect()
|
|
185
|
+
|
|
186
|
+
def refresh_all(self) -> None:
|
|
187
|
+
"""Refresh all module states.
|
|
188
|
+
|
|
189
|
+
Queries module_output_state datapoint for eligible modules (XP24, XP33LR, XP33LED).
|
|
190
|
+
Updates outputs column and last_update timestamp for each queried module.
|
|
191
|
+
"""
|
|
192
|
+
self.on_status_message.emit("Refreshing module states...")
|
|
193
|
+
|
|
194
|
+
# Eligible module types that support output state queries
|
|
195
|
+
eligible_types = {"XP24", "XP33LR", "XP33LED"}
|
|
196
|
+
|
|
197
|
+
# Filter and query eligible modules
|
|
198
|
+
for module_state in self._module_states.values():
|
|
199
|
+
if module_state.module_type in eligible_types:
|
|
200
|
+
self._query_module_output_state(module_state.serial_number)
|
|
201
|
+
self.logger.debug(
|
|
202
|
+
f"Querying output state for {module_state.name} ({module_state.module_type})"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def _query_module_output_state(self, serial_number: str) -> None:
|
|
206
|
+
"""Query module output state datapoint.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
serial_number: Module serial number to query.
|
|
210
|
+
"""
|
|
211
|
+
self._conbus_protocol.send_telegram(
|
|
212
|
+
telegram_type=TelegramType.SYSTEM,
|
|
213
|
+
serial_number=serial_number,
|
|
214
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
215
|
+
data_value=str(DataPointType.MODULE_OUTPUT_STATE.value),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _on_connection_made(self) -> None:
|
|
219
|
+
"""Handle connection made event."""
|
|
220
|
+
if self._state_machine.transition("connected", ConnectionState.CONNECTED):
|
|
221
|
+
self._connection_state = ConnectionState.CONNECTED
|
|
222
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
223
|
+
self.on_status_message.emit(f"Connected to {self.server_info}")
|
|
224
|
+
|
|
225
|
+
# Emit initial module list
|
|
226
|
+
self.on_module_list_updated.emit(self.module_states)
|
|
227
|
+
|
|
228
|
+
def _on_connection_failed(self, failure: Exception) -> None:
|
|
229
|
+
"""Handle connection failed event.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
failure: Exception that caused the failure.
|
|
233
|
+
"""
|
|
234
|
+
if self._state_machine.transition("failed", ConnectionState.FAILED):
|
|
235
|
+
self._connection_state = ConnectionState.FAILED
|
|
236
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
237
|
+
self.on_status_message.emit(f"Connection failed: {failure}")
|
|
238
|
+
|
|
239
|
+
def _on_telegram_received(self, event: TelegramReceivedEvent) -> None:
|
|
240
|
+
"""Handle telegram received event.
|
|
241
|
+
|
|
242
|
+
Parse output states from telegram and update module state.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
event: Telegram received event.
|
|
246
|
+
"""
|
|
247
|
+
# Only process reply telegrams
|
|
248
|
+
if event.telegram_type != TelegramType.REPLY:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
serial_number = event.serial_number
|
|
252
|
+
if not serial_number or serial_number not in self._module_states:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Parse the reply telegram
|
|
256
|
+
reply_telegram = self._telegram_service.parse_reply_telegram(event.frame)
|
|
257
|
+
if not reply_telegram:
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# Check if this is a module output state response
|
|
261
|
+
if (
|
|
262
|
+
reply_telegram.system_function == SystemFunction.READ_DATAPOINT
|
|
263
|
+
and reply_telegram.datapoint_type == DataPointType.MODULE_OUTPUT_STATE
|
|
264
|
+
):
|
|
265
|
+
module_state = self._module_states[serial_number]
|
|
266
|
+
|
|
267
|
+
# Parse output state from data_value using TelegramOutputService
|
|
268
|
+
outputs = TelegramOutputService.format_output_state(
|
|
269
|
+
reply_telegram.data_value
|
|
270
|
+
)
|
|
271
|
+
module_state.outputs = outputs
|
|
272
|
+
module_state.last_update = datetime.now()
|
|
273
|
+
|
|
274
|
+
self.on_module_state_changed.emit(module_state)
|
|
275
|
+
self.logger.debug(f"Updated outputs for {module_state.name}: {outputs}")
|
|
276
|
+
|
|
277
|
+
def _on_timeout(self) -> None:
|
|
278
|
+
"""Handle timeout event."""
|
|
279
|
+
self.on_status_message.emit("Connection timeout")
|
|
280
|
+
|
|
281
|
+
def _on_failed(self, failure: Exception) -> None:
|
|
282
|
+
"""Handle protocol failure event.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
failure: Exception that caused the failure.
|
|
286
|
+
"""
|
|
287
|
+
if self._state_machine.transition("failed", ConnectionState.FAILED):
|
|
288
|
+
self._connection_state = ConnectionState.FAILED
|
|
289
|
+
self.on_connection_state_changed.emit(self._connection_state)
|
|
290
|
+
self.on_status_message.emit(f"Protocol error: {failure}")
|
|
291
|
+
|
|
292
|
+
def cleanup(self) -> None:
|
|
293
|
+
"""Clean up service resources."""
|
|
294
|
+
self._disconnect_signals()
|
|
295
|
+
self.logger.debug("StateMonitorService cleaned up")
|
|
296
|
+
|
|
297
|
+
def __enter__(self) -> "StateMonitorService":
|
|
298
|
+
"""Context manager entry.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Self for context manager.
|
|
302
|
+
"""
|
|
303
|
+
return self
|
|
304
|
+
|
|
305
|
+
def __exit__(self, _exc_type: object, _exc_val: object, _exc_tb: object) -> None:
|
|
306
|
+
"""Context manager exit.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
_exc_type: Exception type.
|
|
310
|
+
_exc_val: Exception value.
|
|
311
|
+
_exc_tb: Exception traceback.
|
|
312
|
+
"""
|
|
313
|
+
self.cleanup()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""State Monitor TUI Application."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from textual.app import App, ComposeResult
|
|
7
|
+
|
|
8
|
+
from xp.services.term.state_monitor_service import StateMonitorService
|
|
9
|
+
from xp.term.widgets.modules_list import ModulesListWidget
|
|
10
|
+
from xp.term.widgets.status_footer import StatusFooterWidget
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StateMonitorApp(App[None]):
|
|
14
|
+
"""Textual app for module state monitoring.
|
|
15
|
+
|
|
16
|
+
Displays module states from Conson configuration in an interactive
|
|
17
|
+
terminal interface with real-time updates.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
state_service: StateMonitorService for module state operations.
|
|
21
|
+
CSS_PATH: Path to CSS stylesheet file.
|
|
22
|
+
BINDINGS: Keyboard bindings for app actions.
|
|
23
|
+
TITLE: Application title displayed in header.
|
|
24
|
+
ENABLE_COMMAND_PALETTE: Disable Textual's command palette feature.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
CSS_PATH = Path(__file__).parent / "state.tcss"
|
|
28
|
+
TITLE = "Modules"
|
|
29
|
+
ENABLE_COMMAND_PALETTE = False
|
|
30
|
+
|
|
31
|
+
BINDINGS = [
|
|
32
|
+
("Q", "quit", "Quit"),
|
|
33
|
+
("C", "toggle_connection", "Connect"),
|
|
34
|
+
("r", "refresh_all", "Refresh"),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def __init__(self, state_service: StateMonitorService) -> None:
|
|
38
|
+
"""Initialize the State Monitor app.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
state_service: StateMonitorService for module state operations.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.state_service: StateMonitorService = state_service
|
|
45
|
+
self.modules_widget: Optional[ModulesListWidget] = None
|
|
46
|
+
self.footer_widget: Optional[StatusFooterWidget] = None
|
|
47
|
+
|
|
48
|
+
def compose(self) -> ComposeResult:
|
|
49
|
+
"""Compose the app layout with widgets.
|
|
50
|
+
|
|
51
|
+
Yields:
|
|
52
|
+
ModulesListWidget and StatusFooterWidget.
|
|
53
|
+
"""
|
|
54
|
+
self.modules_widget = ModulesListWidget(
|
|
55
|
+
service=self.state_service, id="modules-list"
|
|
56
|
+
)
|
|
57
|
+
yield self.modules_widget
|
|
58
|
+
|
|
59
|
+
self.footer_widget = StatusFooterWidget(
|
|
60
|
+
service=self.state_service, id="footer-container"
|
|
61
|
+
)
|
|
62
|
+
yield self.footer_widget
|
|
63
|
+
|
|
64
|
+
async def on_mount(self) -> None:
|
|
65
|
+
"""Initialize app after UI is mounted.
|
|
66
|
+
|
|
67
|
+
Delays connection by 0.5s to let UI render first.
|
|
68
|
+
Sets up automatic screen refresh every second to update elapsed times.
|
|
69
|
+
"""
|
|
70
|
+
import asyncio
|
|
71
|
+
|
|
72
|
+
# Delay connection to let UI render
|
|
73
|
+
await asyncio.sleep(0.5)
|
|
74
|
+
self.state_service.connect()
|
|
75
|
+
|
|
76
|
+
# Set up periodic refresh to update elapsed times
|
|
77
|
+
self.set_interval(1.0, self._refresh_last_update_column)
|
|
78
|
+
|
|
79
|
+
def _refresh_last_update_column(self) -> None:
|
|
80
|
+
"""Refresh only the last_update column to show elapsed time."""
|
|
81
|
+
if self.modules_widget:
|
|
82
|
+
self.modules_widget.refresh_last_update_times()
|
|
83
|
+
|
|
84
|
+
def action_toggle_connection(self) -> None:
|
|
85
|
+
"""Toggle connection on 'c' key press.
|
|
86
|
+
|
|
87
|
+
Connects if disconnected/failed, disconnects if connected/connecting.
|
|
88
|
+
"""
|
|
89
|
+
self.state_service.toggle_connection()
|
|
90
|
+
|
|
91
|
+
def action_refresh_all(self) -> None:
|
|
92
|
+
"""Refresh all module data on 'r' key press."""
|
|
93
|
+
self.state_service.refresh_all()
|
|
94
|
+
|
|
95
|
+
def on_unmount(self) -> None:
|
|
96
|
+
"""Clean up service when app unmounts."""
|
|
97
|
+
self.state_service.cleanup()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* State Monitor TUI Styling */
|
|
2
|
+
|
|
3
|
+
/* Color overrides */
|
|
4
|
+
$success: #00ff00;
|
|
5
|
+
|
|
6
|
+
/* App-level styling */
|
|
7
|
+
Screen {
|
|
8
|
+
background: $background;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Modules List Widget */
|
|
12
|
+
ModulesListWidget {
|
|
13
|
+
border: solid $success;
|
|
14
|
+
border-title-align: left;
|
|
15
|
+
width: 1fr;
|
|
16
|
+
height: 1fr;
|
|
17
|
+
background: $background;
|
|
18
|
+
padding: 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ModulesListWidget:focus {
|
|
22
|
+
background: $background;
|
|
23
|
+
background-tint: transparent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#modules-table {
|
|
27
|
+
background: $background !important;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 1fr;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#modules-table:focus {
|
|
33
|
+
background: $background !important;
|
|
34
|
+
background-tint: transparent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
DataTable {
|
|
38
|
+
background: $background;
|
|
39
|
+
color: $success;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
DataTable > .datatable--header {
|
|
43
|
+
background: $background;
|
|
44
|
+
color: $success;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
DataTable > .datatable--cursor {
|
|
48
|
+
background: $background;
|
|
49
|
+
color: $success;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
DataTable:focus > .datatable--cursor {
|
|
53
|
+
background: $background;
|
|
54
|
+
color: $success;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Footer styling */
|
|
58
|
+
#footer-container {
|
|
59
|
+
dock: bottom;
|
|
60
|
+
height: 1;
|
|
61
|
+
background: $background;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Footer {
|
|
65
|
+
width: auto;
|
|
66
|
+
background: $background;
|
|
67
|
+
color: $text;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#status-text {
|
|
71
|
+
dock: right;
|
|
72
|
+
width: auto;
|
|
73
|
+
padding: 0 3;
|
|
74
|
+
background: $background;
|
|
75
|
+
color: $text;
|
|
76
|
+
text-align: right;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#status-line {
|
|
80
|
+
dock: right;
|
|
81
|
+
width: auto;
|
|
82
|
+
padding: 0 1;
|
|
83
|
+
background: $background;
|
|
84
|
+
color: $text;
|
|
85
|
+
text-align: right;
|
|
86
|
+
}
|