예제 #1
0
    def get(
        self,
        logical_name: Union[str, Obis],
        attribute: int,
        selective_access: Optional[RangeDescriptor] = None,
    ):
        obis = force_obis(logical_name)
        instance = self.objects.get(obis.dotted_repr(), None)
        if instance is None:
            raise ValueError(
                f"Object with logical name {obis.dotted_repr()} does not exist on meter"
            )
        if instance.is_static_attribute(attribute):
            # check if the value is already present on the meter
            value = getattr(
                instance, instance.STATIC_ATTRIBUTES[attribute].attribute_name)
            if value:
                return value

        value = utils.parse_as_dlms_data(
            self.dlms_client.get(cosem_attribute=cosem.CosemAttribute(
                interface=instance.INTERFACE_CLASS_ID,
                instance=obis,
                attribute=attribute,
            )))

        if instance.is_static_attribute(attribute):
            setattr(instance,
                    instance.STATIC_ATTRIBUTES[attribute].attribute_name,
                    value)

        converter = instance.DYNAMIC_CONVERTERS.get(attribute, None)
        if converter:
            return converter(instance, value)
        return value
예제 #2
0
def getStructs(data: bytes, logger=None):
    dn = xdlms.DataNotification.from_bytes(data)  # The first 3 bytes should be ignored.
    result = parse_as_dlms_data(dn.body)

    # First is date
    date_row = result.pop(0)
    #clock_obis = Obis.from_bytes(date_row)
    clock, stats = datetime_from_bytes(date_row)
    if logger is not None:
        logger.debug(f"Clock object: datetime={clock}")
    else:
        print(f"Clock object: datetime={clock}")

    offset = 0

    structs = {"system": (clock, stats)}

    while offset < len(result):
        try:
            structs[Obis.from_bytes(result[offset]).to_string()] = ( result[offset+1:offset+3] )
            offset += 3
        except ValueError:
            structs["id"] = result[offset]
            offset += 1
    return structs
예제 #3
0
    def associate(
        self,
        association_request: Optional[
            acse.ApplicationAssociationRequest] = None,
    ) -> acse.ApplicationAssociationResponse:

        # the aarq can be overridden or the standard one from the connection is used.
        aarq = association_request or self.dlms_connection.get_aarq()

        self.send(aarq)
        response = self.next_event()
        # we could have received an exception from the meter.
        if isinstance(response, xdlms.ExceptionResponse):
            raise exceptions.DlmsClientException(
                f"DLMS Exception: {response.state_error!r}:{response.service_error!r}"
            )
        # the association might not be accepted by the meter
        if isinstance(response, acse.ApplicationAssociationResponse):
            if response.result is not enumerations.AssociationResult.ACCEPTED:
                # there could be an error suppled with the reject.
                extra_error = None
                if response.user_information:
                    if isinstance(response.user_information.content,
                                  ConfirmedServiceError):
                        extra_error = response.user_information.content.error
                raise exceptions.DlmsClientException(
                    f"Unable to perform Association: {response.result!r} and "
                    f"{response.result_source_diagnostics!r}, extra info: {extra_error}"
                )
        else:
            raise exceptions.LocalDlmsProtocolError(
                "Did not receive an AARE after sending AARQ")

        if self.should_send_hls_reply():
            try:
                hls_response = self.send_hls_reply()
            except ActionError as e:
                raise HLSError from e

            if not hls_response:
                raise HLSError("No HLS data in response")

            hls_data = utils.parse_as_dlms_data(hls_response)

            if not hls_response:
                raise HLSError("Did not receive any HLS response data")

            if not self.dlms_connection.hls_response_valid(hls_data):
                raise HLSError(
                    f"Meter did not respond with correct challenge calculation"
                )

        return response
예제 #4
0
def test_lte_monitoring_quality_of_service():
    """
    LTE Monitoring v0, attr 2.
    {
    T3402:   long-unsigned
    T3412:   long-unsigned
    RSRQ:   unsigned int
    RSRP:   unsigned int
    qRxlevMin: integer
    }
    """
    data = b"\x02\x05\x12\x02\xd0\x12\x0c\xa8\x11\x12\x11\x1e\x0f\xc0"
    result = parse_as_dlms_data(data)
    assert 5 == len(result)
예제 #5
0
    def next_event(self):
        """
        Will parse the buffer into an APDU. In lower levels we need the case to get more
        data. But this is not needed in the DLMS connections as it is the lower layers
        responisbility to make sure the data is complete before handing the control
        back to the dlms-layer. In the HDLC case the data is complete when we get the
        last nonsegmented informtion frame. And in the IP case we know the length of
        the data from the IP wrapper so we can keep on trying until we get all data.
        """
        apdu = XDlmsApduFactory.apdu_from_bytes(self.buffer)

        if self.use_protection:
            apdu = self.unprotect(apdu)

        self.update_negotiated_parameters(apdu)

        self.state.process_event(apdu)
        self.clear_buffer()

        if isinstance(apdu, acse.ApplicationAssociationResponseApdu):
            if apdu.result in [
                    enums.AssociationResult.REJECTED_PERMANENT,
                    enums.AssociationResult.REJECTED_TRANSIENT,
            ]:
                # reset the association on a reject
                self.state.process_event(dlms_state.RejectAssociation())

            # we need to start the HLS auth.
            elif apdu.authentication == enums.AuthenticationMechanism.HLS_GMAC:
                self.state.process_event(dlms_state.HlsStart())

        # Handle HLS verification
        if self.state.current_state == dlms_state.HLS_DONE:
            if isinstance(apdu, xdlms.ActionResponseNormalWithData):
                if apdu.status != enums.ActionResultStatus.SUCCESS:
                    self.state.process_event(dlms_state.HlsFailed())
                if self.hls_response_valid(utils.parse_as_dlms_data(
                        apdu.data)):
                    self.state.process_event(dlms_state.HlsSuccess())
                else:
                    self.state.process_event(dlms_state.HlsFailed())
            elif isinstance(apdu, (xdlms.ActionResponseNormalWithError,
                                   xdlms.ActionResponseNormal)):
                self.state.process_event(dlms_state.HlsFailed())

            else:
                raise exceptions.LocalDlmsProtocolError(
                    "Received a non Action response when in HLS DONE")

        return apdu
예제 #6
0
def test_gsm_diagnistics_cell_info():
    """
    GSM Diagnoistivs v1, attr 6

    {
     cell_ID: double-long-unsigned,
     location_ID: long-unsigned,
     signal_quality: unsigned,
     ber: unsigned,
     mcc: long-unsigned,
     mnc: long-unsigned,
    }
    """
    data = b"\x02\x07\x06\x00\x00\x00\x00\x12\x00\x00\x11\x00\x11\x00\x12\x00\x00\x12\x00\x00\x12\x00\x00"
    result = parse_as_dlms_data(data)

    assert 7 == len(result)
    assert result[0] == 0
    assert result[1] == 0
예제 #7
0
    def from_bytes(cls, source_bytes: bytes) -> "RangeDescriptor":
        data = bytearray(source_bytes)
        access_descriptor = data.pop(0)
        if access_descriptor is not cls.ACCESS_DESCRIPTOR:
            raise ValueError(
                f"Access descriptor {access_descriptor} is not valid for "
                f"RangeDescriptor. It should be {cls.ACCESS_DESCRIPTOR}"
            )
        parsed_data = utils.parse_as_dlms_data(data)

        restricting_object_data = parsed_data[0]
        from_value_data = parsed_data[1]
        to_value_data = parsed_data[2]
        selected_values_data = parsed_data[3]

        restricting_cosem_attribute = cosem.CosemAttribute(
            interface=enumerations.CosemInterface(restricting_object_data[0]),
            instance=cosem.Obis.from_bytes(restricting_object_data[1]),
            attribute=restricting_object_data[2],
        )
        restricting_object = CaptureObject(
            cosem_attribute=restricting_cosem_attribute,
            data_index=restricting_object_data[3],
        )
        from_dt, clock_status = time.datetime_from_bytes(from_value_data)
        to_dt, clock_status = time.datetime_from_bytes(to_value_data)
        if selected_values_data:
            raise NotImplementedError()
        else:
            selected_values = None

        return cls(
            restricting_object=restricting_object,
            from_value=from_dt,
            to_value=to_dt,
            selected_values=selected_values,
        )
예제 #8
0
    def next_event(self):
        """
        Will parse the buffer into an APDU. In lower levels we need the case to get more
        data. But this is not needed in the DLMS connections as it is the lower layers
        responsibility to make sure the data is complete before handing the control
        back to the dlms-layer. In the HDLC case the data is complete when we get the
        last unsegmented information frame. And in the IP case the length is known from
        the IP wrapper element so it is possible to can keep on trying until all data
        is received.
        """
        apdu = XDlmsApduFactory.apdu_from_bytes(self.buffer)

        if isinstance(apdu, acse.ApplicationAssociationResponse):
            # To be able to run the decryption we need to know some things about the
            # meter and that has to be extracted first
            self.update_meter_info(apdu)

        if self.use_protection:
            apdu = self.unprotect(apdu)

        if self.is_pre_established:
            if isinstance(
                    apdu,
                (acse.ApplicationAssociationResponse, acse.ReleaseResponse),
            ):
                raise exceptions.PreEstablishedAssociationError(
                    f"Received a {apdu.__class__.__name__}. In a pre-established "
                    f"association it is not possible to handle ACSE services.")

        self.state.process_event(apdu)
        self.clear_buffer()

        if isinstance(apdu, acse.ApplicationAssociationResponse):
            self.update_negotiated_parameters(apdu)

            if apdu.result in [
                    enums.AssociationResult.REJECTED_PERMANENT,
                    enums.AssociationResult.REJECTED_TRANSIENT,
            ]:
                # reset the association on a reject
                self.state.process_event(dlms_state.RejectAssociation())

            # we need to start the HLS auth.
            elif apdu.authentication == enums.AuthenticationMechanism.HLS_GMAC:
                self.state.process_event(dlms_state.HlsStart())

        # Handle HLS verification
        if self.state.current_state == dlms_state.HLS_DONE:
            if isinstance(apdu, xdlms.ActionResponseNormalWithData):
                if apdu.status != enums.ActionResultStatus.SUCCESS:
                    self.state.process_event(dlms_state.HlsFailed())
                if self.hls_response_valid(utils.parse_as_dlms_data(
                        apdu.data)):
                    self.state.process_event(dlms_state.HlsSuccess())
                else:
                    self.state.process_event(dlms_state.HlsFailed())
            elif isinstance(apdu, (xdlms.ActionResponseNormalWithError,
                                   xdlms.ActionResponseNormal)):
                self.state.process_event(dlms_state.HlsFailed())

            else:
                raise exceptions.LocalDlmsProtocolError(
                    "Received a non Action response when in HLS DONE")

        return apdu
예제 #9
0
LOAD_PROFILE_BUFFER = cosem.CosemAttribute(
    interface=enumerations.CosemInterface.PROFILE_GENERIC,
    instance=cosem.Obis(1, 0, 99, 1, 0),
    attribute=2,
)

CURRENT_ASSOCIATION_OBJECTS = cosem.CosemAttribute(
    interface=enumerations.CosemInterface.ASSOCIATION_LN,
    instance=cosem.Obis(0, 0, 40, 0, 0),
    attribute=2,
)

with public_client().session() as client:
    invocation_counter = utils.parse_as_dlms_data(
        client.get(cosem_attribute=cosem.CosemAttribute(
            interface=enumerations.CosemInterface.DATA,
            instance=cosem.Obis.from_string("0.0.43.1.0.255"),
            attribute=2,
        )))

    print(f"invocation_counter = {invocation_counter}")

    # with management_client(
    #    client_initial_invocation_counter=invocation_counter + 1
    # ).session() as client:

    profile = client.get(
        CURRENT_ASSOCIATION_OBJECTS,
        # access_descriptor=RangeDescriptor(
        #    restricting_object=selective_access.CaptureObject(
        #        cosem_attribute=cosem.CosemAttribute(
        #            interface=enumerations.CosemInterface.CLOCK,
예제 #10
0
    structs = {"system": (clock, stats)}

    while offset < len(result):
        try:
            structs[Obis.from_bytes(result[offset]).to_string()] = ( result[offset+1:offset+3] )
            offset += 3
        except ValueError:
            structs["id"] = result[offset]
            offset += 1
    return structs

if __name__ == "__main__":
    print(data[0])
    dn = xdlms.DataNotification.from_bytes(bytes.fromhex(hdlc_data_hex))  # The first 3 bytes should be ignored.
    result = parse_as_dlms_data(dn.body)

    # First is date
    date_row = result.pop(0)
    #clock_obis = Obis.from_bytes(date_row)
    clock, stats = datetime_from_bytes(date_row)
    print(f"Clock object: datetime={clock}")

    offset = 0

    structs = {}

    while offset < len(result):
        try:
            structs[Obis.from_bytes(result[offset]).to_string()] = ( result[offset+1:offset+3] )
            offset += 3
예제 #11
0
            cosem.CosemAttribute(
                interface=enumerations.CosemInterface.DATA,
                instance=cosem.Obis(0, 0, 96, 10, 1, 255),
                attribute=2,
            ),
            cosem.CosemAttribute(
                interface=enumerations.CosemInterface.REGISTER,
                instance=cosem.Obis(1, 0, 1, 8, 0, 255),
                attribute=2,
            ),
            cosem.CosemAttribute(
                interface=enumerations.CosemInterface.REGISTER,
                instance=cosem.Obis(1, 0, 2, 8, 0, 255),
                attribute=2,
            ),
        ],
        capture_period=60,
    )

    # result = parser.parse_bytes(profile)
    result = utils.parse_as_dlms_data(profile)
    # meter_objects_list = AssociationObjectListParser.parse_entries(result)
    # meter_objects_dict = {
    #     obj.logical_name.dotted_repr(): obj for obj in meter_objects_list
    # }
    # pprint(meter_objects_dict)
    pprint(profile)
    pprint(result)
    print(client.dlms_connection.meter_system_title.hex())
    # print(result[0][0].value.isoformat())