conson-xp 1.41.0__tar.gz → 1.44.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.41.0 → conson_xp-1.44.0}/PKG-INFO +2 -1
- {conson_xp-1.41.0 → conson_xp-1.44.0}/pyproject.toml +2 -1
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/__init__.py +1 -1
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +5 -1
- conson_xp-1.44.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +525 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/protocol/conbus_event_protocol.py +10 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/server_service.py +1 -1
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_actiontable_integration.py +23 -5
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +16 -1
- conson_xp-1.44.0/tests/unit/test_services/test_actiontable_download_service.py +596 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_actiontable_service.py +38 -24
- conson_xp-1.41.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -203
- {conson_xp-1.41.0 → conson_xp-1.44.0}/LICENSE +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/README.md +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/term/term_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_export.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/config/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/config/conson_module_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/module_state.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/term/telegram_display.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_export_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/msactiontable/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/msactiontable/msactiontable_upload_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/client_buffer_manager.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/server/xp33_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/term/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/services/term/state_monitor_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/state.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/state.tcss +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/widgets/help_menu.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/widgets/modules_list.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/widgets/protocol_log.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/term/widgets/status_footer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/dependencies.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/logging.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/.coverage +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/conftest.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_output_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_msactiontable_upload_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_state_monitor_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_telegram_output_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp33_short_format.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.41.0 → conson_xp-1.44.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.44.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -49,6 +49,7 @@ Requires-Dist: twisted>=25.5.0
|
|
|
49
49
|
Requires-Dist: bubus>=1.5.6
|
|
50
50
|
Requires-Dist: psygnal>=0.15.0
|
|
51
51
|
Requires-Dist: textual>=1.0.0
|
|
52
|
+
Requires-Dist: python-statemachine>=2.5.0
|
|
52
53
|
Description-Content-Type: text/markdown
|
|
53
54
|
|
|
54
55
|
# 🔌 XP Protocol Communication Tool
|
|
@@ -19,10 +19,11 @@ dependencies = [
|
|
|
19
19
|
"bubus>=1.5.6",
|
|
20
20
|
"psygnal>=0.15.0",
|
|
21
21
|
"textual>=1.0.0",
|
|
22
|
+
"python-statemachine>=2.5.0",
|
|
22
23
|
]
|
|
23
24
|
requires-python = ">=3.11"
|
|
24
25
|
readme = "README.md"
|
|
25
|
-
version = "1.
|
|
26
|
+
version = "1.44.0"
|
|
26
27
|
|
|
27
28
|
[project.license]
|
|
28
29
|
file = "LICENSE"
|
{conson_xp-1.41.0 → conson_xp-1.44.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py
RENAMED
|
@@ -61,7 +61,7 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
61
61
|
"""
|
|
62
62
|
click.echo(progress, nl=False)
|
|
63
63
|
|
|
64
|
-
def
|
|
64
|
+
def on_actiontable_received(
|
|
65
65
|
_actiontable: ActionTable,
|
|
66
66
|
actiontable_dict: Dict[str, Any],
|
|
67
67
|
actiontable_short: list[str],
|
|
@@ -79,6 +79,9 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
79
79
|
"actiontable": actiontable_dict,
|
|
80
80
|
}
|
|
81
81
|
click.echo(json.dumps(output, indent=2, default=str))
|
|
82
|
+
|
|
83
|
+
def on_finish() -> None:
|
|
84
|
+
"""Handle successful completion of action table download."""
|
|
82
85
|
service.stop_reactor()
|
|
83
86
|
|
|
84
87
|
def on_error(error: str) -> None:
|
|
@@ -93,6 +96,7 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
93
96
|
with service:
|
|
94
97
|
service.on_progress.connect(on_progress)
|
|
95
98
|
service.on_finish.connect(on_finish)
|
|
99
|
+
service.on_actiontable_received.connect(on_actiontable_received)
|
|
96
100
|
service.on_error.connect(on_error)
|
|
97
101
|
service.start(serial_number=serial_number)
|
|
98
102
|
service.start_reactor()
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
"""Service for downloading ActionTable via Conbus protocol."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from psygnal import SignalInstance
|
|
9
|
+
from statemachine import State, StateMachine
|
|
10
|
+
|
|
11
|
+
from xp.models.actiontable.actiontable import ActionTable
|
|
12
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
13
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
14
|
+
from xp.models.telegram.reply_telegram import ReplyTelegram
|
|
15
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
16
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
17
|
+
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
18
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
19
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
20
|
+
|
|
21
|
+
# Constants
|
|
22
|
+
NO_ERROR_CODE = "00"
|
|
23
|
+
CHUNK_HEADER_LENGTH = 2 # data_value format: 2-char counter + actiontable chunk
|
|
24
|
+
MAX_ERROR_RETRIES = 3 # Max retries for error_status_received before giving up
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Phase(Enum):
|
|
28
|
+
"""Download workflow phases.
|
|
29
|
+
|
|
30
|
+
The download workflow consists of three sequential phases:
|
|
31
|
+
- INIT: Drain pending telegrams, query error status → proceed to DOWNLOAD
|
|
32
|
+
- DOWNLOAD: Request actiontable, receive chunks with ACK, until EOF
|
|
33
|
+
- CLEANUP: Drain pending telegrams, query error status → proceed to COMPLETED
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
INIT: Initial phase - drain pending telegrams and query error status.
|
|
37
|
+
DOWNLOAD: Download phase - request actiontable and receive chunks.
|
|
38
|
+
CLEANUP: Cleanup phase - drain remaining telegrams and verify status.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
INIT = "init"
|
|
42
|
+
DOWNLOAD = "download"
|
|
43
|
+
CLEANUP = "cleanup"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ActionTableDownloadService(StateMachine):
|
|
47
|
+
"""Service for downloading action tables from Conbus modules via TCP.
|
|
48
|
+
|
|
49
|
+
Inherits from StateMachine - the service IS the state machine. Uses guard
|
|
50
|
+
conditions to share states between INIT and CLEANUP phases.
|
|
51
|
+
see: Download-ActionTable-Workflow.dot
|
|
52
|
+
|
|
53
|
+
States (9 total):
|
|
54
|
+
idle -> receiving -> resetting -> waiting_ok -> requesting
|
|
55
|
+
-> waiting_data <-> receiving_chunk -> processing_eof -> completed
|
|
56
|
+
|
|
57
|
+
Phases - INIT and CLEANUP share the same states (receiving, resetting, waiting_ok):
|
|
58
|
+
|
|
59
|
+
INIT phase (drain → reset → wait_ok):
|
|
60
|
+
idle -> receiving -> resetting -> waiting_ok --(guard: is_init_phase)--> requesting
|
|
61
|
+
|
|
62
|
+
DOWNLOAD phase (request → receive chunks → EOF):
|
|
63
|
+
requesting -> waiting_data <-> receiving_chunk -> processing_eof
|
|
64
|
+
|
|
65
|
+
CLEANUP phase (drain → reset → wait_ok):
|
|
66
|
+
processing_eof -> receiving -> resetting -> waiting_ok --(guard: is_cleanup_phase)--> completed
|
|
67
|
+
|
|
68
|
+
The drain/reset/wait_ok cycle:
|
|
69
|
+
1. Drain pending telegrams (receiving state discards telegram without reading them).
|
|
70
|
+
There may be a lot of telegram. We are listening and receiving until no more
|
|
71
|
+
telegram arrive and timeout occurs.
|
|
72
|
+
2. Timeout triggers error status query (resetting)
|
|
73
|
+
3. Wait for response (waiting_ok)
|
|
74
|
+
4. On no error: guard determines target (requesting or completed)
|
|
75
|
+
On error: retry from drain step
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
on_progress: Signal emitted with "." for each chunk received.
|
|
79
|
+
on_error: Signal emitted with error message string.
|
|
80
|
+
on_actiontable_received: Signal emitted with (ActionTable, dict, list).
|
|
81
|
+
on_finish: Signal emitted when download and cleanup completed.
|
|
82
|
+
idle: Initial state - waiting for connection.
|
|
83
|
+
receiving: Drain telegrams state (INIT or CLEANUP phase).
|
|
84
|
+
resetting: Query error status state.
|
|
85
|
+
waiting_ok: Await error status response state.
|
|
86
|
+
requesting: DOWNLOAD phase - send download request state.
|
|
87
|
+
waiting_data: DOWNLOAD phase - await chunks state.
|
|
88
|
+
receiving_chunk: DOWNLOAD phase - process chunk state.
|
|
89
|
+
processing_eof: DOWNLOAD phase - deserialize result state.
|
|
90
|
+
completed: Final state - download finished.
|
|
91
|
+
do_connect: Transition from idle to receiving.
|
|
92
|
+
filter_telegram: Self-transition in receiving to drain telegrams.
|
|
93
|
+
do_timeout: Transition on timeout events.
|
|
94
|
+
send_error_status: Transition from resetting to waiting_ok.
|
|
95
|
+
error_status_received: Transition when error status is received.
|
|
96
|
+
no_error_status_received: Transition when no error status received.
|
|
97
|
+
send_download: Transition from requesting to waiting_data.
|
|
98
|
+
receive_chunk: Transition from waiting_data to receiving_chunk.
|
|
99
|
+
send_ack: Transition from receiving_chunk to waiting_data.
|
|
100
|
+
receive_eof: Transition from waiting_data to processing_eof.
|
|
101
|
+
do_finish: Transition from processing_eof to receiving (cleanup).
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> with download_service as service:
|
|
105
|
+
... service.configure(serial_number="12345678")
|
|
106
|
+
... service.on_actiontable_received.connect(handle_result)
|
|
107
|
+
... service.start_reactor()
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
# States - unified for INIT and CLEANUP phases using guards
|
|
111
|
+
idle = State(initial=True)
|
|
112
|
+
receiving = State() # Drain telegrams (INIT or CLEANUP phase)
|
|
113
|
+
resetting = State() # Query error status
|
|
114
|
+
waiting_ok = State() # Await error status response
|
|
115
|
+
|
|
116
|
+
requesting = State() # DOWNLOAD phase: send download request
|
|
117
|
+
waiting_data = State() # DOWNLOAD phase: await chunks
|
|
118
|
+
receiving_chunk = State() # DOWNLOAD phase: process chunk
|
|
119
|
+
processing_eof = State() # DOWNLOAD phase: deserialize result
|
|
120
|
+
|
|
121
|
+
completed = State(final=True)
|
|
122
|
+
|
|
123
|
+
# Phase transitions - shared states with guards for phase-dependent routing
|
|
124
|
+
do_connect = idle.to(receiving)
|
|
125
|
+
filter_telegram = receiving.to(receiving) # Self-transition: drain to /dev/null
|
|
126
|
+
do_timeout = receiving.to(resetting) | waiting_ok.to(receiving)
|
|
127
|
+
send_error_status = resetting.to(waiting_ok)
|
|
128
|
+
error_status_received = waiting_ok.to(
|
|
129
|
+
receiving, cond="can_retry"
|
|
130
|
+
) # Retry if under limit
|
|
131
|
+
|
|
132
|
+
# Conditional transitions based on phase
|
|
133
|
+
no_error_status_received = waiting_ok.to(
|
|
134
|
+
requesting, cond="is_init_phase"
|
|
135
|
+
) | waiting_ok.to(completed, cond="is_cleanup_phase")
|
|
136
|
+
|
|
137
|
+
# DOWNLOAD phase transitions
|
|
138
|
+
send_download = requesting.to(waiting_data)
|
|
139
|
+
receive_chunk = waiting_data.to(receiving_chunk)
|
|
140
|
+
send_ack = receiving_chunk.to(waiting_data)
|
|
141
|
+
receive_eof = waiting_data.to(processing_eof)
|
|
142
|
+
|
|
143
|
+
# Return to drain/reset cycle for CLEANUP phase
|
|
144
|
+
do_finish = processing_eof.to(receiving)
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
conbus_protocol: ConbusEventProtocol,
|
|
149
|
+
actiontable_serializer: ActionTableSerializer,
|
|
150
|
+
telegram_service: TelegramService,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Initialize the action table download service.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
conbus_protocol: ConbusEventProtocol instance.
|
|
156
|
+
actiontable_serializer: Action table serializer.
|
|
157
|
+
telegram_service: Telegram service for parsing.
|
|
158
|
+
"""
|
|
159
|
+
self.conbus_protocol = conbus_protocol
|
|
160
|
+
self.serializer = actiontable_serializer
|
|
161
|
+
self.telegram_service = telegram_service
|
|
162
|
+
self.serial_number: str = ""
|
|
163
|
+
self.actiontable_data: list[str] = []
|
|
164
|
+
self.logger = logging.getLogger(__name__)
|
|
165
|
+
self._phase: Phase = Phase.INIT
|
|
166
|
+
self._error_retry_count: int = 0
|
|
167
|
+
self._signals_connected: bool = False
|
|
168
|
+
|
|
169
|
+
# Signals (instance attributes to avoid conflict with statemachine)
|
|
170
|
+
self.on_progress: SignalInstance = SignalInstance((str,))
|
|
171
|
+
self.on_error: SignalInstance = SignalInstance((str,))
|
|
172
|
+
self.on_finish: SignalInstance = SignalInstance()
|
|
173
|
+
self.on_actiontable_received: SignalInstance = SignalInstance(
|
|
174
|
+
(ActionTable, dict[str, Any], list[str])
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Initialize state machine first (before connecting signals)
|
|
178
|
+
super().__init__(allow_event_without_transition=True)
|
|
179
|
+
|
|
180
|
+
# Connect protocol signals
|
|
181
|
+
self._connect_signals()
|
|
182
|
+
|
|
183
|
+
# Guard conditions for phase-dependent transitions
|
|
184
|
+
|
|
185
|
+
def is_init_phase(self) -> bool:
|
|
186
|
+
"""Guard: check if currently in INIT phase.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if in INIT phase, False otherwise.
|
|
190
|
+
"""
|
|
191
|
+
return self._phase == Phase.INIT
|
|
192
|
+
|
|
193
|
+
def is_cleanup_phase(self) -> bool:
|
|
194
|
+
"""Guard: check if currently in CLEANUP phase.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
True if in CLEANUP phase, False otherwise.
|
|
198
|
+
"""
|
|
199
|
+
return self._phase == Phase.CLEANUP
|
|
200
|
+
|
|
201
|
+
def can_retry(self) -> bool:
|
|
202
|
+
"""Guard: check if retry is allowed (under max limit).
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
True if retry count is below MAX_ERROR_RETRIES, False otherwise.
|
|
206
|
+
"""
|
|
207
|
+
return self._error_retry_count < MAX_ERROR_RETRIES
|
|
208
|
+
|
|
209
|
+
# State machine lifecycle hooks
|
|
210
|
+
# Note: receiving state is used to drain pending telegrams from the connection
|
|
211
|
+
# pipe. Any telegram received in this state is intentionally discarded (sent
|
|
212
|
+
# to /dev/null) to ensure a clean state before processing.
|
|
213
|
+
|
|
214
|
+
def on_enter_receiving(self) -> None:
|
|
215
|
+
"""Enter receiving state - drain pending telegrams."""
|
|
216
|
+
self.logger.debug(f"Entering RECEIVING state (phase={self._phase.value})")
|
|
217
|
+
self.conbus_protocol.wait()
|
|
218
|
+
|
|
219
|
+
def on_enter_resetting(self) -> None:
|
|
220
|
+
"""Enter resetting state - query error status."""
|
|
221
|
+
self.logger.debug(f"Entering RESETTING state (phase={self._phase.value})")
|
|
222
|
+
self.conbus_protocol.send_telegram(
|
|
223
|
+
telegram_type=TelegramType.SYSTEM,
|
|
224
|
+
serial_number=self.serial_number,
|
|
225
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
226
|
+
data_value=DataPointType.MODULE_ERROR_CODE.value,
|
|
227
|
+
)
|
|
228
|
+
self.send_error_status()
|
|
229
|
+
|
|
230
|
+
def on_enter_waiting_ok(self) -> None:
|
|
231
|
+
"""Enter waiting_ok state - awaiting error status response."""
|
|
232
|
+
self.logger.debug(f"Entering WAITING_OK state (phase={self._phase.value})")
|
|
233
|
+
self.conbus_protocol.wait()
|
|
234
|
+
|
|
235
|
+
def on_enter_requesting(self) -> None:
|
|
236
|
+
"""Enter requesting state - send download request."""
|
|
237
|
+
self._phase = Phase.DOWNLOAD
|
|
238
|
+
self.logger.debug("Entering REQUESTING state - sending download request")
|
|
239
|
+
self.conbus_protocol.send_telegram(
|
|
240
|
+
telegram_type=TelegramType.SYSTEM,
|
|
241
|
+
serial_number=self.serial_number,
|
|
242
|
+
system_function=SystemFunction.DOWNLOAD_ACTIONTABLE,
|
|
243
|
+
data_value=NO_ERROR_CODE,
|
|
244
|
+
)
|
|
245
|
+
self.send_download()
|
|
246
|
+
|
|
247
|
+
def on_enter_waiting_data(self) -> None:
|
|
248
|
+
"""Enter waiting_data state - wait for actiontable chunks."""
|
|
249
|
+
self.logger.debug("Entering WAITING_DATA state - awaiting chunks")
|
|
250
|
+
self.conbus_protocol.wait()
|
|
251
|
+
|
|
252
|
+
def on_enter_receiving_chunk(self) -> None:
|
|
253
|
+
"""Enter receiving_chunk state - send ACK."""
|
|
254
|
+
self.logger.debug("Entering RECEIVING_CHUNK state - sending ACK")
|
|
255
|
+
self.conbus_protocol.send_telegram(
|
|
256
|
+
telegram_type=TelegramType.SYSTEM,
|
|
257
|
+
serial_number=self.serial_number,
|
|
258
|
+
system_function=SystemFunction.ACK,
|
|
259
|
+
data_value=NO_ERROR_CODE,
|
|
260
|
+
)
|
|
261
|
+
self.send_ack()
|
|
262
|
+
|
|
263
|
+
def on_enter_processing_eof(self) -> None:
|
|
264
|
+
"""Enter processing_eof state - deserialize and emit result, then cleanup."""
|
|
265
|
+
self.logger.debug("Entering PROCESSING_EOF state - deserializing")
|
|
266
|
+
all_data = "".join(self.actiontable_data)
|
|
267
|
+
actiontable = self.serializer.from_encoded_string(all_data)
|
|
268
|
+
actiontable_dict = asdict(actiontable)
|
|
269
|
+
actiontable_short = self.serializer.format_decoded_output(actiontable)
|
|
270
|
+
self.on_actiontable_received.emit(
|
|
271
|
+
actiontable, actiontable_dict, actiontable_short
|
|
272
|
+
)
|
|
273
|
+
# Switch to CLEANUP phase before returning to receiving state
|
|
274
|
+
self._phase = Phase.CLEANUP
|
|
275
|
+
self.do_finish()
|
|
276
|
+
|
|
277
|
+
def on_enter_completed(self) -> None:
|
|
278
|
+
"""Enter completed state - download finished."""
|
|
279
|
+
self.logger.debug("Entering COMPLETED state - download finished")
|
|
280
|
+
self.on_finish.emit()
|
|
281
|
+
|
|
282
|
+
# Protocol event handlers
|
|
283
|
+
|
|
284
|
+
def _on_connection_made(self) -> None:
|
|
285
|
+
"""Handle connection established event."""
|
|
286
|
+
self.logger.debug("Connection made")
|
|
287
|
+
if self.idle.is_active:
|
|
288
|
+
self.do_connect()
|
|
289
|
+
|
|
290
|
+
def _on_telegram_sent(self, telegram_sent: str) -> None:
|
|
291
|
+
"""Handle telegram sent event.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
telegram_sent: The telegram that was sent.
|
|
295
|
+
"""
|
|
296
|
+
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
297
|
+
|
|
298
|
+
def _on_read_datapoint_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
299
|
+
"""Handle READ_DATAPOINT response for error status check.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
reply_telegram: The parsed reply telegram.
|
|
303
|
+
"""
|
|
304
|
+
self.logger.debug(f"Received READ_DATAPOINT in {self.current_state}")
|
|
305
|
+
|
|
306
|
+
if reply_telegram.datapoint_type != DataPointType.MODULE_ERROR_CODE:
|
|
307
|
+
self.logger.debug(
|
|
308
|
+
f"Filtered: not a MODULE_ERROR_CODE (got {reply_telegram.datapoint_type})"
|
|
309
|
+
)
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
if not self.waiting_ok.is_active:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
is_no_error = reply_telegram.data_value == NO_ERROR_CODE
|
|
316
|
+
if is_no_error:
|
|
317
|
+
self._error_retry_count = 0 # Reset on success
|
|
318
|
+
self.no_error_status_received() # Guards determine target state
|
|
319
|
+
else:
|
|
320
|
+
self._error_retry_count += 1
|
|
321
|
+
self.logger.debug(
|
|
322
|
+
f"Error status received, retry {self._error_retry_count}/{MAX_ERROR_RETRIES}"
|
|
323
|
+
)
|
|
324
|
+
# Guard can_retry blocks transition if max retries exceeded
|
|
325
|
+
self.error_status_received()
|
|
326
|
+
# Check if guard blocked the transition (still in waiting_ok)
|
|
327
|
+
if self.waiting_ok.is_active:
|
|
328
|
+
self.logger.error(
|
|
329
|
+
f"Max error retries ({MAX_ERROR_RETRIES}) exceeded, giving up"
|
|
330
|
+
)
|
|
331
|
+
self.on_error.emit(
|
|
332
|
+
f"Module error persists after {MAX_ERROR_RETRIES} retries"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def _on_actiontable_chunk_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
336
|
+
"""Handle actiontable chunk telegram received.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
reply_telegram: The parsed reply telegram containing chunk data.
|
|
340
|
+
"""
|
|
341
|
+
self.logger.debug(f"Received actiontable chunk in {self.current_state}")
|
|
342
|
+
if self.waiting_data.is_active:
|
|
343
|
+
data_part = reply_telegram.data_value[CHUNK_HEADER_LENGTH:]
|
|
344
|
+
self.actiontable_data.append(data_part)
|
|
345
|
+
self.on_progress.emit(".")
|
|
346
|
+
self.receive_chunk()
|
|
347
|
+
|
|
348
|
+
def _on_eof_received(self, _reply_telegram: ReplyTelegram) -> None:
|
|
349
|
+
"""Handle EOF telegram received.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
_reply_telegram: The parsed reply telegram (unused).
|
|
353
|
+
"""
|
|
354
|
+
self.logger.debug(f"Received EOF in {self.current_state}")
|
|
355
|
+
if self.waiting_data.is_active:
|
|
356
|
+
self.receive_eof()
|
|
357
|
+
|
|
358
|
+
def _on_telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
359
|
+
"""Handle telegram received event.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
telegram_received: The telegram received event.
|
|
363
|
+
"""
|
|
364
|
+
self.logger.debug(f"Received {telegram_received} in {self.current_state}")
|
|
365
|
+
|
|
366
|
+
# In receiving state, drain pending telegrams from pipe (discard to /dev/null).
|
|
367
|
+
# This ensures clean state before processing by clearing any stale messages.
|
|
368
|
+
if self.receiving.is_active:
|
|
369
|
+
self.filter_telegram()
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
# Filter invalid telegrams
|
|
373
|
+
if not telegram_received.checksum_valid:
|
|
374
|
+
self.logger.debug("Filtered: invalid checksum")
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
if telegram_received.telegram_type != TelegramType.REPLY.value:
|
|
378
|
+
self.logger.debug(
|
|
379
|
+
f"Filtered: not a reply (got {telegram_received.telegram_type})"
|
|
380
|
+
)
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
if telegram_received.serial_number != self.serial_number:
|
|
384
|
+
self.logger.debug(
|
|
385
|
+
f"Filtered: wrong serial {telegram_received.serial_number} != {self.serial_number}"
|
|
386
|
+
)
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
390
|
+
telegram_received.frame
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
if reply_telegram.system_function == SystemFunction.READ_DATAPOINT:
|
|
394
|
+
self._on_read_datapoint_received(reply_telegram)
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
if reply_telegram.system_function == SystemFunction.ACTIONTABLE:
|
|
398
|
+
self._on_actiontable_chunk_received(reply_telegram)
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
if reply_telegram.system_function == SystemFunction.EOF:
|
|
402
|
+
self._on_eof_received(reply_telegram)
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
def _on_timeout(self) -> None:
|
|
406
|
+
"""Handle timeout event."""
|
|
407
|
+
self.logger.debug(f"Timeout occurred (phase={self._phase.value})")
|
|
408
|
+
if self.receiving.is_active:
|
|
409
|
+
self.do_timeout() # receiving -> resetting
|
|
410
|
+
elif self.waiting_ok.is_active:
|
|
411
|
+
self.do_timeout() # waiting_ok -> receiving (retry)
|
|
412
|
+
elif self.waiting_data.is_active:
|
|
413
|
+
self.logger.error("Timeout waiting for actiontable data")
|
|
414
|
+
self.on_error.emit("Timeout waiting for actiontable data")
|
|
415
|
+
else:
|
|
416
|
+
self.logger.debug("Timeout in non-recoverable state")
|
|
417
|
+
self.on_error.emit("Timeout")
|
|
418
|
+
|
|
419
|
+
def _on_failed(self, message: str) -> None:
|
|
420
|
+
"""Handle failed connection event.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
message: Failure message.
|
|
424
|
+
"""
|
|
425
|
+
self.logger.debug(f"Failed: {message}")
|
|
426
|
+
self.on_error.emit(message)
|
|
427
|
+
|
|
428
|
+
# Public API
|
|
429
|
+
|
|
430
|
+
def configure(
|
|
431
|
+
self,
|
|
432
|
+
serial_number: str,
|
|
433
|
+
timeout_seconds: Optional[float] = 2.0,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Configure download parameters before starting.
|
|
436
|
+
|
|
437
|
+
Sets the target module serial number and timeout. Call this before
|
|
438
|
+
start_reactor() to configure the download target.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
serial_number: Module serial number to download from.
|
|
442
|
+
timeout_seconds: Timeout in seconds for each operation (default 2.0).
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
RuntimeError: If called while download is in progress.
|
|
446
|
+
"""
|
|
447
|
+
if not self.idle.is_active:
|
|
448
|
+
raise RuntimeError("Cannot configure while download in progress")
|
|
449
|
+
self.logger.info("Configuring actiontable download")
|
|
450
|
+
self.serial_number = serial_number
|
|
451
|
+
if timeout_seconds:
|
|
452
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
453
|
+
|
|
454
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
455
|
+
"""Set operation timeout.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
timeout_seconds: Timeout in seconds.
|
|
459
|
+
"""
|
|
460
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
461
|
+
|
|
462
|
+
def start_reactor(self) -> None:
|
|
463
|
+
"""Start the reactor."""
|
|
464
|
+
self.conbus_protocol.start_reactor()
|
|
465
|
+
|
|
466
|
+
def stop_reactor(self) -> None:
|
|
467
|
+
"""Stop the reactor."""
|
|
468
|
+
self.conbus_protocol.stop_reactor()
|
|
469
|
+
|
|
470
|
+
def _connect_signals(self) -> None:
|
|
471
|
+
"""Connect protocol signals to handlers (idempotent)."""
|
|
472
|
+
if self._signals_connected:
|
|
473
|
+
return
|
|
474
|
+
self.conbus_protocol.on_connection_made.connect(self._on_connection_made)
|
|
475
|
+
self.conbus_protocol.on_telegram_sent.connect(self._on_telegram_sent)
|
|
476
|
+
self.conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
|
|
477
|
+
self.conbus_protocol.on_timeout.connect(self._on_timeout)
|
|
478
|
+
self.conbus_protocol.on_failed.connect(self._on_failed)
|
|
479
|
+
self._signals_connected = True
|
|
480
|
+
|
|
481
|
+
def _disconnect_signals(self) -> None:
|
|
482
|
+
"""Disconnect protocol signals from handlers (idempotent)."""
|
|
483
|
+
if not self._signals_connected:
|
|
484
|
+
return
|
|
485
|
+
self.conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
|
|
486
|
+
self.conbus_protocol.on_telegram_sent.disconnect(self._on_telegram_sent)
|
|
487
|
+
self.conbus_protocol.on_telegram_received.disconnect(self._on_telegram_received)
|
|
488
|
+
self.conbus_protocol.on_timeout.disconnect(self._on_timeout)
|
|
489
|
+
self.conbus_protocol.on_failed.disconnect(self._on_failed)
|
|
490
|
+
self._signals_connected = False
|
|
491
|
+
|
|
492
|
+
def __enter__(self) -> "ActionTableDownloadService":
|
|
493
|
+
"""Enter context manager - reset state and reconnect signals.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
Self for context manager protocol.
|
|
497
|
+
"""
|
|
498
|
+
# Reset state for singleton reuse
|
|
499
|
+
self.actiontable_data = []
|
|
500
|
+
self._phase = Phase.INIT
|
|
501
|
+
self._error_retry_count = 0
|
|
502
|
+
# Reset state machine to idle
|
|
503
|
+
self._reset_state()
|
|
504
|
+
# Reconnect signals (in case previously disconnected)
|
|
505
|
+
self._connect_signals()
|
|
506
|
+
return self
|
|
507
|
+
|
|
508
|
+
def _reset_state(self) -> None:
|
|
509
|
+
"""Reset state machine to initial state."""
|
|
510
|
+
# python-statemachine uses model.state to track current state
|
|
511
|
+
# Set it directly to the initial state id
|
|
512
|
+
self.model.state = self.idle.id
|
|
513
|
+
|
|
514
|
+
def __exit__(
|
|
515
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
516
|
+
) -> None:
|
|
517
|
+
"""Exit context manager and disconnect signals."""
|
|
518
|
+
self._disconnect_signals()
|
|
519
|
+
# Disconnect service signals
|
|
520
|
+
self.on_progress.disconnect()
|
|
521
|
+
self.on_error.disconnect()
|
|
522
|
+
self.on_actiontable_received.disconnect()
|
|
523
|
+
self.on_finish.disconnect()
|
|
524
|
+
# Stop reactor
|
|
525
|
+
self.stop_reactor()
|
|
@@ -103,6 +103,16 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
103
103
|
# Start inactivity timeout
|
|
104
104
|
self._reset_timeout()
|
|
105
105
|
|
|
106
|
+
def wait(self, wait_timeout: Optional[float] = None) -> None:
|
|
107
|
+
"""Wait for incoming telegrams with optional timeout override.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
wait_timeout: Optional timeout in seconds to override default.
|
|
111
|
+
"""
|
|
112
|
+
if wait_timeout:
|
|
113
|
+
self.timeout_seconds = wait_timeout
|
|
114
|
+
self._reset_timeout()
|
|
115
|
+
|
|
106
116
|
def dataReceived(self, data: bytes) -> None:
|
|
107
117
|
"""Handle received data from TCP connection.
|
|
108
118
|
|
|
@@ -337,7 +337,7 @@ class ServerService:
|
|
|
337
337
|
self.logger.warning(f"Failed to parse telegram: {telegram}")
|
|
338
338
|
return responses
|
|
339
339
|
|
|
340
|
-
self.client_buffers.broadcast(parsed_telegram.raw_telegram)
|
|
340
|
+
# self.client_buffers.broadcast(parsed_telegram.raw_telegram)
|
|
341
341
|
|
|
342
342
|
# Handle discover requests
|
|
343
343
|
if self.discover_service.is_discover_request(parsed_telegram):
|
|
@@ -103,7 +103,11 @@ class TestActionTableIntegration:
|
|
|
103
103
|
mock_service.__exit__ = Mock(return_value=None)
|
|
104
104
|
|
|
105
105
|
# Store the callbacks that are connected
|
|
106
|
-
callbacks = {
|
|
106
|
+
callbacks = {
|
|
107
|
+
"on_finish": None,
|
|
108
|
+
"on_progress": None,
|
|
109
|
+
"on_actiontable_received": None,
|
|
110
|
+
}
|
|
107
111
|
|
|
108
112
|
def mock_on_finish_connect(callback):
|
|
109
113
|
"""Mock on_finish event connection.
|
|
@@ -121,10 +125,21 @@ class TestActionTableIntegration:
|
|
|
121
125
|
"""
|
|
122
126
|
callbacks["on_progress"] = callback
|
|
123
127
|
|
|
128
|
+
def mock_on_actiontable_received_connect(callback):
|
|
129
|
+
"""Mock on_actiontable_received event connection.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
callback: Callback function to store.
|
|
133
|
+
"""
|
|
134
|
+
callbacks["on_actiontable_received"] = callback
|
|
135
|
+
|
|
124
136
|
mock_service.on_finish.connect.side_effect = mock_on_finish_connect
|
|
125
137
|
mock_service.on_progress.connect.side_effect = mock_on_progress_connect
|
|
138
|
+
mock_service.on_actiontable_received.connect.side_effect = (
|
|
139
|
+
mock_on_actiontable_received_connect
|
|
140
|
+
)
|
|
126
141
|
|
|
127
|
-
# Mock the start method to call
|
|
142
|
+
# Mock the start method to call callbacks immediately
|
|
128
143
|
def mock_start(serial_number):
|
|
129
144
|
"""Test helper function.
|
|
130
145
|
|
|
@@ -136,11 +151,14 @@ class TestActionTableIntegration:
|
|
|
136
151
|
actiontable_short = ActionTableSerializer.format_decoded_output(
|
|
137
152
|
sample_actiontable
|
|
138
153
|
)
|
|
139
|
-
# Call the
|
|
140
|
-
if callbacks["
|
|
141
|
-
callbacks["
|
|
154
|
+
# Call the on_actiontable_received callback with data
|
|
155
|
+
if callbacks["on_actiontable_received"]:
|
|
156
|
+
callbacks["on_actiontable_received"](
|
|
142
157
|
sample_actiontable, actiontable_dict, actiontable_short
|
|
143
158
|
)
|
|
159
|
+
# Call the on_finish callback without arguments
|
|
160
|
+
if callbacks["on_finish"]:
|
|
161
|
+
callbacks["on_finish"]()
|
|
144
162
|
|
|
145
163
|
def mock_start_reactor() -> None:
|
|
146
164
|
"""Mock reactor start method."""
|