async def call(self, *, message: Any, context: RouterContext): ocpp_version = subprotocol_to_ocpp_version(self.subprotocol) camel_case_payload = snake_to_camel_case(asdict(message)) call = Call( unique_id=str(self._unique_id_generator()), action=message.__class__.__name__[:-7], payload=remove_nones(camel_case_payload), ) validate_payload(call, ocpp_version) await self._send(message=call.to_json(), is_response=False, context=context) self.subscriptions[call.unique_id] = context.queue try: response = await asyncio.wait_for(context.queue.get(), self._response_timeout) except asyncio.TimeoutError: del self.subscriptions[call.unique_id] raise if response.message_type_id == MessageType.CallError: log.warning("Received a CALLError: %s'", response) raise response.to_exception() else: response.action = call.action validate_payload(response, ocpp_version) snake_case_payload = camel_to_snake_case(response.payload) call_result = context.ocpp_adapter.call_result cls = getattr(call_result, message.__class__.__name__) return cls(**snake_case_payload)
async def call(self, payload, suppress=True): """ Send Call message to client and return payload of response. The given payload is transformed into a Call object by looking at the type of the payload. A payload of type BootNotificationPayload will turn in a Call with Action BootNotification, a HeartbeatPayload will result in a Call with Action Heartbeat etc. A timeout is raised when no response has arrived before expiring of the configured timeout. When waiting for a response no other Call message can be send. So this function will wait before response arrives or response timeout has expired. This is in line the OCPP specification Suppress is used to maintain backwards compatibility. When set to True, if response is a CallError, then this call will be suppressed. When set to False, an exception will be raised for users to handle this CallError. """ camel_case_payload = snake_to_camel_case(asdict(payload)) call = Call(unique_id=str(self._unique_id_generator()), action=payload.__class__.__name__[:-7], payload=remove_nones(camel_case_payload)) validate_payload(call, self._ocpp_version) # Use a lock to prevent make sure that only 1 message can be send at a # a time. async with self._call_lock: await self._send(call.to_json()) try: response = \ await self._get_specific_response(call.unique_id, self._response_timeout) except asyncio.TimeoutError: raise asyncio.TimeoutError( f"Waited {self._response_timeout}s for response on " f"{call.to_json()}.") if response.message_type_id == MessageType.CallError: LOGGER.warning("Received a CALLError: %s'", response) if suppress: return raise response.to_exception() else: response.action = call.action validate_payload(response, self._ocpp_version) snake_case_payload = camel_to_snake_case(response.payload) # Create the correct Payload instance based on the received payload. If # this method is called with a call.BootNotificationPayload, then it # will create a call_result.BootNotificationPayload. If this method is # called with a call.HeartbeatPayload, then it will create a # call_result.HeartbeatPayload etc. cls = getattr(self._call_result, payload.__class__.__name__) # noqa return cls(**snake_case_payload)
def payload_to_message(payload: Any) -> str: camel_case_payload = snake_to_camel_case(asdict(payload)) call = Call( unique_id=str(uuid4()), action=payload.__class__.__name__[:-7], payload=remove_nones(camel_case_payload), ) msg = call.to_json() return msg
async def call(self, payload): """ Send Call message to client and return payload of response. The given payload is transformed into a Call object by looking at the type of the payload. A payload of type BootNotificationPayload will turn in a Call with Action BootNotification, a HeartbeatPayload will result in a Call with Action Heartbeat etc. A timeout is raised when no response has arrived before expiring of the configured timeout. When waiting for a response no other Call message can be send. So this function will wait before response arrives or response timeout has expired. This is in line the OCPP specification """ camel_case_payload = snake_to_camel_case(asdict(payload)) call = Call( unique_id=str(self._unique_id_generator()), action=payload.__class__.__name__[:-7], payload=remove_nones(camel_case_payload), ) # Use a lock to prevent make sure that only 1 message can be send at a # a time. await self._call_lock.acquire() await self._send(call.to_json()) try: response = \ await self._get_specific_response(call.unique_id, self._response_timeout) except asyncio.TimeoutError: self._call_lock.release() raise self._call_lock.release() if response.message_type_id == MessageType.CallError: LOGGER.warning("Received a CALLError: %s'", response) return snake_case_payload = camel_to_snake_case(response.payload) # Create the correct Payload instance based on the received payload. If # this method is called with a call.BootNotificationPayload, then it # will create a call_result.BootNotificationPayload. If this method is # called with a call.HeartbeatPayload, then it will create a # call_result.HeartbeatPayload etc. cls = getattr(call_result, payload.__class__.__name__) return cls(**snake_case_payload)
def test_validate_set_charging_profile_payload(): """" Test if payloads with floats are validated correctly. This test uses the value of 21.4, which is internally represented as 21.39999999999999857891452847979962825775146484375. You can verify this using `decimal.Decimal(21.4)` """ message = Call(unique_id="1234", action="SetChargingProfile", payload={ 'connectorId': 1, 'csChargingProfiles': { 'chargingProfileId': 1, 'stackLevel': 0, 'chargingProfilePurpose': 'TxProfile', 'chargingProfileKind': 'Relative', 'chargingSchedule': { 'chargingRateUnit': 'A', 'chargingSchedulePeriod': [{ 'startPeriod': 0, 'limit': 21.4 }] }, 'transactionId': 123456789, } }) validate_payload(message, ocpp_version="1.6")
def test_validate_meter_values_hertz(): """ Tests that a unit of measure called "Hertz" is permitted in validation. This was missing from the original 1.6 spec, but was added as an errata (see the OCPP 1.6 Errata sheet, v4.0 Release, 2019-10-23, page 34). """ message = Call(unique_id="1234", action="MeterValues", payload={ 'connectorId': 1, 'transactionId': 123456789, 'meterValue': [{ 'timestamp': '2020-02-21T13:48:45.459756Z', 'sampledValue': [{ "value": "50.0", "measurand": "Frequency", "unit": "Hertz", }] }] }) validate_payload(message, ocpp_version="1.6")
def boot_notification_call(): return Call(unique_id="1", action=Action.BootNotification, payload={ 'chargePointVendor': 'Alfen BV', 'chargePointModel': 'ICU Eve Mini', 'firmwareVersion': "#1:3.4.0-2990#N:217H;1.0-223", }).to_json()
def boot_notification_call(): return Call(unique_id="1", action='BootNotification', payload={ 'reason': 'PowerUp', 'chargingStation': { 'vendorName': 'ICU Eve Mini', 'firmwareVersion': "#1:3.4.0-2990#N:217H;1.0-223", 'model': 'ICU Eve Mini', }, }).to_json()
def test_validate_set_maxlength_violation_payload(): """ Test if payloads that violate maxLength raise a TypeConstraintViolationError """ message = Call( unique_id="1234", action="StartTransaction", payload={ "idTag": "012345678901234567890", "connectorId": 1, }, ) with pytest.raises(TypeConstraintViolationError): validate_payload(message, ocpp_version="1.6")
def test_validate_payload_with_invalid_missing_property_payload(): """ Test if validate_payload raises ProtocolError when validation of payload with missing properties fails. """ message = Call( unique_id="1234", action="StartTransaction", payload={ 'connectorId': 1, 'idTag': "okTag", # meterStart is purposely missing 'timestamp': '2022-01-25T19:18:30.018Z' }, ) with pytest.raises(ProtocolError): validate_payload(message, ocpp_version="1.6")
def test_validate_payload_with_invalid_type_payload(): """ Test if validate_payload raises TypeConstraintViolationError when validation of payload with mismatched type fails. """ message = Call( unique_id="1234", action="StartTransaction", payload={ 'connectorId': 1, 'idTag': "okTag", 'meterStart': "invalid_type", 'timestamp': '2022-01-25T19:18:30.018Z' }, ) with pytest.raises(TypeConstraintViolationError): validate_payload(message, ocpp_version="1.6")
def test_call_representation(): call = Call(unique_id="1", action=Action.Heartbeat, payload={}) assert str(call) == "<Call - unique_id=1, action=Heartbeat, payload={}>"
def heartbeat_call(): return Call(unique_id=1, action="Heartbeat", payload={}).to_json()
def test_create_call_error(): payload = asdict(call_v16.HeartbeatPayload()) call: Call = Call(unique_id=123, action="Heartbeat", payload=payload) message = call.to_json() call_error: str = create_call_error(message) assert type(call_error) is str