Bump pyairvisual to 5.0.2 (#40554)

* Bump pyairvisual to 5.0.2

* Fix tests
This commit is contained in:
Aaron Bach 2020-09-24 15:31:47 -06:00 committed by GitHub
parent 0856c7292c
commit f82ca44aac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 59 additions and 72 deletions

View File

@ -3,7 +3,7 @@ import asyncio
from datetime import timedelta from datetime import timedelta
from math import ceil from math import ceil
from pyairvisual import Client from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import ( from pyairvisual.errors import (
AirVisualError, AirVisualError,
InvalidKeyError, InvalidKeyError,
@ -211,23 +211,22 @@ def _standardize_node_pro_config_entry(hass, config_entry):
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass, config_entry):
"""Set up AirVisual as config entry.""" """Set up AirVisual as config entry."""
websession = aiohttp_client.async_get_clientsession(hass)
if CONF_API_KEY in config_entry.data: if CONF_API_KEY in config_entry.data:
_standardize_geography_config_entry(hass, config_entry) _standardize_geography_config_entry(hass, config_entry)
client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) websession = aiohttp_client.async_get_clientsession(hass)
cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession)
async def async_update_data(): async def async_update_data():
"""Get new data from the API.""" """Get new data from the API."""
if CONF_CITY in config_entry.data: if CONF_CITY in config_entry.data:
api_coro = client.api.city( api_coro = cloud_api.air_quality.city(
config_entry.data[CONF_CITY], config_entry.data[CONF_CITY],
config_entry.data[CONF_STATE], config_entry.data[CONF_STATE],
config_entry.data[CONF_COUNTRY], config_entry.data[CONF_COUNTRY],
) )
else: else:
api_coro = client.api.nearest_city( api_coro = cloud_api.air_quality.nearest_city(
config_entry.data[CONF_LATITUDE], config_entry.data[CONF_LATITUDE],
config_entry.data[CONF_LONGITUDE], config_entry.data[CONF_LONGITUDE],
) )
@ -267,17 +266,13 @@ async def async_setup_entry(hass, config_entry):
else: else:
_standardize_node_pro_config_entry(hass, config_entry) _standardize_node_pro_config_entry(hass, config_entry)
client = Client(session=websession)
async def async_update_data(): async def async_update_data():
"""Get new data from the API.""" """Get new data from the API."""
try: try:
return await client.node.from_samba( async with NodeSamba(
config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_PASSWORD]
config_entry.data[CONF_PASSWORD], ) as node:
include_history=False, return await node.async_get_latest_measurements()
include_trends=False,
)
except NodeProError as err: except NodeProError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err raise UpdateFailed(f"Error while retrieving data: {err}") from err

View File

@ -40,9 +40,9 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
@property @property
def air_quality_index(self): def air_quality_index(self):
"""Return the Air Quality Index (AQI).""" """Return the Air Quality Index (AQI)."""
if self.coordinator.data["current"]["settings"]["is_aqi_usa"]: if self.coordinator.data["settings"]["is_aqi_usa"]:
return self.coordinator.data["current"]["measurements"]["aqi_us"] return self.coordinator.data["measurements"]["aqi_us"]
return self.coordinator.data["current"]["measurements"]["aqi_cn"] return self.coordinator.data["measurements"]["aqi_cn"]
@property @property
def available(self): def available(self):
@ -52,61 +52,59 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
@property @property
def carbon_dioxide(self): def carbon_dioxide(self):
"""Return the CO2 (carbon dioxide) level.""" """Return the CO2 (carbon dioxide) level."""
return self.coordinator.data["current"]["measurements"].get("co2") return self.coordinator.data["measurements"].get("co2")
@property @property
def device_info(self): def device_info(self):
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return {
"identifiers": { "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
(DOMAIN, self.coordinator.data["current"]["serial_number"]) "name": self.coordinator.data["settings"]["node_name"],
},
"name": self.coordinator.data["current"]["settings"]["node_name"],
"manufacturer": "AirVisual", "manufacturer": "AirVisual",
"model": f'{self.coordinator.data["current"]["status"]["model"]}', "model": f'{self.coordinator.data["status"]["model"]}',
"sw_version": ( "sw_version": (
f'Version {self.coordinator.data["current"]["status"]["system_version"]}' f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["current"]["status"]["app_version"]}' f'{self.coordinator.data["status"]["app_version"]}'
), ),
} }
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
node_name = self.coordinator.data["current"]["settings"]["node_name"] node_name = self.coordinator.data["settings"]["node_name"]
return f"{node_name} Node/Pro: Air Quality" return f"{node_name} Node/Pro: Air Quality"
@property @property
def particulate_matter_2_5(self): def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level.""" """Return the particulate matter 2.5 level."""
return self.coordinator.data["current"]["measurements"].get("pm2_5") return self.coordinator.data["measurements"].get("pm2_5")
@property @property
def particulate_matter_10(self): def particulate_matter_10(self):
"""Return the particulate matter 10 level.""" """Return the particulate matter 10 level."""
return self.coordinator.data["current"]["measurements"].get("pm1_0") return self.coordinator.data["measurements"].get("pm1_0")
@property @property
def particulate_matter_0_1(self): def particulate_matter_0_1(self):
"""Return the particulate matter 0.1 level.""" """Return the particulate matter 0.1 level."""
return self.coordinator.data["current"]["measurements"].get("pm0_1") return self.coordinator.data["measurements"].get("pm0_1")
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, Home Assistant friendly identifier for this entity.""" """Return a unique, Home Assistant friendly identifier for this entity."""
return self.coordinator.data["current"]["serial_number"] return self.coordinator.data["serial_number"]
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self):
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
self._attrs.update( self._attrs.update(
{ {
ATTR_VOC: self.coordinator.data["current"]["measurements"].get("voc"), ATTR_VOC: self.coordinator.data["measurements"].get("voc"),
**{ **{
ATTR_SENSOR_LIFE.format(pollutant): lifespan ATTR_SENSOR_LIFE.format(pollutant): lifespan
for pollutant, lifespan in self.coordinator.data["current"][ for pollutant, lifespan in self.coordinator.data["status"][
"status" "sensor_life"
]["sensor_life"].items() ].items()
}, },
} }
) )

View File

@ -1,7 +1,7 @@
"""Define a config flow manager for AirVisual.""" """Define a config flow manager for AirVisual."""
import asyncio import asyncio
from pyairvisual import Client from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import InvalidKeyError, NodeProError from pyairvisual.errors import InvalidKeyError, NodeProError
import voluptuous as vol import voluptuous as vol
@ -108,7 +108,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
websession = aiohttp_client.async_get_clientsession(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass)
client = Client(session=websession, api_key=user_input[CONF_API_KEY]) cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
# If this is the first (and only the first) time we've seen this API key, check # If this is the first (and only the first) time we've seen this API key, check
# that it's valid: # that it's valid:
@ -120,7 +120,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async with check_keys_lock: async with check_keys_lock:
if user_input[CONF_API_KEY] not in checked_keys: if user_input[CONF_API_KEY] not in checked_keys:
try: try:
await client.api.nearest_city() await cloud_api.air_quality.nearest_city()
except InvalidKeyError: except InvalidKeyError:
return self.async_show_form( return self.async_show_form(
step_id="geography", step_id="geography",
@ -157,16 +157,10 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) await self._async_set_unique_id(user_input[CONF_IP_ADDRESS])
websession = aiohttp_client.async_get_clientsession(self.hass) node = NodeSamba(user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD])
client = Client(session=websession)
try: try:
await client.node.from_samba( await node.async_connect()
user_input[CONF_IP_ADDRESS],
user_input[CONF_PASSWORD],
include_history=False,
include_trends=False,
)
except NodeProError as err: except NodeProError as err:
LOGGER.error("Error connecting to Node/Pro unit: %s", err) LOGGER.error("Error connecting to Node/Pro unit: %s", err)
return self.async_show_form( return self.async_show_form(
@ -175,6 +169,8 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors={CONF_IP_ADDRESS: "unable_to_connect"}, errors={CONF_IP_ADDRESS: "unable_to_connect"},
) )
await node.async_disconnect()
return self.async_create_entry( return self.async_create_entry(
title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})",
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO}, data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO},

View File

@ -3,6 +3,6 @@
"name": "AirVisual", "name": "AirVisual",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airvisual", "documentation": "https://www.home-assistant.io/integrations/airvisual",
"requirements": ["pyairvisual==4.4.0"], "requirements": ["pyairvisual==5.0.2"],
"codeowners": ["@bachya"] "codeowners": ["@bachya"]
} }

View File

@ -225,22 +225,20 @@ class AirVisualNodeProSensor(AirVisualEntity):
def device_info(self): def device_info(self):
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return {
"identifiers": { "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
(DOMAIN, self.coordinator.data["current"]["serial_number"]) "name": self.coordinator.data["settings"]["node_name"],
},
"name": self.coordinator.data["current"]["settings"]["node_name"],
"manufacturer": "AirVisual", "manufacturer": "AirVisual",
"model": f'{self.coordinator.data["current"]["status"]["model"]}', "model": f'{self.coordinator.data["status"]["model"]}',
"sw_version": ( "sw_version": (
f'Version {self.coordinator.data["current"]["status"]["system_version"]}' f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["current"]["status"]["app_version"]}' f'{self.coordinator.data["status"]["app_version"]}'
), ),
} }
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
node_name = self.coordinator.data["current"]["settings"]["node_name"] node_name = self.coordinator.data["settings"]["node_name"]
return f"{node_name} Node/Pro: {self._name}" return f"{node_name} Node/Pro: {self._name}"
@property @property
@ -251,18 +249,14 @@ class AirVisualNodeProSensor(AirVisualEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, Home Assistant friendly identifier for this entity.""" """Return a unique, Home Assistant friendly identifier for this entity."""
return f"{self.coordinator.data['current']['serial_number']}_{self._kind}" return f"{self.coordinator.data['serial_number']}_{self._kind}"
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self):
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
if self._kind == SENSOR_KIND_BATTERY_LEVEL: if self._kind == SENSOR_KIND_BATTERY_LEVEL:
self._state = self.coordinator.data["current"]["status"]["battery"] self._state = self.coordinator.data["status"]["battery"]
elif self._kind == SENSOR_KIND_HUMIDITY: elif self._kind == SENSOR_KIND_HUMIDITY:
self._state = self.coordinator.data["current"]["measurements"].get( self._state = self.coordinator.data["measurements"].get("humidity")
"humidity"
)
elif self._kind == SENSOR_KIND_TEMPERATURE: elif self._kind == SENSOR_KIND_TEMPERATURE:
self._state = self.coordinator.data["current"]["measurements"].get( self._state = self.coordinator.data["measurements"].get("temperature_C")
"temperature_C"
)

View File

@ -1229,7 +1229,7 @@ pyaehw4a1==0.3.9
pyaftership==0.1.2 pyaftership==0.1.2
# homeassistant.components.airvisual # homeassistant.components.airvisual
pyairvisual==4.4.0 pyairvisual==5.0.2
# homeassistant.components.almond # homeassistant.components.almond
pyalmond==0.0.2 pyalmond==0.0.2

View File

@ -598,7 +598,7 @@ py_nextbusnext==0.1.4
pyaehw4a1==0.3.9 pyaehw4a1==0.3.9
# homeassistant.components.airvisual # homeassistant.components.airvisual
pyairvisual==4.4.0 pyairvisual==5.0.2
# homeassistant.components.almond # homeassistant.components.almond
pyalmond==0.0.2 pyalmond==0.0.2

View File

@ -69,7 +69,7 @@ async def test_invalid_identifier(hass):
} }
with patch( with patch(
"pyairvisual.api.API.nearest_city", "pyairvisual.air_quality.AirQuality",
side_effect=InvalidKeyError, side_effect=InvalidKeyError,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -96,7 +96,7 @@ async def test_migration(hass):
assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1
with patch("pyairvisual.api.API.nearest_city"), patch.object( with patch("pyairvisual.air_quality.AirQuality.nearest_city"), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setup"
): ):
assert await async_setup_component(hass, DOMAIN, {DOMAIN: conf}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: conf})
@ -130,7 +130,7 @@ async def test_node_pro_error(hass):
node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"} node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"}
with patch( with patch(
"pyairvisual.node.Node.from_samba", "pyairvisual.node.NodeSamba.async_connect",
side_effect=NodeProError, side_effect=NodeProError,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -185,7 +185,7 @@ async def test_step_geography(hass):
with patch( with patch(
"homeassistant.components.airvisual.async_setup_entry", return_value=True "homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.api.API.nearest_city"): ), patch("pyairvisual.air_quality.AirQuality.nearest_city"):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
) )
@ -209,7 +209,7 @@ async def test_step_import(hass):
with patch( with patch(
"homeassistant.components.airvisual.async_setup_entry", return_value=True "homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.api.API.nearest_city"): ), patch("pyairvisual.air_quality.AirQuality.nearest_city"):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=geography_conf DOMAIN, context={"source": SOURCE_IMPORT}, data=geography_conf
) )
@ -230,7 +230,11 @@ async def test_step_node_pro(hass):
with patch( with patch(
"homeassistant.components.airvisual.async_setup_entry", return_value=True "homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.node.Node.from_samba"): ), patch("pyairvisual.node.NodeSamba.async_connect"), patch(
"pyairvisual.node.NodeSamba.async_get_latest_measurements"
), patch(
"pyairvisual.node.NodeSamba.async_disconnect"
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"}
) )
@ -268,8 +272,8 @@ async def test_step_reauth(hass):
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
with patch( with patch(
"homeassistant.components.simplisafe.async_setup_entry", return_value=True "homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.api.API.nearest_city"): ), patch("pyairvisual.air_quality.AirQuality"):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_API_KEY: "defgh67890"} result["flow_id"], user_input={CONF_API_KEY: "defgh67890"}
) )