conson-xp 1.28.0__tar.gz → 1.32.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.28.0 → conson_xp-1.32.0}/PKG-INFO +2 -1
- {conson_xp-1.28.0 → conson_xp-1.32.0}/README.md +1 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/pyproject.toml +1 -1
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/__init__.py +1 -1
- conson_xp-1.32.0/src/xp/cli/commands/conbus/conbus_export_commands.py +88 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/main.py +4 -2
- conson_xp-1.32.0/src/xp/models/conbus/conbus_export.py +31 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/module_state.py +2 -0
- conson_xp-1.32.0/src/xp/services/conbus/conbus_export_service.py +460 -0
- conson_xp-1.32.0/src/xp/services/server/client_buffer_manager.py +69 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/server_service.py +21 -11
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/term/state_monitor_service.py +105 -5
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/widgets/modules_list.py +16 -9
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/dependencies.py +9 -0
- conson_xp-1.32.0/tests/unit/test_services/test_client_buffer_manager.py +313 -0
- conson_xp-1.32.0/tests/unit/test_services/test_state_monitor_service.py +364 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/LICENSE +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/commands/term/term_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/connection/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/connection/exceptions.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/term/telegram_display.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/protocol/conbus_event_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/server/xp33_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/term/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/state.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/state.tcss +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/widgets/help_menu.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/widgets/protocol_log.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/term/widgets/status_footer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/logging.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/.coverage +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/conftest.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_connection/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_connection/test_connection_init.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_connection/test_exceptions.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_telegram_output_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.28.0 → conson_xp-1.32.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.32.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -325,6 +325,7 @@ xp conbus event
|
|
|
325
325
|
xp conbus event list
|
|
326
326
|
xp conbus event raw
|
|
327
327
|
|
|
328
|
+
xp conbus export
|
|
328
329
|
|
|
329
330
|
xp conbus lightlevel
|
|
330
331
|
xp conbus lightlevel get
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Conbus export CLI command."""
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from xp.cli.commands.conbus.conbus import conbus
|
|
8
|
+
from xp.cli.utils.decorators import connection_command
|
|
9
|
+
from xp.models.conbus.conbus_export import ConbusExportResponse
|
|
10
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleConfig
|
|
11
|
+
from xp.services.conbus.conbus_export_service import ConbusExportService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@conbus.command("export")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
@connection_command()
|
|
17
|
+
def export_conbus_config(ctx: click.Context) -> None:
|
|
18
|
+
r"""Export Conbus device configuration to YAML file.
|
|
19
|
+
|
|
20
|
+
Discovers all devices on the Conbus network and queries their configuration
|
|
21
|
+
datapoints to generate a complete export.yml file in conson.yml format.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
ctx: Click context object.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
\b
|
|
28
|
+
# Export to export.yml in current directory
|
|
29
|
+
xp conbus export
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def on_progress(serial_number: str, current: int, total: int) -> None:
|
|
33
|
+
"""Handle progress updates during export.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
serial_number: Serial number of discovered device.
|
|
37
|
+
current: Current device number.
|
|
38
|
+
total: Total devices discovered.
|
|
39
|
+
"""
|
|
40
|
+
click.echo(f"Querying device {current}/{total}: {serial_number}...")
|
|
41
|
+
|
|
42
|
+
def on_device_exported(module: ConsonModuleConfig) -> None:
|
|
43
|
+
"""Handle device export completion.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
module: Exported module configuration.
|
|
47
|
+
"""
|
|
48
|
+
module_type = module.module_type or "UNKNOWN"
|
|
49
|
+
module_code = (
|
|
50
|
+
module.module_type_code if module.module_type_code is not None else "?"
|
|
51
|
+
)
|
|
52
|
+
click.echo(f" ✓ Module type: {module_type} ({module_code})")
|
|
53
|
+
|
|
54
|
+
if module.link_number is not None:
|
|
55
|
+
click.echo(f" ✓ Link number: {module.link_number}")
|
|
56
|
+
if module.sw_version:
|
|
57
|
+
click.echo(f" ✓ Software version: {module.sw_version}")
|
|
58
|
+
|
|
59
|
+
def on_finish(result: ConbusExportResponse) -> None:
|
|
60
|
+
"""Handle export completion.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
result: Export result.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ClickException: When export fails with error message from result.
|
|
67
|
+
"""
|
|
68
|
+
# Try to stop reactor (may already be stopped)
|
|
69
|
+
with suppress(Exception):
|
|
70
|
+
service.stop_reactor()
|
|
71
|
+
|
|
72
|
+
if result.success:
|
|
73
|
+
click.echo(
|
|
74
|
+
f"\nExport complete: {result.output_file} ({result.device_count} devices)"
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
click.echo(f"Error: {result.error}", err=True)
|
|
78
|
+
raise click.ClickException(result.error or "Export failed")
|
|
79
|
+
|
|
80
|
+
service: ConbusExportService = (
|
|
81
|
+
ctx.obj.get("container").get_container().resolve(ConbusExportService)
|
|
82
|
+
)
|
|
83
|
+
with service:
|
|
84
|
+
service.on_progress.connect(on_progress)
|
|
85
|
+
service.on_device_exported.connect(on_device_exported)
|
|
86
|
+
service.on_finish.connect(on_finish)
|
|
87
|
+
service.set_timeout(5)
|
|
88
|
+
service.start_reactor()
|
|
@@ -4,11 +4,13 @@ import click
|
|
|
4
4
|
from click_help_colors import HelpColorsGroup
|
|
5
5
|
|
|
6
6
|
from xp.cli.commands import homekit
|
|
7
|
+
|
|
8
|
+
# Import all conbus command modules to register their commands
|
|
9
|
+
from xp.cli.commands.conbus import conbus_discover_commands # noqa: F401
|
|
10
|
+
from xp.cli.commands.conbus import conbus_export_commands # noqa: F401
|
|
7
11
|
from xp.cli.commands.conbus.conbus import conbus
|
|
8
12
|
from xp.cli.commands.file_commands import file
|
|
9
13
|
from xp.cli.commands.module_commands import module
|
|
10
|
-
|
|
11
|
-
# Import all conbus command modules to register their commands
|
|
12
14
|
from xp.cli.commands.reverse_proxy_commands import reverse_proxy
|
|
13
15
|
from xp.cli.commands.server.server_commands import server
|
|
14
16
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Conbus export response model."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ConbusExportResponse:
|
|
11
|
+
"""Response from Conbus export operation.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
success: Whether the operation was successful.
|
|
15
|
+
config: Exported module configuration list.
|
|
16
|
+
device_count: Number of devices exported.
|
|
17
|
+
output_file: Path to output file.
|
|
18
|
+
export_status: Export status (OK, FAILED_TIMEOUT, FAILED_NO_DEVICES, etc.).
|
|
19
|
+
error: Error message if operation failed.
|
|
20
|
+
sent_telegrams: List of telegrams sent during export.
|
|
21
|
+
received_telegrams: List of telegrams received during export.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
success: bool
|
|
25
|
+
config: Optional[ConsonModuleListConfig] = None
|
|
26
|
+
device_count: int = 0
|
|
27
|
+
output_file: str = "export.yml"
|
|
28
|
+
export_status: str = "OK"
|
|
29
|
+
error: Optional[str] = None
|
|
30
|
+
sent_telegrams: list[str] = field(default_factory=list)
|
|
31
|
+
received_telegrams: list[str] = field(default_factory=list)
|
|
@@ -13,6 +13,7 @@ class ModuleState:
|
|
|
13
13
|
name: Module name/identifier (e.g., A01, A02).
|
|
14
14
|
serial_number: Module serial number.
|
|
15
15
|
module_type: Module type designation (e.g., XP130, XP230, XP24).
|
|
16
|
+
link_number: Link number for the module.
|
|
16
17
|
outputs: Output states as space-separated binary values. Empty string for modules without outputs.
|
|
17
18
|
auto_report: Auto-report enabled status (Y/N).
|
|
18
19
|
error_status: Module status ("OK" or error code like "E10").
|
|
@@ -22,6 +23,7 @@ class ModuleState:
|
|
|
22
23
|
name: str
|
|
23
24
|
serial_number: str
|
|
24
25
|
module_type: str
|
|
26
|
+
link_number: int
|
|
25
27
|
outputs: str
|
|
26
28
|
auto_report: bool
|
|
27
29
|
error_status: str
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"""Conbus export service for exporting device configurations."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from psygnal import Signal
|
|
10
|
+
|
|
11
|
+
from xp.models.conbus.conbus_export import ConbusExportResponse
|
|
12
|
+
from xp.models.homekit.homekit_conson_config import (
|
|
13
|
+
ConsonModuleConfig,
|
|
14
|
+
ConsonModuleListConfig,
|
|
15
|
+
)
|
|
16
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
17
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
18
|
+
from xp.models.telegram.reply_telegram import ReplyTelegram
|
|
19
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
20
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
21
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
22
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConbusExportService:
|
|
26
|
+
"""Service for exporting Conbus device configurations.
|
|
27
|
+
|
|
28
|
+
Discovers all devices on the Conbus network and queries their configuration
|
|
29
|
+
datapoints to generate a structured export file compatible with conson.yml format.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
conbus_protocol: Protocol for Conbus communication.
|
|
33
|
+
discovered_devices: List of discovered device serial numbers.
|
|
34
|
+
device_configs: Partial device configurations being built.
|
|
35
|
+
device_datapoints_received: Set of datapoints received per device.
|
|
36
|
+
export_result: Final export result.
|
|
37
|
+
export_status: Export status (OK, FAILED_TIMEOUT, etc.).
|
|
38
|
+
on_progress: Signal emitted on device discovery (serial, current, total).
|
|
39
|
+
on_device_exported: Signal emitted when device export completes.
|
|
40
|
+
on_finish: Signal emitted when export finishes.
|
|
41
|
+
DATAPOINT_SEQUENCE: Sequence of 7 datapoints to query for each device.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Signals (class attributes)
|
|
45
|
+
on_progress: Signal = Signal(str, int, int) # serial, current, total
|
|
46
|
+
on_device_exported: Signal = Signal(ConsonModuleConfig)
|
|
47
|
+
on_finish: Signal = Signal(ConbusExportResponse)
|
|
48
|
+
|
|
49
|
+
# Datapoint sequence to query for each device
|
|
50
|
+
DATAPOINT_SEQUENCE = [
|
|
51
|
+
DataPointType.MODULE_TYPE,
|
|
52
|
+
DataPointType.MODULE_TYPE_CODE,
|
|
53
|
+
DataPointType.LINK_NUMBER,
|
|
54
|
+
DataPointType.MODULE_NUMBER,
|
|
55
|
+
DataPointType.SW_VERSION,
|
|
56
|
+
DataPointType.HW_VERSION,
|
|
57
|
+
DataPointType.AUTO_REPORT_STATUS,
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def __init__(self, conbus_protocol: ConbusEventProtocol) -> None:
|
|
61
|
+
"""Initialize the Conbus export service.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
conbus_protocol: Protocol for Conbus communication.
|
|
65
|
+
"""
|
|
66
|
+
self.logger = logging.getLogger(__name__)
|
|
67
|
+
self.conbus_protocol = conbus_protocol
|
|
68
|
+
self.telegram_service = TelegramService()
|
|
69
|
+
|
|
70
|
+
# State management
|
|
71
|
+
self.discovered_devices: list[str] = []
|
|
72
|
+
self.device_configs: dict[str, dict[str, Any]] = {}
|
|
73
|
+
self.device_datapoints_received: dict[str, set[str]] = {}
|
|
74
|
+
self.export_result = ConbusExportResponse(success=False)
|
|
75
|
+
self.export_status = "OK"
|
|
76
|
+
self._finalized = False # Track if export has been finalized
|
|
77
|
+
|
|
78
|
+
# Connect protocol signals
|
|
79
|
+
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
80
|
+
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
81
|
+
self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
|
|
82
|
+
self.conbus_protocol.on_timeout.connect(self.timeout)
|
|
83
|
+
self.conbus_protocol.on_failed.connect(self.failed)
|
|
84
|
+
|
|
85
|
+
def connection_made(self) -> None:
|
|
86
|
+
"""Handle connection established event."""
|
|
87
|
+
self.logger.debug("Connection established, starting discovery")
|
|
88
|
+
|
|
89
|
+
# Send DISCOVERY telegram
|
|
90
|
+
self.conbus_protocol.send_telegram(
|
|
91
|
+
telegram_type=TelegramType.SYSTEM,
|
|
92
|
+
serial_number="0000000000",
|
|
93
|
+
system_function=SystemFunction.DISCOVERY,
|
|
94
|
+
data_value="00",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def telegram_sent(self, telegram: str) -> None:
|
|
98
|
+
"""Handle telegram sent event.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
telegram: Telegram that was sent.
|
|
102
|
+
"""
|
|
103
|
+
self.export_result.sent_telegrams.append(telegram)
|
|
104
|
+
|
|
105
|
+
def telegram_received(self, event: TelegramReceivedEvent) -> None:
|
|
106
|
+
"""Handle telegram received event.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
event: Telegram received event.
|
|
110
|
+
"""
|
|
111
|
+
self.export_result.received_telegrams.append(event.telegram)
|
|
112
|
+
|
|
113
|
+
# Only process valid reply telegrams
|
|
114
|
+
if not event.checksum_valid or event.telegram_type != TelegramType.REPLY.value:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Parse telegram using TelegramService
|
|
118
|
+
try:
|
|
119
|
+
parsed: ReplyTelegram = self.telegram_service.parse_reply_telegram(
|
|
120
|
+
event.frame
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
self.logger.debug(f"Failed to parse telegram: {e}")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# Check for discovery response (F01D)
|
|
127
|
+
if parsed.system_function == SystemFunction.DISCOVERY:
|
|
128
|
+
self._handle_discovery_response(parsed.serial_number)
|
|
129
|
+
|
|
130
|
+
# Check for datapoint response (F02D)
|
|
131
|
+
elif parsed.system_function == SystemFunction.READ_DATAPOINT:
|
|
132
|
+
if parsed.datapoint_type and parsed.data_value:
|
|
133
|
+
self._handle_datapoint_response(
|
|
134
|
+
parsed.serial_number, parsed.datapoint_type.value, parsed.data_value
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _handle_discovery_response(self, serial_number: str) -> None:
|
|
138
|
+
"""Handle discovery response and query all datapoints.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
serial_number: Serial number of discovered device.
|
|
142
|
+
"""
|
|
143
|
+
if serial_number in self.discovered_devices:
|
|
144
|
+
self.logger.debug(f"Ignoring duplicate discovery: {serial_number}")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
self.logger.debug(f"Device discovered: {serial_number}")
|
|
148
|
+
self.discovered_devices.append(serial_number)
|
|
149
|
+
self.device_configs[serial_number] = {"serial_number": serial_number}
|
|
150
|
+
self.device_datapoints_received[serial_number] = set()
|
|
151
|
+
|
|
152
|
+
# Emit progress signal
|
|
153
|
+
current = len(self.discovered_devices)
|
|
154
|
+
total = current # We don't know total until timeout
|
|
155
|
+
self.on_progress.emit(serial_number, current, total)
|
|
156
|
+
|
|
157
|
+
# Send all datapoint queries immediately (protocol handles throttling)
|
|
158
|
+
self.logger.debug(
|
|
159
|
+
f"Sending {len(self.DATAPOINT_SEQUENCE)} queries for {serial_number}"
|
|
160
|
+
)
|
|
161
|
+
for datapoint in self.DATAPOINT_SEQUENCE:
|
|
162
|
+
self.conbus_protocol.send_telegram(
|
|
163
|
+
telegram_type=TelegramType.SYSTEM,
|
|
164
|
+
serial_number=serial_number,
|
|
165
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
166
|
+
data_value=datapoint.value,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _handle_datapoint_response(
|
|
170
|
+
self, serial_number: str, datapoint_code: str, value: str
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Handle datapoint response and store value.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
serial_number: Serial number of device.
|
|
176
|
+
datapoint_code: Datapoint type code.
|
|
177
|
+
value: Datapoint value.
|
|
178
|
+
"""
|
|
179
|
+
if serial_number not in self.device_configs:
|
|
180
|
+
self.logger.warning(
|
|
181
|
+
f"Received datapoint for unknown device: {serial_number}"
|
|
182
|
+
)
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
self.logger.debug(f"Datapoint {datapoint_code}={value} for {serial_number}")
|
|
186
|
+
|
|
187
|
+
# Store value in device config
|
|
188
|
+
datapoint = DataPointType.from_code(datapoint_code)
|
|
189
|
+
if datapoint:
|
|
190
|
+
self._store_datapoint_value(serial_number, datapoint, value)
|
|
191
|
+
self.device_datapoints_received[serial_number].add(datapoint_code)
|
|
192
|
+
self._check_device_complete(serial_number)
|
|
193
|
+
else:
|
|
194
|
+
self.logger.warning(f"Unknown datapoint code: {datapoint_code}")
|
|
195
|
+
|
|
196
|
+
def _store_datapoint_value(
|
|
197
|
+
self, serial_number: str, datapoint: DataPointType, value: str
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Store datapoint value in device config.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
serial_number: Serial number of device.
|
|
203
|
+
datapoint: Datapoint type.
|
|
204
|
+
value: Datapoint value.
|
|
205
|
+
"""
|
|
206
|
+
config = self.device_configs[serial_number]
|
|
207
|
+
|
|
208
|
+
if datapoint == DataPointType.MODULE_TYPE:
|
|
209
|
+
config["module_type"] = value
|
|
210
|
+
elif datapoint == DataPointType.MODULE_TYPE_CODE:
|
|
211
|
+
try:
|
|
212
|
+
config["module_type_code"] = int(value)
|
|
213
|
+
except ValueError:
|
|
214
|
+
self.logger.warning(f"Invalid module_type_code: {value}")
|
|
215
|
+
elif datapoint == DataPointType.LINK_NUMBER:
|
|
216
|
+
try:
|
|
217
|
+
config["link_number"] = int(value)
|
|
218
|
+
except ValueError:
|
|
219
|
+
self.logger.warning(f"Invalid link_number: {value}")
|
|
220
|
+
elif datapoint == DataPointType.MODULE_NUMBER:
|
|
221
|
+
try:
|
|
222
|
+
config["module_number"] = int(value)
|
|
223
|
+
except ValueError:
|
|
224
|
+
self.logger.warning(f"Invalid module_number: {value}")
|
|
225
|
+
elif datapoint == DataPointType.SW_VERSION:
|
|
226
|
+
config["sw_version"] = value
|
|
227
|
+
elif datapoint == DataPointType.HW_VERSION:
|
|
228
|
+
config["hw_version"] = value
|
|
229
|
+
elif datapoint == DataPointType.AUTO_REPORT_STATUS:
|
|
230
|
+
config["auto_report_status"] = value
|
|
231
|
+
|
|
232
|
+
def _check_device_complete(self, serial_number: str) -> None:
|
|
233
|
+
"""Check if device has all datapoints and emit completion signal.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
serial_number: Serial number of device.
|
|
237
|
+
"""
|
|
238
|
+
received = self.device_datapoints_received[serial_number]
|
|
239
|
+
expected = {dp.value for dp in self.DATAPOINT_SEQUENCE}
|
|
240
|
+
|
|
241
|
+
if received == expected:
|
|
242
|
+
self.logger.debug(f"Device {serial_number} complete (7/7 datapoints)")
|
|
243
|
+
config = self.device_configs[serial_number]
|
|
244
|
+
|
|
245
|
+
# Build ConsonModuleConfig with name based on link_number
|
|
246
|
+
try:
|
|
247
|
+
# Add required 'name' field as A{link_number}
|
|
248
|
+
if "name" not in config:
|
|
249
|
+
link_number = config.get("link_number", 0)
|
|
250
|
+
config["name"] = f"A{link_number}"
|
|
251
|
+
module_config = ConsonModuleConfig(**config)
|
|
252
|
+
self.on_device_exported.emit(module_config)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
self.logger.error(f"Failed to build config for {serial_number}: {e}")
|
|
255
|
+
|
|
256
|
+
# Check if all devices complete
|
|
257
|
+
if all(
|
|
258
|
+
len(self.device_datapoints_received[sn]) == len(self.DATAPOINT_SEQUENCE)
|
|
259
|
+
for sn in self.discovered_devices
|
|
260
|
+
):
|
|
261
|
+
self.logger.debug("All devices complete")
|
|
262
|
+
self._finalize_export()
|
|
263
|
+
|
|
264
|
+
def _finalize_export(self) -> None:
|
|
265
|
+
"""Finalize export and write file."""
|
|
266
|
+
# Only finalize once
|
|
267
|
+
if self._finalized:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
self._finalized = True
|
|
271
|
+
self.logger.info("Finalizing export")
|
|
272
|
+
|
|
273
|
+
if not self.discovered_devices:
|
|
274
|
+
self.export_status = "FAILED_NO_DEVICES"
|
|
275
|
+
self.export_result.success = False
|
|
276
|
+
self.export_result.error = "No devices found"
|
|
277
|
+
self.export_result.export_status = self.export_status
|
|
278
|
+
self.on_finish.emit(self.export_result)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# Build module list (including partial devices)
|
|
282
|
+
modules = []
|
|
283
|
+
for serial_number in self.discovered_devices:
|
|
284
|
+
config = self.device_configs[serial_number].copy()
|
|
285
|
+
try:
|
|
286
|
+
# Add required 'name' field as A{link_number} if not present
|
|
287
|
+
if "name" not in config:
|
|
288
|
+
link_number = config.get("link_number", 0)
|
|
289
|
+
config["name"] = f"A{link_number}"
|
|
290
|
+
# Only include fields that were received
|
|
291
|
+
module_config = ConsonModuleConfig(**config)
|
|
292
|
+
modules.append(module_config)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self.logger.warning(f"Partial device {serial_number}: {e}")
|
|
295
|
+
|
|
296
|
+
# Sort modules by link_number
|
|
297
|
+
modules.sort(key=lambda m: m.link_number if m.link_number is not None else 999)
|
|
298
|
+
|
|
299
|
+
# Create ConsonModuleListConfig
|
|
300
|
+
try:
|
|
301
|
+
module_list = ConsonModuleListConfig(root=modules)
|
|
302
|
+
self.export_result.config = module_list
|
|
303
|
+
self.export_result.device_count = len(modules)
|
|
304
|
+
|
|
305
|
+
# Write to file
|
|
306
|
+
self._write_export_file("export.yml")
|
|
307
|
+
|
|
308
|
+
self.export_result.success = True
|
|
309
|
+
self.export_result.export_status = self.export_status
|
|
310
|
+
self.on_finish.emit(self.export_result)
|
|
311
|
+
|
|
312
|
+
except Exception as e:
|
|
313
|
+
self.logger.error(f"Failed to create export: {e}")
|
|
314
|
+
self.export_status = "FAILED_WRITE"
|
|
315
|
+
self.export_result.success = False
|
|
316
|
+
self.export_result.error = str(e)
|
|
317
|
+
self.export_result.export_status = self.export_status
|
|
318
|
+
self.on_finish.emit(self.export_result)
|
|
319
|
+
|
|
320
|
+
def _write_export_file(self, path: str) -> None:
|
|
321
|
+
"""Write export to YAML file.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
path: Output file path.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
Exception: If file write fails.
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
output_path = Path(path)
|
|
331
|
+
|
|
332
|
+
if self.export_result.config:
|
|
333
|
+
# Use Pydantic's model_dump to serialize, excluding only internal fields
|
|
334
|
+
data = self.export_result.config.model_dump(
|
|
335
|
+
exclude={
|
|
336
|
+
"root": {
|
|
337
|
+
"__all__": {
|
|
338
|
+
"enabled",
|
|
339
|
+
"conbus_ip",
|
|
340
|
+
"conbus_port",
|
|
341
|
+
"action_table",
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
exclude_none=True,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Export as list at root level (not wrapped in 'root:' key)
|
|
349
|
+
modules_list = data.get("root", [])
|
|
350
|
+
|
|
351
|
+
with output_path.open("w") as f:
|
|
352
|
+
# Dump each module separately with blank lines between them
|
|
353
|
+
for i, module in enumerate(modules_list):
|
|
354
|
+
# Add blank line before each module except the first
|
|
355
|
+
if i > 0:
|
|
356
|
+
f.write("\n")
|
|
357
|
+
|
|
358
|
+
# Dump single item as list element
|
|
359
|
+
yaml_str = yaml.safe_dump(
|
|
360
|
+
[module],
|
|
361
|
+
default_flow_style=False,
|
|
362
|
+
sort_keys=False,
|
|
363
|
+
allow_unicode=True,
|
|
364
|
+
)
|
|
365
|
+
# Remove the trailing newline and write
|
|
366
|
+
f.write(yaml_str.rstrip("\n") + "\n")
|
|
367
|
+
|
|
368
|
+
self.logger.info(f"Export written to {path}")
|
|
369
|
+
self.export_result.output_file = path
|
|
370
|
+
|
|
371
|
+
except Exception as e:
|
|
372
|
+
self.logger.error(f"Failed to write export file: {e}")
|
|
373
|
+
self.export_status = "FAILED_WRITE"
|
|
374
|
+
raise
|
|
375
|
+
|
|
376
|
+
def timeout(self) -> None:
|
|
377
|
+
"""Handle timeout event."""
|
|
378
|
+
timeout = self.conbus_protocol.timeout_seconds
|
|
379
|
+
self.logger.info(f"Export timeout after {timeout}s")
|
|
380
|
+
|
|
381
|
+
# Check if any devices incomplete
|
|
382
|
+
incomplete = [
|
|
383
|
+
sn
|
|
384
|
+
for sn in self.discovered_devices
|
|
385
|
+
if len(self.device_datapoints_received[sn]) < len(self.DATAPOINT_SEQUENCE)
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
if incomplete:
|
|
389
|
+
self.logger.warning(f"Partial export: {len(incomplete)} incomplete devices")
|
|
390
|
+
self.export_status = "FAILED_TIMEOUT"
|
|
391
|
+
|
|
392
|
+
self._finalize_export()
|
|
393
|
+
|
|
394
|
+
def failed(self, message: str) -> None:
|
|
395
|
+
"""Handle connection failure event.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
message: Failure message.
|
|
399
|
+
"""
|
|
400
|
+
self.logger.error(f"Connection failed: {message}")
|
|
401
|
+
self.export_status = "FAILED_CONNECTION"
|
|
402
|
+
self.export_result.success = False
|
|
403
|
+
self.export_result.error = message
|
|
404
|
+
self.export_result.export_status = self.export_status
|
|
405
|
+
self.on_finish.emit(self.export_result)
|
|
406
|
+
|
|
407
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
408
|
+
"""Set timeout for export operation.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
timeout_seconds: Timeout in seconds.
|
|
412
|
+
"""
|
|
413
|
+
self.logger.debug(f"Set timeout: {timeout_seconds}s")
|
|
414
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
415
|
+
|
|
416
|
+
def set_event_loop(self, event_loop: asyncio.AbstractEventLoop) -> None:
|
|
417
|
+
"""Set event loop for async operations.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
event_loop: Event loop to use.
|
|
421
|
+
"""
|
|
422
|
+
self.logger.debug("Set event loop")
|
|
423
|
+
self.conbus_protocol.set_event_loop(event_loop)
|
|
424
|
+
|
|
425
|
+
def start_reactor(self) -> None:
|
|
426
|
+
"""Start the reactor."""
|
|
427
|
+
self.conbus_protocol.start_reactor()
|
|
428
|
+
|
|
429
|
+
def stop_reactor(self) -> None:
|
|
430
|
+
"""Stop the reactor."""
|
|
431
|
+
self.conbus_protocol.stop_reactor()
|
|
432
|
+
|
|
433
|
+
def __enter__(self) -> "ConbusExportService":
|
|
434
|
+
"""Enter context manager.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Self for context manager protocol.
|
|
438
|
+
"""
|
|
439
|
+
# Reset state for reuse
|
|
440
|
+
self.discovered_devices = []
|
|
441
|
+
self.device_configs = {}
|
|
442
|
+
self.device_datapoints_received = {}
|
|
443
|
+
self.export_result = ConbusExportResponse(success=False)
|
|
444
|
+
self.export_status = "OK"
|
|
445
|
+
self._finalized = False
|
|
446
|
+
return self
|
|
447
|
+
|
|
448
|
+
def __exit__(
|
|
449
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
450
|
+
) -> None:
|
|
451
|
+
"""Exit context manager and disconnect signals."""
|
|
452
|
+
self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
|
|
453
|
+
self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
|
|
454
|
+
self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
|
|
455
|
+
self.conbus_protocol.on_timeout.disconnect(self.timeout)
|
|
456
|
+
self.conbus_protocol.on_failed.disconnect(self.failed)
|
|
457
|
+
self.on_progress.disconnect()
|
|
458
|
+
self.on_device_exported.disconnect()
|
|
459
|
+
self.on_finish.disconnect()
|
|
460
|
+
self.stop_reactor()
|