velbus-aio 2024.5.1__tar.gz → 2024.7.1__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 (113) hide show
  1. {velbus_aio-2024.5.1/velbus_aio.egg-info → velbus_aio-2024.7.1}/PKG-INFO +1 -1
  2. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/pyproject.toml +2 -2
  3. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/requirements.txt +2 -0
  4. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1/velbus_aio.egg-info}/PKG-INFO +1 -1
  5. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/channels.py +1 -0
  6. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/const.py +7 -1
  7. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/controller.py +24 -55
  8. velbus_aio-2024.7.1/velbusaio/handler.py +277 -0
  9. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/message.py +6 -6
  10. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/module_subtype.py +3 -3
  11. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/module_type.py +5 -5
  12. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/module.py +11 -16
  13. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/protocol.json +68 -0
  14. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/protocol.py +4 -10
  15. velbus_aio-2024.5.1/velbusaio/handler.py +0 -163
  16. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/LICENSE +0 -0
  17. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/MANIFEST.in +0 -0
  18. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/README.md +0 -0
  19. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/setup.cfg +0 -0
  20. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/setup.py +0 -0
  21. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbus_aio.egg-info/SOURCES.txt +0 -0
  22. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbus_aio.egg-info/dependency_links.txt +0 -0
  23. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbus_aio.egg-info/not-zip-safe +0 -0
  24. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbus_aio.egg-info/requires.txt +0 -0
  25. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbus_aio.egg-info/top_level.txt +0 -0
  26. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/__init__.py +0 -0
  27. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/command_registry.py +0 -0
  28. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/discovery.py +0 -0
  29. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/exceptions.py +0 -0
  30. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/helpers.py +0 -0
  31. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/__init__.py +0 -0
  32. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/blind_status.py +0 -0
  33. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/bus_active.py +0 -0
  34. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/bus_error_counter_status.py +0 -0
  35. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/bus_error_counter_status_request.py +0 -0
  36. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/bus_off.py +0 -0
  37. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/channel_name_part1.py +0 -0
  38. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/channel_name_part2.py +0 -0
  39. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/channel_name_part3.py +0 -0
  40. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/channel_name_request.py +0 -0
  41. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/clear_led.py +0 -0
  42. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/counter_status.py +0 -0
  43. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/counter_status_request.py +0 -0
  44. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/cover_down.py +0 -0
  45. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/cover_off.py +0 -0
  46. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/cover_position.py +0 -0
  47. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/cover_up.py +0 -0
  48. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/dali_device_settings.py +0 -0
  49. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/dali_device_settings_request.py +0 -0
  50. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/dali_dim_value_status.py +0 -0
  51. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/dimmer_channel_status.py +0 -0
  52. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/dimmer_status.py +0 -0
  53. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/edge_set_color.py +0 -0
  54. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/edge_set_custom_color.py +0 -0
  55. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/fast_blinking_led.py +0 -0
  56. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/forced_off.py +0 -0
  57. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/forced_on.py +0 -0
  58. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/interface_status_request.py +0 -0
  59. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/ir_receiver_status.py +0 -0
  60. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/kwh_status.py +0 -0
  61. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/light_value_request.py +0 -0
  62. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/memo_text.py +0 -0
  63. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/memory_data.py +0 -0
  64. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/memory_data_block.py +0 -0
  65. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/memory_dump_request.py +0 -0
  66. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/module_status.py +0 -0
  67. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/module_status_request.py +0 -0
  68. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/module_type_request.py +0 -0
  69. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/push_button_status.py +0 -0
  70. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/raw.py +0 -0
  71. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/read_data_block_from_memory.py +0 -0
  72. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/read_data_from_memory.py +0 -0
  73. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/realtime_clock_status_request.py +0 -0
  74. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/receive_buffer_full.py +0 -0
  75. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/receive_ready.py +0 -0
  76. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/relay_status.py +0 -0
  77. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/restore_dimmer.py +0 -0
  78. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/select_program.py +0 -0
  79. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/sensor_settings_request.py +0 -0
  80. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/sensor_temp_request.py +0 -0
  81. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/sensor_temperature.py +0 -0
  82. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_date.py +0 -0
  83. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_daylight_saving.py +0 -0
  84. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_dimmer.py +0 -0
  85. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_led.py +0 -0
  86. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_realtime_clock.py +0 -0
  87. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/set_temperature.py +0 -0
  88. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/slider_status.py +0 -0
  89. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/slow_blinking_led.py +0 -0
  90. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/start_relay_blinking_timer.py +0 -0
  91. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/start_relay_timer.py +0 -0
  92. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_relay_off.py +0 -0
  93. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_relay_on.py +0 -0
  94. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_to_comfort.py +0 -0
  95. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_to_day.py +0 -0
  96. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_to_night.py +0 -0
  97. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/switch_to_safe.py +0 -0
  98. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_settings_part1.py +0 -0
  99. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_settings_part2.py +0 -0
  100. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_settings_part3.py +0 -0
  101. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_settings_part4.py +0 -0
  102. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_settings_request.py +0 -0
  103. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_sensor_status.py +0 -0
  104. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_set_cooling.py +0 -0
  105. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/temp_set_heating.py +0 -0
  106. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/update_led_status.py +0 -0
  107. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/very_fast_blinking_led.py +0 -0
  108. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/write_data_to_memory.py +0 -0
  109. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/write_memory_block.py +0 -0
  110. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/messages/write_module_address_and_serial_number.py +0 -0
  111. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/py.typed +0 -0
  112. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/raw_message.py +0 -0
  113. {velbus_aio-2024.5.1 → velbus_aio-2024.7.1}/velbusaio/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: velbus-aio
3
- Version: 2024.5.1
3
+ Version: 2024.7.1
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 = "2024.5.1"
7
+ version = "2024.7.1"
8
8
  description = "Open-source home automation platform running on Python 3."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -51,7 +51,7 @@ exclude_dirs = ["tests"]
51
51
  skips = ["B301", "B403", "B323", "B104", "B110"]
52
52
 
53
53
  [tool.bumpver]
54
- current_version = "2024.5.1"
54
+ current_version = "2024.7.1"
55
55
  version_pattern = "YYYY.MM.INC0"
56
56
  commit_message = "bump version {old_version} -> {new_version}"
57
57
  commit = true
@@ -1,3 +1,5 @@
1
+ aiofile
1
2
  backoff>=1.10.0
2
3
  pyserial>=3.5.0
3
4
  pyserial-asyncio-fast>=0.11
5
+ setuptools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: velbus-aio
3
- Version: 2024.5.1
3
+ Version: 2024.7.1
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
@@ -508,6 +508,7 @@ class Temperature(Channel):
508
508
  _min = None
509
509
  _target = 0
510
510
  _cmode = None
511
+ _coolmode = None
511
512
  _cstatus = None
512
513
  _thermostat = False
513
514
  _sleep_timer = 0
@@ -38,7 +38,13 @@ RTR: Final = 0x40
38
38
  NO_RTR: Final = 0x00
39
39
 
40
40
  CACHEDIR: Final = ".velbuscache"
41
- LOAD_TIMEOUT: Final = 600
41
+
42
+ # Module scan timeout values (in mSec)
43
+ SCAN_MODULETYPE_TIMEOUT: Final = 2000 # time to wait for ModuleTypeRequest
44
+ SCAN_MODULEINFO_TIMEOUT_INITIAL: Final = 1000 # time to wait for first info (status)
45
+ SCAN_MODULEINFO_TIMEOUT_INTERVAL: Final = (
46
+ 150 # time to wait for info interval (between next message)
47
+ )
42
48
 
43
49
  DEVICE_CLASS_ILLUMINANCE: Final = "illuminance"
44
50
  DEVICE_CLASS_TEMPERATURE: Final = "temperature"
@@ -14,7 +14,6 @@ import serial
14
14
  import serial_asyncio_fast
15
15
 
16
16
  from velbusaio.channels import Channel
17
- from velbusaio.const import LOAD_TIMEOUT
18
17
  from velbusaio.exceptions import VelbusConnectionFailed
19
18
  from velbusaio.handler import PacketHandler
20
19
  from velbusaio.helpers import get_cache_dir
@@ -42,20 +41,22 @@ class Velbus:
42
41
  self._protocol = VelbusProtocol(
43
42
  message_received_callback=self._on_message_received,
44
43
  connection_lost_callback=self._on_connection_lost,
45
- end_of_scan_callback=self._on_end_of_scan,
46
44
  )
47
45
  self._closing = False
48
46
  self._auto_reconnect = True
49
47
 
50
48
  self._dsn = dsn
51
- self._handler = PacketHandler(self.send, self)
49
+ self._handler = PacketHandler(self)
52
50
  self._modules: dict[int, Module] = {}
53
51
  self._submodules: list[int] = []
54
- self._send_queue = asyncio.Queue()
55
- self._cache_dir = cache_dir
52
+ self._send_queue: asyncio.Queue = asyncio.Queue()
53
+ self._cache_dir: str = cache_dir
56
54
  # make sure the cachedir exists
57
55
  pathlib.Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
58
56
 
57
+ def get_cache_dir(self) -> str:
58
+ return self._cache_dir
59
+
59
60
  async def _on_message_received(self, msg: RawMessage) -> None:
60
61
  """On message received function."""
61
62
  await self._handler.handle(msg)
@@ -66,11 +67,7 @@ class Velbus:
66
67
  self._log.debug("Reconnecting to transport")
67
68
  asyncio.ensure_future(self.connect())
68
69
 
69
- def _on_end_of_scan(self) -> None:
70
- """Notify the scan failure."""
71
- self._handler.scan_finished()
72
-
73
- async def add_module(
70
+ def add_module(
74
71
  self,
75
72
  addr: int,
76
73
  typ: int,
@@ -81,8 +78,7 @@ class Velbus:
81
78
  build_week: int | None = None,
82
79
  ) -> None:
83
80
  """Add a found module to the module cache."""
84
- self._log.info(f"Found module: type:{typ} address:{addr}")
85
- self._modules[addr] = Module.factory(
81
+ module = Module.factory(
86
82
  addr,
87
83
  typ,
88
84
  data,
@@ -92,30 +88,31 @@ class Velbus:
92
88
  memorymap=memorymap,
93
89
  cache_dir=self._cache_dir,
94
90
  )
95
- self._modules[addr].initialize(self.send)
96
- await self._modules[addr].load()
91
+ module.initialize(self.send)
92
+ self._modules[addr] = module
93
+ self._log.info(f"Found module {addr}: {module}")
97
94
 
98
- async def add_submodules(self, addr: int, subList: dict[int, int]) -> None:
95
+ def add_submodules(self, module: Module, subList: dict[int, int]) -> None:
99
96
  """Add submodules address to module."""
100
97
  for sub_num, sub_addr in subList.items():
101
98
  if sub_addr == 0xFF:
102
99
  continue
103
100
  self._submodules.append(sub_addr)
104
- self._modules[addr]._sub_address[sub_num] = sub_addr
105
- self._modules[sub_addr] = self._modules[addr]
106
- self._modules[addr].cleanupSubChannels()
101
+ module._sub_address[sub_num] = sub_addr
102
+ self._modules[sub_addr] = module
103
+ module.cleanupSubChannels()
107
104
 
108
105
  def get_modules(self) -> dict:
109
106
  """Return the module cache."""
110
107
  return self._modules
111
108
 
112
- def get_module(self, addr: str) -> None | Module:
109
+ def get_module(self, addr: int) -> None | Module:
113
110
  """Get a module on an address."""
114
111
  if addr in self._modules:
115
112
  return self._modules[addr]
116
113
  return None
117
114
 
118
- def get_channels(self, addr: str) -> None | dict:
115
+ def get_channels(self, addr: int) -> None | dict:
119
116
  """Get the channels for an address."""
120
117
  if addr in self._modules:
121
118
  return (self._modules[addr]).get_channels()
@@ -129,6 +126,7 @@ class Velbus:
129
126
 
130
127
  async def connect(self, test_connect: bool = False) -> None:
131
128
  """Connect to the bus and load all the data."""
129
+ await self._handler.read_protocol_data()
132
130
  auth = None
133
131
  # connect to the bus
134
132
  if ":" in self._dsn:
@@ -181,44 +179,15 @@ class Velbus:
181
179
  await self._protocol.write_auth_key(auth)
182
180
 
183
181
  # scan the bus
184
- await self.scan()
182
+ await self._handler.scan()
185
183
 
186
184
  async def scan(self) -> None:
187
- """Scan the bus."""
188
- self._handler.scan_started()
189
- for addr in range(1, 256):
190
- msg = ModuleTypeRequestMessage(addr)
191
- await self.send(msg)
192
- await self._handler._scan_complete_event.wait()
193
- # calculate how long to wait
194
- calc_timeout = len(self._modules) * 30
195
- if calc_timeout < LOAD_TIMEOUT:
196
- timeout = calc_timeout
197
- else:
198
- timeout = LOAD_TIMEOUT
199
- # create a task to wait until we have all modules loaded
200
- tsk = asyncio.Task(self._check_if_modules_are_loaded())
201
- try:
202
- await asyncio.wait_for(tsk, timeout=timeout)
203
- except asyncio.TimeoutError:
204
- self._log.error(
205
- f"Not all modules are loaded within a timeout of {LOAD_TIMEOUT} seconds, continuing with the loaded modules"
206
- )
185
+ """Service endpoint to restart the scan"""
186
+ await self._handler.scan(True)
207
187
 
208
- async def _check_if_modules_are_loaded(self) -> None:
209
- """Task to wait until modules are loaded."""
210
- while True:
211
- mods_loaded = 0
212
- for mod in (self.get_modules()).values():
213
- if mod.is_loaded():
214
- mods_loaded += 1
215
- else:
216
- self._log.warning(f"Waiting for module {mod._address}")
217
- if mods_loaded == len(self.get_modules()):
218
- self._log.info("All modules loaded")
219
- return
220
- self._log.info("Not all modules loaded yet, waiting 15 seconds")
221
- await asyncio.sleep(15)
188
+ async def sendTypeRequestMessage(self, address: int) -> None:
189
+ msg = ModuleTypeRequestMessage(address)
190
+ await self.send(msg)
222
191
 
223
192
  async def send(self, msg: Message) -> None:
224
193
  """Send a packet."""
@@ -0,0 +1,277 @@
1
+ """
2
+ Velbus packet handler
3
+ :Author maikel punie <maikel.punie@gmail.com>
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from velbusaio.const import SCAN_MODULETYPE_TIMEOUT
9
+ from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INITIAL
10
+ from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INTERVAL
11
+
12
+ import asyncio
13
+ import json
14
+ import logging
15
+ import threading
16
+ import os
17
+ import pathlib
18
+
19
+ from aiofile import async_open
20
+
21
+ from typing import TYPE_CHECKING, Awaitable, Callable
22
+ import pkg_resources
23
+
24
+ from velbusaio.command_registry import commandRegistry
25
+ from velbusaio.helpers import h2, keys_exists
26
+ from velbusaio.message import Message
27
+ from velbusaio.messages.module_subtype import ModuleSubTypeMessage
28
+ from velbusaio.messages.module_type import ModuleTypeMessage, ModuleType2Message
29
+ from velbusaio.raw_message import RawMessage
30
+
31
+
32
+ if TYPE_CHECKING:
33
+ from velbusaio.controller import Velbus
34
+
35
+
36
+ class PacketHandler:
37
+ """
38
+ The packetHandler class
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ velbus: Velbus,
44
+ ) -> None:
45
+ self._log = logging.getLogger("velbus-handler")
46
+ self._log.setLevel(logging.DEBUG)
47
+ self._velbus = velbus
48
+ self._typeResponseReceived = asyncio.Event()
49
+ self._scanLock = threading.Lock()
50
+ self._modulescan_address = 0
51
+ self._scan_complete = False
52
+ self._scan_delay_msec = 0
53
+
54
+ async def read_protocol_data(self):
55
+ async with async_open(
56
+ pkg_resources.resource_filename(__name__, "protocol.json")
57
+ ) as protocol_file:
58
+ self.pdata = json.loads(await protocol_file.read())
59
+
60
+ def empty_cache(self) -> bool:
61
+ if (
62
+ len(
63
+ [
64
+ name
65
+ for name in os.listdir(f"{self._velbus.get_cache_dir()}")
66
+ if os.path.isfile(f"{self._velbus.get_cache_dir()}/{name}")
67
+ ]
68
+ )
69
+ == 0
70
+ ):
71
+ return True
72
+ return False
73
+
74
+ async def scan(self, reload_cache: bool = False) -> None:
75
+ if reload_cache:
76
+ self._modulescan_address = 0
77
+ self._scan_complete = False
78
+ # non-blocking check to see if the cache_dir is empty
79
+ loop = asyncio.get_running_loop()
80
+ print(self._velbus.get_cache_dir())
81
+ print(await loop.run_in_executor(None, self.empty_cache))
82
+ if not reload_cache and await loop.run_in_executor(None, self.empty_cache):
83
+ self._log.info("No cache yet, so forcing a bus scan")
84
+ reload_cache = True
85
+ self._log.info("Start module scan")
86
+ while self._modulescan_address < 254:
87
+ address = 0
88
+ module = None
89
+ with self._scanLock:
90
+ self._modulescan_address = self._modulescan_address + 1
91
+ address = self._modulescan_address
92
+ module = self._velbus.get_module(address)
93
+
94
+ self._log.info(f"Starting handling scan {address}")
95
+
96
+ cfile = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
97
+ # cleanup the old module cache if needed
98
+ scanModule = reload_cache
99
+ if scanModule and os.path.isfile(cfile):
100
+ os.remove(cfile)
101
+ elif os.path.isfile(cfile):
102
+ scanModule = os.path.isfile(cfile)
103
+ if scanModule:
104
+ try:
105
+ self._log.info(f"Starting scan {address}")
106
+ self._typeResponseReceived.clear()
107
+ await self._velbus.sendTypeRequestMessage(address)
108
+ await asyncio.wait_for(
109
+ self._typeResponseReceived.wait(),
110
+ SCAN_MODULETYPE_TIMEOUT / 1000.0,
111
+ )
112
+ with self._scanLock:
113
+ module = self._velbus.get_module(address)
114
+ except asyncio.TimeoutError:
115
+ self._log.info(
116
+ f"Scan module {address} failed: not present or unavailable"
117
+ )
118
+ if module is not None:
119
+ try:
120
+ self._log.debug(f"Module {address} detected: start loading")
121
+ await asyncio.wait_for(
122
+ module.load(from_cache=True),
123
+ SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
124
+ )
125
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INITIAL
126
+ while self._scan_delay_msec > 50 and not module.is_loaded():
127
+ # self._log.debug(
128
+ # f"\t... waiting {self._scan_delay_msec} is_loaded={module.is_loaded()}"
129
+ # )
130
+ self._scan_delay_msec = self._scan_delay_msec - 50
131
+ await asyncio.sleep(0.05)
132
+ self._log.info(
133
+ f"Scan module {address} completed, module loaded={module.is_loaded()}"
134
+ )
135
+ except asyncio.TimeoutError:
136
+ self._log.error(
137
+ f"Module {address} did not respond to info requests after successful type request"
138
+ )
139
+ self._scan_complete = True
140
+ self._log.info("Module scan completed")
141
+
142
+ async def handle(self, rawmsg: RawMessage) -> None:
143
+ """
144
+ Handle a received packet
145
+ """
146
+ if rawmsg.address < 1 or rawmsg.address > 254:
147
+ return
148
+ if rawmsg.command is None:
149
+ return
150
+
151
+ priority = rawmsg.priority
152
+ address = rawmsg.address
153
+ rtr = rawmsg.rtr
154
+ command_value = rawmsg.command
155
+ data = rawmsg.data_only
156
+
157
+ # handle module type response message
158
+ if command_value == 0xFF:
159
+ if not self._scan_complete:
160
+ tmsg: ModuleTypeMessage = ModuleTypeMessage()
161
+ tmsg.populate(priority, address, rtr, data)
162
+ with self._scanLock:
163
+ self._handle_module_type(tmsg)
164
+ if address == self._modulescan_address:
165
+ self._typeResponseReceived.set()
166
+ else:
167
+ self._log.debug(
168
+ f"Unexpected module type message module address {address}, Velbuslink scan?"
169
+ )
170
+ self._modulescan_address = address - 1
171
+
172
+ self._typeResponseReceived.set()
173
+
174
+ # handle module subtype response message
175
+ elif command_value in (0xB0, 0xA7, 0xA6):
176
+ if not self._scan_complete:
177
+ msg: ModuleSubTypeMessage = ModuleSubTypeMessage()
178
+ msg.populate(priority, address, rtr, data)
179
+ if command_value == 0xB0:
180
+ msg.sub_address_offset = 0
181
+ elif command_value == 0xA7:
182
+ msg.sub_address_offset = 4
183
+ elif command_value == 0xA6:
184
+ msg.sub_address_offset = 8
185
+ with self._scanLock:
186
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INITIAL
187
+ self._handle_module_subtype(msg)
188
+
189
+ # ignore broadcast
190
+ elif command_value in self.pdata["MessagesBroadCast"]:
191
+ self._log.debug(
192
+ "Received broadcast message {} from {}, ignoring".format(
193
+ self.pdata["MessageBroadCast"][str(command_value).upper()], address
194
+ )
195
+ )
196
+
197
+ # handle other messages for modules that are already scanned
198
+ else:
199
+ module = None
200
+ with self._scanLock:
201
+ module = self._velbus.get_module(address)
202
+ if module is not None:
203
+ module_type = module.get_type()
204
+ if commandRegistry.has_command(int(command_value), module_type):
205
+ command = commandRegistry.get_command(command_value, module_type)
206
+ if not command:
207
+ return
208
+ msg = command()
209
+ msg.populate(priority, address, rtr, data)
210
+ # restart the info completion time when info message received
211
+ if command_value in (
212
+ 0xF0,
213
+ 0xF1,
214
+ 0xF2,
215
+ 0xFB,
216
+ 0xFE,
217
+ 0xCC,
218
+ ): # names, memory data, memory block
219
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INTERVAL
220
+ # self._log.debug(f"Restart timeout {msg}")
221
+ # send the message to the modules
222
+ await module.on_message(msg)
223
+ else:
224
+ self._log.warning(f"NOT FOUND IN command_registry: {rawmsg}")
225
+
226
+ def _handle_module_type(self, msg: ModuleTypeMessage | ModuleType2Message) -> None:
227
+ """
228
+ load the module data
229
+ """
230
+ if msg is not None:
231
+ module = self._velbus.get_module(msg.address)
232
+ if module is None:
233
+ data = keys_exists(self.pdata, "ModuleTypes", h2(msg.module_type))
234
+ if not data:
235
+ self._log.warning(f"Module not recognized: {msg.module_type}")
236
+ return
237
+ self._velbus.add_module(
238
+ msg.address,
239
+ msg.module_type,
240
+ data,
241
+ memorymap=msg.memory_map_version,
242
+ build_year=msg.build_year,
243
+ build_week=msg.build_week,
244
+ serial=msg.serial,
245
+ )
246
+ else:
247
+ self._log.debug(
248
+ f"***Module already exists scanAddr={self._modulescan_address} addr={msg.address} {msg}"
249
+ )
250
+
251
+ # else:
252
+ # self._log.debug("*** handle_module_type called without response message")
253
+
254
+ def _handle_module_subtype(self, msg: ModuleSubTypeMessage) -> None:
255
+ module = self._velbus.get_module(msg.address)
256
+ if module is not None:
257
+ addrList = {
258
+ (msg.sub_address_offset + 1): msg.sub_address_1,
259
+ (msg.sub_address_offset + 2): msg.sub_address_2,
260
+ (msg.sub_address_offset + 3): msg.sub_address_3,
261
+ (msg.sub_address_offset + 4): msg.sub_address_4,
262
+ }
263
+ self._velbus.add_submodules(module, addrList)
264
+
265
+
266
+ # def _channel_convert(self, module: str, channel: str, ctype: str) -> None | int:
267
+ # data = keys_exists(
268
+ # self.pdata, "ModuleTypes", h2(module), "ChannelNumbers", ctype
269
+ # )
270
+ # if data and "Map" in data and h2(channel) in data["Map"]:
271
+ # return data["Map"][h2(channel)]
272
+ # if data and "Convert" in data:
273
+ # return int(channel)
274
+ # for offset in range(0, 8):
275
+ # if channel & (1 << offset):
276
+ # return offset + 1
277
+ # return None
@@ -20,19 +20,19 @@ class Message:
20
20
  Base Velbus message
21
21
  """
22
22
 
23
- def __init__(self, address: int = None) -> None:
23
+ def __init__(self, address: int = 0) -> None:
24
24
  self.priority = PRIORITY_LOW
25
- self.address = None
26
- self.rtr = False
25
+ self.address: int = 0
26
+ self.rtr: bool = False
27
27
  self.data = bytearray()
28
28
  self.set_defaults(address)
29
29
 
30
- def set_attributes(self, priority: int, address: int, rtr: int) -> None:
30
+ def set_attributes(self, priority: int, address: int, rtr: bool) -> None:
31
31
  self.priority = priority
32
32
  self.address = address
33
33
  self.rtr = rtr
34
34
 
35
- def populate(self, priority: int, address: int, rtr: int, data: int) -> None:
35
+ def populate(self, priority: int, address: int, rtr: bool, data: int) -> None:
36
36
  raise NotImplementedError
37
37
 
38
38
  def set_defaults(self, address: int | None) -> None:
@@ -66,7 +66,7 @@ class Message:
66
66
  if callable(getattr(self, key)) or key.startswith("__"):
67
67
  del me[key]
68
68
  if isinstance(me[key], (bytes, bytearray)):
69
- me[key] = str(me[key], "utf-8")
69
+ me[key] = str(me[key])
70
70
  return me
71
71
 
72
72
  def to_json(self) -> str:
@@ -25,7 +25,7 @@ class ModuleSubTypeMessage(Message):
25
25
 
26
26
  # pylint: disable-msg=R0902
27
27
 
28
- def __init__(self, address=None, sub_address_offset: int = 0):
28
+ def __init__(self, address=None, sub_address_offset: int = 0) -> None:
29
29
  Message.__init__(self)
30
30
  self.module_type = 0x00
31
31
  self.sub_address_1 = 0xFF
@@ -36,13 +36,13 @@ class ModuleSubTypeMessage(Message):
36
36
  self.serial = 0
37
37
  self.sub_address_offset = sub_address_offset
38
38
 
39
- def module_name(self):
39
+ def module_name(self) -> str:
40
40
  """
41
41
  :return: str
42
42
  """
43
43
  return "Unknown"
44
44
 
45
- def populate(self, priority, address, rtr, data):
45
+ def populate(self, priority, address, rtr, data) -> None:
46
46
  """
47
47
  :return: None
48
48
  """
@@ -98,7 +98,7 @@ class ModuleTypeMessage(Message):
98
98
 
99
99
  # pylint: disable-msg=R0902
100
100
 
101
- def __init__(self, address=None):
101
+ def __init__(self, address=None) -> None:
102
102
  Message.__init__(self)
103
103
  self.module_type = 0x00
104
104
  self.led_on = []
@@ -110,13 +110,13 @@ class ModuleTypeMessage(Message):
110
110
  self.build_week = 0
111
111
  self.set_defaults(address)
112
112
 
113
- def module_name(self):
113
+ def module_name(self) -> str:
114
114
  """
115
115
  :return: str
116
116
  """
117
117
  return "Unknown"
118
118
 
119
- def populate(self, priority, address, rtr, data):
119
+ def populate(self, priority, address, rtr, data) -> None:
120
120
  """
121
121
  :return: None
122
122
  """
@@ -151,7 +151,7 @@ class ModuleTypeMessage(Message):
151
151
  ],
152
152
  )
153
153
  class ModuleType2Message(Message):
154
- def __init__(self, address=None):
154
+ def __init__(self, address=None) -> None:
155
155
  Message.__init__(self)
156
156
  self.module_type = 0x00
157
157
  self.led_on = []
@@ -164,7 +164,7 @@ class ModuleType2Message(Message):
164
164
  self.term = 0
165
165
  self.set_defaults(address)
166
166
 
167
- def module_name(self):
167
+ def module_name(self) -> str:
168
168
  """
169
169
  :return: str
170
170
  """