Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ build.json @home-assistant/supervisor
/tests/components/broadlink/ @danielhiversen @felipediel @L-I-Am
/homeassistant/components/brother/ @bieniu
/tests/components/brother/ @bieniu
/homeassistant/components/brottsplatskartan/ @gjohansson-ST
/tests/components/brottsplatskartan/ @gjohansson-ST
/homeassistant/components/brunt/ @eavanvalkenburg
/tests/components/brunt/ @eavanvalkenburg
/homeassistant/components/bsblan/ @liudger
Expand Down
20 changes: 20 additions & 0 deletions homeassistant/components/brottsplatskartan/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
"""The brottsplatskartan component."""
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import PLATFORMS


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up brottsplatskartan from a config entry."""

hass.config_entries.async_setup_platforms(entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload brottsplatskartan config entry."""

return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
97 changes: 97 additions & 0 deletions homeassistant/components/brottsplatskartan/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Adds config flow for Brottsplatskartan integration."""
from __future__ import annotations

from typing import Any
import uuid

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import selector

from .const import AREAS, CONF_APP_ID, CONF_AREA, DEFAULT_NAME, DOMAIN

DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_LOCATION): selector.LocationSelector(
selector.LocationSelectorConfig(radius=False, icon="")
),
vol.Optional(CONF_AREA, default="none"): selector.SelectSelector(
selector.SelectSelectorConfig(
options=AREAS,
mode=selector.SelectSelectorMode.DROPDOWN,
translation_key="areas",
)
),
}
)


class BPKConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Brottsplatskartan integration."""

VERSION = 1

async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Import a configuration from config.yaml."""

if config.get(CONF_LATITUDE):
config[CONF_LOCATION] = {
CONF_LATITUDE: config[CONF_LATITUDE],
CONF_LONGITUDE: config[CONF_LONGITUDE],
}
if not config.get(CONF_AREA):
config[CONF_AREA] = "none"
else:
config[CONF_AREA] = config[CONF_AREA][0]

return await self.async_step_user(user_input=config)

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

if user_input is not None:
latitude: float | None = None
longitude: float | None = None
area: str | None = (
user_input[CONF_AREA] if user_input[CONF_AREA] != "none" else None
)

if area:
name = f"{DEFAULT_NAME} {area}"
elif location := user_input.get(CONF_LOCATION):
lat: float = location[CONF_LATITUDE]
long: float = location[CONF_LONGITUDE]
latitude = lat
longitude = long
name = f"{DEFAULT_NAME} {round(latitude, 2)}, {round(longitude, 2)}"
else:
latitude = self.hass.config.latitude
longitude = self.hass.config.longitude
name = f"{DEFAULT_NAME} HOME"

app = f"ha-{uuid.getnode()}"

self._async_abort_entries_match(
{CONF_AREA: area, CONF_LATITUDE: latitude, CONF_LONGITUDE: longitude}
)
return self.async_create_entry(
title=name,
data={
CONF_LATITUDE: latitude,
CONF_LONGITUDE: longitude,
CONF_AREA: area,
CONF_APP_ID: app,
},
)

return self.async_show_form(
step_id="user",
data_schema=DATA_SCHEMA,
errors=errors,
)
8 changes: 7 additions & 1 deletion homeassistant/components/brottsplatskartan/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import logging

from homeassistant.const import Platform

DOMAIN = "brottsplatskartan"
PLATFORMS = [Platform.SENSOR]

LOGGER = logging.getLogger(__package__)

CONF_AREA = "area"
CONF_APP_ID = "app_id"
DEFAULT_NAME = "Brottsplatskartan"

AREAS = [
"N/A",
"none",
"Blekinge län",
"Dalarnas län",
"Gotlands län",
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/brottsplatskartan/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"domain": "brottsplatskartan",
"name": "Brottsplatskartan",
"codeowners": [],
"codeowners": ["@gjohansson-ST"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/brottsplatskartan",
"iot_class": "cloud_polling",
"loggers": ["brottsplatskartan"],
Expand Down
74 changes: 53 additions & 21 deletions homeassistant/components/brottsplatskartan/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@

from collections import defaultdict
from datetime import timedelta
import uuid

import brottsplatskartan
from brottsplatskartan import ATTRIBUTION, BrottsplatsKartan
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import AREAS, CONF_AREA, DEFAULT_NAME, LOGGER
from .const import AREAS, CONF_APP_ID, CONF_AREA, DEFAULT_NAME, DOMAIN, LOGGER

SCAN_INTERVAL = timedelta(minutes=30)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
{
vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude,
Expand All @@ -29,39 +35,65 @@
)


def setup_platform(
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Brottsplatskartan platform."""

area = config.get(CONF_AREA)
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
name = config[CONF_NAME]

# Every Home Assistant instance should have their own unique
# app parameter: https://brottsplatskartan.se/sida/api
app = f"ha-{uuid.getnode()}"
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2023.7.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
)

bpk = brottsplatskartan.BrottsplatsKartan(
app=app, area=area, latitude=latitude, longitude=longitude
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)

add_entities([BrottsplatskartanSensor(bpk, name)], True)

async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Brottsplatskartan sensor entry."""

area = entry.data.get(CONF_AREA)
latitude = entry.data.get(CONF_LATITUDE)
longitude = entry.data.get(CONF_LONGITUDE)
app = entry.data[CONF_APP_ID]
name = entry.title

bpk = BrottsplatsKartan(app=app, area=area, latitude=latitude, longitude=longitude)

async_add_entities([BrottsplatskartanSensor(bpk, name, entry.entry_id)], True)


class BrottsplatskartanSensor(SensorEntity):
"""Representation of a Brottsplatskartan Sensor."""

_attr_attribution = brottsplatskartan.ATTRIBUTION
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True

def __init__(self, bpk: brottsplatskartan.BrottsplatsKartan, name: str) -> None:
def __init__(self, bpk: BrottsplatsKartan, name: str, entry_id: str) -> None:
"""Initialize the Brottsplatskartan sensor."""
self._brottsplatskartan = bpk
self._attr_name = name
self._attr_unique_id = entry_id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, entry_id)},
manufacturer="Brottsplatskartan",
name=name,
)

def update(self) -> None:
"""Update device state."""
Expand Down
32 changes: 32 additions & 0 deletions homeassistant/components/brottsplatskartan/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"step": {
"user": {
"data": {
"location": "[%key:common::config_flow::data::location%]",
"area": "Area"
},
"data_description": {
"location": "Put marker on location to cover within 5km radius",
"area": "If area is selected, any marked location is ignored"
}
}
}
},
"issues": {
"deprecated_yaml": {
"title": "The Brottsplatskartan YAML configuration is being removed",
"description": "Configuring Brottsplatskartan using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Brottsplatskartan YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
},
"selector": {
"areas": {
"options": {
"none": "No area"
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"braviatv",
"broadlink",
"brother",
"brottsplatskartan",
"brunt",
"bsblan",
"bthome",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@
"brottsplatskartan": {
"name": "Brottsplatskartan",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"browser": {
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,9 @@ broadlink==0.18.3
# homeassistant.components.brother
brother==2.3.0

# homeassistant.components.brottsplatskartan
brottsplatskartan==0.0.1

# homeassistant.components.brunt
brunt==1.2.0

Expand Down
1 change: 1 addition & 0 deletions tests/components/brottsplatskartan/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Brottsplatskartan integration."""
25 changes: 25 additions & 0 deletions tests/components/brottsplatskartan/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Test fixtures for Brottplatskartan."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch

import pytest


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.brottsplatskartan.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry


@pytest.fixture(autouse=True)
def uuid_generator() -> Generator[AsyncMock, None, None]:
"""Generate uuid for app-id."""
with patch(
"homeassistant.components.brottsplatskartan.config_flow.uuid.getnode",
return_value="1234567890",
) as uuid_generator:
yield uuid_generator
Loading