test_meter_pro.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. from unittest.mock import AsyncMock
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchbotOperationError
  5. from switchbot.devices.meter_pro import MAX_TIME_OFFSET, SwitchbotMeterProCO2
  6. def create_device():
  7. ble_device = BLEDevice(
  8. address="aa:bb:cc:dd:ee:ff", name="any", details={"rssi": -80}
  9. )
  10. device = SwitchbotMeterProCO2(ble_device)
  11. device._send_command = AsyncMock()
  12. return device
  13. @pytest.mark.asyncio
  14. @pytest.mark.parametrize(
  15. (
  16. "device_response",
  17. "expected_offset",
  18. ),
  19. [
  20. ("0100101bc9", 1055689), # 01 (success) 00 (plus offset) 10 1b c9 (1055689)
  21. ("0180101bc9", -1055689), # 01 (success) 80 (minus offset) 10 1b c9 (1055689)
  22. ],
  23. )
  24. async def test_get_time_offset(device_response: str, expected_offset: int):
  25. device = create_device()
  26. device._send_command.return_value = bytes.fromhex(device_response)
  27. offset = await device.get_time_offset()
  28. device._send_command.assert_called_with("570f690506")
  29. assert offset == expected_offset
  30. @pytest.mark.asyncio
  31. async def test_get_time_offset_failure():
  32. device = create_device()
  33. # Invalid 1st byte
  34. device._send_command.return_value = bytes.fromhex("0080101bc9")
  35. with pytest.raises(SwitchbotOperationError):
  36. await device.get_time_offset()
  37. device._send_command.assert_called_with("570f690506")
  38. @pytest.mark.asyncio
  39. async def test_get_time_offset_wrong_response():
  40. device = create_device()
  41. # Response too short (only status byte returned)
  42. device._send_command.return_value = bytes.fromhex("01")
  43. with pytest.raises(SwitchbotOperationError):
  44. await device.get_time_offset()
  45. @pytest.mark.asyncio
  46. @pytest.mark.parametrize(
  47. (
  48. "offset_sec",
  49. "expected_payload",
  50. ),
  51. [
  52. (1055689, "00101bc9"), # "00" for positive offset, 101bc9 for 1055689
  53. (-4096, "80001000"), # "80" for negative offset, 001000 for 4096
  54. (0, "00000000"),
  55. (-0, "00000000"), # -0 == 0 in Python
  56. ],
  57. )
  58. async def test_set_time_offset(offset_sec: int, expected_payload: str):
  59. device = create_device()
  60. device._send_command.return_value = bytes.fromhex("01")
  61. await device.set_time_offset(offset_sec)
  62. device._send_command.assert_called_with("570f680506" + expected_payload)
  63. @pytest.mark.asyncio
  64. async def test_set_time_offset_too_large():
  65. device = create_device()
  66. with pytest.raises(SwitchbotOperationError):
  67. await device.set_time_offset(MAX_TIME_OFFSET + 1)
  68. with pytest.raises(SwitchbotOperationError):
  69. await device.set_time_offset(-(MAX_TIME_OFFSET + 1))
  70. @pytest.mark.asyncio
  71. async def test_set_time_offset_failure():
  72. device = create_device()
  73. device._send_command.return_value = bytes.fromhex("00")
  74. with pytest.raises(SwitchbotOperationError):
  75. await device.set_time_offset(100)
  76. @pytest.mark.asyncio
  77. async def test_get_datetime_success():
  78. device = create_device()
  79. # Mock response:
  80. # byte 0: 01 (success)
  81. # bytes 1-4: e4 02 94 23 (temp, ignored)
  82. # byte 5: 00 (24h mode)
  83. # bytes 6-7: 07 e9 (year 2025)
  84. # byte 8: 0c (Dec)
  85. # byte 9: 1e (30)
  86. # byte 10: 08 (Hour)
  87. # byte 11: 37 (Minute = 55)
  88. # byte 12: 01 (Second)
  89. response_hex = "01e40294230007e90c1e083701"
  90. device._send_command.return_value = bytes.fromhex(response_hex)
  91. result = await device.get_datetime()
  92. device._send_command.assert_called_with("570f6901")
  93. assert result["12h_mode"] is False
  94. assert result["year"] == 2025
  95. assert result["month"] == 12
  96. assert result["day"] == 30
  97. assert result["hour"] == 8
  98. assert result["minute"] == 55
  99. assert result["second"] == 1
  100. @pytest.mark.asyncio
  101. async def test_get_datetime_12h_mode():
  102. device = create_device()
  103. # byte 5: 80 (12h mode)
  104. # Time: 12:00:00
  105. response_hex = "010000000080000001010c0000"
  106. device._send_command.return_value = bytes.fromhex(response_hex)
  107. result = await device.get_datetime()
  108. device._send_command.assert_called_with("570f6901")
  109. assert result["12h_mode"] is True
  110. assert result["year"] == 0
  111. assert result["month"] == 1
  112. assert result["day"] == 1
  113. assert result["hour"] == 12
  114. assert result["minute"] == 0
  115. assert result["second"] == 0
  116. @pytest.mark.asyncio
  117. async def test_get_datetime_failure():
  118. device = create_device()
  119. device._send_command.return_value = bytes.fromhex("00")
  120. with pytest.raises(SwitchbotOperationError):
  121. await device.get_datetime()
  122. @pytest.mark.asyncio
  123. async def test_get_datetime_wrong_response():
  124. device = create_device()
  125. device._send_command.return_value = bytes.fromhex("0100")
  126. with pytest.raises(SwitchbotOperationError):
  127. await device.get_datetime()
  128. @pytest.mark.asyncio
  129. @pytest.mark.parametrize(
  130. (
  131. "timestamp",
  132. "utc_offset_hours",
  133. "utc_offset_minutes",
  134. "expected_ts",
  135. "expected_utc",
  136. "expected_min",
  137. ),
  138. [
  139. (1709251200, 0, 0, "65e11a80", "0c", "00"), # 2024-03-01T00:00:00+00:00
  140. (1709251200, 1, 0, "65e11a80", "0d", "00"), # 2024-03-01T00:00:00+01:00
  141. (1709251200, 5, 45, "65e1250c", "11", "2d"), # 2024-03-01T00:00:00+05:45
  142. (1709251200, -6, 15, "65e11e04", "06", "0f"), # 2024-03-01T00:00:00-05:45
  143. ],
  144. )
  145. async def test_set_datetime( # noqa: PLR0913
  146. timestamp: int,
  147. utc_offset_hours: int,
  148. utc_offset_minutes: int,
  149. expected_ts: str,
  150. expected_utc: str,
  151. expected_min: str,
  152. ):
  153. device = create_device()
  154. device._send_command.return_value = bytes.fromhex("01")
  155. await device.set_datetime(
  156. timestamp,
  157. utc_offset_hours=utc_offset_hours,
  158. utc_offset_minutes=utc_offset_minutes,
  159. )
  160. expected_ts = expected_ts.zfill(16)
  161. expected_payload = "57000503" + expected_utc + expected_ts + expected_min
  162. device._send_command.assert_called_with(expected_payload)
  163. @pytest.mark.asyncio
  164. @pytest.mark.parametrize(
  165. "bad_hour",
  166. [-13, 15],
  167. )
  168. async def test_set_datetime_invalid_utc_offset_hours(bad_hour: int):
  169. device = create_device()
  170. with pytest.raises(SwitchbotOperationError):
  171. await device.set_datetime(1709251200, utc_offset_hours=bad_hour)
  172. @pytest.mark.asyncio
  173. @pytest.mark.parametrize(
  174. "bad_min",
  175. [-1, 60],
  176. )
  177. async def test_set_datetime_invalid_utc_offset_minutes(bad_min: int):
  178. device = create_device()
  179. with pytest.raises(SwitchbotOperationError):
  180. await device.set_datetime(1709251200, utc_offset_minutes=bad_min)
  181. @pytest.mark.asyncio
  182. @pytest.mark.parametrize(
  183. ("is_12h_mode", "expected_payload"),
  184. [
  185. (True, "80"),
  186. (False, "00"),
  187. ],
  188. )
  189. async def test_set_time_display_format(is_12h_mode: bool, expected_payload: str):
  190. device = create_device()
  191. device._send_command.return_value = bytes.fromhex("01")
  192. await device.set_time_display_format(is_12h_mode=is_12h_mode)
  193. device._send_command.assert_called_with("570f680505" + expected_payload)
  194. @pytest.mark.asyncio
  195. async def test_set_time_display_format_failure():
  196. device = create_device()
  197. device._send_command.return_value = bytes.fromhex("00")
  198. with pytest.raises(SwitchbotOperationError):
  199. await device.set_time_display_format(is_12h_mode=True)