conson-xp 1.4.0__tar.gz → 1.5.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.4.0 → conson_xp-1.5.0}/PKG-INFO +1 -1
- {conson_xp-1.4.0 → conson_xp-1.5.0}/pyproject.toml +1 -1
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/__init__.py +1 -1
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/base_server_service.py +29 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/server_service.py +87 -6
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/xp33_server_service.py +189 -1
- conson_xp-1.5.0/tests/unit/test_services/test_xp_server_services.py +360 -0
- conson_xp-1.4.0/tests/unit/test_services/test_xp_server_services.py +0 -189
- {conson_xp-1.4.0 → conson_xp-1.5.0}/LICENSE +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/README.md +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/connection/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/connection/exceptions.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_discover.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/cp20_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/xp130_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/xp20_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/xp230_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/server/xp24_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/dependencies.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/.coverage +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/conftest.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_connection/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_connection_init.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_exceptions.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_base_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.4.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_time_utils.py +0 -0
|
@@ -5,6 +5,7 @@ containing common functionality like module type response generation.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
import threading
|
|
8
9
|
from abc import ABC
|
|
9
10
|
from typing import Optional
|
|
10
11
|
|
|
@@ -42,6 +43,9 @@ class BaseServerService(ABC):
|
|
|
42
43
|
self.temperature: str = "+23,5§C"
|
|
43
44
|
self.voltage: str = "+12,5§V"
|
|
44
45
|
|
|
46
|
+
self.telegram_buffer: list[str] = []
|
|
47
|
+
self.telegram_buffer_lock = threading.Lock() # Lock for socket set
|
|
48
|
+
|
|
45
49
|
def generate_datapoint_type_response(
|
|
46
50
|
self, datapoint_type: DataPointType
|
|
47
51
|
) -> Optional[str]:
|
|
@@ -257,3 +261,28 @@ class BaseServerService(ABC):
|
|
|
257
261
|
The response telegram string, or None if request cannot be handled.
|
|
258
262
|
"""
|
|
259
263
|
return None
|
|
264
|
+
|
|
265
|
+
def add_telegram_buffer(self, telegram: str) -> None:
|
|
266
|
+
"""Add telegram to the buffer.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
telegram: The telegram string to add to the buffer.
|
|
270
|
+
"""
|
|
271
|
+
self.logger.debug(f"Add telegram to the buffer: {telegram}")
|
|
272
|
+
with self.telegram_buffer_lock:
|
|
273
|
+
self.telegram_buffer.append(telegram)
|
|
274
|
+
|
|
275
|
+
def collect_telegram_buffer(self) -> list[str]:
|
|
276
|
+
"""Collecting telegrams from the buffer.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
List of telegram strings from the buffer. The buffer is cleared after collection.
|
|
280
|
+
"""
|
|
281
|
+
self.logger.debug(
|
|
282
|
+
f"Collecting {self.serial_number} telegrams from buffer: {len(self.telegram_buffer)}"
|
|
283
|
+
)
|
|
284
|
+
with self.telegram_buffer_lock:
|
|
285
|
+
result = self.telegram_buffer.copy()
|
|
286
|
+
self.logger.debug(f"Resetting {self.serial_number} buffer")
|
|
287
|
+
self.telegram_buffer.clear()
|
|
288
|
+
return result
|
|
@@ -71,6 +71,13 @@ class ServerService:
|
|
|
71
71
|
],
|
|
72
72
|
] = {} # serial -> device service instance
|
|
73
73
|
|
|
74
|
+
# Collect device buffer to broadcast to client
|
|
75
|
+
self.collector_thread: Optional[threading.Thread] = (
|
|
76
|
+
None # Background thread for storm
|
|
77
|
+
)
|
|
78
|
+
self.collector_stop_event = threading.Event() # Event to stop thread
|
|
79
|
+
self.collector_buffer: list[str] = [] # All collected buffers
|
|
80
|
+
|
|
74
81
|
# Set up logging
|
|
75
82
|
self.logger = logging.getLogger(__name__)
|
|
76
83
|
|
|
@@ -167,6 +174,8 @@ class ServerService:
|
|
|
167
174
|
self.server_socket.bind(("0.0.0.0", self.port))
|
|
168
175
|
self.server_socket.listen(1) # Accept single connection as per spec
|
|
169
176
|
|
|
177
|
+
self._start_device_collector_thread()
|
|
178
|
+
|
|
170
179
|
self.is_running = True
|
|
171
180
|
self.logger.info(f"Conbus emulator server started on port {self.port}")
|
|
172
181
|
self.logger.info(
|
|
@@ -221,17 +230,44 @@ class ServerService:
|
|
|
221
230
|
) -> None:
|
|
222
231
|
"""Handle individual client connection."""
|
|
223
232
|
try:
|
|
233
|
+
|
|
234
|
+
idle_timeout = 300
|
|
235
|
+
rcv_timeout = 10
|
|
236
|
+
|
|
224
237
|
# Set timeout for idle connections (30 seconds as per spec)
|
|
225
|
-
client_socket.settimeout(
|
|
238
|
+
client_socket.settimeout(rcv_timeout)
|
|
239
|
+
timeout = idle_timeout / rcv_timeout
|
|
226
240
|
|
|
227
241
|
while True:
|
|
242
|
+
|
|
243
|
+
# send waiting buffer
|
|
244
|
+
for i in range(len(self.collector_buffer)):
|
|
245
|
+
buffer = self.collector_buffer.pop()
|
|
246
|
+
client_socket.send(buffer.encode("latin-1"))
|
|
247
|
+
self.logger.debug(f"Sent buffer to {client_address}")
|
|
248
|
+
|
|
228
249
|
# Receive data from client
|
|
229
|
-
data
|
|
250
|
+
self.logger.debug(f"Receiving data {client_address}")
|
|
251
|
+
data = None
|
|
252
|
+
try:
|
|
253
|
+
data = client_socket.recv(1024)
|
|
254
|
+
except socket.timeout:
|
|
255
|
+
self.logger.debug(
|
|
256
|
+
f"Timeout receiving data {client_address} ({timeout})"
|
|
257
|
+
)
|
|
258
|
+
finally:
|
|
259
|
+
timeout -= 1
|
|
260
|
+
|
|
230
261
|
if not data:
|
|
231
|
-
|
|
262
|
+
if timeout <= 0:
|
|
263
|
+
break
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# reset timeout on receiving data
|
|
267
|
+
timeout = idle_timeout / rcv_timeout
|
|
232
268
|
|
|
233
269
|
message = data.decode("latin-1").strip()
|
|
234
|
-
self.logger.
|
|
270
|
+
self.logger.debug(f"Received from {client_address}: {message}")
|
|
235
271
|
|
|
236
272
|
# Process request (discover or data request)
|
|
237
273
|
responses = self._process_request(message)
|
|
@@ -239,10 +275,10 @@ class ServerService:
|
|
|
239
275
|
# Send responses
|
|
240
276
|
for response in responses:
|
|
241
277
|
client_socket.send(response.encode("latin-1"))
|
|
242
|
-
self.logger.
|
|
278
|
+
self.logger.debug(f"Sent to {client_address}: {response[:-1]}")
|
|
243
279
|
|
|
244
280
|
except socket.timeout:
|
|
245
|
-
self.logger.
|
|
281
|
+
self.logger.debug(f"Client {client_address} timed out")
|
|
246
282
|
except Exception as e:
|
|
247
283
|
self.logger.error(f"Error handling client {client_address}: {e}")
|
|
248
284
|
finally:
|
|
@@ -390,3 +426,48 @@ class ServerService:
|
|
|
390
426
|
self.logger.info(
|
|
391
427
|
f"Configuration reloaded: {len(self.devices)} devices, {len(self.device_services)} services"
|
|
392
428
|
)
|
|
429
|
+
|
|
430
|
+
def _start_device_collector_thread(self) -> None:
|
|
431
|
+
"""Start device buffer collector thread."""
|
|
432
|
+
if self.collector_thread and self.collector_thread.is_alive():
|
|
433
|
+
self.logger.debug("Collector thread already running")
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# Start background thread to send storm telegrams
|
|
437
|
+
self.collector_thread = threading.Thread(
|
|
438
|
+
target=self._device_collector_thread, daemon=True, name="DeviceCollector"
|
|
439
|
+
)
|
|
440
|
+
self.collector_thread.start()
|
|
441
|
+
self.logger.info("Collector thread started")
|
|
442
|
+
|
|
443
|
+
def _stop_device_collector_thread(self) -> None:
|
|
444
|
+
"""Stop device buffer collector thread."""
|
|
445
|
+
if not self.collector_thread or not self.collector_thread.is_alive():
|
|
446
|
+
self.logger.debug("Collector thread not running")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
self.logger.info(f"Stopping collector thread: {self.collector_thread.name}")
|
|
450
|
+
|
|
451
|
+
# Wait for thread to finish (with timeout)
|
|
452
|
+
if self.collector_thread and self.collector_thread.is_alive():
|
|
453
|
+
self.collector_thread.join(timeout=1.0)
|
|
454
|
+
|
|
455
|
+
self.logger.info("Collector stopped.")
|
|
456
|
+
|
|
457
|
+
def _device_collector_thread(self) -> None:
|
|
458
|
+
"""Device buffer collector thread."""
|
|
459
|
+
self.logger.info("Collector thread starting")
|
|
460
|
+
|
|
461
|
+
while True:
|
|
462
|
+
self.logger.debug(
|
|
463
|
+
f"Collector thread collecting ({len(self.collector_buffer)})"
|
|
464
|
+
)
|
|
465
|
+
collected = 0
|
|
466
|
+
for device_service in self.device_services.values():
|
|
467
|
+
telegram_buffer = device_service.collect_telegram_buffer()
|
|
468
|
+
self.collector_buffer.extend(telegram_buffer)
|
|
469
|
+
collected += len(telegram_buffer)
|
|
470
|
+
|
|
471
|
+
# Wait a bit before checking again
|
|
472
|
+
self.logger.debug(f"Collector thread collected ({collected})")
|
|
473
|
+
self.collector_stop_event.wait(timeout=1)
|
|
@@ -5,6 +5,8 @@ including response generation and device configuration handling for
|
|
|
5
5
|
3-channel light dimmer modules.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import socket
|
|
9
|
+
import threading
|
|
8
10
|
from typing import Dict, Optional
|
|
9
11
|
|
|
10
12
|
from xp.models import ModuleTypeCode
|
|
@@ -72,6 +74,17 @@ class XP33ServerService(BaseServerService):
|
|
|
72
74
|
4: [0, 0, 0], # Scene 4: Off
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
# Storm mode state (XP33 Storm Simulator)
|
|
78
|
+
self.storm_mode = False # Track if device is in storm mode
|
|
79
|
+
self.last_response: Optional[str] = None # Cache last response for storm replay
|
|
80
|
+
self.storm_thread: Optional[threading.Thread] = (
|
|
81
|
+
None # Background thread for storm
|
|
82
|
+
)
|
|
83
|
+
self.storm_stop_event = threading.Event() # Event to stop storm thread
|
|
84
|
+
self.client_sockets: set[socket.socket] = set() # All active client sockets
|
|
85
|
+
self.client_sockets_lock = threading.Lock() # Lock for socket set
|
|
86
|
+
self.storm_packets_sent = 0 # Counter for packets sent during storm
|
|
87
|
+
|
|
75
88
|
def _handle_device_specific_action_request(
|
|
76
89
|
self, request: SystemTelegram
|
|
77
90
|
) -> Optional[str]:
|
|
@@ -160,11 +173,32 @@ class XP33ServerService(BaseServerService):
|
|
|
160
173
|
def _handle_device_specific_data_request(
|
|
161
174
|
self, request: SystemTelegram
|
|
162
175
|
) -> Optional[str]:
|
|
163
|
-
"""Handle XP33-specific data requests."""
|
|
176
|
+
"""Handle XP33-specific data requests with storm mode support."""
|
|
164
177
|
if not request.datapoint_type:
|
|
178
|
+
# Check for D99 storm trigger (not in DataPointType enum)
|
|
179
|
+
if request.data and request.data.startswith("99"):
|
|
180
|
+
return self._trigger_storm_mode()
|
|
165
181
|
return None
|
|
166
182
|
|
|
167
183
|
datapoint_type = request.datapoint_type
|
|
184
|
+
|
|
185
|
+
# Storm mode handling
|
|
186
|
+
if datapoint_type == DataPointType.MODULE_ERROR_CODE:
|
|
187
|
+
if self.storm_mode:
|
|
188
|
+
# MODULE_ERROR_CODE query stops storm
|
|
189
|
+
return self._exit_storm_mode()
|
|
190
|
+
else:
|
|
191
|
+
# Normal operation - return error code 00
|
|
192
|
+
return self._build_error_code_response("00")
|
|
193
|
+
|
|
194
|
+
# If in storm mode and not MODULE_ERROR_CODE query, ignore (background thread is sending)
|
|
195
|
+
if self.storm_mode:
|
|
196
|
+
self.logger.debug(
|
|
197
|
+
f"Ignoring query during storm mode for device {self.serial_number}"
|
|
198
|
+
)
|
|
199
|
+
return None # Background thread is sending storm telegrams
|
|
200
|
+
|
|
201
|
+
# Normal data request handling
|
|
168
202
|
handler = {
|
|
169
203
|
DataPointType.MODULE_OUTPUT_STATE: self._handle_read_module_output_state,
|
|
170
204
|
DataPointType.MODULE_STATE: self._handle_read_module_state,
|
|
@@ -183,6 +217,9 @@ class XP33ServerService(BaseServerService):
|
|
|
183
217
|
)
|
|
184
218
|
telegram = self._build_response_telegram(data_part)
|
|
185
219
|
|
|
220
|
+
# Cache response for potential storm replay
|
|
221
|
+
self.last_response = telegram
|
|
222
|
+
|
|
186
223
|
self.logger.debug(
|
|
187
224
|
f"Generated {self.device_type} module type response: {telegram}"
|
|
188
225
|
)
|
|
@@ -230,6 +267,157 @@ class XP33ServerService(BaseServerService):
|
|
|
230
267
|
]
|
|
231
268
|
return ",".join(levels)
|
|
232
269
|
|
|
270
|
+
def _trigger_storm_mode(self) -> Optional[str]:
|
|
271
|
+
"""Trigger storm mode via D99 query.
|
|
272
|
+
|
|
273
|
+
Starts a background thread that sends 2 packets per second.
|
|
274
|
+
If storm is already active, this is a no-op.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
None (no response - storm mode activated).
|
|
278
|
+
"""
|
|
279
|
+
# If storm already active, just log and continue
|
|
280
|
+
if self.storm_mode and self.storm_thread and self.storm_thread.is_alive():
|
|
281
|
+
self.logger.debug(
|
|
282
|
+
f"Storm already active for device {self.serial_number}, "
|
|
283
|
+
f"sent {self.storm_packets_sent}/200 packets"
|
|
284
|
+
)
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
if not self.last_response:
|
|
288
|
+
self.logger.warning(
|
|
289
|
+
f"Cannot trigger storm for device {self.serial_number}: "
|
|
290
|
+
f"no cached response"
|
|
291
|
+
)
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
self.storm_mode = True
|
|
295
|
+
self.storm_packets_sent = 0
|
|
296
|
+
self.storm_stop_event.clear()
|
|
297
|
+
|
|
298
|
+
# Start background thread to send storm telegrams
|
|
299
|
+
self.storm_thread = threading.Thread(
|
|
300
|
+
target=self._storm_sender_thread,
|
|
301
|
+
daemon=True,
|
|
302
|
+
name=f"Storm-{self.serial_number}",
|
|
303
|
+
)
|
|
304
|
+
self.storm_thread.start()
|
|
305
|
+
|
|
306
|
+
self.logger.info(
|
|
307
|
+
f"Storm triggered via D99 query for device {self.serial_number}"
|
|
308
|
+
)
|
|
309
|
+
return None # No response when entering storm mode
|
|
310
|
+
|
|
311
|
+
def _exit_storm_mode(self) -> str:
|
|
312
|
+
"""Exit storm mode and return error code FE.
|
|
313
|
+
|
|
314
|
+
Stops the background storm thread and returns error code.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
MODULE_ERROR_CODE response with error code FE (buffer overflow).
|
|
318
|
+
"""
|
|
319
|
+
self.logger.info(
|
|
320
|
+
f"MODULE_ERROR_CODE query received, stopping storm for device {self.serial_number}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Signal the storm thread to stop
|
|
324
|
+
self.storm_stop_event.set()
|
|
325
|
+
self.storm_mode = False
|
|
326
|
+
|
|
327
|
+
# Wait for thread to finish (with timeout)
|
|
328
|
+
if self.storm_thread and self.storm_thread.is_alive():
|
|
329
|
+
self.storm_thread.join(timeout=1.0)
|
|
330
|
+
|
|
331
|
+
self.logger.info(
|
|
332
|
+
f"Storm stopped after {self.storm_packets_sent} packets for device {self.serial_number}"
|
|
333
|
+
)
|
|
334
|
+
self.logger.info(
|
|
335
|
+
f"Storm stopped, returning to normal operation for device {self.serial_number}"
|
|
336
|
+
)
|
|
337
|
+
return self._build_error_code_response("FE")
|
|
338
|
+
|
|
339
|
+
def _storm_sender_thread(self) -> None:
|
|
340
|
+
"""Background thread that sends storm telegrams continuously.
|
|
341
|
+
|
|
342
|
+
Sends 2 packets per second (500ms delay) until:
|
|
343
|
+
- 200 packets have been sent, or
|
|
344
|
+
- Storm mode is stopped via stop event
|
|
345
|
+
|
|
346
|
+
The storm persists across socket disconnections. If the client disconnects
|
|
347
|
+
and reconnects, the storm will continue on the new connection.
|
|
348
|
+
"""
|
|
349
|
+
if not self.last_response:
|
|
350
|
+
self.logger.error(
|
|
351
|
+
f"Storm thread started but missing cached response for {self.serial_number}"
|
|
352
|
+
)
|
|
353
|
+
self.storm_mode = False
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
self.logger.info(
|
|
357
|
+
f"Storm thread started, sending 200 duplicate telegrams at 2 packets/sec for device {self.serial_number}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Type narrowing for mypy
|
|
361
|
+
cached_response: str = self.last_response
|
|
362
|
+
max_packets = 200
|
|
363
|
+
packets_per_second = 2
|
|
364
|
+
delay_between_packets = 1.0 / packets_per_second # 0.5 seconds
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
while (
|
|
368
|
+
self.storm_packets_sent < max_packets
|
|
369
|
+
and not self.storm_stop_event.is_set()
|
|
370
|
+
):
|
|
371
|
+
# Wait for a valid socket (client may have disconnected and reconnected)
|
|
372
|
+
self.add_telegram_buffer(cached_response)
|
|
373
|
+
self.storm_packets_sent += 1
|
|
374
|
+
self.logger.debug(
|
|
375
|
+
f"Storm packet {self.storm_packets_sent}/{max_packets} sent for {self.serial_number}"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Wait before sending next packet (0.5 seconds for 2 packets/sec)
|
|
379
|
+
if self.storm_packets_sent < max_packets:
|
|
380
|
+
self.storm_stop_event.wait(timeout=delay_between_packets)
|
|
381
|
+
|
|
382
|
+
# Log completion status
|
|
383
|
+
if self.storm_packets_sent >= max_packets:
|
|
384
|
+
self.logger.info(
|
|
385
|
+
f"Storm completed: sent all {self.storm_packets_sent} packets for {self.serial_number}"
|
|
386
|
+
)
|
|
387
|
+
elif self.storm_stop_event.is_set():
|
|
388
|
+
self.logger.info(
|
|
389
|
+
f"Storm stopped by error code query: sent {self.storm_packets_sent} packets for {self.serial_number}"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Clean up storm mode
|
|
393
|
+
self.storm_mode = False
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
self.logger.error(
|
|
397
|
+
f"Unexpected error in storm thread for {self.serial_number}: {e}"
|
|
398
|
+
)
|
|
399
|
+
self.storm_mode = False
|
|
400
|
+
|
|
401
|
+
def _build_error_code_response(self, error_code: str) -> str:
|
|
402
|
+
"""Build MODULE_ERROR_CODE response telegram.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
error_code: Error code (00 = normal, FE = buffer overflow).
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
The complete MODULE_ERROR_CODE response telegram.
|
|
409
|
+
"""
|
|
410
|
+
data_part = (
|
|
411
|
+
f"R{self.serial_number}"
|
|
412
|
+
f"F02D{DataPointType.MODULE_ERROR_CODE.value}"
|
|
413
|
+
f"{error_code}"
|
|
414
|
+
)
|
|
415
|
+
telegram = self._build_response_telegram(data_part)
|
|
416
|
+
self.logger.debug(
|
|
417
|
+
f"Generated {self.device_type} error code response: {telegram}"
|
|
418
|
+
)
|
|
419
|
+
return telegram
|
|
420
|
+
|
|
233
421
|
def set_channel_dimming(self, channel: int, level: int) -> bool:
|
|
234
422
|
"""Set individual channel dimming level.
|
|
235
423
|
|