Skip to content

Commit 962be14

Browse files
authored
Improve handling of availability (#106)
1 parent 9c20cba commit 962be14

File tree

6 files changed

+97
-24
lines changed

6 files changed

+97
-24
lines changed

custom_components/ocpp/api.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Dict
88

99
from homeassistant.config_entries import ConfigEntry
10-
from homeassistant.const import TIME_MINUTES
10+
from homeassistant.const import STATE_OK, STATE_UNAVAILABLE, TIME_MINUTES
1111
from homeassistant.core import HomeAssistant
1212
from homeassistant.helpers import device_registry, entity_component, entity_registry
1313
import voluptuous as vol
@@ -164,6 +164,7 @@ async def on_connect(self, websocket, path: str):
164164
except Exception as e:
165165
_LOGGER.info(f"Exception occurred:\n{e}")
166166
finally:
167+
self.charge_points[self.cpid].status = STATE_UNAVAILABLE
167168
_LOGGER.info(f"Charger {cp_id} disconnected from {self.host}:{self.port}.")
168169

169170
def get_metric(self, cp_id: str, measurand: str):
@@ -184,6 +185,12 @@ def get_extra_attr(self, cp_id: str, measurand: str):
184185
return self.charge_points[cp_id].get_extra_attr(measurand)
185186
return None
186187

188+
def get_available(self, cp_id: str):
189+
"""Return whether the charger is available."""
190+
if cp_id in self.charge_points:
191+
return self.charge_points[cp_id].status == STATE_OK
192+
return False
193+
187194
async def set_charger_state(
188195
self, cp_id: str, service_name: str, state: bool = True
189196
):
@@ -261,10 +268,14 @@ async def post_connect(self):
261268
# Define custom service handles for charge point
262269
async def handle_clear_profile(call):
263270
"""Handle the clear profile service call."""
271+
if self.status == STATE_UNAVAILABLE:
272+
return
264273
await self.clear_profile()
265274

266275
async def handle_set_charge_rate(call):
267276
"""Handle the set charge rate service call."""
277+
if self.status == STATE_UNAVAILABLE:
278+
return
268279
lim_A = call.data.get("limit_amps")
269280
lim_W = call.data.get("limit_watts")
270281
if lim_A is not None and lim_W is not None:
@@ -278,24 +289,29 @@ async def handle_set_charge_rate(call):
278289

279290
async def handle_update_firmware(call):
280291
"""Handle the firmware update service call."""
292+
if self.status == STATE_UNAVAILABLE:
293+
return
281294
url = call.data.get("firmware_url")
282295
delay = int(call.data.get("delay_hours", 0))
283296
await self.update_firmware(url, delay)
284297

285298
async def handle_configure(call):
286299
"""Handle the configure service call."""
300+
if self.status == STATE_UNAVAILABLE:
301+
return
287302
key = call.data.get("ocpp_key")
288303
value = call.data.get("value")
289304
await self.configure(key, value)
290-
return
291305

292306
async def handle_get_configuration(call):
293307
"""Handle the get configuration service call."""
308+
if self.status == STATE_UNAVAILABLE:
309+
return
294310
key = call.data.get("ocpp_key")
295311
await self.get_configuration(key)
296-
return
297312

298313
try:
314+
self.status = STATE_OK
299315
await self.get_supported_features()
300316
if om.feature_profile_remote.value in self._features_supported:
301317
await self.trigger_boot_notification()
@@ -650,7 +666,11 @@ async def start(self):
650666
async def reconnect(self, connection):
651667
"""Reconnect charge point."""
652668
self._connection = connection
653-
await self.start()
669+
try:
670+
self.status = STATE_OK
671+
await super().start()
672+
except websockets.exceptions.ConnectionClosed as e:
673+
_LOGGER.debug(e)
654674

655675
async def async_update_device_info(self, boot_info: dict):
656676
"""Update device info asynchronuously."""

custom_components/ocpp/enums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
class HAChargerServices(str, Enum):
66
"""Charger status conditions to report in home assistant."""
77

8-
"""For HA service reference use .name for function to call use .value"""
8+
"""For HA service reference and for function to call use .value"""
99

1010
service_charge_start = "start_transaction"
1111
service_charge_stop = "stop_transaction"

custom_components/ocpp/sensor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def state(self):
6767
"""Return the state of the sensor."""
6868
return self.central_system.get_metric(self.cp_id, self.metric)
6969

70+
@property
71+
def available(self) -> bool:
72+
"""Return if switch is available."""
73+
return self.central_system.get_available(self.cp_id)
74+
7075
@property
7176
def unit_of_measurement(self):
7277
"""Return the unit the value is expressed in."""

custom_components/ocpp/switch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def unique_id(self):
4545
@property
4646
def available(self) -> bool:
4747
"""Return if switch is available."""
48-
return True # type: ignore [no-any-return]
48+
return self.central_system.get_available(self.cp_id) # type: ignore [no-any-return]
4949

5050
@property
5151
def is_on(self) -> bool:

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@ branch = False
4141

4242
[coverage:report]
4343
show_missing = true
44-
fail_under = 85
44+
fail_under = 90
45+
4546

tests/test_charge_point.py

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from custom_components.ocpp import async_setup_entry, async_unload_entry
1111
from custom_components.ocpp.const import DOMAIN, SWITCH, SWITCHES
12-
from custom_components.ocpp.enums import ConfigurationKey
12+
from custom_components.ocpp.enums import ConfigurationKey, HAChargerServices as csvcs
1313
from ocpp.routing import on
1414
from ocpp.v16 import ChargePoint as cpclass, call, call_result
1515
from ocpp.v16.enums import (
@@ -38,7 +38,6 @@ async def test_cms_responses(hass):
3838

3939
async def test_switches(hass):
4040
"""Test switch operations."""
41-
4241
for switch in SWITCHES:
4342
result = await hass.services.async_call(
4443
SWITCH,
@@ -60,6 +59,31 @@ async def test_switches(hass):
6059
)
6160
assert result
6261

62+
async def test_services(hass):
63+
"""Test service operations."""
64+
SERVICES = [
65+
csvcs.service_update_firmware,
66+
csvcs.service_configure,
67+
csvcs.service_get_configuration,
68+
csvcs.service_clear_profile,
69+
csvcs.service_set_charge_rate,
70+
]
71+
for service in SERVICES:
72+
data = {}
73+
if service == csvcs.service_update_firmware:
74+
data = {"firmware_url": "http://www.charger.com/firmware.bin"}
75+
if service == csvcs.service_configure:
76+
data = {"ocpp_key": "WebSocketPingInterval", "value": "60"}
77+
if service == csvcs.service_get_configuration:
78+
data = {"ocpp_key": "WebSocketPingInterval"}
79+
result = await hass.services.async_call(
80+
DOMAIN,
81+
service.value,
82+
service_data=data,
83+
blocking=True,
84+
)
85+
assert result
86+
6387
# Create a mock entry so we don't have to go through config flow
6488
config_entry = MockConfigEntry(
6589
domain=DOMAIN, data=MOCK_CONFIG_DATA, entry_id="test_cms"
@@ -69,6 +93,7 @@ async def test_switches(hass):
6993

7094
cs = hass.data[DOMAIN][config_entry.entry_id]
7195

96+
# test ocpp messages sent from charger to cms
7297
async with websockets.connect(
7398
"ws://localhost:9000/CP_1",
7499
subprotocols=["ocpp1.6"],
@@ -85,27 +110,36 @@ async def test_switches(hass):
85110
cp.send_status_notification(),
86111
cp.send_firmware_status(),
87112
cp.send_data_transfer(),
88-
cp.send_start_transaction(),
89113
cp.send_meter_data(),
114+
cp.send_start_transaction(),
90115
cp.send_stop_transaction(),
91-
cs.charge_points["test_cpid"].start_transaction(),
92-
cs.charge_points["test_cpid"].reset(),
93-
cs.charge_points["test_cpid"].set_charge_rate(),
94-
cs.charge_points["test_cpid"].clear_profile(),
95-
cs.charge_points["test_cpid"].update_firmware(
96-
"http://www.charger.com/file.bin"
97-
),
98-
cs.charge_points["test_cpid"].unlock(),
116+
),
117+
timeout=4,
118+
)
119+
except asyncio.TimeoutError:
120+
pass
121+
assert int(cs.get_metric("test_cpid", "Energy.Active.Import.Register")) == int(
122+
1305570 / 1000
123+
)
124+
assert cs.get_unit("test_cpid", "Energy.Active.Import.Register") == "kWh"
125+
126+
# test ocpp messages sent from cms to charger, through HA switches/services
127+
async with websockets.connect(
128+
"ws://localhost:9000/CP_1",
129+
subprotocols=["ocpp1.6"],
130+
) as ws:
131+
cp = ChargePoint("CP_1_test", ws)
132+
try:
133+
await asyncio.wait_for(
134+
asyncio.gather(
135+
cp.start(),
99136
test_switches(hass),
137+
test_services(hass),
100138
),
101-
timeout=5,
139+
timeout=4,
102140
)
103141
except asyncio.TimeoutError:
104142
pass
105-
assert int(cs.get_metric("test_cpid", "Energy.Active.Import.Register")) == int(
106-
1305570 / 1000
107-
)
108-
assert cs.get_unit("test_cpid", "Energy.Active.Import.Register") == "kWh"
109143
await async_unload_entry(hass, config_entry)
110144
await hass.async_block_till_done()
111145

@@ -282,6 +316,16 @@ async def send_start_transaction(self):
282316

283317
async def send_status_notification(self):
284318
"""Send a status notification."""
319+
request = call.StatusNotificationPayload(
320+
connector_id=1,
321+
error_code=ChargePointErrorCode.no_error,
322+
status=ChargePointStatus.suspended_ev,
323+
timestamp=datetime.now(tz=timezone.utc).isoformat(),
324+
info="Test info",
325+
vendor_id="The Mobility House",
326+
vendor_error_code="Test error",
327+
)
328+
resp = await self.call(request)
285329
request = call.StatusNotificationPayload(
286330
connector_id=1,
287331
error_code=ChargePointErrorCode.no_error,
@@ -352,7 +396,7 @@ async def send_meter_data(self):
352396
"context": "Sample.Periodic",
353397
"measurand": "Power.Active.Import",
354398
"location": "Outlet",
355-
"unit": "W",
399+
"unit": "kW",
356400
},
357401
{
358402
"value": "0.000",
@@ -450,6 +494,9 @@ async def send_meter_data(self):
450494
"context": "Transaction.Begin",
451495
"unit": "kWh",
452496
},
497+
{
498+
"value": "1305570.000",
499+
},
453500
],
454501
}
455502
],

0 commit comments

Comments
 (0)