1
0

test_outdoor_plug_eu.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. from unittest.mock import AsyncMock
  2. import pytest
  3. from switchbot import (
  4. SwitchBotAdvertisement,
  5. SwitchbotModel,
  6. SwitchbotOutdoorPlug,
  7. parse_advertisement_data,
  8. )
  9. from switchbot.adv_parsers.plug import process_outdoor_plug
  10. from switchbot.devices.device import API_MODEL_TO_ENUM
  11. from .test_adv_parser import generate_advertisement_data, generate_ble_device
  12. @pytest.mark.parametrize("network", [0, 0x15, 0x75])
  13. @pytest.mark.parametrize(("status", "is_on"), [(0x01, False), (0x87, True)])
  14. @pytest.mark.parametrize("encrypted", [False, True])
  15. def test_outdoor_plug_eu_advertisement(network, status, is_on, encrypted):
  16. service = bytes([0x80 if encrypted else 0, 0, 0, 0, 0x12, 0x18, 0xD0, network])
  17. manufacturer = (
  18. bytes.fromhex("010203040506")
  19. + bytes([99, status, 0, 0])
  20. + bytes.fromhex("04e6007b0000")
  21. )
  22. adv = parse_advertisement_data(
  23. generate_ble_device("01:02:03:04:05:06", "Outdoor Plug (EU)"),
  24. generate_advertisement_data(
  25. manufacturer_data={2409: manufacturer},
  26. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": service},
  27. ),
  28. )
  29. assert adv is not None
  30. assert adv.data["modelName"] == SwitchbotModel.OUTDOOR_PLUG_EU
  31. assert adv.data["modelFriendlyName"] == "Outdoor Plug (EU)"
  32. assert adv.data["isEncrypted"] is encrypted
  33. assert adv.data["data"] == {
  34. "switchMode": True,
  35. "sequence_number": 99,
  36. "isOn": is_on,
  37. "power": 1254.0,
  38. "power_production": 123.0,
  39. }
  40. def create_device():
  41. return SwitchbotOutdoorPlug(
  42. generate_ble_device("01:02:03:04:05:06", "Outdoor Plug"),
  43. "ff",
  44. "ffffffffffffffffffffffffffffffff",
  45. )
  46. @pytest.mark.asyncio
  47. @pytest.mark.parametrize("is_on", [False, True])
  48. async def test_bidirectional_metering(is_on):
  49. device = create_device()
  50. device.get_current_time_and_start_time = lambda: ("00000010", "00000000")
  51. common = bytes([1, 99, 0x81 if is_on else 1]) + bytes(13) + bytes([3])
  52. consumed = bytes.fromhex("0100ea60003a98003c08fc03e808fc")
  53. produced = bytes.fromhex("0100bb80007530001e08fc07d011f8")
  54. device._get_basic_info = AsyncMock(side_effect=[common, consumed, produced])
  55. info = await device.get_basic_info()
  56. assert info["isOn"] is is_on
  57. assert info["energy"] == 1.0
  58. assert info["energy_production"] == 0.8
  59. assert info["power"] == (230.0 if is_on else 0)
  60. assert info["power_production"] == (460.0 if is_on else 0)
  61. assert info["current"] == (1.0 if is_on else 0)
  62. assert info["current_production"] == (2.0 if is_on else 0)
  63. assert info["voltage"] == (230 if is_on else 0)
  64. assert device._get_basic_info.call_args_list[1].args == (
  65. "570f7106000000001000000000",
  66. )
  67. assert device._get_basic_info.call_args_list[2].args == (
  68. "570f7106010000001000000000",
  69. )
  70. @pytest.mark.asyncio
  71. @pytest.mark.parametrize("reply", [None, b"", b"\x01", bytes(14)])
  72. async def test_short_production_response(reply):
  73. device = create_device()
  74. common = bytes([1, 99, 0x81]) + bytes(13) + bytes([3])
  75. device._get_basic_info = AsyncMock(side_effect=[common, bytes(15), reply])
  76. assert await device.get_basic_info() is None
  77. @pytest.mark.parametrize("manufacturer", [None, b"", bytes(13)])
  78. def test_short_advertisement(manufacturer):
  79. assert process_outdoor_plug(None, manufacturer) == {}
  80. @pytest.mark.asyncio
  81. async def test_advertisement_preserves_energy_and_refreshes_power():
  82. device = create_device()
  83. device.update = AsyncMock()
  84. def advertisement(sequence, power, production):
  85. return SwitchBotAdvertisement(
  86. address="01:02:03:04:05:06",
  87. data={
  88. "data": {
  89. "sequence_number": sequence,
  90. "isOn": True,
  91. "power": power,
  92. "power_production": production,
  93. }
  94. },
  95. device=device._device,
  96. rssi=-40,
  97. active=True,
  98. )
  99. device.update_from_advertisement(advertisement(1, 20, 30))
  100. device.data["data"].update(
  101. {"energy": 1.2, "energy_production": 3.4, "current_production": 0.5}
  102. )
  103. device.update_from_advertisement(advertisement(2, 40, 50))
  104. assert device.parsed_data["energy"] == 1.2
  105. assert device.parsed_data["energy_production"] == 3.4
  106. assert device.parsed_data["current_production"] == 0.5
  107. assert device.parsed_data["power"] == 40
  108. assert device.parsed_data["power_production"] == 50
  109. def test_cloud_model_mapping():
  110. assert API_MODEL_TO_ENUM["W1186000"] is SwitchbotModel.OUTDOOR_PLUG_EU