def validate_checksum(telegram): """ :param str telegram: :raises ParseError: :raises InvalidChecksumError: """ # Extract the part for which the checksum applies. checksum_contents = re.search(r'\/.+\!', telegram, re.DOTALL) # Extract the hexadecimal checksum value itself. # The line ending '\r\n' for the checksum line can be ignored. checksum_hex = re.search(r'((?<=\!)[0-9A-Z]{4})+', telegram) if not checksum_contents or not checksum_hex: raise ParseError( 'Failed to perform CRC validation because the telegram is ' 'incomplete. The checksum and/or content values are missing.' ) calculated_crc = TelegramParser.crc16(checksum_contents.group(0)) expected_crc = int(checksum_hex.group(0), base=16) if calculated_crc != expected_crc: raise InvalidChecksumError( "Invalid telegram. The CRC checksum '{}' does not match the " "expected '{}'".format( calculated_crc, expected_crc ) )
def _parse(self, line): # Match value groups, but exclude the parentheses pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') values = re.findall(pattern, line) if not self._is_line_wellformed(line, values): raise ParseError("Invalid '%s' line for '%s'", line, self) # Convert empty value groups to None for clarity. values = [None if value == '' else value for value in values] return self._parse_values(values)
def _parse(self, line): # Match value groups, but exclude the parentheses pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*]{0,}(?=\)))+') values = re.findall(pattern, line) # Convert empty value groups to None for clarity. values = [None if value == '' else value for value in values] if not values or len(values) != len(self.value_formats): raise ParseError("Invalid '%s' line for '%s'", line, self) return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
def parse(self, telegram_data): """ Parse telegram from string to dict. The telegram str type makes python 2.x integration easier. :param str telegram_data: full telegram from start ('/') to checksum ('!ABCD') including line endings in between the telegram's lines :rtype: dict :returns: Shortened example: { .. r'\d-\d:96\.1\.1.+?\r\n': <CosemObject>, # EQUIPMENT_IDENTIFIER r'\d-\d:1\.8\.1.+?\r\n': <CosemObject>, # ELECTRICITY_USED_TARIFF_1 r'\d-\d:24\.3\.0.+?\r\n.+?\r\n': <MBusObject>, # GAS_METER_READING .. } :raises ParseError: :raises InvalidChecksumError: """ if self.apply_checksum_validation \ and self.telegram_specification['checksum_support']: self.validate_checksum(telegram_data) telegram = {} for signature, parser in self.telegram_specification['objects'].items( ): match = re.search(signature, telegram_data, re.DOTALL) # All telegram specification lines/signatures are expected to be # present. if not match: raise ParseError('Telegram specification does not match ' 'telegram data') telegram[signature] = parser.parse(match.group(0)) return telegram