conson-xp 2.0.1__tar.gz → 2.0.3__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-2.0.1 → conson_xp-2.0.3}/PKG-INFO +1 -1
- {conson_xp-2.0.1 → conson_xp-2.0.3}/pyproject.toml +1 -1
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/__init__.py +1 -1
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/homekit/homekit_config.py +8 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/actiontable_serializer.py +4 -4
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +8 -1
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/term/homekit_accessory_driver.py +67 -11
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/term/homekit_service.py +168 -12
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/homekit.py +23 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_actiontable_serializer.py +111 -1
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_actiontable_upload_service.py +6 -2
- {conson_xp-2.0.1 → conson_xp-2.0.3}/LICENSE +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/README.md +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/__main__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/term/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/term/term.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/commands/term/term_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/main.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/module_type_choice.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/actiontable_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_event_list.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_event_raw.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_export.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_logger_config.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/config/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/config/conson_module_config.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/log_entry.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/response.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/accessory_state.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/connection_state.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/module_state.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/protocol_keys_config.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/status_message.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/term/telegram_display.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/download_state_machine.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/actiontable/serializer_protocol.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_export_actiontable_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_export_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/protocol/conbus_event_protocol.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/base_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/client_buffer_manager.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/device_service_factory.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/server/xp33_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/term/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/term/protocol_monitor_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/term/state_monitor_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/homekit.tcss +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/protocol.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/protocol.tcss +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/state.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/state.tcss +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/help_menu.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/modules_list.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/protocol_log.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/room_list.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/term/widgets/status_footer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/checksum.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/dependencies.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/logging.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/serialization.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/state_machine.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/.coverage +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/conftest.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/.coverage +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_term_logging_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_cli/test_term_commands.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_logger_config.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_term/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_term/test_accessory_state.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_actiontable_download_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_output_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_device_service_factory.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_homekit_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_state_monitor_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_telegram_output_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp33_short_format.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_xp_server_services.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_tui/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_tui/test_homekit_app.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_tui/test_protocol_log.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_tui/test_room_list.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/test_logging.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_utils/test_time_utils.py +0 -0
|
@@ -66,6 +66,10 @@ class HomekitAccessoryConfig(BaseModel):
|
|
|
66
66
|
on_action: on code for the accessory.
|
|
67
67
|
off_action: off code for the accessory.
|
|
68
68
|
toggle_action: Optional toggle action code for the accessory.
|
|
69
|
+
dimup_action: Optional dim up action code for the dimmable accessory.
|
|
70
|
+
dimdown_action: Optional dim down action code for the dimmable accessory.
|
|
71
|
+
levelup_action: Optional level up action code for the dimmable accessory.
|
|
72
|
+
leveldown_action: Optional level down action code for the dimmable accessory.
|
|
69
73
|
hap_accessory: Optional HAP accessory identifier.
|
|
70
74
|
"""
|
|
71
75
|
|
|
@@ -78,6 +82,10 @@ class HomekitAccessoryConfig(BaseModel):
|
|
|
78
82
|
on_action: str
|
|
79
83
|
off_action: str
|
|
80
84
|
toggle_action: Optional[str] = None
|
|
85
|
+
dimup_action: Optional[str] = None
|
|
86
|
+
dimdown_action: Optional[str] = None
|
|
87
|
+
levelup_action: Optional[str] = None
|
|
88
|
+
leveldown_action: Optional[str] = None
|
|
81
89
|
hap_accessory: Optional[int] = None
|
|
82
90
|
|
|
83
91
|
|
|
@@ -65,8 +65,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
65
65
|
link_number = de_bcd(data[i + 1])
|
|
66
66
|
module_input = de_bcd(data[i + 2])
|
|
67
67
|
|
|
68
|
-
# Extract output
|
|
69
|
-
module_output = lower3(data[i + 3])
|
|
68
|
+
# Extract output (0-indexed in wire format, convert to 1-indexed) and command
|
|
69
|
+
module_output = lower3(data[i + 3]) + 1
|
|
70
70
|
command_raw = upper5(data[i + 3])
|
|
71
71
|
|
|
72
72
|
parameter_raw = byte_to_unsigned(data[i + 4])
|
|
@@ -125,8 +125,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
125
125
|
link_byte = to_bcd(entry.link_number)
|
|
126
126
|
input_byte = to_bcd(entry.module_input)
|
|
127
127
|
|
|
128
|
-
# Combine output (lower 3 bits) and command (upper 5 bits)
|
|
129
|
-
output_command_byte = (entry.module_output & 0x07) | (
|
|
128
|
+
# Combine output (lower 3 bits, 0-indexed) and command (upper 5 bits)
|
|
129
|
+
output_command_byte = ((entry.module_output - 1) & 0x07) | (
|
|
130
130
|
(entry.command.value & 0x1F) << 3
|
|
131
131
|
)
|
|
132
132
|
|
{conson_xp-2.0.1 → conson_xp-2.0.3}/src/xp/services/conbus/actiontable/actiontable_upload_service.py
RENAMED
|
@@ -81,6 +81,7 @@ class ActionTableUploadService:
|
|
|
81
81
|
# Upload state
|
|
82
82
|
self.upload_data_chunks: list[str] = []
|
|
83
83
|
self.current_chunk_index: int = 0
|
|
84
|
+
self._eof_sent: bool = False
|
|
84
85
|
|
|
85
86
|
# Set up logging
|
|
86
87
|
self.logger = logging.getLogger(__name__)
|
|
@@ -173,7 +174,7 @@ class ActionTableUploadService:
|
|
|
173
174
|
)
|
|
174
175
|
self.current_chunk_index += 1
|
|
175
176
|
self.on_progress.emit(".")
|
|
176
|
-
|
|
177
|
+
elif not self._eof_sent:
|
|
177
178
|
# All chunks sent, send EOF
|
|
178
179
|
self.logger.debug("All chunks sent, sending EOF")
|
|
179
180
|
self.conbus_protocol.send_telegram(
|
|
@@ -182,7 +183,13 @@ class ActionTableUploadService:
|
|
|
182
183
|
system_function=SystemFunction.EOF,
|
|
183
184
|
data_value="00",
|
|
184
185
|
)
|
|
186
|
+
self.on_progress.emit("END")
|
|
187
|
+
self.logger.debug("EOF sent, waiting for last ACK")
|
|
188
|
+
self._eof_sent = True
|
|
189
|
+
else:
|
|
190
|
+
self.logger.debug("Last ACK received, closing connection")
|
|
185
191
|
self.on_finish.emit(True)
|
|
192
|
+
|
|
186
193
|
elif reply_telegram.system_function == SystemFunction.NAK:
|
|
187
194
|
self.logger.debug("Received NAK during upload")
|
|
188
195
|
self.failed("Upload failed: NAK received")
|
|
@@ -10,9 +10,18 @@ from pyhap.const import CATEGORY_LIGHTBULB, CATEGORY_OUTLET
|
|
|
10
10
|
|
|
11
11
|
from xp.models.homekit.homekit_config import HomekitConfig
|
|
12
12
|
|
|
13
|
+
# Callback type: (accessory_name, is_on, brightness_or_none)
|
|
14
|
+
OnSetCallback = Callable[[str, bool, Optional[int]], None]
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class XPAccessory(Accessory):
|
|
15
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
Single accessory wrapping a Conbus output.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
logger: Logger instance for this accessory.
|
|
23
|
+
current_brightness: Current brightness value 0-100.
|
|
24
|
+
"""
|
|
16
25
|
|
|
17
26
|
def __init__(
|
|
18
27
|
self,
|
|
@@ -35,12 +44,19 @@ class XPAccessory(Accessory):
|
|
|
35
44
|
super().__init__(driver._driver, display_name, aid=aid)
|
|
36
45
|
self._hk_driver = driver
|
|
37
46
|
self._accessory_id = name
|
|
47
|
+
self._is_dimmable = service_type == "dimminglight"
|
|
48
|
+
self._char_brightness: Optional[object] = None
|
|
49
|
+
self._current_brightness: int = 100
|
|
38
50
|
self.logger = logging.getLogger(__name__)
|
|
39
51
|
|
|
40
|
-
if
|
|
52
|
+
if self._is_dimmable:
|
|
41
53
|
self.category = CATEGORY_LIGHTBULB
|
|
42
54
|
serv = self.add_preload_service("Lightbulb", chars=["On", "Brightness"])
|
|
43
|
-
|
|
55
|
+
self._char_brightness = serv.configure_char(
|
|
56
|
+
"Brightness",
|
|
57
|
+
setter_callback=self._set_brightness,
|
|
58
|
+
value=self._current_brightness,
|
|
59
|
+
)
|
|
44
60
|
elif service_type == "outlet":
|
|
45
61
|
self.category = CATEGORY_OUTLET
|
|
46
62
|
serv = self.add_preload_service("Outlet")
|
|
@@ -58,16 +74,36 @@ class XPAccessory(Accessory):
|
|
|
58
74
|
value: True for on, False for off.
|
|
59
75
|
"""
|
|
60
76
|
if self._hk_driver._on_set:
|
|
61
|
-
self._hk_driver._on_set(self._accessory_id, value)
|
|
77
|
+
self._hk_driver._on_set(self._accessory_id, value, None)
|
|
78
|
+
|
|
79
|
+
def _set_brightness(self, value: int) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Handle HomeKit set brightness request.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
value: Brightness value 0-100.
|
|
85
|
+
"""
|
|
86
|
+
if self._hk_driver._on_set:
|
|
87
|
+
self._hk_driver._on_set(self._accessory_id, True, value)
|
|
88
|
+
self._current_brightness = value
|
|
62
89
|
|
|
63
|
-
def update_state(self, is_on: bool) -> None:
|
|
90
|
+
def update_state(self, is_on: bool, brightness: Optional[int] = None) -> None:
|
|
64
91
|
"""
|
|
65
92
|
Update accessory state from Conbus event.
|
|
66
93
|
|
|
67
94
|
Args:
|
|
68
95
|
is_on: True if accessory is on, False otherwise.
|
|
96
|
+
brightness: Optional brightness value 0-100.
|
|
69
97
|
"""
|
|
70
98
|
self._char_on.set_value(is_on)
|
|
99
|
+
if brightness is not None and self._char_brightness:
|
|
100
|
+
self._char_brightness.set_value(brightness) # type: ignore[attr-defined]
|
|
101
|
+
self._current_brightness = brightness
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def current_brightness(self) -> int:
|
|
105
|
+
"""Get current brightness value."""
|
|
106
|
+
return self._current_brightness
|
|
71
107
|
|
|
72
108
|
|
|
73
109
|
class HomekitAccessoryDriver:
|
|
@@ -84,14 +120,15 @@ class HomekitAccessoryDriver:
|
|
|
84
120
|
self._homekit_config = homekit_config
|
|
85
121
|
self._driver: Optional[AccessoryDriver] = None
|
|
86
122
|
self._accessories: Dict[str, XPAccessory] = {}
|
|
87
|
-
self._on_set: Optional[
|
|
123
|
+
self._on_set: Optional[OnSetCallback] = None
|
|
88
124
|
|
|
89
|
-
def set_callback(self, on_set:
|
|
125
|
+
def set_callback(self, on_set: OnSetCallback) -> None:
|
|
90
126
|
"""
|
|
91
127
|
Set callback for HomeKit set events.
|
|
92
128
|
|
|
93
129
|
Args:
|
|
94
|
-
on_set: Callback(accessory_name, is_on) called when HomeKit app
|
|
130
|
+
on_set: Callback(accessory_name, is_on, brightness) called when HomeKit app changes state.
|
|
131
|
+
brightness is None for on/off only, or 0-100 for dimming.
|
|
95
132
|
"""
|
|
96
133
|
self._on_set = on_set
|
|
97
134
|
|
|
@@ -157,15 +194,34 @@ class HomekitAccessoryDriver:
|
|
|
157
194
|
except Exception as e:
|
|
158
195
|
self.logger.error(f"Error stopping AccessoryDriver: {e}", exc_info=True)
|
|
159
196
|
|
|
160
|
-
def update_state(
|
|
197
|
+
def update_state(
|
|
198
|
+
self, accessory_name: str, is_on: bool, brightness: Optional[int] = None
|
|
199
|
+
) -> None:
|
|
161
200
|
"""
|
|
162
201
|
Update accessory state from Conbus event.
|
|
163
202
|
|
|
164
203
|
Args:
|
|
165
204
|
accessory_name: Accessory name to update.
|
|
166
205
|
is_on: True if accessory is on, False otherwise.
|
|
206
|
+
brightness: Optional brightness value 0-100.
|
|
167
207
|
"""
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
acc = self._accessories.get(accessory_name)
|
|
209
|
+
if acc:
|
|
210
|
+
acc.update_state(is_on, brightness)
|
|
170
211
|
else:
|
|
171
212
|
self.logger.warning(f"Unknown accessory name: {accessory_name}")
|
|
213
|
+
|
|
214
|
+
def get_brightness(self, accessory_name: str) -> int:
|
|
215
|
+
"""
|
|
216
|
+
Get current brightness for an accessory.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
accessory_name: Accessory name.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Current brightness 0-100, defaults to 100 if not found.
|
|
223
|
+
"""
|
|
224
|
+
acc = self._accessories.get(accessory_name)
|
|
225
|
+
if acc:
|
|
226
|
+
return acc.current_brightness
|
|
227
|
+
return 100
|
|
@@ -81,6 +81,9 @@ class HomekitService:
|
|
|
81
81
|
# Set up HomeKit callback
|
|
82
82
|
self._accessory_driver.set_callback(self._on_homekit_set)
|
|
83
83
|
|
|
84
|
+
# Track active level action: (accessory_id, action_type) or None
|
|
85
|
+
self._active_level_action: Optional[tuple[str, str]] = None
|
|
86
|
+
|
|
84
87
|
# Connect to protocol signals
|
|
85
88
|
self._connect_signals()
|
|
86
89
|
|
|
@@ -297,23 +300,82 @@ class HomekitService:
|
|
|
297
300
|
await self._accessory_driver.stop()
|
|
298
301
|
self.cleanup()
|
|
299
302
|
|
|
300
|
-
def _on_homekit_set(
|
|
303
|
+
def _on_homekit_set(
|
|
304
|
+
self, accessory_name: str, is_on: bool, brightness: Optional[int]
|
|
305
|
+
) -> None:
|
|
301
306
|
"""
|
|
302
|
-
Handle HomeKit app
|
|
307
|
+
Handle HomeKit app set request (on/off or brightness).
|
|
303
308
|
|
|
304
309
|
Args:
|
|
305
310
|
accessory_name: Accessory name from HomeKit.
|
|
306
311
|
is_on: True for on, False for off.
|
|
312
|
+
brightness: Brightness value 0-100, or None for on/off only.
|
|
307
313
|
"""
|
|
308
314
|
config = self._find_accessory_config(accessory_name)
|
|
309
|
-
if config:
|
|
315
|
+
if not config:
|
|
316
|
+
self.logger.warning(f"No config found for accessory: {accessory_name}")
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
if brightness is not None:
|
|
320
|
+
# Handle brightness change
|
|
321
|
+
self._handle_brightness_change(accessory_name, config, brightness)
|
|
322
|
+
else:
|
|
323
|
+
# Handle on/off toggle
|
|
310
324
|
action = config.on_action if is_on else config.off_action
|
|
311
325
|
self.send_action(action)
|
|
312
326
|
self.on_status_message.emit(
|
|
313
327
|
f"HomeKit: {accessory_name} {'ON' if is_on else 'OFF'}"
|
|
314
328
|
)
|
|
329
|
+
|
|
330
|
+
def _handle_brightness_change(
|
|
331
|
+
self,
|
|
332
|
+
accessory_name: str,
|
|
333
|
+
config: "HomekitAccessoryConfig",
|
|
334
|
+
target_brightness: int,
|
|
335
|
+
) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Handle brightness change by sending dimup/dimdown actions.
|
|
338
|
+
|
|
339
|
+
Calculates delta from current brightness and sends appropriate
|
|
340
|
+
number of LEVELINC or LEVELDEC commands (step = 10%).
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
accessory_name: Accessory name.
|
|
344
|
+
config: Accessory configuration.
|
|
345
|
+
target_brightness: Target brightness 0-100.
|
|
346
|
+
"""
|
|
347
|
+
current = self._accessory_driver.get_brightness(accessory_name)
|
|
348
|
+
delta = target_brightness - current
|
|
349
|
+
|
|
350
|
+
if delta == 0:
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
# Determine action and steps (10% per step)
|
|
354
|
+
step_size = 10
|
|
355
|
+
steps = abs(delta) // step_size
|
|
356
|
+
|
|
357
|
+
if delta > 0:
|
|
358
|
+
# Increase brightness
|
|
359
|
+
if not config.dimup_action:
|
|
360
|
+
self.logger.warning(f"No dimup_action for {accessory_name}")
|
|
361
|
+
return
|
|
362
|
+
action = config.dimup_action
|
|
363
|
+
direction = "+"
|
|
315
364
|
else:
|
|
316
|
-
|
|
365
|
+
# Decrease brightness
|
|
366
|
+
if not config.dimdown_action:
|
|
367
|
+
self.logger.warning(f"No dimdown_action for {accessory_name}")
|
|
368
|
+
return
|
|
369
|
+
action = config.dimdown_action
|
|
370
|
+
direction = "-"
|
|
371
|
+
|
|
372
|
+
# Send action for each step
|
|
373
|
+
for _ in range(steps):
|
|
374
|
+
self.send_action(action)
|
|
375
|
+
|
|
376
|
+
self.on_status_message.emit(
|
|
377
|
+
f"HomeKit: {accessory_name} {current}% → {target_brightness}% ({direction}{steps * step_size}%)"
|
|
378
|
+
)
|
|
317
379
|
|
|
318
380
|
def send_action(self, action: str) -> None:
|
|
319
381
|
"""
|
|
@@ -421,12 +483,15 @@ class HomekitService:
|
|
|
421
483
|
Returns:
|
|
422
484
|
True if command was sent, False otherwise.
|
|
423
485
|
"""
|
|
486
|
+
config = self._find_accessory_config_by_id(accessory_id)
|
|
424
487
|
state = self._accessory_states.get(accessory_id)
|
|
425
|
-
if not state:
|
|
488
|
+
if not config or not state or not config.dimup_action:
|
|
489
|
+
self.logger.warning(f"No config for accessory {accessory_id}")
|
|
426
490
|
return False
|
|
427
|
-
|
|
428
|
-
self.
|
|
429
|
-
|
|
491
|
+
|
|
492
|
+
self.send_action(config.dimup_action)
|
|
493
|
+
self.on_status_message.emit(f"Dim+ {state.accessory_name}")
|
|
494
|
+
return True
|
|
430
495
|
|
|
431
496
|
def decrease_dimmer(self, accessory_id: str) -> bool:
|
|
432
497
|
"""
|
|
@@ -438,12 +503,103 @@ class HomekitService:
|
|
|
438
503
|
Returns:
|
|
439
504
|
True if command was sent, False otherwise.
|
|
440
505
|
"""
|
|
506
|
+
config = self._find_accessory_config_by_id(accessory_id)
|
|
441
507
|
state = self._accessory_states.get(accessory_id)
|
|
442
|
-
if not state:
|
|
508
|
+
if not config or not state or not config.dimdown_action:
|
|
509
|
+
self.logger.warning(f"No config for accessory {accessory_id}")
|
|
443
510
|
return False
|
|
444
|
-
|
|
445
|
-
self.
|
|
446
|
-
|
|
511
|
+
|
|
512
|
+
self.send_action(config.dimdown_action)
|
|
513
|
+
self.on_status_message.emit(f"Dim- {state.accessory_name}")
|
|
514
|
+
return True
|
|
515
|
+
|
|
516
|
+
def levelup_selected(self, accessory_id: str) -> bool:
|
|
517
|
+
"""
|
|
518
|
+
Increase level for accessory (toggle Make/Break).
|
|
519
|
+
|
|
520
|
+
First press sends Make (M), second press sends Break (B).
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
accessory_id: Accessory ID (e.g., "A12_1").
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
True if command was sent, False otherwise.
|
|
527
|
+
"""
|
|
528
|
+
config = self._find_accessory_config_by_id(accessory_id)
|
|
529
|
+
state = self._accessory_states.get(accessory_id)
|
|
530
|
+
if not config or not state or not config.levelup_action:
|
|
531
|
+
self.logger.warning(f"No config for accessory {accessory_id}")
|
|
532
|
+
return False
|
|
533
|
+
|
|
534
|
+
return self._send_level_action(
|
|
535
|
+
accessory_id, "levelup", config.levelup_action, state.accessory_name
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
def leveldown_selected(self, accessory_id: str) -> bool:
|
|
539
|
+
"""
|
|
540
|
+
Decrease level for accessory (toggle Make/Break).
|
|
541
|
+
|
|
542
|
+
First press sends Make (M), second press sends Break (B).
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
accessory_id: Accessory ID (e.g., "A12_1").
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
True if command was sent, False otherwise.
|
|
549
|
+
"""
|
|
550
|
+
config = self._find_accessory_config_by_id(accessory_id)
|
|
551
|
+
state = self._accessory_states.get(accessory_id)
|
|
552
|
+
if not config or not state or not config.leveldown_action:
|
|
553
|
+
self.logger.warning(f"No config for accessory {accessory_id}")
|
|
554
|
+
return False
|
|
555
|
+
|
|
556
|
+
return self._send_level_action(
|
|
557
|
+
accessory_id, "leveldown", config.leveldown_action, state.accessory_name
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
def _send_level_action(
|
|
561
|
+
self, accessory_id: str, action_type: str, action: str, name: str
|
|
562
|
+
) -> bool:
|
|
563
|
+
"""
|
|
564
|
+
Send level action with Make/Break toggle.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
accessory_id: Accessory ID.
|
|
568
|
+
action_type: "levelup" or "leveldown".
|
|
569
|
+
action: Action code (e.g., "E02L13I15").
|
|
570
|
+
name: Accessory name for status message.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
True if command was sent.
|
|
574
|
+
"""
|
|
575
|
+
current = self._active_level_action
|
|
576
|
+
|
|
577
|
+
# If same action is active, send Break and clear
|
|
578
|
+
if current and current[0] == accessory_id and current[1] == action_type:
|
|
579
|
+
self._conbus_protocol.send_raw_telegram(f"{action}B")
|
|
580
|
+
self._active_level_action = None
|
|
581
|
+
direction = "+" if action_type == "levelup" else "-"
|
|
582
|
+
self.on_status_message.emit(f"Level{direction} {name} [B]")
|
|
583
|
+
return True
|
|
584
|
+
|
|
585
|
+
# If different action is active, send Break for it first
|
|
586
|
+
if current:
|
|
587
|
+
old_config = self._find_accessory_config_by_id(current[0])
|
|
588
|
+
if old_config:
|
|
589
|
+
old_action = (
|
|
590
|
+
old_config.levelup_action
|
|
591
|
+
if current[1] == "levelup"
|
|
592
|
+
else old_config.leveldown_action
|
|
593
|
+
)
|
|
594
|
+
if old_action:
|
|
595
|
+
self._conbus_protocol.send_raw_telegram(f"{old_action}B")
|
|
596
|
+
|
|
597
|
+
# Send Make for new action
|
|
598
|
+
self._conbus_protocol.send_raw_telegram(f"{action}M")
|
|
599
|
+
self._active_level_action = (accessory_id, action_type)
|
|
600
|
+
direction = "+" if action_type == "levelup" else "-"
|
|
601
|
+
self.on_status_message.emit(f"Level{direction} {name} [M]")
|
|
602
|
+
return True
|
|
447
603
|
|
|
448
604
|
def refresh_all(self) -> None:
|
|
449
605
|
"""
|
|
@@ -41,6 +41,8 @@ class HomekitApp(App[None]):
|
|
|
41
41
|
("minus", "turn_off_selected", "Off"),
|
|
42
42
|
("plus", "dim_up", "Dim+"),
|
|
43
43
|
("quotation_mark", "dim_down", "Dim-"),
|
|
44
|
+
("asterisk", "level_up", "Level+"),
|
|
45
|
+
("ç", "level_down", "Level-"),
|
|
44
46
|
]
|
|
45
47
|
|
|
46
48
|
def __init__(self, homekit_service: HomekitService) -> None:
|
|
@@ -106,12 +108,17 @@ class HomekitApp(App[None]):
|
|
|
106
108
|
- - : Turn OFF
|
|
107
109
|
- + : Dim up
|
|
108
110
|
- " : Dim down
|
|
111
|
+
- * : Level up
|
|
112
|
+
- ç : Level down
|
|
109
113
|
|
|
110
114
|
Args:
|
|
111
115
|
event: Key press event.
|
|
112
116
|
"""
|
|
113
117
|
key = event.key
|
|
114
118
|
|
|
119
|
+
# Debug: show received key
|
|
120
|
+
self.homekit_service.on_status_message.emit(f"Key: {key}")
|
|
121
|
+
|
|
115
122
|
# Selection keys (a-z0-9)
|
|
116
123
|
if len(key) == 1 and (("a" <= key <= "z") or ("0" <= key <= "9")):
|
|
117
124
|
accessory_id = self.homekit_service.select_accessory(key)
|
|
@@ -140,6 +147,12 @@ class HomekitApp(App[None]):
|
|
|
140
147
|
elif key in ("quotation_mark", '"'):
|
|
141
148
|
self.homekit_service.decrease_dimmer(self.selected_accessory_id)
|
|
142
149
|
event.prevent_default()
|
|
150
|
+
elif key in ("asterisk", "star", "*"):
|
|
151
|
+
self.homekit_service.levelup_selected(self.selected_accessory_id)
|
|
152
|
+
event.prevent_default()
|
|
153
|
+
elif key in ("cedille", "ç"):
|
|
154
|
+
self.homekit_service.leveldown_selected(self.selected_accessory_id)
|
|
155
|
+
event.prevent_default()
|
|
143
156
|
|
|
144
157
|
def _select_row(self, action_key: str) -> None:
|
|
145
158
|
"""
|
|
@@ -243,6 +256,16 @@ class HomekitApp(App[None]):
|
|
|
243
256
|
if self.selected_accessory_id:
|
|
244
257
|
self.homekit_service.decrease_dimmer(self.selected_accessory_id)
|
|
245
258
|
|
|
259
|
+
def action_level_up(self) -> None:
|
|
260
|
+
"""Increase level on selected accessory."""
|
|
261
|
+
if self.selected_accessory_id:
|
|
262
|
+
self.homekit_service.levelup_selected(self.selected_accessory_id)
|
|
263
|
+
|
|
264
|
+
def action_level_down(self) -> None:
|
|
265
|
+
"""Decrease level on selected accessory."""
|
|
266
|
+
if self.selected_accessory_id:
|
|
267
|
+
self.homekit_service.leveldown_selected(self.selected_accessory_id)
|
|
268
|
+
|
|
246
269
|
async def on_unmount(self) -> None:
|
|
247
270
|
"""Stop AccessoryDriver and clean up service when app unmounts."""
|
|
248
271
|
await self.homekit_service.stop()
|
|
@@ -398,7 +398,7 @@ class TestActionTableSerializerPadding:
|
|
|
398
398
|
assert result[0] == 0x02 # CP20 (value=2) in BCD
|
|
399
399
|
assert result[1] == 0x01 # link_number
|
|
400
400
|
assert result[2] == 0x02 # module_input
|
|
401
|
-
assert result[3] ==
|
|
401
|
+
assert result[3] == 0x0A # output 0-indexed: (3-1) | (ON << 3) = 2 | 8 = 10
|
|
402
402
|
assert result[4] == 0x04 # parameter
|
|
403
403
|
|
|
404
404
|
# Rest should be padding
|
|
@@ -414,3 +414,113 @@ class TestActionTableSerializerPadding:
|
|
|
414
414
|
# Should still be 480 bytes, all zeros
|
|
415
415
|
assert len(result) == 480
|
|
416
416
|
assert result == b"\x00" * 480
|
|
417
|
+
|
|
418
|
+
def test_to_encoded_string_cp20_link4_input0_output1_on(self):
|
|
419
|
+
"""
|
|
420
|
+
Test encoding CP20 4 0 > 1 ON produces expected BCD string.
|
|
421
|
+
|
|
422
|
+
ActionTable: CP20 4 0 > 1 ON;
|
|
423
|
+
Serialized BCD (first 8 chars): ACAEAAAI
|
|
424
|
+
"""
|
|
425
|
+
action_table = ActionTable(
|
|
426
|
+
entries=[
|
|
427
|
+
ActionTableEntry(
|
|
428
|
+
module_type=ModuleTypeCode.CP20,
|
|
429
|
+
link_number=4,
|
|
430
|
+
module_input=0,
|
|
431
|
+
module_output=1,
|
|
432
|
+
command=InputActionType.ON,
|
|
433
|
+
parameter=TimeParam.NONE,
|
|
434
|
+
inverted=False,
|
|
435
|
+
)
|
|
436
|
+
]
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
encoded_string = ActionTableSerializer.to_encoded_string(action_table)
|
|
440
|
+
|
|
441
|
+
# First 8 characters (4 bytes in BCD, high-nibble first):
|
|
442
|
+
# AC = 0x02 (CP20, value=2)
|
|
443
|
+
# AE = 0x04 (link_number=4)
|
|
444
|
+
# AA = 0x00 (module_input=0)
|
|
445
|
+
# AI = 0x08 (output 0-indexed: (1-1) | (ON<<3) = 0 | 8 = 8)
|
|
446
|
+
assert encoded_string[:8] == "ACAEAAAI"
|
|
447
|
+
|
|
448
|
+
def test_from_encoded_string_cp20_link4_input0_output1_on(self):
|
|
449
|
+
"""
|
|
450
|
+
Test decoding BCD string ACAEAAAI produces expected ActionTable.
|
|
451
|
+
|
|
452
|
+
Serialized BCD: ACAEAAAI (+ padding)
|
|
453
|
+
ActionTable: CP20 4 0 > 1 ON;
|
|
454
|
+
"""
|
|
455
|
+
# Build full 960-char encoded string (96 entries × 5 bytes × 2 nibbles)
|
|
456
|
+
# First entry: ACAEAAAI, rest is padding (AA = 0x00)
|
|
457
|
+
encoded_string = "ACAEAAAI" + "AA" * 476
|
|
458
|
+
|
|
459
|
+
action_table = ActionTableSerializer.from_encoded_string(encoded_string)
|
|
460
|
+
|
|
461
|
+
# Should have exactly 1 entry (padding entries with NOMOD are filtered out)
|
|
462
|
+
assert len(action_table.entries) == 1
|
|
463
|
+
|
|
464
|
+
entry = action_table.entries[0]
|
|
465
|
+
assert entry.module_type == ModuleTypeCode.CP20
|
|
466
|
+
assert entry.link_number == 4
|
|
467
|
+
assert entry.module_input == 0
|
|
468
|
+
assert entry.module_output == 1 # 0-indexed in wire format, converted to 1
|
|
469
|
+
assert entry.command == InputActionType.ON
|
|
470
|
+
assert entry.parameter == TimeParam.NONE
|
|
471
|
+
assert entry.inverted is False
|
|
472
|
+
|
|
473
|
+
def test_to_encoded_string_cp20_link13_input9_output1_levelinc(self):
|
|
474
|
+
"""
|
|
475
|
+
Test encoding CP20 13 9 > 1 LEVELINC produces expected BCD string.
|
|
476
|
+
|
|
477
|
+
ActionTable: CP20 13 9 > 1 LEVELINC;
|
|
478
|
+
Serialized BCD (first 8 chars): ACBDAJEI
|
|
479
|
+
"""
|
|
480
|
+
action_table = ActionTable(
|
|
481
|
+
entries=[
|
|
482
|
+
ActionTableEntry(
|
|
483
|
+
module_type=ModuleTypeCode.CP20,
|
|
484
|
+
link_number=13,
|
|
485
|
+
module_input=9,
|
|
486
|
+
module_output=1,
|
|
487
|
+
command=InputActionType.LEVELINC,
|
|
488
|
+
parameter=TimeParam.NONE,
|
|
489
|
+
inverted=False,
|
|
490
|
+
)
|
|
491
|
+
]
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
encoded_string = ActionTableSerializer.to_encoded_string(action_table)
|
|
495
|
+
|
|
496
|
+
# First 8 characters (4 bytes in BCD, high-nibble first):
|
|
497
|
+
# AC = 0x02 (CP20, value=2)
|
|
498
|
+
# BD = 0x13 (link_number=13 in BCD)
|
|
499
|
+
# AJ = 0x09 (module_input=9)
|
|
500
|
+
# EI = 0x48 (output 0-indexed: (1-1) | (LEVELINC<<3) = 0 | 72 = 0x48)
|
|
501
|
+
assert encoded_string[:8] == "ACBDAJEI"
|
|
502
|
+
|
|
503
|
+
def test_from_encoded_string_cp20_link13_input9_output1_levelinc(self):
|
|
504
|
+
"""
|
|
505
|
+
Test decoding BCD string ACBDAJEIAA produces expected ActionTable.
|
|
506
|
+
|
|
507
|
+
Serialized BCD: ACBDAJEIAA (+ padding)
|
|
508
|
+
ActionTable: CP20 13 9 > 1 LEVELINC;
|
|
509
|
+
"""
|
|
510
|
+
# Build full 960-char encoded string (96 entries × 5 bytes × 2 nibbles)
|
|
511
|
+
# First entry: ACBDAJEIAA, rest is padding (AA = 0x00)
|
|
512
|
+
encoded_string = "ACBDAJEIAA" + "AA" * 475
|
|
513
|
+
|
|
514
|
+
action_table = ActionTableSerializer.from_encoded_string(encoded_string)
|
|
515
|
+
|
|
516
|
+
# Should have exactly 1 entry (padding entries with NOMOD are filtered out)
|
|
517
|
+
assert len(action_table.entries) == 1
|
|
518
|
+
|
|
519
|
+
entry = action_table.entries[0]
|
|
520
|
+
assert entry.module_type == ModuleTypeCode.CP20
|
|
521
|
+
assert entry.link_number == 13
|
|
522
|
+
assert entry.module_input == 9
|
|
523
|
+
assert entry.module_output == 1 # 0-indexed in wire format, converted to 1
|
|
524
|
+
assert entry.command == InputActionType.LEVELINC
|
|
525
|
+
assert entry.parameter == TimeParam.NONE
|
|
526
|
+
assert entry.inverted is False
|
{conson_xp-2.0.1 → conson_xp-2.0.3}/tests/unit/test_services/test_actiontable_upload_service.py
RENAMED
|
@@ -408,6 +408,7 @@ class TestActionTableUploadChunkPrefix:
|
|
|
408
408
|
mock_reply = Mock()
|
|
409
409
|
mock_reply.system_function = SystemFunction.ACK
|
|
410
410
|
|
|
411
|
+
# First ACK after all chunks sent triggers EOF
|
|
411
412
|
service._handle_upload_response(mock_reply)
|
|
412
413
|
|
|
413
414
|
# Should send EOF
|
|
@@ -418,6 +419,9 @@ class TestActionTableUploadChunkPrefix:
|
|
|
418
419
|
data_value="00",
|
|
419
420
|
)
|
|
420
421
|
|
|
422
|
+
# Second ACK (after EOF) triggers finish signal
|
|
423
|
+
service._handle_upload_response(mock_reply)
|
|
424
|
+
|
|
421
425
|
# Should call finish signal with True
|
|
422
426
|
mock_finish.assert_called_once_with(True)
|
|
423
427
|
|
|
@@ -580,11 +584,11 @@ class TestActionTableUploadFullSequence:
|
|
|
580
584
|
# Simulate connection made
|
|
581
585
|
service.connection_made()
|
|
582
586
|
|
|
583
|
-
# Simulate ACK responses for each chunk +
|
|
587
|
+
# Simulate ACK responses for each chunk + ACK to trigger EOF + ACK after EOF
|
|
584
588
|
mock_ack = Mock()
|
|
585
589
|
mock_ack.system_function = SystemFunction.ACK
|
|
586
590
|
|
|
587
|
-
for _ in range(
|
|
591
|
+
for _ in range(17): # 15 chunks + 1 ACK to trigger EOF + 1 ACK after EOF
|
|
588
592
|
service._handle_upload_response(mock_ack)
|
|
589
593
|
|
|
590
594
|
# Verify: Exactly 17 telegrams sent (1 UPLOAD_ACTIONTABLE + 15 ACTIONTABLE + 1 EOF)
|
|
File without changes
|
|
File without changes
|