Skip to content

Commit

Permalink
feat: add claim support (#308)
Browse files Browse the repository at this point in the history
* feat: add claim support

* update requirements

* formatting

* add list_overrides service, fix tests

* manifest update for HA 2024.3.x

* sort manifest file
  • Loading branch information
firstof9 authored Mar 1, 2024
1 parent e09e815 commit 3323623
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 13 deletions.
12 changes: 12 additions & 0 deletions custom_components/openevse/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
SERVICE_SET_LIMIT = "set_limit"
SERVICE_CLEAR_LIMIT = "clear_limit"
SERVICE_GET_LIMIT = "get_limit"
SERVICE_MAKE_CLAIM = "make_claim"
SERVICE_LIST_CLAIMS = "list_claims"
SERVICE_RELEASE_CLAIM = "release_claim"
SERVICE_LIST_OVERRIDES = "list_overrides"

# attributes
ATTR_DEVICE_ID = "device_id"
Expand Down Expand Up @@ -347,6 +351,14 @@
key="state",
toggle_command="toggle_override",
device_class=SwitchDeviceClass.SWITCH,
entity_registry_enabled_default=False,
),
"sleep_mode_new": OpenEVSESwitchEntityDescription(
name="Sleep Mode (new)",
key="state",
toggle_command="claim",
device_class=SwitchDeviceClass.SWITCH,
entity_registry_enabled_default=False,
),
"manual_override": OpenEVSESwitchEntityDescription(
name="Manual Override",
Expand Down
3 changes: 2 additions & 1 deletion custom_components/openevse/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"codeowners": ["@firstof9"],
"config_flow": true,
"documentation": "https://github.com/firstof9/openevse/",
"import_executor": true,
"iot_class": "local_push",
"issue_tracker": "https://github.com/firstof9/openevse/issues",
"requirements": ["python-openevse-http==0.1.58"],
"requirements": ["python-openevse-http==0.1.59"],
"version": "0.0.0-dev",
"zeroconf": ["_openevse._tcp.local."]
}
169 changes: 169 additions & 0 deletions custom_components/openevse/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
SERVICE_GET_LIMIT,
SERVICE_SET_LIMIT,
SERVICE_SET_OVERRIDE,
SERVICE_LIST_CLAIMS,
SERVICE_LIST_OVERRIDES,
SERVICE_MAKE_CLAIM,
SERVICE_RELEASE_CLAIM,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -123,6 +127,60 @@ def async_register(self) -> None:
supports_response=SupportsResponse.ONLY,
)

self.hass.services.async_register(
DOMAIN,
SERVICE_MAKE_CLAIM,
self._make_claim,
schema=vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): vol.Coerce(list),
vol.Optional(ATTR_STATE): vol.Coerce(str),
vol.Optional(ATTR_CHARGE_CURRENT): vol.All(
vol.Coerce(int), vol.Range(min=1, max=48)
),
vol.Optional(ATTR_MAX_CURRENT): vol.All(
vol.Coerce(int), vol.Range(min=1, max=48)
),
vol.Optional(ATTR_AUTO_RELEASE): vol.Coerce(bool),
}
),
)

self.hass.services.async_register(
DOMAIN,
SERVICE_LIST_CLAIMS,
self._list_claims,
schema=vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): vol.Coerce(list),
}
),
supports_response=SupportsResponse.ONLY,
)

self.hass.services.async_register(
DOMAIN,
SERVICE_RELEASE_CLAIM,
self._release_claim,
schema=vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): vol.Coerce(list),
}
),
)

self.hass.services.async_register(
DOMAIN,
SERVICE_LIST_OVERRIDES,
self._list_overrides,
schema=vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): vol.Coerce(list),
}
),
supports_response=SupportsResponse.ONLY,
)

# Setup services
async def _set_override(self, service: ServiceCall) -> None:
"""Set the override."""
Expand Down Expand Up @@ -276,3 +334,114 @@ async def _get_limit(self, service: ServiceCall) -> ServiceResponse:
response = await manager.get_limit()
_LOGGER.debug("Get limit response %s.", response)
return response

async def _make_claim(self, service: ServiceCall) -> None:
"""Make a claim."""
data = service.data
for device in data[ATTR_DEVICE_ID]:
device_id = device
_LOGGER.debug("Device ID: %s", device_id)

dev_reg = dr.async_get(self.hass)
device_entry = dev_reg.async_get(device_id)
_LOGGER.debug("Device_entry: %s", device_entry)

if not device_entry:
raise ValueError(f"Device ID {device_id} is not valid")

config_id = list(device_entry.config_entries)[0]
_LOGGER.debug("Config ID: %s", config_id)
manager = self.hass.data[DOMAIN][config_id][MANAGER]

if ATTR_STATE in data:
state = data[ATTR_STATE]
else:
state = None
if ATTR_CHARGE_CURRENT in data:
charge_current = data[ATTR_CHARGE_CURRENT]
else:
charge_current = None
if ATTR_MAX_CURRENT in data:
max_current = data[ATTR_MAX_CURRENT]
else:
max_current = None
if ATTR_AUTO_RELEASE in data:
auto_release = data[ATTR_AUTO_RELEASE]
else:
auto_release = None

response = await manager.make_claim(
state=state,
charge_current=charge_current,
max_current=max_current,
auto_release=auto_release,
)
_LOGGER.debug("Make claim response: %s", response)

async def _release_claim(self, service: ServiceCall) -> None:
"""Release a claim."""
data = service.data
_LOGGER.debug("Data: %s", data)
for device in data[ATTR_DEVICE_ID]:
device_id = device
_LOGGER.debug("Device ID: %s", device_id)

dev_reg = dr.async_get(self.hass)
device_entry = dev_reg.async_get(device_id)
_LOGGER.debug("Device_entry: %s", device_entry)

if not device_entry:
raise ValueError(f"Device ID {device_id} is not valid")

config_id = list(device_entry.config_entries)[0]
_LOGGER.debug("Config ID: %s Type: %s", config_id, type(config_id))
manager = self.hass.data[DOMAIN][config_id][MANAGER]

await manager.release_claim()
_LOGGER.debug("Release claim command sent.")

async def _list_claims(self, service: ServiceCall) -> ServiceResponse:
"""Get the claims."""
data = service.data
_LOGGER.debug("Data: %s", data)
for device in data[ATTR_DEVICE_ID]:
device_id = device
_LOGGER.debug("Device ID: %s", device_id)

dev_reg = dr.async_get(self.hass)
device_entry = dev_reg.async_get(device_id)
_LOGGER.debug("Device_entry: %s", device_entry)

if not device_entry:
raise ValueError(f"Device ID {device_id} is not valid")

config_id = list(device_entry.config_entries)[0]
_LOGGER.debug("Config ID: %s Type: %s", config_id, type(config_id))
manager = self.hass.data[DOMAIN][config_id][MANAGER]

response = await manager.list_claims()
_LOGGER.debug("List claims response %s.", response)
return response

async def _list_overrides(self, service: ServiceCall) -> ServiceResponse:
"""Get the overrides."""
data = service.data
_LOGGER.debug("Data: %s", data)
for device in data[ATTR_DEVICE_ID]:
device_id = device
_LOGGER.debug("Device ID: %s", device_id)

dev_reg = dr.async_get(self.hass)
device_entry = dev_reg.async_get(device_id)
_LOGGER.debug("Device_entry: %s", device_entry)

if not device_entry:
raise ValueError(f"Device ID {device_id} is not valid")

config_id = list(device_entry.config_entries)[0]
_LOGGER.debug("Config ID: %s Type: %s", config_id, type(config_id))
manager = self.hass.data[DOMAIN][config_id][MANAGER]

response = await manager.get_override()
_LOGGER.debug("List overrides response %s.", response)
return response
69 changes: 68 additions & 1 deletion custom_components/openevse/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,71 @@ get_limit:
description: Gets a limit on a charger.
target:
device:
integration: openevse
integration: openevse

make_claim:
name: Make Claim
description: Make/Update a claim.
target:
device:
integration: openevse
fields:
state:
name: State
description: Enable or disable charging.
required: false
default: disabled
example: disabled
selector:
select:
options:
- "active"
- "disabled"
charge_current:
name: Charge current
description: Specify the active charge current in Amps
required: false
example: 30
selector:
number:
min: 1
max: 48
unit_of_measurement: ampers
mode: box
max_current:
name: Max current
description: Dynamically alter the max current while still allowing other claims to very the current via charge_current that can not exceede this value.
required: false
example: 32
selector:
number:
min: 1
max: 48
unit_of_measurement: ampers
mode: box
auto_release:
name: Auto release
description: If the manual override is auto-released when the vehicle is disconnected.
required: false
default: true
example: true
selector:
boolean:
release_claim:
name: Release claim
description: Releases a claim on a charger.
target:
device:
integration: openevse
list_claims:
name: List claims
description: Lists claims on an EVSE.
target:
device:
integration: openevse
list_overrides:
name: List overrides
description: Lists overrides on an EVSE.
target:
device:
integration: openevse
16 changes: 11 additions & 5 deletions custom_components/openevse/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def is_on(self) -> bool:
"""Return True if switch is on."""
data = self.coordinator.data
if self._type not in data.keys():
_LOGGER.info("switch [%s] not supported.", self._type)
_LOGGER.warning("switch [%s] not supported.", self._type)
return None
_LOGGER.debug("switch [%s]: %s", self._attr_name, data[self._type])
if self._type == ATTR_STATE:
Expand All @@ -92,14 +92,20 @@ def is_on(self) -> bool:

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
if self.toggle_command is not None and not self.is_on:
await getattr(self._manager, self.toggle_command)()
if not self.is_on:
if self.toggle_command == "claim":
await self._manager.release_claim()
else:
await getattr(self._manager, self.toggle_command)()
else:
return

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
if self.toggle_command is not None and self.is_on:
await getattr(self._manager, self.toggle_command)()
if self.is_on:
if self.toggle_command == "claim":
await self._manager.make_claim(state="active")
else:
await getattr(self._manager, self.toggle_command)()
else:
return
1 change: 1 addition & 0 deletions tests/fixtures/status.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"ota_update": 0,
"time": "2021-08-10T23:00:11Z",
"offset": "-0700",
"manual_override": 0,
"shaper": 1,
"shaper_live_pwr": 2299,
"shaper_max_pwr": 4000,
Expand Down
6 changes: 3 additions & 3 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def test_setup_entry(hass, test_charger, mock_ws_start):

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 20
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
Expand All @@ -53,7 +53,7 @@ async def test_setup_entry_bad_serial(hass, test_charger_bad_serial, mock_ws_sta

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 20
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
Expand All @@ -73,7 +73,7 @@ async def test_setup_and_unload_entry(hass, test_charger):

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 20
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def test_sensors(hass, test_charger, mock_ws_start):

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 20
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
Expand Down
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[tox]
skipsdist = true
envlist = py310, py311, lint, mypy
envlist = py310, py311, py312, lint, mypy
skip_missing_interpreters = True

[gh-actions]
python =
3.10: py310
3.11: py311, lint, mypy
3.11: py311
3.12: py312, lint, mypy

[testenv]
commands =
Expand Down

0 comments on commit 3323623

Please sign in to comment.