Skip to content

Commit

Permalink
Implement translation keys, OhmeEntity base class and new unique IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-r committed Nov 21, 2024
1 parent 94028fe commit 0e4494e
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 693 deletions.
20 changes: 20 additions & 0 deletions custom_components/ohme/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from homeassistant import core
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
from .const import *
from .utils import get_option
from .api_client import OhmeApiClient
Expand Down Expand Up @@ -35,6 +36,25 @@ async def async_update_listener(hass, entry):

async def async_setup_entry(hass, entry):
"""This is called from the config flow."""

def _update_unique_id(entry: RegistryEntry) -> dict[str, str] | None:
"""Update unique IDs from old format."""
if entry.unique_id.startswith("ohme_"):
parts = entry.unique_id.split('_')
legacy_id = '_'.join(parts[2:])

if legacy_id in LEGACY_MAPPING:
new_id = LEGACY_MAPPING[legacy_id]
else:
new_id = legacy_id

new_id = f"{parts[1]}_{new_id}"

return {"new_unique_id": new_id}
return None

await async_migrate_entries(hass, entry.entry_id, _update_unique_id)

account_id = entry.data['email']

hass.data.setdefault(DOMAIN, {})
Expand Down
25 changes: 11 additions & 14 deletions custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, email, password):

# User info
self._user_id = ""
self._serial = ""
self.serial = ""

# Cache the last rule to use when we disable max charge or change schedule
self._last_rule = {}
Expand Down Expand Up @@ -171,29 +171,26 @@ def cap_available(self):
def get_device_info(self):
return self._device_info

def get_unique_id(self, name):
return f"ohme_{self._serial}_{name}"

# Push methods

async def async_pause_charge(self):
"""Pause an ongoing charge"""
result = await self._post_request(f"/v1/chargeSessions/{self._serial}/stop", skip_json=True)
result = await self._post_request(f"/v1/chargeSessions/{self.serial}/stop", skip_json=True)
return bool(result)

async def async_resume_charge(self):
"""Resume a paused charge"""
result = await self._post_request(f"/v1/chargeSessions/{self._serial}/resume", skip_json=True)
result = await self._post_request(f"/v1/chargeSessions/{self.serial}/resume", skip_json=True)
return bool(result)

async def async_approve_charge(self):
"""Approve a charge"""
result = await self._put_request(f"/v1/chargeSessions/{self._serial}/approve?approve=true")
result = await self._put_request(f"/v1/chargeSessions/{self.serial}/approve?approve=true")
return bool(result)

async def async_max_charge(self, state=True):
"""Enable max charge"""
result = await self._put_request(f"/v1/chargeSessions/{self._serial}/rule?maxCharge=" + str(state).lower())
result = await self._put_request(f"/v1/chargeSessions/{self.serial}/rule?maxCharge=" + str(state).lower())
return bool(result)

async def async_apply_session_rule(self, max_price=None, target_time=None, target_percent=None, pre_condition=None, pre_condition_length=None):
Expand Down Expand Up @@ -228,7 +225,7 @@ async def async_apply_session_rule(self, max_price=None, target_time=None, targe
max_price = 'true' if max_price else 'false'
pre_condition = 'true' if pre_condition else 'false'

result = await self._put_request(f"/v1/chargeSessions/{self._serial}/rule?enableMaxPrice={max_price}&targetTs={target_ts}&enablePreconditioning={pre_condition}&toPercent={target_percent}&preconditionLengthMins={pre_condition_length}")
result = await self._put_request(f"/v1/chargeSessions/{self.serial}/rule?enableMaxPrice={max_price}&targetTs={target_ts}&enablePreconditioning={pre_condition}&toPercent={target_percent}&preconditionLengthMins={pre_condition_length}")
return bool(result)

async def async_change_price_cap(self, enabled=None, cap=None):
Expand Down Expand Up @@ -275,7 +272,7 @@ async def async_update_schedule(self, target_percent=None, target_time=None, pre

async def async_set_configuration_value(self, values):
"""Set a configuration value or values."""
result = await self._put_request(f"/v1/chargeDevices/{self._serial}/appSettings", data=values)
result = await self._put_request(f"/v1/chargeDevices/{self.serial}/appSettings", data=values)
return bool(result)

# Pull methods
Expand Down Expand Up @@ -305,16 +302,16 @@ async def async_update_device_info(self, is_retry=False):

self._capabilities = device['modelCapabilities']
self._user_id = resp['user']['id']
self._serial = device['id']
self.serial = device['id']
self._provision_date = device['provisioningTs']

self._device_info = DeviceInfo(
identifiers={(DOMAIN, f"ohme_charger_{self._serial}")},
identifiers={(DOMAIN, f"ohme_charger_{self.serial}")},
name=device['modelTypeDisplayName'],
manufacturer="Ohme",
model=device['modelTypeDisplayName'].replace("Ohme ", ""),
sw_version=device['firmwareVersionLabel'],
serial_number=self._serial
serial_number=self.serial
)


Expand All @@ -329,7 +326,7 @@ async def async_update_device_info(self, is_retry=False):

async def async_get_advanced_settings(self):
"""Get advanced settings (mainly for CT clamp reading)"""
resp = await self._get_request(f"/v1/chargeDevices/{self._serial}/advancedSettings")
resp = await self._get_request(f"/v1/chargeDevices/{self.serial}/advancedSettings")

# If we ever get a reading above 0, assume CT connected
if resp['clampAmps'] and resp['clampAmps'] > 0:
Expand Down
37 changes: 37 additions & 0 deletions custom_components/ohme/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from homeassistant.helpers.entity import Entity
from homeassistant.core import callback

class OhmeEntity(Entity):
"""Base class for all Ohme entities."""

_attr_has_entity_name = True

def __init__(self, cooordinator, hass, client):
"""Initialize the entity."""
self.coordinator = cooordinator
self._hass = hass
self._client = client

self._attributes = {}
self._last_updated = None
self._state = None

self._attr_device_info = client.get_device_info()

async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(
self._handle_coordinator_update, None
)
)

@callback
def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()

@property
def unique_id(self):
"""Return unique ID of the entity."""
return f"{self._client.serial}_{self._attr_translation_key}"
157 changes: 17 additions & 140 deletions custom_components/ohme/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util.dt import (utcnow)
from .const import DOMAIN, DATA_COORDINATORS, DATA_SLOTS, COORDINATOR_CHARGESESSIONS, COORDINATOR_ADVANCED, DATA_CLIENT
from .coordinator import OhmeChargeSessionsCoordinator, OhmeAdvancedSettingsCoordinator
from .utils import in_slot
from .base import OhmeEntity

_LOGGER = logging.getLogger(__name__)

Expand All @@ -36,40 +36,14 @@ async def async_setup_entry(


class ConnectedBinarySensor(
CoordinatorEntity[OhmeChargeSessionsCoordinator],
OhmeEntity,
BinarySensorEntity):
"""Binary sensor for if car is plugged in."""

_attr_name = "Car Connected"
_attr_translation_key = "car_connected"
_attr_icon = "mdi:ev-plug-type2"
_attr_device_class = BinarySensorDeviceClass.PLUG

def __init__(
self,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)

self._attributes = {}
self._last_updated = None
self._state = False
self._client = client

self.entity_id = generate_entity_id(
"binary_sensor.{}", "ohme_car_connected", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:ev-plug-type2"

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._client.get_unique_id("car_connected")

@property
def is_on(self) -> bool:
if self.coordinator.data is None:
Expand All @@ -81,24 +55,20 @@ def is_on(self) -> bool:


class ChargingBinarySensor(
CoordinatorEntity[OhmeChargeSessionsCoordinator],
OhmeEntity,
BinarySensorEntity):
"""Binary sensor for if car is charging."""

_attr_name = "Car Charging"
_attr_translation_key = "car_charging"
_attr_icon = "mdi:battery-charging-100"
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING

def __init__(
self,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)

self._attributes = {}
self._last_updated = None
self._state = False
self._client = client
super().__init__(coordinator, hass, client)

# Cache the last power readings
self._last_reading = None
Expand All @@ -107,21 +77,6 @@ def __init__(
# State variables for charge state detection
self._trigger_count = 0

self.entity_id = generate_entity_id(
"binary_sensor.{}", "ohme_car_charging", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:battery-charging-100"

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._client.get_unique_id("ohme_car_charging")

@property
def is_on(self) -> bool:
return self._state
Expand Down Expand Up @@ -208,38 +163,12 @@ def _handle_coordinator_update(self) -> None:


class PendingApprovalBinarySensor(
CoordinatorEntity[OhmeChargeSessionsCoordinator],
OhmeEntity,
BinarySensorEntity):
"""Binary sensor for if a charge is pending approval."""

_attr_name = "Pending Approval"

def __init__(
self,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)

self._attributes = {}
self._last_updated = None
self._state = False
self._client = client

self.entity_id = generate_entity_id(
"binary_sensor.{}", "ohme_pending_approval", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:alert-decagram"

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._client.get_unique_id("pending_approval")
_attr_translation_key = "pending_approval"
_attr_icon = "mdi:alert-decagram"

@property
def is_on(self) -> bool:
Expand All @@ -253,38 +182,12 @@ def is_on(self) -> bool:


class CurrentSlotBinarySensor(
CoordinatorEntity[OhmeChargeSessionsCoordinator],
OhmeEntity,
BinarySensorEntity):
"""Binary sensor for if we are currently in a smart charge slot."""

_attr_name = "Charge Slot Active"

def __init__(
self,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)

self._last_updated = None
self._state = False
self._client = client
self._hass = hass

self.entity_id = generate_entity_id(
"binary_sensor.{}", "ohme_slot_active", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:calendar-check"

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._client.get_unique_id("ohme_slot_active")
_attr_translation_key = "slot_active"
_attr_icon = "mdi:calendar-check"

@property
def extra_state_attributes(self):
Expand Down Expand Up @@ -316,40 +219,14 @@ def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()

class ChargerOnlineBinarySensor(
CoordinatorEntity[OhmeAdvancedSettingsCoordinator],
OhmeEntity,
BinarySensorEntity):
"""Binary sensor for if charger is online."""

_attr_name = "Charger Online"
_attr_translation_key = "charger_online"
_attr_icon = "mdi:web"
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

def __init__(
self,
coordinator: OhmeAdvancedSettingsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)

self._attributes = {}
self._last_updated = None
self._state = None
self._client = client

self.entity_id = generate_entity_id(
"binary_sensor.{}", "ohme_charger_online", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:web"

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._client.get_unique_id("charger_online")

@property
def is_on(self) -> bool:
if self.coordinator.data and self.coordinator.data["online"]:
Expand Down
Loading

0 comments on commit 0e4494e

Please sign in to comment.