Skip to content

Commit

Permalink
Added approve charge functionality (#7)
Browse files Browse the repository at this point in the history
* Add settings switches

* Add capability check

* Doc updates

* Doc updates

* Added pending approval sensor and approve button

* Doc changes
  • Loading branch information
dan-r committed Dec 28, 2023
1 parent 44d796d commit a1301d5
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 12 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This integration exposes the following entities:
* Binary Sensors
* Car Connected - On when a car is plugged in
* Car Charging - On when a car is connected and drawing power
* Pending Approval - On when a car is connected and waiting for approval
* Sensors
* Power Draw (Watts) - Power draw of connected car
* Accumulative Energy Usage (kWh) - Total energy used by the charger
Expand All @@ -23,6 +24,8 @@ This integration exposes the following entities:
* Switches (Charge state) - These are only functional when a car is connected
* Max Charge - Forces the connected car to charge regardless of set schedule
* Pause Charge - Pauses an ongoing charge
* Buttons
* Approve Charge - Approves a charge when 'Pending Approval' is on

## Installation

Expand Down
6 changes: 5 additions & 1 deletion custom_components/ohme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ async def async_setup_entry(hass, entry):

await async_setup_dependencies(hass, config)

hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR] = OhmeChargeSessionsCoordinator(hass=hass)
hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR] = OhmeChargeSessionsCoordinator(
hass=hass)
await hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR].async_config_entry_first_refresh()

hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR] = OhmeStatisticsCoordinator(
Expand All @@ -52,6 +53,9 @@ async def async_setup_entry(hass, entry):
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch")
)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "button")
)

return True

Expand Down
11 changes: 8 additions & 3 deletions custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async def _put_request(self, url, data=None, is_retry=False):
headers={
"Authorization": "Firebase %s" % self._token,
"Content-Type": "application/json"
}
}
) as resp:
if resp.status != 200 and not is_retry:
await self.async_refresh_session()
Expand Down Expand Up @@ -104,6 +104,11 @@ async def async_resume_charge(self):
result = await self._post_request(f"https://api.ohme.io/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"https://api.ohme.io/v1/chargeSessions/{self._serial}/approve?approve=true")
return bool(result)

async def async_max_charge(self):
"""Enable max charge"""
result = await self._put_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/rule?maxCharge=true")
Expand Down Expand Up @@ -135,7 +140,7 @@ async def async_get_account_info(self):

if not resp:
return False

return resp

async def async_update_device_info(self, is_retry=False):
Expand Down Expand Up @@ -166,7 +171,7 @@ async def async_update_device_info(self, is_retry=False):
def is_capable(self, capability):
"""Return whether or not this model has a given capability."""
return bool(self._capabilities[capability])

def _last_second_of_month_timestamp(self):
"""Get the last second of this month."""
dt = datetime.today()
Expand Down
49 changes: 48 additions & 1 deletion custom_components/ohme/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ async def async_setup_entry(
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]

sensors = [ConnectedSensor(coordinator, hass, client),
ChargingSensor(coordinator, hass, client)]
ChargingSensor(coordinator, hass, client),
PendingApprovalSensor(coordinator, hass, client)]

async_add_entities(sensors, update_before_add=True)

Expand Down Expand Up @@ -118,3 +119,49 @@ def is_on(self) -> bool:
self._state = False

return self._state


class PendingApprovalSensor(
CoordinatorEntity[OhmeChargeSessionsCoordinator],
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 = hass.data[DOMAIN][DATA_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")

@property
def is_on(self) -> bool:
if self.coordinator.data is None:
self._state = False
else:
self._state = bool(
self.coordinator.data["mode"] == "PENDING_APPROVAL")

return self._state
66 changes: 66 additions & 0 deletions custom_components/ohme/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from __future__ import annotations
import logging
import asyncio

from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.components.button import ButtonEntity

from .const import DOMAIN, DATA_CLIENT, DATA_CHARGESESSIONS_COORDINATOR
from .coordinator import OhmeChargeSessionsCoordinator

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
async_add_entities
):
"""Setup switches."""
client = hass.data[DOMAIN][DATA_CLIENT]
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]

buttons = []

if client.is_capable("pluginsRequireApprovalMode"):
buttons.append(
OhmeApproveChargeButton(coordinator, hass, client)
)

async_add_entities(buttons, update_before_add=True)


class OhmeApproveChargeButton(ButtonEntity):
"""Button for approving a charge."""
_attr_name = "Approve Charge"

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

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

self.entity_id = generate_entity_id(
"switch.{}", "ohme_approve_charge", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def unique_id(self):
"""The unique ID of the switch."""
return self._client.get_unique_id("approve_charge")

@property
def icon(self):
"""Icon of the switch."""
return "mdi:check-decagram-outline"

async def async_press(self):
"""Approve the charge."""
await self._client.async_approve_charge()

await asyncio.sleep(1)
await self._coordinator.async_refresh()
1 change: 1 addition & 0 deletions custom_components/ohme/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async def _async_update_data(self):
except BaseException:
raise UpdateFailed("Error communicating with API")


class OhmeAccountInfoCoordinator(DataUpdateCoordinator):
"""Coordinator to pull from API periodically."""

Expand Down
17 changes: 10 additions & 7 deletions custom_components/ohme/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,22 @@ async def async_setup_entry(
client = hass.data[DOMAIN][DATA_CLIENT]

switches = [OhmePauseChargeSwitch(coordinator, hass, client),
OhmeMaxChargeSwitch(coordinator, hass, client)]
OhmeMaxChargeSwitch(coordinator, hass, client)]

if client.is_capable("buttonsLockable"):
switches.append(
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Lock Buttons", "lock", "buttonsLocked")
OhmeConfigurationSwitch(
accountinfo_coordinator, hass, client, "Lock Buttons", "lock", "buttonsLocked")
)
if client.is_capable("pluginsRequireApprovalMode"):
switches.append(
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Require Approval", "check-decagram", "pluginsRequireApproval")
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client,
"Require Approval", "check-decagram", "pluginsRequireApproval")
)
if client.is_capable("stealth"):
switches.append(
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Sleep When Inactive", "power-sleep", "stealthEnabled")
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client,
"Sleep When Inactive", "power-sleep", "stealthEnabled")
)

async_add_entities(switches, update_before_add=True)
Expand Down Expand Up @@ -206,14 +209,14 @@ def _handle_coordinator_update(self) -> None:

async def async_turn_on(self):
"""Turn on the switch."""
await self._client.async_set_configuration_value({ self._config_key: True })
await self._client.async_set_configuration_value({self._config_key: True})

await asyncio.sleep(1)
await self.coordinator.async_refresh()

async def async_turn_off(self):
"""Turn off the switch."""
await self._client.async_set_configuration_value({ self._config_key: False})
await self._client.async_set_configuration_value({self._config_key: False})

await asyncio.sleep(1)
await self.coordinator.async_refresh()

0 comments on commit a1301d5

Please sign in to comment.