1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
import sqlalchemy
import sqlalchemy.types as sqltypes
from sqlalchemy.sql import and_
from sqlalchemy.schema import UniqueConstraint
from sqlalchemy.sql.expression import bindparam
from pymodbus.exceptions import NotImplementedException
from pymodbus.interfaces import IModbusSlaveContext
#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging;
_logger = logging.getLogger(__name__)
#---------------------------------------------------------------------------#
# Context
#---------------------------------------------------------------------------#
class DatabaseSlaveContext(IModbusSlaveContext):
'''
This creates a modbus data model with each data access
stored in its own personal block
'''
def __init__(self, *args, **kwargs):
''' Initializes the datastores
:param kwargs: Each element is a ModbusDataBlock
'''
self.table = kwargs.get('table', 'pymodbus')
self.database = kwargs.get('database', 'sqlite:///pymodbus.db')
self.__db_create(self.table, self.database)
def __str__(self):
''' Returns a string representation of the context
:returns: A string representation of the context
'''
return "Modbus Slave Context"
def reset(self):
''' Resets all the datastores to their default values '''
self._metadata.drop_all()
self.__db_create(self.table, self.database)
raise NotImplementedException() # TODO drop table?
def validate(self, fx, address, count=1):
''' Validates the request to make sure it is in range
:param fx: The function we are working with
:param address: The starting address
:param count: The number of values to test
:returns: True if the request in within range, False otherwise
'''
address = address + 1 # section 4.4 of specification
_logger.debug("validate[%d] %d:%d" % (fx, address, count))
return self.__validate(self.decode(fx), address, count)
def getValues(self, fx, address, count=1):
''' Validates the request to make sure it is in range
:param fx: The function we are working with
:param address: The starting address
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
'''
address = address + 1 # section 4.4 of specification
_logger.debug("get-values[%d] %d:%d" % (fx, address, count))
return self.__get(self.decode(fx), address, count)
def setValues(self, fx, address, values):
''' Sets the datastore with the supplied values
:param fx: The function we are working with
:param address: The starting address
:param values: The new values to be set
'''
address = address + 1 # section 4.4 of specification
_logger.debug("set-values[%d] %d:%d" % (fx, address, len(values)))
self.__set(self.decode(fx), address, values)
#--------------------------------------------------------------------------#
# Sqlite Helper Methods
#--------------------------------------------------------------------------#
def __db_create(self, table, database):
''' A helper method to initialize the database and handles
:param table: The table name to create
:param database: The database uri to use
'''
self._engine = sqlalchemy.create_engine(database, echo=False)
self._metadata = sqlalchemy.MetaData(self._engine)
self._table = sqlalchemy.Table(table, self._metadata,
sqlalchemy.Column('type', sqltypes.String(1)),
sqlalchemy.Column('index', sqltypes.Integer),
sqlalchemy.Column('value', sqltypes.Integer),
UniqueConstraint('type', 'index', name='key'))
self._table.create(checkfirst=True)
self._connection = self._engine.connect()
def __get(self, type, offset, count):
'''
:param type: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
:returns: The resulting values
'''
query = self._table.select(and_(
self._table.c.type == type,
self._table.c.index >= offset,
self._table.c.index <= offset + count))
query = query.order_by(self._table.c.index.asc())
result = self._connection.execute(query).fetchall()
return [row.value for row in result]
def __build_set(self, type, offset, values, p=''):
''' A helper method to generate the sql update context
:param type: The key prefix to use
:param offset: The address offset to start at
:param values: The values to set
'''
result = []
for index, value in enumerate(values):
result.append({
p + 'type' : type,
p + 'index' : offset + index,
'value' : value
})
return result
def __set(self, type, offset, values):
'''
:param key: The type prefix to use
:param offset: The address offset to start at
:param values: The values to set
'''
context = self.__build_set(type, offset, values)
query = self._table.insert()
result = self._connection.execute(query, context)
return result.rowcount == len(values)
def __update(self, type, offset, values):
'''
:param type: The type prefix to use
:param offset: The address offset to start at
:param values: The values to set
'''
context = self.__build_set(type, offset, values, p='x_')
query = self._table.update().values(name='value')
query = query.where(and_(
self._table.c.type == bindparam('x_type'),
self._table.c.index == bindparam('x_index')))
result = self._connection.execute(query, context)
return result.rowcount == len(values)
def __validate(self, key, offset, count):
'''
:param key: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
:returns: The result of the validation
'''
query = self._table.select(and_(
self._table.c.type == type,
self._table.c.index >= offset,
self._table.c.index <= offset + count))
result = self._connection.execute(query)
return result.rowcount == count
|