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
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
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
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)
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
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
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, )
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
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,
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
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())