conson-xp 1.41.0__tar.gz → 1.44.0__tar.gz

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