conson-xp 1.43.0__tar.gz → 1.45.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 (321) hide show
  1. {conson_xp-1.43.0 → conson_xp-1.45.0}/PKG-INFO +1 -1
  2. {conson_xp-1.43.0 → conson_xp-1.45.0}/pyproject.toml +1 -1
  3. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/__init__.py +1 -1
  4. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +1 -1
  5. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/actiontable/actiontable_download_service.py +219 -161
  6. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_actiontable_integration.py +2 -2
  7. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +1 -1
  8. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_actiontable_download_service.py +214 -28
  9. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_actiontable_service.py +1 -1
  10. {conson_xp-1.43.0 → conson_xp-1.45.0}/LICENSE +0 -0
  11. {conson_xp-1.43.0 → conson_xp-1.45.0}/README.md +0 -0
  12. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/__init__.py +0 -0
  13. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/__main__.py +0 -0
  14. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/__init__.py +0 -0
  15. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
  16. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
  17. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
  18. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
  19. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
  20. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
  21. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
  22. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
  23. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
  24. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
  25. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
  26. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
  27. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
  28. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
  29. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
  30. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
  31. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
  32. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
  33. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/file_commands.py +0 -0
  34. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
  35. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
  36. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
  37. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/module_commands.py +0 -0
  38. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
  39. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/server/__init__.py +0 -0
  40. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/server/server_commands.py +0 -0
  41. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
  42. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
  43. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
  44. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
  45. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
  46. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
  47. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
  48. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
  49. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/term/__init__.py +0 -0
  50. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/term/term.py +0 -0
  51. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/commands/term/term_commands.py +0 -0
  52. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/main.py +0 -0
  53. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/__init__.py +0 -0
  54. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/click_tree.py +0 -0
  55. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
  56. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/decorators.py +0 -0
  57. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/error_handlers.py +0 -0
  58. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/formatters.py +0 -0
  59. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/module_type_choice.py +0 -0
  60. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/serial_number_type.py +0 -0
  61. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/system_function_choice.py +0 -0
  62. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/cli/utils/xp_module_type.py +0 -0
  63. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/__init__.py +0 -0
  64. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/actiontable/__init__.py +0 -0
  65. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/actiontable/actiontable.py +0 -0
  66. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
  67. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
  68. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
  69. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/__init__.py +0 -0
  70. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus.py +0 -0
  71. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
  72. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_blink.py +0 -0
  73. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
  74. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
  75. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_custom.py +0 -0
  76. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
  77. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_discover.py +0 -0
  78. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
  79. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
  80. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_export.py +0 -0
  81. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
  82. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
  83. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
  84. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_output.py +0 -0
  85. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_raw.py +0 -0
  86. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_receive.py +0 -0
  87. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
  88. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/config/__init__.py +0 -0
  89. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/config/conson_module_config.py +0 -0
  90. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/homekit/__init__.py +0 -0
  91. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
  92. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/homekit/homekit_config.py +0 -0
  93. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/log_entry.py +0 -0
  94. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/protocol/__init__.py +0 -0
  95. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
  96. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/response.py +0 -0
  97. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/__init__.py +0 -0
  98. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/action_type.py +0 -0
  99. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/datapoint_type.py +0 -0
  100. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/event_telegram.py +0 -0
  101. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/event_type.py +0 -0
  102. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/input_action_type.py +0 -0
  103. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/input_type.py +0 -0
  104. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/module_type.py +0 -0
  105. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/module_type_code.py +0 -0
  106. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/output_telegram.py +0 -0
  107. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/reply_telegram.py +0 -0
  108. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/system_function.py +0 -0
  109. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/system_telegram.py +0 -0
  110. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/telegram.py +0 -0
  111. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/telegram_type.py +0 -0
  112. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/telegram/timeparam_type.py +0 -0
  113. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/__init__.py +0 -0
  114. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/connection_state.py +0 -0
  115. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/module_state.py +0 -0
  116. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/protocol_keys_config.py +0 -0
  117. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/status_message.py +0 -0
  118. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/term/telegram_display.py +0 -0
  119. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/models/write_config_type.py +0 -0
  120. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/__init__.py +0 -0
  121. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/__init__.py +0 -0
  122. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
  123. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
  124. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
  125. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
  126. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
  127. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/__init__.py +0 -0
  128. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
  129. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
  130. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
  131. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
  132. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
  133. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
  134. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
  135. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
  136. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
  137. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
  138. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
  139. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
  140. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_export_service.py +0 -0
  141. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
  142. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
  143. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
  144. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
  145. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/msactiontable/__init__.py +0 -0
  146. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -0
  147. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -0
  148. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -0
  149. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/msactiontable/msactiontable_upload_service.py +0 -0
  150. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/conbus/write_config_service.py +0 -0
  151. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/__init__.py +0 -0
  152. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
  153. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
  154. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
  155. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
  156. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
  157. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
  158. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
  159. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
  160. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
  161. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
  162. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
  163. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
  164. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/homekit/homekit_service.py +0 -0
  165. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/log_file_service.py +0 -0
  166. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/module_type_service.py +0 -0
  167. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/protocol/__init__.py +0 -0
  168. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/protocol/conbus_event_protocol.py +0 -0
  169. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
  170. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/protocol/protocol_factory.py +0 -0
  171. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
  172. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/reverse_proxy_service.py +0 -0
  173. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/__init__.py +0 -0
  174. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/base_server_service.py +0 -0
  175. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/client_buffer_manager.py +0 -0
  176. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/cp20_server_service.py +0 -0
  177. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/device_service_factory.py +0 -0
  178. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/server_service.py +0 -0
  179. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/xp130_server_service.py +0 -0
  180. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/xp20_server_service.py +0 -0
  181. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/xp230_server_service.py +0 -0
  182. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/xp24_server_service.py +0 -0
  183. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/server/xp33_server_service.py +0 -0
  184. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/__init__.py +0 -0
  185. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
  186. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
  187. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
  188. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
  189. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
  190. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
  191. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_service.py +0 -0
  192. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
  193. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/term/__init__.py +0 -0
  194. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
  195. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/services/term/state_monitor_service.py +0 -0
  196. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/__init__.py +0 -0
  197. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/protocol.py +0 -0
  198. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/protocol.tcss +0 -0
  199. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/state.py +0 -0
  200. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/state.tcss +0 -0
  201. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/widgets/__init__.py +0 -0
  202. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/widgets/help_menu.py +0 -0
  203. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/widgets/modules_list.py +0 -0
  204. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/widgets/protocol_log.py +0 -0
  205. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/term/widgets/status_footer.py +0 -0
  206. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/__init__.py +0 -0
  207. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/checksum.py +0 -0
  208. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/dependencies.py +0 -0
  209. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/event_helper.py +0 -0
  210. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/logging.py +0 -0
  211. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/serialization.py +0 -0
  212. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/state_machine.py +0 -0
  213. {conson_xp-1.43.0 → conson_xp-1.45.0}/src/xp/utils/time_utils.py +0 -0
  214. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/.coverage +0 -0
  215. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/__init__.py +0 -0
  216. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/conftest.py +0 -0
  217. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/.coverage +0 -0
  218. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/__init__.py +0 -0
  219. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/telegram_test_data.py +0 -0
  220. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_api/.coverage +0 -0
  221. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_api/__init__.py +0 -0
  222. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_blink_integration.py +0 -0
  223. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_checksum_integration.py +0 -0
  224. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_conbus_blink_integration.py +0 -0
  225. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
  226. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_conbus_raw_integration.py +0 -0
  227. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_conbus_receive_integration.py +0 -0
  228. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_discovery_integration.py +0 -0
  229. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_event_telegram_integration.py +0 -0
  230. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_homekit_config_integration.py +0 -0
  231. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_link_number_integration.py +0 -0
  232. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_module_integration.py +0 -0
  233. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_output_integration.py +0 -0
  234. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
  235. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
  236. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_term_logging_integration.py +0 -0
  237. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_version_integration.py +0 -0
  238. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
  239. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
  240. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/__init__.py +0 -0
  241. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_api/__init__.py +0 -0
  242. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/__init__.py +0 -0
  243. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_click_tree.py +0 -0
  244. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
  245. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
  246. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
  247. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_decorators.py +0 -0
  248. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
  249. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_formatters.py +0 -0
  250. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
  251. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
  252. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_cli/test_term_commands.py +0 -0
  253. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_encoding/__init__.py +0 -0
  254. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
  255. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/__init__.py +0 -0
  256. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_conbus.py +0 -0
  257. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
  258. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
  259. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
  260. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_event_telegram.py +0 -0
  261. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_log_entry.py +0 -0
  262. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_logger_config.py +0 -0
  263. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_module_type.py +0 -0
  264. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
  265. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_system_telegram.py +0 -0
  266. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
  267. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_version_telegram.py +0 -0
  268. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_write_config_type.py +0 -0
  269. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
  270. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
  271. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
  272. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
  273. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/__init__.py +0 -0
  274. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
  275. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
  276. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_base_server_service.py +0 -0
  277. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_blink_service.py +0 -0
  278. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_checksum_service.py +0 -0
  279. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
  280. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
  281. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
  282. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
  283. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
  284. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_output_service.py +0 -0
  285. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
  286. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
  287. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
  288. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
  289. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
  290. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_discovery_service.py +0 -0
  291. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
  292. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
  293. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
  294. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_homekit_services.py +0 -0
  295. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_log_file_service.py +0 -0
  296. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_module_type_service.py +0 -0
  297. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_msactiontable_upload_service.py +0 -0
  298. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_protocol.py +0 -0
  299. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
  300. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_server_service.py +0 -0
  301. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_state_monitor_service.py +0 -0
  302. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
  303. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_telegram_output_service.py +0 -0
  304. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
  305. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_telegram_service.py +0 -0
  306. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_version_service.py +0 -0
  307. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
  308. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
  309. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
  310. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
  311. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
  312. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp33_short_format.py +0 -0
  313. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
  314. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_tui/__init__.py +0 -0
  315. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
  316. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_utils/__init__.py +0 -0
  317. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_utils/test_checksum.py +0 -0
  318. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_utils/test_event_helper.py +0 -0
  319. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_utils/test_logging.py +0 -0
  320. {conson_xp-1.43.0 → conson_xp-1.45.0}/tests/unit/test_utils/test_serialization.py +0 -0
  321. {conson_xp-1.43.0 → conson_xp-1.45.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.43.0
3
+ Version: 1.45.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -23,7 +23,7 @@ dependencies = [
23
23
  ]
24
24
  requires-python = ">=3.11"
25
25
  readme = "README.md"
26
- version = "1.43.0"
26
+ version = "1.45.0"
27
27
 
28
28
  [project.license]
29
29
  file = "LICENSE"
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.43.0"
6
+ __version__ = "1.45.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -98,7 +98,7 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
98
98
  service.on_finish.connect(on_finish)
99
99
  service.on_actiontable_received.connect(on_actiontable_received)
100
100
  service.on_error.connect(on_error)
101
- service.start(serial_number=serial_number)
101
+ service.configure(serial_number=serial_number)
102
102
  service.start_reactor()
103
103
 
104
104
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  import logging
4
4
  from dataclasses import asdict
5
- from typing import Any, Dict, Optional
5
+ from enum import Enum
6
+ from typing import Any, Optional
6
7
 
7
8
  from psygnal import SignalInstance
8
9
  from statemachine import State, StateMachine
@@ -17,87 +18,130 @@ from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
17
18
  from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
18
19
  from xp.services.telegram.telegram_service import TelegramService
19
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
+
20
45
 
21
46
  class ActionTableDownloadService(StateMachine):
22
- """TCP client service for downloading action tables from Conbus modules.
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
23
61
 
24
- Inherits from StateMachine - the service IS the state machine.
62
+ DOWNLOAD phase (request receive chunks EOF):
63
+ requesting -> waiting_data <-> receiving_chunk -> processing_eof
25
64
 
26
- Manages TCP socket connections, handles telegram generation and transmission,
27
- and processes server responses for action table downloads.
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
28
76
 
29
77
  Attributes:
30
- on_progress: Signal emitted with telegram frame when progress is made.
31
- on_error: Signal emitted with error message string when an error occurs.
32
- on_finish: Signal emitted with (ActionTable, Dict[str, Any], list[str]) when complete.
33
- idle: Initial state, waiting for connection.
34
- receiving: Listening for telegrams, filtering relevant responses.
35
- resetting: Timeout occurred, preparing error status query.
36
- waiting_ok: Sent error status query, awaiting ACK/NAK.
37
- requesting: Ready to send download request.
38
- waiting_data: Awaiting actiontable chunk or EOF.
39
- receiving_chunk: Processing received actiontable data.
40
- processing_eof: Received EOF, deserializing actiontable.
41
- completed: Download finished successfully.
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.
42
91
  do_connect: Transition from idle to receiving.
43
- do_timeout: Transition from receiving to resetting.
92
+ filter_telegram: Self-transition in receiving to drain telegrams.
93
+ do_timeout: Transition on timeout events.
44
94
  send_error_status: Transition from resetting to waiting_ok.
45
- error_status_received: Transition from waiting_ok to receiving (retry).
46
- no_error_status_received: Transition from waiting_ok to requesting or completed.
95
+ error_status_received: Transition when error status is received.
96
+ no_error_status_received: Transition when no error status received.
47
97
  send_download: Transition from requesting to waiting_data.
48
98
  receive_chunk: Transition from waiting_data to receiving_chunk.
49
99
  send_ack: Transition from receiving_chunk to waiting_data.
50
100
  receive_eof: Transition from waiting_data to processing_eof.
51
- do_finish: Transition from processing_eof to receiving.
52
- receiving2: Second receiving state after EOF processing.
53
- resetting2: Second resetting state for finalization phase.
54
- waiting_ok2: Second waiting_ok state for finalization phase.
55
- filter_telegram: Self-transition for filtering telegrams in receiving state.
56
- filter_telegram2: Self-transition for filtering telegrams in receiving2 state.
57
- do_timeout2: Timeout transition for finalization phase.
58
- send_error_status2: Error status query transition for finalization phase.
59
- error_status_received2: Error received transition for finalization phase.
60
- no_error_status_received2: No error received transition to completed state.
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()
61
108
  """
62
109
 
63
- # States (9 states as per spec)
110
+ # States - unified for INIT and CLEANUP phases using guards
64
111
  idle = State(initial=True)
65
- receiving = State()
66
- resetting = State()
67
- waiting_ok = State()
68
-
69
- requesting = State()
70
- waiting_data = State()
71
- receiving_chunk = State()
72
- processing_eof = State()
112
+ receiving = State() # Drain telegrams (INIT or CLEANUP phase)
113
+ resetting = State() # Query error status
114
+ waiting_ok = State() # Await error status response
73
115
 
74
- receiving2 = State()
75
- resetting2 = State()
76
- waiting_ok2 = State()
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
77
120
 
78
121
  completed = State(final=True)
79
122
 
80
- # Phase 1: Connection & Initialization
123
+ # Phase transitions - shared states with guards for phase-dependent routing
81
124
  do_connect = idle.to(receiving)
82
- filter_telegram = receiving.to(receiving) # Self-transition for filtering
125
+ filter_telegram = receiving.to(receiving) # Self-transition: drain to /dev/null
83
126
  do_timeout = receiving.to(resetting) | waiting_ok.to(receiving)
84
127
  send_error_status = resetting.to(waiting_ok)
85
- error_status_received = waiting_ok.to(receiving)
86
- no_error_status_received = waiting_ok.to(requesting)
128
+ error_status_received = waiting_ok.to(
129
+ receiving, cond="can_retry"
130
+ ) # Retry if under limit
87
131
 
88
- # Phase 2: Download
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
89
138
  send_download = requesting.to(waiting_data)
90
139
  receive_chunk = waiting_data.to(receiving_chunk)
91
140
  send_ack = receiving_chunk.to(waiting_data)
92
141
  receive_eof = waiting_data.to(processing_eof)
93
142
 
94
- # Phase 3: Finalization
95
- do_finish = processing_eof.to(receiving2)
96
- filter_telegram2 = receiving2.to(receiving2) # Self-transition for filtering
97
- do_timeout2 = receiving2.to(resetting2) | waiting_ok2.to(receiving2)
98
- send_error_status2 = resetting2.to(waiting_ok2)
99
- error_status_received2 = waiting_ok2.to(receiving2)
100
- no_error_status_received2 = waiting_ok2.to(completed)
143
+ # Return to drain/reset cycle for CLEANUP phase
144
+ do_finish = processing_eof.to(receiving)
101
145
 
102
146
  def __init__(
103
147
  self,
@@ -118,42 +162,63 @@ class ActionTableDownloadService(StateMachine):
118
162
  self.serial_number: str = ""
119
163
  self.actiontable_data: list[str] = []
120
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
121
168
 
122
169
  # Signals (instance attributes to avoid conflict with statemachine)
123
170
  self.on_progress: SignalInstance = SignalInstance((str,))
124
171
  self.on_error: SignalInstance = SignalInstance((str,))
125
172
  self.on_finish: SignalInstance = SignalInstance()
126
173
  self.on_actiontable_received: SignalInstance = SignalInstance(
127
- (ActionTable, Dict[str, Any], list[str])
174
+ (ActionTable, dict[str, Any], list[str])
128
175
  )
129
176
 
177
+ # Initialize state machine first (before connecting signals)
178
+ super().__init__(allow_event_without_transition=True)
179
+
130
180
  # Connect protocol signals
131
- self.conbus_protocol.on_connection_made.connect(self._on_connection_made)
132
- self.conbus_protocol.on_telegram_sent.connect(self._on_telegram_sent)
133
- self.conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
134
- self.conbus_protocol.on_timeout.connect(self._on_timeout)
135
- self.conbus_protocol.on_failed.connect(self._on_failed)
181
+ self._connect_signals()
136
182
 
137
- # Initialize state machine
138
- super().__init__(allow_event_without_transition=True)
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
139
208
 
140
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.
141
213
 
142
214
  def on_enter_receiving(self) -> None:
143
- """Enter receiving state - listening for telegrams."""
144
- self.logger.debug("Entering RECEIVING state - waiting for telegrams")
145
- self.conbus_protocol.wait()
146
-
147
- def on_enter_receiving2(self) -> None:
148
- """Enter receiving state - listening for telegrams."""
149
- self.logger.debug("Entering RECEIVING2 state - waiting for telegrams")
215
+ """Enter receiving state - drain pending telegrams."""
216
+ self.logger.debug(f"Entering RECEIVING state (phase={self._phase.value})")
150
217
  self.conbus_protocol.wait()
151
218
 
152
219
  def on_enter_resetting(self) -> None:
153
220
  """Enter resetting state - query error status."""
154
- self.logger.debug("Entering RESETTING state - querying error status")
155
-
156
- # query_datapoint_module_error_code
221
+ self.logger.debug(f"Entering RESETTING state (phase={self._phase.value})")
157
222
  self.conbus_protocol.send_telegram(
158
223
  telegram_type=TelegramType.SYSTEM,
159
224
  serial_number=self.serial_number,
@@ -162,37 +227,20 @@ class ActionTableDownloadService(StateMachine):
162
227
  )
163
228
  self.send_error_status()
164
229
 
165
- def on_enter_resetting2(self) -> None:
166
- """Enter resetting state - query error status."""
167
- self.logger.debug("Entering RESETTING2 state - querying error status")
168
-
169
- # query_datapoint_module_error_code
170
- self.conbus_protocol.send_telegram(
171
- telegram_type=TelegramType.SYSTEM,
172
- serial_number=self.serial_number,
173
- system_function=SystemFunction.READ_DATAPOINT,
174
- data_value=DataPointType.MODULE_ERROR_CODE.value,
175
- )
176
- self.send_error_status2()
177
-
178
230
  def on_enter_waiting_ok(self) -> None:
179
- """Enter waiting_ok state - awaiting ERROR/NO_ERROR."""
180
- self.logger.debug("Entering WAITING_OK state - awaiting ERROR/NO_ERROR")
181
- self.conbus_protocol.wait()
182
-
183
- def on_enter_waiting_ok2(self) -> None:
184
- """Enter waiting_ok state - awaiting ERROR/NO_ERROR."""
185
- self.logger.debug("Entering WAITING_OK state - awaiting ERROR/NO_ERROR")
231
+ """Enter waiting_ok state - awaiting error status response."""
232
+ self.logger.debug(f"Entering WAITING_OK state (phase={self._phase.value})")
186
233
  self.conbus_protocol.wait()
187
234
 
188
235
  def on_enter_requesting(self) -> None:
189
236
  """Enter requesting state - send download request."""
237
+ self._phase = Phase.DOWNLOAD
190
238
  self.logger.debug("Entering REQUESTING state - sending download request")
191
239
  self.conbus_protocol.send_telegram(
192
240
  telegram_type=TelegramType.SYSTEM,
193
241
  serial_number=self.serial_number,
194
242
  system_function=SystemFunction.DOWNLOAD_ACTIONTABLE,
195
- data_value="00",
243
+ data_value=NO_ERROR_CODE,
196
244
  )
197
245
  self.send_download()
198
246
 
@@ -208,12 +256,12 @@ class ActionTableDownloadService(StateMachine):
208
256
  telegram_type=TelegramType.SYSTEM,
209
257
  serial_number=self.serial_number,
210
258
  system_function=SystemFunction.ACK,
211
- data_value="00",
259
+ data_value=NO_ERROR_CODE,
212
260
  )
213
261
  self.send_ack()
214
262
 
215
263
  def on_enter_processing_eof(self) -> None:
216
- """Enter processing_eof state - deserialize and emit result."""
264
+ """Enter processing_eof state - deserialize and emit result, then cleanup."""
217
265
  self.logger.debug("Entering PROCESSING_EOF state - deserializing")
218
266
  all_data = "".join(self.actiontable_data)
219
267
  actiontable = self.serializer.from_encoded_string(all_data)
@@ -222,6 +270,8 @@ class ActionTableDownloadService(StateMachine):
222
270
  self.on_actiontable_received.emit(
223
271
  actiontable, actiontable_dict, actiontable_short
224
272
  )
273
+ # Switch to CLEANUP phase before returning to receiving state
274
+ self._phase = Phase.CLEANUP
225
275
  self.do_finish()
226
276
 
227
277
  def on_enter_completed(self) -> None:
@@ -259,44 +309,28 @@ class ActionTableDownloadService(StateMachine):
259
309
  )
260
310
  return
261
311
 
262
- if reply_telegram.data_value == "00":
263
- if self.waiting_ok.is_active:
264
- self.no_error_status_received()
312
+ if not self.waiting_ok.is_active:
313
+ return
265
314
 
266
- if reply_telegram.data_value != "00":
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)
267
327
  if self.waiting_ok.is_active:
268
- self.error_status_received()
269
-
270
- if reply_telegram.data_value == "00":
271
- if self.waiting_ok2.is_active:
272
- self.no_error_status_received2()
273
-
274
- if reply_telegram.data_value != "00":
275
- if self.waiting_ok2.is_active:
276
- self.error_status_received2()
277
-
278
- def _on_ack_received(self, _reply_telegram: ReplyTelegram) -> None:
279
- """Handle ACK telegram received.
280
-
281
- Args:
282
- _reply_telegram: The parsed reply telegram (unused).
283
- """
284
- self.logger.debug(f"Received ACK in {self.current_state}")
285
- if self.waiting_ok.is_active:
286
- self.ack_received()
287
-
288
- if self.waiting_ok2.is_active:
289
- self.ack_received2()
290
-
291
- def _on_nack_received(self, _reply_telegram: ReplyTelegram) -> None:
292
- """Handle NAK telegram received.
293
-
294
- Args:
295
- _reply_telegram: The parsed reply telegram (unused).
296
- """
297
- self.logger.debug(f"Received NAK in {self.current_state}")
298
- if self.waiting_ok.is_active:
299
- self.nak_received()
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
+ )
300
334
 
301
335
  def _on_actiontable_chunk_received(self, reply_telegram: ReplyTelegram) -> None:
302
336
  """Handle actiontable chunk telegram received.
@@ -306,7 +340,7 @@ class ActionTableDownloadService(StateMachine):
306
340
  """
307
341
  self.logger.debug(f"Received actiontable chunk in {self.current_state}")
308
342
  if self.waiting_data.is_active:
309
- data_part = reply_telegram.data_value[2:]
343
+ data_part = reply_telegram.data_value[CHUNK_HEADER_LENGTH:]
310
344
  self.actiontable_data.append(data_part)
311
345
  self.on_progress.emit(".")
312
346
  self.receive_chunk()
@@ -327,7 +361,10 @@ class ActionTableDownloadService(StateMachine):
327
361
  Args:
328
362
  telegram_received: The telegram received event.
329
363
  """
330
- self.logger.debug(f"Received{telegram_received} in {self.current_state}")
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.
331
368
  if self.receiving.is_active:
332
369
  self.filter_telegram()
333
370
  return
@@ -336,11 +373,13 @@ class ActionTableDownloadService(StateMachine):
336
373
  if not telegram_received.checksum_valid:
337
374
  self.logger.debug("Filtered: invalid checksum")
338
375
  return
376
+
339
377
  if telegram_received.telegram_type != TelegramType.REPLY.value:
340
378
  self.logger.debug(
341
379
  f"Filtered: not a reply (got {telegram_received.telegram_type})"
342
380
  )
343
381
  return
382
+
344
383
  if telegram_received.serial_number != self.serial_number:
345
384
  self.logger.debug(
346
385
  f"Filtered: wrong serial {telegram_received.serial_number} != {self.serial_number}"
@@ -355,14 +394,6 @@ class ActionTableDownloadService(StateMachine):
355
394
  self._on_read_datapoint_received(reply_telegram)
356
395
  return
357
396
 
358
- if reply_telegram.system_function == SystemFunction.ACK:
359
- self._on_ack_received(reply_telegram)
360
- return
361
-
362
- if reply_telegram.system_function == SystemFunction.NAK:
363
- self._on_nack_received(reply_telegram)
364
- return
365
-
366
397
  if reply_telegram.system_function == SystemFunction.ACTIONTABLE:
367
398
  self._on_actiontable_chunk_received(reply_telegram)
368
399
  return
@@ -373,15 +404,14 @@ class ActionTableDownloadService(StateMachine):
373
404
 
374
405
  def _on_timeout(self) -> None:
375
406
  """Handle timeout event."""
376
- self.logger.debug("Timeout occurred")
407
+ self.logger.debug(f"Timeout occurred (phase={self._phase.value})")
377
408
  if self.receiving.is_active:
378
409
  self.do_timeout() # receiving -> resetting
379
410
  elif self.waiting_ok.is_active:
380
- self.do_timeout() # waiting_ok -> receiving
381
- elif self.receiving2.is_active:
382
- self.do_timeout2() # receiving2 -> resetting2
383
- elif self.waiting_ok2.is_active:
384
- self.do_timeout2() # waiting_ok2 -> receiving2
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")
385
415
  else:
386
416
  self.logger.debug("Timeout in non-recoverable state")
387
417
  self.on_error.emit("Timeout")
@@ -397,18 +427,26 @@ class ActionTableDownloadService(StateMachine):
397
427
 
398
428
  # Public API
399
429
 
400
- def start(
430
+ def configure(
401
431
  self,
402
432
  serial_number: str,
403
433
  timeout_seconds: Optional[float] = 2.0,
404
434
  ) -> None:
405
- """Run reactor in dedicated thread with its own event loop.
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.
406
439
 
407
440
  Args:
408
- serial_number: Module serial number.
409
- timeout_seconds: Optional timeout in seconds.
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.
410
446
  """
411
- self.logger.info("Starting actiontable download")
447
+ if not self.idle.is_active:
448
+ raise RuntimeError("Cannot configure while download in progress")
449
+ self.logger.info("Configuring actiontable download")
412
450
  self.serial_number = serial_number
413
451
  if timeout_seconds:
414
452
  self.conbus_protocol.timeout_seconds = timeout_seconds
@@ -429,17 +467,42 @@ class ActionTableDownloadService(StateMachine):
429
467
  """Stop the reactor."""
430
468
  self.conbus_protocol.stop_reactor()
431
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
+
432
492
  def __enter__(self) -> "ActionTableDownloadService":
433
- """Enter context manager - reset state for singleton reuse.
493
+ """Enter context manager - reset state and reconnect signals.
434
494
 
435
495
  Returns:
436
496
  Self for context manager protocol.
437
497
  """
438
498
  # Reset state for singleton reuse
439
499
  self.actiontable_data = []
440
- self._download_complete = False
500
+ self._phase = Phase.INIT
501
+ self._error_retry_count = 0
441
502
  # Reset state machine to idle
442
503
  self._reset_state()
504
+ # Reconnect signals (in case previously disconnected)
505
+ self._connect_signals()
443
506
  return self
444
507
 
445
508
  def _reset_state(self) -> None:
@@ -447,21 +510,16 @@ class ActionTableDownloadService(StateMachine):
447
510
  # python-statemachine uses model.state to track current state
448
511
  # Set it directly to the initial state id
449
512
  self.model.state = self.idle.id
450
- self._download_complete = False
451
513
 
452
514
  def __exit__(
453
515
  self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
454
516
  ) -> None:
455
517
  """Exit context manager and disconnect signals."""
456
- # Disconnect protocol signals
457
- self.conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
458
- self.conbus_protocol.on_telegram_sent.disconnect(self._on_telegram_sent)
459
- self.conbus_protocol.on_telegram_received.disconnect(self._on_telegram_received)
460
- self.conbus_protocol.on_timeout.disconnect(self._on_timeout)
461
- self.conbus_protocol.on_failed.disconnect(self._on_failed)
518
+ self._disconnect_signals()
462
519
  # Disconnect service signals
463
520
  self.on_progress.disconnect()
464
521
  self.on_error.disconnect()
522
+ self.on_actiontable_received.disconnect()
465
523
  self.on_finish.disconnect()
466
524
  # Stop reactor
467
525
  self.stop_reactor()
@@ -165,7 +165,7 @@ class TestActionTableIntegration:
165
165
  # Do nothing in test
166
166
  pass
167
167
 
168
- mock_service.start.side_effect = mock_start
168
+ mock_service.configure.side_effect = mock_start
169
169
  mock_service.start_reactor.side_effect = mock_start_reactor
170
170
 
171
171
  # Setup mock container
@@ -191,7 +191,7 @@ class TestActionTableIntegration:
191
191
  assert "actiontable" in result.output
192
192
 
193
193
  # Verify service.start was called
194
- assert mock_service.start.called
194
+ assert mock_service.configure.called
195
195
 
196
196
  def test_bcd_encoding_decoding(self):
197
197
  """Test BCD encoding/decoding functionality."""
@@ -150,7 +150,7 @@ class TestConbusActionTableCommands:
150
150
 
151
151
  # Verify success
152
152
  assert result.exit_code == 0
153
- mock_service.start.assert_called_once()
153
+ mock_service.configure.assert_called_once()
154
154
 
155
155
  # Verify output contains expected data
156
156
  assert "0000012345" in result.output