conson-xp 1.3.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.3.0 → conson_xp-1.5.0}/PKG-INFO +1 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/pyproject.toml +1 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/__init__.py +1 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_discover.py +19 -3
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_telegram.py +4 -4
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_discover_service.py +120 -2
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_scan_service.py +1 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/telegram_protocol.py +4 -4
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/base_server_service.py +38 -4
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/cp20_server_service.py +2 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/server_service.py +162 -10
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp130_server_service.py +2 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp20_server_service.py +2 -1
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp230_server_service.py +2 -1
- conson_xp-1.5.0/src/xp/services/server/xp24_server_service.py +192 -0
- conson_xp-1.5.0/src/xp/services/server/xp33_server_service.py +494 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_discover.py +28 -2
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_base_server_service.py +4 -3
- conson_xp-1.5.0/tests/unit/test_services/test_xp_server_services.py +360 -0
- conson_xp-1.3.0/src/xp/services/server/xp24_server_service.py +0 -119
- conson_xp-1.3.0/src/xp/services/server/xp33_server_service.py +0 -176
- conson_xp-1.3.0/tests/unit/test_services/test_xp_server_services.py +0 -189
- {conson_xp-1.3.0 → conson_xp-1.5.0}/LICENSE +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/README.md +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/__main__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/file_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/module_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/server_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/main.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/click_tree.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/decorators.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/error_handlers.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/formatters.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/serial_number_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/system_function_choice.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/xp_module_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/connection/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/connection/exceptions.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/actiontable.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_blink.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_custom.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_output.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_raw.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_receive.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_config.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/log_entry.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/protocol/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/response.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/action_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/datapoint_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_action_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type_code.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/output_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/reply_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_function.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/timeparam_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/write_config_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp20_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/write_config_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/log_file_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/module_type_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/protocol_factory.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/reverse_proxy_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/checksum.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/dependencies.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/event_helper.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/serialization.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/time_utils.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/.coverage +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/conftest.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/.coverage +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/telegram_test_data.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_actiontable_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_api/.coverage +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_api/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_blink_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_checksum_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_blink_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_raw_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_receive_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_discovery_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_event_telegram_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_homekit_config_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_link_number_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_module_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_output_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_version_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_api/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_click_tree.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_decorators.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_formatters.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_connection_init.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_exceptions.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_encoding/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_event_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_log_entry.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_module_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_version_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_write_config_type.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_blink_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_checksum_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_discovery_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_services.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_log_file_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_module_type_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_protocol.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_server_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_version_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/__init__.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_checksum.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_event_helper.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_serialization.py +0 -0
- {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_time_utils.py +0 -0
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import Any, Dict, Optional
|
|
5
|
+
from typing import Any, Dict, Optional, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DiscoveredDevice(TypedDict):
|
|
9
|
+
"""Discovered device information.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
serial_number: Serial number of the device.
|
|
13
|
+
module_type: Module type name (e.g., "XP24", "XP230"), None if not yet retrieved.
|
|
14
|
+
module_type_code: Module type code (e.g., "13", "10"), None if not yet retrieved.
|
|
15
|
+
module_type_name: Module type name converted from module_type_code, None if not yet retrieved.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
serial_number: str
|
|
19
|
+
module_type: Optional[str]
|
|
20
|
+
module_type_code: Optional[int]
|
|
21
|
+
module_type_name: Optional[str]
|
|
6
22
|
|
|
7
23
|
|
|
8
24
|
@dataclass
|
|
@@ -13,7 +29,7 @@ class ConbusDiscoverResponse:
|
|
|
13
29
|
success: Whether the operation was successful.
|
|
14
30
|
sent_telegram: Telegram sent to discover devices.
|
|
15
31
|
received_telegrams: List of telegrams received.
|
|
16
|
-
discovered_devices: List of discovered
|
|
32
|
+
discovered_devices: List of discovered devices with their module types.
|
|
17
33
|
error: Error message if operation failed.
|
|
18
34
|
timestamp: Timestamp of the response.
|
|
19
35
|
"""
|
|
@@ -21,7 +37,7 @@ class ConbusDiscoverResponse:
|
|
|
21
37
|
success: bool
|
|
22
38
|
sent_telegram: Optional[str] = None
|
|
23
39
|
received_telegrams: Optional[list[str]] = None
|
|
24
|
-
discovered_devices: Optional[list[
|
|
40
|
+
discovered_devices: Optional[list[DiscoveredDevice]] = None
|
|
25
41
|
error: Optional[str] = None
|
|
26
42
|
timestamp: Optional[datetime] = None
|
|
27
43
|
|
|
@@ -22,10 +22,10 @@ class SystemTelegram(Telegram):
|
|
|
22
22
|
Examples: <S0020012521F02D18FN>
|
|
23
23
|
|
|
24
24
|
Attributes:
|
|
25
|
-
serial_number: Serial number of the device
|
|
26
|
-
system_function: System function code.
|
|
27
|
-
data: Data payload
|
|
28
|
-
datapoint_type: Type of datapoint.
|
|
25
|
+
serial_number: Serial number of the device (0020012521)
|
|
26
|
+
system_function: System function code (02).
|
|
27
|
+
data: Data payload (18)
|
|
28
|
+
datapoint_type: Type of datapoint (18).
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
serial_number: str = ""
|
|
@@ -10,7 +10,10 @@ from typing import Callable, Optional
|
|
|
10
10
|
from twisted.internet.posixbase import PosixReactorBase
|
|
11
11
|
|
|
12
12
|
from xp.models import ConbusClientConfig, ConbusDiscoverResponse
|
|
13
|
+
from xp.models.conbus.conbus_discover import DiscoveredDevice
|
|
13
14
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
15
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
16
|
+
from xp.models.telegram.module_type_code import MODULE_TYPE_REGISTRY
|
|
14
17
|
from xp.models.telegram.system_function import SystemFunction
|
|
15
18
|
from xp.models.telegram.telegram_type import TelegramType
|
|
16
19
|
from xp.services.protocol.conbus_protocol import ConbusProtocol
|
|
@@ -73,6 +76,7 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
73
76
|
self.discovered_device_result.received_telegrams = []
|
|
74
77
|
self.discovered_device_result.received_telegrams.append(telegram_received.frame)
|
|
75
78
|
|
|
79
|
+
# Check for discovery response
|
|
76
80
|
if (
|
|
77
81
|
telegram_received.checksum_valid
|
|
78
82
|
and telegram_received.telegram_type == TelegramType.REPLY.value
|
|
@@ -80,8 +84,30 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
80
84
|
and len(telegram_received.payload) == 15
|
|
81
85
|
):
|
|
82
86
|
self.discovered_device(telegram_received.serial_number)
|
|
87
|
+
|
|
88
|
+
# Check for module type response (F02D07)
|
|
89
|
+
elif (
|
|
90
|
+
telegram_received.checksum_valid
|
|
91
|
+
and telegram_received.telegram_type == TelegramType.REPLY.value
|
|
92
|
+
and telegram_received.payload[11:17] == "F02D07"
|
|
93
|
+
and len(telegram_received.payload) >= 19
|
|
94
|
+
):
|
|
95
|
+
self.handle_module_type_code_response(
|
|
96
|
+
telegram_received.serial_number, telegram_received.payload[17:19]
|
|
97
|
+
)
|
|
98
|
+
# Check for module type response (F02D00)
|
|
99
|
+
elif (
|
|
100
|
+
telegram_received.checksum_valid
|
|
101
|
+
and telegram_received.telegram_type == TelegramType.REPLY.value
|
|
102
|
+
and telegram_received.payload[11:17] == "F02D00"
|
|
103
|
+
and len(telegram_received.payload) >= 19
|
|
104
|
+
):
|
|
105
|
+
self.handle_module_type_response(
|
|
106
|
+
telegram_received.serial_number, telegram_received.payload[17:19]
|
|
107
|
+
)
|
|
108
|
+
|
|
83
109
|
else:
|
|
84
|
-
self.logger.debug("Not a discover response")
|
|
110
|
+
self.logger.debug("Not a discover or module type response")
|
|
85
111
|
|
|
86
112
|
def discovered_device(self, serial_number: str) -> None:
|
|
87
113
|
"""Handle discovered device event.
|
|
@@ -92,10 +118,102 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
92
118
|
self.logger.info("discovered_device: %s", serial_number)
|
|
93
119
|
if not self.discovered_device_result.discovered_devices:
|
|
94
120
|
self.discovered_device_result.discovered_devices = []
|
|
95
|
-
|
|
121
|
+
|
|
122
|
+
# Add device with module_type as None initially
|
|
123
|
+
device: DiscoveredDevice = {
|
|
124
|
+
"serial_number": serial_number,
|
|
125
|
+
"module_type": None,
|
|
126
|
+
"module_type_code": None,
|
|
127
|
+
"module_type_name": None,
|
|
128
|
+
}
|
|
129
|
+
self.discovered_device_result.discovered_devices.append(device)
|
|
130
|
+
|
|
131
|
+
# Send READ_DATAPOINT telegram to query module type
|
|
132
|
+
self.logger.debug(f"Sending module type query for {serial_number}")
|
|
133
|
+
self.send_telegram(
|
|
134
|
+
telegram_type=TelegramType.SYSTEM,
|
|
135
|
+
serial_number=serial_number,
|
|
136
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
137
|
+
data_value=DataPointType.MODULE_TYPE.value,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self.send_telegram(
|
|
141
|
+
telegram_type=TelegramType.SYSTEM,
|
|
142
|
+
serial_number=serial_number,
|
|
143
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
144
|
+
data_value=DataPointType.MODULE_TYPE_CODE.value,
|
|
145
|
+
)
|
|
146
|
+
|
|
96
147
|
if self.progress_callback:
|
|
97
148
|
self.progress_callback(serial_number)
|
|
98
149
|
|
|
150
|
+
def handle_module_type_code_response(
|
|
151
|
+
self, serial_number: str, module_type_code: str
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Handle module type code response and update discovered device.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
serial_number: Serial number of the device.
|
|
157
|
+
module_type_code: Module type code from telegram (e.g., "07", "24").
|
|
158
|
+
"""
|
|
159
|
+
self.logger.info(
|
|
160
|
+
f"Received module type code {module_type_code} for {serial_number}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Convert module type code to name
|
|
164
|
+
code = 0
|
|
165
|
+
try:
|
|
166
|
+
# The telegram format uses decimal values represented as strings
|
|
167
|
+
code = int(module_type_code)
|
|
168
|
+
module_info = MODULE_TYPE_REGISTRY.get(code)
|
|
169
|
+
|
|
170
|
+
if module_info:
|
|
171
|
+
module_type_name = module_info["name"]
|
|
172
|
+
self.logger.debug(
|
|
173
|
+
f"Module type code {module_type_code} ({code}) = {module_type_name}"
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
module_type_name = f"UNKNOWN_{module_type_code}"
|
|
177
|
+
self.logger.warning(
|
|
178
|
+
f"Unknown module type code {module_type_code} ({code})"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
except ValueError:
|
|
182
|
+
self.logger.error(
|
|
183
|
+
f"Invalid module type code format: {module_type_code} for {serial_number}"
|
|
184
|
+
)
|
|
185
|
+
module_type_name = f"INVALID_{module_type_code}"
|
|
186
|
+
|
|
187
|
+
# Find and update the device in discovered_devices
|
|
188
|
+
if self.discovered_device_result.discovered_devices:
|
|
189
|
+
for device in self.discovered_device_result.discovered_devices:
|
|
190
|
+
if device["serial_number"] == serial_number:
|
|
191
|
+
device["module_type_code"] = code
|
|
192
|
+
device["module_type_name"] = module_type_name
|
|
193
|
+
self.logger.debug(
|
|
194
|
+
f"Updated device {serial_number} with module_type {module_type_name}"
|
|
195
|
+
)
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
def handle_module_type_response(self, serial_number: str, module_type: str) -> None:
|
|
199
|
+
"""Handle module type response and update discovered device.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
serial_number: Serial number of the device.
|
|
203
|
+
module_type: Module type code from telegram (e.g., "XP33", "XP24").
|
|
204
|
+
"""
|
|
205
|
+
self.logger.info(f"Received module type {module_type} for {serial_number}")
|
|
206
|
+
|
|
207
|
+
# Find and update the device in discovered_devices
|
|
208
|
+
if self.discovered_device_result.discovered_devices:
|
|
209
|
+
for device in self.discovered_device_result.discovered_devices:
|
|
210
|
+
if device["serial_number"] == serial_number:
|
|
211
|
+
device["module_type"] = module_type
|
|
212
|
+
self.logger.debug(
|
|
213
|
+
f"Updated device {serial_number} with module_type {module_type}"
|
|
214
|
+
)
|
|
215
|
+
break
|
|
216
|
+
|
|
99
217
|
def timeout(self) -> bool:
|
|
100
218
|
"""Handle timeout event to stop discovery.
|
|
101
219
|
|
|
@@ -128,7 +128,7 @@ class ConbusScanService(ConbusProtocol):
|
|
|
128
128
|
function_code: str,
|
|
129
129
|
progress_callback: Callable[[str], None],
|
|
130
130
|
finish_callback: Callable[[ConbusResponse], None],
|
|
131
|
-
timeout_seconds:
|
|
131
|
+
timeout_seconds: float = 0.25,
|
|
132
132
|
) -> None:
|
|
133
133
|
"""Scan a module for all datapoints by function code.
|
|
134
134
|
|
|
@@ -124,7 +124,7 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
124
124
|
payload = telegram[:-2] # S0123450001F02D12
|
|
125
125
|
checksum = telegram[-2:].decode() # FK
|
|
126
126
|
serial_number = (
|
|
127
|
-
telegram[1:11] if telegram_type in "S" else b""
|
|
127
|
+
telegram[1:11] if telegram_type in ("S", "R") else b""
|
|
128
128
|
) # 0123450001
|
|
129
129
|
calculated_checksum = calculate_checksum(payload.decode(encoding="latin-1"))
|
|
130
130
|
|
|
@@ -151,9 +151,9 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
151
151
|
await self.event_bus.dispatch(
|
|
152
152
|
TelegramReceivedEvent(
|
|
153
153
|
protocol=self,
|
|
154
|
-
frame=frame.decode(),
|
|
155
|
-
telegram=telegram.decode(),
|
|
156
|
-
payload=payload.decode(),
|
|
154
|
+
frame=frame.decode("latin-1"),
|
|
155
|
+
telegram=telegram.decode("latin-1"),
|
|
156
|
+
payload=payload.decode("latin-1"),
|
|
157
157
|
telegram_type=telegram_type,
|
|
158
158
|
serial_number=serial_number,
|
|
159
159
|
checksum=checksum,
|
|
@@ -5,9 +5,11 @@ 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
|
|
|
12
|
+
from xp.models import ModuleTypeCode
|
|
11
13
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
12
14
|
from xp.models.telegram.system_function import SystemFunction
|
|
13
15
|
from xp.models.telegram.system_telegram import SystemTelegram
|
|
@@ -33,7 +35,7 @@ class BaseServerService(ABC):
|
|
|
33
35
|
|
|
34
36
|
# Must be set by subclasses
|
|
35
37
|
self.device_type: str = ""
|
|
36
|
-
self.module_type_code:
|
|
38
|
+
self.module_type_code: ModuleTypeCode = ModuleTypeCode.NOMOD
|
|
37
39
|
self.hardware_version: str = ""
|
|
38
40
|
self.software_version: str = ""
|
|
39
41
|
self.device_status: str = "OK"
|
|
@@ -41,6 +43,9 @@ class BaseServerService(ABC):
|
|
|
41
43
|
self.temperature: str = "+23,5§C"
|
|
42
44
|
self.voltage: str = "+12,5§V"
|
|
43
45
|
|
|
46
|
+
self.telegram_buffer: list[str] = []
|
|
47
|
+
self.telegram_buffer_lock = threading.Lock() # Lock for socket set
|
|
48
|
+
|
|
44
49
|
def generate_datapoint_type_response(
|
|
45
50
|
self, datapoint_type: DataPointType
|
|
46
51
|
) -> Optional[str]:
|
|
@@ -54,11 +59,11 @@ class BaseServerService(ABC):
|
|
|
54
59
|
"""
|
|
55
60
|
datapoint_values = {
|
|
56
61
|
DataPointType.TEMPERATURE: self.temperature,
|
|
57
|
-
DataPointType.MODULE_TYPE_CODE: f"{self.module_type_code:
|
|
62
|
+
DataPointType.MODULE_TYPE_CODE: f"{self.module_type_code.value:02}",
|
|
58
63
|
DataPointType.SW_VERSION: self.software_version,
|
|
59
64
|
DataPointType.MODULE_STATE: self.device_status,
|
|
60
65
|
DataPointType.MODULE_TYPE: self.device_type,
|
|
61
|
-
DataPointType.LINK_NUMBER: f"{self.link_number:
|
|
66
|
+
DataPointType.LINK_NUMBER: f"{self.link_number:02}",
|
|
62
67
|
DataPointType.VOLTAGE: self.voltage,
|
|
63
68
|
DataPointType.HW_VERSION: self.hardware_version,
|
|
64
69
|
DataPointType.MODULE_ERROR_CODE: "00",
|
|
@@ -187,11 +192,15 @@ class BaseServerService(ABC):
|
|
|
187
192
|
self.logger.debug(
|
|
188
193
|
f"_handle_return_data_request {self.device_type} request: {request}"
|
|
189
194
|
)
|
|
195
|
+
module_specific = self._handle_device_specific_data_request(request)
|
|
196
|
+
if module_specific:
|
|
197
|
+
return module_specific
|
|
198
|
+
|
|
190
199
|
if request.datapoint_type:
|
|
191
200
|
return self.generate_datapoint_type_response(request.datapoint_type)
|
|
192
201
|
|
|
193
202
|
# Allow device-specific handlers
|
|
194
|
-
return
|
|
203
|
+
return None
|
|
195
204
|
|
|
196
205
|
def _handle_device_specific_data_request(
|
|
197
206
|
self, request: SystemTelegram
|
|
@@ -252,3 +261,28 @@ class BaseServerService(ABC):
|
|
|
252
261
|
The response telegram string, or None if request cannot be handled.
|
|
253
262
|
"""
|
|
254
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
|
|
@@ -6,6 +6,7 @@ including response generation and device configuration handling.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict, Optional
|
|
8
8
|
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
9
10
|
from xp.models.telegram.system_telegram import SystemTelegram
|
|
10
11
|
from xp.services.server.base_server_service import BaseServerService
|
|
11
12
|
|
|
@@ -32,7 +33,7 @@ class CP20ServerService(BaseServerService):
|
|
|
32
33
|
"""
|
|
33
34
|
super().__init__(serial_number)
|
|
34
35
|
self.device_type = "CP20"
|
|
35
|
-
self.module_type_code =
|
|
36
|
+
self.module_type_code = ModuleTypeCode.CP20 # CP20 module type from registry
|
|
36
37
|
self.firmware_version = "CP20_V0.01.05"
|
|
37
38
|
|
|
38
39
|
def _handle_device_specific_data_request(
|
|
@@ -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:
|
|
@@ -253,15 +289,86 @@ class ServerService:
|
|
|
253
289
|
self.logger.error(f"Error closing client socket: {e}")
|
|
254
290
|
|
|
255
291
|
def _process_request(self, message: str) -> List[str]:
|
|
256
|
-
"""Process incoming request and generate responses.
|
|
292
|
+
"""Process incoming request and generate responses.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
message: Message potentially containing multiple telegrams in format <TELEGRAM><TELEGRAM2>...
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
List of responses for all processed telegrams.
|
|
299
|
+
"""
|
|
300
|
+
responses: list[str] = []
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
# Split message into individual telegrams (enclosed in angle brackets)
|
|
304
|
+
telegrams = self._split_telegrams(message)
|
|
305
|
+
|
|
306
|
+
if not telegrams:
|
|
307
|
+
self.logger.warning(f"No valid telegrams found in message: {message}")
|
|
308
|
+
return responses
|
|
309
|
+
|
|
310
|
+
# Process each telegram
|
|
311
|
+
for telegram in telegrams:
|
|
312
|
+
telegram_responses = self._process_single_telegram(telegram)
|
|
313
|
+
responses.extend(telegram_responses)
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
self.logger.error(f"Error processing request: {e}")
|
|
317
|
+
|
|
318
|
+
return responses
|
|
319
|
+
|
|
320
|
+
def _split_telegrams(self, message: str) -> List[str]:
|
|
321
|
+
"""Split message into individual telegrams.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
message: Raw message containing one or more telegrams in format <TELEGRAM><TELEGRAM2>...
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List of individual telegram strings including angle brackets.
|
|
328
|
+
"""
|
|
329
|
+
telegrams = []
|
|
330
|
+
start = 0
|
|
331
|
+
|
|
332
|
+
while True:
|
|
333
|
+
# Find the start of a telegram
|
|
334
|
+
start_idx = message.find("<", start)
|
|
335
|
+
if start_idx == -1:
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
# Find the end of the telegram
|
|
339
|
+
end_idx = message.find(">", start_idx)
|
|
340
|
+
if end_idx == -1:
|
|
341
|
+
self.logger.warning(
|
|
342
|
+
f"Incomplete telegram found starting at position {start_idx}"
|
|
343
|
+
)
|
|
344
|
+
break
|
|
345
|
+
|
|
346
|
+
# Extract telegram including angle brackets
|
|
347
|
+
telegram = message[start_idx : end_idx + 1]
|
|
348
|
+
telegrams.append(telegram)
|
|
349
|
+
|
|
350
|
+
# Move to the next position
|
|
351
|
+
start = end_idx + 1
|
|
352
|
+
|
|
353
|
+
return telegrams
|
|
354
|
+
|
|
355
|
+
def _process_single_telegram(self, telegram: str) -> List[str]:
|
|
356
|
+
"""Process a single telegram and generate responses.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
telegram: A single telegram string.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
List of response strings for this telegram.
|
|
363
|
+
"""
|
|
257
364
|
responses: list[str] = []
|
|
258
365
|
|
|
259
366
|
try:
|
|
260
367
|
# Parse the telegram
|
|
261
|
-
parsed_telegram = self.telegram_service.parse_system_telegram(
|
|
368
|
+
parsed_telegram = self.telegram_service.parse_system_telegram(telegram)
|
|
262
369
|
|
|
263
370
|
if not parsed_telegram:
|
|
264
|
-
self.logger.warning(f"Failed to parse telegram: {
|
|
371
|
+
self.logger.warning(f"Failed to parse telegram: {telegram}")
|
|
265
372
|
return responses
|
|
266
373
|
|
|
267
374
|
# Handle discover requests
|
|
@@ -296,7 +403,7 @@ class ServerService:
|
|
|
296
403
|
)
|
|
297
404
|
|
|
298
405
|
except Exception as e:
|
|
299
|
-
self.logger.error(f"Error processing
|
|
406
|
+
self.logger.error(f"Error processing telegram: {e}")
|
|
300
407
|
|
|
301
408
|
return responses
|
|
302
409
|
|
|
@@ -319,3 +426,48 @@ class ServerService:
|
|
|
319
426
|
self.logger.info(
|
|
320
427
|
f"Configuration reloaded: {len(self.devices)} devices, {len(self.device_services)} services"
|
|
321
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)
|
|
@@ -7,6 +7,7 @@ XP130 is an Ethernet/TCPIP interface module.
|
|
|
7
7
|
|
|
8
8
|
from typing import Dict
|
|
9
9
|
|
|
10
|
+
from xp.models import ModuleTypeCode
|
|
10
11
|
from xp.services.server.base_server_service import BaseServerService
|
|
11
12
|
|
|
12
13
|
|
|
@@ -32,7 +33,7 @@ class XP130ServerService(BaseServerService):
|
|
|
32
33
|
"""
|
|
33
34
|
super().__init__(serial_number)
|
|
34
35
|
self.device_type = "XP130"
|
|
35
|
-
self.module_type_code =
|
|
36
|
+
self.module_type_code = ModuleTypeCode.XP130 # XP130 module type from registry
|
|
36
37
|
self.firmware_version = "XP130_V1.02.15"
|
|
37
38
|
|
|
38
39
|
# XP130-specific network configuration
|
|
@@ -6,6 +6,7 @@ including response generation and device configuration handling.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict
|
|
8
8
|
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
9
10
|
from xp.services.server.base_server_service import BaseServerService
|
|
10
11
|
|
|
11
12
|
|
|
@@ -31,7 +32,7 @@ class XP20ServerService(BaseServerService):
|
|
|
31
32
|
"""
|
|
32
33
|
super().__init__(serial_number)
|
|
33
34
|
self.device_type = "XP20"
|
|
34
|
-
self.module_type_code =
|
|
35
|
+
self.module_type_code = ModuleTypeCode.XP20 # XP20 module type from registry
|
|
35
36
|
self.firmware_version = "XP20_V0.01.05"
|
|
36
37
|
|
|
37
38
|
def get_device_info(self) -> Dict:
|
|
@@ -6,6 +6,7 @@ including response generation and device configuration handling.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict
|
|
8
8
|
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
9
10
|
from xp.services.server.base_server_service import BaseServerService
|
|
10
11
|
|
|
11
12
|
|
|
@@ -31,7 +32,7 @@ class XP230ServerService(BaseServerService):
|
|
|
31
32
|
"""
|
|
32
33
|
super().__init__(serial_number)
|
|
33
34
|
self.device_type = "XP230"
|
|
34
|
-
self.module_type_code =
|
|
35
|
+
self.module_type_code = ModuleTypeCode.XP230 # XP230 module type from registry
|
|
35
36
|
self.firmware_version = "XP230_V1.00.04"
|
|
36
37
|
|
|
37
38
|
def get_device_info(self) -> Dict:
|