conson-xp 1.45.0__tar.gz → 1.46.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 (323) hide show
  1. {conson_xp-1.45.0 → conson_xp-1.46.0}/PKG-INFO +1 -1
  2. {conson_xp-1.45.0 → conson_xp-1.46.0}/pyproject.toml +1 -1
  3. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/__init__.py +1 -1
  4. conson_xp-1.46.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +345 -0
  5. conson_xp-1.46.0/src/xp/services/conbus/actiontable/actiontable_download_state_machine.py +276 -0
  6. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/conbus_event_protocol.py +98 -1
  7. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_download_service.py +3 -41
  8. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_service.py +14 -52
  9. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_protocol.py +11 -2
  10. conson_xp-1.45.0/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -525
  11. {conson_xp-1.45.0 → conson_xp-1.46.0}/LICENSE +0 -0
  12. {conson_xp-1.45.0 → conson_xp-1.46.0}/README.md +0 -0
  13. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/__init__.py +0 -0
  14. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/__main__.py +0 -0
  15. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/__init__.py +0 -0
  16. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/__init__.py +0 -0
  17. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus.py +0 -0
  18. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
  19. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
  20. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
  21. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
  22. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
  23. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
  24. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
  25. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
  26. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
  27. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
  28. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
  29. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
  30. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
  31. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
  32. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
  33. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
  34. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
  35. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/file_commands.py +0 -0
  36. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/__init__.py +0 -0
  37. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/homekit.py +0 -0
  38. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/homekit/homekit_start_commands.py +0 -0
  39. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/module_commands.py +0 -0
  40. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
  41. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/server/__init__.py +0 -0
  42. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/server/server_commands.py +0 -0
  43. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/__init__.py +0 -0
  44. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram.py +0 -0
  45. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
  46. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
  47. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
  48. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
  49. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
  50. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
  51. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/__init__.py +0 -0
  52. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/term.py +0 -0
  53. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/commands/term/term_commands.py +0 -0
  54. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/main.py +0 -0
  55. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/__init__.py +0 -0
  56. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/click_tree.py +0 -0
  57. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
  58. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/decorators.py +0 -0
  59. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/error_handlers.py +0 -0
  60. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/formatters.py +0 -0
  61. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/module_type_choice.py +0 -0
  62. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/serial_number_type.py +0 -0
  63. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/system_function_choice.py +0 -0
  64. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/cli/utils/xp_module_type.py +0 -0
  65. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/__init__.py +0 -0
  66. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/__init__.py +0 -0
  67. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/actiontable.py +0 -0
  68. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
  69. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
  70. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
  71. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/__init__.py +0 -0
  72. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus.py +0 -0
  73. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_autoreport.py +0 -0
  74. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_blink.py +0 -0
  75. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_client_config.py +0 -0
  76. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_connection_status.py +0 -0
  77. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_custom.py +0 -0
  78. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_datapoint.py +0 -0
  79. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_discover.py +0 -0
  80. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_event_list.py +0 -0
  81. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_event_raw.py +0 -0
  82. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_export.py +0 -0
  83. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
  84. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_linknumber.py +0 -0
  85. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_logger_config.py +0 -0
  86. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_output.py +0 -0
  87. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_raw.py +0 -0
  88. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_receive.py +0 -0
  89. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
  90. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/config/__init__.py +0 -0
  91. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/config/conson_module_config.py +0 -0
  92. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/__init__.py +0 -0
  93. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/homekit_accessory.py +0 -0
  94. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/homekit/homekit_config.py +0 -0
  95. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/log_entry.py +0 -0
  96. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/protocol/__init__.py +0 -0
  97. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/protocol/conbus_protocol.py +0 -0
  98. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/response.py +0 -0
  99. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/__init__.py +0 -0
  100. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/action_type.py +0 -0
  101. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/datapoint_type.py +0 -0
  102. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/event_telegram.py +0 -0
  103. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/event_type.py +0 -0
  104. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/input_action_type.py +0 -0
  105. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/input_type.py +0 -0
  106. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/module_type.py +0 -0
  107. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/module_type_code.py +0 -0
  108. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/output_telegram.py +0 -0
  109. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/reply_telegram.py +0 -0
  110. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/system_function.py +0 -0
  111. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/system_telegram.py +0 -0
  112. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/telegram.py +0 -0
  113. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/telegram_type.py +0 -0
  114. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/telegram/timeparam_type.py +0 -0
  115. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/__init__.py +0 -0
  116. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/connection_state.py +0 -0
  117. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/module_state.py +0 -0
  118. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/protocol_keys_config.py +0 -0
  119. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/status_message.py +0 -0
  120. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/term/telegram_display.py +0 -0
  121. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/models/write_config_type.py +0 -0
  122. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/__init__.py +0 -0
  123. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/__init__.py +0 -0
  124. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/actiontable_serializer.py +0 -0
  125. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
  126. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
  127. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
  128. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
  129. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/__init__.py +0 -0
  130. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/__init__.py +0 -0
  131. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
  132. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
  133. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +0 -0
  134. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
  135. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_blink_service.py +0 -0
  136. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_custom_service.py +0 -0
  137. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
  138. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
  139. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_discover_service.py +0 -0
  140. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
  141. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
  142. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_export_service.py +0 -0
  143. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_output_service.py +0 -0
  144. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_raw_service.py +0 -0
  145. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_receive_service.py +0 -0
  146. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/conbus_scan_service.py +0 -0
  147. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/__init__.py +0 -0
  148. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -0
  149. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -0
  150. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -0
  151. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/msactiontable/msactiontable_upload_service.py +0 -0
  152. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/conbus/write_config_service.py +0 -0
  153. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/__init__.py +0 -0
  154. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_cache_service.py +0 -0
  155. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_conbus_service.py +0 -0
  156. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_config_validator.py +0 -0
  157. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_conson_validator.py +0 -0
  158. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_dimminglight.py +0 -0
  159. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_dimminglight_service.py +0 -0
  160. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_hap_service.py +0 -0
  161. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_lightbulb.py +0 -0
  162. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_lightbulb_service.py +0 -0
  163. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_module_service.py +0 -0
  164. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_outlet.py +0 -0
  165. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_outlet_service.py +0 -0
  166. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/homekit/homekit_service.py +0 -0
  167. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/log_file_service.py +0 -0
  168. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/module_type_service.py +0 -0
  169. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/__init__.py +0 -0
  170. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/conbus_protocol.py +0 -0
  171. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/protocol_factory.py +0 -0
  172. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/protocol/telegram_protocol.py +0 -0
  173. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/reverse_proxy_service.py +0 -0
  174. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/__init__.py +0 -0
  175. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/base_server_service.py +0 -0
  176. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/client_buffer_manager.py +0 -0
  177. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/cp20_server_service.py +0 -0
  178. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/device_service_factory.py +0 -0
  179. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/server_service.py +0 -0
  180. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp130_server_service.py +0 -0
  181. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp20_server_service.py +0 -0
  182. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp230_server_service.py +0 -0
  183. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp24_server_service.py +0 -0
  184. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/server/xp33_server_service.py +0 -0
  185. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/__init__.py +0 -0
  186. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_blink_service.py +0 -0
  187. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
  188. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
  189. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_discover_service.py +0 -0
  190. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
  191. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_output_service.py +0 -0
  192. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_service.py +0 -0
  193. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/telegram/telegram_version_service.py +0 -0
  194. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/__init__.py +0 -0
  195. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/protocol_monitor_service.py +0 -0
  196. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/services/term/state_monitor_service.py +0 -0
  197. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/__init__.py +0 -0
  198. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/protocol.py +0 -0
  199. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/protocol.tcss +0 -0
  200. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/state.py +0 -0
  201. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/state.tcss +0 -0
  202. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/__init__.py +0 -0
  203. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/help_menu.py +0 -0
  204. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/modules_list.py +0 -0
  205. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/protocol_log.py +0 -0
  206. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/term/widgets/status_footer.py +0 -0
  207. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/__init__.py +0 -0
  208. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/checksum.py +0 -0
  209. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/dependencies.py +1 -1
  210. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/event_helper.py +0 -0
  211. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/logging.py +0 -0
  212. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/serialization.py +0 -0
  213. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/state_machine.py +0 -0
  214. {conson_xp-1.45.0 → conson_xp-1.46.0}/src/xp/utils/time_utils.py +0 -0
  215. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/.coverage +0 -0
  216. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/__init__.py +0 -0
  217. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/conftest.py +0 -0
  218. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/.coverage +0 -0
  219. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/__init__.py +0 -0
  220. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/telegram_test_data.py +0 -0
  221. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_actiontable_integration.py +0 -0
  222. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_api/.coverage +0 -0
  223. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_api/__init__.py +0 -0
  224. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_blink_integration.py +0 -0
  225. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_checksum_integration.py +0 -0
  226. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_blink_integration.py +0 -0
  227. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_datapoint_integration.py +0 -0
  228. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_raw_integration.py +0 -0
  229. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_conbus_receive_integration.py +0 -0
  230. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_discovery_integration.py +0 -0
  231. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_event_telegram_integration.py +0 -0
  232. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_homekit_config_integration.py +0 -0
  233. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_link_number_integration.py +0 -0
  234. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_module_integration.py +0 -0
  235. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_output_integration.py +0 -0
  236. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_reverse_proxy_integration.py +0 -0
  237. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_system_reply_telegram_integration.py +0 -0
  238. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_term_logging_integration.py +0 -0
  239. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_version_integration.py +0 -0
  240. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_xp20_action_table_integration.py +0 -0
  241. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/integration/test_xp24_action_table_integration.py +0 -0
  242. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/__init__.py +0 -0
  243. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_api/__init__.py +0 -0
  244. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/__init__.py +0 -0
  245. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_click_tree.py +0 -0
  246. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
  247. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
  248. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
  249. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
  250. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_decorators.py +0 -0
  251. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_error_handlers.py +0 -0
  252. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_formatters.py +0 -0
  253. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_serial_number_type.py +0 -0
  254. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_system_function_choice.py +0 -0
  255. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_cli/test_term_commands.py +0 -0
  256. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_encoding/__init__.py +0 -0
  257. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
  258. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/__init__.py +0 -0
  259. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus.py +0 -0
  260. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_client_send.py +0 -0
  261. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_discover.py +0 -0
  262. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
  263. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_event_telegram.py +0 -0
  264. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_log_entry.py +0 -0
  265. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_logger_config.py +0 -0
  266. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_module_type.py +0 -0
  267. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_reply_telegram.py +0 -0
  268. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_system_telegram.py +0 -0
  269. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
  270. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_version_telegram.py +0 -0
  271. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_write_config_type.py +0 -0
  272. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp20_action_table.py +0 -0
  273. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_table.py +0 -0
  274. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
  275. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
  276. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/__init__.py +0 -0
  277. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_serializer.py +0 -0
  278. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_actiontable_upload_service.py +0 -0
  279. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_base_server_service.py +0 -0
  280. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_blink_service.py +0 -0
  281. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_checksum_service.py +0 -0
  282. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
  283. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
  284. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
  285. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
  286. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_output_service.py +0 -0
  287. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
  288. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
  289. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
  290. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
  291. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_device_service_factory.py +0 -0
  292. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_discovery_service.py +0 -0
  293. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_cache_service.py +0 -0
  294. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_config_validator.py +0 -0
  295. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_conson_service.py +0 -0
  296. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_homekit_services.py +0 -0
  297. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_log_file_service.py +0 -0
  298. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_module_type_service.py +0 -0
  299. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_msactiontable_upload_service.py +0 -0
  300. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_protocol.py +0 -0
  301. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
  302. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_server_service.py +0 -0
  303. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_state_monitor_service.py +0 -0
  304. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_input_service.py +0 -0
  305. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_output_service.py +0 -0
  306. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_protocol.py +0 -0
  307. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_telegram_service.py +0 -0
  308. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_version_service.py +0 -0
  309. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
  310. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_service.py +0 -0
  311. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
  312. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
  313. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
  314. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp33_short_format.py +0 -0
  315. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_services/test_xp_server_services.py +0 -0
  316. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_tui/__init__.py +0 -0
  317. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_tui/test_protocol_log.py +0 -0
  318. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/__init__.py +0 -0
  319. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_checksum.py +0 -0
  320. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_event_helper.py +0 -0
  321. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_logging.py +0 -0
  322. {conson_xp-1.45.0 → conson_xp-1.46.0}/tests/unit/test_utils/test_serialization.py +0 -0
  323. {conson_xp-1.45.0 → conson_xp-1.46.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.45.0
3
+ Version: 1.46.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.45.0"
26
+ version = "1.46.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.45.0"
6
+ __version__ = "1.46.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -0,0 +1,345 @@
1
+ """Service for downloading ActionTable via Conbus protocol."""
2
+
3
+ import logging
4
+ from dataclasses import asdict
5
+ from typing import Any, Optional
6
+
7
+ from psygnal import SignalInstance
8
+
9
+ from xp.models.actiontable.actiontable import ActionTable
10
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
11
+ from xp.models.telegram.datapoint_type import DataPointType
12
+ from xp.models.telegram.reply_telegram import ReplyTelegram
13
+ from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
14
+ from xp.services.conbus.actiontable.actiontable_download_state_machine import (
15
+ MAX_ERROR_RETRIES,
16
+ ActionTableDownloadStateMachine,
17
+ )
18
+ from xp.services.protocol.conbus_event_protocol import (
19
+ NO_ERROR_CODE,
20
+ ConbusEventProtocol,
21
+ )
22
+
23
+
24
+ class ActionTableDownloadService(ActionTableDownloadStateMachine):
25
+ """Service for downloading action tables from Conbus modules via TCP.
26
+
27
+ Inherits from ActionTableDownloadStateMachine and overrides on_enter_*
28
+ methods to add protocol-specific behavior.
29
+
30
+ The workflow consists of three phases:
31
+
32
+ INIT phase (drain → reset → wait_ok):
33
+ Connection established, drain pending telegrams, query error status.
34
+
35
+ DOWNLOAD phase (request → receive chunks → EOF):
36
+ Request actiontable, receive and ACK chunks until EOF.
37
+
38
+ CLEANUP phase (drain → reset → wait_ok):
39
+ After EOF, drain remaining telegrams and verify final status.
40
+
41
+ Attributes:
42
+ on_progress: Signal emitted with "." for each chunk received.
43
+ on_error: Signal emitted with error message string.
44
+ on_actiontable_received: Signal emitted with (ActionTable, dict, list).
45
+ on_finish: Signal emitted when download and cleanup completed.
46
+
47
+ Example:
48
+ >>> with download_service as service:
49
+ ... service.configure(serial_number="12345678")
50
+ ... service.on_actiontable_received.connect(handle_result)
51
+ ... service.start_reactor()
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ conbus_protocol: ConbusEventProtocol,
57
+ actiontable_serializer: ActionTableSerializer,
58
+ ) -> None:
59
+ """Initialize the action table download service.
60
+
61
+ Args:
62
+ conbus_protocol: ConbusEventProtocol instance.
63
+ actiontable_serializer: Action table serializer.
64
+ """
65
+ self.conbus_protocol = conbus_protocol
66
+ self.serializer = actiontable_serializer
67
+ self.serial_number: str = ""
68
+ self.actiontable_data: list[str] = []
69
+ self._signals_connected: bool = False
70
+
71
+ # Service signals
72
+ self.on_progress: SignalInstance = SignalInstance((str,))
73
+ self.on_error: SignalInstance = SignalInstance((str,))
74
+ self.on_finish: SignalInstance = SignalInstance()
75
+ self.on_actiontable_received: SignalInstance = SignalInstance(
76
+ (ActionTable, dict[str, Any], list[str])
77
+ )
78
+
79
+ # Initialize state machine (must be last - triggers introspection)
80
+ super().__init__()
81
+
82
+ # Override logger for service-specific logging
83
+ self.logger = logging.getLogger(__name__)
84
+
85
+ # Connect protocol signals
86
+ self._connect_signals()
87
+
88
+ # Override state entry hooks with protocol behavior
89
+
90
+ def on_enter_receiving(self) -> None:
91
+ """Enter receiving state - wait for telegrams to drain."""
92
+ self.logger.debug(f"Entering RECEIVING state (phase={self.phase.value})")
93
+ self.conbus_protocol.wait()
94
+
95
+ def on_enter_resetting(self) -> None:
96
+ """Enter resetting state - send error status query."""
97
+ self.logger.debug(f"Entering RESETTING state (phase={self.phase.value})")
98
+ self.conbus_protocol.send_error_status_query(serial_number=self.serial_number)
99
+ self.send_error_status()
100
+
101
+ def on_enter_waiting_ok(self) -> None:
102
+ """Enter waiting_ok state - wait for error status response."""
103
+ self.logger.debug(f"Entering WAITING_OK state (phase={self.phase.value})")
104
+ self.conbus_protocol.wait()
105
+
106
+ def on_enter_requesting(self) -> None:
107
+ """Enter requesting state - send download request."""
108
+ self.enter_download_phase() # Sets phase to DOWNLOAD
109
+ self.conbus_protocol.send_download_request(serial_number=self.serial_number)
110
+ self.send_download()
111
+
112
+ def on_enter_waiting_data(self) -> None:
113
+ """Enter waiting_data state - wait for actiontable chunks."""
114
+ self.logger.debug("Entering WAITING_DATA state - awaiting chunks")
115
+ self.conbus_protocol.wait()
116
+
117
+ def on_enter_receiving_chunk(self) -> None:
118
+ """Enter receiving_chunk state - send ACK."""
119
+ self.logger.debug("Entering RECEIVING_CHUNK state - sending ACK")
120
+ self.conbus_protocol.send_ack(serial_number=self.serial_number)
121
+ self.send_ack()
122
+
123
+ def on_enter_processing_eof(self) -> None:
124
+ """Enter processing_eof state - deserialize and emit result."""
125
+ self.logger.debug("Entering PROCESSING_EOF state - deserializing")
126
+ all_data = "".join(self.actiontable_data)
127
+ actiontable = self.serializer.from_encoded_string(all_data)
128
+ actiontable_dict = asdict(actiontable)
129
+ actiontable_short = self.serializer.format_decoded_output(actiontable)
130
+ self.on_actiontable_received.emit(
131
+ actiontable, actiontable_dict, actiontable_short
132
+ )
133
+ # Switch to CLEANUP phase
134
+ self.start_cleanup_phase()
135
+
136
+ def on_enter_completed(self) -> None:
137
+ """Enter completed state - emit finish signal."""
138
+ self.logger.debug("Entering COMPLETED state - download finished")
139
+ self.on_finish.emit()
140
+
141
+ def on_max_retries_exceeded(self) -> None:
142
+ """Handle max retries exceeded - emit error signal."""
143
+ self.logger.error(f"Max error retries ({MAX_ERROR_RETRIES}) exceeded")
144
+ self.on_error.emit(f"Module error persists after {MAX_ERROR_RETRIES} retries")
145
+
146
+ # Protocol event handlers
147
+
148
+ def _on_connection_made(self) -> None:
149
+ """Handle connection established event."""
150
+ self.logger.debug("Connection made")
151
+ if self.idle.is_active:
152
+ self.do_connect()
153
+
154
+ def _on_read_datapoint_received(self, reply_telegram: ReplyTelegram) -> None:
155
+ """Handle READ_DATAPOINT response for error status check.
156
+
157
+ Args:
158
+ reply_telegram: The parsed reply telegram.
159
+ """
160
+ self.logger.debug(f"Received READ_DATAPOINT in {self.current_state}")
161
+ if reply_telegram.serial_number != self.serial_number:
162
+ return
163
+
164
+ if reply_telegram.datapoint_type != DataPointType.MODULE_ERROR_CODE:
165
+ return
166
+
167
+ if not self.waiting_ok.is_active:
168
+ return
169
+
170
+ is_no_error = reply_telegram.data_value == NO_ERROR_CODE
171
+ if is_no_error:
172
+ self.handle_no_error_received()
173
+ else:
174
+ self.handle_error_received()
175
+
176
+ def _on_actiontable_chunk_received(
177
+ self, reply_telegram: ReplyTelegram, actiontable_chunk: str
178
+ ) -> None:
179
+ """Handle actiontable chunk telegram received.
180
+
181
+ Args:
182
+ reply_telegram: The parsed reply telegram containing chunk data.
183
+ actiontable_chunk: The chunk data.
184
+ """
185
+ self.logger.debug(f"Received actiontable chunk in {self.current_state}")
186
+ if reply_telegram.serial_number != self.serial_number:
187
+ return
188
+
189
+ if self.waiting_data.is_active:
190
+ self.actiontable_data.append(actiontable_chunk)
191
+ self.on_progress.emit(".")
192
+ self.receive_chunk()
193
+
194
+ def _on_eof_received(self, reply_telegram: ReplyTelegram) -> None:
195
+ """Handle EOF telegram received.
196
+
197
+ Args:
198
+ reply_telegram: The parsed reply telegram (unused).
199
+ """
200
+ self.logger.debug(f"Received EOF in {self.current_state}")
201
+ if reply_telegram.serial_number != self.serial_number:
202
+ return
203
+
204
+ if self.waiting_data.is_active:
205
+ self.receive_eof()
206
+
207
+ def _on_telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
208
+ """Handle telegram received event.
209
+
210
+ Args:
211
+ telegram_received: The telegram received event.
212
+ """
213
+ self.logger.debug(f"Received {telegram_received} in {self.current_state}")
214
+
215
+ # In receiving state, drain pending telegrams from pipe (discard to /dev/null).
216
+ # This ensures clean state before processing by clearing any stale messages.
217
+ if self.receiving.is_active:
218
+ self.filter_telegram()
219
+ return
220
+
221
+ def _on_timeout(self) -> None:
222
+ """Handle timeout event."""
223
+ self.logger.debug(f"Timeout occurred (phase={self.phase.value})")
224
+ if self.receiving.is_active:
225
+ self.do_timeout() # receiving -> resetting
226
+ elif self.waiting_ok.is_active:
227
+ self.do_timeout() # waiting_ok -> receiving (retry)
228
+ elif self.waiting_data.is_active:
229
+ self.logger.error("Timeout waiting for actiontable data")
230
+ self.on_error.emit("Timeout waiting for actiontable data")
231
+ else:
232
+ self.logger.debug("Timeout in non-recoverable state")
233
+ self.on_error.emit("Timeout")
234
+
235
+ def _on_failed(self, message: str) -> None:
236
+ """Handle failed connection event.
237
+
238
+ Args:
239
+ message: Failure message.
240
+ """
241
+ self.logger.debug(f"Failed: {message}")
242
+ self.on_error.emit(message)
243
+
244
+ # Public API
245
+
246
+ def configure(
247
+ self,
248
+ serial_number: str,
249
+ timeout_seconds: Optional[float] = 2.0,
250
+ ) -> None:
251
+ """Configure download parameters before starting.
252
+
253
+ Sets the target module serial number and timeout. Call this before
254
+ start_reactor() to configure the download target.
255
+
256
+ Args:
257
+ serial_number: Module serial number to download from.
258
+ timeout_seconds: Timeout in seconds for each operation (default 2.0).
259
+
260
+ Raises:
261
+ RuntimeError: If called while download is in progress.
262
+ """
263
+ if not self.idle.is_active:
264
+ raise RuntimeError("Cannot configure while download in progress")
265
+ self.logger.info("Configuring actiontable download")
266
+ self.serial_number = serial_number
267
+ if timeout_seconds:
268
+ self.conbus_protocol.timeout_seconds = timeout_seconds
269
+
270
+ def set_timeout(self, timeout_seconds: float) -> None:
271
+ """Set operation timeout.
272
+
273
+ Args:
274
+ timeout_seconds: Timeout in seconds.
275
+ """
276
+ self.conbus_protocol.timeout_seconds = timeout_seconds
277
+
278
+ def start_reactor(self) -> None:
279
+ """Start the reactor."""
280
+ self.conbus_protocol.start_reactor()
281
+
282
+ def stop_reactor(self) -> None:
283
+ """Stop the reactor."""
284
+ self.conbus_protocol.stop_reactor()
285
+
286
+ def _connect_signals(self) -> None:
287
+ """Connect protocol signals to handlers (idempotent)."""
288
+ if self._signals_connected:
289
+ return
290
+ self.conbus_protocol.on_connection_made.connect(self._on_connection_made)
291
+ self.conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
292
+ self.conbus_protocol.on_read_datapoint_received.connect(
293
+ self._on_read_datapoint_received
294
+ )
295
+ self.conbus_protocol.on_actiontable_chunk_received.connect(
296
+ self._on_actiontable_chunk_received
297
+ )
298
+ self.conbus_protocol.on_eof_received.connect(self._on_eof_received)
299
+ self.conbus_protocol.on_timeout.connect(self._on_timeout)
300
+ self.conbus_protocol.on_failed.connect(self._on_failed)
301
+ self._signals_connected = True
302
+
303
+ def _disconnect_signals(self) -> None:
304
+ """Disconnect protocol signals from handlers (idempotent)."""
305
+ if not self._signals_connected:
306
+ return
307
+ self.conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
308
+ self.conbus_protocol.on_telegram_received.disconnect(self._on_telegram_received)
309
+ self.conbus_protocol.on_read_datapoint_received.disconnect(
310
+ self._on_read_datapoint_received
311
+ )
312
+ self.conbus_protocol.on_actiontable_chunk_received.disconnect(
313
+ self._on_actiontable_chunk_received
314
+ )
315
+ self.conbus_protocol.on_eof_received.disconnect(self._on_eof_received)
316
+ self.conbus_protocol.on_timeout.disconnect(self._on_timeout)
317
+ self.conbus_protocol.on_failed.disconnect(self._on_failed)
318
+ self._signals_connected = False
319
+
320
+ def __enter__(self) -> "ActionTableDownloadService":
321
+ """Enter context manager - reset state and reconnect signals.
322
+
323
+ Returns:
324
+ Self for context manager protocol.
325
+ """
326
+ # Reset state for singleton reuse
327
+ self.actiontable_data = []
328
+ # Reset state machine
329
+ self.reset()
330
+ # Reconnect signals (in case previously disconnected)
331
+ self._connect_signals()
332
+ return self
333
+
334
+ def __exit__(
335
+ self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
336
+ ) -> None:
337
+ """Exit context manager and disconnect signals."""
338
+ self._disconnect_signals()
339
+ # Disconnect service signals
340
+ self.on_progress.disconnect()
341
+ self.on_error.disconnect()
342
+ self.on_actiontable_received.disconnect()
343
+ self.on_finish.disconnect()
344
+ # Stop reactor
345
+ self.stop_reactor()
@@ -0,0 +1,276 @@
1
+ """State machine for ActionTable download workflow."""
2
+
3
+ import logging
4
+ from abc import ABCMeta, abstractmethod
5
+ from enum import Enum
6
+
7
+ from statemachine import State, StateMachine
8
+ from statemachine.factory import StateMachineMetaclass
9
+
10
+
11
+ class AbstractStateMachineMeta(StateMachineMetaclass, ABCMeta):
12
+ """Combined metaclass for abstract state machines.
13
+
14
+ Combines StateMachineMetaclass (for state machine introspection) with
15
+ ABCMeta (for abstract method enforcement).
16
+ """
17
+
18
+ pass
19
+
20
+
21
+ # Constants
22
+ MAX_ERROR_RETRIES = 3 # Max retries for error_status_received before giving up
23
+
24
+
25
+ class Phase(Enum):
26
+ """Download workflow phases.
27
+
28
+ The download workflow consists of three sequential phases:
29
+ - INIT: Drain pending telegrams, query error status → proceed to DOWNLOAD
30
+ - DOWNLOAD: Request actiontable, receive chunks with ACK, until EOF
31
+ - CLEANUP: Drain pending telegrams, query error status → proceed to COMPLETED
32
+
33
+ Attributes:
34
+ INIT: Initial phase - drain pending telegrams and query error status.
35
+ DOWNLOAD: Download phase - request actiontable and receive chunks.
36
+ CLEANUP: Cleanup phase - drain remaining telegrams and verify status.
37
+ """
38
+
39
+ INIT = "init"
40
+ DOWNLOAD = "download"
41
+ CLEANUP = "cleanup"
42
+
43
+
44
+ class ActionTableDownloadStateMachine(StateMachine, metaclass=AbstractStateMachineMeta):
45
+ """State machine for ActionTable download workflow.
46
+
47
+ Pure state machine with states, transitions, and guards. Subclasses can
48
+ override on_enter_* methods to add protocol-specific behavior.
49
+
50
+ States (9 total):
51
+ idle -> receiving -> resetting -> waiting_ok -> requesting
52
+ -> waiting_data <-> receiving_chunk -> processing_eof -> completed
53
+
54
+ Phases - INIT and CLEANUP share the same states (receiving, resetting, waiting_ok):
55
+
56
+ INIT phase (drain → reset → wait_ok):
57
+ idle -> receiving -> resetting -> waiting_ok --(guard: is_init_phase)--> requesting
58
+
59
+ DOWNLOAD phase (request → receive chunks → EOF):
60
+ requesting -> waiting_data <-> receiving_chunk -> processing_eof
61
+
62
+ CLEANUP phase (drain → reset → wait_ok):
63
+ processing_eof -> receiving -> resetting -> waiting_ok --(guard: is_cleanup_phase)--> completed
64
+
65
+ The drain/reset/wait_ok cycle:
66
+ 1. Drain pending telegrams (receiving state discards telegrams)
67
+ 2. Timeout triggers error status query (resetting)
68
+ 3. Wait for response (waiting_ok)
69
+ 4. On no error: guard determines target (requesting or completed)
70
+ On error: retry from drain step (limited by MAX_ERROR_RETRIES)
71
+
72
+ Attributes:
73
+ phase: Current workflow phase (INIT, DOWNLOAD, CLEANUP).
74
+ error_retry_count: Current error retry count.
75
+ idle: Initial state before connection.
76
+ receiving: Drain pending telegrams state (INIT or CLEANUP phase).
77
+ resetting: Query error status state.
78
+ waiting_ok: Await error status response state.
79
+ requesting: DOWNLOAD phase state - send download request.
80
+ waiting_data: DOWNLOAD phase state - await chunks.
81
+ receiving_chunk: DOWNLOAD phase state - process chunk.
82
+ processing_eof: DOWNLOAD phase state - deserialize result.
83
+ completed: Final state - download finished.
84
+ do_connect: Transition from idle to receiving.
85
+ filter_telegram: Self-transition in receiving state for draining.
86
+ do_timeout: Timeout transitions from receiving/waiting_ok.
87
+ send_error_status: Transition from resetting to waiting_ok.
88
+ error_status_received: Transition from waiting_ok to receiving on error.
89
+ no_error_status_received: Conditional transition based on phase.
90
+ send_download: Transition from requesting to waiting_data.
91
+ receive_chunk: Transition from waiting_data to receiving_chunk.
92
+ send_ack: Transition from receiving_chunk to waiting_data.
93
+ receive_eof: Transition from waiting_data to processing_eof.
94
+ do_finish: Transition from processing_eof to receiving.
95
+ """
96
+
97
+ # States - unified for INIT and CLEANUP phases using guards
98
+ idle = State(initial=True)
99
+ receiving = State() # Drain telegrams (INIT or CLEANUP phase)
100
+ resetting = State() # Query error status
101
+ waiting_ok = State() # Await error status response
102
+
103
+ requesting = State() # DOWNLOAD phase: send download request
104
+ waiting_data = State() # DOWNLOAD phase: await chunks
105
+ receiving_chunk = State() # DOWNLOAD phase: process chunk
106
+ processing_eof = State() # DOWNLOAD phase: deserialize result
107
+
108
+ completed = State(final=True)
109
+
110
+ # Phase transitions - shared states with guards for phase-dependent routing
111
+ do_connect = idle.to(receiving)
112
+ filter_telegram = receiving.to(receiving) # Self-transition: drain to /dev/null
113
+ do_timeout = receiving.to(resetting) | waiting_ok.to(receiving)
114
+ send_error_status = resetting.to(waiting_ok)
115
+ error_status_received = waiting_ok.to(
116
+ receiving, cond="can_retry"
117
+ ) # Retry if under limit
118
+
119
+ # Conditional transitions based on phase
120
+ no_error_status_received = waiting_ok.to(
121
+ requesting, cond="is_init_phase"
122
+ ) | waiting_ok.to(completed, cond="is_cleanup_phase")
123
+
124
+ # DOWNLOAD phase transitions
125
+ send_download = requesting.to(waiting_data)
126
+ receive_chunk = waiting_data.to(receiving_chunk)
127
+ send_ack = receiving_chunk.to(waiting_data)
128
+ receive_eof = waiting_data.to(processing_eof)
129
+
130
+ # Return to drain/reset cycle for CLEANUP phase
131
+ do_finish = processing_eof.to(receiving)
132
+
133
+ def __init__(self) -> None:
134
+ """Initialize the state machine."""
135
+ self.logger = logging.getLogger(__name__)
136
+ self._phase: Phase = Phase.INIT
137
+ self._error_retry_count: int = 0
138
+
139
+ # Initialize state machine
140
+ super().__init__(allow_event_without_transition=True)
141
+
142
+ @property
143
+ def phase(self) -> Phase:
144
+ """Get current phase."""
145
+ return self._phase
146
+
147
+ @phase.setter
148
+ def phase(self, value: Phase) -> None:
149
+ """Set current phase.
150
+
151
+ Args:
152
+ value: The phase value to set.
153
+ """
154
+ self._phase = value
155
+
156
+ @property
157
+ def error_retry_count(self) -> int:
158
+ """Get current error retry count."""
159
+ return self._error_retry_count
160
+
161
+ @error_retry_count.setter
162
+ def error_retry_count(self, value: int) -> None:
163
+ """Set error retry count.
164
+
165
+ Args:
166
+ value: The error retry count value to set.
167
+ """
168
+ self._error_retry_count = value
169
+
170
+ # Guard conditions for phase-dependent transitions
171
+
172
+ def is_init_phase(self) -> bool:
173
+ """Guard: check if currently in INIT phase.
174
+
175
+ Returns:
176
+ True if in INIT phase, False otherwise.
177
+ """
178
+ return self._phase == Phase.INIT
179
+
180
+ def is_cleanup_phase(self) -> bool:
181
+ """Guard: check if currently in CLEANUP phase.
182
+
183
+ Returns:
184
+ True if in CLEANUP phase, False otherwise.
185
+ """
186
+ return self._phase == Phase.CLEANUP
187
+
188
+ def can_retry(self) -> bool:
189
+ """Guard: check if retry is allowed (under max limit).
190
+
191
+ Returns:
192
+ True if retry count is under MAX_ERROR_RETRIES, False otherwise.
193
+ """
194
+ return self._error_retry_count < MAX_ERROR_RETRIES
195
+
196
+ # State entry hooks - subclasses MUST implement these
197
+
198
+ @abstractmethod
199
+ def on_enter_receiving(self) -> None:
200
+ """Enter receiving state - drain pending telegrams."""
201
+ ...
202
+
203
+ @abstractmethod
204
+ def on_enter_resetting(self) -> None:
205
+ """Enter resetting state - query error status."""
206
+ ...
207
+
208
+ @abstractmethod
209
+ def on_enter_waiting_ok(self) -> None:
210
+ """Enter waiting_ok state - awaiting error status response."""
211
+ ...
212
+
213
+ @abstractmethod
214
+ def on_enter_requesting(self) -> None:
215
+ """Enter requesting state - send download request."""
216
+ ...
217
+
218
+ @abstractmethod
219
+ def on_enter_waiting_data(self) -> None:
220
+ """Enter waiting_data state - wait for actiontable chunks."""
221
+ ...
222
+
223
+ @abstractmethod
224
+ def on_enter_receiving_chunk(self) -> None:
225
+ """Enter receiving_chunk state - send ACK."""
226
+ ...
227
+
228
+ @abstractmethod
229
+ def on_enter_processing_eof(self) -> None:
230
+ """Enter processing_eof state - deserialize and emit result."""
231
+ ...
232
+
233
+ @abstractmethod
234
+ def on_enter_completed(self) -> None:
235
+ """Enter completed state - download finished."""
236
+ ...
237
+
238
+ @abstractmethod
239
+ def on_max_retries_exceeded(self) -> None:
240
+ """Called when max error retries exceeded."""
241
+ ...
242
+
243
+ # Public methods for state machine control
244
+
245
+ def enter_download_phase(self) -> None:
246
+ """Enter requesting state - send download request."""
247
+ self._phase = Phase.DOWNLOAD
248
+
249
+ def handle_no_error_received(self) -> None:
250
+ """Handle successful error status check (no error)."""
251
+ self._error_retry_count = 0 # Reset on success
252
+ self.no_error_status_received()
253
+
254
+ def handle_error_received(self) -> None:
255
+ """Handle error status received - increment retry and attempt transition."""
256
+ self._error_retry_count += 1
257
+ self.logger.debug(
258
+ f"Error status received, retry {self._error_retry_count}/{MAX_ERROR_RETRIES}"
259
+ )
260
+ # Guard can_retry blocks transition if max retries exceeded
261
+ self.error_status_received()
262
+ # Check if guard blocked the transition (still in waiting_ok)
263
+ if self.waiting_ok.is_active:
264
+ self.on_max_retries_exceeded()
265
+
266
+ def start_cleanup_phase(self) -> None:
267
+ """Switch to CLEANUP phase and trigger do_finish transition."""
268
+ self._phase = Phase.CLEANUP
269
+ self.do_finish()
270
+
271
+ def reset(self) -> None:
272
+ """Reset state machine to initial state."""
273
+ self._phase = Phase.INIT
274
+ self._error_retry_count = 0
275
+ # python-statemachine uses model.state to track current state
276
+ self.model.state = self.idle.id