Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5f17a52
introduce the Metric class
Jul 30, 2021
24968d3
fix errors
Jul 30, 2021
09b9765
imake extra attr a metric property
Jul 30, 2021
dae06db
fix unreachable code
Jul 30, 2021
9df2697
Merge pull request #27 from lbbrhzn/main
drc38 Jul 31, 2021
4cf5636
Revert "Merge branch 'main' into main"
drc38 Jul 31, 2021
def6499
Merge pull request #28 from lbbrhzn/metric_class
drc38 Jul 31, 2021
d3e5695
Update tests.yaml
drc38 Aug 13, 2021
988b6d7
Merge remote-tracking branch 'upstream/main' into main
drc38 Aug 23, 2021
b8db4a4
Merge branch 'main' of https://github.com/drc38/ocpp into main
drc38 Aug 23, 2021
3e5832c
use flags for supported features
drc38 Aug 23, 2021
48901e3
fix enum reference
drc38 Aug 23, 2021
b2659be
Merge pull request #29 from drc38/supported-features
drc38 Aug 23, 2021
b1f7d0a
initial addition of slider
drc38 Aug 24, 2021
43aabcd
Merge branch 'main' into slider
drc38 Aug 24, 2021
9c5609c
add slider to platforms
drc38 Aug 24, 2021
26700ed
Merge branch 'slider' of https://github.com/drc38/ocpp into slider
drc38 Aug 24, 2021
443f657
fix enums
drc38 Aug 24, 2021
0dbe450
rename to input_number
drc38 Aug 24, 2021
4408bea
incorporate @lbbrhzn edits
drc38 Aug 25, 2021
d554162
add number slider test
drc38 Aug 25, 2021
1ec975d
update test service call
drc38 Aug 25, 2021
1c1ea1e
fix call
drc38 Aug 25, 2021
0c95b3a
fix entity_id
drc38 Aug 25, 2021
37e2281
fix set_charge_rate call
drc38 Aug 25, 2021
3519d60
add self.name
drc38 Aug 25, 2021
ac9adda
change to self._name
drc38 Aug 25, 2021
88505d7
check SMART profile supported, tidy refs
drc38 Aug 26, 2021
a5c8484
change to bitwise compare
drc38 Aug 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
2 changes: 0 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
- master
- dev
pull_request:
schedule:
- cron: "0 0 * * *"

env:
DEFAULT_PYTHON: 3.9
Expand Down
40 changes: 12 additions & 28 deletions custom_components/ocpp/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,6 @@
_LOGGER: logging.Logger = logging.getLogger(__package__)
logging.getLogger(DOMAIN).setLevel(logging.DEBUG)

SCR_SERVICE_DATA_SCHEMA = vol.Schema(
{
vol.Optional("limit_amps"): int,
vol.Optional("limit_watts"): int,
}
)
UFW_SERVICE_DATA_SCHEMA = vol.Schema(
{
vol.Required("firmware_url"): str,
Expand Down Expand Up @@ -199,6 +193,18 @@ def get_available(self, cp_id: str):
return self.charge_points[cp_id].status == STATE_OK
return False

def get_supported_features(self, cp_id: str):
"""Return what profiles the charger supports."""
if cp_id in self.charge_points:
return self.charge_points[cp_id].supported_features
return None

async def set_max_charge_rate_amps(self, cp_id: str, value: float):
"""Set the maximum charge rate in amps."""
if cp_id in self.charge_points:
return await self.charge_points[cp_id].set_charge_rate(limit_amps=value)
return False

async def set_charger_state(
self, cp_id: str, service_name: str, state: bool = True
):
Expand Down Expand Up @@ -279,22 +285,6 @@ async def handle_clear_profile(call):
return
await self.clear_profile()

async def handle_set_charge_rate(call):
"""Handle the set charge rate service call."""
if self.status == STATE_UNAVAILABLE:
_LOGGER.warning("%s charger is currently unavailable", self.id)
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:
await self.set_charge_rate(lim_A, lim_W)
elif lim_A is not None:
await self.set_charge_rate(limit_amps=lim_A)
elif lim_W is not None:
await self.set_charge_rate(limit_watts=lim_W)
else:
await self.set_charge_rate()

async def handle_update_firmware(call):
"""Handle the firmware update service call."""
if self.status == STATE_UNAVAILABLE:
Expand Down Expand Up @@ -370,12 +360,6 @@ async def handle_get_diagnostics(call):
self.hass.services.async_register(
DOMAIN, csvcs.service_clear_profile.value, handle_clear_profile
)
self.hass.services.async_register(
DOMAIN,
csvcs.service_set_charge_rate.value,
handle_set_charge_rate,
SCR_SERVICE_DATA_SCHEMA,
)
if prof.FW in self._attr_supported_features:
self.hass.services.async_register(
DOMAIN,
Expand Down
43 changes: 33 additions & 10 deletions custom_components/ocpp/const.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
"""Define constants for OCPP integration."""
import homeassistant.components.input_number as input_number
import homeassistant.const as ha

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

from .enums import HAChargerServices, HAChargerStatuses

DOMAIN = "ocpp"
CONF_METER_INTERVAL = "meter_interval"
CONF_USERNAME = ha.CONF_USERNAME
CONF_PASSWORD = ha.CONF_PASSWORD
CONF_CPI = "charge_point_identity"
CONF_CPID = "cpid"
CONF_CSID = "csid"
CONF_HOST = ha.CONF_HOST
CONF_ICON = ha.CONF_ICON
CONF_INITIAL = input_number.CONF_INITIAL
CONF_MAX = input_number.CONF_MAX
CONF_MIN = input_number.CONF_MIN
CONF_METER_INTERVAL = "meter_interval"
CONF_MODE = ha.CONF_MODE
CONF_MONITORED_VARIABLES = ha.CONF_MONITORED_VARIABLES
CONF_NAME = ha.CONF_NAME
CONF_CPID = "cpid"
CONF_CSID = "csid"
CONF_PASSWORD = ha.CONF_PASSWORD
CONF_PORT = ha.CONF_PORT
CONF_STEP = input_number.CONF_STEP
CONF_SUBPROTOCOL = "subprotocol"
CONF_CPI = "charge_point_identity"
CONF_UNIT_OF_MEASUREMENT = ha.CONF_UNIT_OF_MEASUREMENT
CONF_USERNAME = ha.CONF_USERNAME
DEFAULT_CSID = "central"
DEFAULT_CPID = "charger"
DEFAULT_HOST = "0.0.0.0"
DEFAULT_PORT = 9000
DEFAULT_SUBPROTOCOL = "ocpp1.6"
DEFAULT_METER_INTERVAL = 60

DOMAIN = "ocpp"
ICON = "mdi:ev-station"
MODE_SLIDER = input_number.MODE_SLIDER
MODE_BOX = input_number.MODE_BOX
SLEEP_TIME = 60

# Platforms
BINARY_SENSOR = "binary_sensor"
NUMBER = "number"
SENSOR = "sensor"
SWITCH = "switch"
PLATFORMS = [SENSOR, SWITCH]

PLATFORMS = [SENSOR, SWITCH, NUMBER]

# Ocpp supported measurands
MEASURANDS = [
Expand Down Expand Up @@ -94,3 +104,16 @@
"pulse": True,
}
SWITCHES = [SWITCH_CHARGE, SWITCH_RESET, SWITCH_UNLOCK, SWITCH_AVAILABILITY]

# Input number definitions
NUMBER_MAX_CURRENT = {
CONF_NAME: "Maximum_Current",
CONF_ICON: ICON,
CONF_MIN: 0,
CONF_MAX: 32,
CONF_STEP: 1,
CONF_INITIAL: 32,
CONF_MODE: MODE_SLIDER,
CONF_UNIT_OF_MEASUREMENT: "A",
}
NUMBERS = [NUMBER_MAX_CURRENT]
74 changes: 74 additions & 0 deletions custom_components/ocpp/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Number platform for ocpp."""
from homeassistant.components.input_number import InputNumber
import voluptuous as vol

from .api import CentralSystem
from .const import CONF_CPID, DEFAULT_CPID, DOMAIN, NUMBERS
from .enums import Profiles


async def async_setup_entry(hass, entry, async_add_devices):
"""Configure the number platform."""
central_system = hass.data[DOMAIN][entry.entry_id]
cp_id = entry.data.get(CONF_CPID, DEFAULT_CPID)

entities = []

for cfg in NUMBERS:
entities.append(Number(central_system, cp_id, cfg))

async_add_devices(entities, False)


class Number(InputNumber):
"""Individual slider for setting charge rate."""

def __init__(self, central_system: CentralSystem, cp_id: str, config: dict):
"""Initialize a Number instance."""
super().__init__(config)
self.cp_id = cp_id
self.central_system = central_system
self.id = ".".join(["number", self.cp_id, config["name"]])
self._name = ".".join([self.cp_id, config["name"]])
self.entity_id = "number." + "_".join([self.cp_id, config["name"]])

@property
def unique_id(self):
"""Return the unique id of this entity."""
return self.id

@property
def name(self):
"""Return the name of this entity."""
return self._name

@property
def available(self) -> bool:
"""Return if entity is available."""
if not (
Profiles.SMART & self.central_system.get_supported_features(self.cp_id)
):
return False
return self.central_system.get_available(self.cp_id) # type: ignore [no-any-return]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will disable the slider when the charge point is not available. From usability point of view it would be nice if you could set the slider value when then charge point is not available. They should then be applied on (re-)connect. I think this should be possible by connecting to a Metric.


@property
def device_info(self):
"""Return device information."""
return {
"identifiers": {(DOMAIN, self.cp_id)},
"via_device": (DOMAIN, self.central_system.id),
}

async def async_set_value(self, value):
"""Set new value."""
num_value = float(value)

if num_value < self._minimum or num_value > self._maximum:
raise vol.Invalid(
f"Invalid value for {self.entity_id}: {value} (range {self._minimum} - {self._maximum})"
)

resp = await self.central_system.set_max_charge_rate_amps(self.cp_id, num_value)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to make the service call parameterizable. Maybe hook it up to a Metric, and let the charger subscribe to notifications? I guess the Number entity should also subscribe to the Metric, to make the slider revert to the previous value if the new value cannot be set.

if resp:
self._current_value = num_value
self.async_write_ha_state()
14 changes: 12 additions & 2 deletions tests/test_charge_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import websockets

from custom_components.ocpp import async_setup_entry, async_unload_entry
from custom_components.ocpp.const import DOMAIN, SWITCH, SWITCHES
from custom_components.ocpp.const import DOMAIN, NUMBER, NUMBERS, SWITCH, SWITCHES
from custom_components.ocpp.enums import ConfigurationKey, HAChargerServices as csvcs
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cpclass, call, call_result
Expand Down Expand Up @@ -68,7 +68,6 @@ async def test_services(hass):
csvcs.service_get_configuration,
csvcs.service_get_diagnostics,
csvcs.service_clear_profile,
csvcs.service_set_charge_rate,
]
for service in SERVICES:
data = {}
Expand All @@ -88,6 +87,17 @@ async def test_services(hass):
)
assert result

for number in NUMBERS:
# test setting value of number slider
result = await hass.services.async_call(
NUMBER,
"set_value",
service_data={"value": "10"},
blocking=True,
target={ATTR_ENTITY_ID: f"{NUMBER}.test_cpid_{number['name'].lower()}"},
)
assert result

# Create a mock entry so we don't have to go through config flow
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONFIG_DATA, entry_id="test_cms"
Expand Down