#!/usr/bin/env python # -*- coding: utf-8 -*- # """ stringtools.stp (Serial transfer protocol) ========================================== **Author:** * Dirk Alders **Description:** This module is a submodule of :mod:`stringtools` and creates an serial frame to transmit and receive messages via an serial interface. **Submodules:** * :class:`stringtools.stp.stp` * :func:`stringtools.stp.build_frame` """ import stringtools import logging import sys try: from config import APP_NAME as ROOT_LOGGER_NAME except ImportError: ROOT_LOGGER_NAME = 'root' logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) DATA_SYNC = b'\x3a' """The data sync byte""" DATA_CLEAR_BUFFER = b'\x3c' """The clear buffer byte ('\\\\x3a\\\\x3c' -> start of message)""" DATA_VALID_MSG = b'\x3e' """The valid message byte ('\\\\x3a\\\\x3e' -> end of message)""" DATA_STORE_SYNC_VALUE = b'\x3d' """The store sync value byte ('\\\\x3a\\\\x3d' -> '\\\\x3a' inside a message)""" STP_STATE_IDLE = 0x00 """Idle state definition (default)""" STP_STATE_ESCAPE_1 = 0x01 """Escape 1 state definition ('\\\\x3a\\\\x3c' found)""" STP_STATE_ESCAPE_2 = 0x02 """Escape 2 state definition ('\\\\x3a' found inside a message)""" STP_STATE_STORE_DATA = 0x03 """Store data state definition (start of message found; data will be stored)""" class stp(object): """This class extracts messages from an "stp-stream". **Example:** .. literalinclude:: stringtools/_examples_/stp.stp.py Will result to the following output: .. literalinclude:: stringtools/_examples_/stp.stp.log """ LOG_PREFIX = 'STP:' def __init__(self): self.state = STP_STATE_IDLE self.__buffer__ = b'' self.__clear_buffer__() def __clear_buffer__(self): if len(self.__buffer__) > 0: logger.warning('%s Chunking "%s" from buffer', self.LOG_PREFIX, stringtools.hexlify(self.__buffer__)) self.__buffer__ = b'' def process(self, data): """ This processes a byte out of a "stp-stream". :param bytes data: A byte stream :returns: The extracted message or None, if no message is identified yet :rtype: str """ if type(data) is list: raise TypeError if sys.version_info <= (3, 0): if type(data) is unicode: raise TypeError # rv = [] # while len(data) > 0: if sys.version_info >= (3, 0): b = bytes([data[0]]) else: b = data[0] data = data[1:] # if self.state == STP_STATE_IDLE: if b == DATA_SYNC: self.state = STP_STATE_ESCAPE_1 logger.debug('%s data sync (%02x) received => changing state STP_STATE_IDLE -> STP_STATE_ESCAPE_1', self.LOG_PREFIX, ord(b)) else: logger.warning('%s no data sync (%02x) received => ignoring byte', self.LOG_PREFIX, ord(b)) elif self.state == STP_STATE_ESCAPE_1: if b == DATA_CLEAR_BUFFER: logger.debug('%s start pattern (%02x %02x) received => changing state STP_STATE_ESCAPE_1 -> STP_STATE_STORE_DATA', self.LOG_PREFIX, ord(DATA_SYNC), ord(b)) self.state = STP_STATE_STORE_DATA self.__clear_buffer__() elif b != DATA_SYNC: self.state = STP_STATE_IDLE logger.warning('%s no start pattern (%02x %02x) received => changing state STP_STATE_ESCAPE_1 -> STP_STATE_IDLE', self.LOG_PREFIX, ord(DATA_SYNC), ord(b)) else: logger.warning('%s 2nd data sync (%02x) received => keep state', self.LOG_PREFIX, ord(b)) elif self.state == STP_STATE_STORE_DATA: if b == DATA_SYNC: self.state = STP_STATE_ESCAPE_2 logger.debug('%s data sync (%02x) received => changing state STP_STATE_STORE_DATA -> STP_STATE_ESCAPE_2', self.LOG_PREFIX, ord(b)) else: self.__buffer__ += b elif self.state == STP_STATE_ESCAPE_2: if b == DATA_CLEAR_BUFFER: logger.warning('%s start pattern (%02x %02x) received => changing state STP_STATE_ESCAPE_2 -> STP_STATE_STORE_DATA', self.LOG_PREFIX, ord(DATA_SYNC), ord(b)) self.state = STP_STATE_STORE_DATA self.__clear_buffer__() elif b == DATA_VALID_MSG: self.state = STP_STATE_IDLE logger.debug('%s end pattern (%02x %02x) received => storing message and changing state STP_STATE_ESCAPE_2 -> STP_STATE_IDLE', self.LOG_PREFIX, ord(DATA_SYNC), ord(b)) rv.append(self.__buffer__) self.__buffer__ = b'' elif b == DATA_STORE_SYNC_VALUE: self.state = STP_STATE_STORE_DATA logger.debug('%s store sync pattern (%02x %02x) received => changing state STP_STATE_ESCAPE_2 -> STP_STATE_STORE_DATA', self.LOG_PREFIX, ord(DATA_SYNC), ord(b)) self.__buffer__ += DATA_SYNC elif b == DATA_SYNC: self.state = STP_STATE_ESCAPE_1 logger.warning('%s second data sync (%02x) received => changing state STP_STATE_ESCAPE_2 -> STP_STATE_ESCAPE_1', self.LOG_PREFIX, ord(b)) self.__clear_buffer__() else: self.state = STP_STATE_IDLE logger.warning('%s data (%02x) received => changing state STP_STATE_ESCAPE_2 -> STP_STATE_IDLE', self.LOG_PREFIX, ord(b)) self.__clear_buffer__() else: logger.error('%s unknown state (%s) => adding value (%02x) back to data again and changing state -> STP_STATE_IDLE', self.LOG_PREFIX, repr(self.state), ord(b)) self.state = STP_STATE_IDLE self.__clear_buffer__() data = b + data for msg in rv: logger.info('%s message identified - %s', self.LOG_PREFIX, stringtools.hexlify(msg)) return rv def build_frame(data): """This Method builds an "stp-frame" to be transfered via a stream. :param str data: A String (Bytes) to be framed :returns: The "stp-framed" message to be sent :rtype: str **Example:** .. literalinclude:: stringtools/_examples_/stp.build_frame.py Will result to the following output: .. literalinclude:: stringtools/_examples_/stp.build_frame.log """ rv = DATA_SYNC + DATA_CLEAR_BUFFER for byte in data: if sys.version_info >= (3, 0): byte = bytes([byte]) if byte == DATA_SYNC: rv += DATA_SYNC + DATA_STORE_SYNC_VALUE else: rv += byte rv += DATA_SYNC + DATA_VALID_MSG return rv