Bump pydroid-ipcam to 2.0.0 (#76906)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Rami Mosleh 2022-08-19 12:57:30 +03:00 committed by GitHub
parent 324f5555ed
commit 63dcd8ec08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 37 deletions

View File

@ -57,4 +57,4 @@ class IPWebcamBinarySensor(AndroidIPCamBaseEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Return if motion is detected."""
return self.cam.export_sensor(MOTION_ACTIVE)[0] == 1.0
return self.cam.get_sensor_value(MOTION_ACTIVE) == 1.0

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any
from pydroid_ipcam import PyDroidIPCam
from pydroid_ipcam.exceptions import PyDroidIPCamException, Unauthorized
import voluptuous as vol
from homeassistant import config_entries
@ -33,7 +34,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool:
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
"""Validate the user input allows us to connect."""
websession = async_get_clientsession(hass)
@ -45,8 +46,16 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool:
password=data.get(CONF_PASSWORD),
ssl=False,
)
await cam.update()
return cam.available
errors = {}
try:
await cam.update()
except Unauthorized:
errors[CONF_USERNAME] = "invalid_auth"
errors[CONF_PASSWORD] = "invalid_auth"
except PyDroidIPCamException:
errors["base"] = "cannot_connect"
return errors
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -68,13 +77,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
)
# to be removed when YAML import is removed
title = user_input.get(CONF_NAME) or user_input[CONF_HOST]
if await validate_input(self.hass, user_input):
if not (errors := await validate_input(self.hass, user_input)):
return self.async_create_entry(title=title, data=user_input)
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors={"base": "cannot_connect"},
errors=errors,
)
async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult:

View File

@ -4,6 +4,7 @@ from datetime import timedelta
import logging
from pydroid_ipcam import PyDroidIPCam
from pydroid_ipcam.exceptions import PyDroidIPCamException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
@ -37,6 +38,7 @@ class AndroidIPCamDataUpdateCoordinator(DataUpdateCoordinator[None]):
async def _async_update_data(self) -> None:
"""Update Android IP Webcam entities."""
await self.cam.update()
if not self.cam.available:
raise UpdateFailed
try:
await self.cam.update()
except PyDroidIPCamException as err:
raise UpdateFailed(err) from err

View File

@ -4,7 +4,7 @@
"config_flow": true,
"dependencies": ["repairs"],
"documentation": "https://www.home-assistant.io/integrations/android_ip_webcam",
"requirements": ["pydroid-ipcam==1.3.1"],
"requirements": ["pydroid-ipcam==2.0.0"],
"codeowners": ["@engrbm87"],
"iot_class": "local_polling"
}

View File

@ -54,8 +54,8 @@ SENSOR_TYPES: tuple[AndroidIPWebcamSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda ipcam: ipcam.export_sensor("battery_level")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("battery_level")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("battery_level"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("battery_level"),
),
AndroidIPWebcamSensorEntityDescription(
key="battery_temp",
@ -63,56 +63,56 @@ SENSOR_TYPES: tuple[AndroidIPWebcamSensorEntityDescription, ...] = (
icon="mdi:thermometer",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda ipcam: ipcam.export_sensor("battery_temp")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("battery_temp")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("battery_temp"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("battery_temp"),
),
AndroidIPWebcamSensorEntityDescription(
key="battery_voltage",
name="Battery voltage",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda ipcam: ipcam.export_sensor("battery_voltage")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("battery_voltage")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("battery_voltage"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("battery_voltage"),
),
AndroidIPWebcamSensorEntityDescription(
key="light",
name="Light level",
icon="mdi:flashlight",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda ipcam: ipcam.export_sensor("light")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("light")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("light"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("light"),
),
AndroidIPWebcamSensorEntityDescription(
key="motion",
name="Motion",
icon="mdi:run",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda ipcam: ipcam.export_sensor("motion")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("motion")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("motion"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("motion"),
),
AndroidIPWebcamSensorEntityDescription(
key="pressure",
name="Pressure",
icon="mdi:gauge",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda ipcam: ipcam.export_sensor("pressure")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("pressure")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("pressure"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("pressure"),
),
AndroidIPWebcamSensorEntityDescription(
key="proximity",
name="Proximity",
icon="mdi:map-marker-radius",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda ipcam: ipcam.export_sensor("proximity")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("proximity")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("proximity"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("proximity"),
),
AndroidIPWebcamSensorEntityDescription(
key="sound",
name="Sound",
icon="mdi:speaker",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda ipcam: ipcam.export_sensor("sound")[0],
unit_fn=lambda ipcam: ipcam.export_sensor("sound")[1],
value_fn=lambda ipcam: ipcam.get_sensor_value("sound"),
unit_fn=lambda ipcam: ipcam.get_sensor_unit("sound"),
),
AndroidIPWebcamSensorEntityDescription(
key="video_connections",

View File

@ -11,7 +11,8 @@
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"

View File

@ -55,8 +55,8 @@ SWITCH_TYPES: tuple[AndroidIPWebcamSwitchEntityDescription, ...] = (
name="Focus",
icon="mdi:image-filter-center-focus",
entity_category=EntityCategory.CONFIG,
on_func=lambda ipcam: ipcam.torch(activate=True),
off_func=lambda ipcam: ipcam.torch(activate=False),
on_func=lambda ipcam: ipcam.focus(activate=True),
off_func=lambda ipcam: ipcam.focus(activate=False),
),
AndroidIPWebcamSwitchEntityDescription(
key="gps_active",
@ -111,8 +111,8 @@ SWITCH_TYPES: tuple[AndroidIPWebcamSwitchEntityDescription, ...] = (
name="Video recording",
icon="mdi:record-rec",
entity_category=EntityCategory.CONFIG,
on_func=lambda ipcam: ipcam.record(activate=True),
off_func=lambda ipcam: ipcam.record(activate=False),
on_func=lambda ipcam: ipcam.record(record=True),
off_func=lambda ipcam: ipcam.record(record=False),
),
)

View File

@ -4,7 +4,8 @@
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect"
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication"
},
"step": {
"user": {

View File

@ -1476,7 +1476,7 @@ pydexcom==0.2.3
pydoods==1.0.2
# homeassistant.components.android_ip_webcam
pydroid-ipcam==1.3.1
pydroid-ipcam==2.0.0
# homeassistant.components.ebox
pyebox==1.1.4

View File

@ -1025,7 +1025,7 @@ pydeconz==103
pydexcom==0.2.3
# homeassistant.components.android_ip_webcam
pydroid-ipcam==1.3.1
pydroid-ipcam==2.0.0
# homeassistant.components.econet
pyeconet==0.1.15

View File

@ -1,6 +1,6 @@
"""Test the Android IP Webcam config flow."""
from datetime import timedelta
from unittest.mock import patch
from unittest.mock import Mock, patch
import aiohttp
@ -99,6 +99,27 @@ async def test_device_already_configured(
assert result2["reason"] == "already_configured"
async def test_form_invalid_auth(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test we handle invalid auth error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
aioclient_mock.get(
"http://1.1.1.1:8080/status.json?show_avail=1",
exc=aiohttp.ClientResponseError(Mock(), (), status=401),
)
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"host": "1.1.1.1", "port": 8080, "username": "user", "password": "wrong-pass"},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"username": "invalid_auth", "password": "invalid_auth"}
async def test_form_cannot_connect(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:

View File

@ -3,6 +3,7 @@
from collections.abc import Awaitable
from typing import Callable
from unittest.mock import Mock
import aiohttp
@ -19,6 +20,8 @@ MOCK_CONFIG_DATA = {
"name": "IP Webcam",
"host": "1.1.1.1",
"port": 8080,
"username": "user",
"password": "pass",
}
@ -50,10 +53,10 @@ async def test_successful_config_entry(
assert entry.state == ConfigEntryState.LOADED
async def test_setup_failed(
async def test_setup_failed_connection_error(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test integration failed due to an error."""
"""Test integration failed due to connection error."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
entry.add_to_hass(hass)
@ -67,6 +70,23 @@ async def test_setup_failed(
assert entry.state == ConfigEntryState.SETUP_RETRY
async def test_setup_failed_invalid_auth(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test integration failed due to invalid auth."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
entry.add_to_hass(hass)
aioclient_mock.get(
"http://1.1.1.1:8080/status.json?show_avail=1",
exc=aiohttp.ClientResponseError(Mock(), (), status=401),
)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == ConfigEntryState.SETUP_RETRY
async def test_unload_entry(hass: HomeAssistant, aioclient_mock_fixture) -> None:
"""Test removing integration."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)