mirror of
https://github.com/home-assistant/core.git
synced 2026-05-05 12:24:48 +02:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 422de2c56d | |||
| 2356c1e52a | |||
| 2220c8cd3f | |||
| 979d37dc19 | |||
| 701fa06584 | |||
| 1c9053fef6 | |||
| 777cf116aa | |||
| a52b4b0f62 | |||
| dfce89f2c7 | |||
| 0cd097cd12 | |||
| 9368f75cec | |||
| d5b419eeda | |||
| e140cd9b6a | |||
| 206437b10c | |||
| 36eec7ddbc | |||
| afb187942a | |||
| 070991c160 | |||
| ebc3e1f658 | |||
| d2cef65b63 |
@@ -19,50 +19,247 @@ API_ACCOUNTS_DATA = "data"
|
||||
API_RATES = "rates"
|
||||
|
||||
WALLETS = {
|
||||
"1INCH": "1INCH",
|
||||
"AAVE": "AAVE",
|
||||
"ADA": "ADA",
|
||||
"AED": "AED",
|
||||
"AFN": "AFN",
|
||||
"ALGO": "ALGO",
|
||||
"ALL": "ALL",
|
||||
"AMD": "AMD",
|
||||
"AMP": "AMP",
|
||||
"ANG": "ANG",
|
||||
"ANKR": "ANKR",
|
||||
"AOA": "AOA",
|
||||
"ARS": "ARS",
|
||||
"ATOM": "ATOM",
|
||||
"AUD": "AUD",
|
||||
"AWG": "AWG",
|
||||
"AZN": "AZN",
|
||||
"BAL": "BAL",
|
||||
"BAM": "BAM",
|
||||
"BAND": "BAND",
|
||||
"BAT": "BAT",
|
||||
"BBD": "BBD",
|
||||
"BCH": "BCH",
|
||||
"BDT": "BDT",
|
||||
"BGN": "BGN",
|
||||
"BHD": "BHD",
|
||||
"BIF": "BIF",
|
||||
"BMD": "BMD",
|
||||
"BND": "BND",
|
||||
"BNT": "BNT",
|
||||
"BOB": "BOB",
|
||||
"BOND": "BOND",
|
||||
"BRL": "BRL",
|
||||
"BSD": "BSD",
|
||||
"BSV": "BSV",
|
||||
"BTC": "BTC",
|
||||
"CGLD": "CLGD",
|
||||
"CVC": "CVC",
|
||||
"BTN": "BTN",
|
||||
"BWP": "BWP",
|
||||
"BYN": "BYN",
|
||||
"BYR": "BYR",
|
||||
"BZD": "BZD",
|
||||
"CAD": "CAD",
|
||||
"CDF": "CDF",
|
||||
"CGLD": "CGLD",
|
||||
"CHF": "CHF",
|
||||
"CHZ": "CHZ",
|
||||
"CLF": "CLF",
|
||||
"CLP": "CLP",
|
||||
"CNH": "CNH",
|
||||
"CNY": "CNY",
|
||||
"COMP": "COMP",
|
||||
"COP": "COP",
|
||||
"CRC": "CRC",
|
||||
"CRV": "CRV",
|
||||
"CTSI": "CTSI",
|
||||
"CUC": "CUC",
|
||||
"CVC": "CVC",
|
||||
"CVE": "CVE",
|
||||
"CZK": "CZK",
|
||||
"DAI": "DAI",
|
||||
"DASH": "DASH",
|
||||
"DJF": "DJF",
|
||||
"DKK": "DKK",
|
||||
"DNT": "DNT",
|
||||
"DOGE": "DOGE",
|
||||
"DOP": "DOP",
|
||||
"DOT": "DOT",
|
||||
"DZD": "DZD",
|
||||
"EGP": "EGP",
|
||||
"ENJ": "ENJ",
|
||||
"EOS": "EOS",
|
||||
"ERN": "ERN",
|
||||
"ETB": "ETB",
|
||||
"ETC": "ETC",
|
||||
"ETH": "ETH",
|
||||
"ETH2": "ETH2",
|
||||
"EUR": "EUR",
|
||||
"FIL": "FIL",
|
||||
"FJD": "FJD",
|
||||
"FKP": "FKP",
|
||||
"FORTH": "FORTH",
|
||||
"GBP": "GBP",
|
||||
"GBX": "GBX",
|
||||
"GEL": "GEL",
|
||||
"GGP": "GGP",
|
||||
"GHS": "GHS",
|
||||
"GIP": "GIP",
|
||||
"GMD": "GMD",
|
||||
"GNF": "GNF",
|
||||
"GRT": "GRT",
|
||||
"GTC": "GTC",
|
||||
"GTQ": "GTQ",
|
||||
"GYD": "GYD",
|
||||
"HKD": "HKD",
|
||||
"HNL": "HNL",
|
||||
"HRK": "HRK",
|
||||
"HTG": "HTG",
|
||||
"HUF": "HUF",
|
||||
"ICP": "ICP",
|
||||
"IDR": "IDR",
|
||||
"ILS": "ILS",
|
||||
"IMP": "IMP",
|
||||
"INR": "INR",
|
||||
"IQD": "IQD",
|
||||
"ISK": "ISK",
|
||||
"JEP": "JEP",
|
||||
"JMD": "JMD",
|
||||
"JOD": "JOD",
|
||||
"JPY": "JPY",
|
||||
"KEEP": "KEEP",
|
||||
"KES": "KES",
|
||||
"KGS": "KGS",
|
||||
"KHR": "KHR",
|
||||
"KMF": "KMF",
|
||||
"KNC": "KNC",
|
||||
"KRW": "KRW",
|
||||
"KWD": "KWD",
|
||||
"KYD": "KYD",
|
||||
"KZT": "KZT",
|
||||
"LAK": "LAK",
|
||||
"LBP": "LBP",
|
||||
"LINK": "LINK",
|
||||
"LKR": "LKR",
|
||||
"LPT": "LPT",
|
||||
"LRC": "LRC",
|
||||
"LRD": "LRD",
|
||||
"LSL": "LSL",
|
||||
"LTC": "LTC",
|
||||
"LYD": "LYD",
|
||||
"MAD": "MAD",
|
||||
"MANA": "MANA",
|
||||
"MATIC": "MATIC",
|
||||
"MDL": "MDL",
|
||||
"MGA": "MGA",
|
||||
"MIR": "MIR",
|
||||
"MKD": "MKD",
|
||||
"MKR": "MKR",
|
||||
"MLN": "MLN",
|
||||
"MMK": "MMK",
|
||||
"MNT": "MNT",
|
||||
"MOP": "MOP",
|
||||
"MRO": "MRO",
|
||||
"MTL": "MTL",
|
||||
"MUR": "MUR",
|
||||
"MVR": "MVR",
|
||||
"MWK": "MWK",
|
||||
"MXN": "MXN",
|
||||
"MYR": "MYR",
|
||||
"MZN": "MZN",
|
||||
"NAD": "NAD",
|
||||
"NGN": "NGN",
|
||||
"NIO": "NIO",
|
||||
"NKN": "NKN",
|
||||
"NMR": "NMR",
|
||||
"NOK": "NOK",
|
||||
"NPR": "NPR",
|
||||
"NU": "NU",
|
||||
"NZD": "NZD",
|
||||
"OGN": "OGN",
|
||||
"OMG": "OMG",
|
||||
"OMR": "OMR",
|
||||
"OXT": "OXT",
|
||||
"PAB": "PAB",
|
||||
"PEN": "PEN",
|
||||
"PGK": "PGK",
|
||||
"PHP": "PHP",
|
||||
"PKR": "PKR",
|
||||
"PLN": "PLN",
|
||||
"PYG": "PYG",
|
||||
"QAR": "QAR",
|
||||
"QNT": "QNT",
|
||||
"REN": "REN",
|
||||
"REP": "REP",
|
||||
"REPV2": "REPV2",
|
||||
"RLC": "RLC",
|
||||
"RON": "RON",
|
||||
"RSD": "RSD",
|
||||
"RUB": "RUB",
|
||||
"RWF": "RWF",
|
||||
"SAR": "SAR",
|
||||
"SBD": "SBD",
|
||||
"SCR": "SCR",
|
||||
"SEK": "SEK",
|
||||
"SGD": "SGD",
|
||||
"SHP": "SHP",
|
||||
"SKL": "SKL",
|
||||
"SLL": "SLL",
|
||||
"SNX": "SNX",
|
||||
"SOL": "SOL",
|
||||
"SOS": "SOS",
|
||||
"SRD": "SRD",
|
||||
"SSP": "SSP",
|
||||
"STD": "STD",
|
||||
"STORJ": "STORJ",
|
||||
"SUSHI": "SUSHI",
|
||||
"SVC": "SVC",
|
||||
"SZL": "SZL",
|
||||
"THB": "THB",
|
||||
"TJS": "TJS",
|
||||
"TMM": "TMM",
|
||||
"TMT": "TMT",
|
||||
"TND": "TND",
|
||||
"TOP": "TOP",
|
||||
"TRB": "TRB",
|
||||
"TRY": "TRY",
|
||||
"TTD": "TTD",
|
||||
"TWD": "TWD",
|
||||
"TZS": "TZS",
|
||||
"UAH": "UAH",
|
||||
"UGX": "UGX",
|
||||
"UMA": "UMA",
|
||||
"UNI": "UNI",
|
||||
"USD": "USD",
|
||||
"USDC": "USDC",
|
||||
"USDT": "USDT",
|
||||
"UYU": "UYU",
|
||||
"UZS": "UZS",
|
||||
"VES": "VES",
|
||||
"VND": "VND",
|
||||
"VUV": "VUV",
|
||||
"WBTC": "WBTC",
|
||||
"WST": "WST",
|
||||
"XAF": "XAF",
|
||||
"XAG": "XAG",
|
||||
"XAU": "XAU",
|
||||
"XCD": "XCD",
|
||||
"XDR": "XDR",
|
||||
"XLM": "XLM",
|
||||
"XOF": "XOF",
|
||||
"XPD": "XPD",
|
||||
"XPF": "XPF",
|
||||
"XPT": "XPT",
|
||||
"XRP": "XRP",
|
||||
"XTZ": "XTZ",
|
||||
"YER": "YER",
|
||||
"YFI": "YFI",
|
||||
"ZAR": "ZAR",
|
||||
"ZEC": "ZEC",
|
||||
"ZMW": "ZMW",
|
||||
"ZRX": "ZRX",
|
||||
"ZWL": "ZWL",
|
||||
}
|
||||
|
||||
RATES = {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "eight_sleep",
|
||||
"name": "Eight Sleep",
|
||||
"documentation": "https://www.home-assistant.io/integrations/eight_sleep",
|
||||
"requirements": ["pyeight==0.1.8"],
|
||||
"requirements": ["pyeight==0.1.9"],
|
||||
"codeowners": ["@mezz64"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20210630.0"
|
||||
"home-assistant-frontend==20210706.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
||||
@@ -80,7 +80,7 @@ class GiosAirQuality(CoordinatorEntity, AirQualityEntity):
|
||||
@property
|
||||
def air_quality_index(self) -> str | None:
|
||||
"""Return the air quality index."""
|
||||
return cast(Optional[str], self.coordinator.data.get(API_AQI, {}).get("value"))
|
||||
return cast(Optional[str], self.coordinator.data.get(API_AQI).get("value"))
|
||||
|
||||
@property
|
||||
def particulate_matter_2_5(self) -> float | None:
|
||||
@@ -141,7 +141,7 @@ class GiosAirQuality(CoordinatorEntity, AirQualityEntity):
|
||||
if sensor in self.coordinator.data:
|
||||
self._attrs[f"{SENSOR_MAP[sensor]}_index"] = self.coordinator.data[
|
||||
sensor
|
||||
]["index"]
|
||||
].get("index")
|
||||
self._attrs[ATTR_STATION] = self.coordinator.gios.station_name
|
||||
return self._attrs
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "GIO\u015a",
|
||||
"documentation": "https://www.home-assistant.io/integrations/gios",
|
||||
"codeowners": ["@bieniu"],
|
||||
"requirements": ["gios==1.0.1"],
|
||||
"requirements": ["gios==1.0.2"],
|
||||
"config_flow": true,
|
||||
"quality_scale": "platinum",
|
||||
"iot_class": "cloud_polling"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "HomeKit",
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit",
|
||||
"requirements": [
|
||||
"HAP-python==3.5.0",
|
||||
"HAP-python==3.5.1",
|
||||
"fnvhash==0.1.0",
|
||||
"PyQRCode==1.2.1",
|
||||
"base36==0.1.1",
|
||||
|
||||
@@ -236,9 +236,20 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
config_num = None
|
||||
|
||||
# Set unique-id and error out if it's already configured
|
||||
existing_entry = await self.async_set_unique_id(normalize_hkid(hkid))
|
||||
updated_ip_port = {
|
||||
"AccessoryIP": discovery_info["host"],
|
||||
"AccessoryPort": discovery_info["port"],
|
||||
}
|
||||
|
||||
# If the device is already paired and known to us we should monitor c#
|
||||
# (config_num) for changes. If it changes, we check for new entities
|
||||
if paired and hkid in self.hass.data.get(KNOWN_DEVICES, {}):
|
||||
if existing_entry:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry, data={**existing_entry.data, **updated_ip_port}
|
||||
)
|
||||
conn = self.hass.data[KNOWN_DEVICES][hkid]
|
||||
# When we rediscover the device, let aiohomekit know
|
||||
# that the device is available and we should not wait
|
||||
@@ -262,8 +273,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
await self.hass.config_entries.async_remove(existing.entry_id)
|
||||
|
||||
# Set unique-id and error out if it's already configured
|
||||
await self.async_set_unique_id(normalize_hkid(hkid))
|
||||
self._abort_if_unique_id_configured()
|
||||
self._abort_if_unique_id_configured(updates=updated_ip_port)
|
||||
|
||||
self.context["hkid"] = hkid
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "HomeKit Controller",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"requirements": ["aiohomekit==0.4.0"],
|
||||
"requirements": ["aiohomekit==0.4.2"],
|
||||
"zeroconf": ["_hap._tcp.local."],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@Jc2k", "@bdraco"],
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
|
||||
from pymodbus.constants import Defaults
|
||||
from pymodbus.exceptions import ModbusException
|
||||
from pymodbus.transaction import ModbusRtuFramer
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_DELAY,
|
||||
@@ -224,7 +225,7 @@ class ModbusHub:
|
||||
# network configuration
|
||||
self._pb_params["host"] = client_config[CONF_HOST]
|
||||
if self._config_type == CONF_RTUOVERTCP:
|
||||
self._pb_params["host"] = "ModbusRtuFramer"
|
||||
self._pb_params["framer"] = ModbusRtuFramer
|
||||
|
||||
Defaults.Timeout = client_config[CONF_TIMEOUT]
|
||||
|
||||
|
||||
@@ -166,8 +166,10 @@ class NmapDeviceScanner:
|
||||
self._scan_interval = timedelta(
|
||||
seconds=config.get(CONF_SCAN_INTERVAL, TRACKER_SCAN_INTERVAL)
|
||||
)
|
||||
self._hosts = cv.ensure_list_csv(config[CONF_HOSTS])
|
||||
self._exclude = cv.ensure_list_csv(config[CONF_EXCLUDE])
|
||||
hosts_list = cv.ensure_list_csv(config[CONF_HOSTS])
|
||||
self._hosts = [host for host in hosts_list if host != ""]
|
||||
excludes_list = cv.ensure_list_csv(config[CONF_EXCLUDE])
|
||||
self._exclude = [exclude for exclude in excludes_list if exclude != ""]
|
||||
self._options = config[CONF_OPTIONS]
|
||||
self.home_interval = timedelta(
|
||||
minutes=cv.positive_int(config[CONF_HOME_INTERVAL])
|
||||
|
||||
@@ -347,7 +347,7 @@ def _drop_foreign_key_constraints(connection, engine, table, columns):
|
||||
)
|
||||
|
||||
|
||||
def _apply_update(engine, session, new_version, old_version): # noqa: C901
|
||||
def _apply_update(engine, session, new_version, old_version):
|
||||
"""Perform operations to bring schema up to date."""
|
||||
connection = session.connection()
|
||||
if new_version == 1:
|
||||
@@ -451,10 +451,8 @@ def _apply_update(engine, session, new_version, old_version): # noqa: C901
|
||||
elif new_version == 14:
|
||||
_modify_columns(connection, engine, "events", ["event_type VARCHAR(64)"])
|
||||
elif new_version == 15:
|
||||
if sqlalchemy.inspect(engine).has_table(Statistics.__tablename__):
|
||||
# Recreate the statistics table
|
||||
Statistics.__table__.drop(engine)
|
||||
Statistics.__table__.create(engine)
|
||||
# This dropped the statistics table, done again in version 18.
|
||||
pass
|
||||
elif new_version == 16:
|
||||
_drop_foreign_key_constraints(
|
||||
connection, engine, TABLE_STATES, ["old_state_id"]
|
||||
@@ -463,14 +461,19 @@ def _apply_update(engine, session, new_version, old_version): # noqa: C901
|
||||
# This dropped the statistics table, done again in version 18.
|
||||
pass
|
||||
elif new_version == 18:
|
||||
# Recreate the statisticsmeta tables
|
||||
if sqlalchemy.inspect(engine).has_table(StatisticsMeta.__tablename__):
|
||||
StatisticsMeta.__table__.drop(engine)
|
||||
StatisticsMeta.__table__.create(engine)
|
||||
# Recreate the statistics and statistics meta tables.
|
||||
#
|
||||
# Order matters! Statistics has a relation with StatisticsMeta,
|
||||
# so statistics need to be deleted before meta (or in pair depending
|
||||
# on the SQL backend); and meta needs to be created before statistics.
|
||||
if sqlalchemy.inspect(engine).has_table(
|
||||
StatisticsMeta.__tablename__
|
||||
) or sqlalchemy.inspect(engine).has_table(Statistics.__tablename__):
|
||||
Base.metadata.drop_all(
|
||||
bind=engine, tables=[Statistics.__table__, StatisticsMeta.__table__]
|
||||
)
|
||||
|
||||
# Recreate the statistics table
|
||||
if sqlalchemy.inspect(engine).has_table(Statistics.__tablename__):
|
||||
Statistics.__table__.drop(engine)
|
||||
StatisticsMeta.__table__.create(engine)
|
||||
Statistics.__table__.create(engine)
|
||||
else:
|
||||
raise ValueError(f"No schema migration defined for version {new_version}")
|
||||
|
||||
@@ -6,7 +6,7 @@ from simplipy import API
|
||||
from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_CODE, CONF_CODE, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import ATTR_CODE, CONF_CODE, CONF_TOKEN, CONF_USERNAME
|
||||
from homeassistant.core import CoreState, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import (
|
||||
@@ -107,6 +107,14 @@ SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA = SERVICE_BASE_SCHEMA.extend(
|
||||
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_save_refresh_token(hass, config_entry, token):
|
||||
"""Save a refresh token to the config entry."""
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, data={**config_entry.data, CONF_TOKEN: token}
|
||||
)
|
||||
|
||||
|
||||
async def async_get_client_id(hass):
|
||||
"""Get a client ID (based on the HASS unique ID) for the SimpliSafe API.
|
||||
|
||||
@@ -134,9 +142,6 @@ async def async_setup_entry(hass, config_entry): # noqa: C901
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = []
|
||||
hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = []
|
||||
|
||||
if CONF_PASSWORD not in config_entry.data:
|
||||
raise ConfigEntryAuthFailed("Config schema change requires re-authentication")
|
||||
|
||||
entry_updates = {}
|
||||
if not config_entry.unique_id:
|
||||
# If the config entry doesn't already have a unique ID, set one:
|
||||
@@ -158,24 +163,20 @@ async def async_setup_entry(hass, config_entry): # noqa: C901
|
||||
client_id = await async_get_client_id(hass)
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
async def async_get_api():
|
||||
"""Define a helper to get an authenticated SimpliSafe API object."""
|
||||
return await API.login_via_credentials(
|
||||
config_entry.data[CONF_USERNAME],
|
||||
config_entry.data[CONF_PASSWORD],
|
||||
client_id=client_id,
|
||||
session=websession,
|
||||
)
|
||||
|
||||
try:
|
||||
api = await async_get_api()
|
||||
except InvalidCredentialsError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
api = await API.login_via_token(
|
||||
config_entry.data[CONF_TOKEN], client_id=client_id, session=websession
|
||||
)
|
||||
except InvalidCredentialsError:
|
||||
LOGGER.error("Invalid credentials provided")
|
||||
return False
|
||||
except SimplipyError as err:
|
||||
LOGGER.error("Config entry failed: %s", err)
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
simplisafe = SimpliSafe(hass, config_entry, api, async_get_api)
|
||||
_async_save_refresh_token(hass, config_entry, api.refresh_token)
|
||||
|
||||
simplisafe = SimpliSafe(hass, api, config_entry)
|
||||
|
||||
try:
|
||||
await simplisafe.async_init()
|
||||
@@ -302,10 +303,10 @@ async def async_reload_entry(hass, config_entry):
|
||||
class SimpliSafe:
|
||||
"""Define a SimpliSafe data object."""
|
||||
|
||||
def __init__(self, hass, config_entry, api, async_get_api):
|
||||
def __init__(self, hass, api, config_entry):
|
||||
"""Initialize."""
|
||||
self._api = api
|
||||
self._async_get_api = async_get_api
|
||||
self._emergency_refresh_token_used = False
|
||||
self._hass = hass
|
||||
self._system_notifications = {}
|
||||
self.config_entry = config_entry
|
||||
@@ -382,17 +383,23 @@ class SimpliSafe:
|
||||
|
||||
for result in results:
|
||||
if isinstance(result, InvalidCredentialsError):
|
||||
try:
|
||||
self._api = await self._async_get_api()
|
||||
return
|
||||
except InvalidCredentialsError as err:
|
||||
if self._emergency_refresh_token_used:
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Unable to re-authenticate with SimpliSafe"
|
||||
) from err
|
||||
"Update failed with stored refresh token"
|
||||
)
|
||||
|
||||
LOGGER.warning("SimpliSafe cloud error; trying stored refresh token")
|
||||
self._emergency_refresh_token_used = True
|
||||
|
||||
try:
|
||||
await self._api.refresh_access_token(
|
||||
self.config_entry.data[CONF_TOKEN]
|
||||
)
|
||||
return
|
||||
except SimplipyError as err:
|
||||
raise UpdateFailed(
|
||||
f"SimpliSafe error while updating: {err}"
|
||||
) from err
|
||||
raise UpdateFailed( # pylint: disable=raise-missing-from
|
||||
f"Error while using stored refresh token: {err}"
|
||||
)
|
||||
|
||||
if isinstance(result, EndpointUnavailable):
|
||||
# In case the user attempts an action not allowed in their current plan,
|
||||
@@ -403,6 +410,16 @@ class SimpliSafe:
|
||||
if isinstance(result, SimplipyError):
|
||||
raise UpdateFailed(f"SimpliSafe error while updating: {result}")
|
||||
|
||||
if self._api.refresh_token != self.config_entry.data[CONF_TOKEN]:
|
||||
_async_save_refresh_token(
|
||||
self._hass, self.config_entry, self._api.refresh_token
|
||||
)
|
||||
|
||||
# If we've reached this point using an emergency refresh token, we're in the
|
||||
# clear and we can discard it:
|
||||
if self._emergency_refresh_token_used:
|
||||
self._emergency_refresh_token_used = False
|
||||
|
||||
|
||||
class SimpliSafeEntity(CoordinatorEntity):
|
||||
"""Define a base SimpliSafe entity."""
|
||||
|
||||
@@ -8,7 +8,7 @@ from simplipy.errors import (
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
@@ -59,7 +59,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors = {}
|
||||
|
||||
try:
|
||||
await self._async_get_simplisafe_api()
|
||||
simplisafe = await self._async_get_simplisafe_api()
|
||||
except PendingAuthorizationError:
|
||||
LOGGER.info("Awaiting confirmation of MFA email click")
|
||||
return await self.async_step_mfa()
|
||||
@@ -79,7 +79,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return await self.async_step_finish(
|
||||
{
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_TOKEN: simplisafe.refresh_token,
|
||||
CONF_CODE: self._code,
|
||||
}
|
||||
)
|
||||
@@ -89,9 +89,6 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
existing_entry = await self.async_set_unique_id(self._username)
|
||||
if existing_entry:
|
||||
self.hass.config_entries.async_update_entry(existing_entry, data=user_input)
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
return self.async_create_entry(title=self._username, data=user_input)
|
||||
|
||||
@@ -101,7 +98,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(step_id="mfa")
|
||||
|
||||
try:
|
||||
await self._async_get_simplisafe_api()
|
||||
simplisafe = await self._async_get_simplisafe_api()
|
||||
except PendingAuthorizationError:
|
||||
LOGGER.error("Still awaiting confirmation of MFA email click")
|
||||
return self.async_show_form(
|
||||
@@ -111,7 +108,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return await self.async_step_finish(
|
||||
{
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_TOKEN: simplisafe.refresh_token,
|
||||
CONF_CODE: self._code,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "Your access has expired or been revoked. Enter your password to re-link your account.",
|
||||
"description": "Your access token has expired or been revoked. Enter your password to re-link your account.",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"data": {
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Your access has expired or been revoked. Enter your password to re-link your account.",
|
||||
"description": "Your access token has expired or been revoked. Enter your password to re-link your account.",
|
||||
"title": "Reauthenticate Integration"
|
||||
},
|
||||
"user": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "SMA Solar",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sma",
|
||||
"requirements": ["pysma==0.6.1"],
|
||||
"requirements": ["pysma==0.6.2"],
|
||||
"codeowners": ["@kellerza", "@rklomp"],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
@@ -100,7 +101,9 @@ class ThresholdSensor(BinarySensorEntity):
|
||||
|
||||
try:
|
||||
self.sensor_value = (
|
||||
None if new_state.state == STATE_UNKNOWN else float(new_state.state)
|
||||
None
|
||||
if new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]
|
||||
else float(new_state.state)
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
self.sensor_value = None
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "zeroconf",
|
||||
"name": "Zero-configuration networking (zeroconf)",
|
||||
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
|
||||
"requirements": ["zeroconf==0.32.0"],
|
||||
"requirements": ["zeroconf==0.32.1"],
|
||||
"dependencies": ["network", "api"],
|
||||
"codeowners": ["@bdraco"],
|
||||
"quality_scale": "internal",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"zha-quirks==0.0.58",
|
||||
"zigpy-cc==0.5.2",
|
||||
"zigpy-deconz==0.12.0",
|
||||
"zigpy==0.35.0",
|
||||
"zigpy==0.35.1",
|
||||
"zigpy-xbee==0.13.0",
|
||||
"zigpy-zigate==0.7.3",
|
||||
"zigpy-znp==0.5.1"
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Final
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 7
|
||||
PATCH_VERSION: Final = "0b2"
|
||||
PATCH_VERSION: Final = "0b4"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||
|
||||
@@ -17,7 +17,7 @@ defusedxml==0.7.1
|
||||
distro==1.5.0
|
||||
emoji==1.2.0
|
||||
hass-nabucasa==0.44.0
|
||||
home-assistant-frontend==20210630.0
|
||||
home-assistant-frontend==20210706.0
|
||||
httpx==0.18.0
|
||||
ifaddr==0.1.7
|
||||
jinja2==3.0.1
|
||||
@@ -33,7 +33,7 @@ sqlalchemy==1.4.17
|
||||
voluptuous-serialize==2.4.0
|
||||
voluptuous==0.12.1
|
||||
yarl==1.6.3
|
||||
zeroconf==0.32.0
|
||||
zeroconf==0.32.1
|
||||
|
||||
pycryptodome>=3.6.6
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Adafruit-SHT31==1.0.2
|
||||
# Adafruit_BBIO==1.1.1
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==3.5.0
|
||||
HAP-python==3.5.1
|
||||
|
||||
# homeassistant.components.mastodon
|
||||
Mastodon.py==1.5.1
|
||||
@@ -175,7 +175,7 @@ aioguardian==1.0.4
|
||||
aioharmony==0.2.7
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==0.4.0
|
||||
aiohomekit==0.4.2
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@@ -678,7 +678,7 @@ georss_qld_bushfire_alert_client==0.5
|
||||
getmac==0.8.2
|
||||
|
||||
# homeassistant.components.gios
|
||||
gios==1.0.1
|
||||
gios==1.0.2
|
||||
|
||||
# homeassistant.components.gitter
|
||||
gitterpy==0.1.7
|
||||
@@ -780,7 +780,7 @@ hole==0.5.1
|
||||
holidays==0.11.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20210630.0
|
||||
home-assistant-frontend==20210706.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -1409,7 +1409,7 @@ pyeconet==0.1.14
|
||||
pyedimax==0.2.1
|
||||
|
||||
# homeassistant.components.eight_sleep
|
||||
pyeight==0.1.8
|
||||
pyeight==0.1.9
|
||||
|
||||
# homeassistant.components.emby
|
||||
pyemby==1.7
|
||||
@@ -1756,7 +1756,7 @@ pysignalclirestapi==0.3.4
|
||||
pyskyqhub==0.1.3
|
||||
|
||||
# homeassistant.components.sma
|
||||
pysma==0.6.1
|
||||
pysma==0.6.2
|
||||
|
||||
# homeassistant.components.smappee
|
||||
pysmappee==0.2.25
|
||||
@@ -2428,7 +2428,7 @@ zeep[async]==4.0.0
|
||||
zengge==0.2
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.32.0
|
||||
zeroconf==0.32.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.58
|
||||
@@ -2455,7 +2455,7 @@ zigpy-zigate==0.7.3
|
||||
zigpy-znp==0.5.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.35.0
|
||||
zigpy==0.35.1
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.5.2
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
AEMET-OpenData==0.2.1
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==3.5.0
|
||||
HAP-python==3.5.1
|
||||
|
||||
# homeassistant.components.flick_electric
|
||||
PyFlick==0.0.2
|
||||
@@ -112,7 +112,7 @@ aioguardian==1.0.4
|
||||
aioharmony==0.2.7
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==0.4.0
|
||||
aiohomekit==0.4.2
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@@ -384,7 +384,7 @@ georss_qld_bushfire_alert_client==0.5
|
||||
getmac==0.8.2
|
||||
|
||||
# homeassistant.components.gios
|
||||
gios==1.0.1
|
||||
gios==1.0.2
|
||||
|
||||
# homeassistant.components.glances
|
||||
glances_api==0.2.0
|
||||
@@ -447,7 +447,7 @@ hole==0.5.1
|
||||
holidays==0.11.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20210630.0
|
||||
home-assistant-frontend==20210706.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -998,7 +998,7 @@ pysiaalarm==3.0.0
|
||||
pysignalclirestapi==0.3.4
|
||||
|
||||
# homeassistant.components.sma
|
||||
pysma==0.6.1
|
||||
pysma==0.6.2
|
||||
|
||||
# homeassistant.components.smappee
|
||||
pysmappee==0.2.25
|
||||
@@ -1331,7 +1331,7 @@ yeelight==0.6.3
|
||||
zeep[async]==4.0.0
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.32.0
|
||||
zeroconf==0.32.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.58
|
||||
@@ -1352,7 +1352,7 @@ zigpy-zigate==0.7.3
|
||||
zigpy-znp==0.5.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.35.0
|
||||
zigpy==0.35.1
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.27.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for homekit_controller config flow."""
|
||||
from unittest import mock
|
||||
import unittest.mock
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import aiohomekit
|
||||
from aiohomekit.model import Accessories, Accessory
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.homekit_controller import config_flow
|
||||
from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
|
||||
from homeassistant.helpers import device_registry
|
||||
|
||||
from tests.common import MockConfigEntry, mock_device_registry
|
||||
@@ -383,11 +384,16 @@ async def test_discovery_invalid_config_entry(hass, controller):
|
||||
|
||||
async def test_discovery_already_configured(hass, controller):
|
||||
"""Already configured."""
|
||||
MockConfigEntry(
|
||||
entry = MockConfigEntry(
|
||||
domain="homekit_controller",
|
||||
data={"AccessoryPairingID": "00:00:00:00:00:00"},
|
||||
data={
|
||||
"AccessoryIP": "4.4.4.4",
|
||||
"AccessoryPort": 66,
|
||||
"AccessoryPairingID": "00:00:00:00:00:00",
|
||||
},
|
||||
unique_id="00:00:00:00:00:00",
|
||||
).add_to_hass(hass)
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
device = setup_mock_accessory(controller)
|
||||
discovery_info = get_device_discovery_info(device)
|
||||
@@ -403,6 +409,49 @@ async def test_discovery_already_configured(hass, controller):
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data["AccessoryIP"] == discovery_info["host"]
|
||||
assert entry.data["AccessoryPort"] == discovery_info["port"]
|
||||
|
||||
|
||||
async def test_discovery_already_configured_update_csharp(hass, controller):
|
||||
"""Already configured and csharp changes."""
|
||||
entry = MockConfigEntry(
|
||||
domain="homekit_controller",
|
||||
data={
|
||||
"AccessoryIP": "4.4.4.4",
|
||||
"AccessoryPort": 66,
|
||||
"AccessoryPairingID": "AA:BB:CC:DD:EE:FF",
|
||||
},
|
||||
unique_id="aa:bb:cc:dd:ee:ff",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
connection_mock = AsyncMock()
|
||||
connection_mock.pairing.connect.reconnect_soon = AsyncMock()
|
||||
connection_mock.async_refresh_entity_map = AsyncMock()
|
||||
hass.data[KNOWN_DEVICES] = {"AA:BB:CC:DD:EE:FF": connection_mock}
|
||||
|
||||
device = setup_mock_accessory(controller)
|
||||
discovery_info = get_device_discovery_info(device)
|
||||
|
||||
# Set device as already paired
|
||||
discovery_info["properties"]["sf"] = 0x00
|
||||
discovery_info["properties"]["c#"] = 99999
|
||||
discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF"
|
||||
|
||||
# Device is discovered
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"homekit_controller",
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=discovery_info,
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.data["AccessoryIP"] == discovery_info["host"]
|
||||
assert entry.data["AccessoryPort"] == discovery_info["port"]
|
||||
assert connection_mock.async_refresh_entity_map.await_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception,expected", PAIRING_START_ABORT_ERRORS)
|
||||
|
||||
@@ -10,7 +10,7 @@ from simplipy.errors import (
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.simplisafe import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
|
||||
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -33,11 +33,7 @@ async def test_duplicate_error(hass):
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="user@email.com",
|
||||
data={
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_CODE: "1234",
|
||||
},
|
||||
data={CONF_USERNAME: "user@email.com", CONF_TOKEN: "12345", CONF_CODE: "1234"},
|
||||
).add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -106,11 +102,7 @@ async def test_step_reauth(hass):
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="user@email.com",
|
||||
data={
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_CODE: "1234",
|
||||
},
|
||||
data={CONF_USERNAME: "user@email.com", CONF_TOKEN: "12345", CONF_CODE: "1234"},
|
||||
).add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -128,8 +120,6 @@ async def test_step_reauth(hass):
|
||||
"homeassistant.components.simplisafe.async_setup_entry", return_value=True
|
||||
), patch(
|
||||
"simplipy.API.login_via_credentials", new=AsyncMock(return_value=mock_api())
|
||||
), patch(
|
||||
"homeassistant.config_entries.ConfigEntries.async_reload"
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_PASSWORD: "password"}
|
||||
@@ -161,7 +151,7 @@ async def test_step_user(hass):
|
||||
assert result["title"] == "user@email.com"
|
||||
assert result["data"] == {
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_TOKEN: "12345abc",
|
||||
CONF_CODE: "1234",
|
||||
}
|
||||
|
||||
@@ -207,7 +197,7 @@ async def test_step_user_mfa(hass):
|
||||
assert result["title"] == "user@email.com"
|
||||
assert result["data"] == {
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_TOKEN: "12345abc",
|
||||
CONF_CODE: "1234",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"""The test for the threshold sensor platform."""
|
||||
|
||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
@@ -283,7 +288,7 @@ async def test_sensor_in_range_with_hysteresis(hass):
|
||||
assert state.state == "on"
|
||||
|
||||
|
||||
async def test_sensor_in_range_unknown_state(hass):
|
||||
async def test_sensor_in_range_unknown_state(hass, caplog):
|
||||
"""Test if source is within the range."""
|
||||
config = {
|
||||
"binary_sensor": {
|
||||
@@ -322,6 +327,16 @@ async def test_sensor_in_range_unknown_state(hass):
|
||||
assert state.attributes.get("position") == "unknown"
|
||||
assert state.state == "off"
|
||||
|
||||
hass.states.async_set("sensor.test_monitored", STATE_UNAVAILABLE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.threshold")
|
||||
|
||||
assert state.attributes.get("position") == "unknown"
|
||||
assert state.state == "off"
|
||||
|
||||
assert "State is not numerical" not in caplog.text
|
||||
|
||||
|
||||
async def test_sensor_lower_zero_threshold(hass):
|
||||
"""Test if a lower threshold of zero is set."""
|
||||
|
||||
Reference in New Issue
Block a user