velbus-aio 2025.4.2__tar.gz → 2025.8.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.

Potentially problematic release.


This version of velbus-aio might be problematic. Click here for more details.

Files changed (200) hide show
  1. {velbus_aio-2025.4.2/velbus_aio.egg-info → velbus_aio-2025.8.0}/PKG-INFO +1 -1
  2. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/pyproject.toml +2 -2
  3. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0/velbus_aio.egg-info}/PKG-INFO +1 -1
  4. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/controller.py +11 -8
  5. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/handler.py +113 -85
  6. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/message.py +7 -1
  7. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/dali_dim_value_status.py +1 -1
  8. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/dimmer_status.py +12 -1
  9. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/module_type.py +8 -3
  10. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module.py +40 -11
  11. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/17.json +11 -1
  12. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/protocol.py +29 -6
  13. velbus_aio-2025.8.0/velbusaio/vlp_reader.py +113 -0
  14. velbus_aio-2025.4.2/velbusaio/vlp_reader.py +0 -33
  15. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/LICENSE +0 -0
  16. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/MANIFEST.in +0 -0
  17. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/README.md +0 -0
  18. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/requirements.txt +0 -0
  19. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/setup.cfg +0 -0
  20. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/setup.py +0 -0
  21. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbus_aio.egg-info/SOURCES.txt +0 -0
  22. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbus_aio.egg-info/dependency_links.txt +0 -0
  23. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbus_aio.egg-info/not-zip-safe +0 -0
  24. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbus_aio.egg-info/requires.txt +0 -0
  25. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbus_aio.egg-info/top_level.txt +0 -0
  26. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/__init__.py +0 -0
  27. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/channels.py +0 -0
  28. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/command_registry.py +0 -0
  29. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/const.py +0 -0
  30. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/discovery.py +0 -0
  31. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/exceptions.py +0 -0
  32. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/helpers.py +0 -0
  33. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/__init__.py +0 -0
  34. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/blind_status.py +0 -0
  35. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/bus_active.py +0 -0
  36. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/bus_error_counter_status.py +0 -0
  37. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/bus_error_counter_status_request.py +0 -0
  38. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/bus_off.py +0 -0
  39. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/channel_name_part1.py +0 -0
  40. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/channel_name_part2.py +0 -0
  41. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/channel_name_part3.py +0 -0
  42. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/channel_name_request.py +0 -0
  43. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/clear_led.py +0 -0
  44. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/counter_status.py +0 -0
  45. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/counter_status_request.py +0 -0
  46. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/counter_value.py +0 -0
  47. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/cover_down.py +0 -0
  48. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/cover_off.py +0 -0
  49. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/cover_position.py +0 -0
  50. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/cover_up.py +0 -0
  51. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/dali_device_settings.py +0 -0
  52. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/dali_device_settings_request.py +0 -0
  53. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/dimmer_channel_status.py +0 -0
  54. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/edge_set_color.py +0 -0
  55. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/edge_set_custom_color.py +0 -0
  56. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/fast_blinking_led.py +0 -0
  57. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/forced_off.py +0 -0
  58. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/forced_on.py +0 -0
  59. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/interface_status_request.py +0 -0
  60. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/ir_receiver_status.py +0 -0
  61. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/kwh_status.py +0 -0
  62. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/light_value_request.py +0 -0
  63. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/memo_text.py +0 -0
  64. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/memory_data.py +0 -0
  65. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/memory_data_block.py +0 -0
  66. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/memory_dump_request.py +0 -0
  67. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/module_status.py +0 -0
  68. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/module_status_request.py +0 -0
  69. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/module_subtype.py +0 -0
  70. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/module_type_request.py +0 -0
  71. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/psu_load.py +0 -0
  72. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/psu_values.py +0 -0
  73. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/push_button_status.py +0 -0
  74. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/raw.py +0 -0
  75. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/read_data_block_from_memory.py +0 -0
  76. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/read_data_from_memory.py +0 -0
  77. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/realtime_clock_status_request.py +0 -0
  78. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/receive_buffer_full.py +0 -0
  79. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/receive_ready.py +0 -0
  80. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/relay_status.py +0 -0
  81. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/restore_dimmer.py +0 -0
  82. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/select_program.py +0 -0
  83. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/sensor_settings_request.py +0 -0
  84. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/sensor_temp_request.py +0 -0
  85. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/sensor_temperature.py +0 -0
  86. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_date.py +0 -0
  87. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_daylight_saving.py +0 -0
  88. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_dimmer.py +0 -0
  89. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_led.py +0 -0
  90. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_realtime_clock.py +0 -0
  91. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/set_temperature.py +0 -0
  92. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/slider_status.py +0 -0
  93. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/slow_blinking_led.py +0 -0
  94. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/start_relay_blinking_timer.py +0 -0
  95. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/start_relay_timer.py +0 -0
  96. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_relay_off.py +0 -0
  97. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_relay_on.py +0 -0
  98. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_to_comfort.py +0 -0
  99. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_to_day.py +0 -0
  100. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_to_night.py +0 -0
  101. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/switch_to_safe.py +0 -0
  102. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_settings_part1.py +0 -0
  103. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_settings_part2.py +0 -0
  104. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_settings_part3.py +0 -0
  105. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_settings_part4.py +0 -0
  106. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_settings_request.py +0 -0
  107. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_sensor_status.py +0 -0
  108. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_set_cooling.py +0 -0
  109. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/temp_set_heating.py +0 -0
  110. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/update_led_status.py +0 -0
  111. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/very_fast_blinking_led.py +0 -0
  112. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/write_data_to_memory.py +0 -0
  113. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/write_memory_block.py +0 -0
  114. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/messages/write_module_address_and_serial_number.py +0 -0
  115. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/01.json +0 -0
  116. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/02.json +0 -0
  117. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/03.json +0 -0
  118. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/04.json +0 -0
  119. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/05.json +0 -0
  120. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/06.json +0 -0
  121. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/07.json +0 -0
  122. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/08.json +0 -0
  123. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/09.json +0 -0
  124. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/0A.json +0 -0
  125. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/0B.json +0 -0
  126. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/0C.json +0 -0
  127. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/0E.json +0 -0
  128. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/0F.json +0 -0
  129. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/10.json +0 -0
  130. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/11.json +0 -0
  131. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/12.json +0 -0
  132. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/13.json +0 -0
  133. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/14.json +0 -0
  134. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/15.json +0 -0
  135. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/16.json +0 -0
  136. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/18.json +0 -0
  137. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/1A.json +0 -0
  138. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/1B.json +0 -0
  139. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/1D.json +0 -0
  140. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/1E.json +0 -0
  141. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/1F.json +0 -0
  142. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/20.json +0 -0
  143. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/21.json +0 -0
  144. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/22.json +0 -0
  145. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/23.json +0 -0
  146. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/25.json +0 -0
  147. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/28.json +0 -0
  148. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/29.json +0 -0
  149. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2A.json +0 -0
  150. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2B.json +0 -0
  151. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2C.json +0 -0
  152. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2D.json +0 -0
  153. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2E.json +0 -0
  154. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/2F.json +0 -0
  155. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/30.json +0 -0
  156. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/31.json +0 -0
  157. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/32.json +0 -0
  158. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/33.json +0 -0
  159. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/34.json +0 -0
  160. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/35.json +0 -0
  161. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/36.json +0 -0
  162. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/37.json +0 -0
  163. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/38.json +0 -0
  164. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/39.json +0 -0
  165. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3A.json +0 -0
  166. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3B.json +0 -0
  167. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3C.json +0 -0
  168. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3D.json +0 -0
  169. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3E.json +0 -0
  170. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/3F.json +0 -0
  171. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/40.json +0 -0
  172. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/41.json +0 -0
  173. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/42.json +0 -0
  174. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/43.json +0 -0
  175. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/44.json +0 -0
  176. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/45.json +0 -0
  177. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/48.json +0 -0
  178. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/49.json +0 -0
  179. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4A.json +0 -0
  180. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4B.json +0 -0
  181. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4C.json +0 -0
  182. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4D.json +0 -0
  183. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4E.json +0 -0
  184. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/4F.json +0 -0
  185. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/50.json +0 -0
  186. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/51.json +0 -0
  187. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/52.json +0 -0
  188. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/54.json +0 -0
  189. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/55.json +0 -0
  190. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/56.json +0 -0
  191. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/57.json +0 -0
  192. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/5A.json +0 -0
  193. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/5B.json +0 -0
  194. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/5C.json +0 -0
  195. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/5F.json +0 -0
  196. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/60.json +0 -0
  197. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/module_spec/broadcast.json +0 -0
  198. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/py.typed +0 -0
  199. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/raw_message.py +0 -0
  200. {velbus_aio-2025.4.2 → velbus_aio-2025.8.0}/velbusaio/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velbus-aio
3
- Version: 2025.4.2
3
+ Version: 2025.8.0
4
4
  Summary: Open-source home automation platform running on Python 3.
5
5
  Author-email: Maikel Punie <maikel.punie@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ requires = ["setuptools", "wheel"]
4
4
  [project]
5
5
  name = "velbus-aio"
6
6
  license = {text = "MIT"}
7
- version = "2025.4.2"
7
+ version = "2025.8.0"
8
8
  description = "Open-source home automation platform running on Python 3."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -52,7 +52,7 @@ exclude_dirs = ["tests"]
52
52
  skips = ["B301", "B403", "B323", "B104", "B110"]
53
53
 
54
54
  [tool.bumpver]
55
- current_version = "2025.4.2"
55
+ current_version = "2025.8.0"
56
56
  version_pattern = "YYYY.MM.INC0"
57
57
  commit_message = "bump version {old_version} -> {new_version}"
58
58
  commit = true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velbus-aio
3
- Version: 2025.4.2
3
+ Version: 2025.8.0
4
4
  Summary: Open-source home automation platform running on Python 3.
5
5
  Author-email: Maikel Punie <maikel.punie@gmail.com>
6
6
  License: MIT
@@ -17,7 +17,6 @@ from velbusaio.channels import (
17
17
  Blind,
18
18
  Button,
19
19
  ButtonCounter,
20
- Channel,
21
20
  Dimmer,
22
21
  EdgeLit,
23
22
  LightSensor,
@@ -61,7 +60,7 @@ class Velbus:
61
60
  self._closing = False
62
61
  self._auto_reconnect = True
63
62
 
64
- self._dsn = dsn
63
+ self._destination = dsn
65
64
  self._handler = PacketHandler(self, one_address)
66
65
  self._modules: dict[int, Module] = {}
67
66
  self._submodules: list[int] = []
@@ -146,12 +145,12 @@ class Velbus:
146
145
  """Connect to the bus and load all the data."""
147
146
  await self._handler.read_protocol_data()
148
147
  # connect to the bus
149
- if ":" in self._dsn:
148
+ if ":" in self._destination:
150
149
  # tcp/ip combination
151
- if not re.search(r"^[A-Za-z0-9+.\-]+://", self._dsn):
150
+ if not re.search(r"^[A-Za-z0-9+.\-]+://", self._destination):
152
151
  # if no scheme, then add the tcp://
153
- self._dsn = f"tcp://{self._dsn}"
154
- parts = urlparse(self._dsn)
152
+ self._destination = f"tcp://{self._destination}"
153
+ parts = urlparse(self._destination)
155
154
  if parts.scheme == "tls":
156
155
  ctx = ssl._create_unverified_context()
157
156
  else:
@@ -176,7 +175,7 @@ class Velbus:
176
175
  await serial_asyncio_fast.create_serial_connection(
177
176
  asyncio.get_event_loop(),
178
177
  lambda: self._protocol,
179
- url=self._dsn,
178
+ url=self._destination,
180
179
  baudrate=38400,
181
180
  bytesize=serial.EIGHTBITS,
182
181
  parity=serial.PARITY_NONE,
@@ -190,7 +189,7 @@ class Velbus:
190
189
 
191
190
  async def start(self) -> None:
192
191
  # if auth is required send the auth key
193
- parts = urlparse(self._dsn)
192
+ parts = urlparse(self._destination)
194
193
  if parts.username:
195
194
  await self._protocol.write_auth_key(parts.username)
196
195
  # scan the bus
@@ -277,3 +276,7 @@ class Velbus:
277
276
  await self.send(SetRealtimeClock(wday=lclt[6], hour=lclt[3], min=lclt[4]))
278
277
  await self.send(SetDate(day=lclt[2], mon=lclt[1], year=lclt[0]))
279
278
  await self.send(SetDaylightSaving(ds=not lclt[8]))
279
+
280
+ async def wait_on_all_messages_sent_async(self) -> None:
281
+ """Wait for all messages to be sent."""
282
+ await self._protocol.wait_on_all_messages_sent_async()
@@ -11,9 +11,9 @@ import json
11
11
  import logging
12
12
  import os
13
13
  import pathlib
14
- import pprint
15
14
  import sys
16
- from typing import TYPE_CHECKING, Awaitable, Callable
15
+ import time
16
+ from typing import TYPE_CHECKING
17
17
 
18
18
  from aiofile import async_open
19
19
 
@@ -23,10 +23,9 @@ from velbusaio.const import (
23
23
  SCAN_MODULEINFO_TIMEOUT_INTERVAL,
24
24
  SCAN_MODULETYPE_TIMEOUT,
25
25
  )
26
- from velbusaio.helpers import h2, keys_exists
27
- from velbusaio.message import Message
28
26
  from velbusaio.messages.module_subtype import ModuleSubTypeMessage
29
27
  from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
28
+ from velbusaio.module import Module
30
29
  from velbusaio.raw_message import RawMessage
31
30
 
32
31
  if TYPE_CHECKING:
@@ -35,7 +34,7 @@ if TYPE_CHECKING:
35
34
 
36
35
  class PacketHandler:
37
36
  """
38
- The packetHandler class
37
+ The PacketHandler class
39
38
  """
40
39
 
41
40
  def __init__(
@@ -49,9 +48,11 @@ class PacketHandler:
49
48
  self._one_address = one_address
50
49
  self._typeResponseReceived = asyncio.Event()
51
50
  self._scanLock = asyncio.Lock()
51
+ self._fullScanLock = asyncio.Lock()
52
52
  self._modulescan_address = 0
53
53
  self._scan_complete = False
54
54
  self._scan_delay_msec = 0
55
+ self.__scan_found_addresses: dict[int, ModuleTypeMessage | None] | None = None
55
56
 
56
57
  async def read_protocol_data(self):
57
58
  if sys.version_info >= (3, 13):
@@ -85,84 +86,123 @@ class PacketHandler:
85
86
  return False
86
87
 
87
88
  async def scan(self, reload_cache: bool = False) -> None:
88
- if reload_cache:
89
- self._modulescan_address = 0
90
- self._scan_complete = False
91
- # non-blocking check to see if the cache_dir is empty
92
- loop = asyncio.get_running_loop()
93
- if not reload_cache and await loop.run_in_executor(None, self.empty_cache):
94
- self._log.info("No cache yet, so forcing a bus scan")
95
- reload_cache = True
89
+ start_address = 1
90
+ max_address = 254 + 1
91
+ if self._one_address is not None:
92
+ start_address = self._one_address
93
+ max_address = self._one_address + 1
94
+ self._log.info(
95
+ f"Scanning only one address {self._one_address} ({self._one_address:#02x})"
96
+ )
97
+
96
98
  self._log.info("Start module scan")
97
- while self._modulescan_address < 254:
98
- address = 0
99
- module = None
100
- async with self._scanLock:
101
- self._modulescan_address = self._modulescan_address + 1
102
- address = self._modulescan_address
103
- if self._velbus.addr_is_submodule(address):
99
+ async with self._fullScanLock:
100
+ start_time = time.perf_counter()
101
+ self._scan_complete = False
102
+
103
+ self._log.debug("Waiting for Velbus bus to be ready to scan...")
104
+ await self._velbus.wait_on_all_messages_sent_async() # don't start a scan while messages are still in the queue
105
+ self._log.debug("Velbus bus is ready to scan!")
106
+
107
+ self._log.info("Sending scan type requests to all addresses...")
108
+ start_scan_time = time.perf_counter()
109
+ self.__scan_found_addresses = {}
110
+ for address in range(start_address, max_address):
111
+ cfile = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
112
+ if reload_cache and os.path.isfile(cfile):
104
113
  self._log.info(
105
- f"Skipping submodule address {address}, already handled"
114
+ f"Reloading cache for address {address} ({address:#02x})"
115
+ )
116
+ os.remove(cfile)
117
+
118
+ self.__scan_found_addresses[address] = None
119
+ async with self._scanLock:
120
+ await self._velbus.sendTypeRequestMessage(address)
121
+
122
+ await self._velbus.wait_on_all_messages_sent_async()
123
+ scan_time = time.perf_counter() - start_scan_time
124
+ self._log.info(
125
+ f"Sent scan type requests to all addresses in {scan_time:.2f}. Going to wait for responses..."
126
+ )
127
+
128
+ await asyncio.sleep(SCAN_MODULETYPE_TIMEOUT / 1000) # wait for responses
129
+
130
+ self._log.info(
131
+ "Waiting for responses done. Going to check for responses..."
132
+ )
133
+ for address in range(start_address, max_address):
134
+ start_module_scan = time.perf_counter()
135
+ module_type_message: ModuleTypeMessage | None = (
136
+ self.__scan_found_addresses[address]
137
+ )
138
+ module: Module | None = None
139
+ if module_type_message is None:
140
+ self._log.debug(
141
+ f"No module found at address {address} ({address:#02x}). Skipping it."
106
142
  )
107
143
  continue
108
- self._log.info(f"Starting handling scan {address}")
109
- module = self._velbus.get_module(address)
110
144
 
111
- if self._one_address is not None and address != int(self._one_address):
112
145
  self._log.info(
113
- f"Skipping address {address} as we requested to only scan one address {self._one_address}"
146
+ f"Found module at address {address} ({address:#02x}): {module_type_message.module_type_name()}"
114
147
  )
115
- continue
116
-
117
- cfile = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
118
- # cleanup the old module cache if needed
119
- scanModule = reload_cache
120
- if scanModule and os.path.isfile(cfile):
121
- os.remove(cfile)
122
- elif os.path.isfile(cfile):
123
- scanModule = os.path.isfile(cfile)
124
- if scanModule:
148
+ # cache_file = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
149
+ # TODO: check if cached file module type is the same?
150
+ await self._handle_module_type(module_type_message)
151
+ async with self._scanLock:
152
+ module = self._velbus.get_module(address)
153
+
154
+ if module is None:
155
+ self._log.info(
156
+ f"Module at address {address} ({address:#02x}) could not be loaded. Skipping it."
157
+ )
158
+ continue
159
+
125
160
  try:
126
- self._log.info(f"Starting scan {address}")
127
- self._typeResponseReceived.clear()
128
- await self._velbus.sendTypeRequestMessage(address)
161
+ self._log.debug(
162
+ f"Module {module.get_address()} ({module.get_address():#02x}) detected: start loading"
163
+ )
129
164
  await asyncio.wait_for(
130
- self._typeResponseReceived.wait(),
131
- SCAN_MODULETYPE_TIMEOUT / 1000.0,
165
+ module.load(from_cache=True),
166
+ SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
132
167
  )
133
- async with self._scanLock:
134
- module = self._velbus.get_module(address)
135
- except asyncio.TimeoutError:
168
+ self._scan_delay_msec = module.get_initial_timeout()
169
+ while self._scan_delay_msec > 50 and not await module.is_loaded():
170
+ # self._log.debug(
171
+ # f"\t... waiting {self._scan_delay_msec} is_loaded={await module.is_loaded()}"
172
+ # )
173
+ self._scan_delay_msec = self._scan_delay_msec - 50
174
+ await asyncio.sleep(0.05)
175
+ module_scan_time = time.perf_counter() - start_module_scan
136
176
  self._log.info(
137
- f"Scan module {address} failed: not present or unavailable"
177
+ f"Scan module {address} ({address:#02x}, {module.get_type_name()}) completed in {module_scan_time:.2f}, module loaded={await module.is_loaded()}"
138
178
  )
139
- if module is not None:
140
- try:
141
- self._log.debug(
142
- f"Module {module._address} detected: start loading"
143
- )
144
- await asyncio.wait_for(
145
- module.load(from_cache=True),
146
- SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
147
- )
148
- self._scan_delay_msec = module.get_initial_timeout()
149
- while (
150
- self._scan_delay_msec > 50 and not await module.is_loaded()
151
- ):
152
- # self._log.debug(
153
- # f"\t... waiting {self._scan_delay_msec} is_loaded={await module.is_loaded()}"
154
- # )
155
- self._scan_delay_msec = self._scan_delay_msec - 50
156
- await asyncio.sleep(0.05)
157
- self._log.info(
158
- f"Scan module {address} completed, module loaded={await module.is_loaded()}"
159
- )
160
- except asyncio.TimeoutError:
161
- self._log.error(
162
- f"Module {address} did not respond to info requests after successful type request"
163
- )
164
- self._scan_complete = True
165
- self._log.info("Module scan completed")
179
+ except asyncio.TimeoutError:
180
+ self._log.error(
181
+ f"Module {address} ({address:#02x}) did not respond to info requests after successful type request"
182
+ )
183
+
184
+ self._scan_complete = True
185
+ total_time = time.perf_counter() - start_time
186
+ self._log.info(f"Module scan completed in {total_time:.2f} seconds")
187
+
188
+ async def __handle_module_type_response_async(self, rawmsg: RawMessage) -> None:
189
+ """
190
+ Handle a received module type response packet
191
+ """
192
+ address = rawmsg.address
193
+
194
+ if self.__scan_found_addresses is None:
195
+ self._log.warning(
196
+ f"Received module type response for address {address} ({address:#02x}) but no scan in progress"
197
+ )
198
+ return
199
+
200
+ tmsg: ModuleTypeMessage = ModuleTypeMessage()
201
+ tmsg.populate(rawmsg.priority, address, rawmsg.rtr, rawmsg.data_only)
202
+ self._log.debug(
203
+ f"A '{tmsg.module_type_name()}' ({tmsg.module_type:#02x}) lives on address {address} ({address:#02x})"
204
+ )
205
+ self.__scan_found_addresses[address] = tmsg
166
206
 
167
207
  async def handle(self, rawmsg: RawMessage) -> None:
168
208
  """
@@ -180,20 +220,8 @@ class PacketHandler:
180
220
  data = rawmsg.data_only
181
221
 
182
222
  # handle module type response message
183
- if command_value == 0xFF and not self._scan_complete:
184
- tmsg: ModuleTypeMessage = ModuleTypeMessage()
185
- tmsg.populate(priority, address, rtr, data)
186
- async with self._scanLock:
187
- await self._handle_module_type(tmsg)
188
- if address == self._modulescan_address:
189
- self._typeResponseReceived.set()
190
- else:
191
- self._log.debug(
192
- f"Unexpected module type message module address {address}, Velbuslink scan?"
193
- )
194
- self._modulescan_address = address - 1
195
-
196
- self._typeResponseReceived.set()
223
+ if command_value == 0xFF:
224
+ await self.__handle_module_type_response_async(rawmsg)
197
225
 
198
226
  # handle module subtype response message
199
227
  elif command_value in (0xB0, 0xA7, 0xA6) and not self._scan_complete:
@@ -4,6 +4,7 @@ The velbus abstract message class
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import enum
7
8
  import json
8
9
 
9
10
  from velbusaio.const import PRIORITY_FIRMWARE, PRIORITY_HIGH, PRIORITY_LOW
@@ -65,8 +66,13 @@ class Message:
65
66
  continue
66
67
  if callable(getattr(self, key)) or key.startswith("__"):
67
68
  del me[key]
68
- if isinstance(me[key], (bytes, bytearray)):
69
+ if isinstance(me[key], (bytes, bytearray, enum.Enum)):
69
70
  me[key] = str(me[key])
71
+ else:
72
+ try:
73
+ json.dumps(me[key]) # Test if the value is JSON serializable
74
+ except (TypeError, ValueError):
75
+ me[key] = str(me[key]) # Convert non-serializable objects to string
70
76
  return me
71
77
 
72
78
  def to_json(self) -> str:
@@ -10,7 +10,7 @@ from velbusaio.message import Message
10
10
  COMMAND_CODE = 0xA5
11
11
 
12
12
 
13
- @register(COMMAND_CODE, ["VMBDALI", "VMBDALI-20"])
13
+ @register(COMMAND_CODE, ["VMBDALI", "VMBDALI-20", "VMB8DC-20", "VMB4LEDPWM-20"])
14
14
  class DimValueStatus(Message):
15
15
  """
16
16
  send by: VMBDALI
@@ -25,7 +25,18 @@ LED_FAST_BLINKING = 1 << 5
25
25
  LED_VERY_FAST_BLINKING = 1 << 4
26
26
 
27
27
 
28
- @register(COMMAND_CODE, ["VMB1DM", "VMBDME", "VMB1LED"])
28
+ @register(
29
+ COMMAND_CODE,
30
+ [
31
+ "VMB1DM",
32
+ "VMBDME",
33
+ "VMB1LED",
34
+ "VMBDALI",
35
+ "VMBDALI-20",
36
+ "VMB8DC-20",
37
+ "VMB4LEDPWM-20",
38
+ ],
39
+ )
29
40
  class DimmerStatusMessage(Message):
30
41
  """
31
42
  sent by: VMBDME
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  import struct
8
8
 
9
- from velbusaio.command_registry import register
9
+ from velbusaio.command_registry import MODULE_DIRECTORY, register
10
10
  from velbusaio.message import Message
11
11
 
12
12
  COMMAND_CODE = 0xFF
@@ -110,11 +110,16 @@ class ModuleTypeMessage(Message):
110
110
  self.build_week = 0
111
111
  self.set_defaults(address)
112
112
 
113
- def module_name(self) -> str:
113
+ def module_type_name(self) -> str:
114
114
  """
115
115
  :return: str
116
116
  """
117
- return "Unknown"
117
+
118
+ return (
119
+ MODULE_DIRECTORY[self.module_type]
120
+ if self.module_type in MODULE_DIRECTORY
121
+ else "Unknown"
122
+ )
118
123
 
119
124
  def populate(self, priority, address, rtr, data) -> None:
120
125
  """
@@ -165,6 +165,7 @@ class Module:
165
165
  self._is_loading = False
166
166
  self._channels = {}
167
167
  self.loaded = False
168
+ self._loaded_cache = {}
168
169
 
169
170
  def get_initial_timeout(self) -> int:
170
171
  return SCAN_MODULEINFO_TIMEOUT_INITIAL
@@ -243,6 +244,9 @@ class Module:
243
244
  d["channels"][num] = chan.to_cache()
244
245
  return d
245
246
 
247
+ def get_address(self) -> int:
248
+ return self._address
249
+
246
250
  def get_addresses(self) -> list:
247
251
  """
248
252
  Get all addresses for this module
@@ -550,6 +554,10 @@ class Module:
550
554
  await self._update_channel(
551
555
  message.channel, {"power": message.power, "energy": message.energy}
552
556
  )
557
+ elif isinstance(message, DimValueStatus):
558
+ for offset, dim_value in enumerate(message.dim_values):
559
+ channel = message.channel + offset
560
+ await self._update_channel(channel, {"state": dim_value})
553
561
 
554
562
  async def _update_channel(self, channel: int, updates: dict):
555
563
  try:
@@ -569,12 +577,8 @@ class Module:
569
577
  # start the loading
570
578
  self._is_loading = True
571
579
  # see if we have a cache
572
- try:
573
- cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
574
- async with async_open(cfile, "r") as fl:
575
- cache = json.loads(await fl.read())
576
- except OSError:
577
- cache = {}
580
+ cache = await self._get_cache()
581
+ self._loaded_cache = cache
578
582
  # load default channels
579
583
  await self._load_default_channels()
580
584
 
@@ -589,8 +593,8 @@ class Module:
589
593
  if "channels" in cache:
590
594
  for num, chan in cache["channels"].items():
591
595
  self._channels[int(num)]._name = chan["name"]
592
- if "sub_device" in chan:
593
- self._channels[int(num)]._sub_device = chan["sub_device"]
596
+ if "subdevice" in chan:
597
+ self._channels[int(num)]._sub_device = chan["subdevice"]
594
598
  else:
595
599
  self._channels[int(num)]._sub_device = False
596
600
  if "Unit" in chan:
@@ -604,6 +608,15 @@ class Module:
604
608
  self._is_loading = False
605
609
  await self._request_module_status()
606
610
 
611
+ async def _get_cache(self):
612
+ try:
613
+ cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
614
+ async with async_open(cfile, "r") as fl:
615
+ cache = json.loads(await fl.read())
616
+ except OSError:
617
+ cache = {}
618
+ return cache
619
+
607
620
  def _load(self) -> None:
608
621
  """
609
622
  Method for per module type loading
@@ -857,7 +870,25 @@ class VmbDali(Module):
857
870
  if message.channel in self._channels:
858
871
  del self._channels[message.channel]
859
872
  elif message.data.device_type == DaliDeviceType.LedModule:
860
- if self._channels.get(message.channel).__class__ != Dimmer:
873
+ cache = self._loaded_cache
874
+ if (
875
+ "channels" in cache
876
+ and str(message.channel) in cache["channels"]
877
+ and cache["channels"][str(message.channel)]["type"] == "Dimmer"
878
+ ):
879
+ # If we have a cached dimmer channel, use that name
880
+ name = cache["channels"][str(message.channel)]["name"]
881
+ self._channels[message.channel] = Dimmer(
882
+ self,
883
+ message.channel,
884
+ name,
885
+ False, # set False to enable an already loaded Dimmer
886
+ True,
887
+ self._writer,
888
+ self._address,
889
+ slider_scale=254,
890
+ )
891
+ elif self._channels.get(message.channel).__class__ != Dimmer:
861
892
  # New or changed type, replace channel:
862
893
  self._channels[message.channel] = Dimmer(
863
894
  self,
@@ -914,8 +945,6 @@ class VmbDali(Module):
914
945
  else:
915
946
  return await super().on_message(message)
916
947
 
917
- await self._cache()
918
-
919
948
  async def _request_channel_name(self) -> None:
920
949
  # Channel names are requested after channel scan
921
950
  # don't do them here (at initialization time)
@@ -113,7 +113,17 @@
113
113
  "03FE": { "ModuleName": "62" },
114
114
  "03FF": { "ModuleName": "64:Save" }
115
115
  },
116
- "ModuleName": "03C0-03FF"
116
+ "ModuleName": "03C0-03FF",
117
+ "Channels": {
118
+ "01": "0000-000F",
119
+ "02": "0010-001F",
120
+ "03": "0020-002F",
121
+ "04": "0030-003F",
122
+ "05": "0040-004F",
123
+ "06": "0050-005F",
124
+ "07": "0060-006F",
125
+ "08": "0070-007F"
126
+ }
117
127
  },
118
128
  "Type": "VMB6PBN"
119
129
  }
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import binascii
5
5
  import logging
6
+ import time
6
7
  import typing as t
7
8
  from asyncio import transports
8
9
 
@@ -180,20 +181,22 @@ class VelbusProtocol(asyncio.BufferedProtocol):
180
181
  await self._write_transport_lock.acquire()
181
182
  while self._restart_writer:
182
183
  # wait for an item from the queue
183
- msg_info = await self._send_queue.get()
184
+ msg_info: RawMessage | None = await self._send_queue.get()
184
185
  if msg_info is None:
185
186
  self._restart_writer = False
186
187
  return
187
188
  message_sent = False
188
189
  try:
190
+ start_time = time.perf_counter()
189
191
  while not message_sent:
190
192
  message_sent = await self._write_message(msg_info)
191
- if msg_info.command == 0xEF:
192
- # 'channel name request' command provokes in worst case 99 answer packets from VMBGPOD
193
- queue_sleep_time = SLEEP_TIME * 33
194
- else:
195
- queue_sleep_time = SLEEP_TIME
193
+ send_time = time.perf_counter() - start_time
194
+
195
+ self._send_queue.task_done() # indicate that the item of the queue has been processed
196
+
197
+ queue_sleep_time = self._calculate_queue_sleep_time(msg_info, send_time)
196
198
  await asyncio.sleep(queue_sleep_time)
199
+
197
200
  except (asyncio.CancelledError, GeneratorExit) as exc:
198
201
  if not self._closing:
199
202
  self._log.error(f"Stopping Velbus writer due to {exc!r}")
@@ -205,6 +208,22 @@ class VelbusProtocol(asyncio.BufferedProtocol):
205
208
  self._write_transport_lock.release()
206
209
  self._log.debug("Ending Velbus write message from send queue")
207
210
 
211
+ @staticmethod
212
+ def _calculate_queue_sleep_time(msg_info, send_time):
213
+ sleep_time = SLEEP_TIME
214
+
215
+ if msg_info.rtr:
216
+ sleep_time = SLEEP_TIME # this is a scan command. We could be quicker?
217
+
218
+ if msg_info.command == 0xEF:
219
+ # 'channel name request' command provokes in worst case 99 answer packets from VMBGPOD
220
+ sleep_time = SLEEP_TIME * 33 # TODO make this adaptable on module_type
221
+
222
+ if send_time > sleep_time:
223
+ return 0 # no need to wait, we are already late
224
+ else:
225
+ return sleep_time - send_time
226
+
208
227
  @backoff.on_predicate(
209
228
  backoff.expo,
210
229
  lambda is_sent: not is_sent,
@@ -218,3 +237,7 @@ class VelbusProtocol(asyncio.BufferedProtocol):
218
237
  return True
219
238
  else:
220
239
  return False
240
+
241
+ async def wait_on_all_messages_sent_async(self) -> None:
242
+ self._log.debug("Waiting on all messages sent")
243
+ await self._send_queue.join()