Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit a1301d5

Browse files
authored
Added approve charge functionality (#7)
* Add settings switches * Add capability check * Doc updates * Doc updates * Added pending approval sensor and approve button * Doc changes
1 parent 44d796d commit a1301d5

File tree

7 files changed

+141
-12
lines changed

7 files changed

+141
-12
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This integration exposes the following entities:
1212
* Binary Sensors
1313
* Car Connected - On when a car is plugged in
1414
* Car Charging - On when a car is connected and drawing power
15+
* Pending Approval - On when a car is connected and waiting for approval
1516
* Sensors
1617
* Power Draw (Watts) - Power draw of connected car
1718
* Accumulative Energy Usage (kWh) - Total energy used by the charger
@@ -23,6 +24,8 @@ This integration exposes the following entities:
2324
* Switches (Charge state) - These are only functional when a car is connected
2425
* Max Charge - Forces the connected car to charge regardless of set schedule
2526
* Pause Charge - Pauses an ongoing charge
27+
* Buttons
28+
* Approve Charge - Approves a charge when 'Pending Approval' is on
2629

2730
## Installation
2831

custom_components/ohme/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ async def async_setup_entry(hass, entry):
3131

3232
await async_setup_dependencies(hass, config)
3333

34-
hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR] = OhmeChargeSessionsCoordinator(hass=hass)
34+
hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR] = OhmeChargeSessionsCoordinator(
35+
hass=hass)
3536
await hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR].async_config_entry_first_refresh()
3637

3738
hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR] = OhmeStatisticsCoordinator(
@@ -52,6 +53,9 @@ async def async_setup_entry(hass, entry):
5253
hass.async_create_task(
5354
hass.config_entries.async_forward_entry_setup(entry, "switch")
5455
)
56+
hass.async_create_task(
57+
hass.config_entries.async_forward_entry_setup(entry, "button")
58+
)
5559

5660
return True
5761

custom_components/ohme/api_client.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def _put_request(self, url, data=None, is_retry=False):
6969
headers={
7070
"Authorization": "Firebase %s" % self._token,
7171
"Content-Type": "application/json"
72-
}
72+
}
7373
) as resp:
7474
if resp.status != 200 and not is_retry:
7575
await self.async_refresh_session()
@@ -104,6 +104,11 @@ async def async_resume_charge(self):
104104
result = await self._post_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/resume", skip_json=True)
105105
return bool(result)
106106

107+
async def async_approve_charge(self):
108+
"""Approve a charge"""
109+
result = await self._put_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/approve?approve=true")
110+
return bool(result)
111+
107112
async def async_max_charge(self):
108113
"""Enable max charge"""
109114
result = await self._put_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/rule?maxCharge=true")
@@ -135,7 +140,7 @@ async def async_get_account_info(self):
135140

136141
if not resp:
137142
return False
138-
143+
139144
return resp
140145

141146
async def async_update_device_info(self, is_retry=False):
@@ -166,7 +171,7 @@ async def async_update_device_info(self, is_retry=False):
166171
def is_capable(self, capability):
167172
"""Return whether or not this model has a given capability."""
168173
return bool(self._capabilities[capability])
169-
174+
170175
def _last_second_of_month_timestamp(self):
171176
"""Get the last second of this month."""
172177
dt = datetime.today()

custom_components/ohme/binary_sensor.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ async def async_setup_entry(
2222
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]
2323

2424
sensors = [ConnectedSensor(coordinator, hass, client),
25-
ChargingSensor(coordinator, hass, client)]
25+
ChargingSensor(coordinator, hass, client),
26+
PendingApprovalSensor(coordinator, hass, client)]
2627

2728
async_add_entities(sensors, update_before_add=True)
2829

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

120121
return self._state
122+
123+
124+
class PendingApprovalSensor(
125+
CoordinatorEntity[OhmeChargeSessionsCoordinator],
126+
BinarySensorEntity):
127+
"""Binary sensor for if a charge is pending approval."""
128+
129+
_attr_name = "Pending Approval"
130+
131+
def __init__(
132+
self,
133+
coordinator: OhmeChargeSessionsCoordinator,
134+
hass: HomeAssistant,
135+
client):
136+
super().__init__(coordinator=coordinator)
137+
138+
self._attributes = {}
139+
self._last_updated = None
140+
self._state = False
141+
self._client = client
142+
143+
self.entity_id = generate_entity_id(
144+
"binary_sensor.{}", "ohme_pending_approval", hass=hass)
145+
146+
self._attr_device_info = hass.data[DOMAIN][DATA_CLIENT].get_device_info(
147+
)
148+
149+
@property
150+
def icon(self):
151+
"""Icon of the sensor."""
152+
return "mdi:alert-decagram"
153+
154+
@property
155+
def unique_id(self) -> str:
156+
"""Return the unique ID of the sensor."""
157+
return self._client.get_unique_id("pending_approval")
158+
159+
@property
160+
def is_on(self) -> bool:
161+
if self.coordinator.data is None:
162+
self._state = False
163+
else:
164+
self._state = bool(
165+
self.coordinator.data["mode"] == "PENDING_APPROVAL")
166+
167+
return self._state

custom_components/ohme/button.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from __future__ import annotations
2+
import logging
3+
import asyncio
4+
5+
from homeassistant.core import HomeAssistant
6+
from homeassistant.helpers.entity import generate_entity_id
7+
from homeassistant.components.button import ButtonEntity
8+
9+
from .const import DOMAIN, DATA_CLIENT, DATA_CHARGESESSIONS_COORDINATOR
10+
from .coordinator import OhmeChargeSessionsCoordinator
11+
12+
_LOGGER = logging.getLogger(__name__)
13+
14+
15+
async def async_setup_entry(
16+
hass: HomeAssistant,
17+
config_entry: config_entries.ConfigEntry,
18+
async_add_entities
19+
):
20+
"""Setup switches."""
21+
client = hass.data[DOMAIN][DATA_CLIENT]
22+
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]
23+
24+
buttons = []
25+
26+
if client.is_capable("pluginsRequireApprovalMode"):
27+
buttons.append(
28+
OhmeApproveChargeButton(coordinator, hass, client)
29+
)
30+
31+
async_add_entities(buttons, update_before_add=True)
32+
33+
34+
class OhmeApproveChargeButton(ButtonEntity):
35+
"""Button for approving a charge."""
36+
_attr_name = "Approve Charge"
37+
38+
def __init__(self, coordinator: OhmeChargeSessionsCoordinator, hass: HomeAssistant, client):
39+
self._client = client
40+
self._coordinator = coordinator
41+
42+
self._state = False
43+
self._last_updated = None
44+
self._attributes = {}
45+
46+
self.entity_id = generate_entity_id(
47+
"switch.{}", "ohme_approve_charge", hass=hass)
48+
49+
self._attr_device_info = client.get_device_info()
50+
51+
@property
52+
def unique_id(self):
53+
"""The unique ID of the switch."""
54+
return self._client.get_unique_id("approve_charge")
55+
56+
@property
57+
def icon(self):
58+
"""Icon of the switch."""
59+
return "mdi:check-decagram-outline"
60+
61+
async def async_press(self):
62+
"""Approve the charge."""
63+
await self._client.async_approve_charge()
64+
65+
await asyncio.sleep(1)
66+
await self._coordinator.async_refresh()

custom_components/ohme/coordinator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ async def _async_update_data(self):
3232
except BaseException:
3333
raise UpdateFailed("Error communicating with API")
3434

35+
3536
class OhmeAccountInfoCoordinator(DataUpdateCoordinator):
3637
"""Coordinator to pull from API periodically."""
3738

custom_components/ohme/switch.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,22 @@ async def async_setup_entry(
2828
client = hass.data[DOMAIN][DATA_CLIENT]
2929

3030
switches = [OhmePauseChargeSwitch(coordinator, hass, client),
31-
OhmeMaxChargeSwitch(coordinator, hass, client)]
32-
31+
OhmeMaxChargeSwitch(coordinator, hass, client)]
32+
3333
if client.is_capable("buttonsLockable"):
3434
switches.append(
35-
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Lock Buttons", "lock", "buttonsLocked")
35+
OhmeConfigurationSwitch(
36+
accountinfo_coordinator, hass, client, "Lock Buttons", "lock", "buttonsLocked")
3637
)
3738
if client.is_capable("pluginsRequireApprovalMode"):
3839
switches.append(
39-
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Require Approval", "check-decagram", "pluginsRequireApproval")
40+
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client,
41+
"Require Approval", "check-decagram", "pluginsRequireApproval")
4042
)
4143
if client.is_capable("stealth"):
4244
switches.append(
43-
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client, "Sleep When Inactive", "power-sleep", "stealthEnabled")
45+
OhmeConfigurationSwitch(accountinfo_coordinator, hass, client,
46+
"Sleep When Inactive", "power-sleep", "stealthEnabled")
4447
)
4548

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

207210
async def async_turn_on(self):
208211
"""Turn on the switch."""
209-
await self._client.async_set_configuration_value({ self._config_key: True })
212+
await self._client.async_set_configuration_value({self._config_key: True})
210213

211214
await asyncio.sleep(1)
212215
await self.coordinator.async_refresh()
213216

214217
async def async_turn_off(self):
215218
"""Turn off the switch."""
216-
await self._client.async_set_configuration_value({ self._config_key: False})
219+
await self._client.async_set_configuration_value({self._config_key: False})
217220

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

0 commit comments

Comments
 (0)