Skip to content

Commit

Permalink
Bump pyemvue to 0.18.2 which has improved resiliency. Formatting fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
magico13 committed Feb 25, 2024
1 parent 2853fd9 commit e90cf4d
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 72 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Michael Marvin
Copyright (c) 2024 Michael Marvin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
42 changes: 22 additions & 20 deletions custom_components/emporia_vue/__init__.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
"""The Emporia Vue integration."""
import asyncio
from datetime import datetime, timedelta, timezone
from typing import Any, Optional
import dateutil.tz
import dateutil.relativedelta
import logging
import re
from typing import Any, Optional

import dateutil.relativedelta
import dateutil.tz
from pyemvue import PyEmVue
from pyemvue.device import (
VueDevice,
VueDeviceChannel,
VueUsageDevice,
VueDeviceChannelUsage,
VueUsageDevice,
)
from pyemvue.enums import Scale
import re
import requests

import voluptuous as vol

from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.update_coordinator import CoordinatorEntity, DataUpdateCoordinator, UpdateFailed
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, VUE_DATA, ENABLE_1M, ENABLE_1D, ENABLE_1MON
from .const import DOMAIN, ENABLE_1D, ENABLE_1M, ENABLE_1MON, VUE_DATA

CONFIG_SCHEMA = vol.Schema(
{
Expand Down Expand Up @@ -91,6 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
loop = asyncio.get_event_loop()
try:
result = await loop.run_in_executor(None, vue.login, email, password)
#result = await loop.run_in_executor(None, vue.login_simulator, "http://localhost:8000", email, password)
if not result:
raise Exception("Could not authenticate with Emporia API")
except Exception:
Expand All @@ -100,7 +100,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
try:
devices = await loop.run_in_executor(None, vue.get_devices)
for device in devices:
if not device.device_gid in DEVICE_GIDS:
if device.device_gid not in DEVICE_GIDS:
DEVICE_GIDS.append(device.device_gid)
_LOGGER.info("Adding gid %s to DEVICE_GIDS list", device.device_gid)
# await loop.run_in_executor(None, vue.populate_device_properties, device)
Expand All @@ -118,7 +118,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
)

async def async_update_data_1min():
"""Fetch data from API endpoint at a 1 minute interval
"""Fetch data from API endpoint at a 1 minute interval.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
Expand All @@ -132,7 +132,7 @@ async def async_update_data_1min():
return data

async def async_update_data_1mon():
"""Fetch data from API endpoint at a 1 hour interval
"""Fetch data from API endpoint at a 1 hour interval.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
Expand Down Expand Up @@ -213,7 +213,7 @@ async def async_update_day_sensors():

# Setup custom services
async def handle_set_charger_current(call):
"""Handle setting the EV Charger current"""
"""Handle setting the EV Charger current."""
_LOGGER.debug(
"executing set_charger_current: %s %s",
str(call.service),
Expand Down Expand Up @@ -350,6 +350,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):


async def update_sensors(vue: PyEmVue, scales: list[str]):
"""Fetch data from API endpoint."""
try:
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
Expand Down Expand Up @@ -482,7 +483,7 @@ async def parse_flattened_usage_data(
str(unused_data),
)
channels_were_added = False
for identifier, channel in unused_data.items():
for _, channel in unused_data.items():
channels_were_added |= await handle_special_channels_for_device(channel)
# we'll also need to register these entities I think. They might show up automatically on the first run
# When we're done handling the unused data we need to rerun the update
Expand All @@ -492,6 +493,7 @@ async def parse_flattened_usage_data(


async def handle_special_channels_for_device(channel: VueDeviceChannel) -> bool:
"""Handle the special channels for a device, if they exist."""
device_info = None
if channel.device_gid in DEVICE_INFORMATION:
device_info = DEVICE_INFORMATION[channel.device_gid]
Expand Down Expand Up @@ -546,12 +548,12 @@ async def handle_special_channels_for_device(channel: VueDeviceChannel) -> bool:


def make_channel_id(channel: VueDeviceChannel, scale: str):
"""Format the channel id for a channel and scale"""
return "{0}-{1}-{2}".format(channel.device_gid, channel.channel_num, scale)
"""Format the channel id for a channel and scale."""
return f"{channel.device_gid}-{channel.channel_num}-{scale}"


def fix_usage_sign(channel_num: str, usage: float):
"""If the channel is not '1,2,3' or 'Balance' we need it to be positive (see https://github.com/magico13/ha-emporia-vue/issues/57)"""
"""If the channel is not '1,2,3' or 'Balance' we need it to be positive (see https://github.com/magico13/ha-emporia-vue/issues/57)."""
if usage and channel_num not in ["1,2,3", "Balance"]:
return abs(usage)
return usage
Expand All @@ -567,7 +569,7 @@ def change_time_to_local(time: datetime, tz_string: str):


def check_for_midnight(timestamp: datetime, device_gid: int, day_id: str):
"""If midnight has recently passed, reset the LAST_DAY_DATA for Day sensors to zero"""
"""If midnight has recently passed, reset the LAST_DAY_DATA for Day sensors to zero."""
if device_gid in DEVICE_INFORMATION:
device_info = DEVICE_INFORMATION[device_gid]
local_time = change_time_to_local(timestamp, device_info.time_zone)
Expand All @@ -589,7 +591,7 @@ def check_for_midnight(timestamp: datetime, device_gid: int, day_id: str):
def determine_reset_datetime(
local_time: datetime, monthly_cycle_start: int, is_month: bool
):
"""Determine the last reset datetime (aware) based on the passed time and cycle start date"""
"""Determine the last reset datetime (aware) based on the passed time and cycle start date."""
reset_datetime = local_time.replace(hour=0, minute=0, second=0, microsecond=0)
if is_month:
# Month should use the last billing_cycle_start_day of either this or last month
Expand Down
23 changes: 11 additions & 12 deletions custom_components/emporia_vue/charger_entity.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import logging
"""Emporia Charger Entity."""
from typing import Any, Optional

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)

from .const import DOMAIN

from pyemvue import pyemvue
from pyemvue.device import VueDevice, ChargerDevice
from pyemvue.device import ChargerDevice, VueDevice

_LOGGER = logging.getLogger(__name__)
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN


class EmporiaChargerEntity(CoordinatorEntity):
"""Emporia Charger Entity"""
"""Emporia Charger Entity."""

def __init__(
self,
Expand All @@ -25,6 +21,7 @@ def __init__(
device_class: str,
enabled_default=True,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._coordinator = coordinator
self._device = device
Expand All @@ -37,10 +34,12 @@ def __init__(

@property
def entity_registry_enabled_default(self):
"""Return whether the entity should be enabled when first added to the entity registry."""
return self._enabled_default

@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes of the device."""
data: ChargerDevice = self._coordinator.data[self._device.device_gid]
if data:
return {
Expand All @@ -57,14 +56,14 @@ def extra_state_attributes(self) -> dict[str, Any]:

@property
def unique_id(self) -> str:
"""Unique ID for the charger"""
"""Unique ID for the charger."""
return f"charger.emporia_vue.{self._device.device_gid}"

@property
def device_info(self):
"""Return the device information."""
return {
"identifiers": {(DOMAIN, "{0}-1,2,3".format(self._device.device_gid))},
"identifiers": {(DOMAIN, f"{self._device.device_gid}-1,2,3")},
"name": self._device.device_name + "-1,2,3",
"model": self._device.model,
"sw_version": self._device.firmware,
Expand Down
9 changes: 4 additions & 5 deletions custom_components/emporia_vue/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"""Config flow for Emporia Vue integration."""
import logging
import asyncio
import logging

from pyemvue import PyEmVue
import voluptuous as vol

from homeassistant import config_entries, core, exceptions
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD

from .const import DOMAIN # pylint:disable=unused-import
from .const import ENABLE_1M, ENABLE_1D, ENABLE_1MON

from pyemvue import PyEmVue
from .const import DOMAIN, ENABLE_1D, ENABLE_1M, ENABLE_1MON

_LOGGER = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion custom_components/emporia_vue/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
ENABLE_1S = "enable_1s"
ENABLE_1M = "enable_1m"
ENABLE_1D = "enable_1d"
ENABLE_1MON = "enable_1mon"
ENABLE_1MON = "enable_1mon"
4 changes: 2 additions & 2 deletions custom_components/emporia_vue/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/magico13/ha-emporia-vue/issues",
"requirements": ["pyemvue==0.17.2"],
"requirements": ["pyemvue==0.18.2"],
"ssdp": [],
"version": "0.9.0",
"version": "0.9.1",
"zeroconf": []
}
31 changes: 12 additions & 19 deletions custom_components/emporia_vue/sensor.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
"""Platform for sensor integration."""
import logging
from typing import Optional

from pyemvue.device import VueDevice, VueDeviceChannel
from pyemvue.enums import Scale

from homeassistant.components.sensor import (
SensorStateClass,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
import logging

from homeassistant.const import (
UnitOfEnergy,
UnitOfPower,
)
from homeassistant.const import UnitOfEnergy, UnitOfPower
from homeassistant.core import HomeAssistant

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN

from pyemvue.enums import Scale
from pyemvue.device import VueDevice, VueDeviceChannel

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -120,22 +114,21 @@ def last_reset(self):

@property
def unique_id(self):
"""Unique ID for the sensor"""
"""Unique ID for the sensor."""
if self._scale == Scale.MINUTE.value:
return f"sensor.emporia_vue.instant.{self._channel.device_gid}-{self._channel.channel_num}"
return f"sensor.emporia_vue.{self._scale}.{self._channel.device_gid}-{self._channel.channel_num}"

@property
def device_info(self):
"""Return the device info."""
device_name = self._channel.name or self._device.device_name
return {
"identifiers": {
# Serial numbers are unique identifiers within a specific domain
(
DOMAIN,
"{0}-{1}".format(
self._device.device_gid, self._channel.channel_num
),
f"{self._device.device_gid}-{self._channel.channel_num}",
)
},
"name": device_name,
Expand All @@ -160,7 +153,7 @@ def scale_usage(self, usage):
return usage

def scale_is_energy(self):
"""Returns True if the scale is an energy unit instead of power (hour and bigger)"""
"""Return True if the scale is an energy unit instead of power."""
return self._scale not in (
Scale.MINUTE.value,
Scale.SECOND.value,
Expand Down
Loading

0 comments on commit e90cf4d

Please sign in to comment.