Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ homeassistant.components.bang_olufsen.*
homeassistant.components.bayesian.*
homeassistant.components.besen.*
homeassistant.components.binary_sensor.*
homeassistant.components.birdnet_go.*
homeassistant.components.bitcoin.*
homeassistant.components.blockchain.*
homeassistant.components.blue_current.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions homeassistant/components/birdnet_go/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""The BirdNET-Go integration."""

from aiobirdnetgo import BirdNetGoClient

from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DEFAULT_PORT
from .coordinator import BirdNetGoConfigEntry, BirdNetGoDataUpdateCoordinator

PLATFORMS: list[Platform] = [Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: BirdNetGoConfigEntry) -> bool:
"""Set up BirdNET-Go from a config entry."""
client = BirdNetGoClient(
host=entry.data[CONF_HOST],
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
use_ssl=entry.data.get(CONF_SSL, False),
session=async_get_clientsession(hass),
)

coordinator = BirdNetGoDataUpdateCoordinator(hass, entry, client)
await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: BirdNetGoConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
100 changes: 100 additions & 0 deletions homeassistant/components/birdnet_go/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Config flow for BirdNET-Go integration."""

from typing import Any, override

from aiobirdnetgo import (
BirdNetGoAuthenticationError,
BirdNetGoClient,
BirdNetGoConnectionError,
BirdNetGoError,
BirdNetGoTimeoutError,
)
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
BooleanSelector,
NumberSelector,
NumberSelectorConfig,
NumberSelectorMode,
TextSelector,
)

from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN, LOGGER

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
vol.Optional(CONF_PORT, default=DEFAULT_PORT): NumberSelector(
NumberSelectorConfig(
min=1,
max=65535,
step=1,
mode=NumberSelectorMode.BOX,
)
),
Comment thread
TN-1 marked this conversation as resolved.
Outdated
vol.Optional(CONF_SSL, default=False): BooleanSelector(),
}
)


class BirdNetGoConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for BirdNET-Go."""

VERSION = 1

@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}

if user_input is not None:
raw_host = user_input[CONF_HOST].strip()
raw_ssl = bool(user_input.get(CONF_SSL, False))

session = async_get_clientsession(self.hass)
try:
raw_port = int(user_input.get(CONF_PORT, DEFAULT_PORT))
client = BirdNetGoClient(
host=raw_host,
port=raw_port,
use_ssl=raw_ssl,
session=session,
)
await client.get_kpis()
Comment thread
TN-1 marked this conversation as resolved.
Comment thread
TN-1 marked this conversation as resolved.
except ValueError:
errors["base"] = "cannot_connect"
except BirdNetGoAuthenticationError:
errors["base"] = "auth_not_supported"
except BirdNetGoConnectionError, BirdNetGoTimeoutError:
errors["base"] = "cannot_connect"
except BirdNetGoError:
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected exception during BirdNET-Go setup")
errors["base"] = "unknown"
else:
if not errors:
host = client.host
port = client.port
use_ssl = client.use_ssl
user_input[CONF_HOST] = host
user_input[CONF_PORT] = port
user_input[CONF_SSL] = use_ssl

unique_id = f"{host}:{port}"
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()

title = f"{DEFAULT_NAME} ({host}:{port})"
Comment thread
TN-1 marked this conversation as resolved.
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=errors,
)
12 changes: 12 additions & 0 deletions homeassistant/components/birdnet_go/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Constants for the BirdNET-Go integration."""

from datetime import timedelta
import logging
from typing import Final

DOMAIN: Final = "birdnet_go"
LOGGER = logging.getLogger(__package__)

DEFAULT_NAME: Final = "BirdNET-Go"
DEFAULT_PORT: Final = 8080
SCAN_INTERVAL: Final = timedelta(seconds=30)
61 changes: 61 additions & 0 deletions homeassistant/components/birdnet_go/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""DataUpdateCoordinator for BirdNET-Go."""

from typing import override

from aiobirdnetgo import (
BirdNetGoAuthenticationError,
BirdNetGoClient,
BirdNetGoConnectionError,
BirdNetGoError,
BirdNetGoTimeoutError,
DashboardKPIs,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, LOGGER, SCAN_INTERVAL

type BirdNetGoConfigEntry = ConfigEntry[BirdNetGoDataUpdateCoordinator]


class BirdNetGoDataUpdateCoordinator(DataUpdateCoordinator[DashboardKPIs]):
"""Class to manage fetching BirdNET-Go data."""

config_entry: BirdNetGoConfigEntry

def __init__(
self,
hass: HomeAssistant,
config_entry: BirdNetGoConfigEntry,
client: BirdNetGoClient,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
LOGGER,
config_entry=config_entry,
name=f"{DOMAIN}_{config_entry.title}",
update_interval=SCAN_INTERVAL,
)
self.client = client

@override
async def _async_update_data(self) -> DashboardKPIs:
"""Fetch data from BirdNET-Go."""
try:
return await self.client.get_kpis()
except BirdNetGoAuthenticationError as err:
raise ConfigEntryAuthFailed(
f"Authentication failed for {self.client.base_url}"
) from err
except (BirdNetGoConnectionError, BirdNetGoTimeoutError) as err:
raise UpdateFailed(
f"Error communicating with BirdNET-Go at {self.client.base_url}: {err}"
) from err
except BirdNetGoError as err:
raise UpdateFailed(
f"Unexpected error communicating with BirdNET-Go: {err}"
) from err
18 changes: 18 additions & 0 deletions homeassistant/components/birdnet_go/icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"entity": {
"sensor": {
"best_day_count": {
"default": "mdi:trophy-award"
},
"detection_streak": {
"default": "mdi:calendar-range"
},
"lifetime_species": {
"default": "mdi:counter"
},
"today_detections": {
"default": "mdi:bird"
}
}
}
}
11 changes: 11 additions & 0 deletions homeassistant/components/birdnet_go/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"domain": "birdnet_go",
"name": "BirdNET-Go",
"codeowners": ["@TN-1"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/birdnet_go",
"integration_type": "service",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["aiobirdnetgo==0.1.4"]
}
86 changes: 86 additions & 0 deletions homeassistant/components/birdnet_go/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
rules:
# Bronze
action-setup:
status: exempt
comment: There are no custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: There are no custom actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Entities do not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done

# Silver
action-exceptions:
status: exempt
comment: There are no custom actions.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: There are no options flow parameters.
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
test-coverage: done

# Gold
devices: done
diagnostics: todo
discovery-update-info:
status: exempt
comment: There is no discovery.
discovery:
status: exempt
comment: There is no discovery.
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices:
status: exempt
comment: Station device is created at config entry setup and does not change at runtime.
entity-category: todo
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues:
status: exempt
comment: There are no repairable issues.
stale-devices:
status: exempt
comment: Station device is created at config entry setup and does not change at runtime.

# Platinum
async-dependency: done
inject-websession: done
strict-typing: done
Loading
Loading