Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
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
Esempio n. 4
0
    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)
Esempio n. 5
0
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")
Esempio n. 6
0
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")
Esempio n. 7
0
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()
Esempio n. 8
0
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()
Esempio n. 9
0
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")
Esempio n. 10
0
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")
Esempio n. 11
0
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")
Esempio n. 12
0
def test_call_representation():
    call = Call(unique_id="1", action=Action.Heartbeat, payload={})

    assert str(call) == "<Call - unique_id=1, action=Heartbeat, payload={}>"
Esempio n. 13
0
def heartbeat_call():
    return Call(unique_id=1, action="Heartbeat", payload={}).to_json()
Esempio n. 14
0
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