conson-xp 2.0.0__tar.gz → 2.0.2__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 (302) hide show
  1. {conson_xp-2.0.0 → conson_xp-2.0.2}/PKG-INFO +1 -1
  2. {conson_xp-2.0.0 → conson_xp-2.0.2}/pyproject.toml +1 -1
  3. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/__init__.py +1 -1
  4. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/main.py +23 -4
  5. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_client_config.py +2 -0
  6. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/homekit/homekit_config.py +8 -0
  7. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/actiontable_serializer.py +4 -4
  8. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/actiontable/actiontable_upload_service.py +8 -1
  9. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/protocol/conbus_event_protocol.py +1 -1
  10. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/term/homekit_service.py +105 -8
  11. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/homekit.py +23 -0
  12. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_actiontable_serializer.py +111 -1
  13. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_actiontable_upload_service.py +6 -2
  14. {conson_xp-2.0.0 → conson_xp-2.0.2}/LICENSE +0 -0
  15. {conson_xp-2.0.0 → conson_xp-2.0.2}/README.md +0 -0
  16. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/__init__.py +0 -0
  17. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/__main__.py +0 -0
  18. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/__init__.py +0 -0
  19. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/__init__.py +0 -0
  20. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus.py +0 -0
  21. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -0
  22. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_autoreport_commands.py +0 -0
  23. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_blink_commands.py +0 -0
  24. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_config_commands.py +0 -0
  25. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_custom_commands.py +0 -0
  26. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_datapoint_commands.py +0 -0
  27. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_discover_commands.py +0 -0
  28. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_event_commands.py +0 -0
  29. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_export_commands.py +0 -0
  30. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_lightlevel_commands.py +0 -0
  31. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_linknumber_commands.py +0 -0
  32. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_modulenumber_commands.py +0 -0
  33. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_msactiontable_commands.py +0 -0
  34. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_output_commands.py +0 -0
  35. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_raw_commands.py +0 -0
  36. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_receive_commands.py +0 -0
  37. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/conbus/conbus_scan_commands.py +0 -0
  38. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/file_commands.py +0 -0
  39. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/module_commands.py +0 -0
  40. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/reverse_proxy_commands.py +0 -0
  41. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/server/__init__.py +0 -0
  42. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/server/server_commands.py +0 -0
  43. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/__init__.py +0 -0
  44. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram.py +0 -0
  45. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_blink_commands.py +0 -0
  46. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_checksum_commands.py +0 -0
  47. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_discover_commands.py +0 -0
  48. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_linknumber_commands.py +0 -0
  49. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_parse_commands.py +0 -0
  50. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/telegram/telegram_version_commands.py +0 -0
  51. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/term/__init__.py +0 -0
  52. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/term/term.py +0 -0
  53. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/commands/term/term_commands.py +0 -0
  54. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/__init__.py +0 -0
  55. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/click_tree.py +0 -0
  56. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/datapoint_type_choice.py +0 -0
  57. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/decorators.py +0 -0
  58. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/error_handlers.py +0 -0
  59. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/formatters.py +0 -0
  60. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/module_type_choice.py +0 -0
  61. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/serial_number_type.py +0 -0
  62. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/cli/utils/system_function_choice.py +0 -0
  63. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/__init__.py +0 -0
  64. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/__init__.py +0 -0
  65. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/actiontable.py +0 -0
  66. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/actiontable_type.py +0 -0
  67. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/msactiontable_xp20.py +0 -0
  68. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/msactiontable_xp24.py +0 -0
  69. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/actiontable/msactiontable_xp33.py +0 -0
  70. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/__init__.py +0 -0
  71. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus.py +0 -0
  72. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_autoreport.py +0 -0
  73. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_blink.py +0 -0
  74. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_connection_status.py +0 -0
  75. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_custom.py +0 -0
  76. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_datapoint.py +0 -0
  77. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_discover.py +0 -0
  78. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_event_list.py +0 -0
  79. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_event_raw.py +0 -0
  80. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_export.py +0 -0
  81. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_lightlevel.py +0 -0
  82. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_linknumber.py +0 -0
  83. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_logger_config.py +0 -0
  84. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_output.py +0 -0
  85. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_raw.py +0 -0
  86. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_receive.py +0 -0
  87. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/conbus/conbus_writeconfig.py +0 -0
  88. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/config/__init__.py +0 -0
  89. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/config/conson_module_config.py +0 -0
  90. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/homekit/__init__.py +0 -0
  91. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/homekit/homekit_accessory.py +0 -0
  92. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/log_entry.py +0 -0
  93. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/protocol/__init__.py +0 -0
  94. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/protocol/conbus_protocol.py +0 -0
  95. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/response.py +0 -0
  96. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/__init__.py +0 -0
  97. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/action_type.py +0 -0
  98. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/datapoint_type.py +0 -0
  99. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/event_telegram.py +0 -0
  100. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/event_type.py +0 -0
  101. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/input_action_type.py +0 -0
  102. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/input_type.py +0 -0
  103. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/module_type.py +0 -0
  104. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/module_type_code.py +0 -0
  105. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/output_telegram.py +0 -0
  106. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/reply_telegram.py +0 -0
  107. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/system_function.py +0 -0
  108. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/system_telegram.py +0 -0
  109. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/telegram.py +0 -0
  110. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/telegram_type.py +0 -0
  111. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/telegram/timeparam_type.py +0 -0
  112. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/__init__.py +0 -0
  113. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/accessory_state.py +0 -0
  114. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/connection_state.py +0 -0
  115. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/module_state.py +0 -0
  116. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/protocol_keys_config.py +0 -0
  117. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/status_message.py +0 -0
  118. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/term/telegram_display.py +0 -0
  119. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/models/write_config_type.py +0 -0
  120. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/__init__.py +0 -0
  121. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/__init__.py +0 -0
  122. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/download_state_machine.py +0 -0
  123. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/msactiontable_serializer.py +0 -0
  124. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/msactiontable_xp20_serializer.py +0 -0
  125. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/msactiontable_xp24_serializer.py +0 -0
  126. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/msactiontable_xp33_serializer.py +0 -0
  127. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/actiontable/serializer_protocol.py +0 -0
  128. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/__init__.py +0 -0
  129. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/actiontable/__init__.py +0 -0
  130. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/actiontable/actiontable_download_service.py +0 -0
  131. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/actiontable/actiontable_list_service.py +0 -0
  132. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/actiontable/actiontable_show_service.py +0 -0
  133. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_blink_all_service.py +0 -0
  134. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_blink_service.py +0 -0
  135. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_custom_service.py +0 -0
  136. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_datapoint_queryall_service.py +0 -0
  137. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_datapoint_service.py +0 -0
  138. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_discover_service.py +0 -0
  139. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_event_list_service.py +0 -0
  140. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_event_raw_service.py +0 -0
  141. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_export_actiontable_service.py +0 -0
  142. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_export_service.py +0 -0
  143. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_output_service.py +0 -0
  144. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_raw_service.py +0 -0
  145. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_receive_service.py +0 -0
  146. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/conbus_scan_service.py +0 -0
  147. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/conbus/write_config_service.py +0 -0
  148. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/log_file_service.py +0 -0
  149. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/module_type_service.py +0 -0
  150. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/protocol/__init__.py +0 -0
  151. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/reverse_proxy_service.py +0 -0
  152. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/__init__.py +0 -0
  153. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/base_server_service.py +0 -0
  154. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/client_buffer_manager.py +0 -0
  155. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/cp20_server_service.py +0 -0
  156. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/device_service_factory.py +0 -0
  157. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/server_service.py +0 -0
  158. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/xp130_server_service.py +0 -0
  159. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/xp20_server_service.py +0 -0
  160. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/xp230_server_service.py +0 -0
  161. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/xp24_server_service.py +0 -0
  162. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/server/xp33_server_service.py +0 -0
  163. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/__init__.py +0 -0
  164. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_blink_service.py +0 -0
  165. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_checksum_service.py +0 -0
  166. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_datapoint_service.py +0 -0
  167. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_discover_service.py +0 -0
  168. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_link_number_service.py +0 -0
  169. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_output_service.py +0 -0
  170. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_service.py +0 -0
  171. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/telegram/telegram_version_service.py +0 -0
  172. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/term/__init__.py +0 -0
  173. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/term/homekit_accessory_driver.py +0 -0
  174. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/term/protocol_monitor_service.py +0 -0
  175. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/services/term/state_monitor_service.py +0 -0
  176. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/__init__.py +0 -0
  177. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/homekit.tcss +0 -0
  178. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/protocol.py +0 -0
  179. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/protocol.tcss +0 -0
  180. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/state.py +0 -0
  181. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/state.tcss +0 -0
  182. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/__init__.py +0 -0
  183. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/help_menu.py +0 -0
  184. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/modules_list.py +0 -0
  185. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/protocol_log.py +0 -0
  186. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/room_list.py +0 -0
  187. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/term/widgets/status_footer.py +0 -0
  188. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/__init__.py +0 -0
  189. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/checksum.py +0 -0
  190. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/dependencies.py +0 -0
  191. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/event_helper.py +0 -0
  192. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/logging.py +0 -0
  193. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/serialization.py +0 -0
  194. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/state_machine.py +0 -0
  195. {conson_xp-2.0.0 → conson_xp-2.0.2}/src/xp/utils/time_utils.py +0 -0
  196. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/.coverage +0 -0
  197. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/__init__.py +0 -0
  198. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/conftest.py +0 -0
  199. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/.coverage +0 -0
  200. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/__init__.py +0 -0
  201. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/telegram_test_data.py +0 -0
  202. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_actiontable_integration.py +0 -0
  203. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_api/.coverage +0 -0
  204. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_api/__init__.py +0 -0
  205. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_blink_integration.py +0 -0
  206. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_checksum_integration.py +0 -0
  207. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_conbus_blink_integration.py +0 -0
  208. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_conbus_datapoint_integration.py +0 -0
  209. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_conbus_raw_integration.py +0 -0
  210. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_conbus_receive_integration.py +0 -0
  211. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_discovery_integration.py +0 -0
  212. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_event_telegram_integration.py +0 -0
  213. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_link_number_integration.py +0 -0
  214. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_module_integration.py +0 -0
  215. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_output_integration.py +0 -0
  216. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_reverse_proxy_integration.py +0 -0
  217. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_system_reply_telegram_integration.py +0 -0
  218. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_term_logging_integration.py +0 -0
  219. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_version_integration.py +0 -0
  220. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_xp20_action_table_integration.py +0 -0
  221. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/integration/test_xp24_action_table_integration.py +0 -0
  222. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/__init__.py +0 -0
  223. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_api/__init__.py +0 -0
  224. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/__init__.py +0 -0
  225. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_click_tree.py +0 -0
  226. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_conbus_actiontable_commands.py +0 -0
  227. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_conbus_blink_commands.py +0 -0
  228. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_conbus_msactiontable_upload_commands.py +0 -0
  229. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_datapoint_type_choice.py +0 -0
  230. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_decorators.py +0 -0
  231. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_error_handlers.py +0 -0
  232. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_formatters.py +0 -0
  233. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_serial_number_type.py +0 -0
  234. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_system_function_choice.py +0 -0
  235. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_cli/test_term_commands.py +0 -0
  236. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_encoding/__init__.py +0 -0
  237. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_encoding/test_latin1_edge_cases.py +0 -0
  238. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/__init__.py +0 -0
  239. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_conbus.py +0 -0
  240. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_conbus_client_send.py +0 -0
  241. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_conbus_discover.py +0 -0
  242. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_conbus_linknumber.py +0 -0
  243. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_event_telegram.py +0 -0
  244. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_log_entry.py +0 -0
  245. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_logger_config.py +0 -0
  246. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_module_type.py +0 -0
  247. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_reply_telegram.py +0 -0
  248. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_system_telegram.py +0 -0
  249. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_system_telegram_enhancements.py +0 -0
  250. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_term/__init__.py +0 -0
  251. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_term/test_accessory_state.py +0 -0
  252. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_version_telegram.py +0 -0
  253. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_write_config_type.py +0 -0
  254. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_xp20_action_table.py +0 -0
  255. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_xp24_action_table.py +0 -0
  256. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_xp24_action_table_short_format.py +0 -0
  257. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_models/test_xp24_action_telegram.py +0 -0
  258. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/__init__.py +0 -0
  259. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_actiontable_download_service.py +0 -0
  260. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_actiontable_service.py +0 -0
  261. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_base_server_service.py +0 -0
  262. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_blink_service.py +0 -0
  263. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_checksum_service.py +0 -0
  264. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_client_buffer_manager.py +0 -0
  265. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_blink_service.py +0 -0
  266. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_event_list_service.py +0 -0
  267. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_event_protocol.py +0 -0
  268. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_event_raw_service.py +0 -0
  269. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_output_service.py +0 -0
  270. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_raw_service.py +0 -0
  271. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_receive_service.py +0 -0
  272. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_reverse_proxy_service.py +0 -0
  273. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_conbus_scan_service.py +0 -0
  274. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_device_service_factory.py +0 -0
  275. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_discovery_service.py +0 -0
  276. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_homekit_service.py +0 -0
  277. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_log_file_service.py +0 -0
  278. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_module_type_service.py +0 -0
  279. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_protocol_monitor_service.py +0 -0
  280. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_server_service.py +0 -0
  281. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_state_monitor_service.py +0 -0
  282. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_telegram_input_service.py +0 -0
  283. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_telegram_output_service.py +0 -0
  284. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_telegram_service.py +0 -0
  285. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_version_service.py +0 -0
  286. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp20_action_table_serializer.py +0 -0
  287. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp24_action_service.py +0 -0
  288. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp24_action_table_serializer.py +0 -0
  289. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp24_action_table_service.py +0 -0
  290. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp33_action_table_serializer.py +0 -0
  291. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp33_short_format.py +0 -0
  292. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_services/test_xp_server_services.py +0 -0
  293. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_tui/__init__.py +0 -0
  294. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_tui/test_homekit_app.py +0 -0
  295. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_tui/test_protocol_log.py +0 -0
  296. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_tui/test_room_list.py +0 -0
  297. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_utils/__init__.py +0 -0
  298. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_utils/test_checksum.py +0 -0
  299. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_utils/test_event_helper.py +0 -0
  300. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_utils/test_logging.py +0 -0
  301. {conson_xp-2.0.0 → conson_xp-2.0.2}/tests/unit/test_utils/test_serialization.py +0 -0
  302. {conson_xp-2.0.0 → conson_xp-2.0.2}/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: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -22,7 +22,7 @@ dependencies = [
22
22
  ]
23
23
  requires-python = ">=3.11"
24
24
  readme = "README.md"
25
- version = "2.0.0"
25
+ version = "2.0.2"
26
26
 
27
27
  [project.license]
28
28
  file = "LICENSE"
@@ -4,7 +4,7 @@ XP CLI tool for remote console bus operations.
4
4
  conson-xp package.
5
5
  """
6
6
 
7
- __version__ = "2.0.0"
7
+ __version__ = "2.0.2"
8
8
  __manufacturer__ = "salchichon"
9
9
  __model__ = "xp.cli"
10
10
  __serial__ = "2025.09.23.000"
@@ -24,17 +24,36 @@ from xp.utils.logging import LoggerService
24
24
  cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
25
25
  )
26
26
  @click.version_option()
27
+ @click.option(
28
+ "--cli-config",
29
+ "-c",
30
+ default="cli.yml",
31
+ help="Path to the CLI configuration file (default: cli.yml)",
32
+ type=click.Path(exists=False),
33
+ )
34
+ @click.option(
35
+ "--log-config",
36
+ "-l",
37
+ default="logger.yml",
38
+ help="Path to the logger configuration file (default: logger.yml)",
39
+ type=click.Path(exists=False),
40
+ )
27
41
  @click.pass_context
28
- def cli(ctx: click.Context) -> None:
42
+ def cli(ctx: click.Context, cli_config: str, log_config: str) -> None:
29
43
  """
30
44
  XP CLI tool for remote console bus operations.
31
45
 
32
46
  Args:
33
47
  ctx: Click context object for passing state between commands.
48
+ cli_config: Path to the CLI configuration file.
49
+ log_config: Path to the logger configuration file.
34
50
  """
35
- container = ServiceContainer()
36
- logger_config = container.get_container().resolve(LoggerService)
37
- logger_config.setup()
51
+ container = ServiceContainer(
52
+ client_config_path=cli_config,
53
+ logger_config_path=log_config,
54
+ )
55
+ logger_service = container.get_container().resolve(LoggerService)
56
+ logger_service.setup()
38
57
 
39
58
  # Initialize the service container and store it in the context
40
59
  ctx.ensure_object(dict)
@@ -15,11 +15,13 @@ class ClientConfig(BaseModel):
15
15
  ip: IP address of the Conbus server.
16
16
  port: Port number for the connection.
17
17
  timeout: Connection timeout in seconds.
18
+ queue_delay_max: Max delay between queued telegrams in centiseconds (default 40 = 0.40s).
18
19
  """
19
20
 
20
21
  ip: str = "192.168.1.100"
21
22
  port: int = 10001
22
23
  timeout: float = 0.1
24
+ queue_delay_max: int = 40
23
25
 
24
26
 
25
27
  class ConbusClientConfig(BaseModel):
@@ -66,6 +66,10 @@ class HomekitAccessoryConfig(BaseModel):
66
66
  on_action: on code for the accessory.
67
67
  off_action: off code for the accessory.
68
68
  toggle_action: Optional toggle action code for the accessory.
69
+ dimup_action: Optional dim up action code for the dimmable accessory.
70
+ dimdown_action: Optional dim down action code for the dimmable accessory.
71
+ levelup_action: Optional level up action code for the dimmable accessory.
72
+ leveldown_action: Optional level down action code for the dimmable accessory.
69
73
  hap_accessory: Optional HAP accessory identifier.
70
74
  """
71
75
 
@@ -78,6 +82,10 @@ class HomekitAccessoryConfig(BaseModel):
78
82
  on_action: str
79
83
  off_action: str
80
84
  toggle_action: Optional[str] = None
85
+ dimup_action: Optional[str] = None
86
+ dimdown_action: Optional[str] = None
87
+ levelup_action: Optional[str] = None
88
+ leveldown_action: Optional[str] = None
81
89
  hap_accessory: Optional[int] = None
82
90
 
83
91
 
@@ -65,8 +65,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
65
65
  link_number = de_bcd(data[i + 1])
66
66
  module_input = de_bcd(data[i + 2])
67
67
 
68
- # Extract output and command from byte 3
69
- module_output = lower3(data[i + 3])
68
+ # Extract output (0-indexed in wire format, convert to 1-indexed) and command
69
+ module_output = lower3(data[i + 3]) + 1
70
70
  command_raw = upper5(data[i + 3])
71
71
 
72
72
  parameter_raw = byte_to_unsigned(data[i + 4])
@@ -125,8 +125,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
125
125
  link_byte = to_bcd(entry.link_number)
126
126
  input_byte = to_bcd(entry.module_input)
127
127
 
128
- # Combine output (lower 3 bits) and command (upper 5 bits)
129
- output_command_byte = (entry.module_output & 0x07) | (
128
+ # Combine output (lower 3 bits, 0-indexed) and command (upper 5 bits)
129
+ output_command_byte = ((entry.module_output - 1) & 0x07) | (
130
130
  (entry.command.value & 0x1F) << 3
131
131
  )
132
132
 
@@ -81,6 +81,7 @@ class ActionTableUploadService:
81
81
  # Upload state
82
82
  self.upload_data_chunks: list[str] = []
83
83
  self.current_chunk_index: int = 0
84
+ self._eof_sent: bool = False
84
85
 
85
86
  # Set up logging
86
87
  self.logger = logging.getLogger(__name__)
@@ -173,7 +174,7 @@ class ActionTableUploadService:
173
174
  )
174
175
  self.current_chunk_index += 1
175
176
  self.on_progress.emit(".")
176
- else:
177
+ elif not self._eof_sent:
177
178
  # All chunks sent, send EOF
178
179
  self.logger.debug("All chunks sent, sending EOF")
179
180
  self.conbus_protocol.send_telegram(
@@ -182,7 +183,13 @@ class ActionTableUploadService:
182
183
  system_function=SystemFunction.EOF,
183
184
  data_value="00",
184
185
  )
186
+ self.on_progress.emit("END")
187
+ self.logger.debug("EOF sent, waiting for last ACK")
188
+ self._eof_sent = True
189
+ else:
190
+ self.logger.debug("Last ACK received, closing connection")
185
191
  self.on_finish.emit(True)
192
+
186
193
  elif reply_telegram.system_function == SystemFunction.NAK:
187
194
  self.logger.debug("Received NAK during upload")
188
195
  self.failed("Upload failed: NAK received")
@@ -513,7 +513,7 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
513
513
  self.logger.debug("Queue manager: event loop")
514
514
  telegram = self.telegram_queue.get_nowait()
515
515
  self.sendFrame(telegram)
516
- later = randint(10, 80) / 100
516
+ later = randint(5, self.cli_config.queue_delay_max) / 100
517
517
  self.call_later(later, self.process_telegram_queue)
518
518
 
519
519
  def set_event_loop(self, event_loop: asyncio.AbstractEventLoop) -> None:
@@ -81,6 +81,9 @@ class HomekitService:
81
81
  # Set up HomeKit callback
82
82
  self._accessory_driver.set_callback(self._on_homekit_set)
83
83
 
84
+ # Track active level action: (accessory_id, action_type) or None
85
+ self._active_level_action: Optional[tuple[str, str]] = None
86
+
84
87
  # Connect to protocol signals
85
88
  self._connect_signals()
86
89
 
@@ -421,12 +424,15 @@ class HomekitService:
421
424
  Returns:
422
425
  True if command was sent, False otherwise.
423
426
  """
427
+ config = self._find_accessory_config_by_id(accessory_id)
424
428
  state = self._accessory_states.get(accessory_id)
425
- if not state:
429
+ if not config or not state or not config.dimup_action:
430
+ self.logger.warning(f"No config for accessory {accessory_id}")
426
431
  return False
427
- # TODO: Implement dimmer control
428
- self.on_status_message.emit(f"Dimmer+ {state.accessory_name} (not implemented)")
429
- return False
432
+
433
+ self.send_action(config.dimup_action)
434
+ self.on_status_message.emit(f"Dim+ {state.accessory_name}")
435
+ return True
430
436
 
431
437
  def decrease_dimmer(self, accessory_id: str) -> bool:
432
438
  """
@@ -438,12 +444,103 @@ class HomekitService:
438
444
  Returns:
439
445
  True if command was sent, False otherwise.
440
446
  """
447
+ config = self._find_accessory_config_by_id(accessory_id)
441
448
  state = self._accessory_states.get(accessory_id)
442
- if not state:
449
+ if not config or not state or not config.dimdown_action:
450
+ self.logger.warning(f"No config for accessory {accessory_id}")
443
451
  return False
444
- # TODO: Implement dimmer control
445
- self.on_status_message.emit(f"Dimmer- {state.accessory_name} (not implemented)")
446
- return False
452
+
453
+ self.send_action(config.dimdown_action)
454
+ self.on_status_message.emit(f"Dim- {state.accessory_name}")
455
+ return True
456
+
457
+ def levelup_selected(self, accessory_id: str) -> bool:
458
+ """
459
+ Increase level for accessory (toggle Make/Break).
460
+
461
+ First press sends Make (M), second press sends Break (B).
462
+
463
+ Args:
464
+ accessory_id: Accessory ID (e.g., "A12_1").
465
+
466
+ Returns:
467
+ True if command was sent, False otherwise.
468
+ """
469
+ config = self._find_accessory_config_by_id(accessory_id)
470
+ state = self._accessory_states.get(accessory_id)
471
+ if not config or not state or not config.levelup_action:
472
+ self.logger.warning(f"No config for accessory {accessory_id}")
473
+ return False
474
+
475
+ return self._send_level_action(
476
+ accessory_id, "levelup", config.levelup_action, state.accessory_name
477
+ )
478
+
479
+ def leveldown_selected(self, accessory_id: str) -> bool:
480
+ """
481
+ Decrease level for accessory (toggle Make/Break).
482
+
483
+ First press sends Make (M), second press sends Break (B).
484
+
485
+ Args:
486
+ accessory_id: Accessory ID (e.g., "A12_1").
487
+
488
+ Returns:
489
+ True if command was sent, False otherwise.
490
+ """
491
+ config = self._find_accessory_config_by_id(accessory_id)
492
+ state = self._accessory_states.get(accessory_id)
493
+ if not config or not state or not config.leveldown_action:
494
+ self.logger.warning(f"No config for accessory {accessory_id}")
495
+ return False
496
+
497
+ return self._send_level_action(
498
+ accessory_id, "leveldown", config.leveldown_action, state.accessory_name
499
+ )
500
+
501
+ def _send_level_action(
502
+ self, accessory_id: str, action_type: str, action: str, name: str
503
+ ) -> bool:
504
+ """
505
+ Send level action with Make/Break toggle.
506
+
507
+ Args:
508
+ accessory_id: Accessory ID.
509
+ action_type: "levelup" or "leveldown".
510
+ action: Action code (e.g., "E02L13I15").
511
+ name: Accessory name for status message.
512
+
513
+ Returns:
514
+ True if command was sent.
515
+ """
516
+ current = self._active_level_action
517
+
518
+ # If same action is active, send Break and clear
519
+ if current and current[0] == accessory_id and current[1] == action_type:
520
+ self._conbus_protocol.send_raw_telegram(f"{action}B")
521
+ self._active_level_action = None
522
+ direction = "+" if action_type == "levelup" else "-"
523
+ self.on_status_message.emit(f"Level{direction} {name} [B]")
524
+ return True
525
+
526
+ # If different action is active, send Break for it first
527
+ if current:
528
+ old_config = self._find_accessory_config_by_id(current[0])
529
+ if old_config:
530
+ old_action = (
531
+ old_config.levelup_action
532
+ if current[1] == "levelup"
533
+ else old_config.leveldown_action
534
+ )
535
+ if old_action:
536
+ self._conbus_protocol.send_raw_telegram(f"{old_action}B")
537
+
538
+ # Send Make for new action
539
+ self._conbus_protocol.send_raw_telegram(f"{action}M")
540
+ self._active_level_action = (accessory_id, action_type)
541
+ direction = "+" if action_type == "levelup" else "-"
542
+ self.on_status_message.emit(f"Level{direction} {name} [M]")
543
+ return True
447
544
 
448
545
  def refresh_all(self) -> None:
449
546
  """
@@ -41,6 +41,8 @@ class HomekitApp(App[None]):
41
41
  ("minus", "turn_off_selected", "Off"),
42
42
  ("plus", "dim_up", "Dim+"),
43
43
  ("quotation_mark", "dim_down", "Dim-"),
44
+ ("asterisk", "level_up", "Level+"),
45
+ ("ç", "level_down", "Level-"),
44
46
  ]
45
47
 
46
48
  def __init__(self, homekit_service: HomekitService) -> None:
@@ -106,12 +108,17 @@ class HomekitApp(App[None]):
106
108
  - - : Turn OFF
107
109
  - + : Dim up
108
110
  - " : Dim down
111
+ - * : Level up
112
+ - ç : Level down
109
113
 
110
114
  Args:
111
115
  event: Key press event.
112
116
  """
113
117
  key = event.key
114
118
 
119
+ # Debug: show received key
120
+ self.homekit_service.on_status_message.emit(f"Key: {key}")
121
+
115
122
  # Selection keys (a-z0-9)
116
123
  if len(key) == 1 and (("a" <= key <= "z") or ("0" <= key <= "9")):
117
124
  accessory_id = self.homekit_service.select_accessory(key)
@@ -140,6 +147,12 @@ class HomekitApp(App[None]):
140
147
  elif key in ("quotation_mark", '"'):
141
148
  self.homekit_service.decrease_dimmer(self.selected_accessory_id)
142
149
  event.prevent_default()
150
+ elif key in ("asterisk", "star", "*"):
151
+ self.homekit_service.levelup_selected(self.selected_accessory_id)
152
+ event.prevent_default()
153
+ elif key in ("cedille", "ç"):
154
+ self.homekit_service.leveldown_selected(self.selected_accessory_id)
155
+ event.prevent_default()
143
156
 
144
157
  def _select_row(self, action_key: str) -> None:
145
158
  """
@@ -243,6 +256,16 @@ class HomekitApp(App[None]):
243
256
  if self.selected_accessory_id:
244
257
  self.homekit_service.decrease_dimmer(self.selected_accessory_id)
245
258
 
259
+ def action_level_up(self) -> None:
260
+ """Increase level on selected accessory."""
261
+ if self.selected_accessory_id:
262
+ self.homekit_service.levelup_selected(self.selected_accessory_id)
263
+
264
+ def action_level_down(self) -> None:
265
+ """Decrease level on selected accessory."""
266
+ if self.selected_accessory_id:
267
+ self.homekit_service.leveldown_selected(self.selected_accessory_id)
268
+
246
269
  async def on_unmount(self) -> None:
247
270
  """Stop AccessoryDriver and clean up service when app unmounts."""
248
271
  await self.homekit_service.stop()
@@ -398,7 +398,7 @@ class TestActionTableSerializerPadding:
398
398
  assert result[0] == 0x02 # CP20 (value=2) in BCD
399
399
  assert result[1] == 0x01 # link_number
400
400
  assert result[2] == 0x02 # module_input
401
- assert result[3] == 0x0B # output (3) | command (1 << 3)
401
+ assert result[3] == 0x0A # output 0-indexed: (3-1) | (ON << 3) = 2 | 8 = 10
402
402
  assert result[4] == 0x04 # parameter
403
403
 
404
404
  # Rest should be padding
@@ -414,3 +414,113 @@ class TestActionTableSerializerPadding:
414
414
  # Should still be 480 bytes, all zeros
415
415
  assert len(result) == 480
416
416
  assert result == b"\x00" * 480
417
+
418
+ def test_to_encoded_string_cp20_link4_input0_output1_on(self):
419
+ """
420
+ Test encoding CP20 4 0 > 1 ON produces expected BCD string.
421
+
422
+ ActionTable: CP20 4 0 > 1 ON;
423
+ Serialized BCD (first 8 chars): ACAEAAAI
424
+ """
425
+ action_table = ActionTable(
426
+ entries=[
427
+ ActionTableEntry(
428
+ module_type=ModuleTypeCode.CP20,
429
+ link_number=4,
430
+ module_input=0,
431
+ module_output=1,
432
+ command=InputActionType.ON,
433
+ parameter=TimeParam.NONE,
434
+ inverted=False,
435
+ )
436
+ ]
437
+ )
438
+
439
+ encoded_string = ActionTableSerializer.to_encoded_string(action_table)
440
+
441
+ # First 8 characters (4 bytes in BCD, high-nibble first):
442
+ # AC = 0x02 (CP20, value=2)
443
+ # AE = 0x04 (link_number=4)
444
+ # AA = 0x00 (module_input=0)
445
+ # AI = 0x08 (output 0-indexed: (1-1) | (ON<<3) = 0 | 8 = 8)
446
+ assert encoded_string[:8] == "ACAEAAAI"
447
+
448
+ def test_from_encoded_string_cp20_link4_input0_output1_on(self):
449
+ """
450
+ Test decoding BCD string ACAEAAAI produces expected ActionTable.
451
+
452
+ Serialized BCD: ACAEAAAI (+ padding)
453
+ ActionTable: CP20 4 0 > 1 ON;
454
+ """
455
+ # Build full 960-char encoded string (96 entries × 5 bytes × 2 nibbles)
456
+ # First entry: ACAEAAAI, rest is padding (AA = 0x00)
457
+ encoded_string = "ACAEAAAI" + "AA" * 476
458
+
459
+ action_table = ActionTableSerializer.from_encoded_string(encoded_string)
460
+
461
+ # Should have exactly 1 entry (padding entries with NOMOD are filtered out)
462
+ assert len(action_table.entries) == 1
463
+
464
+ entry = action_table.entries[0]
465
+ assert entry.module_type == ModuleTypeCode.CP20
466
+ assert entry.link_number == 4
467
+ assert entry.module_input == 0
468
+ assert entry.module_output == 1 # 0-indexed in wire format, converted to 1
469
+ assert entry.command == InputActionType.ON
470
+ assert entry.parameter == TimeParam.NONE
471
+ assert entry.inverted is False
472
+
473
+ def test_to_encoded_string_cp20_link13_input9_output1_levelinc(self):
474
+ """
475
+ Test encoding CP20 13 9 > 1 LEVELINC produces expected BCD string.
476
+
477
+ ActionTable: CP20 13 9 > 1 LEVELINC;
478
+ Serialized BCD (first 8 chars): ACBDAJEI
479
+ """
480
+ action_table = ActionTable(
481
+ entries=[
482
+ ActionTableEntry(
483
+ module_type=ModuleTypeCode.CP20,
484
+ link_number=13,
485
+ module_input=9,
486
+ module_output=1,
487
+ command=InputActionType.LEVELINC,
488
+ parameter=TimeParam.NONE,
489
+ inverted=False,
490
+ )
491
+ ]
492
+ )
493
+
494
+ encoded_string = ActionTableSerializer.to_encoded_string(action_table)
495
+
496
+ # First 8 characters (4 bytes in BCD, high-nibble first):
497
+ # AC = 0x02 (CP20, value=2)
498
+ # BD = 0x13 (link_number=13 in BCD)
499
+ # AJ = 0x09 (module_input=9)
500
+ # EI = 0x48 (output 0-indexed: (1-1) | (LEVELINC<<3) = 0 | 72 = 0x48)
501
+ assert encoded_string[:8] == "ACBDAJEI"
502
+
503
+ def test_from_encoded_string_cp20_link13_input9_output1_levelinc(self):
504
+ """
505
+ Test decoding BCD string ACBDAJEIAA produces expected ActionTable.
506
+
507
+ Serialized BCD: ACBDAJEIAA (+ padding)
508
+ ActionTable: CP20 13 9 > 1 LEVELINC;
509
+ """
510
+ # Build full 960-char encoded string (96 entries × 5 bytes × 2 nibbles)
511
+ # First entry: ACBDAJEIAA, rest is padding (AA = 0x00)
512
+ encoded_string = "ACBDAJEIAA" + "AA" * 475
513
+
514
+ action_table = ActionTableSerializer.from_encoded_string(encoded_string)
515
+
516
+ # Should have exactly 1 entry (padding entries with NOMOD are filtered out)
517
+ assert len(action_table.entries) == 1
518
+
519
+ entry = action_table.entries[0]
520
+ assert entry.module_type == ModuleTypeCode.CP20
521
+ assert entry.link_number == 13
522
+ assert entry.module_input == 9
523
+ assert entry.module_output == 1 # 0-indexed in wire format, converted to 1
524
+ assert entry.command == InputActionType.LEVELINC
525
+ assert entry.parameter == TimeParam.NONE
526
+ assert entry.inverted is False
@@ -408,6 +408,7 @@ class TestActionTableUploadChunkPrefix:
408
408
  mock_reply = Mock()
409
409
  mock_reply.system_function = SystemFunction.ACK
410
410
 
411
+ # First ACK after all chunks sent triggers EOF
411
412
  service._handle_upload_response(mock_reply)
412
413
 
413
414
  # Should send EOF
@@ -418,6 +419,9 @@ class TestActionTableUploadChunkPrefix:
418
419
  data_value="00",
419
420
  )
420
421
 
422
+ # Second ACK (after EOF) triggers finish signal
423
+ service._handle_upload_response(mock_reply)
424
+
421
425
  # Should call finish signal with True
422
426
  mock_finish.assert_called_once_with(True)
423
427
 
@@ -580,11 +584,11 @@ class TestActionTableUploadFullSequence:
580
584
  # Simulate connection made
581
585
  service.connection_made()
582
586
 
583
- # Simulate ACK responses for each chunk + final ACK to trigger EOF
587
+ # Simulate ACK responses for each chunk + ACK to trigger EOF + ACK after EOF
584
588
  mock_ack = Mock()
585
589
  mock_ack.system_function = SystemFunction.ACK
586
590
 
587
- for _ in range(16): # 15 chunks + 1 final ACK to trigger EOF
591
+ for _ in range(17): # 15 chunks + 1 ACK to trigger EOF + 1 ACK after EOF
588
592
  service._handle_upload_response(mock_ack)
589
593
 
590
594
  # Verify: Exactly 17 telegrams sent (1 UPLOAD_ACTIONTABLE + 15 ACTIONTABLE + 1 EOF)
File without changes
File without changes