Switchbot bump Dependency 0.14.0 (#74001)

* Bump requirement.

* Switchbot depenacy update, full async.

* Update tests, remove redundant config entry check.

* Update requirements_test_all.txt

* Update requirements_all.txt

* Remove asyncio lock. Not required anymore with bleak.

* Update requirements_all.txt

* Update requirements_test_all.txt

* pyswitchbot no longer uses bluepy
This commit is contained in:
RenierM26 2022-06-27 13:56:51 +02:00 committed by GitHub
parent b185de0ac0
commit 10ea88e0ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 59 additions and 88 deletions

View File

@ -1,17 +1,14 @@
"""Support for Switchbot devices."""
from asyncio import Lock
import switchbot # pylint: disable=import-error
import switchbot
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SENSOR_TYPE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import (
ATTR_BOT,
ATTR_CURTAIN,
BTLE_LOCK,
COMMON_OPTIONS,
CONF_RETRY_COUNT,
CONF_RETRY_TIMEOUT,
@ -50,12 +47,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Uses BTLE advertisement data, all Switchbot devices in range is stored here.
if DATA_COORDINATOR not in hass.data[DOMAIN]:
# Check if asyncio.lock is stored in hass data.
# BTLE has issues with multiple connections,
# so we use a lock to ensure that only one API request is reaching it at a time:
if BTLE_LOCK not in hass.data[DOMAIN]:
hass.data[DOMAIN][BTLE_LOCK] = Lock()
if COMMON_OPTIONS not in hass.data[DOMAIN]:
hass.data[DOMAIN][COMMON_OPTIONS] = {**entry.options}
@ -72,7 +63,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api=switchbot,
retry_count=hass.data[DOMAIN][COMMON_OPTIONS][CONF_RETRY_COUNT],
scan_timeout=hass.data[DOMAIN][COMMON_OPTIONS][CONF_SCAN_TIMEOUT],
api_lock=hass.data[DOMAIN][BTLE_LOCK],
)
hass.data[DOMAIN][DATA_COORDINATOR] = coordinator
@ -82,9 +72,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
hass.data[DOMAIN][entry.entry_id] = {DATA_COORDINATOR: coordinator}

View File

@ -8,6 +8,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -33,8 +34,8 @@ async def async_setup_entry(
DATA_COORDINATOR
]
if not coordinator.data[entry.unique_id].get("data"):
return
if not coordinator.data.get(entry.unique_id):
raise PlatformNotReady
async_add_entities(
[

View File

@ -1,11 +1,10 @@
"""Config flow for Switchbot."""
from __future__ import annotations
from asyncio import Lock
import logging
from typing import Any
from switchbot import GetSwitchbotDevices # pylint: disable=import-error
from switchbot import GetSwitchbotDevices
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
@ -14,7 +13,6 @@ from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from .const import (
BTLE_LOCK,
CONF_RETRY_COUNT,
CONF_RETRY_TIMEOUT,
CONF_SCAN_TIMEOUT,
@ -30,10 +28,10 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
def _btle_connect() -> dict:
async def _btle_connect() -> dict:
"""Scan for BTLE advertisement data."""
switchbot_devices = GetSwitchbotDevices().discover()
switchbot_devices = await GetSwitchbotDevices().discover()
if not switchbot_devices:
raise NotConnectedError("Failed to discover switchbot")
@ -52,14 +50,9 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
# store asyncio.lock in hass data if not present.
if DOMAIN not in self.hass.data:
self.hass.data.setdefault(DOMAIN, {})
if BTLE_LOCK not in self.hass.data[DOMAIN]:
self.hass.data[DOMAIN][BTLE_LOCK] = Lock()
connect_lock = self.hass.data[DOMAIN][BTLE_LOCK]
# Discover switchbots nearby.
async with connect_lock:
_btle_adv_data = await self.hass.async_add_executor_job(_btle_connect)
_btle_adv_data = await _btle_connect()
return _btle_adv_data

View File

@ -22,5 +22,4 @@ CONF_SCAN_TIMEOUT = "scan_timeout"
# Data
DATA_COORDINATOR = "coordinator"
BTLE_LOCK = "btle_lock"
COMMON_OPTIONS = "common_options"

View File

@ -1,11 +1,10 @@
"""Provides the switchbot DataUpdateCoordinator."""
from __future__ import annotations
from asyncio import Lock
from datetime import timedelta
import logging
import switchbot # pylint: disable=import-error
import switchbot
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -26,7 +25,6 @@ class SwitchbotDataUpdateCoordinator(DataUpdateCoordinator):
api: switchbot,
retry_count: int,
scan_timeout: int,
api_lock: Lock,
) -> None:
"""Initialize global switchbot data updater."""
self.switchbot_api = api
@ -39,20 +37,12 @@ class SwitchbotDataUpdateCoordinator(DataUpdateCoordinator):
hass, _LOGGER, name=DOMAIN, update_interval=self.update_interval
)
self.api_lock = api_lock
def _update_data(self) -> dict | None:
"""Fetch device states from switchbot api."""
return self.switchbot_data.discover(
retry=self.retry_count, scan_timeout=self.scan_timeout
)
async def _async_update_data(self) -> dict | None:
"""Fetch data from switchbot."""
async with self.api_lock:
switchbot_data = await self.hass.async_add_executor_job(self._update_data)
switchbot_data = await self.switchbot_data.discover(
retry=self.retry_count, scan_timeout=self.scan_timeout
)
if not switchbot_data:
raise UpdateFailed("Unable to fetch switchbot services data")

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import logging
from typing import Any
from switchbot import SwitchbotCurtain # pylint: disable=import-error
from switchbot import SwitchbotCurtain
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
@ -16,6 +16,7 @@ from homeassistant.components.cover import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC, CONF_NAME, CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
@ -36,6 +37,9 @@ async def async_setup_entry(
DATA_COORDINATOR
]
if not coordinator.data.get(entry.unique_id):
raise PlatformNotReady
async_add_entities(
[
SwitchBotCurtainEntity(
@ -94,44 +98,30 @@ class SwitchBotCurtainEntity(SwitchbotEntity, CoverEntity, RestoreEntity):
"""Open the curtain."""
_LOGGER.debug("Switchbot to open curtain %s", self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(self._device.open)
)
self._last_run_success = bool(await self._device.open())
self.async_write_ha_state()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the curtain."""
_LOGGER.debug("Switchbot to close the curtain %s", self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(self._device.close)
)
self._last_run_success = bool(await self._device.close())
self.async_write_ha_state()
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the moving of this device."""
_LOGGER.debug("Switchbot to stop %s", self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(self._device.stop)
)
self._last_run_success = bool(await self._device.stop())
self.async_write_ha_state()
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover shutter to a specific position."""
position = kwargs.get(ATTR_POSITION)
_LOGGER.debug("Switchbot to move at %d %s", position, self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(
self._device.set_position, position
)
)
self._last_run_success = bool(await self._device.set_position(position))
self.async_write_ha_state()
@callback
def _handle_coordinator_update(self) -> None:

View File

@ -2,7 +2,7 @@
"domain": "switchbot",
"name": "SwitchBot",
"documentation": "https://www.home-assistant.io/integrations/switchbot",
"requirements": ["PySwitchbot==0.13.3"],
"requirements": ["PySwitchbot==0.14.0"],
"config_flow": true,
"codeowners": ["@danielhiversen", "@RenierM26"],
"iot_class": "local_polling",

View File

@ -14,6 +14,7 @@ from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -53,8 +54,8 @@ async def async_setup_entry(
DATA_COORDINATOR
]
if not coordinator.data[entry.unique_id].get("data"):
return
if not coordinator.data.get(entry.unique_id):
raise PlatformNotReady
async_add_entities(
[

View File

@ -4,12 +4,13 @@ from __future__ import annotations
import logging
from typing import Any
from switchbot import Switchbot # pylint: disable=import-error
from switchbot import Switchbot
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC, CONF_NAME, CONF_PASSWORD, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import entity_platform
from homeassistant.helpers.restore_state import RestoreEntity
@ -32,6 +33,9 @@ async def async_setup_entry(
DATA_COORDINATOR
]
if not coordinator.data.get(entry.unique_id):
raise PlatformNotReady
async_add_entities(
[
SwitchBotBotEntity(
@ -80,25 +84,19 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
"""Turn device on."""
_LOGGER.info("Turn Switchbot bot on %s", self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(self._device.turn_on)
)
if self._last_run_success:
self._attr_is_on = True
self.async_write_ha_state()
self._last_run_success = bool(await self._device.turn_on())
if self._last_run_success:
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn device off."""
_LOGGER.info("Turn Switchbot bot off %s", self._mac)
async with self.coordinator.api_lock:
self._last_run_success = bool(
await self.hass.async_add_executor_job(self._device.turn_off)
)
if self._last_run_success:
self._attr_is_on = False
self.async_write_ha_state()
self._last_run_success = bool(await self._device.turn_off())
if self._last_run_success:
self._attr_is_on = False
self.async_write_ha_state()
@property
def assumed_state(self) -> bool:

View File

@ -34,7 +34,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
# PySwitchbot==0.13.3
PySwitchbot==0.14.0
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1

View File

@ -30,7 +30,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
# PySwitchbot==0.13.3
PySwitchbot==0.14.0
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1

View File

@ -30,7 +30,6 @@ COMMENT_REQUIREMENTS = (
"opencv-python-headless",
"pybluez",
"pycups",
"PySwitchbot",
"pySwitchmate",
"python-eq3bt",
"python-gammu",

View File

@ -45,6 +45,19 @@ class MocGetSwitchbotDevices:
"model": "m",
"rawAdvData": "000d6d00",
},
"c0ceb0d426be": {
"mac_address": "c0:ce:b0:d4:26:be",
"isEncrypted": False,
"data": {
"temp": {"c": 21.6, "f": 70.88},
"fahrenheit": False,
"humidity": 73,
"battery": 100,
"rssi": -58,
},
"model": "T",
"modelName": "WoSensorTH",
},
}
self._curtain_all_services_data = {
"mac_address": "e7:89:43:90:90:90",
@ -72,11 +85,11 @@ class MocGetSwitchbotDevices:
"modelName": "WoOther",
}
def discover(self, retry=0, scan_timeout=0):
async def discover(self, retry=0, scan_timeout=0):
"""Mock discover."""
return self._all_services_data
def get_device_data(self, mac=None):
async def get_device_data(self, mac=None):
"""Return data for specific device."""
if mac == "e7:89:43:99:99:99":
return self._all_services_data