Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to ocpp 2.0.0 to fix blocking #1423

Merged
merged 25 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion custom_components/ocpp/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/lbbrhzn/ocpp/issues",
"requirements": [
"ocpp==1.0.0",
"ocpp>=2.0.0",
"websockets>=14.1"
],
"version": "0.6.3"
Expand Down
151 changes: 79 additions & 72 deletions custom_components/ocpp/ocppv201.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@
from ocpp.v201 import call, call_result
from ocpp.v16.enums import ChargePointStatus as ChargePointStatusv16
from ocpp.v201.enums import (
ConnectorStatusType,
GetVariableStatusType,
IdTokenType,
MeasurandType,
OperationalStatusType,
ResetType,
ResetStatusType,
SetVariableStatusType,
AuthorizationStatusType,
TransactionEventType,
ReadingContextType,
RequestStartStopStatusType,
ChargingStateType,
ChargingProfilePurposeType,
ChargingRateUnitType,
ChargingProfileKindType,
ChargingProfileStatus,
Action,
ConnectorStatusEnumType,
GetVariableStatusEnumType,
IdTokenEnumType,
MeasurandEnumType,
OperationalStatusEnumType,
ResetEnumType,
ResetStatusEnumType,
SetVariableStatusEnumType,
AuthorizationStatusEnumType,
TransactionEventEnumType,
ReadingContextEnumType,
RequestStartStopStatusEnumType,
ChargingStateEnumType,
ChargingProfilePurposeEnumType,
ChargingRateUnitEnumType,
ChargingProfileKindEnumType,
ChargingProfileStatusEnumType,
)

from .chargepoint import (
Expand Down Expand Up @@ -71,15 +72,15 @@ class InventoryReport:
smart_charging_available: bool = False
reservation_available: bool = False
local_auth_available: bool = False
tx_updated_measurands: list[MeasurandType] = []
tx_updated_measurands: list[MeasurandEnumType] = []


class ChargePoint(cp):
"""Server side representation of a charger."""

_inventory: InventoryReport | None = None
_wait_inventory: asyncio.Event | None = None
_connector_status: list[list[ConnectorStatusType | None]] = []
_connector_status: list[list[ConnectorStatusEnumType | None]] = []
_tx_start_time: datetime | None = None

def __init__(
Expand Down Expand Up @@ -254,7 +255,7 @@ async def clear_profile(self):
req: call.ClearChargingProfile = call.ClearChargingProfile(
None,
{
"charging_profile_Purpose": ChargingProfilePurposeType.charging_station_max_profile.value
"charging_profile_Purpose": ChargingProfilePurposeEnumType.charging_station_max_profile.value
},
)
await self.call(req)
Expand All @@ -275,10 +276,10 @@ async def set_charge_rate(
schedule: dict = {"id": 1}
if limit_amps < 32:
period["limit"] = limit_amps
schedule["charging_rate_unit"] = ChargingRateUnitType.amps.value
schedule["charging_rate_unit"] = ChargingRateUnitEnumType.amps.value
elif limit_watts < 22000:
period["limit"] = limit_watts
schedule["charging_rate_unit"] = ChargingRateUnitType.watts.value
schedule["charging_rate_unit"] = ChargingRateUnitEnumType.watts.value
else:
await self.clear_profile()
return
Expand All @@ -289,14 +290,14 @@ async def set_charge_rate(
{
"id": 1,
"stack_level": 0,
"charging_profile_purpose": ChargingProfilePurposeType.charging_station_max_profile,
"charging_profile_kind": ChargingProfileKindType.relative.value,
"charging_profile_purpose": ChargingProfilePurposeEnumType.charging_station_max_profile,
"charging_profile_kind": ChargingProfileKindEnumType.relative.value,
"charging_schedule": [schedule],
},
)

resp: call_result.SetChargingProfile = await self.call(req)
if resp.status != ChargingProfileStatus.accepted:
if resp.status != ChargingProfileStatusEnumType.accepted:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_variables_error",
Expand All @@ -308,9 +309,9 @@ async def set_charge_rate(
async def set_availability(self, state: bool = True):
"""Change availability."""
req: call.ChangeAvailability = call.ChangeAvailability(
OperationalStatusType.operative.value
OperationalStatusEnumType.operative.value
if state
else OperationalStatusType.inoperative.value
else OperationalStatusEnumType.inoperative.value
)
await self.call(req)

Expand All @@ -319,26 +320,26 @@ async def start_transaction(self) -> bool:
req: call.RequestStartTransaction = call.RequestStartTransaction(
id_token={
"id_token": self._remote_id_tag,
"type": IdTokenType.central.value,
"type": IdTokenEnumType.central.value,
},
remote_start_id=1,
)
resp: call_result.RequestStartTransaction = await self.call(req)
return resp.status == RequestStartStopStatusType.accepted.value
return resp.status == RequestStartStopStatusEnumType.accepted.value

async def stop_transaction(self) -> bool:
"""Request remote stop of current transaction."""
req: call.RequestStopTransaction = call.RequestStopTransaction(
transaction_id=self._metrics[csess.transaction_id.value].value
)
resp: call_result.RequestStopTransaction = await self.call(req)
return resp.status == RequestStartStopStatusType.accepted.value
return resp.status == RequestStartStopStatusEnumType.accepted.value

async def reset(self, typ: str = ""):
"""Hard reset charger unless soft reset requested."""
req: call.Reset = call.Reset(ResetType.immediate)
req: call.Reset = call.Reset(ResetEnumType.immediate)
resp = await self.call(req)
if resp.status != ResetStatusType.accepted.value:
if resp.status != ResetStatusEnumType.accepted.value:
status_suffix: str = f": {resp.status_info}" if resp.status_info else ""
raise HomeAssistantError(
translation_domain=DOMAIN,
Expand Down Expand Up @@ -382,7 +383,7 @@ async def get_configuration(self, key: str = "") -> str | None:
translation_placeholders={"message": str(e)},
)
result: dict = resp.get_variable_result[0]
if result["attribute_status"] != GetVariableStatusType.accepted:
if result["attribute_status"] != GetVariableStatusEnumType.accepted:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="get_variables_error",
Expand All @@ -405,9 +406,9 @@ async def configure(self, key: str, value: str) -> SetVariableResult:
translation_placeholders={"message": str(e)},
)
result: dict = resp.set_variable_result[0]
if result["attribute_status"] == SetVariableStatusType.accepted:
if result["attribute_status"] == SetVariableStatusEnumType.accepted:
return SetVariableResult.accepted
elif result["attribute_status"] == SetVariableStatusType.reboot_required:
elif result["attribute_status"] == SetVariableStatusEnumType.reboot_required:
return SetVariableResult.reboot_required
else:
raise HomeAssistantError(
Expand All @@ -416,7 +417,7 @@ async def configure(self, key: str, value: str) -> SetVariableResult:
translation_placeholders={"message": str(result)},
)

@on("BootNotification")
@on(Action.boot_notification)
def on_boot_notification(self, charging_station, reason, **kwargs):
"""Perform OCPP callback."""
resp = call_result.BootNotification(
Expand All @@ -432,7 +433,7 @@ def on_boot_notification(self, charging_station, reason, **kwargs):
self._register_boot_notification()
return resp

@on("Heartbeat")
@on(Action.heartbeat)
def on_heartbeat(self, **kwargs):
"""Perform OCPP callback."""
return call_result.Heartbeat(current_time=datetime.now(tz=UTC).isoformat())
Expand All @@ -448,7 +449,7 @@ def _report_evse_status(self, evse_id: int, evse_status_v16: ChargePointStatusv1
)
self.hass.async_create_task(self.update(self.central.cpid))

@on("StatusNotification")
@on(Action.status_notification)
def on_status_notification(
self, timestamp: str, connector_status: str, evse_id: int, connector_id: int
):
Expand All @@ -460,25 +461,25 @@ def on_status_notification(
connector_id - len(self._connector_status[evse_id - 1])
)

evse: list[ConnectorStatusType] = self._connector_status[evse_id - 1]
evse[connector_id - 1] = ConnectorStatusType(connector_status)
evse_status: ConnectorStatusType | None = None
evse: list[ConnectorStatusEnumType] = self._connector_status[evse_id - 1]
evse[connector_id - 1] = ConnectorStatusEnumType(connector_status)
evse_status: ConnectorStatusEnumType | None = None
for status in evse:
if status is None:
evse_status = status
break
else:
evse_status = status
if status != ConnectorStatusType.available:
if status != ConnectorStatusEnumType.available:
break
evse_status_v16: ChargePointStatusv16 | None
if evse_status is None:
evse_status_v16 = None
elif evse_status == ConnectorStatusType.available:
elif evse_status == ConnectorStatusEnumType.available:
evse_status_v16 = ChargePointStatusv16.available
elif evse_status == ConnectorStatusType.faulted:
elif evse_status == ConnectorStatusEnumType.faulted:
evse_status_v16 = ChargePointStatusv16.faulted
elif evse_status == ConnectorStatusType.unavailable:
elif evse_status == ConnectorStatusEnumType.unavailable:
evse_status_v16 = ChargePointStatusv16.unavailable
else:
evse_status_v16 = ChargePointStatusv16.preparing
Expand All @@ -488,15 +489,15 @@ def on_status_notification(

return call_result.StatusNotification()

@on("FirmwareStatusNotification")
@on("MeterValues")
@on("LogStatusNotification")
@on("NotifyEvent")
@on(Action.firmware_status_notification)
@on(Action.meter_values)
@on(Action.log_status_notification)
@on(Action.notify_event)
def ack(self, **kwargs):
"""Perform OCPP callback."""
return call_result.StatusNotification()

@on("NotifyReport")
@on(Action.notify_report)
def on_report(self, request_id: int, generated_at: str, seq_no: int, **kwargs):
"""Perform OCPP callback."""
if self._wait_inventory is None:
Expand Down Expand Up @@ -559,23 +560,23 @@ def on_report(self, request_id: int, generated_at: str, seq_no: int, **kwargs):
characteristics: dict = report_data["variable_characteristics"]
values: str = characteristics.get("values_list", "")
self._inventory.tx_updated_measurands = [
MeasurandType(s) for s in values.split(",")
MeasurandEnumType(s) for s in values.split(",")
]

if not kwargs.get("tbc", False):
self._wait_inventory.set()
return call_result.NotifyReport()

@on("Authorize")
@on(Action.authorize)
def on_authorize(self, id_token: dict, **kwargs):
"""Perform OCPP callback."""
status: str = AuthorizationStatusType.unknown.value
status: str = AuthorizationStatusEnumType.unknown.value
token_type: str = id_token["type"]
token: str = id_token["id_token"]
if (
(token_type == IdTokenType.iso14443)
or (token_type == IdTokenType.iso15693)
or (token_type == IdTokenType.central)
(token_type == IdTokenEnumType.iso14443)
or (token_type == IdTokenEnumType.iso15693)
or (token_type == IdTokenEnumType.central)
):
status = self.get_authorization_status(token)
return call_result.Authorize(id_token_info={"status": status})
Expand All @@ -586,7 +587,7 @@ def _set_meter_values(self, tx_event_type: str, meter_values: list[dict]):
measurands: list[MeasurandValue] = []
for sampled_value in meter_value["sampled_value"]:
measurand: str = sampled_value.get(
"measurand", MeasurandType.energy_active_import_register.value
"measurand", MeasurandEnumType.energy_active_import_register.value
)
value: float = sampled_value["value"]
context: str = sampled_value.get("context", None)
Expand All @@ -602,11 +603,11 @@ def _set_meter_values(self, tx_event_type: str, meter_values: list[dict]):
)
converted_values.append(measurands)

if (tx_event_type == TransactionEventType.started.value) or (
(tx_event_type == TransactionEventType.updated.value)
if (tx_event_type == TransactionEventEnumType.started.value) or (
(tx_event_type == TransactionEventEnumType.updated.value)
and (self._metrics[csess.meter_start].value is None)
):
energy_measurand = MeasurandType.energy_active_import_register.value
energy_measurand = MeasurandEnumType.energy_active_import_register.value
for meter_value in converted_values:
for measurand_item in meter_value:
if measurand_item.measurand == energy_measurand:
Expand All @@ -617,9 +618,9 @@ def _set_meter_values(self, tx_event_type: str, meter_values: list[dict]):

self.process_measurands(converted_values, True)

if tx_event_type == TransactionEventType.ended.value:
if tx_event_type == TransactionEventEnumType.ended.value:
measurands_in_tx: set[str] = set()
tx_end_context = ReadingContextType.transaction_end.value
tx_end_context = ReadingContextEnumType.transaction_end.value
for meter_value in converted_values:
for measurand_item in meter_value:
if measurand_item.context == tx_end_context:
Expand All @@ -633,9 +634,15 @@ def _set_meter_values(self, tx_event_type: str, meter_values: list[dict]):
):
self._metrics[measurand].value = 0

@on("TransactionEvent")
@on(Action.transaction_event)
def on_transaction_event(
self, event_type, timestamp, trigger_reason, seq_no, transaction_info, **kwargs
self,
event_type,
timestamp,
trigger_reason,
seq_no,
transaction_info,
**kwargs,
):
"""Perform OCPP callback."""
offline: bool = kwargs.get("offline", False)
Expand All @@ -647,27 +654,27 @@ def on_transaction_event(
state = transaction_info["charging_state"]
evse_id: int = kwargs["evse"]["id"] if "evse" in kwargs else 1
evse_status_v16: ChargePointStatusv16 | None = None
if state == ChargingStateType.idle:
if state == ChargingStateEnumType.idle:
evse_status_v16 = ChargePointStatusv16.available
elif state == ChargingStateType.ev_connected:
elif state == ChargingStateEnumType.ev_connected:
evse_status_v16 = ChargePointStatusv16.preparing
elif state == ChargingStateType.suspended_evse:
elif state == ChargingStateEnumType.suspended_evse:
evse_status_v16 = ChargePointStatusv16.suspended_evse
elif state == ChargingStateType.suspended_ev:
elif state == ChargingStateEnumType.suspended_ev:
evse_status_v16 = ChargePointStatusv16.suspended_ev
elif state == ChargingStateType.charging:
elif state == ChargingStateEnumType.charging:
evse_status_v16 = ChargePointStatusv16.charging
if evse_status_v16:
self._report_evse_status(evse_id, evse_status_v16)

response = call_result.TransactionEvent()
id_token = kwargs.get("id_token")
if id_token:
response.id_token_info = {"status": AuthorizationStatusType.accepted}
response.id_token_info = {"status": AuthorizationStatusEnumType.accepted}
id_tag_string: str = id_token["type"] + ":" + id_token["id_token"]
self._metrics[cstat.id_tag.value].value = id_tag_string

if event_type == TransactionEventType.started.value:
if event_type == TransactionEventEnumType.started.value:
self._tx_start_time = t
tx_id: str = transaction_info["transaction_id"]
self._metrics[csess.transaction_id.value].value = tx_id
Expand All @@ -678,7 +685,7 @@ def on_transaction_event(
duration_minutes: int = ((t - self._tx_start_time).seconds + 59) // 60
self._metrics[csess.session_time].value = duration_minutes
self._metrics[csess.session_time].unit = UnitOfTime.MINUTES
if event_type == TransactionEventType.ended.value:
if event_type == TransactionEventEnumType.ended.value:
self._metrics[csess.transaction_id.value].value = ""
self._metrics[cstat.id_tag.value].value = ""

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
colorlog==6.9.0
uv>=0.4
ruff==0.8.5
ocpp==1.0.0
ocpp==2.0.0
websockets==14.1
jsonschema==4.23.0
pre-commit==4.0.1
Expand Down
Loading
Loading