1
0

test_relay_switch.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. from unittest.mock import AsyncMock, MagicMock
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotModel
  5. from switchbot.devices import relay_switch
  6. from switchbot.devices.device import _merge_data as merge_data
  7. from .test_adv_parser import generate_ble_device
  8. common_params = [
  9. (b";\x00\x00\x00", SwitchbotModel.RELAY_SWITCH_1),
  10. (b"<\x00\x00\x00", SwitchbotModel.RELAY_SWITCH_1PM),
  11. (b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER),
  12. (b"?\x00\x00\x00", SwitchbotModel.PLUG_MINI_EU),
  13. ]
  14. @pytest.fixture
  15. def common_parametrize_2pm():
  16. """Provide common test data."""
  17. return {
  18. "rawAdvData": b"\x00\x00\x00\x00\x00\x00",
  19. "model": SwitchbotModel.RELAY_SWITCH_2PM,
  20. }
  21. def create_device_for_command_testing(
  22. rawAdvData: bytes, model: str, init_data: dict | None = None
  23. ):
  24. """Create a device for command testing."""
  25. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  26. if model == SwitchbotModel.GARAGE_DOOR_OPENER:
  27. device_class = relay_switch.SwitchbotGarageDoorOpener
  28. elif model == SwitchbotModel.RELAY_SWITCH_2PM:
  29. device_class = relay_switch.SwitchbotRelaySwitch2PM
  30. else:
  31. device_class = relay_switch.SwitchbotRelaySwitch
  32. device = device_class(
  33. ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=model
  34. )
  35. device.update_from_advertisement(
  36. make_advertisement_data(ble_device, rawAdvData, model, init_data)
  37. )
  38. device._send_command = AsyncMock()
  39. device._check_command_result = MagicMock()
  40. device.update = AsyncMock()
  41. return device
  42. def make_advertisement_data(
  43. ble_device: BLEDevice, rawAdvData: bytes, model: str, init_data: dict | None = None
  44. ):
  45. """Set advertisement data with defaults."""
  46. if init_data is None:
  47. init_data = {}
  48. if model == SwitchbotModel.RELAY_SWITCH_2PM:
  49. return SwitchBotAdvertisement(
  50. address="aa:bb:cc:dd:ee:ff",
  51. data={
  52. "rawAdvData": rawAdvData,
  53. "data": {
  54. 1: {
  55. "switchMode": True,
  56. "sequence_number": 99,
  57. "isOn": True,
  58. },
  59. 2: {
  60. "switchMode": True,
  61. "sequence_number": 99,
  62. "isOn": False,
  63. },
  64. }
  65. | init_data,
  66. "isEncrypted": False,
  67. },
  68. device=ble_device,
  69. rssi=-80,
  70. active=True,
  71. )
  72. if model == SwitchbotModel.GARAGE_DOOR_OPENER:
  73. return SwitchBotAdvertisement(
  74. address="aa:bb:cc:dd:ee:ff",
  75. data={
  76. "rawAdvData": rawAdvData,
  77. "data": {
  78. "switchMode": True,
  79. "sequence_number": 96,
  80. "isOn": True,
  81. "door_open": False,
  82. }
  83. | init_data,
  84. "isEncrypted": False,
  85. },
  86. device=ble_device,
  87. rssi=-80,
  88. active=True,
  89. )
  90. return SwitchBotAdvertisement(
  91. address="aa:bb:cc:dd:ee:ff",
  92. data={
  93. "rawAdvData": rawAdvData,
  94. "data": {
  95. "switchMode": True,
  96. "sequence_number": 96,
  97. "isOn": True,
  98. }
  99. | init_data,
  100. "isEncrypted": False,
  101. },
  102. device=ble_device,
  103. rssi=-80,
  104. active=True,
  105. )
  106. @pytest.mark.asyncio
  107. @pytest.mark.parametrize(
  108. "init_data",
  109. [
  110. {1: {"isOn": True}, 2: {"isOn": True}},
  111. ],
  112. )
  113. async def test_turn_on_2PM(common_parametrize_2pm, init_data):
  114. """Test turn on command."""
  115. device = create_device_for_command_testing(
  116. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"], init_data
  117. )
  118. await device.turn_on(1)
  119. device._send_command.assert_called_with(
  120. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_ON[common_parametrize_2pm["model"]][1]
  121. )
  122. assert device.is_on(1) is True
  123. await device.turn_on(2)
  124. device._send_command.assert_called_with(
  125. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_ON[common_parametrize_2pm["model"]][2]
  126. )
  127. assert device.is_on(2) is True
  128. @pytest.mark.asyncio
  129. @pytest.mark.parametrize(
  130. "init_data",
  131. [
  132. {1: {"isOn": False}, 2: {"isOn": False}},
  133. ],
  134. )
  135. async def test_turn_off_2PM(common_parametrize_2pm, init_data):
  136. """Test turn off command."""
  137. device = create_device_for_command_testing(
  138. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"], init_data
  139. )
  140. await device.turn_off(1)
  141. device._send_command.assert_called_with(
  142. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_OFF[common_parametrize_2pm["model"]][1]
  143. )
  144. assert device.is_on(1) is False
  145. await device.turn_off(2)
  146. device._send_command.assert_called_with(
  147. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_OFF[common_parametrize_2pm["model"]][2]
  148. )
  149. assert device.is_on(2) is False
  150. @pytest.mark.asyncio
  151. async def test_turn_toggle_2PM(common_parametrize_2pm):
  152. """Test toggle command."""
  153. device = create_device_for_command_testing(
  154. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  155. )
  156. await device.async_toggle(1)
  157. device._send_command.assert_called_with(
  158. relay_switch.MULTI_CHANNEL_COMMANDS_TOGGLE[common_parametrize_2pm["model"]][1]
  159. )
  160. assert device.is_on(1) is True
  161. await device.async_toggle(2)
  162. device._send_command.assert_called_with(
  163. relay_switch.MULTI_CHANNEL_COMMANDS_TOGGLE[common_parametrize_2pm["model"]][2]
  164. )
  165. assert device.is_on(2) is False
  166. @pytest.mark.asyncio
  167. async def test_get_switch_mode_2PM(common_parametrize_2pm):
  168. """Test get switch mode."""
  169. device = create_device_for_command_testing(
  170. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  171. )
  172. assert device.switch_mode(1) is True
  173. assert device.switch_mode(2) is True
  174. @pytest.mark.asyncio
  175. @pytest.mark.parametrize(
  176. ("info_data", "result"),
  177. [
  178. (
  179. {
  180. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  181. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  182. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  183. },
  184. [False, 0, 0, 0, 0, True, 0.02, 23, 0.3, 7.0],
  185. ),
  186. (
  187. {
  188. "basic_info": b"\x01\x9e\x81\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  189. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  190. "channel2_info": b"\x01\x00\x05\xbc\x00'<\x02\xb1\x00\xea\x01-\x00F",
  191. },
  192. [True, 0, 23, 0.1, 0.0, False, 0.02, 0, 0, 0],
  193. ),
  194. ],
  195. )
  196. async def test_get_basic_info_2PM(common_parametrize_2pm, info_data, result):
  197. """Test get_basic_info for 2PM devices."""
  198. device = create_device_for_command_testing(
  199. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  200. )
  201. assert device.channel == 2
  202. device.get_current_time_and_start_time = MagicMock(
  203. return_value=("683074d6", "682fba80")
  204. )
  205. async def mock_get_basic_info(arg):
  206. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  207. return info_data["basic_info"]
  208. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  209. return info_data["channel1_info"]
  210. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  211. return info_data["channel2_info"]
  212. return None
  213. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  214. info = await device.get_basic_info()
  215. assert info is not None
  216. assert 1 in info
  217. assert 2 in info
  218. assert info[1]["isOn"] == result[0]
  219. assert info[1]["energy"] == result[1]
  220. assert info[1]["voltage"] == result[2]
  221. assert info[1]["current"] == result[3]
  222. assert info[1]["power"] == result[4]
  223. assert info[2]["isOn"] == result[5]
  224. assert info[2]["energy"] == result[6]
  225. assert info[2]["voltage"] == result[7]
  226. assert info[2]["current"] == result[8]
  227. assert info[2]["power"] == result[9]
  228. @pytest.mark.asyncio
  229. @pytest.mark.parametrize(
  230. "info_data",
  231. [
  232. {
  233. "basic_info": None,
  234. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  235. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  236. },
  237. {
  238. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  239. "channel1_info": None,
  240. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  241. },
  242. {
  243. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  244. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  245. "channel2_info": None,
  246. },
  247. ],
  248. )
  249. async def test_basic_info_exceptions_2PM(common_parametrize_2pm, info_data):
  250. """Test get_basic_info exceptions."""
  251. device = create_device_for_command_testing(
  252. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  253. )
  254. device.get_current_time_and_start_time = MagicMock(
  255. return_value=("683074d6", "682fba80")
  256. )
  257. async def mock_get_basic_info(arg):
  258. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  259. return info_data["basic_info"]
  260. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  261. return info_data["channel1_info"]
  262. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  263. return info_data["channel2_info"]
  264. return None
  265. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  266. info = await device.get_basic_info()
  267. assert info is None
  268. @pytest.mark.asyncio
  269. @pytest.mark.parametrize(
  270. "info_data",
  271. [
  272. # Truncated basic_info (single byte from the wire — repro of issue #369)
  273. {
  274. "basic_info": b"\x02",
  275. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  276. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  277. },
  278. # Basic_info just below the 17-byte minimum
  279. {
  280. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  281. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  282. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  283. },
  284. # Truncated channel1_info (single byte — repro of issue #369 user_data crash)
  285. {
  286. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  287. "channel1_info": b"\x01",
  288. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  289. },
  290. # Channel1_info just below the 15-byte minimum
  291. {
  292. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  293. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00",
  294. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  295. },
  296. # Truncated channel2_info
  297. {
  298. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  299. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  300. "channel2_info": b"\x01",
  301. },
  302. ],
  303. )
  304. async def test_get_basic_info_2PM_short_response(common_parametrize_2pm, info_data):
  305. """
  306. Truncated BLE responses must yield None instead of crashing.
  307. Regression coverage for issue #369: a single-byte payload reaches
  308. `_parse_common_data`/`_parse_user_data` and raises IndexError/ValueError.
  309. """
  310. device = create_device_for_command_testing(
  311. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  312. )
  313. device.get_current_time_and_start_time = MagicMock(
  314. return_value=("683074d6", "682fba80")
  315. )
  316. async def mock_get_basic_info(arg):
  317. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  318. return info_data["basic_info"]
  319. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  320. return info_data["channel1_info"]
  321. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  322. return info_data["channel2_info"]
  323. return None
  324. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  325. info = await device.get_basic_info()
  326. assert info is None
  327. @pytest.mark.asyncio
  328. @pytest.mark.parametrize(
  329. ("rawAdvData", "model"),
  330. common_params,
  331. )
  332. @pytest.mark.parametrize(
  333. "info_data",
  334. [
  335. {
  336. "basic_info": b"\x02",
  337. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  338. },
  339. {
  340. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  341. "channel1_info": b"\x01",
  342. },
  343. ],
  344. )
  345. async def test_get_basic_info_short_response(rawAdvData, model, info_data):
  346. """Truncated BLE responses on single-channel relay/garage/plug must yield None."""
  347. device = create_device_for_command_testing(rawAdvData, model)
  348. device.get_current_time_and_start_time = MagicMock(
  349. return_value=("683074d6", "682fba80")
  350. )
  351. async def mock_get_basic_info(arg):
  352. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  353. return info_data["basic_info"]
  354. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  355. return info_data["channel1_info"]
  356. return None
  357. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  358. info = await device.get_basic_info()
  359. assert info is None
  360. @pytest.mark.asyncio
  361. async def test_get_parsed_data_2PM(common_parametrize_2pm):
  362. """Test get_parsed_data for 2PM devices."""
  363. device = create_device_for_command_testing(
  364. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  365. )
  366. info = device.get_parsed_data(1)
  367. assert info["isOn"] is True
  368. info = device.get_parsed_data(2)
  369. assert info["isOn"] is False
  370. @pytest.mark.asyncio
  371. @pytest.mark.parametrize(
  372. ("rawAdvData", "model"),
  373. common_params,
  374. )
  375. async def test_turn_on(rawAdvData, model):
  376. """Test turn on command."""
  377. device = create_device_for_command_testing(rawAdvData, model)
  378. await device.turn_on()
  379. device._send_command.assert_awaited_once_with(device._turn_on_command)
  380. assert device.is_on() is True
  381. @pytest.mark.asyncio
  382. @pytest.mark.parametrize(
  383. ("rawAdvData", "model"),
  384. common_params,
  385. )
  386. async def test_turn_off(rawAdvData, model):
  387. """Test turn off command."""
  388. device = create_device_for_command_testing(rawAdvData, model, {"isOn": False})
  389. await device.turn_off()
  390. device._send_command.assert_awaited_once_with(device._turn_off_command)
  391. assert device.is_on() is False
  392. @pytest.mark.asyncio
  393. @pytest.mark.parametrize(
  394. ("rawAdvData", "model"),
  395. common_params,
  396. )
  397. async def test_toggle(rawAdvData, model):
  398. """Test toggle command."""
  399. device = create_device_for_command_testing(rawAdvData, model)
  400. await device.async_toggle()
  401. device._send_command.assert_awaited_once_with(relay_switch.COMMAND_TOGGLE)
  402. assert device.is_on() is True
  403. @pytest.mark.asyncio
  404. @pytest.mark.parametrize(
  405. ("rawAdvData", "model", "info_data"),
  406. [
  407. (
  408. b">\x00\x00\x00",
  409. SwitchbotModel.GARAGE_DOOR_OPENER,
  410. {
  411. "basic_info": b"\x01>\x80\x0c\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x10",
  412. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  413. },
  414. )
  415. ],
  416. )
  417. async def test_get_basic_info_garage_door_opener(rawAdvData, model, info_data):
  418. """Test get_basic_info for garage door opener."""
  419. device = create_device_for_command_testing(rawAdvData, model)
  420. device.get_current_time_and_start_time = MagicMock(
  421. return_value=("683074d6", "682fba80")
  422. )
  423. async def mock_get_basic_info(arg):
  424. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  425. return info_data["basic_info"]
  426. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  427. return info_data["channel1_info"]
  428. return None
  429. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  430. info = await device.get_basic_info()
  431. assert info is not None
  432. assert info["isOn"] is True
  433. assert info["door_open"] is True
  434. @pytest.mark.parametrize(
  435. ("dev_cls", "expected_model"),
  436. [
  437. (relay_switch.SwitchbotRelaySwitch, SwitchbotModel.RELAY_SWITCH_1PM),
  438. (relay_switch.SwitchbotGarageDoorOpener, SwitchbotModel.GARAGE_DOOR_OPENER),
  439. (relay_switch.SwitchbotRelaySwitch2PM, SwitchbotModel.RELAY_SWITCH_2PM),
  440. ],
  441. )
  442. def test_default_model_classvar(dev_cls, expected_model):
  443. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  444. device = dev_cls(ble_device, "ff", "ffffffffffffffffffffffffffffffff")
  445. assert device._model == expected_model
  446. @pytest.mark.parametrize(
  447. ("old_data", "new_data", "expected_result"),
  448. [
  449. (
  450. {"isOn": True, "sequence_number": 1},
  451. {"isOn": False},
  452. {"isOn": False, "sequence_number": 1},
  453. ),
  454. (
  455. {
  456. 1: {"current": 0, "voltage": 220, "power": 0},
  457. 2: {"current": 1, "voltage": 0, "power": 10},
  458. },
  459. {1: {"current": 1, "power": 10}, 2: {"current": 0, "voltage": 220}},
  460. {
  461. 1: {"current": 1, "voltage": 220, "power": 10},
  462. 2: {"current": 0, "voltage": 220, "power": 10},
  463. },
  464. ),
  465. ],
  466. )
  467. def test_merge_data(old_data, new_data, expected_result):
  468. """Test merging of data dictionaries."""
  469. result = merge_data(old_data, new_data)
  470. assert result == expected_result
  471. @pytest.mark.asyncio
  472. async def test_garage_door_opener_open():
  473. """Test open the garage door."""
  474. device = create_device_for_command_testing(
  475. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  476. )
  477. await device.open()
  478. device._send_command.assert_awaited_once_with(device._open_command)
  479. @pytest.mark.asyncio
  480. async def test_garage_door_opener_close():
  481. """Test close the garage door."""
  482. device = create_device_for_command_testing(
  483. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  484. )
  485. await device.close()
  486. device._send_command.assert_awaited_once_with(device._close_command)
  487. @pytest.mark.parametrize(
  488. "door_open",
  489. [
  490. True,
  491. False,
  492. ],
  493. )
  494. @pytest.mark.asyncio
  495. async def test_garage_door_opener_door_open(door_open):
  496. """Test get garage door state."""
  497. device = create_device_for_command_testing(
  498. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER, {"door_open": door_open}
  499. )
  500. assert device.door_open() is door_open
  501. @pytest.mark.asyncio
  502. async def test_press():
  503. """Test the press command for garage door opener."""
  504. device = create_device_for_command_testing(
  505. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  506. )
  507. await device.press()
  508. device._send_command.assert_awaited_once_with(device._press_command)