Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PECO power outage counter integration #65194

Merged
merged 40 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
38309c5
Create a new NWS Alerts integration
IceBotYT Jan 24, 2022
aeabdd3
Create a new NWS Alerts integration
IceBotYT Jan 24, 2022
03c9141
Create new PECO integration
IceBotYT Jan 29, 2022
34d051a
Remove empty keys
IceBotYT Jan 29, 2022
906e4f8
Revert "Create a new NWS Alerts integration"
IceBotYT Jan 29, 2022
15e8d40
Revert "Create a new NWS Alerts integration"
IceBotYT Jan 29, 2022
f646cd9
Fix test with new mock data
IceBotYT Jan 29, 2022
ced625f
Add init and sensor to .coveragerc and more tests for config flow
IceBotYT Jan 29, 2022
fe96df9
Small fixes and replacing patch with pytest.raises in testing invalid…
IceBotYT Jan 30, 2022
1a3de51
Add type defs and fix test_config_flow to use MultipleValid instead
IceBotYT Jan 30, 2022
5f708a6
Fix issues with 'typing.Dict'
IceBotYT Jan 30, 2022
cfb772a
Move API communication to seperate PyPI library
IceBotYT Jan 31, 2022
d267e43
Switch PyPI library from httpx to aiohttp to allow for passing in web…
IceBotYT Feb 1, 2022
586d8ff
Commit file changes requested by farmio as listed here: https://githu…
IceBotYT Feb 2, 2022
813b1f4
Add suggestions requested by farmio as listed here: https://github.co…
IceBotYT Feb 3, 2022
5a16de2
Move native_unit_of_measurement from prop to attr
IceBotYT Feb 4, 2022
d056b01
Update PLATFORMS constant type annotation
IceBotYT Feb 4, 2022
c452e8d
Add peco to .strict-typing
IceBotYT Feb 4, 2022
ad0b648
Merge branch 'dev' into peco-outage-count
IceBotYT Feb 4, 2022
17ab75d
Forgot to import Final
IceBotYT Feb 4, 2022
387ef99
Do as requested [here](https://github.com/home-assistant/core/runs/50…
IceBotYT Feb 4, 2022
20490c0
Updated mypy.ini, checks should pass now
IceBotYT Feb 4, 2022
f82aaaa
Fix to conform to mypy restrictions https://github.com/home-assistant…
IceBotYT Feb 4, 2022
52804b2
Fix type annotations
IceBotYT Feb 4, 2022
e06a126
Fix tests
IceBotYT Feb 4, 2022
465380e
Use cast in async_update_data
IceBotYT Feb 4, 2022
5b12a18
Add data type to CoordinatorEntity and DataUpdateCoordinator
IceBotYT Feb 5, 2022
92e6bd8
Merge branch 'dev' into peco-outage-count
IceBotYT Mar 12, 2022
e40955a
More cleanup from suggestions here: https://github.com/home-assistant…
IceBotYT Mar 14, 2022
0f19d62
Fix tests for new code
IceBotYT Mar 14, 2022
cba3b90
Merge branch 'peco-outage-count' of https://github.com/IceBotYT/core …
IceBotYT Mar 14, 2022
a276553
Cleaning up a speck of dust
IceBotYT Mar 14, 2022
7265ff2
Remove unused variable from the peco sensor
IceBotYT Mar 15, 2022
1704d36
Add rounding to percentage, and extra clean-up
IceBotYT Mar 15, 2022
b83e77f
Final suggestions from @farmio
IceBotYT Mar 20, 2022
fd11fb6
Update SCAN_INTERVAL to be a little bit faster
IceBotYT Mar 20, 2022
53d2625
Change the SCAN_INTERVAL to be somewhat near the update interval of t…
IceBotYT Mar 20, 2022
9491e60
Merge remote-tracking branch 'upstream/dev' into peco-outage-count
IceBotYT Mar 20, 2022
a8e9218
Fix config_flows.py to new method
IceBotYT Mar 21, 2022
49c180b
New UpdateCoordinator typing
IceBotYT Mar 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,8 @@ omit =
homeassistant/components/ozw/services.py
homeassistant/components/panasonic_bluray/media_player.py
homeassistant/components/panasonic_viera/media_player.py
homeassistant/components/peco/__init__.py
homeassistant/components/peco/sensor.py
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
homeassistant/components/pandora/media_player.py
homeassistant/components/pcal9535a/*
homeassistant/components/pencom/switch.py
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,8 @@ tests/components/panel_custom/* @home-assistant/frontend
homeassistant/components/panel_iframe/* @home-assistant/frontend
tests/components/panel_iframe/* @home-assistant/frontend
homeassistant/components/pcal9535a/* @Shulyaka
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
homeassistant/components/peco/* @IceBotYT
tests/components/peco/* @IceBotYT
homeassistant/components/persistent_notification/* @home-assistant/core
tests/components/persistent_notification/* @home-assistant/core
homeassistant/components/philips_js/* @elupus
Expand Down
35 changes: 35 additions & 0 deletions homeassistant/components/peco/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""The PECO Outage Counter integration."""
from __future__ import annotations

import aiohttp
import peco

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

from .const import DOMAIN

PLATFORMS: list[str] = [Platform.SENSOR]
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved


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

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
"api": peco,
"websession": aiohttp.ClientSession(),
farmio marked this conversation as resolved.
Show resolved Hide resolved
}
hass.config_entries.async_setup_platforms(entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved

return unload_ok
52 changes: 52 additions & 0 deletions homeassistant/components/peco/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Config flow for PECO Outage Counter integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult

from .const import COUNTY_LIST, DOMAIN

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("county"): vol.In(COUNTY_LIST),
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
}
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for PECO Outage Counter."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)

county = user_input[
"county"
] # Voluptuous automatically detects if the county is invalid
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved

await self.async_set_unique_id(county)
self._abort_if_unique_id_configured() # this should abort if the county was already configured

# take the county name and make it all lowercase except for the first letter
# this is to make it easier to read in the UI
county_name = county.lower()
county_name = county_name[0].upper() + county_name[1:]
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved

return self.async_create_entry(
title=county_name + " Outage Count", data=user_input
)

async def async_step_reauth(self, user_input: dict[str, Any] = None) -> FlowResult:
"""Handle re-auth step."""
return await self.async_step_user(user_input)
15 changes: 15 additions & 0 deletions homeassistant/components/peco/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Constants for the PECO Outage Counter integration."""
import logging

DOMAIN = "peco"
_LOGGER = logging.getLogger(__name__)
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
COUNTY_LIST = [
"BUCKS",
"CHESTER",
"DELAWARE",
"MONTGOMERY",
"PHILADELPHIA",
"YORK",
"TOTAL",
]
SCAN_INTERVAL = 5
13 changes: 13 additions & 0 deletions homeassistant/components/peco/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"domain": "peco",
"name": "PECO Outage Counter",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/peco",
"codeowners": [
"@IceBotYT"
],
"iot_class": "cloud_polling",
"requirements": [
"peco==0.0.14"
]
}
117 changes: 117 additions & 0 deletions homeassistant/components/peco/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Sensor component for PECO outage counter."""
from datetime import timedelta
from types import MappingProxyType
from typing import Any, Final

import async_timeout
from peco import BadJSONError, HttpError, InvalidCountyError

from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)

from .const import _LOGGER, DOMAIN, SCAN_INTERVAL

PARALLEL_UPDATES: Final = 0
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
api = hass.data[DOMAIN][config_entry.entry_id]["api"]
websession = hass.data[DOMAIN][config_entry.entry_id]["websession"]
conf: MappingProxyType[str, Any] = config_entry.data
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved

async def async_update_data():
"""Fetch data from API."""
try:
async with async_timeout.timeout(10):
if conf["county"] == "TOTAL":
return await api.get_outage_totals(websession)
else:
return await api.get_outage_count(conf["county"], websession)
except InvalidCountyError as err:
raise ConfigEntryAuthFailed from err
except HttpError as err:
raise UpdateFailed from err
except BadJSONError as err:
raise UpdateFailed from err

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="PECO Outage Count",
update_method=async_update_data,
update_interval=timedelta(minutes=SCAN_INTERVAL),
)

await coordinator.async_config_entry_first_refresh()
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved

_LOGGER.info("Setting up sensor platform")
county = conf["county"]
_LOGGER.info("County: %s", county)
async_add_entities(
[PecoOutageCounterSensorEntity(hass, config_entry.title, county, coordinator)],
True,
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
)
return
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved


class PecoOutageCounterSensorEntity(CoordinatorEntity, SensorEntity):
"""PECO outage counter sensor."""

_attr_state_class = SensorStateClass.MEASUREMENT
_attr_icon: str = "mdi:power-plug-off"

def __init__(
self,
hass: HomeAssistant,
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
name: str,
county: str,
coordinator: DataUpdateCoordinator,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.hass = hass
self._county = county
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
self._name = name

@property
def name(self) -> str:
farmio marked this conversation as resolved.
Show resolved Hide resolved
"""Return the name of the sensor."""
return self._name

@property
def native_value(self) -> int:
"""Return the value of the sensor."""
return self.coordinator.data["customers_out"]

@property
def extra_state_attributes(self):
farmio marked this conversation as resolved.
Show resolved Hide resolved
"""Return the state attributes."""
if self.coordinator.data["percent_customers_out"] < 5:
percent_customers_out = "Less than 5%"
else:
percent_customers_out = (
str(self.coordinator.data["percent_customers_out"]) + "%"
)
return {
"percent_customers_out": percent_customers_out,
"outage_count": self.coordinator.data["outage_count"],
"customers_served": self.coordinator.data["customers_served"],
}

@property
def unique_id(self) -> str:
farmio marked this conversation as resolved.
Show resolved Hide resolved
"""Return a unique ID."""
return self._county
16 changes: 16 additions & 0 deletions homeassistant/components/peco/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config": {
"step": {
"user": {
"title": "PECO Outage Counter",
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
"description": "Please choose your county below.",
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
"data": {
"county": "County"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
16 changes: 16 additions & 0 deletions homeassistant/components/peco/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
IceBotYT marked this conversation as resolved.
Show resolved Hide resolved
},
"step": {
"user": {
"data": {
"county": "County"
},
"description": "Please choose your county below.",
"title": "PECO Outage Counter"
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
"ozw",
"p1_monitor",
"panasonic_viera",
"peco",
"philips_js",
"pi_hole",
"picnic",
Expand Down
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,9 @@ pcal9535a==0.7
# homeassistant.components.dunehd
pdunehd==1.3.2

# homeassistant.components.peco
peco==0.0.14

# homeassistant.components.pencom
pencompy==0.0.3

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ panasonic_viera==0.3.6
# homeassistant.components.dunehd
pdunehd==1.3.2

# homeassistant.components.peco
peco==0.0.14

# homeassistant.components.aruba
# homeassistant.components.cisco_ios
# homeassistant.components.pandora
Expand Down
1 change: 1 addition & 0 deletions tests/components/peco/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the PECO Outage Counter integration."""