Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
7e9d3db
ocpp: update sensors when data received
drc38 Jul 18, 2021
fffcec1
ocpp: remove await
drc38 Jul 18, 2021
310b346
ocpp: fix entries for device
drc38 Jul 18, 2021
c399ab7
Merge pull request #10 from drc38/enums
drc38 Jul 18, 2021
67fc04c
occp: push metric updates
drc38 Jul 18, 2021
8812834
Merge branch 'main' into push-updates
drc38 Jul 18, 2021
6def802
ocpp: create task to update entity
drc38 Jul 18, 2021
a3dcd14
Merge branch 'push-updates' of https://github.com/drc38/ocpp into pus…
drc38 Jul 18, 2021
1c65e14
ocpp: change to async update
drc38 Jul 18, 2021
eac2ffc
Merge pull request #11 from drc38/push-updates
drc38 Jul 18, 2021
c46321e
ocpp: add async update to switch
drc38 Jul 18, 2021
8ad3e9b
Merge pull request #12 from drc38/push-updates
drc38 Jul 18, 2021
9a93461
ocpp: draft support for 3-phase
drc38 Jul 19, 2021
c92740c
ocpp: remove enumerate
drc38 Jul 19, 2021
761584b
ocpp: remove async from process phases
drc38 Jul 19, 2021
e456714
ocpp: update voltage current average
drc38 Jul 19, 2021
9f22e59
ocpp: update voltage current average
drc38 Jul 19, 2021
1c1bfc5
Merge branch '3-phase' of https://github.com/drc38/ocpp into 3-phase
drc38 Jul 19, 2021
ea709d7
ocpp: add debug log
drc38 Jul 19, 2021
d19d57f
ocpp: modify process phases
drc38 Jul 19, 2021
2f928f2
ocpp: modify process phases
drc38 Jul 19, 2021
3c6fb67
Merge branch '3-phase' of https://github.com/drc38/ocpp into 3-phase
drc38 Jul 19, 2021
42dcd22
ocpp: extra debug
drc38 Jul 19, 2021
334f71b
ocpp: try again
drc38 Jul 19, 2021
5728ccc
ocpp: try again
drc38 Jul 19, 2021
62c1d85
ocpp: tidy logic
drc38 Jul 19, 2021
17b4eaf
ocpp: correct dict entry
drc38 Jul 19, 2021
5e76428
ocpp: correct unprocessed logic
drc38 Jul 19, 2021
14fe182
ocpp: pop by reverse sort
drc38 Jul 19, 2021
2259577
try again
drc38 Jul 19, 2021
bf77b49
and again
drc38 Jul 19, 2021
eb21d80
and ...
drc38 Jul 19, 2021
698c2ef
take2
drc38 Jul 19, 2021
2d5cdf7
final tidy
drc38 Jul 19, 2021
dfdf127
Merge pull request #13 from drc38/3-phase
drc38 Jul 19, 2021
7ce4726
tests: improve switch testing to 100%
drc38 Jul 24, 2021
cf7baaf
tests: fail under increased to 85%
drc38 Jul 24, 2021
db49144
tests: correct SWITCHES import
drc38 Jul 24, 2021
aa3972f
ocpp: set unavailable state on charger disconnect
drc38 Jul 25, 2021
e3e465e
ocpp: fix error if location is not specified
drc38 Jul 25, 2021
edd4271
tests: add test for no location
drc38 Jul 25, 2021
c3315f2
ocpp: fix if no measurand present in sv
drc38 Jul 25, 2021
3ea8c1e
Merge branch 'main' into bug-fix-location
drc38 Jul 25, 2021
037da54
Merge pull request #15 from drc38/bug-fix-location
drc38 Jul 25, 2021
8c6a8bb
Merge pull request #16 from drc38/tests
drc38 Jul 25, 2021
93ffe57
Merge pull request #17 from drc38/available
drc38 Jul 25, 2021
fcb01a9
ocpp: fix cp_id reference
drc38 Jul 25, 2021
7d4aa01
ocpp: fix cp_id reference
drc38 Jul 25, 2021
3b3bf4a
Merge branch 'available' of https://github.com/drc38/ocpp into available
drc38 Jul 25, 2021
f9d6fbc
tests: start charger first
drc38 Jul 25, 2021
19850fe
tests: handle websocket close exception
drc38 Jul 25, 2021
e547e5f
tests: update assert and exc
drc38 Jul 25, 2021
5b1dff4
tests: try exc handle again
drc38 Jul 25, 2021
e4b7a97
tests: use gather to return exc
drc38 Jul 25, 2021
ee1eadb
tests: revert to 1 gather
drc38 Jul 25, 2021
ca946d8
tests: change status notify to increase code coverage
drc38 Jul 25, 2021
9b63c4b
tests: extra status notification
drc38 Jul 25, 2021
5234f8d
tests: try reconnecting
drc38 Jul 25, 2021
dcf399a
and again
drc38 Jul 25, 2021
646609e
ocpp: improve reconnection
drc38 Jul 25, 2021
617e90d
tests: add remote stop transaction
drc38 Jul 25, 2021
3fddd9c
ocpp: set status on reconnect
drc38 Jul 25, 2021
6f37ab8
tests: increase fail under to 90%
drc38 Jul 25, 2021
956ebaf
test: add extra sampled values
drc38 Jul 25, 2021
be0f6e7
Merge branch 'main' into available
drc38 Jul 25, 2021
ca79ed1
tests: fix assert error
drc38 Jul 25, 2021
bca2e8d
Merge branch 'available' of https://github.com/drc38/ocpp into available
drc38 Jul 25, 2021
775a1fe
tests: test service calls
drc38 Jul 26, 2021
faef9f7
tests: correct switch domain
drc38 Jul 26, 2021
c01f8cb
tests: fix enum refs
drc38 Jul 26, 2021
b44d103
tests: fix enums refs
drc38 Jul 26, 2021
17b7e6a
tests: limit services
drc38 Jul 26, 2021
78b2afa
tests: change dict value to str
drc38 Jul 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions custom_components/ocpp/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Dict

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TIME_MINUTES
from homeassistant.const import STATE_OK, STATE_UNAVAILABLE, TIME_MINUTES
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry, entity_component, entity_registry
import voluptuous as vol
Expand Down Expand Up @@ -163,6 +163,7 @@ async def on_connect(self, websocket, path: str):
except Exception as e:
_LOGGER.info(f"Exception occurred:\n{e}")
finally:
self.charge_points[self.cpid].status = STATE_UNAVAILABLE
_LOGGER.info(f"Charger {cp_id} disconnected from {self.host}:{self.port}.")

def get_metric(self, cp_id: str, measurand: str):
Expand All @@ -183,6 +184,12 @@ def get_extra_attr(self, cp_id: str, measurand: str):
return self.charge_points[cp_id].get_extra_attr(measurand)
return None

def get_available(self, cp_id: str):
"""Return whether the charger is available."""
if cp_id in self.charge_points:
return self.charge_points[cp_id].status == STATE_OK
return False

async def set_charger_state(
self, cp_id: str, service_name: str, state: bool = True
):
Expand Down Expand Up @@ -260,10 +267,14 @@ async def post_connect(self):
# Define custom service handles for charge point
async def handle_clear_profile(call):
"""Handle the clear profile service call."""
if self.status == STATE_UNAVAILABLE:
return
await self.clear_profile()

async def handle_set_charge_rate(call):
"""Handle the set charge rate service call."""
if self.status == STATE_UNAVAILABLE:
return
lim_A = call.data.get("limit_amps")
lim_W = call.data.get("limit_watts")
if lim_A is not None and lim_W is not None:
Expand All @@ -277,24 +288,29 @@ async def handle_set_charge_rate(call):

async def handle_update_firmware(call):
"""Handle the firmware update service call."""
if self.status == STATE_UNAVAILABLE:
return
url = call.data.get("firmware_url")
delay = int(call.data.get("delay_hours", 0))
await self.update_firmware(url, delay)

async def handle_configure(call):
"""Handle the configure service call."""
if self.status == STATE_UNAVAILABLE:
return
key = call.data.get("ocpp_key")
value = call.data.get("value")
await self.configure(key, value)
return

async def handle_get_configuration(call):
"""Handle the get configuration service call."""
if self.status == STATE_UNAVAILABLE:
return
key = call.data.get("ocpp_key")
await self.get_configuration(key)
return

try:
self.status = STATE_OK
await self.get_supported_features()
if om.feature_profile_remote.value in self._features_supported:
await self.trigger_boot_notification()
Expand Down Expand Up @@ -649,7 +665,11 @@ async def start(self):
async def reconnect(self, connection):
"""Reconnect charge point."""
self._connection = connection
await self.start()
try:
self.status = STATE_OK
await super().start()
except websockets.exceptions.ConnectionClosed as e:
_LOGGER.debug(e)

async def async_update_device_info(self, boot_info: dict):
"""Update device info asynchronuously."""
Expand Down Expand Up @@ -719,31 +739,31 @@ def process_phases(self, data):
@on(Action.MeterValues)
def on_meter_values(self, connector_id: int, meter_value: Dict, **kwargs):
"""Request handler for MeterValues Calls."""
m = om.measurand.value
for bucket in meter_value:
unprocessed = bucket[om.sampled_value.name]
processed_keys = []
for idx, sv in enumerate(bucket[om.sampled_value.name]):
if om.measurand.value in sv and om.phase.value not in sv:
self._metrics[sv[om.measurand.value]] = round(
float(sv[om.value.value]), 1
)
if m in sv and om.phase.value not in sv:
self._metrics[sv[m]] = round(float(sv[om.value.value]), 1)
if om.unit.value in sv:
if sv[om.unit.value] == DEFAULT_POWER_UNIT:
self._metrics[sv[om.measurand.value]] = (
float(sv[om.value.value]) / 1000
)
self._units[sv[om.measurand.value]] = HA_POWER_UNIT
self._metrics[sv[m]] = float(sv[om.value.value]) / 1000
self._units[sv[m]] = HA_POWER_UNIT
if sv[om.unit.value] == DEFAULT_ENERGY_UNIT:
self._metrics[sv[om.measurand.value]] = (
float(sv[om.value.value]) / 1000
)
self._units[sv[om.measurand.value]] = HA_ENERGY_UNIT
self._metrics[sv[m]] = float(sv[om.value.value]) / 1000
self._units[sv[m]] = HA_ENERGY_UNIT
processed_keys.append(idx)
if len(sv.keys()) == 1: # for backwards compatibility
self._metrics[DEFAULT_MEASURAND] = float(sv[om.value.value]) / 1000
self._units[DEFAULT_MEASURAND] = HA_ENERGY_UNIT
processed_keys.append(idx)
self._extra_attr[sv[om.location.value]] = sv.get(om.location.value)
if m in sv and om.location.value in sv:
if self._extra_attr.get(sv[m]) is None:
self._extra_attr[sv[m]] = {}
self._extra_attr[sv[m]][om.location.value] = sv.get(
om.location.value
)
for idx in sorted(processed_keys, reverse=True):
unprocessed.pop(idx)
# _LOGGER.debug("Meter data not yet processed: %s", unprocessed)
Expand Down
56 changes: 32 additions & 24 deletions custom_components/ocpp/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Define constants for OCPP integration."""
import homeassistant.const as ha

from ocpp.v16.enums import Measurand, UnitOfMeasure
from ocpp.v16.enums import ChargePointStatus, Measurand, UnitOfMeasure

from .enums import HAChargerServices, HAChargerStatuses

DOMAIN = "ocpp"
CONF_METER_INTERVAL = "meter_interval"
Expand Down Expand Up @@ -63,26 +65,32 @@
HA_ENERGY_UNIT = UnitOfMeasure.kwh.value
HA_POWER_UNIT = UnitOfMeasure.kw.value

# Additional conditions/states to monitor
CONDITIONS = [
"Status",
"Heartbeat",
"Error.Code",
"Stop.Reason",
"FW.Status",
"Session.Time", # in min
"Session.Energy", # in kWh
"Meter.Start", # in kWh
]

# Additional general information to report
GENERAL = [
"ID",
"Model",
"Vendor",
"Serial",
"FW.Version",
"Features",
"Connectors",
"Transaction.Id",
]
# Switch configuration definitions
# At a minimum define switch name and on service call, pulse used to call a service once such as reset
# metric and condition combination can be used to drive switch state, use default to set initial state to True
SWITCH_CHARGE = {
"name": "Charge_Control",
"on": HAChargerServices.service_charge_start.name,
"off": HAChargerServices.service_charge_stop.name,
"metric": HAChargerStatuses.status.value,
"condition": ChargePointStatus.charging.value,
}
SWITCH_AVAILABILITY = {
"name": "Availability",
"on": HAChargerServices.service_availability.name,
"off": HAChargerServices.service_availability.name,
"default": True,
"metric": HAChargerStatuses.status.value,
"condition": ChargePointStatus.available.value,
}
SWITCH_RESET = {
"name": "Reset",
"on": HAChargerServices.service_reset.name,
"pulse": True,
}
SWITCH_UNLOCK = {
"name": "Unlock",
"on": HAChargerServices.service_unlock.name,
"pulse": True,
}
SWITCHES = [SWITCH_CHARGE, SWITCH_RESET, SWITCH_UNLOCK, SWITCH_AVAILABILITY]
2 changes: 1 addition & 1 deletion custom_components/ocpp/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class HAChargerServices(str, Enum):
"""Charger status conditions to report in home assistant."""

"""For HA service reference use .name for function to call use .value"""
"""For HA service reference and for function to call use .value"""

service_charge_start = "start_transaction"
service_charge_stop = "stop_transaction"
Expand Down
5 changes: 5 additions & 0 deletions custom_components/ocpp/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ def state(self):
"""Return the state of the sensor."""
return self.central_system.get_metric(self.cp_id, self.metric)

@property
def available(self) -> bool:
"""Return if switch is available."""
return self.central_system.get_available(self.cp_id)

@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""
Expand Down
37 changes: 3 additions & 34 deletions custom_components/ocpp/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,8 @@

from homeassistant.components.switch import SwitchEntity

from ocpp.v16.enums import ChargePointStatus

from .api import CentralSystem
from .const import CONF_CPID, DOMAIN, ICON
from .enums import HAChargerServices, HAChargerStatuses

# At a minimum define switch name and on service call, pulse used to call a service once such as reset
# metric and condition combination can be used to drive switch state, use default to set initial state to True
SWITCH_CHARGE = {
"name": "Charge_Control",
"on": HAChargerServices.service_charge_start.name,
"off": HAChargerServices.service_charge_stop.name,
"metric": HAChargerStatuses.status.value,
"condition": ChargePointStatus.charging.value,
}
SWITCH_AVAILABILITY = {
"name": "Availability",
"on": HAChargerServices.service_availability.name,
"off": HAChargerServices.service_availability.name,
"default": True,
"metric": HAChargerStatuses.status.value,
"condition": ChargePointStatus.available.value,
}
SWITCH_RESET = {
"name": "Reset",
"on": HAChargerServices.service_reset.name,
"pulse": True,
}
SWITCH_UNLOCK = {
"name": "Unlock",
"on": HAChargerServices.service_unlock.name,
"pulse": True,
}
from .const import CONF_CPID, DOMAIN, ICON, SWITCHES


async def async_setup_entry(hass, entry, async_add_devices):
Expand All @@ -45,7 +14,7 @@ async def async_setup_entry(hass, entry, async_add_devices):

entities = []

for ent in [SWITCH_CHARGE, SWITCH_AVAILABILITY, SWITCH_RESET, SWITCH_UNLOCK]:
for ent in SWITCHES:
entities.append(ChargePointSwitch(central_system, cp_id, ent))

async_add_devices(entities, False)
Expand Down Expand Up @@ -76,7 +45,7 @@ def unique_id(self):
@property
def available(self) -> bool:
"""Return if switch is available."""
return True # type: ignore [no-any-return]
return self.central_system.get_available(self.cp_id) # type: ignore [no-any-return]

@property
def is_on(self) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ branch = False

[coverage:report]
show_missing = true
fail_under = 80
fail_under = 90

Loading