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.
Files changed (257) hide show
  1. {conson_xp-1.3.0 → conson_xp-1.5.0}/PKG-INFO +1 -1
  2. {conson_xp-1.3.0 → conson_xp-1.5.0}/pyproject.toml +1 -1
  3. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/__init__.py +1 -1
  4. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_discover.py +19 -3
  5. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_telegram.py +4 -4
  6. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_discover_service.py +120 -2
  7. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_scan_service.py +1 -1
  8. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/telegram_protocol.py +4 -4
  9. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/base_server_service.py +38 -4
  10. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/cp20_server_service.py +2 -1
  11. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/server_service.py +162 -10
  12. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp130_server_service.py +2 -1
  13. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp20_server_service.py +2 -1
  14. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/xp230_server_service.py +2 -1
  15. conson_xp-1.5.0/src/xp/services/server/xp24_server_service.py +192 -0
  16. conson_xp-1.5.0/src/xp/services/server/xp33_server_service.py +494 -0
  17. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_discover.py +28 -2
  18. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_base_server_service.py +4 -3
  19. conson_xp-1.5.0/tests/unit/test_services/test_xp_server_services.py +360 -0
  20. conson_xp-1.3.0/src/xp/services/server/xp24_server_service.py +0 -119
  21. conson_xp-1.3.0/src/xp/services/server/xp33_server_service.py +0 -176
  22. conson_xp-1.3.0/tests/unit/test_services/test_xp_server_services.py +0 -189
  23. {conson_xp-1.3.0 → conson_xp-1.5.0}/LICENSE +0 -0
  24. {conson_xp-1.3.0 → conson_xp-1.5.0}/README.md +0 -0
  25. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/__init__.py +0 -0
  26. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/__main__.py +0 -0
  27. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/__init__.py +0 -0
  28. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
  29. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
  30. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
  31. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
  32. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
  33. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
  34. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
  35. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
  36. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
  37. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
  38. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
  39. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
  40. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
  41. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
  42. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
  43. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
  44. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/file_commands.py +0 -0
  45. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
  46. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
  47. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
  48. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/module_commands.py +0 -0
  49. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
  50. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/__init__.py +0 -0
  51. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/server/server_commands.py +0 -0
  52. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
  53. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
  54. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
  55. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
  56. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
  57. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
  58. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
  59. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
  60. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/main.py +0 -0
  61. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/__init__.py +0 -0
  62. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/click_tree.py +0 -0
  63. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
  64. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/decorators.py +0 -0
  65. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/error_handlers.py +0 -0
  66. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/formatters.py +0 -0
  67. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/serial_number_type.py +0 -0
  68. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/system_function_choice.py +0 -0
  69. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/cli/utils/xp_module_type.py +0 -0
  70. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/connection/__init__.py +0 -0
  71. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/connection/exceptions.py +0 -0
  72. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/__init__.py +0 -0
  73. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/__init__.py +0 -0
  74. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/actiontable.py +0 -0
  75. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
  76. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
  77. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
  78. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/__init__.py +0 -0
  79. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus.py +0 -0
  80. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
  81. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_blink.py +0 -0
  82. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
  83. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
  84. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_custom.py +0 -0
  85. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
  86. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
  87. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
  88. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_output.py +0 -0
  89. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_raw.py +0 -0
  90. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_receive.py +0 -0
  91. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
  92. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/__init__.py +0 -0
  93. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
  94. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_config.py +0 -0
  95. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/homekit/homekit_conson_config.py +0 -0
  96. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/log_entry.py +0 -0
  97. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/protocol/__init__.py +0 -0
  98. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
  99. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/response.py +0 -0
  100. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/__init__.py +0 -0
  101. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/action_type.py +0 -0
  102. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/datapoint_type.py +0 -0
  103. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_telegram.py +0 -0
  104. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/event_type.py +0 -0
  105. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_action_type.py +0 -0
  106. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/input_type.py +0 -0
  107. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type.py +0 -0
  108. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/module_type_code.py +0 -0
  109. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/output_telegram.py +0 -0
  110. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/reply_telegram.py +0 -0
  111. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/system_function.py +0 -0
  112. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram.py +0 -0
  113. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/telegram_type.py +0 -0
  114. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/telegram/timeparam_type.py +0 -0
  115. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/models/write_config_type.py +0 -0
  116. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/__init__.py +0 -0
  117. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/__init__.py +0 -0
  118. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
  119. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_serializer.py +0 -0
  120. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/actiontable_service.py +0 -0
  121. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_service.py +0 -0
  122. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp20_serializer.py +0 -0
  123. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +0 -0
  124. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +0 -0
  125. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
  126. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
  127. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
  128. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
  129. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
  130. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
  131. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
  132. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
  133. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/conbus/write_config_service.py +0 -0
  134. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/__init__.py +0 -0
  135. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
  136. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
  137. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
  138. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
  139. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
  140. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
  141. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
  142. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
  143. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
  144. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
  145. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
  146. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
  147. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/homekit/homekit_service.py +0 -0
  148. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/log_file_service.py +0 -0
  149. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/module_type_service.py +0 -0
  150. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/__init__.py +0 -0
  151. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
  152. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/protocol/protocol_factory.py +0 -0
  153. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/reverse_proxy_service.py +0 -0
  154. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/server/__init__.py +0 -0
  155. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/__init__.py +0 -0
  156. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
  157. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
  158. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
  159. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
  160. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
  161. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
  162. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_service.py +0 -0
  163. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
  164. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/__init__.py +0 -0
  165. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/checksum.py +0 -0
  166. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/dependencies.py +0 -0
  167. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/event_helper.py +0 -0
  168. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/serialization.py +0 -0
  169. {conson_xp-1.3.0 → conson_xp-1.5.0}/src/xp/utils/time_utils.py +0 -0
  170. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/.coverage +0 -0
  171. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/__init__.py +0 -0
  172. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/conftest.py +0 -0
  173. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/.coverage +0 -0
  174. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/__init__.py +0 -0
  175. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/telegram_test_data.py +0 -0
  176. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_actiontable_integration.py +0 -0
  177. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_api/.coverage +0 -0
  178. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_api/__init__.py +0 -0
  179. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_blink_integration.py +0 -0
  180. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_checksum_integration.py +0 -0
  181. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_blink_integration.py +0 -0
  182. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
  183. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_raw_integration.py +0 -0
  184. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_conbus_receive_integration.py +0 -0
  185. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_discovery_integration.py +0 -0
  186. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_event_telegram_integration.py +0 -0
  187. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_homekit_config_integration.py +0 -0
  188. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_link_number_integration.py +0 -0
  189. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_module_integration.py +0 -0
  190. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_output_integration.py +0 -0
  191. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
  192. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
  193. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_version_integration.py +0 -0
  194. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
  195. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
  196. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/__init__.py +0 -0
  197. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_api/__init__.py +0 -0
  198. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/__init__.py +0 -0
  199. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_click_tree.py +0 -0
  200. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
  201. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
  202. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
  203. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_decorators.py +0 -0
  204. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
  205. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_formatters.py +0 -0
  206. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
  207. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
  208. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/__init__.py +0 -0
  209. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_connection_init.py +0 -0
  210. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_connection/test_exceptions.py +0 -0
  211. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_encoding/__init__.py +0 -0
  212. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
  213. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/__init__.py +0 -0
  214. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus.py +0 -0
  215. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
  216. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
  217. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_event_telegram.py +0 -0
  218. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_log_entry.py +0 -0
  219. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_module_type.py +0 -0
  220. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
  221. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram.py +0 -0
  222. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
  223. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_version_telegram.py +0 -0
  224. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_write_config_type.py +0 -0
  225. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
  226. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
  227. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
  228. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/__init__.py +0 -0
  229. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_actiontable_service.py +0 -0
  230. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_blink_service.py +0 -0
  231. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_checksum_service.py +0 -0
  232. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
  233. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
  234. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
  235. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_discovery_service.py +0 -0
  236. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
  237. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
  238. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
  239. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_homekit_services.py +0 -0
  240. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_log_file_service.py +0 -0
  241. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_module_type_service.py +0 -0
  242. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_protocol.py +0 -0
  243. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_server_service.py +0 -0
  244. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
  245. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
  246. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_telegram_service.py +0 -0
  247. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_version_service.py +0 -0
  248. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
  249. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
  250. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
  251. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
  252. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
  253. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/__init__.py +0 -0
  254. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_checksum.py +0 -0
  255. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_event_helper.py +0 -0
  256. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_serialization.py +0 -0
  257. {conson_xp-1.3.0 → conson_xp-1.5.0}/tests/unit/test_utils/test_time_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -20,7 +20,7 @@ dependencies = [
20
20
  ]
21
21
  requires-python = ">=3.11"
22
22
  readme = "README.md"
23
- version = "1.3.0"
23
+ version = "1.5.0"
24
24
 
25
25
  [project.license]
26
26
  file = "LICENSE"
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.3.0"
6
+ __version__ = "1.5.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -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 device serial numbers.
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[str]] = None
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
- self.discovered_device_result.discovered_devices.append(serial_number)
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: Optional[float] = None,
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: int = 0
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:02X}",
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:02X}",
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 self._handle_device_specific_data_request(request)
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 = 2 # CP20 module type from registry
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(300.0)
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 = client_socket.recv(1024)
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
- break
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.info(f"Received from {client_address}: {message}")
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.info(f"Sent to {client_address}: {response[:-1]}")
278
+ self.logger.debug(f"Sent to {client_address}: {response[:-1]}")
243
279
 
244
280
  except socket.timeout:
245
- self.logger.info(f"Client {client_address} timed out")
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(message)
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: {message}")
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 request: {e}")
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 = 13 # XP130 module type from registry
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 = 33 # XP20 module type from registry
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 = 34 # XP230 module type from registry
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: