PySwitchbot 0.67.0__tar.gz → 0.68.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.
Files changed (93) hide show
  1. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PKG-INFO +1 -1
  2. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/setup.py +1 -1
  4. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/__init__.py +6 -1
  5. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/device.py +15 -1
  6. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/lock.py +6 -0
  7. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/relay_switch.py +1 -0
  8. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_encrypted_device.py +19 -0
  9. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_lock.py +29 -0
  10. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_relay_switch.py +11 -0
  11. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/LICENSE +0 -0
  12. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/MANIFEST.in +0 -0
  13. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PySwitchbot.egg-info/SOURCES.txt +0 -0
  14. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  15. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PySwitchbot.egg-info/requires.txt +0 -0
  16. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/PySwitchbot.egg-info/top_level.txt +0 -0
  17. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/README.md +0 -0
  18. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/pyproject.toml +0 -0
  19. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/setup.cfg +0 -0
  20. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parser.py +0 -0
  21. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/__init__.py +0 -0
  22. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/air_purifier.py +0 -0
  23. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/blind_tilt.py +0 -0
  24. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/bot.py +0 -0
  25. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/bulb.py +0 -0
  26. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/ceiling_light.py +0 -0
  27. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/contact.py +0 -0
  28. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/curtain.py +0 -0
  29. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/fan.py +0 -0
  30. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/hub2.py +0 -0
  31. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/hub3.py +0 -0
  32. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/hubmini_matter.py +0 -0
  33. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/humidifier.py +0 -0
  34. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/keypad.py +0 -0
  35. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/leak.py +0 -0
  36. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/light_strip.py +0 -0
  37. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/lock.py +0 -0
  38. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/meter.py +0 -0
  39. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/motion.py +0 -0
  40. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/plug.py +0 -0
  41. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/relay_switch.py +0 -0
  42. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/remote.py +0 -0
  43. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/roller_shade.py +0 -0
  44. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/adv_parsers/vacuum.py +0 -0
  45. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/api_config.py +0 -0
  46. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/__init__.py +0 -0
  47. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/air_purifier.py +0 -0
  48. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/evaporative_humidifier.py +0 -0
  49. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/fan.py +0 -0
  50. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/hub2.py +0 -0
  51. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/hub3.py +0 -0
  52. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/light.py +0 -0
  53. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/const/lock.py +0 -0
  54. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/__init__.py +0 -0
  55. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/air_purifier.py +0 -0
  56. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/base_cover.py +0 -0
  57. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/base_light.py +0 -0
  58. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/blind_tilt.py +0 -0
  59. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/bot.py +0 -0
  60. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/bulb.py +0 -0
  61. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/ceiling_light.py +0 -0
  62. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/contact.py +0 -0
  63. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/curtain.py +0 -0
  64. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/evaporative_humidifier.py +0 -0
  65. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/fan.py +0 -0
  66. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/humidifier.py +0 -0
  67. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/keypad.py +0 -0
  68. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/light_strip.py +0 -0
  69. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/meter.py +0 -0
  70. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/motion.py +0 -0
  71. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/plug.py +0 -0
  72. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/roller_shade.py +0 -0
  73. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/devices/vacuum.py +0 -0
  74. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/discovery.py +0 -0
  75. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/enum.py +0 -0
  76. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/helpers.py +0 -0
  77. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/switchbot/models.py +0 -0
  78. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_adv_parser.py +0 -0
  79. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_air_purifier.py +0 -0
  80. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_base_cover.py +0 -0
  81. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_blind_tilt.py +0 -0
  82. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_bulb.py +0 -0
  83. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_ceiling_light.py +0 -0
  84. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_colormode_imports.py +0 -0
  85. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_curtain.py +0 -0
  86. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_evaporative_humidifier.py +0 -0
  87. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_fan.py +0 -0
  88. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_helpers.py +0 -0
  89. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_hub2.py +0 -0
  90. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_hub3.py +0 -0
  91. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_roller_shade.py +0 -0
  92. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_strip_light.py +0 -0
  93. {pyswitchbot-0.67.0 → pyswitchbot-0.68.1}/tests/test_vacuum.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.67.0
3
+ Version: 0.68.1
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.67.0
3
+ Version: 0.68.1
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -20,7 +20,7 @@ setup(
20
20
  "cryptography>=39.0.0",
21
21
  "pyOpenSSL>=23.0.0",
22
22
  ],
23
- version="0.67.0",
23
+ version="0.68.1",
24
24
  description="A library to communicate with Switchbot",
25
25
  long_description=long_description,
26
26
  long_description_content_type="text/markdown",
@@ -32,7 +32,11 @@ from .devices.bot import Switchbot
32
32
  from .devices.bulb import SwitchbotBulb
33
33
  from .devices.ceiling_light import SwitchbotCeilingLight
34
34
  from .devices.curtain import SwitchbotCurtain
35
- from .devices.device import SwitchbotDevice, SwitchbotEncryptedDevice
35
+ from .devices.device import (
36
+ SwitchbotDevice,
37
+ SwitchbotEncryptedDevice,
38
+ SwitchbotOperationError,
39
+ )
36
40
  from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
37
41
  from .devices.fan import SwitchbotFan
38
42
  from .devices.humidifier import SwitchbotHumidifier
@@ -78,6 +82,7 @@ __all__ = [
78
82
  "SwitchbotLock",
79
83
  "SwitchbotModel",
80
84
  "SwitchbotModel",
85
+ "SwitchbotOperationError",
81
86
  "SwitchbotPlugMini",
82
87
  "SwitchbotPlugMini",
83
88
  "SwitchbotRelaySwitch",
@@ -127,6 +127,7 @@ class SwitchbotBaseDevice:
127
127
 
128
128
  _turn_on_command: str | None = None
129
129
  _turn_off_command: str | None = None
130
+ _press_command: str | None = None
130
131
 
131
132
  def __init__(
132
133
  self,
@@ -294,7 +295,7 @@ class SwitchbotBaseDevice:
294
295
  """Return RSSI of device."""
295
296
  if self._sb_adv_data:
296
297
  return self._sb_adv_data.rssi
297
- return self._device.rssi
298
+ return -127
298
299
 
299
300
  async def _ensure_connected(self):
300
301
  """Ensure connection to device is established."""
@@ -718,6 +719,13 @@ class SwitchbotBaseDevice:
718
719
  result = await self._send_command(self._turn_off_command)
719
720
  return self._check_command_result(result, 0, {1})
720
721
 
722
+ @update_after_operation
723
+ async def press(self) -> bool:
724
+ """Press the device."""
725
+ self._check_function_support(self._press_command)
726
+ result = await self._send_command(self._press_command)
727
+ return self._check_command_result(result, 0, {1})
728
+
721
729
 
722
730
  class SwitchbotDevice(SwitchbotBaseDevice):
723
731
  """
@@ -947,6 +955,12 @@ class SwitchbotEncryptedDevice(SwitchbotDevice):
947
955
  if len(data) == 0:
948
956
  return b""
949
957
  if self._iv is None:
958
+ if self._expected_disconnect:
959
+ _LOGGER.debug(
960
+ "%s: Cannot decrypt, IV is None during expected disconnect",
961
+ self.name,
962
+ )
963
+ return b""
950
964
  raise RuntimeError("Cannot decrypt: IV is None")
951
965
  decryptor = self._get_cipher().decryptor()
952
966
  return decryptor.update(data) + decryptor.finalize()
@@ -214,6 +214,12 @@ class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
214
214
 
215
215
  def _notification_handler(self, _sender: int, data: bytearray) -> None:
216
216
  if self._notifications_enabled and self._check_command_result(data, 0, {0xF}):
217
+ if self._expected_disconnect:
218
+ _LOGGER.debug(
219
+ "%s: Ignoring lock notification during expected disconnect",
220
+ self.name,
221
+ )
222
+ return
217
223
  self._update_lock_status(data)
218
224
  else:
219
225
  super()._notification_handler(_sender, data)
@@ -61,6 +61,7 @@ class SwitchbotRelaySwitch(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
61
61
 
62
62
  _turn_on_command = f"{COMMAND_CONTROL}010100"
63
63
  _turn_off_command = f"{COMMAND_CONTROL}010000"
64
+ _press_command = f"{COMMAND_CONTROL}110329" # for garage door opener toggle
64
65
 
65
66
  def __init__(
66
67
  self,
@@ -365,3 +365,22 @@ async def test_empty_data_encryption_decryption() -> None:
365
365
  # Test empty decryption
366
366
  decrypted = device._decrypt(bytearray())
367
367
  assert decrypted == b""
368
+
369
+
370
+ @pytest.mark.asyncio
371
+ async def test_decrypt_with_none_iv_during_disconnect() -> None:
372
+ """Test that decryption returns empty bytes when IV is None during expected disconnect."""
373
+ device = create_encrypted_device()
374
+
375
+ # Simulate disconnection in progress
376
+ device._expected_disconnect = True
377
+ device._iv = None
378
+
379
+ # Should return empty bytes instead of raising
380
+ result = device._decrypt(bytearray(b"encrypted_data"))
381
+ assert result == b""
382
+
383
+ # Verify it still raises when not disconnecting
384
+ device._expected_disconnect = False
385
+ with pytest.raises(RuntimeError, match="Cannot decrypt: IV is None"):
386
+ device._decrypt(bytearray(b"encrypted_data"))
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from unittest.mock import AsyncMock, Mock, patch
2
3
 
3
4
  import pytest
@@ -478,6 +479,34 @@ def test_notification_handler_not_enabled(model: str):
478
479
  mock_super.assert_called_once()
479
480
 
480
481
 
482
+ @pytest.mark.parametrize(
483
+ "model",
484
+ [
485
+ SwitchbotModel.LOCK,
486
+ SwitchbotModel.LOCK_LITE,
487
+ SwitchbotModel.LOCK_PRO,
488
+ SwitchbotModel.LOCK_ULTRA,
489
+ ],
490
+ )
491
+ def test_notification_handler_during_disconnect(
492
+ model: str, caplog: pytest.LogCaptureFixture
493
+ ) -> None:
494
+ """Test _notification_handler during expected disconnect."""
495
+ device = create_device_for_command_testing(model)
496
+ device._notifications_enabled = True
497
+ device._expected_disconnect = True
498
+ data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
499
+ with (
500
+ patch.object(device, "_update_lock_status") as mock_update,
501
+ caplog.at_level(logging.DEBUG),
502
+ ):
503
+ device._notification_handler(0, data)
504
+ # Should not update lock status during disconnect
505
+ mock_update.assert_not_called()
506
+ # Should log debug message
507
+ assert "Ignoring lock notification during expected disconnect" in caplog.text
508
+
509
+
481
510
  @pytest.mark.parametrize(
482
511
  "model",
483
512
  [
@@ -451,3 +451,14 @@ def test_merge_data(old_data, new_data, expected_result):
451
451
  """Test merging of data dictionaries."""
452
452
  result = merge_data(old_data, new_data)
453
453
  assert result == expected_result
454
+
455
+
456
+ @pytest.mark.asyncio
457
+ async def test_press():
458
+ """Test the press command for garage door opener."""
459
+ device = create_device_for_command_testing(
460
+ b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
461
+ )
462
+
463
+ await device.press()
464
+ device._send_command.assert_awaited_once_with(device._press_command)
File without changes
File without changes
File without changes
File without changes