示例#1
0
class DSMRProtocol(asyncio.Protocol):
    """Assemble and handle incoming data into complete DSM telegrams."""

    transport = None
    telegram_callback = None

    def __init__(self, loop, telegram_parser, telegram_callback=None):
        """Initialize class."""
        self.loop = loop
        self.log = logging.getLogger(__name__)
        self.telegram_parser = telegram_parser
        # callback to call on complete telegram
        self.telegram_callback = telegram_callback
        # buffer to keep incomplete incoming data
        self.telegram_buffer = TelegramBuffer()
        # keep a lock until the connection is closed
        self._closed = asyncio.Event()

    def connection_made(self, transport):
        """Just logging for now."""
        self.transport = transport
        self.log.debug('connected')

    def data_received(self, data):
        """Add incoming data to buffer."""
        data = data.decode('ascii')
        self.log.debug('received data: %s', data)
        self.telegram_buffer.append(data)

        for telegram in self.telegram_buffer.get_all():
            self.handle_telegram(telegram)

    def connection_lost(self, exc):
        """Stop when connection is lost."""
        if exc:
            self.log.exception('disconnected due to exception', exc_info=exc)
        else:
            self.log.info('disconnected because of close/abort.')
        self._closed.set()

    def handle_telegram(self, telegram):
        """Send off parsed telegram to handling callback."""
        self.log.debug('got telegram: %s', telegram)

        try:
            parsed_telegram = self.telegram_parser.parse(telegram)
        except InvalidChecksumError as e:
            self.log.warning(str(e))
        except ParseError:
            self.log.exception("failed to parse telegram")
        else:
            self.telegram_callback(parsed_telegram)

    @asyncio.coroutine
    def wait_closed(self):
        """Wait until connection is closed."""
        yield from self._closed.wait()
示例#2
0
class FileInputReader(object):
    """
     Filereader to read and parse raw telegram strings from stdin or files specified at the commandline
     and instantiate Telegram objects for each read telegram.
     Usage python script "syphon_smartmeter_readings_stdin.py":
        from dsmr_parser import telegram_specifications
        from dsmr_parser.clients.filereader import FileInputReader

        if __name__== "__main__":

            fileinput_reader = FileReader(
                file = infile,
                telegram_specification = telegram_specifications.V4
                )

            for telegram in fileinput_reader.read_as_object():
                print(telegram)

    Command line:
        tail -f /data/smartmeter/readings.txt | python3 syphon_smartmeter_readings_stdin.py

     """
    def __init__(self, telegram_specification):
        self.telegram_parser = TelegramParser(telegram_specification)
        self.telegram_buffer = TelegramBuffer()
        self.telegram_specification = telegram_specification

    def read_as_object(self):
        """
        Read complete DSMR telegram's from stdin of filearguments specified on teh command line
        and return a Telegram object.
        :rtype: generator
        """
        with fileinput.input(mode='rb') as file_handle:
            while True:
                data = file_handle.readline()
                str = data.decode()
                self.telegram_buffer.append(str)

                for telegram in self.telegram_buffer.get_all():
                    try:
                        yield Telegram(telegram, self.telegram_parser,
                                       self.telegram_specification)
                    except InvalidChecksumError as e:
                        logger.warning(str(e))
                    except ParseError as e:
                        logger.error('Failed to parse telegram: %s', e)
示例#3
0
class FileTailReader(object):
    """
      Filereader to read and parse raw telegram strings from the tail of a
      given file and instantiate Telegram objects for each read telegram.
      Usage python script "syphon_smartmeter_readings_stdin.py":
        from dsmr_parser import telegram_specifications
        from dsmr_parser.clients.filereader import FileTailReader

        if __name__== "__main__":

            infile = '/data/smartmeter/readings.txt'

            filetail_reader = FileTailReader(
                file = infile,
                telegram_specification = telegram_specifications.V5
                )

            for telegram in filetail_reader.read_as_object():
                print(telegram)
      """
    def __init__(self, file, telegram_specification):
        self._file = file
        self.telegram_parser = TelegramParser(telegram_specification)
        self.telegram_buffer = TelegramBuffer()
        self.telegram_specification = telegram_specification

    def read_as_object(self):
        """
        Read complete DSMR telegram's from a files tail and return a Telegram object.
        :rtype: generator
        """
        with open(self._file, "rb") as file_handle:
            for data in tailer.follow(file_handle):
                str = data.decode()
                self.telegram_buffer.append(str)

                for telegram in self.telegram_buffer.get_all():
                    try:
                        yield Telegram(telegram, self.telegram_parser,
                                       self.telegram_specification)
                    except InvalidChecksumError as e:
                        logger.warning(str(e))
                    except ParseError as e:
                        logger.error('Failed to parse telegram: %s', e)
示例#4
0
class FileReader(object):
    """
     Filereader to read and parse raw telegram strings from a file and instantiate Telegram objects
     for each read telegram.
     Usage:
        from dsmr_parser import telegram_specifications
        from dsmr_parser.clients.filereader import FileReader

        if __name__== "__main__":

            infile = '/data/smartmeter/readings.txt'

            file_reader = FileReader(
                file = infile,
                telegram_specification = telegram_specifications.V4
                )

            for telegram in file_reader.read_as_object():
                print(telegram)

     The file can be created like:
        from dsmr_parser import telegram_specifications
        from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5

        if __name__== "__main__":

            outfile = '/data/smartmeter/readings.txt'

            serial_reader = SerialReader(
                device='/dev/ttyUSB0',
                serial_settings=SERIAL_SETTINGS_V5,
                telegram_specification=telegram_specifications.V4
            )

            for telegram in serial_reader.read_as_object():
                f=open(outfile,"ab+")
                f.write(telegram._telegram_data.encode())
                f.close()
     """
    def __init__(self, file, telegram_specification):
        self._file = file
        self.telegram_parser = TelegramParser(telegram_specification)
        self.telegram_buffer = TelegramBuffer()
        self.telegram_specification = telegram_specification

    def read_as_object(self):
        """
        Read complete DSMR telegram's from a file and return a Telegram object.
        :rtype: generator
        """
        with open(self._file, "rb") as file_handle:
            while True:
                data = file_handle.readline()
                str = data.decode()
                self.telegram_buffer.append(str)

                for telegram in self.telegram_buffer.get_all():
                    try:
                        yield Telegram(telegram, self.telegram_parser,
                                       self.telegram_specification)
                    except InvalidChecksumError as e:
                        logger.warning(str(e))
                    except ParseError as e:
                        logger.error('Failed to parse telegram: %s', e)
示例#5
0
class DSMRProtocol(asyncio.Protocol):
    """Assemble and handle incoming data into complete DSM telegrams."""

    transport = None
    telegram_callback = None

    def __init__(self,
                 loop,
                 telegram_parser,
                 telegram_callback=None,
                 keep_alive_interval=None):
        """Initialize class."""
        self.loop = loop
        self.log = logging.getLogger(__name__)
        self.telegram_parser = telegram_parser
        # callback to call on complete telegram
        self.telegram_callback = telegram_callback
        # buffer to keep incomplete incoming data
        self.telegram_buffer = TelegramBuffer()
        # keep a lock until the connection is closed
        self._closed = asyncio.Event()
        self._keep_alive_interval = keep_alive_interval
        self._active = True

    def connection_made(self, transport):
        """Just logging for now."""
        self.transport = transport
        self.log.debug('connected')
        self._active = False
        if self.loop and self._keep_alive_interval:
            self.loop.call_later(self._keep_alive_interval, self.keep_alive)

    def data_received(self, data):
        """Add incoming data to buffer."""

        # accept latin-1 (8-bit) on the line, to allow for non-ascii transport or padding
        data = data.decode("latin1")
        self._active = True
        self.log.debug('received data: %s', data)
        self.telegram_buffer.append(data)

        for telegram in self.telegram_buffer.get_all():
            # ensure actual telegram is ascii (7-bit) only (ISO 646:1991 IRV required in section 5.5 of IEC 62056-21)
            telegram = telegram.encode("latin1").decode("ascii")
            self.handle_telegram(telegram)

    def keep_alive(self):
        if self._active:
            self.log.debug('keep-alive checked')
            self._active = False
            if self.loop:
                self.loop.call_later(self._keep_alive_interval,
                                     self.keep_alive)
        else:
            self.log.warning('keep-alive check failed')
            if self.transport:
                self.transport.close()

    def connection_lost(self, exc):
        """Stop when connection is lost."""
        if exc:
            self.log.exception('disconnected due to exception', exc_info=exc)
        else:
            self.log.info('disconnected because of close/abort.')
        self._closed.set()

    def handle_telegram(self, telegram):
        """Send off parsed telegram to handling callback."""
        self.log.debug('got telegram: %s', telegram)

        try:
            parsed_telegram = self.telegram_parser.parse(telegram)
        except InvalidChecksumError as e:
            self.log.warning(str(e))
        except ParseError:
            self.log.exception("failed to parse telegram")
        else:
            self.telegram_callback(parsed_telegram)

    async def wait_closed(self):
        """Wait until connection is closed."""
        await self._closed.wait()
示例#6
0
class TelegramBufferTest(unittest.TestCase):
    def setUp(self):
        self.telegram_buffer = TelegramBuffer()

    def test_v22_telegram(self):
        self.telegram_buffer.append(TELEGRAM_V2_2)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V2_2)
        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_v42_telegram(self):
        self.telegram_buffer.append(TELEGRAM_V4_2)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_multiple_mixed_telegrams(self):
        self.telegram_buffer.append(''.join(
            (TELEGRAM_V2_2, TELEGRAM_V4_2, TELEGRAM_V2_2)))

        telegrams = list(self.telegram_buffer.get_all())

        self.assertListEqual(telegrams,
                             [TELEGRAM_V2_2, TELEGRAM_V4_2, TELEGRAM_V2_2])

        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_v42_telegram_preceded_with_unclosed_telegram(self):
        # There are unclosed telegrams at the start of the buffer.
        incomplete_telegram = TELEGRAM_V4_2[:-1]

        self.telegram_buffer.append(incomplete_telegram + TELEGRAM_V4_2)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_v42_telegram_preceded_with_unopened_telegram(self):
        # There is unopened telegrams at the start of the buffer indicating that
        # the buffer was being filled while the telegram was outputted halfway.
        incomplete_telegram = TELEGRAM_V4_2[1:]

        self.telegram_buffer.append(incomplete_telegram + TELEGRAM_V4_2)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_v42_telegram_trailed_by_unclosed_telegram(self):
        incomplete_telegram = TELEGRAM_V4_2[:-1]

        self.telegram_buffer.append(TELEGRAM_V4_2 + incomplete_telegram)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, incomplete_telegram)

    def test_v42_telegram_trailed_by_unopened_telegram(self):
        incomplete_telegram = TELEGRAM_V4_2[1:]

        self.telegram_buffer.append(TELEGRAM_V4_2 + incomplete_telegram)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, incomplete_telegram)

    def test_v42_telegram_adding_line_by_line(self):
        for line in TELEGRAM_V4_2.splitlines(keepends=True):
            self.telegram_buffer.append(line)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, '')

    def test_v42_telegram_adding_char_by_char(self):
        for char in TELEGRAM_V4_2:
            self.telegram_buffer.append(char)

        telegram = next(self.telegram_buffer.get_all())

        self.assertEqual(telegram, TELEGRAM_V4_2)
        self.assertEqual(self.telegram_buffer._buffer, '')
示例#7
0
class P1test(hass.Hass):
    def _logme(self, line):
        print(line)

    def initialize(self, *args, **kwargs):
        try:
            self.log("Using hass logger!")
        except Exception:
            self.log = self._logme
            self.log("Using test logger!")
        self.mode = 'tcp'
        self.host = 'homeassistant.fritz.box'
        self.port = 3333
        self.device = 'COM3'
        self.dsmr_version = '5'
        self.terminal_name = 'test'
        self.stop = False
        self.transport = None
        self.log("Starting thread...")
        self.log("P1 test started")
        parser = self.test_serial
        # parser = self.tcp

        dsmr_version = self.dsmr_version
        if dsmr_version == '2.2':
            specification = telegram_specifications.V2_2
            serial_settings = SERIAL_SETTINGS_V2_2
        elif dsmr_version == '4':
            specification = telegram_specifications.V4
            serial_settings = SERIAL_SETTINGS_V4
        elif dsmr_version == '5':
            specification = telegram_specifications.V5
            serial_settings = SERIAL_SETTINGS_V5
        elif dsmr_version == '5B':
            specification = telegram_specifications.BELGIUM_FLUVIUS
            serial_settings = SERIAL_SETTINGS_V5
        else:
            raise NotImplementedError(
                "No telegram parser found for version: %s", dsmr_version)
        self.telegram_parser = TelegramParser(specification)
        self.serial_settings = serial_settings
        # buffer to keep incomplete incoming data
        self.telegram_buffer = TelegramBuffer()

        self.thr = threading.Thread(target=parser, daemon=True)
        self.thr.start()
        self.log("Started!")
        # logging.basicConfig(level=logging.DEBUG)

    def dsmr_serial_callback(self, telegram):
        self.log('Telegram received')

    def terminate(self):
        self.stop = True
        self.log("Closing transport...")
        if self.transport:
            self.transport.close()
        self.thr.join(10)
        # Stopping loop the hard
        if self.thr.is_alive():
            self.log(
                "Stopping the loop unfortunally did not stop the thread, waiting for completion..."
            )
        else:
            self.log("Thread exited nicely")
        self.thr.join()
        self.log("Thread has stopped!")

    def test_serial(self):
        s = serial.Serial(port=self.device, **self.serial_settings)
        while not self.stop:
            data = s.read_until()
            print(f'{data}')
            self.data_received(data)
        s.close()

    def test_tcp(self):
        server_address = (self.host, self.port)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect(server_address)
        while not self.stop:
            data = sock.recv(1024)
            self.data_received(data)

        sock.close()

    def data_received(self, data):
        """Add incoming data to buffer."""
        data = data.decode('ascii')
        self.telegram_buffer.append(data)

        for telegram in self.telegram_buffer.get_all():
            self.handle_telegram(telegram)

    def handle_telegram(self, telegram):
        """Send off parsed telegram to handling callback."""
        try:
            parsed_telegram = self.telegram_parser.parse(telegram)
        except InvalidChecksumError as e:
            self.log.warning(str(e))
        except ParseError:
            self.log.exception("failed to parse telegram")
        else:
            self.dsmr_serial_callback(parsed_telegram)
示例#8
0
class SocketReader(object):

    BUFFER_SIZE = 256

    def __init__(self, host, port, telegram_specification):
        self.host = host
        self.port = port

        self.telegram_parser = TelegramParser(telegram_specification)
        self.telegram_buffer = TelegramBuffer()
        self.telegram_specification = telegram_specification

    def read(self):
        """
        Read complete DSMR telegram's from remote interface and parse it
        into CosemObject's and MbusObject's

        :rtype: generator
        """
        buffer = b""

        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:

            socket_handle.connect((self.host, self.port))

            while True:
                buffer += socket_handle.recv(self.BUFFER_SIZE)

                lines = buffer.splitlines(keepends=True)

                if len(lines) == 0:
                    continue

                for data in lines:
                    self.telegram_buffer.append(data.decode('ascii'))

                for telegram in self.telegram_buffer.get_all():
                    try:
                        yield self.telegram_parser.parse(telegram)
                    except InvalidChecksumError as e:
                        logger.warning(str(e))
                    except ParseError as e:
                        logger.error('Failed to parse telegram: %s', e)

                buffer = b""

    def read_as_object(self):
        """
        Read complete DSMR telegram's from remote and return a Telegram object.

        :rtype: generator
        """
        buffer = b""

        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:

            socket_handle.connect((self.host, self.port))

            while True:
                buffer += socket_handle.recv(self.BUFFER_SIZE)

                lines = buffer.splitlines(keepends=True)

                if len(lines) == 0:
                    continue

                for data in lines:
                    self.telegram_buffer.append(data.decode('ascii'))

                    for telegram in self.telegram_buffer.get_all():
                        try:
                            yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
                        except InvalidChecksumError as e:
                            logger.warning(str(e))
                        except ParseError as e:
                            logger.error('Failed to parse telegram: %s', e)

                buffer = b""