예제 #1
0
    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
                )
            )
예제 #2
0
    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)
예제 #3
0
    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)]
예제 #4
0
    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