stringtools/stp.py

176 rivejä
6.8 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
stp (Serial transfer protocol)
==============================
**Author:**
* Dirk Alders <sudo-dirk@mount-mockery.de>
**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
logger_name = 'STRINGTOOLS'
logger = logging.getLogger(logger_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:: ../examples/stp.stp.py
Will result to the following output:
.. literalinclude:: ../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:: ../examples/stp.build_frame.py
Will result to the following output:
.. literalinclude:: ../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