conson-xp 1.45.0__tar.gz → 1.46.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.45.0 → conson_xp-1.46.0}/PKG-INFO +1 -1
- {conson_xp-1.45.0 → conson_xp-1.46.0}/pyproject.toml +1 -1
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/__init__.py +1 -1
- conson_xp-1.46.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +345 -0
- conson_xp-1.46.0/src/xp/services/conbus/actiontable/actiontable_download_state_machine.py +276 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/conbus_event_protocol.py +98 -1
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_download_service.py +3 -41
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_service.py +14 -52
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_protocol.py +11 -2
- conson_xp-1.45.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -525
- {conson_xp-1.45.0 → conson_xp-1.46.0}/LICENSE +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/README.md +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/term_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_export.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/config/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/config/conson_module_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/module_state.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/telegram_display.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_export_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_upload_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/client_buffer_manager.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp33_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/state_monitor_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/state.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/state.tcss +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/help_menu.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/modules_list.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/protocol_log.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/status_footer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/dependencies.py +1 -1
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/logging.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/.coverage +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/conftest.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_output_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_msactiontable_upload_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_state_monitor_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_output_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp33_short_format.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_time_utils.py +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Service for downloading ActionTable via Conbus protocol."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from psygnal import SignalInstance
|
|
8
|
+
|
|
9
|
+
from xp.models.actiontable.actiontable import ActionTable
|
|
10
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
11
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
12
|
+
from xp.models.telegram.reply_telegram import ReplyTelegram
|
|
13
|
+
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
14
|
+
from xp.services.conbus.actiontable.actiontable_download_state_machine import (
|
|
15
|
+
MAX_ERROR_RETRIES,
|
|
16
|
+
ActionTableDownloadStateMachine,
|
|
17
|
+
)
|
|
18
|
+
from xp.services.protocol.conbus_event_protocol import (
|
|
19
|
+
NO_ERROR_CODE,
|
|
20
|
+
ConbusEventProtocol,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ActionTableDownloadService(ActionTableDownloadStateMachine):
|
|
25
|
+
"""Service for downloading action tables from Conbus modules via TCP.
|
|
26
|
+
|
|
27
|
+
Inherits from ActionTableDownloadStateMachine and overrides on_enter_*
|
|
28
|
+
methods to add protocol-specific behavior.
|
|
29
|
+
|
|
30
|
+
The workflow consists of three phases:
|
|
31
|
+
|
|
32
|
+
INIT phase (drain → reset → wait_ok):
|
|
33
|
+
Connection established, drain pending telegrams, query error status.
|
|
34
|
+
|
|
35
|
+
DOWNLOAD phase (request → receive chunks → EOF):
|
|
36
|
+
Request actiontable, receive and ACK chunks until EOF.
|
|
37
|
+
|
|
38
|
+
CLEANUP phase (drain → reset → wait_ok):
|
|
39
|
+
After EOF, drain remaining telegrams and verify final status.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
on_progress: Signal emitted with "." for each chunk received.
|
|
43
|
+
on_error: Signal emitted with error message string.
|
|
44
|
+
on_actiontable_received: Signal emitted with (ActionTable, dict, list).
|
|
45
|
+
on_finish: Signal emitted when download and cleanup completed.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> with download_service as service:
|
|
49
|
+
... service.configure(serial_number="12345678")
|
|
50
|
+
... service.on_actiontable_received.connect(handle_result)
|
|
51
|
+
... service.start_reactor()
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
conbus_protocol: ConbusEventProtocol,
|
|
57
|
+
actiontable_serializer: ActionTableSerializer,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize the action table download service.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
conbus_protocol: ConbusEventProtocol instance.
|
|
63
|
+
actiontable_serializer: Action table serializer.
|
|
64
|
+
"""
|
|
65
|
+
self.conbus_protocol = conbus_protocol
|
|
66
|
+
self.serializer = actiontable_serializer
|
|
67
|
+
self.serial_number: str = ""
|
|
68
|
+
self.actiontable_data: list[str] = []
|
|
69
|
+
self._signals_connected: bool = False
|
|
70
|
+
|
|
71
|
+
# Service signals
|
|
72
|
+
self.on_progress: SignalInstance = SignalInstance((str,))
|
|
73
|
+
self.on_error: SignalInstance = SignalInstance((str,))
|
|
74
|
+
self.on_finish: SignalInstance = SignalInstance()
|
|
75
|
+
self.on_actiontable_received: SignalInstance = SignalInstance(
|
|
76
|
+
(ActionTable, dict[str, Any], list[str])
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Initialize state machine (must be last - triggers introspection)
|
|
80
|
+
super().__init__()
|
|
81
|
+
|
|
82
|
+
# Override logger for service-specific logging
|
|
83
|
+
self.logger = logging.getLogger(__name__)
|
|
84
|
+
|
|
85
|
+
# Connect protocol signals
|
|
86
|
+
self._connect_signals()
|
|
87
|
+
|
|
88
|
+
# Override state entry hooks with protocol behavior
|
|
89
|
+
|
|
90
|
+
def on_enter_receiving(self) -> None:
|
|
91
|
+
"""Enter receiving state - wait for telegrams to drain."""
|
|
92
|
+
self.logger.debug(f"Entering RECEIVING state (phase={self.phase.value})")
|
|
93
|
+
self.conbus_protocol.wait()
|
|
94
|
+
|
|
95
|
+
def on_enter_resetting(self) -> None:
|
|
96
|
+
"""Enter resetting state - send error status query."""
|
|
97
|
+
self.logger.debug(f"Entering RESETTING state (phase={self.phase.value})")
|
|
98
|
+
self.conbus_protocol.send_error_status_query(serial_number=self.serial_number)
|
|
99
|
+
self.send_error_status()
|
|
100
|
+
|
|
101
|
+
def on_enter_waiting_ok(self) -> None:
|
|
102
|
+
"""Enter waiting_ok state - wait for error status response."""
|
|
103
|
+
self.logger.debug(f"Entering WAITING_OK state (phase={self.phase.value})")
|
|
104
|
+
self.conbus_protocol.wait()
|
|
105
|
+
|
|
106
|
+
def on_enter_requesting(self) -> None:
|
|
107
|
+
"""Enter requesting state - send download request."""
|
|
108
|
+
self.enter_download_phase() # Sets phase to DOWNLOAD
|
|
109
|
+
self.conbus_protocol.send_download_request(serial_number=self.serial_number)
|
|
110
|
+
self.send_download()
|
|
111
|
+
|
|
112
|
+
def on_enter_waiting_data(self) -> None:
|
|
113
|
+
"""Enter waiting_data state - wait for actiontable chunks."""
|
|
114
|
+
self.logger.debug("Entering WAITING_DATA state - awaiting chunks")
|
|
115
|
+
self.conbus_protocol.wait()
|
|
116
|
+
|
|
117
|
+
def on_enter_receiving_chunk(self) -> None:
|
|
118
|
+
"""Enter receiving_chunk state - send ACK."""
|
|
119
|
+
self.logger.debug("Entering RECEIVING_CHUNK state - sending ACK")
|
|
120
|
+
self.conbus_protocol.send_ack(serial_number=self.serial_number)
|
|
121
|
+
self.send_ack()
|
|
122
|
+
|
|
123
|
+
def on_enter_processing_eof(self) -> None:
|
|
124
|
+
"""Enter processing_eof state - deserialize and emit result."""
|
|
125
|
+
self.logger.debug("Entering PROCESSING_EOF state - deserializing")
|
|
126
|
+
all_data = "".join(self.actiontable_data)
|
|
127
|
+
actiontable = self.serializer.from_encoded_string(all_data)
|
|
128
|
+
actiontable_dict = asdict(actiontable)
|
|
129
|
+
actiontable_short = self.serializer.format_decoded_output(actiontable)
|
|
130
|
+
self.on_actiontable_received.emit(
|
|
131
|
+
actiontable, actiontable_dict, actiontable_short
|
|
132
|
+
)
|
|
133
|
+
# Switch to CLEANUP phase
|
|
134
|
+
self.start_cleanup_phase()
|
|
135
|
+
|
|
136
|
+
def on_enter_completed(self) -> None:
|
|
137
|
+
"""Enter completed state - emit finish signal."""
|
|
138
|
+
self.logger.debug("Entering COMPLETED state - download finished")
|
|
139
|
+
self.on_finish.emit()
|
|
140
|
+
|
|
141
|
+
def on_max_retries_exceeded(self) -> None:
|
|
142
|
+
"""Handle max retries exceeded - emit error signal."""
|
|
143
|
+
self.logger.error(f"Max error retries ({MAX_ERROR_RETRIES}) exceeded")
|
|
144
|
+
self.on_error.emit(f"Module error persists after {MAX_ERROR_RETRIES} retries")
|
|
145
|
+
|
|
146
|
+
# Protocol event handlers
|
|
147
|
+
|
|
148
|
+
def _on_connection_made(self) -> None:
|
|
149
|
+
"""Handle connection established event."""
|
|
150
|
+
self.logger.debug("Connection made")
|
|
151
|
+
if self.idle.is_active:
|
|
152
|
+
self.do_connect()
|
|
153
|
+
|
|
154
|
+
def _on_read_datapoint_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
155
|
+
"""Handle READ_DATAPOINT response for error status check.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
reply_telegram: The parsed reply telegram.
|
|
159
|
+
"""
|
|
160
|
+
self.logger.debug(f"Received READ_DATAPOINT in {self.current_state}")
|
|
161
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if reply_telegram.datapoint_type != DataPointType.MODULE_ERROR_CODE:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if not self.waiting_ok.is_active:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
is_no_error = reply_telegram.data_value == NO_ERROR_CODE
|
|
171
|
+
if is_no_error:
|
|
172
|
+
self.handle_no_error_received()
|
|
173
|
+
else:
|
|
174
|
+
self.handle_error_received()
|
|
175
|
+
|
|
176
|
+
def _on_actiontable_chunk_received(
|
|
177
|
+
self, reply_telegram: ReplyTelegram, actiontable_chunk: str
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Handle actiontable chunk telegram received.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
reply_telegram: The parsed reply telegram containing chunk data.
|
|
183
|
+
actiontable_chunk: The chunk data.
|
|
184
|
+
"""
|
|
185
|
+
self.logger.debug(f"Received actiontable chunk in {self.current_state}")
|
|
186
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
if self.waiting_data.is_active:
|
|
190
|
+
self.actiontable_data.append(actiontable_chunk)
|
|
191
|
+
self.on_progress.emit(".")
|
|
192
|
+
self.receive_chunk()
|
|
193
|
+
|
|
194
|
+
def _on_eof_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
195
|
+
"""Handle EOF telegram received.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
reply_telegram: The parsed reply telegram (unused).
|
|
199
|
+
"""
|
|
200
|
+
self.logger.debug(f"Received EOF in {self.current_state}")
|
|
201
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
if self.waiting_data.is_active:
|
|
205
|
+
self.receive_eof()
|
|
206
|
+
|
|
207
|
+
def _on_telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
208
|
+
"""Handle telegram received event.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
telegram_received: The telegram received event.
|
|
212
|
+
"""
|
|
213
|
+
self.logger.debug(f"Received {telegram_received} in {self.current_state}")
|
|
214
|
+
|
|
215
|
+
# In receiving state, drain pending telegrams from pipe (discard to /dev/null).
|
|
216
|
+
# This ensures clean state before processing by clearing any stale messages.
|
|
217
|
+
if self.receiving.is_active:
|
|
218
|
+
self.filter_telegram()
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
def _on_timeout(self) -> None:
|
|
222
|
+
"""Handle timeout event."""
|
|
223
|
+
self.logger.debug(f"Timeout occurred (phase={self.phase.value})")
|
|
224
|
+
if self.receiving.is_active:
|
|
225
|
+
self.do_timeout() # receiving -> resetting
|
|
226
|
+
elif self.waiting_ok.is_active:
|
|
227
|
+
self.do_timeout() # waiting_ok -> receiving (retry)
|
|
228
|
+
elif self.waiting_data.is_active:
|
|
229
|
+
self.logger.error("Timeout waiting for actiontable data")
|
|
230
|
+
self.on_error.emit("Timeout waiting for actiontable data")
|
|
231
|
+
else:
|
|
232
|
+
self.logger.debug("Timeout in non-recoverable state")
|
|
233
|
+
self.on_error.emit("Timeout")
|
|
234
|
+
|
|
235
|
+
def _on_failed(self, message: str) -> None:
|
|
236
|
+
"""Handle failed connection event.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
message: Failure message.
|
|
240
|
+
"""
|
|
241
|
+
self.logger.debug(f"Failed: {message}")
|
|
242
|
+
self.on_error.emit(message)
|
|
243
|
+
|
|
244
|
+
# Public API
|
|
245
|
+
|
|
246
|
+
def configure(
|
|
247
|
+
self,
|
|
248
|
+
serial_number: str,
|
|
249
|
+
timeout_seconds: Optional[float] = 2.0,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Configure download parameters before starting.
|
|
252
|
+
|
|
253
|
+
Sets the target module serial number and timeout. Call this before
|
|
254
|
+
start_reactor() to configure the download target.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
serial_number: Module serial number to download from.
|
|
258
|
+
timeout_seconds: Timeout in seconds for each operation (default 2.0).
|
|
259
|
+
|
|
260
|
+
Raises:
|
|
261
|
+
RuntimeError: If called while download is in progress.
|
|
262
|
+
"""
|
|
263
|
+
if not self.idle.is_active:
|
|
264
|
+
raise RuntimeError("Cannot configure while download in progress")
|
|
265
|
+
self.logger.info("Configuring actiontable download")
|
|
266
|
+
self.serial_number = serial_number
|
|
267
|
+
if timeout_seconds:
|
|
268
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
269
|
+
|
|
270
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
271
|
+
"""Set operation timeout.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
timeout_seconds: Timeout in seconds.
|
|
275
|
+
"""
|
|
276
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
277
|
+
|
|
278
|
+
def start_reactor(self) -> None:
|
|
279
|
+
"""Start the reactor."""
|
|
280
|
+
self.conbus_protocol.start_reactor()
|
|
281
|
+
|
|
282
|
+
def stop_reactor(self) -> None:
|
|
283
|
+
"""Stop the reactor."""
|
|
284
|
+
self.conbus_protocol.stop_reactor()
|
|
285
|
+
|
|
286
|
+
def _connect_signals(self) -> None:
|
|
287
|
+
"""Connect protocol signals to handlers (idempotent)."""
|
|
288
|
+
if self._signals_connected:
|
|
289
|
+
return
|
|
290
|
+
self.conbus_protocol.on_connection_made.connect(self._on_connection_made)
|
|
291
|
+
self.conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
|
|
292
|
+
self.conbus_protocol.on_read_datapoint_received.connect(
|
|
293
|
+
self._on_read_datapoint_received
|
|
294
|
+
)
|
|
295
|
+
self.conbus_protocol.on_actiontable_chunk_received.connect(
|
|
296
|
+
self._on_actiontable_chunk_received
|
|
297
|
+
)
|
|
298
|
+
self.conbus_protocol.on_eof_received.connect(self._on_eof_received)
|
|
299
|
+
self.conbus_protocol.on_timeout.connect(self._on_timeout)
|
|
300
|
+
self.conbus_protocol.on_failed.connect(self._on_failed)
|
|
301
|
+
self._signals_connected = True
|
|
302
|
+
|
|
303
|
+
def _disconnect_signals(self) -> None:
|
|
304
|
+
"""Disconnect protocol signals from handlers (idempotent)."""
|
|
305
|
+
if not self._signals_connected:
|
|
306
|
+
return
|
|
307
|
+
self.conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
|
|
308
|
+
self.conbus_protocol.on_telegram_received.disconnect(self._on_telegram_received)
|
|
309
|
+
self.conbus_protocol.on_read_datapoint_received.disconnect(
|
|
310
|
+
self._on_read_datapoint_received
|
|
311
|
+
)
|
|
312
|
+
self.conbus_protocol.on_actiontable_chunk_received.disconnect(
|
|
313
|
+
self._on_actiontable_chunk_received
|
|
314
|
+
)
|
|
315
|
+
self.conbus_protocol.on_eof_received.disconnect(self._on_eof_received)
|
|
316
|
+
self.conbus_protocol.on_timeout.disconnect(self._on_timeout)
|
|
317
|
+
self.conbus_protocol.on_failed.disconnect(self._on_failed)
|
|
318
|
+
self._signals_connected = False
|
|
319
|
+
|
|
320
|
+
def __enter__(self) -> "ActionTableDownloadService":
|
|
321
|
+
"""Enter context manager - reset state and reconnect signals.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Self for context manager protocol.
|
|
325
|
+
"""
|
|
326
|
+
# Reset state for singleton reuse
|
|
327
|
+
self.actiontable_data = []
|
|
328
|
+
# Reset state machine
|
|
329
|
+
self.reset()
|
|
330
|
+
# Reconnect signals (in case previously disconnected)
|
|
331
|
+
self._connect_signals()
|
|
332
|
+
return self
|
|
333
|
+
|
|
334
|
+
def __exit__(
|
|
335
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
336
|
+
) -> None:
|
|
337
|
+
"""Exit context manager and disconnect signals."""
|
|
338
|
+
self._disconnect_signals()
|
|
339
|
+
# Disconnect service signals
|
|
340
|
+
self.on_progress.disconnect()
|
|
341
|
+
self.on_error.disconnect()
|
|
342
|
+
self.on_actiontable_received.disconnect()
|
|
343
|
+
self.on_finish.disconnect()
|
|
344
|
+
# Stop reactor
|
|
345
|
+
self.stop_reactor()
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""State machine for ActionTable download workflow."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from abc import ABCMeta, abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from statemachine import State, StateMachine
|
|
8
|
+
from statemachine.factory import StateMachineMetaclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AbstractStateMachineMeta(StateMachineMetaclass, ABCMeta):
|
|
12
|
+
"""Combined metaclass for abstract state machines.
|
|
13
|
+
|
|
14
|
+
Combines StateMachineMetaclass (for state machine introspection) with
|
|
15
|
+
ABCMeta (for abstract method enforcement).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Constants
|
|
22
|
+
MAX_ERROR_RETRIES = 3 # Max retries for error_status_received before giving up
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Phase(Enum):
|
|
26
|
+
"""Download workflow phases.
|
|
27
|
+
|
|
28
|
+
The download workflow consists of three sequential phases:
|
|
29
|
+
- INIT: Drain pending telegrams, query error status → proceed to DOWNLOAD
|
|
30
|
+
- DOWNLOAD: Request actiontable, receive chunks with ACK, until EOF
|
|
31
|
+
- CLEANUP: Drain pending telegrams, query error status → proceed to COMPLETED
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
INIT: Initial phase - drain pending telegrams and query error status.
|
|
35
|
+
DOWNLOAD: Download phase - request actiontable and receive chunks.
|
|
36
|
+
CLEANUP: Cleanup phase - drain remaining telegrams and verify status.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
INIT = "init"
|
|
40
|
+
DOWNLOAD = "download"
|
|
41
|
+
CLEANUP = "cleanup"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ActionTableDownloadStateMachine(StateMachine, metaclass=AbstractStateMachineMeta):
|
|
45
|
+
"""State machine for ActionTable download workflow.
|
|
46
|
+
|
|
47
|
+
Pure state machine with states, transitions, and guards. Subclasses can
|
|
48
|
+
override on_enter_* methods to add protocol-specific behavior.
|
|
49
|
+
|
|
50
|
+
States (9 total):
|
|
51
|
+
idle -> receiving -> resetting -> waiting_ok -> requesting
|
|
52
|
+
-> waiting_data <-> receiving_chunk -> processing_eof -> completed
|
|
53
|
+
|
|
54
|
+
Phases - INIT and CLEANUP share the same states (receiving, resetting, waiting_ok):
|
|
55
|
+
|
|
56
|
+
INIT phase (drain → reset → wait_ok):
|
|
57
|
+
idle -> receiving -> resetting -> waiting_ok --(guard: is_init_phase)--> requesting
|
|
58
|
+
|
|
59
|
+
DOWNLOAD phase (request → receive chunks → EOF):
|
|
60
|
+
requesting -> waiting_data <-> receiving_chunk -> processing_eof
|
|
61
|
+
|
|
62
|
+
CLEANUP phase (drain → reset → wait_ok):
|
|
63
|
+
processing_eof -> receiving -> resetting -> waiting_ok --(guard: is_cleanup_phase)--> completed
|
|
64
|
+
|
|
65
|
+
The drain/reset/wait_ok cycle:
|
|
66
|
+
1. Drain pending telegrams (receiving state discards telegrams)
|
|
67
|
+
2. Timeout triggers error status query (resetting)
|
|
68
|
+
3. Wait for response (waiting_ok)
|
|
69
|
+
4. On no error: guard determines target (requesting or completed)
|
|
70
|
+
On error: retry from drain step (limited by MAX_ERROR_RETRIES)
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
phase: Current workflow phase (INIT, DOWNLOAD, CLEANUP).
|
|
74
|
+
error_retry_count: Current error retry count.
|
|
75
|
+
idle: Initial state before connection.
|
|
76
|
+
receiving: Drain pending telegrams state (INIT or CLEANUP phase).
|
|
77
|
+
resetting: Query error status state.
|
|
78
|
+
waiting_ok: Await error status response state.
|
|
79
|
+
requesting: DOWNLOAD phase state - send download request.
|
|
80
|
+
waiting_data: DOWNLOAD phase state - await chunks.
|
|
81
|
+
receiving_chunk: DOWNLOAD phase state - process chunk.
|
|
82
|
+
processing_eof: DOWNLOAD phase state - deserialize result.
|
|
83
|
+
completed: Final state - download finished.
|
|
84
|
+
do_connect: Transition from idle to receiving.
|
|
85
|
+
filter_telegram: Self-transition in receiving state for draining.
|
|
86
|
+
do_timeout: Timeout transitions from receiving/waiting_ok.
|
|
87
|
+
send_error_status: Transition from resetting to waiting_ok.
|
|
88
|
+
error_status_received: Transition from waiting_ok to receiving on error.
|
|
89
|
+
no_error_status_received: Conditional transition based on phase.
|
|
90
|
+
send_download: Transition from requesting to waiting_data.
|
|
91
|
+
receive_chunk: Transition from waiting_data to receiving_chunk.
|
|
92
|
+
send_ack: Transition from receiving_chunk to waiting_data.
|
|
93
|
+
receive_eof: Transition from waiting_data to processing_eof.
|
|
94
|
+
do_finish: Transition from processing_eof to receiving.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
# States - unified for INIT and CLEANUP phases using guards
|
|
98
|
+
idle = State(initial=True)
|
|
99
|
+
receiving = State() # Drain telegrams (INIT or CLEANUP phase)
|
|
100
|
+
resetting = State() # Query error status
|
|
101
|
+
waiting_ok = State() # Await error status response
|
|
102
|
+
|
|
103
|
+
requesting = State() # DOWNLOAD phase: send download request
|
|
104
|
+
waiting_data = State() # DOWNLOAD phase: await chunks
|
|
105
|
+
receiving_chunk = State() # DOWNLOAD phase: process chunk
|
|
106
|
+
processing_eof = State() # DOWNLOAD phase: deserialize result
|
|
107
|
+
|
|
108
|
+
completed = State(final=True)
|
|
109
|
+
|
|
110
|
+
# Phase transitions - shared states with guards for phase-dependent routing
|
|
111
|
+
do_connect = idle.to(receiving)
|
|
112
|
+
filter_telegram = receiving.to(receiving) # Self-transition: drain to /dev/null
|
|
113
|
+
do_timeout = receiving.to(resetting) | waiting_ok.to(receiving)
|
|
114
|
+
send_error_status = resetting.to(waiting_ok)
|
|
115
|
+
error_status_received = waiting_ok.to(
|
|
116
|
+
receiving, cond="can_retry"
|
|
117
|
+
) # Retry if under limit
|
|
118
|
+
|
|
119
|
+
# Conditional transitions based on phase
|
|
120
|
+
no_error_status_received = waiting_ok.to(
|
|
121
|
+
requesting, cond="is_init_phase"
|
|
122
|
+
) | waiting_ok.to(completed, cond="is_cleanup_phase")
|
|
123
|
+
|
|
124
|
+
# DOWNLOAD phase transitions
|
|
125
|
+
send_download = requesting.to(waiting_data)
|
|
126
|
+
receive_chunk = waiting_data.to(receiving_chunk)
|
|
127
|
+
send_ack = receiving_chunk.to(waiting_data)
|
|
128
|
+
receive_eof = waiting_data.to(processing_eof)
|
|
129
|
+
|
|
130
|
+
# Return to drain/reset cycle for CLEANUP phase
|
|
131
|
+
do_finish = processing_eof.to(receiving)
|
|
132
|
+
|
|
133
|
+
def __init__(self) -> None:
|
|
134
|
+
"""Initialize the state machine."""
|
|
135
|
+
self.logger = logging.getLogger(__name__)
|
|
136
|
+
self._phase: Phase = Phase.INIT
|
|
137
|
+
self._error_retry_count: int = 0
|
|
138
|
+
|
|
139
|
+
# Initialize state machine
|
|
140
|
+
super().__init__(allow_event_without_transition=True)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def phase(self) -> Phase:
|
|
144
|
+
"""Get current phase."""
|
|
145
|
+
return self._phase
|
|
146
|
+
|
|
147
|
+
@phase.setter
|
|
148
|
+
def phase(self, value: Phase) -> None:
|
|
149
|
+
"""Set current phase.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
value: The phase value to set.
|
|
153
|
+
"""
|
|
154
|
+
self._phase = value
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def error_retry_count(self) -> int:
|
|
158
|
+
"""Get current error retry count."""
|
|
159
|
+
return self._error_retry_count
|
|
160
|
+
|
|
161
|
+
@error_retry_count.setter
|
|
162
|
+
def error_retry_count(self, value: int) -> None:
|
|
163
|
+
"""Set error retry count.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
value: The error retry count value to set.
|
|
167
|
+
"""
|
|
168
|
+
self._error_retry_count = value
|
|
169
|
+
|
|
170
|
+
# Guard conditions for phase-dependent transitions
|
|
171
|
+
|
|
172
|
+
def is_init_phase(self) -> bool:
|
|
173
|
+
"""Guard: check if currently in INIT phase.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if in INIT phase, False otherwise.
|
|
177
|
+
"""
|
|
178
|
+
return self._phase == Phase.INIT
|
|
179
|
+
|
|
180
|
+
def is_cleanup_phase(self) -> bool:
|
|
181
|
+
"""Guard: check if currently in CLEANUP phase.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if in CLEANUP phase, False otherwise.
|
|
185
|
+
"""
|
|
186
|
+
return self._phase == Phase.CLEANUP
|
|
187
|
+
|
|
188
|
+
def can_retry(self) -> bool:
|
|
189
|
+
"""Guard: check if retry is allowed (under max limit).
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if retry count is under MAX_ERROR_RETRIES, False otherwise.
|
|
193
|
+
"""
|
|
194
|
+
return self._error_retry_count < MAX_ERROR_RETRIES
|
|
195
|
+
|
|
196
|
+
# State entry hooks - subclasses MUST implement these
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
def on_enter_receiving(self) -> None:
|
|
200
|
+
"""Enter receiving state - drain pending telegrams."""
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
def on_enter_resetting(self) -> None:
|
|
205
|
+
"""Enter resetting state - query error status."""
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
@abstractmethod
|
|
209
|
+
def on_enter_waiting_ok(self) -> None:
|
|
210
|
+
"""Enter waiting_ok state - awaiting error status response."""
|
|
211
|
+
...
|
|
212
|
+
|
|
213
|
+
@abstractmethod
|
|
214
|
+
def on_enter_requesting(self) -> None:
|
|
215
|
+
"""Enter requesting state - send download request."""
|
|
216
|
+
...
|
|
217
|
+
|
|
218
|
+
@abstractmethod
|
|
219
|
+
def on_enter_waiting_data(self) -> None:
|
|
220
|
+
"""Enter waiting_data state - wait for actiontable chunks."""
|
|
221
|
+
...
|
|
222
|
+
|
|
223
|
+
@abstractmethod
|
|
224
|
+
def on_enter_receiving_chunk(self) -> None:
|
|
225
|
+
"""Enter receiving_chunk state - send ACK."""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
@abstractmethod
|
|
229
|
+
def on_enter_processing_eof(self) -> None:
|
|
230
|
+
"""Enter processing_eof state - deserialize and emit result."""
|
|
231
|
+
...
|
|
232
|
+
|
|
233
|
+
@abstractmethod
|
|
234
|
+
def on_enter_completed(self) -> None:
|
|
235
|
+
"""Enter completed state - download finished."""
|
|
236
|
+
...
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def on_max_retries_exceeded(self) -> None:
|
|
240
|
+
"""Called when max error retries exceeded."""
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
# Public methods for state machine control
|
|
244
|
+
|
|
245
|
+
def enter_download_phase(self) -> None:
|
|
246
|
+
"""Enter requesting state - send download request."""
|
|
247
|
+
self._phase = Phase.DOWNLOAD
|
|
248
|
+
|
|
249
|
+
def handle_no_error_received(self) -> None:
|
|
250
|
+
"""Handle successful error status check (no error)."""
|
|
251
|
+
self._error_retry_count = 0 # Reset on success
|
|
252
|
+
self.no_error_status_received()
|
|
253
|
+
|
|
254
|
+
def handle_error_received(self) -> None:
|
|
255
|
+
"""Handle error status received - increment retry and attempt transition."""
|
|
256
|
+
self._error_retry_count += 1
|
|
257
|
+
self.logger.debug(
|
|
258
|
+
f"Error status received, retry {self._error_retry_count}/{MAX_ERROR_RETRIES}"
|
|
259
|
+
)
|
|
260
|
+
# Guard can_retry blocks transition if max retries exceeded
|
|
261
|
+
self.error_status_received()
|
|
262
|
+
# Check if guard blocked the transition (still in waiting_ok)
|
|
263
|
+
if self.waiting_ok.is_active:
|
|
264
|
+
self.on_max_retries_exceeded()
|
|
265
|
+
|
|
266
|
+
def start_cleanup_phase(self) -> None:
|
|
267
|
+
"""Switch to CLEANUP phase and trigger do_finish transition."""
|
|
268
|
+
self._phase = Phase.CLEANUP
|
|
269
|
+
self.do_finish()
|
|
270
|
+
|
|
271
|
+
def reset(self) -> None:
|
|
272
|
+
"""Reset state machine to initial state."""
|
|
273
|
+
self._phase = Phase.INIT
|
|
274
|
+
self._error_retry_count = 0
|
|
275
|
+
# python-statemachine uses model.state to track current state
|
|
276
|
+
self.model.state = self.idle.id
|