pymodbus/ 0000755 0001755 0001755 00000000000 12607273654 012466 5 ustar debacle debacle pymodbus/ez_setup.py 0000755 0001755 0001755 00000022764 12607272152 014704 0 ustar debacle debacle #!python
"""Bootstrap setuptools installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from ez_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import sys
DEFAULT_VERSION = "0.6c9"
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = {
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
}
import sys, os
try: from hashlib import md5
except ImportError: from md5 import md5
def _validate_md5(egg_name, data):
if egg_name in md5_data:
digest = md5(data).hexdigest()
if digest != md5_data[egg_name]:
print >>sys.stderr, (
"md5 validation of %s failed! (Possible download problem?)"
% egg_name
)
sys.exit(2)
return data
def use_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
download_delay=15
):
"""Automatically find/download setuptools and make it available on sys.path
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end with
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
it is not already available. If `download_delay` is specified, it should
be the number of seconds that will be paused before initiating a download,
should one be required. If an older version of setuptools is installed,
this routine will print a message to ``sys.stderr`` and raise SystemExit in
an attempt to abort the calling script.
"""
was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
def do_download():
egg = download_setuptools(version, download_base, to_dir, download_delay)
sys.path.insert(0, egg)
import setuptools; setuptools.bootstrap_install_from = egg
try:
import pkg_resources
except ImportError:
return do_download()
try:
pkg_resources.require("setuptools>="+version); return
except pkg_resources.VersionConflict, e:
if was_imported:
print >>sys.stderr, (
"The required version of setuptools (>=%s) is not available, and\n"
"can't be installed while this script is running. Please install\n"
" a more recent version first, using 'easy_install -U setuptools'."
"\n\n(Currently using %r)"
) % (version, e.args[0])
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return do_download()
except pkg_resources.DistributionNotFound:
return do_download()
def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
delay = 15
):
"""Download setuptools from a specified location and return its filename
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download attempt.
"""
import urllib2, shutil
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
url = download_base + egg_name
saveto = os.path.join(to_dir, egg_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
from distutils import log
if delay:
log.warn("""
---------------------------------------------------------------------------
This script requires setuptools version %s to run (even to display
help). I will attempt to download it for you (from
%s), but
you may need to enable firewall access for this script first.
I will start the download in %d seconds.
(Note: if this machine does not have network access, please obtain the file
%s
and place it in this directory before rerunning this script.)
---------------------------------------------------------------------------""",
version, download_base, delay, url
); from time import sleep; sleep(delay)
log.warn("Downloading %s", url)
src = urllib2.urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = _validate_md5(egg_name, src.read())
dst = open(saveto,"wb"); dst.write(data)
finally:
if src: src.close()
if dst: dst.close()
return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
try:
import setuptools
except ImportError:
egg = None
try:
egg = download_setuptools(version, delay=0)
sys.path.insert(0,egg)
from setuptools.command.easy_install import main
return main(list(argv)+[egg]) # we're done here
finally:
if egg and os.path.exists(egg):
os.unlink(egg)
else:
if setuptools.__version__ == '0.0.1':
print >>sys.stderr, (
"You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
req = "setuptools>="+version
import pkg_resources
try:
pkg_resources.require(req)
except pkg_resources.VersionConflict:
try:
from setuptools.command.easy_install import main
except ImportError:
from easy_install import main
main(list(argv)+[download_setuptools(delay=0)])
sys.exit(0) # try to force an exit
else:
if argv:
from setuptools.command.easy_install import main
main(argv)
else:
print "Setuptools version",version,"or greater has been installed."
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames):
"""Update our built-in md5 registry"""
import re
for name in filenames:
base = os.path.basename(name)
f = open(name,'rb')
md5_data[base] = md5(f.read()).hexdigest()
f.close()
data = [" %r: %r,\n" % it for it in md5_data.items()]
data.sort()
repl = "".join(data)
import inspect
srcfile = inspect.getsourcefile(sys.modules[__name__])
f = open(srcfile, 'rb'); src = f.read(); f.close()
match = re.search("\nmd5_data = {\n([^}]+)}", src)
if not match:
print >>sys.stderr, "Internal error!"
sys.exit(2)
src = src[:match.start(1)] + repl + src[match.end(1):]
f = open(srcfile,'w')
f.write(src)
f.close()
if __name__=='__main__':
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
update_md5(sys.argv[2:])
else:
main(sys.argv[1:])
pymodbus/setup_commands.py 0000755 0001755 0001755 00000012646 12607272152 016065 0 ustar debacle debacle from distutils.core import Command
import sys, os, shutil
#---------------------------------------------------------------------------#
# Extra Commands
#---------------------------------------------------------------------------#
class BuildApiDocsCommand(Command):
''' Helper command to build the available api documents
This scans all the subdirectories under api and runs the
build.py script underneath trying to build the api
documentation for the given format.
'''
description = "build all the projects api documents"
user_options = []
def initialize_options(self):
''' options setup '''
if not os.path.exists('./build'):
os.mkdir('./build')
def finalize_options(self):
''' options teardown '''
pass
def run(self):
''' command runner '''
old_cwd = os.getcwd()
directories = (d for d in os.listdir('./doc/api') if not d.startswith('.'))
for entry in directories:
os.chdir('./doc/api/%s' % entry)
os.system('python build.py')
os.chdir(old_cwd)
class DeepCleanCommand(Command):
''' Helper command to return the directory to a completely
clean state.
'''
description = "clean everything that we don't want"
user_options = []
def initialize_options(self):
''' options setup '''
self.trash = ['build', 'dist', 'pymodbus.egg-info',
os.path.join(os.path.join('doc','sphinx'),'build'),
]
def finalize_options(self):
pass
def run(self):
''' command runner '''
self.__delete_pyc_files()
self.__delete_trash_dirs()
def __delete_trash_dirs(self):
''' remove all directories created in building '''
self.__delete_pyc_files()
for directory in self.trash:
if os.path.exists(directory):
shutil.rmtree(directory)
def __delete_pyc_files(self):
''' remove all python cache files '''
for root,dirs,files in os.walk('.'):
for file in files:
if file.endswith('.pyc'):
os.remove(os.path.join(root,file))
class LintCommand(Command):
''' Helper command to perform a lint scan of the
sourcecode and return the results.
'''
description = "perform a lint scan of the code"
user_options = []
def initialize_options(self):
''' options setup '''
if not os.path.exists('./build'):
os.mkdir('./build')
def finalize_options(self):
pass
def run(self):
''' command runner '''
scanners = [s for s in dir(self) if s.find('__try') >= 0]
for scanner in scanners:
if getattr(self, scanner)():
break
def __try_pyflakes(self):
try:
from pyflakes.scripts.pyflakes import main
sys.argv = '''pyflakes pymodbus'''.split()
main()
return True
except: return False
def __try_pychecker(self):
try:
import pychecker
sys.argv = '''pychecker pymodbus/*.py'''.split()
main()
return True
except: return False
def __try_pylint(self):
try:
import pylint
sys.argv = '''pylint pymodbus/*.py'''.split()
main()
return True
except: return False
class Python3Command(Command):
''' Helper command to scan for potential python 3
errors.
./setup.py scan_2to3 > build/diffs_2to3 build/report_2to3
'''
description = "perform 2to3 scan of the code"
user_options = []
def initialize_options(self):
''' options setup '''
if not os.path.exists('./build'):
os.mkdir('./build')
self.directories = ['pymodbus', 'test', 'examples']
def finalize_options(self):
pass
def run(self):
''' command runner '''
self.__run_python3()
def __run_python3(self):
try:
from lib2to3.main import main
sys.argv = ['2to3'] + self.directories
main("lib2to3.fixes")
return True
except: return False
class Pep8Command(Command):
''' Helper command to scan for potential pep8 violations
'''
description = "perform pep8 scan of the code"
user_options = []
def initialize_options(self):
''' options setup '''
if not os.path.exists('./build'):
os.mkdir('./build')
self.directories = ['pymodbus']
def finalize_options(self):
pass
def run(self):
''' command runner '''
self.__run_pep8()
def __run_pep8(self):
try:
from pep8 import _main as main
sys.argv = '''pep8 --repeat --count --statistics
'''.split() + self.directories
main()
return True
except: return False
#---------------------------------------------------------------------------#
# Command Configuration
#---------------------------------------------------------------------------#
command_classes = {
'deep_clean' : DeepCleanCommand,
'build_apidocs' : BuildApiDocsCommand,
'lint' : LintCommand,
'scan_2to3' : Python3Command,
'pep8' : Pep8Command,
}
#---------------------------------------------------------------------------#
# Export command list
#---------------------------------------------------------------------------#
__all__ = ['command_classes']
pymodbus/examples/ 0000755 0001755 0001755 00000000000 12607273533 014300 5 ustar debacle debacle pymodbus/examples/contrib/ 0000755 0001755 0001755 00000000000 12607272152 015734 5 ustar debacle debacle pymodbus/examples/contrib/sunspec_client.py 0000644 0001755 0001755 00000025376 12607272152 021341 0 ustar debacle debacle from pymodbus.constants import Endian
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from twisted.internet.defer import Deferred
#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.DEBUG)
logging.basicConfig()
#---------------------------------------------------------------------------#
# Sunspec Common Constants
#---------------------------------------------------------------------------#
class SunspecDefaultValue(object):
''' A collection of constants to indicate if
a value is not implemented.
'''
Signed16 = 0x8000
Unsigned16 = 0xffff
Accumulator16 = 0x0000
Scale = 0x8000
Signed32 = 0x80000000
Float32 = 0x7fc00000
Unsigned32 = 0xffffffff
Accumulator32 = 0x00000000
Signed64 = 0x8000000000000000
Unsigned64 = 0xffffffffffffffff
Accumulator64 = 0x0000000000000000
String = '\x00'
class SunspecStatus(object):
''' Indicators of the current status of a
sunspec device
'''
Normal = 0x00000000
Error = 0xfffffffe
Unknown = 0xffffffff
class SunspecIdentifier(object):
''' Assigned identifiers that are pre-assigned
by the sunspec protocol.
'''
Sunspec = 0x53756e53
class SunspecModel(object):
''' Assigned device indentifiers that are pre-assigned
by the sunspec protocol.
'''
#---------------------------------------------
# 0xx Common Models
#---------------------------------------------
CommonBlock = 1
AggregatorBlock = 2
#---------------------------------------------
# 1xx Inverter Models
#---------------------------------------------
SinglePhaseIntegerInverter = 101
SplitPhaseIntegerInverter = 102
ThreePhaseIntegerInverter = 103
SinglePhaseFloatsInverter = 103
SplitPhaseFloatsInverter = 102
ThreePhaseFloatsInverter = 103
#---------------------------------------------
# 2xx Meter Models
#---------------------------------------------
SinglePhaseMeter = 201
SplitPhaseMeter = 201
WyeConnectMeter = 201
DeltaConnectMeter = 201
#---------------------------------------------
# 3xx Environmental Models
#---------------------------------------------
BaseMeteorological = 301
Irradiance = 302
BackOfModuleTemperature = 303
Inclinometer = 304
Location = 305
ReferencePoint = 306
BaseMeteorological = 307
MiniMeteorological = 308
#---------------------------------------------
# 4xx String Combiner Models
#---------------------------------------------
BasicStringCombiner = 401
AdvancedStringCombiner = 402
#---------------------------------------------
# 5xx Panel Models
#---------------------------------------------
PanelFloat = 501
PanelInteger = 502
#---------------------------------------------
# 641xx Outback Blocks
#---------------------------------------------
OutbackDeviceIdentifier = 64110
OutbackChargeController = 64111
OutbackFMSeriesChargeController = 64112
OutbackFXInverterRealTime = 64113
OutbackFXInverterConfiguration = 64114
OutbackSplitPhaseRadianInverter = 64115
OutbackRadianInverterConfiguration = 64116
OutbackSinglePhaseRadianInverterRealTime = 64117
OutbackFLEXNetDCRealTime = 64118
OutbackFLEXNetDCConfiguration = 64119
OutbackSystemControl = 64120
#---------------------------------------------
# 64xxx Vender Extension Block
#---------------------------------------------
EndOfSunSpecMap = 65535
@classmethod
def lookup(klass, code):
''' Given a device identifier, return the
device model name for that identifier
:param code: The device code to lookup
:returns: The device model name, or None if none available
'''
values = dict((v, k) for k, v in klass.__dict__.iteritems()
if not callable(v))
return values.get(code, None)
class SunspecOffsets(object):
''' Well known offsets that are used throughout
the sunspec protocol
'''
CommonBlock = 40000
CommonBlockLength = 69
AlternateCommonBlock = 50000
#---------------------------------------------------------------------------#
# Common Functions
#---------------------------------------------------------------------------#
def defer_or_apply(func):
''' Decorator to apply an adapter method
to a result regardless if it is a deferred
or a concrete response.
:param func: The function to decorate
'''
def closure(future, adapt):
if isinstance(defer, Deferred):
d = Deferred()
future.addCallback(lambda r: d.callback(adapt(r)))
return d
return adapt(future)
return closure
def create_sunspec_sync_client(host):
''' A quick helper method to create a sunspec
client.
:param host: The host to connect to
:returns: an initialized SunspecClient
'''
modbus = ModbusTcpClient(host)
modbus.connect()
client = SunspecClient(modbus)
client.initialize()
return client
#---------------------------------------------------------------------------#
# Sunspec Client
#---------------------------------------------------------------------------#
class SunspecDecoder(BinaryPayloadDecoder):
''' A decoder that deals correctly with the sunspec
binary format.
'''
def __init__(self, payload, endian):
''' Initialize a new instance of the SunspecDecoder
.. note:: This is always set to big endian byte order
as specified in the protocol.
'''
endian = Endian.Big
BinaryPayloadDecoder.__init__(self, payload, endian)
def decode_string(self, size=1):
''' Decodes a string from the buffer
:param size: The size of the string to decode
'''
self._pointer += size
string = self._payload[self._pointer - size:self._pointer]
return string.split(SunspecDefaultValue.String)[0]
class SunspecClient(object):
def __init__(self, client):
''' Initialize a new instance of the client
:param client: The modbus client to use
'''
self.client = client
self.offset = SunspecOffsets.CommonBlock
def initialize(self):
''' Initialize the underlying client values
:returns: True if successful, false otherwise
'''
decoder = self.get_device_block(self.offset, 2)
if decoder.decode_32bit_uint() == SunspecIdentifier.Sunspec:
return True
self.offset = SunspecOffsets.AlternateCommonBlock
decoder = self.get_device_block(self.offset, 2)
return decoder.decode_32bit_uint() == SunspecIdentifier.Sunspec
def get_common_block(self):
''' Read and return the sunspec common information
block.
:returns: A dictionary of the common block information
'''
length = SunspecOffsets.CommonBlockLength
decoder = self.get_device_block(self.offset, length)
return {
'SunSpec_ID': decoder.decode_32bit_uint(),
'SunSpec_DID': decoder.decode_16bit_uint(),
'SunSpec_Length': decoder.decode_16bit_uint(),
'Manufacturer': decoder.decode_string(size=32),
'Model': decoder.decode_string(size=32),
'Options': decoder.decode_string(size=16),
'Version': decoder.decode_string(size=16),
'SerialNumber': decoder.decode_string(size=32),
'DeviceAddress': decoder.decode_16bit_uint(),
'Next_DID': decoder.decode_16bit_uint(),
'Next_DID_Length': decoder.decode_16bit_uint(),
}
def get_device_block(self, offset, size):
''' A helper method to retrieve the next device block
.. note:: We will read 2 more registers so that we have
the information for the next block.
:param offset: The offset to start reading at
:param size: The size of the offset to read
:returns: An initialized decoder for that result
'''
_logger.debug("reading device block[{}..{}]".format(offset, offset + size))
response = self.client.read_holding_registers(offset, size + 2)
return SunspecDecoder.fromRegisters(response.registers)
def get_all_device_blocks(self):
''' Retrieve all the available blocks in the supplied
sunspec device.
.. note:: Since we do not know how to decode the available
blocks, this returns a list of dictionaries of the form:
decoder: the-binary-decoder,
model: the-model-identifier (name)
:returns: A list of the available blocks
'''
blocks = []
offset = self.offset + 2
model = SunspecModel.CommonBlock
while model != SunspecModel.EndOfSunSpecMap:
decoder = self.get_device_block(offset, 2)
model = decoder.decode_16bit_uint()
length = decoder.decode_16bit_uint()
blocks.append({
'model' : model,
'name' : SunspecModel.lookup(model),
'length': length,
'offset': offset + length + 2
})
offset += length + 2
return blocks
#------------------------------------------------------------
# A quick test runner
#------------------------------------------------------------
if __name__ == "__main__":
client = create_sunspec_sync_client("YOUR.HOST.GOES.HERE")
# print out all the device common block
common = client.get_common_block()
for key, value in common.iteritems():
if key == "SunSpec_DID":
value = SunspecModel.lookup(value)
print "{:<20}: {}".format(key, value)
# print out all the available device blocks
blocks = client.get_all_device_blocks()
for block in blocks:
print block
client.client.close()
pymodbus/examples/contrib/redis-datastore.py 0000644 0001755 0001755 00000022016 12607272152 021401 0 ustar debacle debacle import redis
from pymodbus.interfaces import IModbusSlaveContext
from pymodbus.utilities import pack_bitstring, unpack_bitstring
#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging;
_logger = logging.getLogger(__name__)
#---------------------------------------------------------------------------#
# Context
#---------------------------------------------------------------------------#
class RedisSlaveContext(IModbusSlaveContext):
'''
This is a modbus slave context using redis as a backing
store.
'''
def __init__(self, **kwargs):
''' Initializes the datastores
:param host: The host to connect to
:param port: The port to connect to
:param prefix: A prefix for the keys
'''
host = kwargs.get('host', 'localhost')
port = kwargs.get('port', 6379)
self.prefix = kwargs.get('prefix', 'pymodbus')
self.client = kwargs.get('client', redis.Redis(host=host, port=port))
self.__build_mapping()
def __str__(self):
''' Returns a string representation of the context
:returns: A string representation of the context
'''
return "Redis Slave Context %s" % self.client
def reset(self):
''' Resets all the datastores to their default values '''
self.client.flushall()
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.__val_callbacks[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("getValues[%d] %d:%d" % (fx, address, count))
return self.__get_callbacks[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("setValues[%d] %d:%d" % (fx, address, len(values)))
self.__set_callbacks[self.decode(fx)](address, values)
#--------------------------------------------------------------------------#
# Redis Helper Methods
#--------------------------------------------------------------------------#
def __get_prefix(self, key):
''' This is a helper to abstract getting bit values
:param key: The key prefix to use
:returns: The key prefix to redis
'''
return "%s:%s" % (self.prefix, key)
def __build_mapping(self):
'''
A quick helper method to build the function
code mapper.
'''
self.__val_callbacks = {
'd' : lambda o, c: self.__val_bit('d', o, c),
'c' : lambda o, c: self.__val_bit('c', o, c),
'h' : lambda o, c: self.__val_reg('h', o, c),
'i' : lambda o, c: self.__val_reg('i', o, c),
}
self.__get_callbacks = {
'd' : lambda o, c: self.__get_bit('d', o, c),
'c' : lambda o, c: self.__get_bit('c', o, c),
'h' : lambda o, c: self.__get_reg('h', o, c),
'i' : lambda o, c: self.__get_reg('i', o, c),
}
self.__set_callbacks = {
'd' : lambda o, v: self.__set_bit('d', o, v),
'c' : lambda o, v: self.__set_bit('c', o, v),
'h' : lambda o, v: self.__set_reg('h', o, v),
'i' : lambda o, v: self.__set_reg('i', o, v),
}
#--------------------------------------------------------------------------#
# Redis discrete implementation
#--------------------------------------------------------------------------#
__bit_size = 16
__bit_default = '\x00' * (__bit_size % 8)
def __get_bit_values(self, key, offset, count):
''' This is a helper to abstract getting bit values
:param key: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
'''
key = self.__get_prefix(key)
s = divmod(offset, self.__bit_size)[0]
e = divmod(offset + count, self.__bit_size)[0]
request = ('%s:%s' % (key, v) for v in range(s, e + 1))
response = self.client.mget(request)
return response
def __val_bit(self, key, offset, count):
''' Validates that the given range is currently set in redis.
If any of the keys return None, then it is invalid.
:param key: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
'''
response = self.__get_bit_values(key, offset, count)
return None not in response
def __get_bit(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
'''
response = self.__get_bit_values(key, offset, count)
response = (r or self.__bit_default for r in response)
result = ''.join(response)
result = unpack_bitstring(result)
return result[offset:offset + count]
def __set_bit(self, key, offset, values):
'''
:param key: The key prefix to use
:param offset: The address offset to start at
:param values: The values to set
'''
count = len(values)
s = divmod(offset, self.__bit_size)[0]
e = divmod(offset + count, self.__bit_size)[0]
value = pack_bitstring(values)
current = self.__get_bit_values(key, offset, count)
current = (r or self.__bit_default for r in current)
current = ''.join(current)
current = current[0:offset] + value + current[offset + count:]
final = (current[s:s + self.__bit_size] for s in range(0, count, self.__bit_size))
key = self.__get_prefix(key)
request = ('%s:%s' % (key, v) for v in range(s, e + 1))
request = dict(zip(request, final))
self.client.mset(request)
#--------------------------------------------------------------------------#
# Redis register implementation
#--------------------------------------------------------------------------#
__reg_size = 16
__reg_default = '\x00' * (__reg_size % 8)
def __get_reg_values(self, key, offset, count):
''' This is a helper to abstract getting register values
:param key: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
'''
key = self.__get_prefix(key)
#s = divmod(offset, self.__reg_size)[0]
#e = divmod(offset+count, self.__reg_size)[0]
#request = ('%s:%s' % (key, v) for v in range(s, e + 1))
request = ('%s:%s' % (key, v) for v in range(offset, count + 1))
response = self.client.mget(request)
return response
def __val_reg(self, key, offset, count):
''' Validates that the given range is currently set in redis.
If any of the keys return None, then it is invalid.
:param key: The key prefix to use
:param offset: The address offset to start at
:param count: The number of bits to read
'''
response = self.__get_reg_values(key, offset, count)
return None not in response
def __get_reg(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
'''
response = self.__get_reg_values(key, offset, count)
response = [r or self.__reg_default for r in response]
return response[offset:offset + count]
def __set_reg(self, key, offset, values):
'''
:param key: The key prefix to use
:param offset: The address offset to start at
:param values: The values to set
'''
count = len(values)
#s = divmod(offset, self.__reg_size)
#e = divmod(offset+count, self.__reg_size)
#current = self.__get_reg_values(key, offset, count)
key = self.__get_prefix(key)
request = ('%s:%s' % (key, v) for v in range(offset, count + 1))
request = dict(zip(request, values))
self.client.mset(request)
pymodbus/examples/contrib/message-generator.py 0000755 0001755 0001755 00000017122 12607272152 021724 0 ustar debacle debacle #!/usr/bin/env python
'''
Modbus Message Generator
--------------------------------------------------------------------------
The following is an example of how to generate example encoded messages
for the supplied modbus format:
* tcp - `./generate-messages.py -f tcp -m rx -b`
* ascii - `./generate-messages.py -f ascii -m tx -a`
* rtu - `./generate-messages.py -f rtu -m rx -b`
* binary - `./generate-messages.py -f binary -m tx -b`
'''
from optparse import OptionParser
#--------------------------------------------------------------------------#
# import all the available framers
#--------------------------------------------------------------------------#
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.transaction import ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer
from pymodbus.transaction import ModbusRtuFramer
#--------------------------------------------------------------------------#
# import all available messages
#--------------------------------------------------------------------------#
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.diag_message import *
from pymodbus.file_message import *
from pymodbus.other_message import *
from pymodbus.mei_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *
#--------------------------------------------------------------------------#
# initialize logging
#--------------------------------------------------------------------------#
import logging
modbus_log = logging.getLogger("pymodbus")
#--------------------------------------------------------------------------#
# enumerate all request messages
#--------------------------------------------------------------------------#
_request_messages = [
ReadHoldingRegistersRequest,
ReadDiscreteInputsRequest,
ReadInputRegistersRequest,
ReadCoilsRequest,
WriteMultipleCoilsRequest,
WriteMultipleRegistersRequest,
WriteSingleRegisterRequest,
WriteSingleCoilRequest,
ReadWriteMultipleRegistersRequest,
ReadExceptionStatusRequest,
GetCommEventCounterRequest,
GetCommEventLogRequest,
ReportSlaveIdRequest,
ReadFileRecordRequest,
WriteFileRecordRequest,
MaskWriteRegisterRequest,
ReadFifoQueueRequest,
ReadDeviceInformationRequest,
ReturnQueryDataRequest,
RestartCommunicationsOptionRequest,
ReturnDiagnosticRegisterRequest,
ChangeAsciiInputDelimiterRequest,
ForceListenOnlyModeRequest,
ClearCountersRequest,
ReturnBusMessageCountRequest,
ReturnBusCommunicationErrorCountRequest,
ReturnBusExceptionErrorCountRequest,
ReturnSlaveMessageCountRequest,
ReturnSlaveNoResponseCountRequest,
ReturnSlaveNAKCountRequest,
ReturnSlaveBusyCountRequest,
ReturnSlaveBusCharacterOverrunCountRequest,
ReturnIopOverrunCountRequest,
ClearOverrunCountRequest,
GetClearModbusPlusRequest,
]
#--------------------------------------------------------------------------#
# enumerate all response messages
#--------------------------------------------------------------------------#
_response_messages = [
ReadHoldingRegistersResponse,
ReadDiscreteInputsResponse,
ReadInputRegistersResponse,
ReadCoilsResponse,
WriteMultipleCoilsResponse,
WriteMultipleRegistersResponse,
WriteSingleRegisterResponse,
WriteSingleCoilResponse,
ReadWriteMultipleRegistersResponse,
ReadExceptionStatusResponse,
GetCommEventCounterResponse,
GetCommEventLogResponse,
ReportSlaveIdResponse,
ReadFileRecordResponse,
WriteFileRecordResponse,
MaskWriteRegisterResponse,
ReadFifoQueueResponse,
ReadDeviceInformationResponse,
ReturnQueryDataResponse,
RestartCommunicationsOptionResponse,
ReturnDiagnosticRegisterResponse,
ChangeAsciiInputDelimiterResponse,
ForceListenOnlyModeResponse,
ClearCountersResponse,
ReturnBusMessageCountResponse,
ReturnBusCommunicationErrorCountResponse,
ReturnBusExceptionErrorCountResponse,
ReturnSlaveMessageCountResponse,
ReturnSlaveNoReponseCountResponse,
ReturnSlaveNAKCountResponse,
ReturnSlaveBusyCountResponse,
ReturnSlaveBusCharacterOverrunCountResponse,
ReturnIopOverrunCountResponse,
ClearOverrunCountResponse,
GetClearModbusPlusResponse,
]
#--------------------------------------------------------------------------#
# build an arguments singleton
#--------------------------------------------------------------------------#
# Feel free to override any values here to generate a specific message
# in question. It should be noted that many argument names are reused
# between different messages, and a number of messages are simply using
# their default values.
#--------------------------------------------------------------------------#
_arguments = {
'address' : 0x12,
'count' : 0x08,
'value' : 0x01,
'values' : [0x01] * 8,
'read_address' : 0x12,
'read_count' : 0x08,
'write_address ' : 0x12,
'write_registers' : [0x01] * 8,
'transaction' : 0x01,
'protocol' : 0x00,
'unit' : 0x01,
}
#---------------------------------------------------------------------------#
# generate all the requested messages
#---------------------------------------------------------------------------#
def generate_messages(framer, options):
''' A helper method to parse the command line options
:param framer: The framer to encode the messages with
:param options: The message options to use
'''
messages = _request_messages if options.messages == 'tx' else _response_messages
for message in messages:
message = message(**_arguments)
print "%-44s = " % message.__class__.__name__,
packet = framer.buildPacket(message)
if not options.ascii:
packet = packet.encode('hex') + '\n'
print packet, # because ascii ends with a \r\n
#---------------------------------------------------------------------------#
# initialize our program settings
#---------------------------------------------------------------------------#
def get_options():
''' A helper method to parse the command line options
:returns: The options manager
'''
parser = OptionParser()
parser.add_option("-f", "--framer",
help="The type of framer to use (tcp, rtu, binary, ascii)",
dest="framer", default="tcp")
parser.add_option("-D", "--debug",
help="Enable debug tracing",
action="store_true", dest="debug", default=False)
parser.add_option("-a", "--ascii",
help="The indicates that the message is ascii",
action="store_true", dest="ascii", default=True)
parser.add_option("-b", "--binary",
help="The indicates that the message is binary",
action="store_false", dest="ascii")
parser.add_option("-m", "--messages",
help="The messages to encode (rx, tx)",
dest="messages", default='rx')
(opt, arg) = parser.parse_args()
return opt
def main():
''' The main runner function
'''
option = get_options()
if option.debug:
try:
modbus_log.setLevel(logging.DEBUG)
logging.basicConfig()
except Exception, e:
print "Logging is not supported on this system"
framer = lookup = {
'tcp': ModbusSocketFramer,
'rtu': ModbusRtuFramer,
'binary': ModbusBinaryFramer,
'ascii': ModbusAsciiFramer,
}.get(option.framer, ModbusSocketFramer)(None)
generate_messages(framer, option)
if __name__ == "__main__":
main()
pymodbus/examples/contrib/modbus_mapper.py 0000644 0001755 0001755 00000025163 12607272152 021152 0 ustar debacle debacle '''
Given a modbus mapping file, this is used to generate
decoder blocks so that non-programmers can define the
register values and then decode a modbus device all
without having to write a line of code for decoding.
Currently supported formats are:
* csv
* json
* xml
Here is an example of generating and using a mapping decoder
(note that this is still in the works and will be greatly
simplified in the final api; it is just an example of the
requested functionality)::
from modbus_mapper import csv_mapping_parser
from modbus_mapper import mapping_decoder
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.payload import BinaryModbusDecoder
template = ['address', 'size', 'function', 'name', 'description']
raw_mapping = csv_mapping_parser('input.csv', template)
mapping = mapping_decoder(raw_mapping)
index, size = 1, 100
client = ModbusTcpClient('localhost')
response = client.read_holding_registers(index, size)
decoder = BinaryModbusDecoder.fromRegisters(response.registers)
while index < size:
print "[{}]\t{}".format(i, mapping[i]['type'](decoder))
index += mapping[i]['size']
Also, using the same input mapping parsers, we can generate
populated slave contexts that can be run behing a modbus server::
from modbus_mapper import csv_mapping_parser
from modbus_mapper import modbus_context_decoder
from pymodbus.client.ssync import StartTcpServer
from pymodbus.datastore.context import ModbusServerContext
template = ['address', 'value', 'function', 'name', 'description']
raw_mapping = csv_mapping_parser('input.csv', template)
slave_context = modbus_context_decoder(raw_mapping)
context = ModbusServerContext(slaves=slave_context, single=True)
StartTcpServer(context)
'''
import csv
import json
from collections import defaultdict
from StringIO import StringIO
from tokenize import generate_tokens
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.datastore.store import ModbusSparseDataBlock
from pymodbus.datastore.context import ModbusSlaveContext
#---------------------------------------------------------------------------#
# raw mapping input parsers
#---------------------------------------------------------------------------#
# These generate the raw mapping_blocks from some form of input
# which can then be passed to the decoder in question to supply
# the requested output result.
#---------------------------------------------------------------------------#
def csv_mapping_parser(path, template):
''' Given a csv file of the the mapping data for
a modbus device, return a mapping layout that can
be used to decode an new block.
.. note:: For the template, a few values are required
to be defined: address, size, function, and type. All the remaining
values will be stored, but not formatted by the application.
So for example::
template = ['address', 'type', 'size', 'name', 'function']
mappings = json_mapping_parser('mapping.json', template)
:param path: The path to the csv input file
:param template: The row value template
:returns: The decoded csv dictionary
'''
mapping_blocks = defaultdict(dict)
with open(path, 'r') as handle:
reader = csv.reader(handle)
reader.next() # skip the csv header
for row in reader:
mapping = dict(zip(template, row))
fid = mapping.pop('function')
aid = int(mapping['address'])
mapping_blocks[aid] = mapping
return mapping_blocks
def json_mapping_parser(path, template):
''' Given a json file of the the mapping data for
a modbus device, return a mapping layout that can
be used to decode an new block.
.. note:: For the template, a few values are required
to be mapped: address, size, and type. All the remaining
values will be stored, but not formatted by the application.
So for example::
template = {
'Start': 'address',
'DataType': 'type',
'Length': 'size'
# the remaining keys will just pass through
}
mappings = json_mapping_parser('mapping.json', template)
:param path: The path to the csv input file
:param template: The row value template
:returns: The decoded csv dictionary
'''
mapping_blocks = {}
with open(path, 'r') as handle:
for tid, rows in json.load(handle).iteritems():
mappings = {}
for key, values in rows.iteritems():
mapping = {template.get(k, k) : v for k, v in values.iteritems()}
mappings[int(key)] = mapping
mapping_blocks[tid] = mappings
return mapping_blocks
def xml_mapping_parser(path):
''' Given an xml file of the the mapping data for
a modbus device, return a mapping layout that can
be used to decode an new block.
.. note:: The input of the xml file is defined as
follows::
:param path: The path to the xml input file
:returns: The decoded csv dictionary
'''
pass
#---------------------------------------------------------------------------#
# modbus context decoders
#---------------------------------------------------------------------------#
# These are used to decode a raw mapping_block into a slave context with
# populated function data blocks.
#---------------------------------------------------------------------------#
def modbus_context_decoder(mapping_blocks):
''' Given a mapping block input, generate a backing
slave context with initialized data blocks.
.. note:: This expects the following for each block:
address, value, and function where function is one of
di (discretes), co (coils), hr (holding registers), or
ir (input registers).
:param mapping_blocks: The mapping blocks
:returns: The initialized modbus slave context
'''
blocks = defaultdict(dict)
for block in mapping_blocks.itervalues():
for mapping in block.itervalues():
value = int(mapping['value'])
address = int(mapping['address'])
function = mapping['function']
blocks[function][address] = value
return ModbusSlaveContext(**blocks)
#---------------------------------------------------------------------------#
# modbus mapping decoder
#---------------------------------------------------------------------------#
# These are used to decode a raw mapping_block into a request decoder.
# So this allows one to simply grab a number of registers, and then
# pass them to this decoder which will do the rest.
#---------------------------------------------------------------------------#
class ModbusTypeDecoder(object):
''' This is a utility to determine the correct
decoder to use given a type name. By default this
supports all the types available in the default modbus
decoder, however this can easily be extended this class
and adding new types to the mapper::
class CustomTypeDecoder(ModbusTypeDecoder):
def __init__(self):
ModbusTypeDecode.__init__(self)
self.mapper['type-token'] = self.callback
def parse_my_bitfield(self, tokens):
return lambda d: d.decode_my_type()
'''
def __init__(self):
''' Initializes a new instance of the decoder
'''
self.default = lambda m: self.parse_16bit_uint
self.parsers = {
'uint': self.parse_16bit_uint,
'uint8': self.parse_8bit_uint,
'uint16': self.parse_16bit_uint,
'uint32': self.parse_32bit_uint,
'uint64': self.parse_64bit_uint,
'int': self.parse_16bit_int,
'int8': self.parse_8bit_int,
'int16': self.parse_16bit_int,
'int32': self.parse_32bit_int,
'int64': self.parse_64bit_int,
'float': self.parse_32bit_float,
'float32': self.parse_32bit_float,
'float64': self.parse_64bit_float,
'string': self.parse_32bit_int,
'bits': self.parse_bits,
}
#------------------------------------------------------------
# Type parsers
#------------------------------------------------------------
def parse_string(self, tokens):
_ = tokens.next()
size = int(tokens.next())
return lambda d: d.decode_string(size=size)
def parse_bits(self, tokens):
return lambda d: d.decode_bits()
def parse_8bit_uint(self, tokens):
return lambda d: d.decode_8bit_uint()
def parse_16bit_uint(self, tokens):
return lambda d: d.decode_16bit_uint()
def parse_32bit_uint(self, tokens):
return lambda d: d.decode_32bit_uint()
def parse_64bit_uint(self, tokens):
return lambda d: d.decode_64bit_uint()
def parse_8bit_int(self, tokens):
return lambda d: d.decode_8bit_int()
def parse_16bit_int(self, tokens):
return lambda d: d.decode_16bit_int()
def parse_32bit_int(self, tokens):
return lambda d: d.decode_32bit_int()
def parse_64bit_int(self, tokens):
return lambda d: d.decode_64bit_int()
def parse_32bit_float(self, tokens):
return lambda d: d.decode_32bit_float()
def parse_64bit_float(self, tokens):
return lambda d: d.decode_64bit_float()
#------------------------------------------------------------
# Public Interface
#------------------------------------------------------------
def tokenize(self, value):
''' Given a value, return the tokens
:param value: The value to tokenize
:returns: A token generator
'''
tokens = generate_tokens(StringIO(value).readline)
for toknum, tokval, _, _, _ in tokens:
yield tokval
def parse(self, value):
''' Given a type value, return a function
that supplied with a decoder, will decode
the correct value.
:param value: The type of value to parse
:returns: The decoder method to use
'''
tokens = self.tokenize(value)
token = tokens.next().lower()
parser = self.parsers.get(token, self.default)
return parser(tokens)
def mapping_decoder(mapping_blocks, decoder=None):
''' Given the raw mapping blocks, convert
them into modbus value decoder map.
:param mapping_blocks: The mapping blocks
:param decoder: The type decoder to use
'''
decoder = decoder or ModbusTypeDecoder()
for block in mapping_blocks.itervalues():
for mapping in block.itervalues():
mapping['address'] = int(mapping['address'])
mapping['size'] = int(mapping['size'])
mapping['type'] = decoder.parse(mapping['type'])
pymodbus/examples/contrib/modbus-scraper.py 0000755 0001755 0001755 00000024243 12607272152 021244 0 ustar debacle debacle #!/usr/bin/env python
'''
This is a simple scraper that can be pointed at a
modbus device to pull down all its values and store
them as a collection of sequential data blocks.
'''
import pickle
from optparse import OptionParser
from twisted.internet import serialport, reactor
from twisted.internet.protocol import ClientFactory
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext
from pymodbus.factory import ClientDecoder
from pymodbus.client.async import ModbusClientProtocol
#--------------------------------------------------------------------------#
# Configure the client logging
#--------------------------------------------------------------------------#
import logging
log = logging.getLogger("pymodbus")
#---------------------------------------------------------------------------#
# Choose the framer you want to use
#---------------------------------------------------------------------------#
from pymodbus.transaction import ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer
from pymodbus.transaction import ModbusRtuFramer
from pymodbus.transaction import ModbusSocketFramer
#---------------------------------------------------------------------------#
# Define some constants
#---------------------------------------------------------------------------#
COUNT = 8 # The number of bits/registers to read at once
DELAY = 0 # The delay between subsequent reads
SLAVE = 0x01 # The slave unit id to read from
#---------------------------------------------------------------------------#
# A simple scraper protocol
#---------------------------------------------------------------------------#
# I tried to spread the load across the device, but feel free to modify the
# logic to suit your own purpose.
#---------------------------------------------------------------------------#
class ScraperProtocol(ModbusClientProtocol):
def __init__(self, framer, endpoint):
''' Initializes our custom protocol
:param framer: The decoder to use to process messages
:param endpoint: The endpoint to send results to
'''
ModbusClientProtocol.__init__(self, framer)
self.endpoint = endpoint
def connectionMade(self):
''' Callback for when the client has connected
to the remote server.
'''
super(ScraperProtocol, self).connectionMade()
log.debug("Beginning the processing loop")
self.address = self.factory.starting
reactor.callLater(DELAY, self.scrape_holding_registers)
def connectionLost(self, reason):
''' Callback for when the client disconnects from the
server.
:param reason: The reason for the disconnection
'''
reactor.callLater(DELAY, reactor.stop)
def scrape_holding_registers(self):
''' Defer fetching holding registers
'''
log.debug("reading holding registers: %d" % self.address)
d = self.read_holding_registers(self.address, count=COUNT, unit=SLAVE)
d.addCallbacks(self.scrape_discrete_inputs, self.error_handler)
def scrape_discrete_inputs(self, response):
''' Defer fetching holding registers
'''
log.debug("reading discrete inputs: %d" % self.address)
self.endpoint.write((3, self.address, response.registers))
d = self.read_discrete_inputs(self.address, count=COUNT, unit=SLAVE)
d.addCallbacks(self.scrape_input_registers, self.error_handler)
def scrape_input_registers(self, response):
''' Defer fetching holding registers
'''
log.debug("reading discrete inputs: %d" % self.address)
self.endpoint.write((2, self.address, response.bits))
d = self.read_input_registers(self.address, count=COUNT, unit=SLAVE)
d.addCallbacks(self.scrape_coils, self.error_handler)
def scrape_coils(self, response):
''' Write values of holding registers, defer fetching coils
:param response: The response to process
'''
log.debug("reading coils: %d" % self.address)
self.endpoint.write((4, self.address, response.registers))
d = self.read_coils(self.address, count=COUNT, unit=SLAVE)
d.addCallbacks(self.start_next_cycle, self.error_handler)
def start_next_cycle(self, response):
''' Write values of coils, trigger next cycle
:param response: The response to process
'''
log.debug("starting next round: %d" % self.address)
self.endpoint.write((1, self.address, response.bits))
self.address += COUNT
if self.address >= self.factory.ending:
self.endpoint.finalize()
self.transport.loseConnection()
else: reactor.callLater(DELAY, self.scrape_holding_registers)
def error_handler(self, failure):
''' Handle any twisted errors
:param failure: The error to handle
'''
log.error(failure)
#---------------------------------------------------------------------------#
# a factory for the example protocol
#---------------------------------------------------------------------------#
# This is used to build client protocol's if you tie into twisted's method
# of processing. It basically produces client instances of the underlying
# protocol::
#
# Factory(Protocol) -> ProtocolInstance
#
# It also persists data between client instances (think protocol singelton).
#---------------------------------------------------------------------------#
class ScraperFactory(ClientFactory):
protocol = ScraperProtocol
def __init__(self, framer, endpoint, query):
''' Remember things necessary for building a protocols '''
self.framer = framer
self.endpoint = endpoint
self.starting, self.ending = query
def buildProtocol(self, _):
''' Create a protocol and start the reading cycle '''
protocol = self.protocol(self.framer, self.endpoint)
protocol.factory = self
return protocol
#---------------------------------------------------------------------------#
# a custom client for our device
#---------------------------------------------------------------------------#
# Twisted provides a number of helper methods for creating and starting
# clients:
# - protocol.ClientCreator
# - reactor.connectTCP
#
# How you start your client is really up to you.
#---------------------------------------------------------------------------#
class SerialModbusClient(serialport.SerialPort):
def __init__(self, factory, *args, **kwargs):
''' Setup the client and start listening on the serial port
:param factory: The factory to build clients with
'''
protocol = factory.buildProtocol(None)
self.decoder = ClientDecoder()
serialport.SerialPort.__init__(self, protocol, *args, **kwargs)
#---------------------------------------------------------------------------#
# a custom endpoint for our results
#---------------------------------------------------------------------------#
# An example line reader, this can replace with:
# - the TCP protocol
# - a context recorder
# - a database or file recorder
#---------------------------------------------------------------------------#
class LoggingContextReader(object):
def __init__(self, output):
''' Initialize a new instance of the logger
:param output: The output file to save to
'''
self.output = output
self.context = ModbusSlaveContext(
di = ModbusSequentialDataBlock.create(),
co = ModbusSequentialDataBlock.create(),
hr = ModbusSequentialDataBlock.create(),
ir = ModbusSequentialDataBlock.create())
def write(self, response):
''' Handle the next modbus response
:param response: The response to process
'''
log.info("Read Data: %s" % str(response))
fx, address, values = response
self.context.setValues(fx, address, values)
def finalize(self):
with open(self.output, "w") as handle:
pickle.dump(self.context, handle)
#--------------------------------------------------------------------------#
# Main start point
#--------------------------------------------------------------------------#
def get_options():
''' A helper method to parse the command line options
:returns: The options manager
'''
parser = OptionParser()
parser.add_option("-o", "--output",
help="The resulting output file for the scrape",
dest="output", default="datastore.pickle")
parser.add_option("-p", "--port",
help="The port to connect to", type='int',
dest="port", default=502)
parser.add_option("-s", "--server",
help="The server to scrape",
dest="host", default="127.0.0.1")
parser.add_option("-r", "--range",
help="The address range to scan",
dest="query", default="0:1000")
parser.add_option("-d", "--debug",
help="Enable debug tracing",
action="store_true", dest="debug", default=False)
(opt, arg) = parser.parse_args()
return opt
def main():
''' The main runner function '''
options = get_options()
if options.debug:
try:
log.setLevel(logging.DEBUG)
logging.basicConfig()
except Exception, ex:
print "Logging is not supported on this system"
# split the query into a starting and ending range
query = [int(p) for p in options.query.split(':')]
try:
log.debug("Initializing the client")
framer = ModbusSocketFramer(ClientDecoder())
reader = LoggingContextReader(options.output)
factory = ScraperFactory(framer, reader, query)
# how to connect based on TCP vs Serial clients
if isinstance(framer, ModbusSocketFramer):
reactor.connectTCP(options.host, options.port, factory)
else: SerialModbusClient(factory, options.port, reactor)
log.debug("Starting the client")
reactor.run()
log.debug("Finished scraping the client")
except Exception, ex:
print ex
#---------------------------------------------------------------------------#
# Main jumper
#---------------------------------------------------------------------------#
if __name__ == "__main__":
main()
pymodbus/examples/contrib/README.rst 0000644 0001755 0001755 00000003252 12607272152 017425 0 ustar debacle debacle ============================================================
Contributed Implementations
============================================================
There are a few example implementations of custom utilities
interacting with the pymodbus library just to show what is
possible.
------------------------------------------------------------
SqlAlchemy Database Datastore Backend
------------------------------------------------------------
This module allows one to use any database available through
the sqlalchemy package as a datastore for the modbus server.
This could be useful to have many servers who have data they
agree upon and is transactional.
------------------------------------------------------------
Redis Datastore Backend
------------------------------------------------------------
This module allows one to use redis as a modbus server
datastore backend. This achieves the same thing as the
sqlalchemy backend, however, it is much more lightweight and
easier to set up.
------------------------------------------------------------
Binary Coded Decimal Payload
------------------------------------------------------------
This module allows one to write binary coded decimal data to
the modbus server using the payload encoder/decoder
interfaces.
------------------------------------------------------------
Message Generator and Parser
------------------------------------------------------------
These are two utilities that can be used to create a number
of modbus messages for any of the available protocols as well
as to decode the messages and print descriptive text about
them.
Also included are a number of request and response messages
in tx-messages and rx-messages.
pymodbus/examples/contrib/thread_safe_datastore.py 0000644 0001755 0001755 00000020104 12607272152 022616 0 ustar debacle debacle import threading
from contextlib import contextmanager
from pymodbus.datastore.store import BaseModbusDataBlock
class ContextWrapper(object):
''' This is a simple wrapper around enter
and exit functions that conforms to the pyhton
context manager protocol:
with ContextWrapper(enter, leave):
do_something()
'''
def __init__(self, enter=None, leave=None, factory=None):
self._enter = enter
self._leave = leave
self._factory = factory
def __enter__(self):
if self.enter: self._enter()
return self if not self._factory else self._factory()
def __exit__(self, args):
if self._leave: self._leave()
class ReadWriteLock(object):
''' This reader writer lock gurantees write order, but not
read order and is generally biased towards allowing writes
if they are available to prevent starvation.
TODO:
* allow user to choose between read/write/random biasing
- currently write biased
- read biased allow N readers in queue
- random is 50/50 choice of next
'''
def __init__(self):
''' Initializes a new instance of the ReadWriteLock
'''
self.queue = [] # the current writer queue
self.lock = threading.Lock() # the underlying condition lock
self.read_condition = threading.Condition(self.lock) # the single reader condition
self.readers = 0 # the number of current readers
self.writer = False # is there a current writer
def __is_pending_writer(self):
return (self.writer # if there is a current writer
or (self.queue # or if there is a waiting writer
and (self.queue[0] != self.read_condition))) # or if the queue head is not a reader
def acquire_reader(self):
''' Notifies the lock that a new reader is requesting
the underlying resource.
'''
with self.lock:
if self.__is_pending_writer(): # if there are existing writers waiting
if self.read_condition not in self.queue: # do not pollute the queue with readers
self.queue.append(self.read_condition) # add the readers in line for the queue
while self.__is_pending_writer(): # until the current writer is finished
self.read_condition.wait(1) # wait on our condition
if self.queue and self.read_condition == self.queue[0]: # if the read condition is at the queue head
self.queue.pop(0) # then go ahead and remove it
self.readers += 1 # update the current number of readers
def acquire_writer(self):
''' Notifies the lock that a new writer is requesting
the underlying resource.
'''
with self.lock:
if self.writer or self.readers: # if we need to wait on a writer or readers
condition = threading.Condition(self.lock) # create a condition just for this writer
self.queue.append(condition) # and put it on the waiting queue
while self.writer or self.readers: # until the write lock is free
condition.wait(1) # wait on our condition
self.queue.pop(0) # remove our condition after our condition is met
self.writer = True # stop other writers from operating
def release_reader(self):
''' Notifies the lock that an existing reader is
finished with the underlying resource.
'''
with self.lock:
self.readers = max(0, self.readers - 1) # readers should never go below 0
if not self.readers and self.queue: # if there are no active readers
self.queue[0].notify_all() # then notify any waiting writers
def release_writer(self):
''' Notifies the lock that an existing writer is
finished with the underlying resource.
'''
with self.lock:
self.writer = False # give up current writing handle
if self.queue: # if someone is waiting in the queue
self.queue[0].notify_all() # wake them up first
else: self.read_condition.notify_all() # otherwise wake up all possible readers
@contextmanager
def get_reader_lock(self):
''' Wrap some code with a reader lock using the
python context manager protocol::
with rwlock.get_reader_lock():
do_read_operation()
'''
try:
self.acquire_reader()
yield self
finally: self.release_reader()
@contextmanager
def get_writer_lock(self):
''' Wrap some code with a writer lock using the
python context manager protocol::
with rwlock.get_writer_lock():
do_read_operation()
'''
try:
self.acquire_writer()
yield self
finally: self.release_writer()
class ThreadSafeDataBlock(BaseModbusDataBlock):
''' This is a simple decorator for a data block. This allows
a user to inject an existing data block which can then be
safely operated on from multiple cocurrent threads.
It should be noted that the choice was made to lock around the
datablock instead of the manager as there is less source of
contention (writes can occur to slave 0x01 while reads can
occur to slave 0x02).
'''
def __init__(self, block):
''' Initialize a new thread safe decorator
:param block: The block to decorate
'''
self.rwlock = ReadWriteLock()
self.block = block
def validate(self, address, count=1):
''' Checks to see if the request is in range
:param address: The starting address
:param count: The number of values to test for
:returns: True if the request in within range, False otherwise
'''
with self.rwlock.get_reader_lock():
return self.block.validate(address, count)
def getValues(self, address, count=1):
''' Returns the requested values of the datastore
:param address: The starting address
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
'''
with self.rwlock.get_reader_lock():
return self.block.getValues(address, count)
def setValues(self, address, values):
''' Sets the requested values of the datastore
:param address: The starting address
:param values: The new values to be set
'''
with self.rwlock.get_writer_lock():
return self.block.setValues(address, values)
if __name__ == "__main__":
class AtomicCounter(object):
def __init__(self, **kwargs):
self.counter = kwargs.get('start', 0)
self.finish = kwargs.get('finish', 1000)
self.lock = threading.Lock()
def increment(self, count=1):
with self.lock:
self.counter += count
def is_running(self):
return self.counter <= self.finish
locker = ReadWriteLock()
readers, writers = AtomicCounter(), AtomicCounter()
def read():
while writers.is_running() and readers.is_running():
with locker.get_reader_lock():
readers.increment()
def write():
while writers.is_running() and readers.is_running():
with locker.get_writer_lock():
writers.increment()
rthreads = [threading.Thread(target=read) for i in range(50)]
wthreads = [threading.Thread(target=write) for i in range(2)]
for t in rthreads + wthreads: t.start()
for t in rthreads + wthreads: t.join()
print "readers[%d] writers[%d]" % (readers.counter, writers.counter)
pymodbus/examples/contrib/concurrent-client.py 0000755 0001755 0001755 00000023236 12607272152 021755 0 ustar debacle debacle #!/usr/bin/env python
'''
Concurrent Modbus Client
---------------------------------------------------------------------------
This is an example of writing a high performance modbus client that allows
a high level of concurrency by using worker threads/processes to handle
writing/reading from one or more client handles at once.
'''
#--------------------------------------------------------------------------#
# import system libraries
#--------------------------------------------------------------------------#
import multiprocessing
import threading
import logging
import time
import itertools
from collections import namedtuple
# we are using the future from the concurrent.futures released with
# python3. Alternatively we will try the backported library::
# pip install futures
try:
from concurrent.futures import Future
except ImportError:
from futures import Future
#--------------------------------------------------------------------------#
# import neccessary modbus libraries
#--------------------------------------------------------------------------#
from pymodbus.client.common import ModbusClientMixin
#--------------------------------------------------------------------------#
# configure the client logging
#--------------------------------------------------------------------------#
import logging
log = logging.getLogger("pymodbus")
log.setLevel(logging.DEBUG)
logging.basicConfig()
#--------------------------------------------------------------------------#
# Initialize out concurrency primitives
#--------------------------------------------------------------------------#
class _Primitives(object):
''' This is a helper class used to group the
threading primitives depending on the type of
worker situation we want to run (threads or processes).
'''
def __init__(self, **kwargs):
self.queue = kwargs.get('queue')
self.event = kwargs.get('event')
self.worker = kwargs.get('worker')
@classmethod
def create(klass, in_process=False):
''' Initialize a new instance of the concurrency
primitives.
:param in_process: True for threaded, False for processes
:returns: An initialized instance of concurrency primitives
'''
if in_process:
from Queue import Queue
from threading import Thread
from threading import Event
return klass(queue=Queue, event=Event, worker=Thread)
else:
from multiprocessing import Queue
from multiprocessing import Event
from multiprocessing import Process
return klass(queue=Queue, event=Event, worker=Process)
#--------------------------------------------------------------------------#
# Define our data transfer objects
#--------------------------------------------------------------------------#
# These will be used to serialize state between the various workers.
# We use named tuples here as they are very lightweight while giving us
# all the benefits of classes.
#--------------------------------------------------------------------------#
WorkRequest = namedtuple('WorkRequest', 'request, work_id')
WorkResponse = namedtuple('WorkResponse', 'is_exception, work_id, response')
#--------------------------------------------------------------------------#
# Define our worker processes
#--------------------------------------------------------------------------#
def _client_worker_process(factory, input_queue, output_queue, is_shutdown):
''' This worker process takes input requests, issues them on its
client handle, and then sends the client response (success or failure)
to the manager to deliver back to the application.
It should be noted that there are N of these workers and they can
be run in process or out of process as all the state serializes.
:param factory: A client factory used to create a new client
:param input_queue: The queue to pull new requests to issue
:param output_queue: The queue to place client responses
:param is_shutdown: Condition variable marking process shutdown
'''
log.info("starting up worker : %s", threading.current_thread())
client = factory()
while not is_shutdown.is_set():
try:
workitem = input_queue.get(timeout=1)
log.debug("dequeue worker request: %s", workitem)
if not workitem: continue
try:
log.debug("executing request on thread: %s", workitem)
result = client.execute(workitem.request)
output_queue.put(WorkResponse(False, workitem.work_id, result))
except Exception, exception:
log.exception("error in worker thread: %s", threading.current_thread())
output_queue.put(WorkResponse(True, workitem.work_id, exception))
except Exception, ex: pass
log.info("request worker shutting down: %s", threading.current_thread())
def _manager_worker_process(output_queue, futures, is_shutdown):
''' This worker process manages taking output responses and
tying them back to the future keyed on the initial transaction id.
Basically this can be thought of as the delivery worker.
It should be noted that there are one of these threads and it must
be an in process thread as the futures will not serialize across
processes..
:param output_queue: The queue holding output results to return
:param futures: The mapping of tid -> future
:param is_shutdown: Condition variable marking process shutdown
'''
log.info("starting up manager worker: %s", threading.current_thread())
while not is_shutdown.is_set():
try:
workitem = output_queue.get()
future = futures.get(workitem.work_id, None)
log.debug("dequeue manager response: %s", workitem)
if not future: continue
if workitem.is_exception:
future.set_exception(workitem.response)
else: future.set_result(workitem.response)
log.debug("updated future result: %s", future)
del futures[workitem.work_id]
except Exception, ex: log.exception("error in manager")
log.info("manager worker shutting down: %s", threading.current_thread())
#--------------------------------------------------------------------------#
# Define our concurrent client
#--------------------------------------------------------------------------#
class ConcurrentClient(ModbusClientMixin):
''' This is a high performance client that can be used
to read/write a large number of reqeusts at once asyncronously.
This operates with a backing worker pool of processes or threads
to achieve its performance.
'''
def __init__(self, **kwargs):
''' Initialize a new instance of the client
'''
worker_count = kwargs.get('count', multiprocessing.cpu_count())
self.factory = kwargs.get('factory')
primitives = _Primitives.create(kwargs.get('in_process', False))
self.is_shutdown = primitives.event() # condition marking process shutdown
self.input_queue = primitives.queue() # input requests to process
self.output_queue = primitives.queue() # output results to return
self.futures = {} # mapping of tid -> future
self.workers = [] # handle to our worker threads
self.counter = itertools.count()
# creating the response manager
self.manager = threading.Thread(target=_manager_worker_process,
args=(self.output_queue, self.futures, self.is_shutdown))
self.manager.start()
self.workers.append(self.manager)
# creating the request workers
for i in range(worker_count):
worker = primitives.worker(target=_client_worker_process,
args=(self.factory, self.input_queue, self.output_queue, self.is_shutdown))
worker.start()
self.workers.append(worker)
def shutdown(self):
''' Shutdown all the workers being used to
concurrently process the requests.
'''
log.info("stating to shut down workers")
self.is_shutdown.set()
self.output_queue.put(WorkResponse(None, None, None)) # to wake up the manager
for worker in self.workers:
worker.join()
log.info("finished shutting down workers")
def execute(self, request):
''' Given a request, enqueue it to be processed
and then return a future linked to the response
of the call.
:param request: The request to execute
:returns: A future linked to the call's response
'''
future, work_id = Future(), self.counter.next()
self.input_queue.put(WorkRequest(request, work_id))
self.futures[work_id] = future
return future
def execute_silently(self, request):
''' Given a write request, enqueue it to
be processed without worrying about calling the
application back (fire and forget)
:param request: The request to execute
'''
self.input_queue.put(WorkRequest(request, None))
if __name__ == "__main__":
from pymodbus.client.sync import ModbusTcpClient
def client_factory():
log.debug("creating client for: %s", threading.current_thread())
client = ModbusTcpClient('127.0.0.1', port=5020)
client.connect()
return client
client = ConcurrentClient(factory = client_factory)
try:
log.info("issuing concurrent requests")
futures = [client.read_coils(i * 8, 8) for i in range(10)]
log.info("waiting on futures to complete")
for future in futures:
log.info("future result: %s", future.result(timeout=1))
finally: client.shutdown()
pymodbus/examples/contrib/rx-messages 0000644 0001755 0001755 00000014215 12607272152 020120 0 ustar debacle debacle # ------------------------------------------------------------
# What follows is a collection of encoded messages that can
# be used to test the message-parser. Simply uncomment the
# messages you want decoded and run the message parser with
# the given arguments. What follows is the listing of messages
# that are encoded in each format:
#
# - ReadHoldingRegistersResponse
# - ReadDiscreteInputsResponse
# - ReadInputRegistersResponse
# - ReadCoilsResponse
# - WriteMultipleCoilsResponse
# - WriteMultipleRegistersResponse
# - WriteSingleRegisterResponse
# - WriteSingleCoilResponse
# - ReadWriteMultipleRegistersResponse
# - ReadExceptionStatusResponse
# - GetCommEventCounterResponse
# - GetCommEventLogResponse
# - ReportSlaveIdResponse
# - ReadFileRecordResponse
# - WriteFileRecordResponse
# - MaskWriteRegisterResponse
# - ReadFifoQueueResponse
# - ReadDeviceInformationResponse
# - ReturnQueryDataResponse
# - RestartCommunicationsOptionResponse
# - ReturnDiagnosticRegisterResponse
# - ChangeAsciiInputDelimiterResponse
# - ForceListenOnlyModeResponse
# - ClearCountersResponse
# - ReturnBusMessageCountResponse
# - ReturnBusCommunicationErrorCountResponse
# - ReturnBusExceptionErrorCountResponse
# - ReturnSlaveMessageCountResponse
# - ReturnSlaveNoReponseCountResponse
# - ReturnSlaveNAKCountResponse
# - ReturnSlaveBusyCountResponse
# - ReturnSlaveBusCharacterOverrunCountResponse
# - ReturnIopOverrunCountResponse
# - ClearOverrunCountResponse
# - GetClearModbusPlusResponse
# ------------------------------------------------------------
# Modbus TCP Messages
# ------------------------------------------------------------
# [ MBAP Header ] [ Function Code] [ Data ]
# [ tid ][ pid ][ length ][ uid ]
# 2b 2b 2b 1b 1b Nb
#
# ./message-parser -b -p tcp -f messages
# ------------------------------------------------------------
#00010000001301031000010001000100010001000100010001
#000100000004010201ff
#00010000001301041000010001000100010001000100010001
#000100000004010101ff
#000100000006010f00120008
#000100000006011000120008
#000100000006010600120001
#00010000000601050012ff00
#00010000001301171000010001000100010001000100010001
#000100000003010700
#000100000006010b00000008
#000100000009010c06000000000000
#00010000000501110300ff
#000100000003011400
#000100000003011500
#00010000000801160012ffff0000
#00010000001601180012001000010001000100010001000100010001
#000100000008012b0e0183000000
#000100000006010800000000
#000100000006010800010000
#000100000006010800020000
#000100000006010800030000
#00010000000401080004
#0001000000060108000a0000
#0001000000060108000b0000
#0001000000060108000c0000
#0001000000060108000d0000
#0001000000060108000e0000
#0001000000060108000f0000
#000100000006010800100000
#000100000006010800110000
#000100000006010800120000
#000100000006010800130000
#000100000006010800140000
#000100000006010800150000
# ------------------------------------------------------------
# Modbus RTU Messages
# ------------------------------------------------------------
# [Address ][ Function Code] [ Data ][ CRC ]
# 1b 1b Nb 2b
#
# ./message-parser -b -p rtu -f messages
# ------------------------------------------------------------
#0103100001000100010001000100010001000193b4
#010201ffe1c8
#0104100001000100010001000100010001000122c1
#010101ff11c8
#010f00120008f408
#01100012000861ca
#010600120001e80f
#01050012ff002c3f
#01171000010001000100010001000100010001d640
#0107002230
#010b00000008a5cd
#010c060000000000006135
#01110300ffacbc
#0114002f00
#0115002e90
#01160012ffff00004e21
#01180012001000010001000100010001000100010001d74d
#012b0e01830000000faf
#010800000000e00b
#010800010000b1cb
#01080002000041cb
#010800030000100b
#0108000481d9
#0108000a0000c009
#0108000b000091c9
#0108000c00002008
#0108000d000071c8
#0108000e000081c8
#0108000f0000d008
#010800100000e1ce
#010800110000b00e
#010800120000400e
#01080013000011ce
#010800140000a00f
#010800150000f1cf
# ------------------------------------------------------------
# Modbus ASCII Messages
# ------------------------------------------------------------
# [ Start ][Address ][ Function ][ Data ][ LRC ][ End ]
# 1c 2c 2c Nc 2c 2c
#
# ./message-parser -a -p ascii -f messages
# ------------------------------------------------------------
#:01031000010001000100010001000100010001E4
#:010201FFFD
#:01041000010001000100010001000100010001E3
#:010101FFFE
#:010F00120008D6
#:011000120008D5
#:010600120001E6
#:01050012FF00E9
#:01171000010001000100010001000100010001D0
#:010700F8
#:010B00000008EC
#:010C06000000000000ED
#:01110300FFEC
#:011400EB
#:011500EA
#:01160012FFFF0000D9
#:01180012001000010001000100010001000100010001BD
#:012B0E018300000042
#:010800000000F7
#:010800010000F6
#:010800020000F5
#:010800030000F4
#:01080004F3
#:0108000A0000ED
#:0108000B0000EC
#:0108000C0000EB
#:0108000D0000EA
#:0108000E0000E9
#:0108000F0000E8
#:010800100000E7
#:010800110000E6
#:010800120000E5
#:010800130000E4
#:010800140000E3
#:010800150000E2
# ------------------------------------------------------------
# Modbus Binary Messages
# ------------------------------------------------------------
# [ Start ][Address ][ Function ][ Data ][ CRC ][ End ]
# 1b 1b 1b Nb 2b 1b
#
# ./message-parser -b -p binary -f messages
# ------------------------------------------------------------
#7b0103100001000100010001000100010001000193b47d
#7b010201ffe1c87d
#7b0104100001000100010001000100010001000122c17d
#7b010101ff11c87d
#7b010f00120008f4087d
#7b01100012000861ca7d
#7b010600120001e80f7d
#7b01050012ff002c3f7d
#7b01171000010001000100010001000100010001d6407d
#7b01070022307d
#7b010b00000008a5cd7d
#7b010c0600000000000061357d
#7b01110300ffacbc7d
#7b0114002f007d
#7b0115002e907d
#7b01160012ffff00004e217d
#7b01180012001000010001000100010001000100010001d74d7d
#7b012b0e01830000000faf7d
#7b010800000000e00b7d
#7b010800010000b1cb7d
#7b01080002000041cb7d
#7b010800030000100b7d
#7b0108000481d97d
#7b0108000a0000c0097d
#7b0108000b000091c97d
#7b0108000c000020087d
#7b0108000d000071c87d
#7b0108000e000081c87d
#7b0108000f0000d0087d
#7b010800100000e1ce7d
#7b010800110000b00e7d
#7b010800120000400e7d
#7b01080013000011ce7d
#7b010800140000a00f7d
#7b010800150000f1cf7d
pymodbus/examples/contrib/remote_server_context.py 0000644 0001755 0001755 00000016442 12607272152 022742 0 ustar debacle debacle '''
Although there is a remote server context already in the main library,
it works under the assumption that users would have a server context
of the following form::
server_context = {
0x00: client('host1.something.com'),
0x01: client('host2.something.com'),
0x02: client('host3.something.com')
}
This example is how to create a server context where the client is
pointing to the same host, but the requested slave id is used as the
slave for the client::
server_context = {
0x00: client('host1.something.com', 0x00),
0x01: client('host1.something.com', 0x01),
0x02: client('host1.something.com', 0x02)
}
'''
from pymodbus.exceptions import NotImplementedException
from pymodbus.interfaces import IModbusSlaveContext
#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)
#---------------------------------------------------------------------------#
# Slave Context
#---------------------------------------------------------------------------#
# Basically we create a new slave context for the given slave identifier so
# that this slave context will only make requests to that slave with the
# client that the server is maintaining.
#---------------------------------------------------------------------------#
class RemoteSingleSlaveContext(IModbusSlaveContext):
''' This is a remote server context that allows one
to create a server context backed by a single client that
may be attached to many slave units. This can be used to
effectively create a modbus forwarding server.
'''
def __init__(self, context, unit_id):
''' Initializes the datastores
:param context: The underlying context to operate with
:param unit_id: The slave that this context will contact
'''
self.context = context
self.unit_id = unit_id
def reset(self):
''' Resets all the datastores to their default values '''
raise NotImplementedException()
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
'''
_logger.debug("validate[%d] %d:%d" % (fx, address, count))
result = context.get_callbacks[self.decode(fx)](address, count, self.unit_id)
return result.function_code < 0x80
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
'''
_logger.debug("get values[%d] %d:%d" % (fx, address, count))
result = context.get_callbacks[self.decode(fx)](address, count, self.unit_id)
return self.__extract_result(self.decode(fx), result)
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
'''
_logger.debug("set values[%d] %d:%d" % (fx, address, len(values)))
context.set_callbacks[self.decode(fx)](address, values, self.unit_id)
def __str__(self):
''' Returns a string representation of the context
:returns: A string representation of the context
'''
return "Remote Single Slave Context(%s)" % self.unit_id
def __extract_result(self, fx, result):
''' A helper method to extract the values out of
a response. The future api should make the result
consistent so we can just call `result.getValues()`.
:param fx: The function to call
:param result: The resulting data
'''
if result.function_code < 0x80:
if fx in ['d', 'c']: return result.bits
if fx in ['h', 'i']: return result.registers
else: return result
#---------------------------------------------------------------------------#
# Server Context
#---------------------------------------------------------------------------#
# Think of this as simply a dictionary of { unit_id: client(req, unit_id) }
#---------------------------------------------------------------------------#
class RemoteServerContext(object):
''' This is a remote server context that allows one
to create a server context backed by a single client that
may be attached to many slave units. This can be used to
effectively create a modbus forwarding server.
'''
def __init__(self, client):
''' Initializes the datastores
:param client: The client to retrieve values with
'''
self.get_callbacks = {
'd': lambda a, c, s: client.read_discrete_inputs(a, c, s),
'c': lambda a, c, s: client.read_coils(a, c, s),
'h': lambda a, c, s: client.read_holding_registers(a, c, s),
'i': lambda a, c, s: client.read_input_registers(a, c, s),
}
self.set_callbacks = {
'd': lambda a, v, s: client.write_coils(a, v, s),
'c': lambda a, v, s: client.write_coils(a, v, s),
'h': lambda a, v, s: client.write_registers(a, v, s),
'i': lambda a, v, s: client.write_registers(a, v, s),
}
self.slaves = {} # simply a cache
def __str__(self):
''' Returns a string representation of the context
:returns: A string representation of the context
'''
return "Remote Server Context(%s)" % self._client
def __iter__(self):
''' Iterater over the current collection of slave
contexts.
:returns: An iterator over the slave contexts
'''
# note, this may not include all slaves
return self.__slaves.iteritems()
def __contains__(self, slave):
''' Check if the given slave is in this list
:param slave: slave The slave to check for existance
:returns: True if the slave exists, False otherwise
'''
# we don't want to check the cache here as the
# slave may not exist yet or may not exist any
# more. The best thing to do is try and fail.
return True
def __setitem__(self, slave, context):
''' Used to set a new slave context
:param slave: The slave context to set
:param context: The new context to set for this slave
'''
raise NotImplementedException() # doesn't make sense here
def __delitem__(self, slave):
''' Wrapper used to access the slave context
:param slave: The slave context to remove
'''
raise NotImplementedException() # doesn't make sense here
def __getitem__(self, slave):
''' Used to get access to a slave context
:param slave: The slave context to get
:returns: The requested slave context
'''
if slave not in self.slaves:
self.slaves[slave] = RemoteSingleSlaveContext(self, slave)
return self.slaves[slave]
pymodbus/examples/contrib/tx-messages 0000644 0001755 0001755 00000013602 12607272152 020121 0 ustar debacle debacle # ------------------------------------------------------------
# What follows is a collection of encoded messages that can
# be used to test the message-parser. Simply uncomment the
# messages you want decoded and run the message parser with
# the given arguments. What follows is the listing of messages
# that are encoded in each format:
#
# - ReadHoldingRegistersRequest
# - ReadDiscreteInputsRequest
# - ReadInputRegistersRequest
# - ReadCoilsRequest
# - WriteMultipleCoilsRequest
# - WriteMultipleRegistersRequest
# - WriteSingleRegisterRequest
# - WriteSingleCoilRequest
# - ReadWriteMultipleRegistersRequest
# - ReadExceptionStatusRequest
# - GetCommEventCounterRequest
# - GetCommEventLogRequest
# - ReportSlaveIdRequest
# - ReadFileRecordRequest
# - WriteFileRecordRequest
# - MaskWriteRegisterRequest
# - ReadFifoQueueRequest
# - ReadDeviceInformationRequest
# - ReturnQueryDataRequest
# - RestartCommunicationsOptionRequest
# - ReturnDiagnosticRegisterRequest
# - ChangeAsciiInputDelimiterRequest
# - ForceListenOnlyModeRequest
# - ClearCountersRequest
# - ReturnBusMessageCountRequest
# - ReturnBusCommunicationErrorCountRequest
# - ReturnBusExceptionErrorCountRequest
# - ReturnSlaveMessageCountRequest
# - ReturnSlaveNoReponseCountRequest
# - ReturnSlaveNAKCountRequest
# - ReturnSlaveBusyCountRequest
# - ReturnSlaveBusCharacterOverrunCountRequest
# - ReturnIopOverrunCountRequest
# - ClearOverrunCountRequest
# - GetClearModbusPlusRequest
# ------------------------------------------------------------
# Modbus TCP Messages
# ------------------------------------------------------------
# [ MBAP Header ] [ Function Code] [ Data ]
# [ tid ][ pid ][ length ][ uid ]
# 2b 2b 2b 1b 1b Nb
#
# ./message-parser -b -p tcp -f messages
# ------------------------------------------------------------
#000100000006010300120008
#000100000006010200120008
#000100000006010400120008
#000100000006010100120008
#000100000008010f0012000801ff
#0001000000170110001200081000010001000100010001000100010001
#000100000006010600120001
#00010000000601050012ff00
#00010000001b011700120008000000081000010001000100010001000100010001
#0001000000020107
#000100000002010b
#000100000002010c
#0001000000020111
#000100000003011400
#000100000003011500
#00010000000801160012ffff0000
#00010000000401180012
#000100000005012b0e0100
#000100000006010800000000
#000100000006010800010000
#000100000006010800020000
#000100000006010800030000
#000100000006010800040000
#0001000000060108000a0000
#0001000000060108000b0000
#0001000000060108000c0000
#0001000000060108000d0000
#0001000000060108000e0000
#0001000000060108000f0000
#000100000006010800100000
#000100000006010800110000
#000100000006010800120000
#000100000006010800130000
#000100000006010800140000
#000100000006010800150000
# ------------------------------------------------------------
# Modbus RTU Messages
# ------------------------------------------------------------
# [Address ][ Function Code] [ Data ][ CRC ]
# 1b 1b Nb 2b
#
# ./message-parser -b -p rtu -f messages
# ------------------------------------------------------------
#010300120008e409
#010200120008d9c9
#01040012000851c9
#0101001200089dc9
#010f0012000801ff06d6
#0110001200081000010001000100010001000100010001d551
#010600120001e80f
#01050012ff002c3f
#011700120008000000081000010001000100010001000100010001e6f8
#010741e2
#010b41e7
#010c0025
#0111c02c
#0114002f00
#0115002e90
#01160012ffff00004e21
#0118001201d2
#012b0e01007077
#010800000000e00b
#010800010000b1cb
#01080002000041cb
#010800030000100b
#010800040000a1ca
#0108000a0000c009
#0108000b000091c9
#0108000c00002008
#0108000d000071c8
#0108000e000081c8
#0108000f0000d008
#010800100000e1ce
#010800110000b00e
#010800120000400e
#01080013000011ce
#010800140000a00f
#010800150000f1cf
# ------------------------------------------------------------
# Modbus ASCII Messages
# ------------------------------------------------------------
# [ Start ][Address ][ Function ][ Data ][ LRC ][ End ]
# 1c 2c 2c Nc 2c 2c
#
# ./message-parser -a -p ascii -f messages
# ------------------------------------------------------------
#:010300120008E2
#:010200120008E3
#:010400120008E1
#:010100120008E4
#:010F0012000801FFD6
#:0110001200081000010001000100010001000100010001BD
#:010600120001E6
#:01050012FF00E9
#:011700120008000000081000010001000100010001000100010001AE
#:0107F8
#:010BF4
#:010CF3
#:0111EE
#:011400EB
#:011500EA
#:01160012FFFF0000D9
#:01180012D5
#:012B0E0100C5
#:010800000000F7
#:010800010000F6
#:010800020000F5
#:010800030000F4
#:010800040000F3
#:0108000A0000ED
#:0108000B0000EC
#:0108000C0000EB
#:0108000D0000EA
#:0108000E0000E9
#:0108000F0000E8
#:010800100000E7
#:010800110000E6
#:010800120000E5
#:010800130000E4
#:010800140000E3
#:010800150000E2
# ------------------------------------------------------------
# Modbus Binary Messages
# ------------------------------------------------------------
# [ Start ][Address ][ Function ][ Data ][ CRC ][ End ]
# 1b 1b 1b Nb 2b 1b
#
# ./message-parser -b -p binary -f messages
# ------------------------------------------------------------
#7b010300120008e4097d
#7b010200120008d9c97d
#7b01040012000851c97d
#7b0101001200089dc97d
#7b010f0012000801ff06d67d
#7b0110001200081000010001000100010001000100010001d5517d
#7b010600120001e80f7d
#7b01050012ff002c3f7d
#7b011700120008000000081000010001000100010001000100010001e6f87d
#7b010741e27d
#7b010b41e77d
#7b010c00257d
#7b0111c02c7d
#7b0114002f007d
#7b0115002e907d
#7b01160012ffff00004e217d
#7b0118001201d27d
#7b012b0e010070777d
#7b010800000000e00b7d
#7b010800010000b1cb7d
#7b01080002000041cb7d
#7b010800030000100b7d
#7b010800040000a1ca7d
#7b0108000a0000c0097d
#7b0108000b000091c97d
#7b0108000c000020087d
#7b0108000d000071c87d
#7b0108000e000081c87d
#7b0108000f0000d0087d
#7b010800100000e1ce7d
#7b010800110000b00e7d
#7b010800120000400e7d
#7b01080013000011ce7d
#7b010800140000a00f7d
#7b010800150000f1cf7d
pymodbus/examples/contrib/message-parser.py 0000755 0001755 0001755 00000013502 12607272152 021230 0 ustar debacle debacle #!/usr/bin/env python
'''
Modbus Message Parser
--------------------------------------------------------------------------
The following is an example of how to parse modbus messages
using the supplied framers for a number of protocols:
* tcp
* ascii
* rtu
* binary
'''
#---------------------------------------------------------------------------#
# import needed libraries
#---------------------------------------------------------------------------#
import sys
import collections
import textwrap
from optparse import OptionParser
from pymodbus.utilities import computeCRC, computeLRC
from pymodbus.factory import ClientDecoder, ServerDecoder
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.transaction import ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer
from pymodbus.transaction import ModbusRtuFramer
#--------------------------------------------------------------------------#
# Logging
#--------------------------------------------------------------------------#
import logging
modbus_log = logging.getLogger("pymodbus")
#---------------------------------------------------------------------------#
# build a quick wrapper around the framers
#---------------------------------------------------------------------------#
class Decoder(object):
def __init__(self, framer, encode=False):
''' Initialize a new instance of the decoder
:param framer: The framer to use
:param encode: If the message needs to be encoded
'''
self.framer = framer
self.encode = encode
def decode(self, message):
''' Attempt to decode the supplied message
:param message: The messge to decode
'''
value = message if self.encode else message.encode('hex')
print "="*80
print "Decoding Message %s" % value
print "="*80
decoders = [
self.framer(ServerDecoder()),
self.framer(ClientDecoder()),
]
for decoder in decoders:
print "%s" % decoder.decoder.__class__.__name__
print "-"*80
try:
decoder.addToFrame(message)
if decoder.checkFrame():
decoder.advanceFrame()
decoder.processIncomingPacket(message, self.report)
else: self.check_errors(decoder, message)
except Exception, ex: self.check_errors(decoder, message)
def check_errors(self, decoder, message):
''' Attempt to find message errors
:param message: The message to find errors in
'''
pass
def report(self, message):
''' The callback to print the message information
:param message: The message to print
'''
print "%-15s = %s" % ('name', message.__class__.__name__)
for k,v in message.__dict__.iteritems():
if isinstance(v, dict):
print "%-15s =" % k
for kk,vv in v.items():
print " %-12s => %s" % (kk, vv)
elif isinstance(v, collections.Iterable):
print "%-15s =" % k
value = str([int(x) for x in v])
for line in textwrap.wrap(value, 60):
print "%-15s . %s" % ("", line)
else: print "%-15s = %s" % (k, hex(v))
print "%-15s = %s" % ('documentation', message.__doc__)
#---------------------------------------------------------------------------#
# and decode our message
#---------------------------------------------------------------------------#
def get_options():
''' A helper method to parse the command line options
:returns: The options manager
'''
parser = OptionParser()
parser.add_option("-p", "--parser",
help="The type of parser to use (tcp, rtu, binary, ascii)",
dest="parser", default="tcp")
parser.add_option("-D", "--debug",
help="Enable debug tracing",
action="store_true", dest="debug", default=False)
parser.add_option("-m", "--message",
help="The message to parse",
dest="message", default=None)
parser.add_option("-a", "--ascii",
help="The indicates that the message is ascii",
action="store_true", dest="ascii", default=True)
parser.add_option("-b", "--binary",
help="The indicates that the message is binary",
action="store_false", dest="ascii")
parser.add_option("-f", "--file",
help="The file containing messages to parse",
dest="file", default=None)
(opt, arg) = parser.parse_args()
if not opt.message and len(arg) > 0:
opt.message = arg[0]
return opt
def get_messages(option):
''' A helper method to generate the messages to parse
:param options: The option manager
:returns: The message iterator to parse
'''
if option.message:
if not option.ascii:
option.message = option.message.decode('hex')
yield option.message
elif option.file:
with open(option.file, "r") as handle:
for line in handle:
if line.startswith('#'): continue
if not option.ascii:
line = line.strip()
line = line.decode('hex')
yield line
def main():
''' The main runner function
'''
option = get_options()
if option.debug:
try:
modbus_log.setLevel(logging.DEBUG)
logging.basicConfig()
except Exception, e:
print "Logging is not supported on this system"
framer = lookup = {
'tcp': ModbusSocketFramer,
'rtu': ModbusRtuFramer,
'binary': ModbusBinaryFramer,
'ascii': ModbusAsciiFramer,
}.get(option.parser, ModbusSocketFramer)
decoder = Decoder(framer, option.ascii)
for message in get_messages(option):
decoder.decode(message)
if __name__ == "__main__":
main()
pymodbus/examples/contrib/libmodbus-client.py 0000755 0001755 0001755 00000042122 12607272152 021546 0 ustar debacle debacle #!/usr/bin/env python
'''
Libmodbus Protocol Wrapper
------------------------------------------------------------
What follows is an example wrapper of the libmodbus library
(http://libmodbus.org/documentation/) for use with pymodbus.
There are two utilities involved here:
* LibmodbusLevel1Client
This is simply a python wrapper around the c library. It is
mostly a clone of the pylibmodbus implementation, but I plan
on extending it to implement all the available protocol using
the raw execute methods.
* LibmodbusClient
This is just another modbus client that can be used just like
any other client in pymodbus.
For these to work, you must have `cffi` and `libmodbus-dev` installed:
sudo apt-get install libmodbus-dev
pip install cffi
'''
#--------------------------------------------------------------------------#
# import system libraries
#--------------------------------------------------------------------------#
from cffi import FFI
#--------------------------------------------------------------------------#
# import pymodbus libraries
#--------------------------------------------------------------------------#
from pymodbus.constants import Defaults
from pymodbus.exceptions import ModbusException
from pymodbus.client.common import ModbusClientMixin
from pymodbus.bit_read_message import ReadCoilsResponse, ReadDiscreteInputsResponse
from pymodbus.register_read_message import ReadHoldingRegistersResponse, ReadInputRegistersResponse
from pymodbus.register_read_message import ReadWriteMultipleRegistersResponse
from pymodbus.bit_write_message import WriteSingleCoilResponse, WriteMultipleCoilsResponse
from pymodbus.register_write_message import WriteSingleRegisterResponse, WriteMultipleRegistersResponse
#--------------------------------------------------------------------------------
# create the C interface
#--------------------------------------------------------------------------------
# * TODO add the protocol needed for the servers
#--------------------------------------------------------------------------------
compiler = FFI()
compiler.cdef("""
typedef struct _modbus modbus_t;
int modbus_connect(modbus_t *ctx);
int modbus_flush(modbus_t *ctx);
void modbus_close(modbus_t *ctx);
const char *modbus_strerror(int errnum);
int modbus_set_slave(modbus_t *ctx, int slave);
void modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
void modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
int modbus_write_bit(modbus_t *ctx, int coil_addr, int status);
int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data);
int modbus_write_register(modbus_t *ctx, int reg_addr, int value);
int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data);
int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb, uint16_t *dest);
int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask);
int modbus_send_raw_request(modbus_t *ctx, uint8_t *raw_req, int raw_req_length);
float modbus_get_float(const uint16_t *src);
void modbus_set_float(float f, uint16_t *dest);
modbus_t* modbus_new_tcp(const char *ip_address, int port);
modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
void modbus_free(modbus_t *ctx);
int modbus_receive(modbus_t *ctx, uint8_t *req);
int modbus_receive_from(modbus_t *ctx, int sockfd, uint8_t *req);
int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp);
""")
LIB = compiler.dlopen('modbus') # create our bindings
#--------------------------------------------------------------------------------
# helper utilites
#--------------------------------------------------------------------------------
def get_float(data):
return LIB.modbus_get_float(data)
def set_float(value, data):
LIB.modbus_set_float(value, data)
def cast_to_int16(data):
return int(compiler.cast('int16_t', data))
def cast_to_int32(data):
return int(compiler.cast('int32_t', data))
#--------------------------------------------------------------------------------
# level1 client
#--------------------------------------------------------------------------------
class LibmodbusLevel1Client(object):
''' A raw wrapper around the libmodbus c library. Feel free
to use it if you want increased performance and don't mind the
entire protocol not being implemented.
'''
@classmethod
def create_tcp_client(klass, host='127.0.0.1', port=Defaults.Port):
''' Create a TCP modbus client for the supplied parameters.
:param host: The host to connect to
:param port: The port to connect to on that host
:returns: A new level1 client
'''
client = LIB.modbus_new_tcp(host.encode(), port)
return klass(client)
@classmethod
def create_rtu_client(klass, **kwargs):
''' Create a TCP modbus client for the supplied parameters.
:param port: The serial port to attach to
:param stopbits: The number of stop bits to use
:param bytesize: The bytesize of the serial messages
:param parity: Which kind of parity to use
:param baudrate: The baud rate to use for the serial device
:returns: A new level1 client
'''
port = kwargs.get('port', '/dev/ttyS0')
baudrate = kwargs.get('baud', Defaults.Baudrate)
parity = kwargs.get('parity', Defaults.Parity)
bytesize = kwargs.get('bytesize', Defaults.Bytesize)
stopbits = kwargs.get('stopbits', Defaults.Stopbits)
client = LIB.modbus_new_rtu(port, baudrate, parity, bytesize, stopbits)
return klass(client)
def __init__(self, client):
''' Initalize a new instance of the LibmodbusLevel1Client. This
method should not be used, instead new instances should be created
using the two supplied factory methods:
* LibmodbusLevel1Client.create_rtu_client(...)
* LibmodbusLevel1Client.create_tcp_client(...)
:param client: The underlying client instance to operate with.
'''
self.client = client
self.slave = Defaults.UnitId
def set_slave(self, slave):
''' Set the current slave to operate against.
:param slave: The new slave to operate against
:returns: The resulting slave to operate against
'''
self.slave = self._execute(LIB.modbus_set_slave, slave)
return self.slave
def connect(self):
''' Attempt to connect to the client target.
:returns: True if successful, throws otherwise
'''
return (self.__execute(LIB.modbus_connect) == 0)
def flush(self):
''' Discards the existing bytes on the wire.
:returns: The number of flushed bytes, or throws
'''
return self.__execute(LIB.modbus_flush)
def close(self):
''' Closes and frees the underlying connection
and context structure.
:returns: Always True
'''
LIB.modbus_close(self.client)
LIB.modbus_free(self.client)
return True
def __execute(self, command, *args):
''' Run the supplied command against the currently
instantiated client with the supplied arguments. This
will make sure to correctly handle resulting errors.
:param command: The command to execute against the context
:param *args: The arguments for the given command
:returns: The result of the operation unless -1 which throws
'''
result = command(self.client, *args)
if result == -1:
message = LIB.modbus_strerror(compiler.errno)
raise ModbusException(compiler.string(message))
return result
def read_bits(self, address, count=1):
'''
:param address: The starting address to read from
:param count: The number of coils to read
:returns: The resulting bits
'''
result = compiler.new("uint8_t[]", count)
self.__execute(LIB.modbus_read_bits, address, count, result)
return result
def read_input_bits(self, address, count=1):
'''
:param address: The starting address to read from
:param count: The number of discretes to read
:returns: The resulting bits
'''
result = compiler.new("uint8_t[]", count)
self.__execute(LIB.modbus_read_input_bits, address, count, result)
return result
def write_bit(self, address, value):
'''
:param address: The starting address to write to
:param value: The value to write to the specified address
:returns: The number of written bits
'''
return self.__execute(LIB.modbus_write_bit, address, value)
def write_bits(self, address, values):
'''
:param address: The starting address to write to
:param values: The values to write to the specified address
:returns: The number of written bits
'''
count = len(values)
return self.__execute(LIB.modbus_write_bits, address, count, values)
def write_register(self, address, value):
'''
:param address: The starting address to write to
:param value: The value to write to the specified address
:returns: The number of written registers
'''
return self.__execute(LIB.modbus_write_register, address, value)
def write_registers(self, address, values):
'''
:param address: The starting address to write to
:param values: The values to write to the specified address
:returns: The number of written registers
'''
count = len(values)
return self.__execute(LIB.modbus_write_registers, address, count, values)
def read_registers(self, address, count=1):
'''
:param address: The starting address to read from
:param count: The number of registers to read
:returns: The resulting read registers
'''
result = compiler.new("uint16_t[]", count)
self.__execute(LIB.modbus_read_registers, address, count, result)
return result
def read_input_registers(self, address, count=1):
'''
:param address: The starting address to read from
:param count: The number of registers to read
:returns: The resulting read registers
'''
result = compiler.new("uint16_t[]", count)
self.__execute(LIB.modbus_read_input_registers, address, count, result)
return result
def read_and_write_registers(self, read_address, read_count, write_address, write_registers):
'''
:param read_address: The address to start reading from
:param read_count: The number of registers to read from address
:param write_address: The address to start writing to
:param write_registers: The registers to write to the specified address
:returns: The resulting read registers
'''
write_count = len(write_registers)
read_result = compiler.new("uint16_t[]", read_count)
self.__execute(LIB.modbus_write_and_read_registers,
write_address, write_count, write_registers,
read_address, read_count, read_result)
return read_result
#--------------------------------------------------------------------------------
# level2 client
#--------------------------------------------------------------------------------
class LibmodbusClient(ModbusClientMixin):
''' A facade around the raw level 1 libmodbus client
that implements the pymodbus protocol on top of the lower level
client.
'''
#-----------------------------------------------------------------------#
# these are used to convert from the pymodbus request types to the
# libmodbus operations (overloaded operator).
#-----------------------------------------------------------------------#
__methods = {
'ReadCoilsRequest' : lambda c, r: c.read_bits(r.address, r.count),
'ReadDiscreteInputsRequest' : lambda c, r: c.read_input_bits(r.address, r.count),
'WriteSingleCoilRequest' : lambda c, r: c.write_bit(r.address, r.value),
'WriteMultipleCoilsRequest' : lambda c, r: c.write_bits(r.address, r.values),
'WriteSingleRegisterRequest' : lambda c, r: c.write_register(r.address, r.value),
'WriteMultipleRegistersRequest' : lambda c, r: c.write_registers(r.address, r.values),
'ReadHoldingRegistersRequest' : lambda c, r: c.read_registers(r.address, r.count),
'ReadInputRegistersRequest' : lambda c, r: c.read_input_registers(r.address, r.count),
'ReadWriteMultipleRegistersRequest' : lambda c, r: c.read_and_write_registers(r.read_address, r.read_count, r.write_address, r.write_registers),
}
#-----------------------------------------------------------------------#
# these are used to convert from the libmodbus result to the
# pymodbus response type
#-----------------------------------------------------------------------#
__adapters = {
'ReadCoilsRequest' : lambda tx, rx: ReadCoilsResponse(list(rx)),
'ReadDiscreteInputsRequest' : lambda tx, rx: ReadDiscreteInputsResponse(list(rx)),
'WriteSingleCoilRequest' : lambda tx, rx: WriteSingleCoilResponse(tx.address, rx),
'WriteMultipleCoilsRequest' : lambda tx, rx: WriteMultipleCoilsResponse(tx.address, rx),
'WriteSingleRegisterRequest' : lambda tx, rx: WriteSingleRegisterResponse(tx.address, rx),
'WriteMultipleRegistersRequest' : lambda tx, rx: WriteMultipleRegistersResponse(tx.address, rx),
'ReadHoldingRegistersRequest' : lambda tx, rx: ReadHoldingRegistersResponse(list(rx)),
'ReadInputRegistersRequest' : lambda tx, rx: ReadInputRegistersResponse(list(rx)),
'ReadWriteMultipleRegistersRequest' : lambda tx, rx: ReadWriteMultipleRegistersResponse(list(rx)),
}
def __init__(self, client):
''' Initalize a new instance of the LibmodbusClient. This should
be initialized with one of the LibmodbusLevel1Client instances:
* LibmodbusLevel1Client.create_rtu_client(...)
* LibmodbusLevel1Client.create_tcp_client(...)
:param client: The underlying client instance to operate with.
'''
self.client = client
#-----------------------------------------------------------------------#
# We use the client mixin to implement the api methods which are all
# forwarded to this method. It is implemented using the previously
# defined lookup tables. Any method not defined simply throws.
#-----------------------------------------------------------------------#
def execute(self, request):
''' Execute the supplied request against the server.
:param request: The request to process
:returns: The result of the request execution
'''
if self.client.slave != request.unit_id:
self.client.set_slave(request.unit_id)
method = request.__class__.__name__
operation = self.__methods.get(method, None)
adapter = self.__adapters.get(method, None)
if not operation or not adapter:
raise NotImplementedException("Method not implemented: " + name)
response = operation(self.client, request)
return adapter(request, response)
#-----------------------------------------------------------------------#
# Other methods can simply be forwarded using the decorator pattern
#-----------------------------------------------------------------------#
def connect(self): return self.client.connect()
def close(self): return self.client.close()
#-----------------------------------------------------------------------#
# magic methods
#-----------------------------------------------------------------------#
def __enter__(self):
''' Implement the client with enter block
:returns: The current instance of the client
'''
self.client.connect()
return self
def __exit__(self, klass, value, traceback):
''' Implement the client with exit block '''
self.client.close()
#--------------------------------------------------------------------------#
# main example runner
#--------------------------------------------------------------------------#
if __name__ == '__main__':
# create our low level client
host = '127.0.0.1'
port = 502
protocol = LibmodbusLevel1Client.create_tcp_client(host, port)
# operate with our high level client
with LibmodbusClient(protocol) as client:
registers = client.write_registers(0, [13, 12, 11])
print registers
registers = client.read_holding_registers(0, 10)
print registers.registers
pymodbus/examples/contrib/bcd-payload.py 0000644 0001755 0001755 00000015254 12607272152 020474 0 ustar debacle debacle '''
Modbus BCD Payload Builder
-----------------------------------------------------------
This is an example of building a custom payload builder
that can be used in the pymodbus library. Below is a
simple binary coded decimal builder and decoder.
'''
from struct import pack, unpack
from pymodbus.constants import Endian
from pymodbus.interfaces import IPayloadBuilder
from pymodbus.utilities import pack_bitstring
from pymodbus.utilities import unpack_bitstring
from pymodbus.exceptions import ParameterException
def convert_to_bcd(decimal):
''' Converts a decimal value to a bcd value
:param value: The decimal value to to pack into bcd
:returns: The number in bcd form
'''
place, bcd = 0, 0
while decimal > 0:
nibble = decimal % 10
bcd += nibble << place
decimal /= 10
place += 4
return bcd
def convert_from_bcd(bcd):
''' Converts a bcd value to a decimal value
:param value: The value to unpack from bcd
:returns: The number in decimal form
'''
place, decimal = 1, 0
while bcd > 0:
nibble = bcd & 0xf
decimal += nibble * place
bcd >>= 4
place *= 10
return decimal
def count_bcd_digits(bcd):
''' Count the number of digits in a bcd value
:param bcd: The bcd number to count the digits of
:returns: The number of digits in the bcd string
'''
count = 0
while bcd > 0:
count += 1
bcd >>= 4
return count
class BcdPayloadBuilder(IPayloadBuilder):
'''
A utility that helps build binary coded decimal payload
messages to be written with the various modbus messages.
example::
builder = BcdPayloadBuilder()
builder.add_number(1)
builder.add_number(int(2.234 * 1000))
payload = builder.build()
'''
def __init__(self, payload=None, endian=Endian.Little):
''' Initialize a new instance of the payload builder
:param payload: Raw payload data to initialize with
:param endian: The endianess of the payload
'''
self._payload = payload or []
self._endian = endian
def __str__(self):
''' Return the payload buffer as a string
:returns: The payload buffer as a string
'''
return ''.join(self._payload)
def reset(self):
''' Reset the payload buffer
'''
self._payload = []
def build(self):
''' Return the payload buffer as a list
This list is two bytes per element and can
thus be treated as a list of registers.
:returns: The payload buffer as a list
'''
string = str(self)
length = len(string)
string = string + ('\x00' * (length % 2))
return [string[i:i+2] for i in xrange(0, length, 2)]
def add_bits(self, values):
''' Adds a collection of bits to be encoded
If these are less than a multiple of eight,
they will be left padded with 0 bits to make
it so.
:param value: The value to add to the buffer
'''
value = pack_bitstring(values)
self._payload.append(value)
def add_number(self, value, size=None):
''' Adds any 8bit numeric type to the buffer
:param value: The value to add to the buffer
'''
encoded = []
value = convert_to_bcd(value)
size = size or count_bcd_digits(value)
while size > 0:
nibble = value & 0xf
encoded.append(pack('B', nibble))
value >>= 4
size -= 1
self._payload.extend(encoded)
def add_string(self, value):
''' Adds a string to the buffer
:param value: The value to add to the buffer
'''
self._payload.append(value)
class BcdPayloadDecoder(object):
'''
A utility that helps decode binary coded decimal payload
messages from a modbus reponse message. What follows is
a simple example::
decoder = BcdPayloadDecoder(payload)
first = decoder.decode_int(2)
second = decoder.decode_int(5) / 100
'''
def __init__(self, payload):
''' Initialize a new payload decoder
:param payload: The payload to decode with
'''
self._payload = payload
self._pointer = 0x00
@staticmethod
def fromRegisters(registers, endian=Endian.Little):
''' Initialize a payload decoder with the result of
reading a collection of registers from a modbus device.
The registers are treated as a list of 2 byte values.
We have to do this because of how the data has already
been decoded by the rest of the library.
:param registers: The register results to initialize with
:param endian: The endianess of the payload
:returns: An initialized PayloadDecoder
'''
if isinstance(registers, list): # repack into flat binary
payload = ''.join(pack('>H', x) for x in registers)
return BinaryPayloadDecoder(payload, endian)
raise ParameterException('Invalid collection of registers supplied')
@staticmethod
def fromCoils(coils, endian=Endian.Little):
''' Initialize a payload decoder with the result of
reading a collection of coils from a modbus device.
The coils are treated as a list of bit(boolean) values.
:param coils: The coil results to initialize with
:param endian: The endianess of the payload
:returns: An initialized PayloadDecoder
'''
if isinstance(coils, list):
payload = pack_bitstring(coils)
return BinaryPayloadDecoder(payload, endian)
raise ParameterException('Invalid collection of coils supplied')
def reset(self):
''' Reset the decoder pointer back to the start
'''
self._pointer = 0x00
def decode_int(self, size=1):
''' Decodes a int or long from the buffer
'''
self._pointer += size
handle = self._payload[self._pointer - size:self._pointer]
return convert_from_bcd(handle)
def decode_bits(self):
''' Decodes a byte worth of bits from the buffer
'''
self._pointer += 1
handle = self._payload[self._pointer - 1:self._pointer]
return unpack_bitstring(handle)
def decode_string(self, size=1):
''' Decodes a string from the buffer
:param size: The size of the string to decode
'''
self._pointer += size
return self._payload[self._pointer - size:self._pointer]
#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
__all__ = ["BcdPayloadBuilder", "BcdPayloadDecoder"]
pymodbus/examples/contrib/requirements.txt 0000644 0001755 0001755 00000000350 12607272152 021216 0 ustar debacle debacle # -------------------------------------------------------------------
# if you want to use the custom data stores, uncomment these
# -------------------------------------------------------------------
SQLAlchemy==0.7.9
redis==2.6.2
pymodbus/examples/contrib/database-datastore.py 0000644 0001755 0001755 00000015010 12607272152 022033 0 ustar debacle debacle 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
pymodbus/examples/contrib/modbus-simulator.py 0000644 0001755 0001755 00000010335 12607272152 021616 0 ustar debacle debacle #!/usr/bin/env python
'''
An example of creating a fully implemented modbus server
with read/write data as well as user configurable base data
'''
import pickle
from optparse import OptionParser
from twisted.internet import reactor
from pymodbus.server.async import StartTcpServer
from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext
#--------------------------------------------------------------------------#
# Logging
#--------------------------------------------------------------------------#
import logging
logging.basicConfig()
server_log = logging.getLogger("pymodbus.server")
protocol_log = logging.getLogger("pymodbus.protocol")
#---------------------------------------------------------------------------#
# Extra Global Functions
#---------------------------------------------------------------------------#
# These are extra helper functions that don't belong in a class
#---------------------------------------------------------------------------#
import getpass
def root_test():
''' Simple test to see if we are running as root '''
return True # removed for the time being as it isn't portable
#return getpass.getuser() == "root"
#--------------------------------------------------------------------------#
# Helper Classes
#--------------------------------------------------------------------------#
class ConfigurationException(Exception):
''' Exception for configuration error '''
def __init__(self, string):
''' Initializes the ConfigurationException instance
:param string: The message to append to the exception
'''
Exception.__init__(self, string)
self.string = string
def __str__(self):
''' Builds a representation of the object
:returns: A string representation of the object
'''
return 'Configuration Error: %s' % self.string
class Configuration:
'''
Class used to parse configuration file and create and modbus
datastore.
The format of the configuration file is actually just a
python pickle, which is a compressed memory dump from
the scraper.
'''
def __init__(self, config):
'''
Trys to load a configuration file, lets the file not
found exception fall through
:param config: The pickled datastore
'''
try:
self.file = open(config, "r")
except Exception:
raise ConfigurationException("File not found %s" % config)
def parse(self):
''' Parses the config file and creates a server context
'''
handle = pickle.load(self.file)
try: # test for existance, or bomb
dsd = handle['di']
csd = handle['ci']
hsd = handle['hr']
isd = handle['ir']
except Exception:
raise ConfigurationException("Invalid Configuration")
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
return ModbusServerContext(slaves=slave)
#--------------------------------------------------------------------------#
# Main start point
#--------------------------------------------------------------------------#
def main():
''' Server launcher '''
parser = OptionParser()
parser.add_option("-c", "--conf",
help="The configuration file to load",
dest="file")
parser.add_option("-D", "--debug",
help="Turn on to enable tracing",
action="store_true", dest="debug", default=False)
(opt, arg) = parser.parse_args()
# enable debugging information
if opt.debug:
try:
server_log.setLevel(logging.DEBUG)
protocol_log.setLevel(logging.DEBUG)
except Exception, e:
print "Logging is not supported on this system"
# parse configuration file and run
try:
conf = Configuration(opt.file)
StartTcpServer(context=conf.parse())
except ConfigurationException, err:
print err
parser.print_help()
#---------------------------------------------------------------------------#
# Main jumper
#---------------------------------------------------------------------------#
if __name__ == "__main__":
if root_test():
main()
else: print "This script must be run as root!"
pymodbus/examples/contrib/modicon-payload.py 0000644 0001755 0001755 00000021722 12607272152 021371 0 ustar debacle debacle '''
Modbus Modicon Payload Builder
-----------------------------------------------------------
This is an example of building a custom payload builder
that can be used in the pymodbus library. Below is a
simple modicon encoded builder and decoder.
'''
from struct import pack, unpack
from pymodbus.constants import Endian
from pymodbus.interfaces import IPayloadBuilder
from pymodbus.utilities import pack_bitstring
from pymodbus.utilities import unpack_bitstring
from pymodbus.exceptions import ParameterException
class ModiconPayloadBuilder(IPayloadBuilder):
'''
A utility that helps build modicon encoded payload
messages to be written with the various modbus messages.
example::
builder = ModiconPayloadBuilder()
builder.add_8bit_uint(1)
builder.add_16bit_uint(2)
payload = builder.build()
'''
def __init__(self, payload=None, endian=Endian.Little):
''' Initialize a new instance of the payload builder
:param payload: Raw payload data to initialize with
:param endian: The endianess of the payload
'''
self._payload = payload or []
self._endian = endian
def __str__(self):
''' Return the payload buffer as a string
:returns: The payload buffer as a string
'''
return ''.join(self._payload)
def reset(self):
''' Reset the payload buffer
'''
self._payload = []
def build(self):
''' Return the payload buffer as a list
This list is two bytes per element and can
thus be treated as a list of registers.
:returns: The payload buffer as a list
'''
string = str(self)
length = len(string)
string = string + ('\x00' * (length % 2))
return [string[i:i+2] for i in xrange(0, length, 2)]
def add_bits(self, values):
''' Adds a collection of bits to be encoded
If these are less than a multiple of eight,
they will be left padded with 0 bits to make
it so.
:param value: The value to add to the buffer
'''
value = pack_bitstring(values)
self._payload.append(value)
def add_8bit_uint(self, value):
''' Adds a 8 bit unsigned int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'B'
self._payload.append(pack(fstring, value))
def add_16bit_uint(self, value):
''' Adds a 16 bit unsigned int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'H'
self._payload.append(pack(fstring, value))
def add_32bit_uint(self, value):
''' Adds a 32 bit unsigned int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'I'
handle = pack(fstring, value)
handle = handle[2:] + handle[:2]
self._payload.append(handle)
def add_8bit_int(self, value):
''' Adds a 8 bit signed int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'b'
self._payload.append(pack(fstring, value))
def add_16bit_int(self, value):
''' Adds a 16 bit signed int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'h'
self._payload.append(pack(fstring, value))
def add_32bit_int(self, value):
''' Adds a 32 bit signed int to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'i'
handle = pack(fstring, value)
handle = handle[2:] + handle[:2]
self._payload.append(handle)
def add_32bit_float(self, value):
''' Adds a 32 bit float to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 'f'
handle = pack(fstring, value)
handle = handle[2:] + handle[:2]
self._payload.append(handle)
def add_string(self, value):
''' Adds a string to the buffer
:param value: The value to add to the buffer
'''
fstring = self._endian + 's'
for c in value:
self._payload.append(pack(fstring, c))
class ModiconPayloadDecoder(object):
'''
A utility that helps decode modicon encoded payload
messages from a modbus reponse message. What follows is
a simple example::
decoder = ModiconPayloadDecoder(payload)
first = decoder.decode_8bit_uint()
second = decoder.decode_16bit_uint()
'''
def __init__(self, payload):
''' Initialize a new payload decoder
:param payload: The payload to decode with
'''
self._payload = payload
self._pointer = 0x00
@staticmethod
def fromRegisters(registers, endian=Endian.Little):
''' Initialize a payload decoder with the result of
reading a collection of registers from a modbus device.
The registers are treated as a list of 2 byte values.
We have to do this because of how the data has already
been decoded by the rest of the library.
:param registers: The register results to initialize with
:param endian: The endianess of the payload
:returns: An initialized PayloadDecoder
'''
if isinstance(registers, list): # repack into flat binary
payload = ''.join(pack('>H', x) for x in registers)
return ModiconPayloadDecoder(payload, endian)
raise ParameterException('Invalid collection of registers supplied')
@staticmethod
def fromCoils(coils, endian=Endian.Little):
''' Initialize a payload decoder with the result of
reading a collection of coils from a modbus device.
The coils are treated as a list of bit(boolean) values.
:param coils: The coil results to initialize with
:param endian: The endianess of the payload
:returns: An initialized PayloadDecoder
'''
if isinstance(coils, list):
payload = pack_bitstring(coils)
return ModiconPayloadDecoder(payload, endian)
raise ParameterException('Invalid collection of coils supplied')
def reset(self):
''' Reset the decoder pointer back to the start
'''
self._pointer = 0x00
def decode_8bit_uint(self):
''' Decodes a 8 bit unsigned int from the buffer
'''
self._pointer += 1
fstring = self._endian + 'B'
handle = self._payload[self._pointer - 1:self._pointer]
return unpack(fstring, handle)[0]
def decode_16bit_uint(self):
''' Decodes a 16 bit unsigned int from the buffer
'''
self._pointer += 2
fstring = self._endian + 'H'
handle = self._payload[self._pointer - 2:self._pointer]
return unpack(fstring, handle)[0]
def decode_32bit_uint(self):
''' Decodes a 32 bit unsigned int from the buffer
'''
self._pointer += 4
fstring = self._endian + 'I'
handle = self._payload[self._pointer - 4:self._pointer]
handle = handle[2:] + handle[:2]
return unpack(fstring, handle)[0]
def decode_8bit_int(self):
''' Decodes a 8 bit signed int from the buffer
'''
self._pointer += 1
fstring = self._endian + 'b'
handle = self._payload[self._pointer - 1:self._pointer]
return unpack(fstring, handle)[0]
def decode_16bit_int(self):
''' Decodes a 16 bit signed int from the buffer
'''
self._pointer += 2
fstring = self._endian + 'h'
handle = self._payload[self._pointer - 2:self._pointer]
return unpack(fstring, handle)[0]
def decode_32bit_int(self):
''' Decodes a 32 bit signed int from the buffer
'''
self._pointer += 4
fstring = self._endian + 'i'
handle = self._payload[self._pointer - 4:self._pointer]
handle = handle[2:] + handle[:2]
return unpack(fstring, handle)[0]
def decode_32bit_float(self, size=1):
''' Decodes a float from the buffer
'''
self._pointer += 4
fstring = self._endian + 'f'
handle = self._payload[self._pointer - 4:self._pointer]
handle = handle[2:] + handle[:2]
return unpack(fstring, handle)[0]
def decode_bits(self):
''' Decodes a byte worth of bits from the buffer
'''
self._pointer += 1
handle = self._payload[self._pointer - 1:self._pointer]
return unpack_bitstring(handle)
def decode_string(self, size=1):
''' Decodes a string from the buffer
:param size: The size of the string to decode
'''
self._pointer += size
return self._payload[self._pointer - size:self._pointer]
#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
__all__ = ["BcdPayloadBuilder", "BcdPayloadDecoder"]
pymodbus/examples/contrib/modbus_saver.py 0000644 0001755 0001755 00000012307 12607272152 021002 0 ustar debacle debacle '''
These are a collection of helper methods that can be
used to save a modbus server context to file for backup,
checkpointing, or any other purpose. There use is very
simple::
context = server.context
saver = JsonDatastoreSaver(context)
saver.save()
These can then be re-opened by the parsers in the
modbus_mapping module. At the moment, the supported
output formats are:
* csv
* json
* xml
To implement your own, simply subclass ModbusDatastoreSaver
and supply the needed callbacks for your given format:
* handle_store_start(self, store)
* handle_store_end(self, store)
* handle_slave_start(self, slave)
* handle_slave_end(self, slave)
* handle_save_start(self)
* handle_save_end(self)
'''
import csv
import json
import xml.etree.ElementTree as xml
class ModbusDatastoreSaver(object):
''' An abstract base class that can be used to implement
a persistance format for the modbus server context. In
order to use it, just complete the neccessary callbacks
(SAX style) that your persistance format needs.
'''
def __init__(self, context, path=None):
''' Initialize a new instance of the saver.
:param context: The modbus server context
:param path: The output path to save to
'''
self.context = context
self.path = path or 'modbus-context-dump'
def save(self):
''' The main runner method to save the
context to file which calls the various
callbacks which the sub classes will
implement.
'''
with open(self.path, 'w') as self.file_handle:
self.handle_save_start()
for slave_name, slave in self.context:
self.handle_slave_start(slave_name)
for store_name, store in slave.store.iteritems():
self.handle_store_start(store_name)
self.handle_store_values(iter(store))
self.handle_store_end(store_name)
self.handle_slave_end(slave_name)
self.handle_save_end()
#------------------------------------------------------------
# predefined state machine callbacks
#------------------------------------------------------------
def handle_save_start(self): pass
def handle_store_start(self, store): pass
def handle_store_end(self, store): pass
def handle_slave_start(self, slave): pass
def handle_slave_end(self, slave): pass
def handle_save_end(self): pass
#----------------------------------------------------------------
# Implementations of the data store savers
#----------------------------------------------------------------
class JsonDatastoreSaver(ModbusDatastoreSaver):
''' An implementation of the modbus datastore saver
that persists the context as a json document.
'''
STORE_NAMES = {
'i' : 'input-registers',
'd' : 'discretes',
'h' : 'holding-registers',
'c' : 'coils',
}
def handle_save_start(self):
self._context = dict()
def handle_slave_start(self, slave):
self._context[hex(slave)] = self._slave = dict()
def handle_store_start(self, store):
self._store = self.STORE_NAMES[store]
def handle_store_values(self, values):
self._slave[self._store] = dict(values)
def handle_save_end(self):
json.dump(self._context, self.file_handle)
class CsvDatastoreSaver(ModbusDatastoreSaver):
''' An implementation of the modbus datastore saver
that persists the context as a csv document.
'''
NEWLINE = '\r\n'
HEADER = "slave,store,address,value" + NEWLINE
STORE_NAMES = {
'i' : 'i',
'd' : 'd',
'h' : 'h',
'c' : 'c',
}
def handle_save_start(self):
self.file_handle.write(self.HEADER)
def handle_slave_start(self, slave):
self._line = [str(slave)]
def handle_store_start(self, store):
self._line.append(self.STORE_NAMES[store])
def handle_store_values(self, values):
self.file_handle.writelines(self.handle_store_value(values))
def handle_store_end(self, store):
self._line.pop()
def handle_store_value(self, values):
for a, v in values:
yield ','.join(self._line + [str(a), str(v)]) + self.NEWLINE
class XmlDatastoreSaver(ModbusDatastoreSaver):
''' An implementation of the modbus datastore saver
that persists the context as a XML document.
'''
STORE_NAMES = {
'i' : 'input-registers',
'd' : 'discretes',
'h' : 'holding-registers',
'c' : 'coils',
}
def handle_save_start(self):
self._context = xml.Element("context")
self._root = xml.ElementTree(self._context)
def handle_slave_start(self, slave):
self._slave = xml.SubElement(self._context, "slave")
self._slave.set("id", str(slave))
def handle_store_start(self, store):
self._store = xml.SubElement(self._slave, "store")
self._store.set("function", self.STORE_NAMES[store])
def handle_store_values(self, values):
for address, value in values:
entry = xml.SubElement(self._store, "entry")
entry.text = str(value)
entry.set("address", str(address))
def handle_save_end(self):
self._root.write(self.file_handle)
pymodbus/examples/contrib/serial-forwarder.py 0000755 0001755 0001755 00000003163 12607272152 021564 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Synchronous Serial Forwarder
--------------------------------------------------------------------------
We basically set the context for the tcp serial server to be that of a
serial client! This is just an example of how clever you can be with
the data context (basically anything can become a modbus device).
'''
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.server.sync import StartTcpServer as StartServer
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
from pymodbus.datastore.remote import RemoteSlaveContext
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# initialize the datastore(serial client)
#---------------------------------------------------------------------------#
client = ModbusClient(method='ascii', port='/dev/pts/14')
store = RemoteSlaveContext(client)
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
StartServer(context)
pymodbus/examples/functional/ 0000755 0001755 0001755 00000000000 12607272152 016436 5 ustar debacle debacle pymodbus/examples/functional/synchronous-tcp-client.py 0000644 0001755 0001755 00000001461 12607272152 023444 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from base_runner import Runner
class SynchronousTcpClient(Runner, unittest.TestCase):
'''
These are the integration tests for the synchronous
tcp client.
'''
def setUp(self):
''' Initializes the test environment '''
self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"])
self.client = ModbusClient(port=12345)
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/asynchronous-ascii-client.py 0000644 0001755 0001755 00000001424 12607272152 024106 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.async import ModbusSerialClient as ModbusClient
from base_runner import Runner
class AsynchronousAsciiClient(Runner, unittest.TestCase):
'''
These are the integration tests for the asynchronous
serial ascii client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
self.client = ModbusClient(method='ascii')
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/remote-slave-context.py 0000644 0001755 0001755 00000001775 12607272152 023107 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.datastore.remote import RemoteSlaveContext
from base_context import ContextRunner
class RemoteSlaveContextTest(ContextRunner, unittest.TestCase):
'''
These are the integration tests for using the redis
slave context.
'''
def setUp(self):
''' Initializes the test environment '''
self.context = RemoteSlaveContext(client=None) # for the log statment
self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"])
self.client = ModbusTcpClient(port=12345)
self.context = RemoteSlaveContext(client=self.client)
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/synchronous-ascii-client.py 0000644 0001755 0001755 00000001744 12607272152 023752 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
from base_runner import Runner
class SynchronousAsciiClient(Runner, unittest.TestCase):
'''
These are the integration tests for the synchronous
serial ascii client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
# "../tools/nullmodem/linux/run",
self.initialize(["../tools/reference/diagslave", "-m", "ascii", "/dev/pts/14"])
self.client = ModbusClient(method='ascii', timeout=0.2, port='/dev/pts/13')
self.client.connect()
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
super(Runner, self).tearDown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/base_runner.py 0000644 0001755 0001755 00000006077 12607272152 021325 0 ustar debacle debacle import os
import time
from subprocess import Popen as execute
from twisted.internet.defer import Deferred
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
log = logging.getLogger(__name__)
class Runner(object):
'''
This is the base runner class for all the integration tests
'''
def initialize(self, service):
''' Initializes the test environment '''
self.fnull = open(os.devnull, 'w')
self.server = execute(service, stdout=self.fnull, stderr=self.fnull)
log.debug("%s service started: %s", service, self.server.pid)
time.sleep(0.2)
def shutdown(self):
''' Cleans up the test environment '''
self.server.kill()
self.fnull.close()
log.debug("service stopped")
def testReadWriteCoil(self):
rq = self.client.write_coil(1, True)
rr = self.client.read_coils(1,1)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.bits[0] == True)
def testReadWriteCoils(self):
rq = self.client.write_coils(1, [True]*8)
rr = self.client.read_coils(1,8)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.bits == [True]*8)
def testReadWriteDiscreteRegisters(self):
rq = self.client.write_coils(1, [False]*8)
rr = self.client.read_discrete_inputs(1,8)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.bits == [False]*8)
def testReadWriteHoldingRegisters(self):
rq = self.client.write_register(1, 10)
rr = self.client.read_holding_registers(1,1)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.registers[0] == 10)
def testReadWriteInputRegisters(self):
rq = self.client.write_registers(1, [10]*8)
rr = self.client.read_input_registers(1,8)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.registers == [10]*8)
def testReadWriteRegistersTogether(self):
arguments = {
'read_address': 1,
'read_count': 8,
'write_address': 1,
'write_registers': [20]*8,
}
rq = self.client.readwrite_registers(**arguments)
rr = self.client.read_input_registers(1,8)
self.__validate(rq, lambda r: r.function_code < 0x80)
self.__validate(rr, lambda r: r.registers == [20]*8)
def __validate(self, result, test):
''' Validate the result whether it is a result or a deferred.
:param result: The result to __validate
:param callback: The test to __validate
'''
if isinstance(result, Deferred):
deferred.callback(lambda : self.assertTrue(test(result)))
deferred.errback(lambda _: self.assertTrue(False))
else: self.assertTrue(test(result))
pymodbus/examples/functional/__init__.py 0000644 0001755 0001755 00000000000 12607272152 020535 0 ustar debacle debacle pymodbus/examples/functional/README.rst 0000644 0001755 0001755 00000001776 12607272152 020140 0 ustar debacle debacle ============================================================================
Pymodbus Functional Tests
============================================================================
Modbus Clients
---------------------------------------------------------------------------
The following can be run to validate the pymodbus clients against a running
modbus instance. For these tests, the following are used as references::
* jamod
* modpoll
Modbus Servers
---------------------------------------------------------------------------
The following can be used to create a null modem loopback for testing the
serial implementations::
* tty0tty (linux)
* com0com (windows)
Specialized Datastores
---------------------------------------------------------------------------
The following can be run to validate the pymodbus specializes datastores.
For these tests, the following are used as references:
* sqlite (for the database datastore)
* redis (for the redis datastore)
* modpoll (for the remote slave datastore)
pymodbus/examples/functional/redis-slave-context.py 0000644 0001755 0001755 00000001610 12607272152 022706 0 ustar debacle debacle #!/usr/bin/env python
import unittest
import os
from subprocess import Popen as execute
from pymodbus.datastore.modredis import RedisSlaveContext
from base_context import ContextRunner
class RedisSlaveContextTest(ContextRunner, unittest.TestCase):
'''
These are the integration tests for using the redis
slave context.
'''
def setUp(self):
''' Initializes the test environment '''
self.context = RedisSlaveContext() # the redis client will block, so no wait needed
self.initialize("redis-server")
def tearDown(self):
''' Cleans up the test environment '''
self.server.kill()
self.fnull.close()
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/asynchronous-udp-client.py 0000644 0001755 0001755 00000001406 12607272152 023606 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusUdpClient as ModbusClient
from base_runner import Runner
class AsynchronousUdpClient(Runner, unittest.TestCase):
'''
These are the integration tests for the asynchronous
udp client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
self.client = ModbusClient()
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
super(Runner, self).tearDown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/memory-slave-context.py 0000755 0001755 0001755 00000002021 12607272152 023110 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.datastore.context import ModbusSlaveContext
from pymodbus.datastore.store import ModbusSequentialDataBlock
from base_context import ContextRunner
class MemorySlaveContextTest(ContextRunner, unittest.TestCase):
'''
These are the integration tests for using the in memory
slave context.
'''
def setUp(self):
''' Initializes the test environment '''
self.context = ModbusSlaveContext(**{
'di' : ModbusSequentialDataBlock(0, [0]*100),
'co' : ModbusSequentialDataBlock(0, [0]*100),
'ir' : ModbusSequentialDataBlock(0, [0]*100),
'hr' : ModbusSequentialDataBlock(0, [0]*100)})
self.initialize()
def tearDown(self):
''' Cleans up the test environment '''
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/database-slave-context.py 0000644 0001755 0001755 00000001704 12607272152 023350 0 ustar debacle debacle #!/usr/bin/env python
import unittest, os
from pymodbus.datastore.database import DatabaseSlaveContext
from base_context import ContextRunner
class DatabaseSlaveContextTest(ContextRunner, unittest.TestCase):
'''
These are the integration tests for using the redis
slave context.
'''
__database = 'sqlite:///pymodbus-test.db'
def setUp(self):
''' Initializes the test environment '''
path = './' + self.__database.split('///')[1]
if os.path.exists(path): os.remove(path)
self.context = DatabaseSlaveContext(database=self.__database)
self.initialize()
def tearDown(self):
''' Cleans up the test environment '''
self.context._connection.close()
self.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/asynchronous-tcp-client.py 0000644 0001755 0001755 00000002216 12607272152 023604 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from twisted.internet import reactor, protocol
from pymodbus.constants import Defaults
from pymodbus.client.async import ModbusClientProtocol
from base_runner import Runner
class AsynchronousTcpClient(Runner, unittest.TestCase):
'''
These are the integration tests for the asynchronous
tcp client.
'''
def setUp(self):
''' Initializes the test environment '''
def _callback(client): self.client = client
self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"])
defer = protocol.ClientCreator(reactor, ModbusClientProtocol
).connectTCP("localhost", Defaults.Port)
defer.addCallback(_callback)
reactor.run()
def tearDown(self):
''' Cleans up the test environment '''
reactor.callLater(1, client.transport.loseConnection)
reactor.callLater(2, reactor.stop)
reactor.shutdown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/synchronous-udp-client.py 0000644 0001755 0001755 00000001404 12607272152 023443 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusUdpClient as ModbusClient
from base_runner import Runner
class SynchronousUdpClient(Runner, unittest.TestCase):
'''
These are the integration tests for the synchronous
udp client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
self.client = ModbusClient()
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
super(Runner, self).tearDown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/base_context.py 0000644 0001755 0001755 00000004142 12607272152 021467 0 ustar debacle debacle import os
import time
from subprocess import Popen as execute
from twisted.internet.defer import Deferred
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
log = logging.getLogger(__name__)
class ContextRunner(object):
'''
This is the base runner class for all the integration tests
'''
__bit_functions = [2,1] # redundant are removed for now
__reg_functions = [4,3] # redundant are removed for now
def initialize(self, service=None):
''' Initializes the test environment '''
if service:
self.fnull = open(os.devnull, 'w')
self.service = execute(service, stdout=self.fnull, stderr=self.fnull)
log.debug("%s service started: %s", service, self.service.pid)
time.sleep(0.2)
else: self.service = None
log.debug("%s context started", self.context)
def shutdown(self):
''' Cleans up the test environment '''
try:
if self.service:
self.service.kill()
self.fnull.close()
self.context.reset()
except: pass
log.debug("%s context stopped" % self.context)
def testDataContextRegisters(self):
''' Test that the context gets and sets registers '''
address = 10
values = [0x1234] * 32
for fx in self.__reg_functions:
self.context.setValues(fx, address, values)
result = self.context.getValues(fx, address, len(values))
self.assertEquals(len(result), len(values))
self.assertEquals(result, values)
def testDataContextDiscretes(self):
''' Test that the context gets and sets discretes '''
address = 10
values = [True] * 32
for fx in self.__bit_functions:
self.context.setValues(fx, address, values)
result = self.context.getValues(fx, address, len(values))
self.assertEquals(len(result), len(values))
self.assertEquals(result, values)
pymodbus/examples/functional/synchronous-rtu-client.py 0000644 0001755 0001755 00000001657 12607272152 023477 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
from base_runner import Runner
class SynchronousRtuClient(Runner, unittest.TestCase):
'''
These are the integration tests for the synchronous
serial rtu client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
self.initialize(["../tools/reference/diagslave", "-m", "rtu", "/dev/pts/14"])
self.client = ModbusClient(method='rtu', timeout=0.2, port='/dev/pts/13')
self.client.connect()
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
super(Runner, self).tearDown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/functional/asynchronous-rtu-client.py 0000644 0001755 0001755 00000001435 12607272152 023632 0 ustar debacle debacle #!/usr/bin/env python
import unittest
from pymodbus.client.async import ModbusSerialClient as ModbusClient
from base_runner import Runner
class AsynchronousRtuClient(Runner, unittest.TestCase):
'''
These are the integration tests for the asynchronous
serial rtu client.
'''
def setUp(self):
''' Initializes the test environment '''
super(Runner, self).setUp()
self.client = ModbusClient(method='rtu')
def tearDown(self):
''' Cleans up the test environment '''
self.client.close()
super(Runner, self).tearDown()
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
unittest.main()
pymodbus/examples/common/ 0000755 0001755 0001755 00000000000 12607272152 015564 5 ustar debacle debacle pymodbus/examples/common/changing-framers.py 0000755 0001755 0001755 00000005123 12607272152 021355 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Client Framer Overload
--------------------------------------------------------------------------
All of the modbus clients are designed to have pluggable framers
so that the transport and protocol are decoupled. This allows a user
to define or plug in their custom protocols into existing transports
(like a binary framer over a serial connection).
It should be noted that although you are not limited to trying whatever
you would like, the library makes no gurantees that all framers with
all transports will produce predictable or correct results (for example
tcp transport with an RTU framer). However, please let us know of any
success cases that are not documented!
'''
#---------------------------------------------------------------------------#
# import the modbus client and the framers
#---------------------------------------------------------------------------#
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
#---------------------------------------------------------------------------#
# Import the modbus framer that you want
#---------------------------------------------------------------------------#
#---------------------------------------------------------------------------#
#from pymodbus.transaction import ModbusSocketFramer as ModbusFramer
from pymodbus.transaction import ModbusRtuFramer as ModbusFramer
#from pymodbus.transaction import ModbusBinaryFramer as ModbusFramer
#from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# Initialize the client
#---------------------------------------------------------------------------#
client = ModbusClient('localhost', port=5020, framer=ModbusFramer)
client.connect()
#---------------------------------------------------------------------------#
# perform your requests
#---------------------------------------------------------------------------#
rq = client.write_coil(1, True)
rr = client.read_coils(1,1)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.bits[0] == True) # test the expected value
#---------------------------------------------------------------------------#
# close the client
#---------------------------------------------------------------------------#
client.close()
pymodbus/examples/common/asynchronous-server.py 0000755 0001755 0001755 00000012101 12607272152 022173 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Asynchronous Server Example
--------------------------------------------------------------------------
The asynchronous server is a high performance implementation using the
twisted library as its backend. This allows it to scale to many thousands
of nodes which can be helpful for testing monitoring software.
'''
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.server.async import StartTcpServer
from pymodbus.server.async import StartUdpServer
from pymodbus.server.async import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# initialize your data store
#---------------------------------------------------------------------------#
# The datastores only respond to the addresses that they are initialized to.
# Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a
# request to 0x100 will respond with an invalid address exception. This is
# because many devices exhibit this kind of behavior (but not all)::
#
# block = ModbusSequentialDataBlock(0x00, [0]*0xff)
#
# Continuing, you can choose to use a sequential or a sparse DataBlock in
# your data context. The difference is that the sequential has no gaps in
# the data while the sparse can. Once again, there are devices that exhibit
# both forms of behavior::
#
# block = ModbusSparseDataBlock({0x00: 0, 0x05: 1})
# block = ModbusSequentialDataBlock(0x00, [0]*5)
#
# Alternately, you can use the factory methods to initialize the DataBlocks
# or simply do not pass them to have them initialized to 0x00 on the full
# address range::
#
# store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create())
# store = ModbusSlaveContext()
#
# Finally, you are allowed to use the same DataBlock reference for every
# table or you you may use a seperate DataBlock for each table. This depends
# if you would like functions to be able to access and modify the same data
# or not::
#
# block = ModbusSequentialDataBlock(0x00, [0]*0xff)
# store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)
#
# The server then makes use of a server context that allows the server to
# respond with different slave contexts for different unit ids. By default
# it will return the same context for every unit id supplied (broadcast
# mode). However, this can be overloaded by setting the single flag to False
# and then supplying a dictionary of unit id to context mapping::
#
# slaves = {
# 0x01: ModbusSlaveContext(...),
# 0x02: ModbusSlaveContext(...),
# 0x03: ModbusSlaveContext(...),
# }
# context = ModbusServerContext(slaves=slaves, single=False)
#
# The slave context can also be initialized in zero_mode which means that a
# request to address(0-7) will map to the address (0-7). The default is
# False which is based on section 4.4 of the specification, so address(0-7)
# will map to (1-8)::
#
# store = ModbusSlaveContext(..., zero_mode=True)
#---------------------------------------------------------------------------#
store = ModbusSlaveContext(
di = ModbusSequentialDataBlock(0, [17]*100),
co = ModbusSequentialDataBlock(0, [17]*100),
hr = ModbusSequentialDataBlock(0, [17]*100),
ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# initialize the server information
#---------------------------------------------------------------------------#
# If you don't set this or any fields, they are defaulted to empty strings.
#---------------------------------------------------------------------------#
identity = ModbusDeviceIdentification()
identity.VendorName = 'Pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
StartTcpServer(context, identity=identity, address=("localhost", 5020))
#StartUdpServer(context, identity=identity, address=("localhost", 502))
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusRtuFramer)
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusAsciiFramer)
pymodbus/examples/common/callback-server.py 0000755 0001755 0001755 00000011723 12607272152 021205 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Server With Callbacks
--------------------------------------------------------------------------
This is an example of adding callbacks to a running modbus server
when a value is written to it. In order for this to work, it needs
a device-mapping file.
'''
#---------------------------------------------------------------------------#
# import the modbus libraries we need
#---------------------------------------------------------------------------#
from pymodbus.server.async import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
#---------------------------------------------------------------------------#
# import the python libraries we need
#---------------------------------------------------------------------------#
from multiprocessing import Queue, Process
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# create your custom data block with callbacks
#---------------------------------------------------------------------------#
class CallbackDataBlock(ModbusSparseDataBlock):
''' A datablock that stores the new value in memory
and passes the operation to a message queue for further
processing.
'''
def __init__(self, devices, queue):
'''
'''
self.devices = devices
self.queue = queue
values = {k:0 for k in devices.iterkeys()}
values[0xbeef] = len(values) # the number of devices
super(CallbackDataBlock, self).__init__(values)
def setValues(self, address, value):
''' Sets the requested values of the datastore
:param address: The starting address
:param values: The new values to be set
'''
super(CallbackDataBlock, self).setValues(address, value)
self.queue.put((self.devices.get(address, None), value))
#---------------------------------------------------------------------------#
# define your callback process
#---------------------------------------------------------------------------#
def rescale_value(value):
''' Rescale the input value from the range
of 0..100 to -3200..3200.
:param value: The input value to scale
:returns: The rescaled value
'''
s = 1 if value >= 50 else -1
c = value if value < 50 else (value - 50)
return s * (c * 64)
def device_writer(queue):
''' A worker process that processes new messages
from a queue to write to device outputs
:param queue: The queue to get new messages from
'''
while True:
device, value = queue.get()
scaled = rescale_value(value[0])
log.debug("Write(%s) = %s" % (device, value))
if not device: continue
# do any logic here to update your devices
#---------------------------------------------------------------------------#
# initialize your device map
#---------------------------------------------------------------------------#
def read_device_map(path):
''' A helper method to read the device
path to address mapping from file::
0x0001,/dev/device1
0x0002,/dev/device2
:param path: The path to the input file
:returns: The input mapping file
'''
devices = {}
with open(path, 'r') as stream:
for line in stream:
piece = line.strip().split(',')
devices[int(piece[0], 16)] = piece[1]
return devices
#---------------------------------------------------------------------------#
# initialize your data store
#---------------------------------------------------------------------------#
queue = Queue()
devices = read_device_map("device-mapping")
block = CallbackDataBlock(devices, queue)
store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# initialize the server information
#---------------------------------------------------------------------------#
identity = ModbusDeviceIdentification()
identity.VendorName = 'pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'pymodbus Server'
identity.ModelName = 'pymodbus Server'
identity.MajorMinorRevision = '1.0'
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
p = Process(target=device_writer, args=(queue,))
p.start()
StartTcpServer(context, identity=identity, address=("localhost", 5020))
pymodbus/examples/common/custom-datablock.py 0000755 0001755 0001755 00000006026 12607272152 021401 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Server With Custom Datablock Side Effect
--------------------------------------------------------------------------
This is an example of performing custom logic after a value has been
written to the datastore.
'''
#---------------------------------------------------------------------------#
# import the modbus libraries we need
#---------------------------------------------------------------------------#
from pymodbus.server.async import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# create your custom data block here
#---------------------------------------------------------------------------#
class CustomDataBlock(ModbusSparseDataBlock):
''' A datablock that stores the new value in memory
and performs a custom action after it has been stored.
'''
def setValues(self, address, value):
''' Sets the requested values of the datastore
:param address: The starting address
:param values: The new values to be set
'''
super(ModbusSparseDataBlock, self).setValues(address, value)
# whatever you want to do with the written value is done here,
# however make sure not to do too much work here or it will
# block the server, espectially if the server is being written
# to very quickly
print "wrote {} to {}".format(value, address)
#---------------------------------------------------------------------------#
# initialize your data store
#---------------------------------------------------------------------------#
block = CustomDataBlock()
store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# initialize the server information
#---------------------------------------------------------------------------#
identity = ModbusDeviceIdentification()
identity.VendorName = 'pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'pymodbus Server'
identity.ModelName = 'pymodbus Server'
identity.MajorMinorRevision = '1.0'
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
p = Process(target=device_writer, args=(queue,))
p.start()
StartTcpServer(context, identity=identity, address=("localhost", 5020))
pymodbus/examples/common/synchronous-client.py 0000755 0001755 0001755 00000012640 12607272152 022012 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Synchronous Client Examples
--------------------------------------------------------------------------
The following is an example of how to use the synchronous modbus client
implementation from pymodbus.
It should be noted that the client can also be used with
the guard construct that is available in python 2.5 and up::
with ModbusClient('127.0.0.1') as client:
result = client.read_coils(1,10)
print result
'''
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
#from pymodbus.client.sync import ModbusUdpClient as ModbusClient
#from pymodbus.client.sync import ModbusSerialClient as ModbusClient
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# choose the client you want
#---------------------------------------------------------------------------#
# make sure to start an implementation to hit against. For this
# you can use an existing device, the reference implementation in the tools
# directory, or start a pymodbus server.
#
# If you use the UDP or TCP clients, you can override the framer being used
# to use a custom implementation (say RTU over TCP). By default they use the
# socket framer::
#
# client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer)
#
# It should be noted that you can supply an ipv4 or an ipv6 host address for
# both the UDP and TCP clients.
#
# There are also other options that can be set on the client that controls
# how transactions are performed. The current ones are:
#
# * retries - Specify how many retries to allow per transaction (default = 3)
# * retry_on_empty - Is an empty response a retry (default = False)
# * source_address - Specifies the TCP source address to bind to
#
# Here is an example of using these options::
#
# client = ModbusClient('localhost', retries=3, retry_on_empty=True)
#---------------------------------------------------------------------------#
client = ModbusClient('localhost', port=502)
#client = ModbusClient(method='ascii', port='/dev/pts/2', timeout=1)
#client = ModbusClient(method='rtu', port='/dev/pts/2', timeout=1)
client.connect()
#---------------------------------------------------------------------------#
# specify slave to query
#---------------------------------------------------------------------------#
# The slave to query is specified in an optional parameter for each
# individual request. This can be done by specifying the `unit` parameter
# which defaults to `0x00`
#---------------------------------------------------------------------------#
rr = client.read_coils(1, 1, unit=0x02)
#---------------------------------------------------------------------------#
# example requests
#---------------------------------------------------------------------------#
# simply call the methods that you would like to use. An example session
# is displayed below along with some assert checks. Note that some modbus
# implementations differentiate holding/input discrete/coils and as such
# you will not be able to write to these, therefore the starting values
# are not known to these tests. Furthermore, some use the same memory
# blocks for the two sets, so a change to one is a change to the other.
# Keep both of these cases in mind when testing as the following will
# _only_ pass with the supplied async modbus server (script supplied).
#---------------------------------------------------------------------------#
rq = client.write_coil(1, True)
rr = client.read_coils(1,1)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.bits[0] == True) # test the expected value
rq = client.write_coils(1, [True]*8)
rr = client.read_coils(1,8)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.bits == [True]*8) # test the expected value
rq = client.write_coils(1, [False]*8)
rr = client.read_discrete_inputs(1,8)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.bits == [False]*8) # test the expected value
rq = client.write_register(1, 10)
rr = client.read_holding_registers(1,1)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.registers[0] == 10) # test the expected value
rq = client.write_registers(1, [10]*8)
rr = client.read_input_registers(1,8)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rr.registers == [10]*8) # test the expected value
arguments = {
'read_address': 1,
'read_count': 8,
'write_address': 1,
'write_registers': [20]*8,
}
rq = client.readwrite_registers(**arguments)
rr = client.read_input_registers(1,8)
assert(rq.function_code < 0x80) # test that we are not an error
assert(rq.registers == [20]*8) # test the expected value
assert(rr.registers == [20]*8) # test the expected value
#---------------------------------------------------------------------------#
# close the client
#---------------------------------------------------------------------------#
client.close()
pymodbus/examples/common/README.rst 0000644 0001755 0001755 00000007374 12607272152 017266 0 ustar debacle debacle ============================================================
Modbus Implementations
============================================================
There are a few reference implementations that you can use
to test modbus serial
------------------------------------------------------------
pymodbus
------------------------------------------------------------
You can use pymodbus as a testing server by simply modifying
one of the run scripts supplied here. There is an
asynchronous version and a synchronous version (that really
differ in how mnay dependencies you are willing to have).
Regardless of which one you choose, they can be started
quite easily::
./asynchronous-server.py
./synchronous-server.py
Currently, each version has implementations of the following:
- modbus tcp
- modbus udp
- modbus udp binary
- modbus ascii serial
- modbus ascii rtu
------------------------------------------------------------
Modbus Driver
------------------------------------------------------------
Included are reference implementations of a modbus client
and server using the modbus driver library (as well as
the relevant source code). Both programs have a wealth of
options and can be used to test either side of your
application::
tools/reference/diagslave -h # (server)
tools/reference/modpoll -h # (client)
------------------------------------------------------------
jamod
------------------------------------------------------------
Jamod is a complete modbus implementation for the java jvm.
Included are a few simple reference servers using the
library, however, a great deal more can be produced using
it. I have not tested it, however, it may even be possible
to use this library in conjunction with jython to interop
between your python code and this library:
* http://jamod.sourceforge.net/
------------------------------------------------------------
nmodbus
------------------------------------------------------------
Although there is not any code included in this package,
nmodbus is a complete implementation of the modbus protocol
for the .net clr. The site has a number of examples that can
be tuned for your testing needs:
* http://code.google.com/p/nmodbus/
============================================================
Serial Loopback Testing
============================================================
In order to test the serial implementations, one needs to
create a loopback connection (virtual serial port). This can
be done in a number of ways.
------------------------------------------------------------
Linux
------------------------------------------------------------
For linux, there are three ways that are included with this
distribution.
One is to use the socat utility. The following will get one
going quickly::
sudo apt-get install socat
sudo socat PTY,link=/dev/pts/13, PTY,link=/dev/pts/14
# connect the master to /dev/pts/13
# connect the client to /dev/pts/14
Next, you can include the loopback kernel driver included in
the tools/nullmodem/linux directory::
sudo ./run
------------------------------------------------------------
Windows
------------------------------------------------------------
For Windows, simply use the com2com application that is in
the directory tools/nullmodem/windows. Instructions are
included in the Readme.txt.
------------------------------------------------------------
Generic
------------------------------------------------------------
For most unix based systems, there is a simple virtual serial
forwarding application in the tools/nullmodem/ directory::
make run
# connect the master to the master output
# connect the client to the client output
Or for a tried and true method, simply connect a null modem
cable between two of your serial ports and then simply reference
those.
pymodbus/examples/common/custom-message.py 0000755 0001755 0001755 00000006631 12607272152 021103 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Synchrnonous Client Examples
--------------------------------------------------------------------------
The following is an example of how to use the synchronous modbus client
implementation from pymodbus.
It should be noted that the client can also be used with
the guard construct that is available in python 2.5 and up::
with ModbusClient('127.0.0.1') as client:
result = client.read_coils(1,10)
print result
'''
import struct
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.pdu import ModbusRequest, ModbusResponse
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# create your custom message
#---------------------------------------------------------------------------#
# The following is simply a read coil request that always reads 16 coils.
# Since the function code is already registered with the decoder factory,
# this will be decoded as a read coil response. If you implement a new
# method that is not currently implemented, you must register the request
# and response with a ClientDecoder factory.
#---------------------------------------------------------------------------#
class CustomModbusRequest(ModbusRequest):
function_code = 1
def __init__(self, address):
ModbusRequest.__init__(self)
self.address = address
self.count = 16
def encode(self):
return struct.pack('>HH', self.address, self.count)
def decode(self, data):
self.address, self.count = struct.unpack('>HH', data)
def execute(self, context):
if not (1 <= self.count <= 0x7d0):
return self.doException(merror.IllegalValue)
if not context.validate(self.function_code, self.address, self.count):
return self.doException(merror.IllegalAddress)
values = context.getValues(self.function_code, self.address, self.count)
return CustomModbusResponse(values)
#---------------------------------------------------------------------------#
# This could also have been defined as
#---------------------------------------------------------------------------#
from pymodbus.bit_read_message import ReadCoilsRequest
class Read16CoilsRequest(ReadCoilsRequest):
def __init__(self, address):
''' Initializes a new instance
:param address: The address to start reading from
'''
ReadCoilsRequest.__init__(self, address, 16)
#---------------------------------------------------------------------------#
# execute the request with your client
#---------------------------------------------------------------------------#
# using the with context, the client will automatically be connected
# and closed when it leaves the current scope.
#---------------------------------------------------------------------------#
with ModbusClient('127.0.0.1') as client:
request = CustomModbusRequest(0)
result = client.execute(request)
print result
pymodbus/examples/common/modbus-payload.py 0000755 0001755 0001755 00000005630 12607272152 021065 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Payload Building/Decoding Example
--------------------------------------------------------------------------
'''
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.INFO)
#---------------------------------------------------------------------------#
# We are going to use a simple client to send our requests
#---------------------------------------------------------------------------#
client = ModbusClient('127.0.0.1')
client.connect()
#---------------------------------------------------------------------------#
# If you need to build a complex message to send, you can use the payload
# builder to simplify the packing logic.
#
# Here we demonstrate packing a random payload layout, unpacked it looks
# like the following:
#
# - a 8 byte string 'abcdefgh'
# - a 32 bit float 22.34
# - a 16 bit unsigned int 0x1234
# - an 8 bit int 0x12
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
#---------------------------------------------------------------------------#
builder = BinaryPayloadBuilder(endian=Endian.Little)
builder.add_string('abcdefgh')
builder.add_32bit_float(22.34)
builder.add_16bit_uint(0x1234)
builder.add_8bit_int(0x12)
builder.add_bits([0,1,0,1,1,0,1,0])
payload = builder.build()
address = 0x01
result = client.write_registers(address, payload, skip_encode=True)
#---------------------------------------------------------------------------#
# If you need to decode a collection of registers in a weird layout, the
# payload decoder can help you as well.
#
# Here we demonstrate decoding a random register layout, unpacked it looks
# like the following:
#
# - a 8 byte string 'abcdefgh'
# - a 32 bit float 22.34
# - a 16 bit unsigned int 0x1234
# - an 8 bit int 0x12
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
#---------------------------------------------------------------------------#
address = 0x01
count = 8
result = client.read_input_registers(address, count)
decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Little)
decoded = {
'string': decoder.decode_string(8),
'float': decoder.decode_32bit_float(),
'16uint': decoder.decode_16bit_uint(),
'8int': decoder.decode_8bit_int(),
'bits': decoder.decode_bits(),
}
print "-" * 60
print "Decoded Data"
print "-" * 60
for name, value in decoded.iteritems():
print ("%s\t" % name), value
#---------------------------------------------------------------------------#
# close the client
#---------------------------------------------------------------------------#
client.close()
pymodbus/examples/common/modbus-logging.py 0000755 0001755 0001755 00000003446 12607272152 021065 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Logging Examples
--------------------------------------------------------------------------
'''
import logging
import logging.handlers as Handlers
#---------------------------------------------------------------------------#
# This will simply send everything logged to console
#---------------------------------------------------------------------------#
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# This will send the error messages in the specified namespace to a file.
# The available namespaces in pymodbus are as follows:
#---------------------------------------------------------------------------#
# * pymodbus.* - The root namespace
# * pymodbus.server.* - all logging messages involving the modbus server
# * pymodbus.client.* - all logging messages involving the client
# * pymodbus.protocol.* - all logging messages inside the protocol layer
#---------------------------------------------------------------------------#
logging.basicConfig()
log = logging.getLogger('pymodbus.server')
log.setLevel(logging.ERROR)
#---------------------------------------------------------------------------#
# This will send the error messages to the specified handlers:
# * docs.python.org/library/logging.html
#---------------------------------------------------------------------------#
log = logging.getLogger('pymodbus')
log.setLevel(logging.ERROR)
handlers = [
Handlers.RotatingFileHandler("logfile", maxBytes=1024*1024),
Handlers.SMTPHandler("mx.host.com", "pymodbus@host.com", ["support@host.com"], "Pymodbus"),
Handlers.SysLogHandler(facility="daemon"),
Handlers.DatagramHandler('localhost', 12345),
]
[log.addHandler(h) for h in handlers]
pymodbus/examples/common/synchronous-client-ext.py 0000755 0001755 0001755 00000016710 12607272152 022612 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Synchronous Client Extended Examples
--------------------------------------------------------------------------
The following is an example of how to use the synchronous modbus client
implementation from pymodbus to perform the extended portions of the
modbus protocol.
'''
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
#from pymodbus.client.sync import ModbusUdpClient as ModbusClient
#from pymodbus.client.sync import ModbusSerialClient as ModbusClient
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# choose the client you want
#---------------------------------------------------------------------------#
# make sure to start an implementation to hit against. For this
# you can use an existing device, the reference implementation in the tools
# directory, or start a pymodbus server.
#
# It should be noted that you can supply an ipv4 or an ipv6 host address for
# both the UDP and TCP clients.
#---------------------------------------------------------------------------#
client = ModbusClient('127.0.0.1')
client.connect()
#---------------------------------------------------------------------------#
# import the extended messages to perform
#---------------------------------------------------------------------------#
from pymodbus.diag_message import *
from pymodbus.file_message import *
from pymodbus.other_message import *
from pymodbus.mei_message import *
#---------------------------------------------------------------------------#
# extra requests
#---------------------------------------------------------------------------#
# If you are performing a request that is not available in the client
# mixin, you have to perform the request like this instead::
#
# from pymodbus.diag_message import ClearCountersRequest
# from pymodbus.diag_message import ClearCountersResponse
#
# request = ClearCountersRequest()
# response = client.execute(request)
# if isinstance(response, ClearCountersResponse):
# ... do something with the response
#
#
# What follows is a listing of all the supported methods. Feel free to
# comment, uncomment, or modify each result set to match with your reference.
#---------------------------------------------------------------------------#
#---------------------------------------------------------------------------#
# information requests
#---------------------------------------------------------------------------#
rq = ReadDeviceInformationRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
assert(rr.function_code < 0x80) # test that we are not an error
assert(rr.information[0] == 'proconX Pty Ltd') # test the vendor name
assert(rr.information[1] == 'FT-MBSV') # test the product code
assert(rr.information[2] == 'EXPERIMENTAL') # test the code revision
rq = ReportSlaveIdRequest()
rr = client.execute(rq)
assert(rr == None) # not supported by reference
#assert(rr.function_code < 0x80) # test that we are not an error
#assert(rr.identifier == 0x00) # test the slave identifier
#assert(rr.status == 0x00) # test that the status is ok
rq = ReadExceptionStatusRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
assert(rr.function_code < 0x80) # test that we are not an error
assert(rr.status == 0x55) # test the status code
rq = GetCommEventCounterRequest()
rr = client.execute(rq)
assert(rr == None) # not supported by reference
#assert(rr.function_code < 0x80) # test that we are not an error
#assert(rr.status == True) # test the status code
#assert(rr.count == 0x00) # test the status code
rq = GetCommEventLogRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
#assert(rr.function_code < 0x80) # test that we are not an error
#assert(rr.status == True) # test the status code
#assert(rr.event_count == 0x00) # test the number of events
#assert(rr.message_count == 0x00) # test the number of messages
#assert(len(rr.events) == 0x00) # test the number of events
#---------------------------------------------------------------------------#
# diagnostic requests
#---------------------------------------------------------------------------#
rq = ReturnQueryDataRequest()
rr = client.execute(rq)
assert(rr == None) # not supported by reference
#assert(rr.message[0] == 0x0000) # test the resulting message
rq = RestartCommunicationsOptionRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
#assert(rr.message == 0x0000) # test the resulting message
rq = ReturnDiagnosticRegisterRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ChangeAsciiInputDelimiterRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ForceListenOnlyModeRequest()
client.execute(rq) # does not send a response
rq = ClearCountersRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnBusCommunicationErrorCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnBusExceptionErrorCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnSlaveMessageCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnSlaveNoResponseCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnSlaveNAKCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnSlaveBusyCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnSlaveBusCharacterOverrunCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ReturnIopOverrunCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = ClearOverrunCountRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
rq = GetClearModbusPlusRequest()
rr = client.execute(rq)
#assert(rr == None) # not supported by reference
#---------------------------------------------------------------------------#
# close the client
#---------------------------------------------------------------------------#
client.close()
pymodbus/examples/common/asynchronous-client.py 0000755 0001755 0001755 00000013434 12607272152 022155 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Asynchronous Client Examples
--------------------------------------------------------------------------
The following is an example of how to use the asynchronous modbus
client implementation from pymodbus.
'''
#---------------------------------------------------------------------------#
# import needed libraries
#---------------------------------------------------------------------------#
from twisted.internet import reactor, protocol
from pymodbus.constants import Defaults
#---------------------------------------------------------------------------#
# choose the requested modbus protocol
#---------------------------------------------------------------------------#
from pymodbus.client.async import ModbusClientProtocol
#from pymodbus.client.async import ModbusUdpClientProtocol
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# helper method to test deferred callbacks
#---------------------------------------------------------------------------#
def dassert(deferred, callback):
def _assertor(value): assert(value)
deferred.addCallback(lambda r: _assertor(callback(r)))
deferred.addErrback(lambda _: _assertor(False))
#---------------------------------------------------------------------------#
# specify slave to query
#---------------------------------------------------------------------------#
# The slave to query is specified in an optional parameter for each
# individual request. This can be done by specifying the `unit` parameter
# which defaults to `0x00`
#---------------------------------------------------------------------------#
def exampleRequests(client):
rr = client.read_coils(1, 1, unit=0x02)
#---------------------------------------------------------------------------#
# example requests
#---------------------------------------------------------------------------#
# simply call the methods that you would like to use. An example session
# is displayed below along with some assert checks. Note that unlike the
# synchronous version of the client, the asynchronous version returns
# deferreds which can be thought of as a handle to the callback to send
# the result of the operation. We are handling the result using the
# deferred assert helper(dassert).
#---------------------------------------------------------------------------#
def beginAsynchronousTest(client):
rq = client.write_coil(1, True)
rr = client.read_coils(1,1)
dassert(rq, lambda r: r.function_code < 0x80) # test that we are not an error
dassert(rr, lambda r: r.bits[0] == True) # test the expected value
rq = client.write_coils(1, [True]*8)
rr = client.read_coils(1,8)
dassert(rq, lambda r: r.function_code < 0x80) # test that we are not an error
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value
rq = client.write_coils(1, [False]*8)
rr = client.read_discrete_inputs(1,8)
dassert(rq, lambda r: r.function_code < 0x80) # test that we are not an error
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value
rq = client.write_register(1, 10)
rr = client.read_holding_registers(1,1)
dassert(rq, lambda r: r.function_code < 0x80) # test that we are not an error
dassert(rr, lambda r: r.registers[0] == 10) # test the expected value
rq = client.write_registers(1, [10]*8)
rr = client.read_input_registers(1,8)
dassert(rq, lambda r: r.function_code < 0x80) # test that we are not an error
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
arguments = {
'read_address': 1,
'read_count': 8,
'write_address': 1,
'write_registers': [20]*8,
}
rq = client.readwrite_registers(**arguments)
rr = client.read_input_registers(1,8)
dassert(rq, lambda r: r.registers == [20]*8) # test the expected value
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
#-----------------------------------------------------------------------#
# close the client at some time later
#-----------------------------------------------------------------------#
reactor.callLater(1, client.transport.loseConnection)
reactor.callLater(2, reactor.stop)
#---------------------------------------------------------------------------#
# extra requests
#---------------------------------------------------------------------------#
# If you are performing a request that is not available in the client
# mixin, you have to perform the request like this instead::
#
# from pymodbus.diag_message import ClearCountersRequest
# from pymodbus.diag_message import ClearCountersResponse
#
# request = ClearCountersRequest()
# response = client.execute(request)
# if isinstance(response, ClearCountersResponse):
# ... do something with the response
#
#---------------------------------------------------------------------------#
#---------------------------------------------------------------------------#
# choose the client you want
#---------------------------------------------------------------------------#
# make sure to start an implementation to hit against. For this
# you can use an existing device, the reference implementation in the tools
# directory, or start a pymodbus server.
#---------------------------------------------------------------------------#
defer = protocol.ClientCreator(reactor, ModbusClientProtocol
).connectTCP("localhost", Defaults.Port)
defer.addCallback(beginAsynchronousTest)
reactor.run()
pymodbus/examples/common/performance.py 0000755 0001755 0001755 00000006430 12607272152 020445 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Performance Example
--------------------------------------------------------------------------
The following is an quick performance check of the synchronous
modbus client.
'''
#---------------------------------------------------------------------------#
# import the necessary modules
#---------------------------------------------------------------------------#
import logging, os
from time import time
from multiprocessing import log_to_stderr
from pymodbus.client.sync import ModbusTcpClient
#---------------------------------------------------------------------------#
# choose between threads or processes
#---------------------------------------------------------------------------#
#from multiprocessing import Process as Worker
from threading import Thread as Worker
#---------------------------------------------------------------------------#
# initialize the test
#---------------------------------------------------------------------------#
# Modify the parameters below to control how we are testing the client:
#
# * workers - the number of workers to use at once
# * cycles - the total number of requests to send
# * host - the host to send the requests to
#---------------------------------------------------------------------------#
workers = 1
cycles = 10000
host = '127.0.0.1'
#---------------------------------------------------------------------------#
# perform the test
#---------------------------------------------------------------------------#
# This test is written such that it can be used by many threads of processes
# although it should be noted that there are performance penalties
# associated with each strategy.
#---------------------------------------------------------------------------#
def single_client_test(host, cycles):
''' Performs a single threaded test of a synchronous
client against the specified host
:param host: The host to connect to
:param cycles: The number of iterations to perform
'''
logger = log_to_stderr()
logger.setLevel(logging.DEBUG)
logger.debug("starting worker: %d" % os.getpid())
try:
count = 0
client = ModbusTcpClient(host)
while count < cycles:
result = client.read_holding_registers(10, 1).getRegister(0)
count += 1
except: logger.exception("failed to run test successfully")
logger.debug("finished worker: %d" % os.getpid())
#---------------------------------------------------------------------------#
# run our test and check results
#---------------------------------------------------------------------------#
# We shard the total number of requests to perform between the number of
# threads that was specified. We then start all the threads and block on
# them to finish. This may need to switch to another mechanism to signal
# finished as the process/thread start up/shut down may skew the test a bit.
#---------------------------------------------------------------------------#
args = (host, int(cycles * 1.0 / workers))
procs = [Worker(target=single_client_test, args=args) for _ in range(workers)]
start = time()
any(p.start() for p in procs) # start the workers
any(p.join() for p in procs) # wait for the workers to finish
stop = time()
print "%d requests/second" % ((1.0 * cycles) / (stop - start))
pymodbus/examples/common/updating-server.py 0000755 0001755 0001755 00000006713 12607272152 021267 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Server With Updating Thread
--------------------------------------------------------------------------
This is an example of having a background thread updating the
context while the server is operating. This can also be done with
a python thread::
from threading import Thread
thread = Thread(target=updating_writer, args=(context,))
thread.start()
'''
#---------------------------------------------------------------------------#
# import the modbus libraries we need
#---------------------------------------------------------------------------#
from pymodbus.server.async import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
#---------------------------------------------------------------------------#
# import the twisted libraries we need
#---------------------------------------------------------------------------#
from twisted.internet.task import LoopingCall
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# define your callback process
#---------------------------------------------------------------------------#
def updating_writer(a):
''' A worker process that runs every so often and
updates live values of the context. It should be noted
that there is a race condition for the update.
:param arguments: The input arguments to the call
'''
log.debug("updating the context")
context = a[0]
register = 3
slave_id = 0x00
address = 0x10
values = context[slave_id].getValues(register, address, count=5)
values = [v + 1 for v in values]
log.debug("new values: " + str(values))
context[slave_id].setValues(register, address, values)
#---------------------------------------------------------------------------#
# initialize your data store
#---------------------------------------------------------------------------#
store = ModbusSlaveContext(
di = ModbusSequentialDataBlock(0, [17]*100),
co = ModbusSequentialDataBlock(0, [17]*100),
hr = ModbusSequentialDataBlock(0, [17]*100),
ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# initialize the server information
#---------------------------------------------------------------------------#
identity = ModbusDeviceIdentification()
identity.VendorName = 'pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'pymodbus Server'
identity.ModelName = 'pymodbus Server'
identity.MajorMinorRevision = '1.0'
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
time = 5 # 5 seconds delay
loop = LoopingCall(f=updating_writer, a=(context,))
loop.start(time, now=False) # initially delay by time
StartTcpServer(context, identity=identity, address=("localhost", 5020))
pymodbus/examples/common/asynchronous-processor.py 0000755 0001755 0001755 00000016110 12607272152 022710 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Asynchronous Processor Example
--------------------------------------------------------------------------
The following is a full example of a continuous client processor. Feel
free to use it as a skeleton guide in implementing your own.
'''
#---------------------------------------------------------------------------#
# import the neccessary modules
#---------------------------------------------------------------------------#
from twisted.internet import serialport, reactor
from twisted.internet.protocol import ClientFactory
from pymodbus.factory import ClientDecoder
from pymodbus.client.async import ModbusClientProtocol
#---------------------------------------------------------------------------#
# Choose the framer you want to use
#---------------------------------------------------------------------------#
#from pymodbus.transaction import ModbusBinaryFramer as ModbusFramer
#from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer
#from pymodbus.transaction import ModbusRtuFramer as ModbusFramer
from pymodbus.transaction import ModbusSocketFramer as ModbusFramer
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger("pymodbus")
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# state a few constants
#---------------------------------------------------------------------------#
SERIAL_PORT = "/dev/ttyS0"
STATUS_REGS = (1, 2)
STATUS_COILS = (1, 3)
CLIENT_DELAY = 1
#---------------------------------------------------------------------------#
# an example custom protocol
#---------------------------------------------------------------------------#
# Here you can perform your main procesing loop utilizing defereds and timed
# callbacks.
#---------------------------------------------------------------------------#
class ExampleProtocol(ModbusClientProtocol):
def __init__(self, framer, endpoint):
''' Initializes our custom protocol
:param framer: The decoder to use to process messages
:param endpoint: The endpoint to send results to
'''
ModbusClientProtocol.__init__(self, framer)
self.endpoint = endpoint
log.debug("Beginning the processing loop")
reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)
def fetch_holding_registers(self):
''' Defer fetching holding registers
'''
log.debug("Starting the next cycle")
d = self.read_holding_registers(*STATUS_REGS)
d.addCallbacks(self.send_holding_registers, self.error_handler)
def send_holding_registers(self, response):
''' Write values of holding registers, defer fetching coils
:param response: The response to process
'''
self.endpoint.write(response.getRegister(0))
self.endpoint.write(response.getRegister(1))
d = self.read_coils(*STATUS_COILS)
d.addCallbacks(self.start_next_cycle, self.error_handler)
def start_next_cycle(self, response):
''' Write values of coils, trigger next cycle
:param response: The response to process
'''
self.endpoint.write(response.getBit(0))
self.endpoint.write(response.getBit(1))
self.endpoint.write(response.getBit(2))
reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)
def error_handler(self, failure):
''' Handle any twisted errors
:param failure: The error to handle
'''
log.error(failure)
#---------------------------------------------------------------------------#
# a factory for the example protocol
#---------------------------------------------------------------------------#
# This is used to build client protocol's if you tie into twisted's method
# of processing. It basically produces client instances of the underlying
# protocol::
#
# Factory(Protocol) -> ProtocolInstance
#
# It also persists data between client instances (think protocol singelton).
#---------------------------------------------------------------------------#
class ExampleFactory(ClientFactory):
protocol = ExampleProtocol
def __init__(self, framer, endpoint):
''' Remember things necessary for building a protocols '''
self.framer = framer
self.endpoint = endpoint
def buildProtocol(self, _):
''' Create a protocol and start the reading cycle '''
proto = self.protocol(self.framer, self.endpoint)
proto.factory = self
return proto
#---------------------------------------------------------------------------#
# a custom client for our device
#---------------------------------------------------------------------------#
# Twisted provides a number of helper methods for creating and starting
# clients:
# - protocol.ClientCreator
# - reactor.connectTCP
#
# How you start your client is really up to you.
#---------------------------------------------------------------------------#
class SerialModbusClient(serialport.SerialPort):
def __init__(self, factory, *args, **kwargs):
''' Setup the client and start listening on the serial port
:param factory: The factory to build clients with
'''
protocol = factory.buildProtocol(None)
self.decoder = ClientDecoder()
serialport.SerialPort.__init__(self, protocol, *args, **kwargs)
#---------------------------------------------------------------------------#
# a custom endpoint for our results
#---------------------------------------------------------------------------#
# An example line reader, this can replace with:
# - the TCP protocol
# - a context recorder
# - a database or file recorder
#---------------------------------------------------------------------------#
class LoggingLineReader(object):
def write(self, response):
''' Handle the next modbus response
:param response: The response to process
'''
log.info("Read Data: %d" % response)
#---------------------------------------------------------------------------#
# start running the processor
#---------------------------------------------------------------------------#
# This initializes the client, the framer, the factory, and starts the
# twisted event loop (the reactor). It should be noted that a number of
# things could be chanegd as one sees fit:
# - The ModbusRtuFramer could be replaced with a ModbusAsciiFramer
# - The SerialModbusClient could be replaced with reactor.connectTCP
# - The LineReader endpoint could be replaced with a database store
#---------------------------------------------------------------------------#
def main():
log.debug("Initializing the client")
framer = ModbusFramer(ClientDecoder())
reader = LoggingLineReader()
factory = ExampleFactory(framer, reader)
SerialModbusClient(factory, SERIAL_PORT, reactor)
#factory = reactor.connectTCP("localhost", 502, factory)
log.debug("Starting the client")
reactor.run()
if __name__ == "__main__":
main()
pymodbus/examples/common/synchronous-server.py 0000755 0001755 0001755 00000012231 12607272152 022036 0 ustar debacle debacle #!/usr/bin/env python
'''
Pymodbus Synchronous Server Example
--------------------------------------------------------------------------
The synchronous server is implemented in pure python without any third
party libraries (unless you need to use the serial protocols which require
pyserial). This is helpful in constrained or old environments where using
twisted just is not feasable. What follows is an examle of its use:
'''
#---------------------------------------------------------------------------#
# import the various server implementations
#---------------------------------------------------------------------------#
from pymodbus.server.sync import StartTcpServer
from pymodbus.server.sync import StartUdpServer
from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer
#---------------------------------------------------------------------------#
# configure the service logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# initialize your data store
#---------------------------------------------------------------------------#
# The datastores only respond to the addresses that they are initialized to.
# Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a
# request to 0x100 will respond with an invalid address exception. This is
# because many devices exhibit this kind of behavior (but not all)::
#
# block = ModbusSequentialDataBlock(0x00, [0]*0xff)
#
# Continuing, you can choose to use a sequential or a sparse DataBlock in
# your data context. The difference is that the sequential has no gaps in
# the data while the sparse can. Once again, there are devices that exhibit
# both forms of behavior::
#
# block = ModbusSparseDataBlock({0x00: 0, 0x05: 1})
# block = ModbusSequentialDataBlock(0x00, [0]*5)
#
# Alternately, you can use the factory methods to initialize the DataBlocks
# or simply do not pass them to have them initialized to 0x00 on the full
# address range::
#
# store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create())
# store = ModbusSlaveContext()
#
# Finally, you are allowed to use the same DataBlock reference for every
# table or you you may use a seperate DataBlock for each table. This depends
# if you would like functions to be able to access and modify the same data
# or not::
#
# block = ModbusSequentialDataBlock(0x00, [0]*0xff)
# store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)
#
# The server then makes use of a server context that allows the server to
# respond with different slave contexts for different unit ids. By default
# it will return the same context for every unit id supplied (broadcast
# mode). However, this can be overloaded by setting the single flag to False
# and then supplying a dictionary of unit id to context mapping::
#
# slaves = {
# 0x01: ModbusSlaveContext(...),
# 0x02: ModbusSlaveContext(...),
# 0x03: ModbusSlaveContext(...),
# }
# context = ModbusServerContext(slaves=slaves, single=False)
#
# The slave context can also be initialized in zero_mode which means that a
# request to address(0-7) will map to the address (0-7). The default is
# False which is based on section 4.4 of the specification, so address(0-7)
# will map to (1-8)::
#
# store = ModbusSlaveContext(..., zero_mode=True)
#---------------------------------------------------------------------------#
store = ModbusSlaveContext(
di = ModbusSequentialDataBlock(0, [17]*100),
co = ModbusSequentialDataBlock(0, [17]*100),
hr = ModbusSequentialDataBlock(0, [17]*100),
ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------#
# initialize the server information
#---------------------------------------------------------------------------#
# If you don't set this or any fields, they are defaulted to empty strings.
#---------------------------------------------------------------------------#
identity = ModbusDeviceIdentification()
identity.VendorName = 'Pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
#---------------------------------------------------------------------------#
# run the server you want
#---------------------------------------------------------------------------#
# Tcp:
StartTcpServer(context, identity=identity, address=("localhost", 5020))
# Udp:
#StartUdpServer(context, identity=identity, address=("localhost", 502))
# Ascii:
#StartSerialServer(context, identity=identity, port='/dev/pts/3', timeout=1)
# RTU:
#StartSerialServer(context, framer=ModbusRtuFramer, identity=identity, port='/dev/pts/3', timeout=.005)
pymodbus/examples/twisted/ 0000755 0001755 0001755 00000000000 12607272152 015757 5 ustar debacle debacle pymodbus/examples/twisted/modbus-udp.tac 0000644 0001755 0001755 00000001750 12607272152 020532 0 ustar debacle debacle '''
This service can be run with the following::
twistd -ny modbus-udp.tac
'''
from twisted.application import service, internet
from twisted.python.log import ILogObserver, FileLogObserver
from twisted.python.logfile import DailyLogFile
from pymodbus.constants import Defaults
from pymodbus.server.async import ModbusUdpProtocol
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.internal.ptwisted import InstallManagementConsole
def BuildService():
'''
A helper method to build the service
'''
context = None
framer = ModbusSocketFramer
server = ModbusUdpProtocol(context, framer)
InstallManagementConsole({ 'server' : server })
application = internet.UDPServer(Defaults.Port, server)
return application
application = service.Application("Modbus UDP Server")
logfile = DailyLogFile("pymodbus.log", "/tmp")
application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
service = BuildService()
service.setServiceParent(application)
pymodbus/examples/twisted/modbus-tcp.tac 0000644 0001755 0001755 00000001757 12607272152 020537 0 ustar debacle debacle '''
This service can be run with the following::
twistd -ny modbus-tcp.tac
'''
from twisted.application import service, internet
from twisted.python.log import ILogObserver, FileLogObserver
from twisted.python.logfile import DailyLogFile
from pymodbus.constants import Defaults
from pymodbus.server.async import ModbusServerFactory
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.internal.ptwisted import InstallManagementConsole
def BuildService():
'''
A helper method to build the service
'''
context = None
framer = ModbusSocketFramer
factory = ModbusServerFactory(context, framer)
InstallManagementConsole({ 'server' : factory })
application = internet.TCPServer(Defaults.Port, factory)
return application
application = service.Application("Modbus TCP Server")
logfile = DailyLogFile("pymodbus.log", "/tmp")
application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
service = BuildService()
service.setServiceParent(application)
pymodbus/examples/twisted/plugins/ 0000755 0001755 0001755 00000000000 12607272152 017440 5 ustar debacle debacle pymodbus/examples/twisted/plugins/pymodbus_plugin.py 0000644 0001755 0001755 00000003641 12607272152 023236 0 ustar debacle debacle '''
'''
from zope.interface import implements
from twisted.python import usage
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from twisted.application import internet
from pymodbus.constants import Defaults
from pymodbus.server.async import ModbusServerFactory
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.internal.ptwisted import InstallManagementConsole
class Options(usage.Options):
'''
The following are the options available to the
pymodbus server.
'''
optParameters = [
["port", "p", Defaults.Port, "The port number to listen on."],
["type", "t", "tcp", "The type of server to host (tcp, udp, ascii, rtu)"],
["store", "s", "./datastore", "The pickled datastore to use"],
["console", "c", False, "Should the management console be started"],
]
class ModbusServiceMaker(object):
'''
A helper class used to build a twisted plugin
'''
implements(IServiceMaker, IPlugin)
tapname = "pymodbus"
description = "A modbus server"
options = Options
def makeService(self, options):
'''
Construct a service from the given options
'''
if options["type"] == "tcp":
server = internet.TCPServer
else: server = internet.UDPServer
framer = ModbusSocketFramer
context = self._build_context(options['store'])
factory = ModbusServerFactory(None, framer)
if options['console']:
InstallManagementConsole({ 'server' : factory })
return server(int(options["port"]), factory)
def _build_context(self, path):
'''
A helper method to unpickle a datastore,
note, this should be a ModbusServerContext.
'''
import pickle
try:
context = pickle.load(path)
except Exception: context = None
return context
serviceMaker = ModbusServiceMaker()
pymodbus/examples/gui/ 0000755 0001755 0001755 00000000000 12607272152 015060 5 ustar debacle debacle pymodbus/examples/gui/gtk/ 0000755 0001755 0001755 00000000000 12607272152 015645 5 ustar debacle debacle pymodbus/examples/gui/gtk/sims/ 0000755 0001755 0001755 00000000000 12607272152 016620 5 ustar debacle debacle pymodbus/examples/gui/gtk/sims/example.sim 0000644 0001755 0001755 00000007552 12607272152 020776 0 ustar debacle debacle (dp0
S'hr'
p1
ccopy_reg
_reconstructor
p2
(cpymodbus.datastore
ModbusSparseDataBlock
p3
c__builtin__
object
p4
Ntp5
Rp6
(dp7
S'default_value'
p8
I1
sS'values'
p9
(dp10
I0
I1
sI1
I2
sI2
I3
sI3
I4
sI4
I5
sI5
I6
sI6
I7
sI7
I8
sI8
I9
sI9
I10
sI10
I11
sI11
I12
sI12
I13
sI13
I14
sI14
I15
sI15
I16
sI16
I17
sI17
I18
sI18
I19
sI19
I20
sI20
I21
sI21
I22
sI22
I23
sI23
I24
sI24
I25
sI25
I26
sI26
I27
sI27
I28
sI28
I29
sI29
I30
sI30
I31
sI31
I32
sI32
I33
sI33
I34
sI34
I35
sI35
I36
sI36
I37
sI37
I38
sI38
I39
sI39
I40
sI40
I41
sI41
I42
sI42
I43
sI43
I44
sI44
I45
sI45
I46
sI46
I47
sI47
I48
sI48
I49
sI49
I50
sI50
I51
sI51
I52
sI52
I53
sI53
I54
sI54
I55
sI55
I56
sI56
I57
sI57
I58
sI58
I59
sI59
I60
sI60
I61
sI61
I62
sI62
I63
sI63
I64
sI64
I65
sI65
I66
sI66
I67
sI67
I68
sI68
I69
sI69
I70
sI70
I71
sI71
I72
sI72
I73
sI73
I74
sI74
I75
sI75
I76
sI76
I77
sI77
I78
sI78
I79
sI79
I80
sI80
I81
sI81
I82
sI82
I83
sI83
I84
sI84
I85
sI85
I86
sI86
I87
sI87
I88
sI88
I89
sI89
I90
sI90
I91
sI91
I92
sI92
I93
sI93
I94
sI94
I95
sI95
I96
sI96
I97
sI97
I98
sI98
I99
ssS'address'
p11
I0
sbsS'ci'
p12
g2
(g3
g4
Ntp13
Rp14
(dp15
g8
I00
sg9
(dp16
I0
I00
sI1
I00
sI2
I00
sI3
I00
sI4
I00
sI5
I00
sI6
I00
sI7
I00
sI8
I00
sI9
I00
sI10
I00
sI11
I00
sI12
I00
sI13
I00
sI14
I00
sI15
I00
sI16
I00
sI17
I00
sI18
I00
sI19
I00
sI20
I00
sI21
I00
sI22
I00
sI23
I00
sI24
I00
sI25
I00
sI26
I00
sI27
I00
sI28
I00
sI29
I00
sI30
I00
sI31
I00
sI32
I00
sI33
I00
sI34
I00
sI35
I00
sI36
I00
sI37
I00
sI38
I00
sI39
I00
sI40
I00
sI41
I00
sI42
I00
sI43
I00
sI44
I00
sI45
I00
sI46
I00
sI47
I00
sI48
I00
sI49
I00
sI50
I00
sI51
I00
sI52
I00
sI53
I00
sI54
I00
sI55
I00
sI56
I00
sI57
I00
sI58
I00
sI59
I00
sI60
I00
sI61
I00
sI62
I00
sI63
I00
sI64
I00
sI65
I00
sI66
I00
sI67
I00
sI68
I00
sI69
I00
sI70
I00
sI71
I00
sI72
I00
sI73
I00
sI74
I00
sI75
I00
sI76
I00
sI77
I00
sI78
I00
sI79
I00
sI80
I00
sI81
I00
sI82
I00
sI83
I00
sI84
I00
sI85
I00
sI86
I00
sI87
I00
sI88
I00
sI89
I00
sI90
I00
sI91
I00
sI92
I00
sI93
I00
sI94
I00
sI95
I00
sI96
I00
sI97
I00
sI98
I00
ssg11
I0
sbsS'ir'
p17
g2
(g3
g4
Ntp18
Rp19
(dp20
g8
I2
sg9
(dp21
I0
I2
sI1
I4
sI2
I6
sI3
I8
sI4
I10
sI5
I12
sI6
I14
sI7
I16
sI8
I18
sI9
I20
sI10
I22
sI11
I24
sI12
I26
sI13
I28
sI14
I30
sI15
I32
sI16
I34
sI17
I36
sI18
I38
sI19
I40
sI20
I42
sI21
I44
sI22
I46
sI23
I48
sI24
I50
sI25
I52
sI26
I54
sI27
I56
sI28
I58
sI29
I60
sI30
I62
sI31
I64
sI32
I66
sI33
I68
sI34
I70
sI35
I72
sI36
I74
sI37
I76
sI38
I78
sI39
I80
sI40
I82
sI41
I84
sI42
I86
sI43
I88
sI44
I90
sI45
I92
sI46
I94
sI47
I96
sI48
I98
sI49
I100
sI50
I102
sI51
I104
sI52
I106
sI53
I108
sI54
I110
sI55
I112
sI56
I114
sI57
I116
sI58
I118
sI59
I120
sI60
I122
sI61
I124
sI62
I126
sI63
I128
sI64
I130
sI65
I132
sI66
I134
sI67
I136
sI68
I138
sI69
I140
sI70
I142
sI71
I144
sI72
I146
sI73
I148
sI74
I150
sI75
I152
sI76
I154
sI77
I156
sI78
I158
sI79
I160
sI80
I162
sI81
I164
sI82
I166
sI83
I168
sI84
I170
sI85
I172
sI86
I174
sI87
I176
sI88
I178
sI89
I180
sI90
I182
sI91
I184
sI92
I186
sI93
I188
sI94
I190
sI95
I192
sI96
I194
sI97
I196
sI98
I198
ssg11
I0
sbsS'di'
p22
g2
(g3
g4
Ntp23
Rp24
(dp25
g8
I01
sg9
(dp26
I0
I01
sI1
I01
sI2
I01
sI3
I01
sI4
I01
sI5
I01
sI6
I01
sI7
I01
sI8
I01
sI9
I01
sI10
I01
sI11
I01
sI12
I01
sI13
I01
sI14
I01
sI15
I01
sI16
I01
sI17
I01
sI18
I01
sI19
I01
sI20
I01
sI21
I01
sI22
I01
sI23
I01
sI24
I01
sI25
I01
sI26
I01
sI27
I01
sI28
I01
sI29
I01
sI30
I01
sI31
I01
sI32
I01
sI33
I01
sI34
I01
sI35
I01
sI36
I01
sI37
I01
sI38
I01
sI39
I01
sI40
I01
sI41
I01
sI42
I01
sI43
I01
sI44
I01
sI45
I01
sI46
I01
sI47
I01
sI48
I01
sI49
I01
sI50
I01
sI51
I01
sI52
I01
sI53
I01
sI54
I01
sI55
I01
sI56
I01
sI57
I01
sI58
I01
sI59
I01
sI60
I01
sI61
I01
sI62
I01
sI63
I01
sI64
I01
sI65
I01
sI66
I01
sI67
I01
sI68
I01
sI69
I01
sI70
I01
sI71
I01
sI72
I01
sI73
I01
sI74
I01
sI75
I01
sI76
I01
sI77
I01
sI78
I01
sI79
I01
sI80
I01
sI81
I01
sI82
I01
sI83
I01
sI84
I01
sI85
I01
sI86
I01
sI87
I01
sI88
I01
sI89
I01
sI90
I01
sI91
I01
sI92
I01
sI93
I01
sI94
I01
sI95
I01
sI96
I01
sI97
I01
sI98
I01
ssg11
I0
sbs. pymodbus/examples/gui/gtk/simulator.glade 0000644 0001755 0001755 00000021166 12607272152 020670 0 ustar debacle debacle
TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKModbus SimulatorFalseGTK_WIN_POS_CENTER400200TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKDevice to SimulateTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK220TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKFalseFalse201TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKStarting Address230TrueTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKFalse2011TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKNumber of Devices230TrueTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK1 0 2000 1 10 0False2012TrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKGTK_BUTTONBOX_SPREADTrueTrueTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKgtk-helpTrue0TrueTrueTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKgtk-applyTrue01TrueTrueTrueGDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASKgtk-stopTrue023
pymodbus/examples/gui/gtk/simulator.py 0000755 0001755 0001755 00000030451 12607272152 020244 0 ustar debacle debacle #!/usr/bin/env python
#---------------------------------------------------------------------------#
# System
#---------------------------------------------------------------------------#
import os
import getpass
import pickle
from threading import Thread
#---------------------------------------------------------------------------#
# For Gui
#---------------------------------------------------------------------------#
from twisted.internet import gtk2reactor
gtk2reactor.install()
import gtk
from gtk import glade
#---------------------------------------------------------------------------#
# SNMP Simulator
#---------------------------------------------------------------------------#
from twisted.internet import reactor
from twisted.internet import error as twisted_error
from pymodbus.server.async import ModbusServerFactory
from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext
#--------------------------------------------------------------------------#
# Logging
#--------------------------------------------------------------------------#
import logging
log = logging.getLogger(__name__)
#---------------------------------------------------------------------------#
# Application Error
#---------------------------------------------------------------------------#
class ConfigurationException(Exception):
''' Exception for configuration error '''
def __init__(self, string):
Exception.__init__(self, string)
self.string = string
def __str__(self):
return 'Configuration Error: %s' % self.string
#---------------------------------------------------------------------------#
# Extra Global Functions
#---------------------------------------------------------------------------#
# These are extra helper functions that don't belong in a class
#---------------------------------------------------------------------------#
def root_test():
''' Simple test to see if we are running as root '''
return getpass.getuser() == "root"
#---------------------------------------------------------------------------#
# Simulator Class
#---------------------------------------------------------------------------#
class Simulator(object):
'''
Class used to parse configuration file and create and modbus
datastore.
The format of the configuration file is actually just a
python pickle, which is a compressed memory dump from
the scraper.
'''
def __init__(self, config):
'''
Trys to load a configuration file, lets the file not
found exception fall through
@param config The pickled datastore
'''
try:
self.file = open(config, "r")
except Exception:
raise ConfigurationException("File not found %s" % config)
def _parse(self):
''' Parses the config file and creates a server context '''
try:
handle = pickle.load(self.file)
dsd = handle['di']
csd = handle['ci']
hsd = handle['hr']
isd = handle['ir']
except KeyError:
raise ConfigurationException("Invalid Configuration")
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
return ModbusServerContext(slaves=slave)
def _simulator(self):
''' Starts the snmp simulator '''
ports = [502]+range(20000,25000)
for port in ports:
try:
reactor.listenTCP(port, ModbusServerFactory(self._parse()))
print 'listening on port', port
return port
except twisted_error.CannotListenError:
pass
def run(self):
''' Used to run the simulator '''
reactor.callWhenRunning(self._simulator)
#---------------------------------------------------------------------------#
# Network reset thread
#---------------------------------------------------------------------------#
# This is linux only, maybe I should make a base class that can be filled
# in for linux(debian/redhat)/windows/nix
#---------------------------------------------------------------------------#
class NetworkReset(Thread):
'''
This class is simply a daemon that is spun off at the end of the
program to call the network restart function (an easy way to
remove all the virtual interfaces)
'''
def __init__(self):
Thread.__init__(self)
self.setDaemon(True)
def run(self):
''' Run the network reset '''
os.system("/etc/init.d/networking restart")
#---------------------------------------------------------------------------#
# Main Gui Class
#---------------------------------------------------------------------------#
# Note, if you are using gtk2 before 2.12, the file_set signal is not
# introduced. To fix this, you need to apply the following patch
#---------------------------------------------------------------------------#
#Index: simulator.py
#===================================================================
#--- simulator.py (revision 60)
#+++ simulator.py (working copy)
#@@ -158,7 +161,7 @@
# "on_helpBtn_clicked" : self.help_clicked,
# "on_quitBtn_clicked" : self.close_clicked,
# "on_startBtn_clicked" : self.start_clicked,
#- "on_file_changed" : self.file_changed,
#+ #"on_file_changed" : self.file_changed,
# "on_window_destroy" : self.close_clicked
# }
# self.tree.signal_autoconnect(actions)
#@@ -235,6 +238,7 @@
# return False
#
# # check input file
#+ self.file_changed(self.tdevice)
# if os.path.exists(self.file):
# self.grey_out()
# handle = Simulator(config=self.file)
#---------------------------------------------------------------------------#
class SimulatorApp(object):
'''
This class implements the GUI for the flasher application
'''
file = "none"
subnet = 205
number = 1
restart = 0
def __init__(self, xml):
''' Sets up the gui, callback, and widget handles '''
#---------------------------------------------------------------------------#
# Action Handles
#---------------------------------------------------------------------------#
self.tree = glade.XML(xml)
self.bstart = self.tree.get_widget("startBtn")
self.bhelp = self.tree.get_widget("helpBtn")
self.bclose = self.tree.get_widget("quitBtn")
self.window = self.tree.get_widget("window")
self.tdevice = self.tree.get_widget("fileTxt")
self.tsubnet = self.tree.get_widget("addressTxt")
self.tnumber = self.tree.get_widget("deviceTxt")
#---------------------------------------------------------------------------#
# Actions
#---------------------------------------------------------------------------#
actions = {
"on_helpBtn_clicked" : self.help_clicked,
"on_quitBtn_clicked" : self.close_clicked,
"on_startBtn_clicked" : self.start_clicked,
"on_file_changed" : self.file_changed,
"on_window_destroy" : self.close_clicked
}
self.tree.signal_autoconnect(actions)
if not root_test():
self.error_dialog("This program must be run with root permissions!", True)
#---------------------------------------------------------------------------#
# Gui helpers
#---------------------------------------------------------------------------#
# Not callbacks, but used by them
#---------------------------------------------------------------------------#
def show_buttons(self, state=False, all=0):
''' Greys out the buttons '''
if all:
self.window.set_sensitive(state)
self.bstart.set_sensitive(state)
self.tdevice.set_sensitive(state)
self.tsubnet.set_sensitive(state)
self.tnumber.set_sensitive(state)
def destroy_interfaces(self):
''' This is used to reset the virtual interfaces '''
if self.restart:
n = NetworkReset()
n.start()
def error_dialog(self, message, quit=False):
''' Quick pop-up for error messages '''
dialog = gtk.MessageDialog(
parent = self.window,
flags = gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
type = gtk.MESSAGE_ERROR,
buttons = gtk.BUTTONS_CLOSE,
message_format = message)
dialog.set_title('Error')
if quit:
dialog.connect("response", lambda w, r: gtk.main_quit())
else:
dialog.connect("response", lambda w, r: w.destroy())
dialog.show()
#---------------------------------------------------------------------------#
# Button Actions
#---------------------------------------------------------------------------#
# These are all callbacks for the various buttons
#---------------------------------------------------------------------------#
def start_clicked(self, widget):
''' Starts the simulator '''
start = 1
base = "172.16"
# check starting network
net = self.tsubnet.get_text()
octets = net.split('.')
if len(octets) == 4:
base = "%s.%s" % (octets[0], octets[1])
net = int(octets[2]) % 255
start = int(octets[3]) % 255
else:
self.error_dialog("Invalid starting address!");
return False
# check interface size
size = int(self.tnumber.get_text())
if (size >= 1):
for i in range(start, (size + start)):
j = i % 255
cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j)
os.system(cmd)
if j == 254: net = net + 1
self.restart = 1
else:
self.error_dialog("Invalid number of devices!");
return False
# check input file
if os.path.exists(self.file):
self.show_buttons(state=False)
try:
handle = Simulator(config=self.file)
handle.run()
except ConfigurationException, ex:
self.error_dialog("Error %s" % ex)
self.show_buttons(state=True)
else:
self.error_dialog("Device to emulate does not exist!");
return False
def help_clicked(self, widget):
''' Quick pop-up for about page '''
data = gtk.AboutDialog()
data.set_version("0.1")
data.set_name(('Modbus Simulator'))
data.set_authors(["Galen Collins"])
data.set_comments(('First Select a device to simulate,\n'
+ 'then select the starting subnet of the new devices\n'
+ 'then select the number of device to simulate and click start'))
data.set_website("http://code.google.com/p/pymodbus/")
data.connect("response", lambda w,r: w.hide())
data.run()
def close_clicked(self, widget):
''' Callback for close button '''
self.destroy_interfaces()
reactor.stop() # quit twisted
def file_changed(self, widget):
''' Callback for the filename change '''
self.file = widget.get_filename()
#---------------------------------------------------------------------------#
# Main handle function
#---------------------------------------------------------------------------#
# This is called when the application is run from a console
# We simply start the gui and start the twisted event loop
#---------------------------------------------------------------------------#
def main():
'''
Main control function
This either launches the gui or runs the command line application
'''
debug = True
if debug:
try:
log.setLevel(logging.DEBUG)
logging.basicConfig()
except Exception, e:
print "Logging is not supported on this system"
simulator = SimulatorApp('./simulator.glade')
reactor.run()
#---------------------------------------------------------------------------#
# Library/Console Test
#---------------------------------------------------------------------------#
# If this is called from console, we start main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
main()
pymodbus/examples/gui/gui-common.py 0000755 0001755 0001755 00000010156 12607272152 017512 0 ustar debacle debacle #!/usr/bin/env python
#---------------------------------------------------------------------------#
# System
#---------------------------------------------------------------------------#
import os
import getpass
import pickle
from threading import Thread
#---------------------------------------------------------------------------#
# SNMP Simulator
#---------------------------------------------------------------------------#
from twisted.internet import reactor
from twisted.internet import error as twisted_error
from pymodbus.server.async import ModbusServerFactory
from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext
#--------------------------------------------------------------------------#
# Logging
#--------------------------------------------------------------------------#
import logging
log = logging.getLogger("pymodbus")
#---------------------------------------------------------------------------#
# Application Error
#---------------------------------------------------------------------------#
class ConfigurationException(Exception):
''' Exception for configuration error '''
pass
#---------------------------------------------------------------------------#
# Extra Global Functions
#---------------------------------------------------------------------------#
# These are extra helper functions that don't belong in a class
#---------------------------------------------------------------------------#
def root_test():
''' Simple test to see if we are running as root '''
return getpass.getuser() == "root"
#---------------------------------------------------------------------------#
# Simulator Class
#---------------------------------------------------------------------------#
class Simulator(object):
'''
Class used to parse configuration file and create and modbus
datastore.
The format of the configuration file is actually just a
python pickle, which is a compressed memory dump from
the scraper.
'''
def __init__(self, config):
'''
Trys to load a configuration file, lets the file not
found exception fall through
:param config: The pickled datastore
'''
try:
self.file = open(config, "r")
except Exception:
raise ConfigurationException("File not found %s" % config)
def _parse(self):
''' Parses the config file and creates a server context '''
try:
handle = pickle.load(self.file)
dsd = handle['di']
csd = handle['ci']
hsd = handle['hr']
isd = handle['ir']
except KeyError:
raise ConfigurationException("Invalid Configuration")
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
return ModbusServerContext(slaves=slave)
def _simulator(self):
''' Starts the snmp simulator '''
ports = [502]+range(20000,25000)
for port in ports:
try:
reactor.listenTCP(port, ModbusServerFactory(self._parse()))
log.debug('listening on port %d' % port)
return port
except twisted_error.CannotListenError:
pass
def run(self):
''' Used to run the simulator '''
log.debug('simulator started')
reactor.callWhenRunning(self._simulator)
#---------------------------------------------------------------------------#
# Network reset thread
#---------------------------------------------------------------------------#
# This is linux only, maybe I should make a base class that can be filled
# in for linux(debian/redhat)/windows/nix
#---------------------------------------------------------------------------#
class NetworkReset(Thread):
'''
This class is simply a daemon that is spun off at the end of the
program to call the network restart function (an easy way to
remove all the virtual interfaces)
'''
def __init__(self):
''' Initialize a new network reset thread '''
Thread.__init__(self)
self.setDaemon(True)
def run(self):
''' Run the network reset '''
os.system("/etc/init.d/networking restart")
pymodbus/examples/gui/bottle/ 0000755 0001755 0001755 00000000000 12607273533 016355 5 ustar debacle debacle pymodbus/examples/gui/bottle/frontend.py 0000644 0001755 0001755 00000025400 12607272152 020543 0 ustar debacle debacle '''
Pymodbus Web Frontend
=======================================
This is a simple web frontend using bottle as the web framework.
This can be hosted using any wsgi adapter.
'''
import json, inspect
from bottle import route, request, Bottle
from bottle import static_file
from bottle import jinja2_template as template
#---------------------------------------------------------------------------#
# configure the client logging
#---------------------------------------------------------------------------#
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# REST API
#---------------------------------------------------------------------------#
class Response(object):
'''
A collection of common responses for the frontend api
'''
success = { 'status' : 200 }
failure = { 'status' : 500 }
class ModbusApiWebApp(object):
'''
This is the web REST api interace into the pymodbus
service. It can be consumed by any utility that can
make web requests (javascript).
'''
_namespace = '/api/v1'
def __init__(self, server):
''' Initialize a new instance of the ModbusApi
:param server: The current server instance
'''
self._server = server
#---------------------------------------------------------------------#
# Device API
#---------------------------------------------------------------------#
def get_device(self):
return {
'mode' : self._server.control.Mode,
'delimiter' : self._server.control.Delimiter,
'readonly' : self._server.control.ListenOnly,
'identity' : self._server.control.Identity.summary(),
'counters' : dict(self._server.control.Counter),
'diagnostic' : self._server.control.getDiagnosticRegister(),
}
def get_device_identity(self):
return {
'identity' : dict(self._server.control.Identity)
}
def get_device_counters(self):
return {
'counters' : dict(self._server.control.Counter)
}
def get_device_events(self):
return {
'events' : self._server.control.Events
}
def get_device_plus(self):
return {
'plus' : dict(self._server.control.Plus)
}
def delete_device_events(self):
self._server.control.clearEvents()
return Response.success
def get_device_host(self):
return {
'hosts' : list(self._server.access)
}
def post_device_host(self):
value = request.forms.get('host')
if value:
self._server.access.add(value)
return Response.success
def delete_device_host(self):
value = request.forms.get('host')
if value:
self._server.access.remove(value)
return Response.success
def post_device_delimiter(self):
value = request.forms.get('delimiter')
if value:
self._server.control.Delimiter = value
return Response.success
def post_device_mode(self):
value = request.forms.get('mode')
if value:
self._server.control.Mode = value
return Response.success
def post_device_reset(self):
self._server.control.reset()
return Response.success
#---------------------------------------------------------------------#
# Datastore Get API
#---------------------------------------------------------------------#
def __get_data(self, store, address, count, slave='00'):
try:
address, count = int(address), int(count)
context = self._server.store[int(store)]
values = context.getValues(store, address, count)
values = dict(zip(range(address, address + count), values))
result = { 'data' : values }
result.update(Response.success)
return result
except Exception, ex: log.error(ex)
return Response.failure
def get_coils(self, address='0', count='1'):
return self.__get_data(1, address, count)
def get_discretes(self, address='0', count='1'):
return self.__get_data(2, address, count)
def get_holdings(self, address='0', count='1'):
return self.__get_data(3, address, count)
def get_inputs(self, address='0', count='1'):
return self.__get_data(4, address, count)
#---------------------------------------------------------------------#
# Datastore Update API
#---------------------------------------------------------------------#
def __set_data(self, store, address, values, slave='00'):
try:
address = int(address)
values = json.loads(values)
print values
context = self._server.store[int(store)]
context.setValues(store, address, values)
return Response.success
except Exception, ex: log.error(ex)
return Response.failure
def post_coils(self, address='0'):
values = request.forms.get('data')
return self.__set_data(1, address, values)
def post_discretes(self, address='0'):
values = request.forms.get('data')
return self.__set_data(2, address, values)
def post_holding(self, address='0'):
values = request.forms.get('data')
return self.__set_data(3, address, values)
def post_inputs(self, address='0'):
values = request.forms.get('data')
return self.__set_data(4, address, values)
#---------------------------------------------------------------------#
# webpage routes
#---------------------------------------------------------------------#
def register_web_routes(application, register):
''' A helper method to register the default web routes of
a single page application.
:param application: The application instance to register
:param register: The bottle instance to register the application with
'''
def get_index_file():
return template('index.html')
def get_static_file(filename):
return static_file(filename, root='./media')
register.route('/', method='GET', name='get_index_file')(get_index_file)
register.route('/media/', method='GET', name='get_static_file')(get_static_file)
#---------------------------------------------------------------------------#
# Configurations
#---------------------------------------------------------------------------#
def register_api_routes(application, register):
''' A helper method to register the routes of an application
based on convention. This is easier to manage than having to
decorate each method with a static route name.
:param application: The application instance to register
:param register: The bottle instance to register the application with
'''
log.info("installing application routes:")
methods = inspect.getmembers(application)
methods = filter(lambda n: not n[0].startswith('_'), methods)
for method, func in dict(methods).iteritems():
pieces = method.split('_')
verb, path = pieces[0], pieces[1:]
args = inspect.getargspec(func).args[1:]
args = ['<%s>' % arg for arg in args]
args = '/'.join(args)
args = '' if len(args) == 0 else '/' + args
path.insert(0, application._namespace)
path = '/'.join(path) + args
log.info("%6s: %s" % (verb, path))
register.route(path, method=verb, name=method)(func)
def build_application(server):
''' Helper method to create and initiailze a bottle application
:param server: The modbus server to pull instance data from
:returns: An initialied bottle application
'''
log.info("building web application")
api = ModbusApiWebApp(server)
register = Bottle()
register_api_routes(api, register)
register_web_routes(api, register)
return register
#---------------------------------------------------------------------------#
# Start Methods
#---------------------------------------------------------------------------#
def RunModbusFrontend(server, port=8080):
''' Helper method to host bottle in twisted
:param server: The modbus server to pull instance data from
:param port: The port to host the service on
'''
from bottle import TwistedServer, run
application = build_application(server)
run(app=application, server=TwistedServer, port=port)
def RunDebugModbusFrontend(server, port=8080):
''' Helper method to start the bottle server
:param server: The modbus server to pull instance data from
:param port: The port to host the service on
'''
from bottle import run
application = build_application(server)
run(app=application, port=port)
if __name__ == '__main__':
# ------------------------------------------------------------
# an example server configuration
# ------------------------------------------------------------
from pymodbus.server.async import ModbusServerFactory
from pymodbus.constants import Defaults
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet import reactor
# ------------------------------------------------------------
# initialize the identity
# ------------------------------------------------------------
identity = ModbusDeviceIdentification()
identity.VendorName = 'Pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
# ------------------------------------------------------------
# initialize the datastore
# ------------------------------------------------------------
store = ModbusSlaveContext(
di = ModbusSequentialDataBlock(0, [17]*100),
co = ModbusSequentialDataBlock(0, [17]*100),
hr = ModbusSequentialDataBlock(0, [17]*100),
ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
# ------------------------------------------------------------
# initialize the factory
# ------------------------------------------------------------
address = ("", Defaults.Port)
factory = ModbusServerFactory(context, None, identity)
# ------------------------------------------------------------
# start the servers
# ------------------------------------------------------------
log.info("Starting Modbus TCP Server on %s:%s" % address)
reactor.listenTCP(address[1], factory, interface=address[0])
RunDebugModbusFrontend(factory)
pymodbus/examples/gui/bottle/requirements.txt 0000644 0001755 0001755 00000000316 12607272152 021635 0 ustar debacle debacle # -------------------------------------------------------------------
# if you want to use this frontend uncomment these
# -------------------------------------------------------------------
bottle==0.11.2
pymodbus/examples/gui/bottle/views/ 0000755 0001755 0001755 00000000000 12607272152 017506 5 ustar debacle debacle pymodbus/examples/gui/bottle/views/index.html 0000644 0001755 0001755 00000010436 12607272152 021507 0 ustar debacle debacle
{% block title %}pymodbus | server{% endblock %}