pymodbus-1.3.2/0000755000175000017500000000000013150641274011516 5ustar wmbwmbpymodbus-1.3.2/ez_setup.py0000755000175000017500000002423013150360615013727 0ustar wmbwmb#!python from __future__ import print_function """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.6c11" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] IS_PYTHON3 = sys.version_info[0] == 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.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', '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 print_error(msg, **kwargs): print(msg, file=sys.stderr, **kwargs) def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print_error("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 as e: if was_imported: print_error(( "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) except pkg_resources.DistributionNotFound: pass del pkg_resources, sys.modules['pkg_resources'] # reload ok 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 urllib.request, urllib.error, urllib.parse, 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 = urllib.request.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(( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ), file=sys.stderr) 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 list(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("Internal error!", file=sys.stderr) 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-1.3.2/setup_commands.py0000755000175000017500000001264613150360615015122 0ustar wmbwmbfrom 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-1.3.2/requirements-checks.txt0000644000175000017500000000015513150360615016236 0ustar wmbwmb# Python packages required to run `make check'. flake8 >= 2.6.0 flake8-docstrings >= 0.2.8 pyflakes >= 1.2.3 pymodbus-1.3.2/examples/0000755000175000017500000000000013150641044013327 5ustar wmbwmbpymodbus-1.3.2/examples/contrib/0000755000175000017500000000000013150360615014771 5ustar wmbwmbpymodbus-1.3.2/examples/contrib/sunspec_client.py0000644000175000017500000002537613150360615020376 0ustar wmbwmbfrom 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-1.3.2/examples/contrib/redis-datastore.py0000644000175000017500000002201613150360615020436 0ustar wmbwmbimport 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-1.3.2/examples/contrib/message-generator.py0000755000175000017500000001713613150360615020766 0ustar wmbwmb#!/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 as 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-1.3.2/examples/contrib/modbus_mapper.py0000644000175000017500000002516313150360615020207 0ustar wmbwmb''' 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-1.3.2/examples/contrib/modbus-scraper.py0000755000175000017500000002424313150360615020301 0ustar wmbwmb#!/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-1.3.2/examples/contrib/README.rst0000644000175000017500000000325213150360615016462 0ustar wmbwmb============================================================ 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-1.3.2/examples/contrib/thread_safe_datastore.py0000644000175000017500000002010413150360615021653 0ustar wmbwmbimport 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-1.3.2/examples/contrib/concurrent-client.py0000755000175000017500000002323613150360615021012 0ustar wmbwmb#!/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-1.3.2/examples/contrib/rx-messages0000644000175000017500000001421513150360615017155 0ustar wmbwmb# ------------------------------------------------------------ # 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-1.3.2/examples/contrib/remote_server_context.py0000644000175000017500000001646113150360615022000 0ustar wmbwmb''' 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 = self.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 = self.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))) self.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-1.3.2/examples/contrib/tx-messages0000644000175000017500000001360213150360615017156 0ustar wmbwmb# ------------------------------------------------------------ # 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-1.3.2/examples/contrib/message-parser.py0000755000175000017500000001371113150360615020267 0ustar wmbwmb#!/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 #---------------------------------------------------------------------------# from __future__ import print_function 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.encode()) if decoder.checkFrame(): decoder.advanceFrame() decoder.processIncomingPacket(message, self.report) else: self.check_errors(decoder, message) except Exception as 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 as e: print("Logging is not supported on this system- {}".format(e)) 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-1.3.2/examples/contrib/libmodbus-client.py0000755000175000017500000004212213150360615020603 0ustar wmbwmb#!/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-1.3.2/examples/contrib/bcd-payload.py0000644000175000017500000001525413150360615017531 0ustar wmbwmb''' 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-1.3.2/examples/contrib/requirements.txt0000644000175000017500000000035013150360615020253 0ustar wmbwmb# ------------------------------------------------------------------- # if you want to use the custom data stores, uncomment these # ------------------------------------------------------------------- SQLAlchemy==0.7.9 redis==2.6.2 pymodbus-1.3.2/examples/contrib/database-datastore.py0000644000175000017500000001501013150360615021070 0ustar wmbwmbimport 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-1.3.2/examples/contrib/modbus-simulator.py0000644000175000017500000001033513150360615020653 0ustar wmbwmb#!/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-1.3.2/examples/contrib/modicon-payload.py0000644000175000017500000002177213150360615020433 0ustar wmbwmb''' 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, endian): ''' Initialize a new payload decoder :param payload: The payload to decode with ''' self._payload = payload self._pointer = 0x00 self._endian = endian @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-1.3.2/examples/contrib/modbus_saver.py0000644000175000017500000001230713150360615020037 0ustar wmbwmb''' 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-1.3.2/examples/contrib/serial-forwarder.py0000755000175000017500000000316313150360615020621 0ustar wmbwmb#!/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-1.3.2/examples/functional/0000755000175000017500000000000013150360615015473 5ustar wmbwmbpymodbus-1.3.2/examples/functional/synchronous-tcp-client.py0000644000175000017500000000146113150360615022501 0ustar wmbwmb#!/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-1.3.2/examples/functional/asynchronous-ascii-client.py0000644000175000017500000000142413150360615023143 0ustar wmbwmb#!/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-1.3.2/examples/functional/remote-slave-context.py0000644000175000017500000000177513150360615022144 0ustar wmbwmb#!/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-1.3.2/examples/functional/synchronous-ascii-client.py0000644000175000017500000000174413150360615023007 0ustar wmbwmb#!/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-1.3.2/examples/functional/base_runner.py0000644000175000017500000000607713150360615020362 0ustar wmbwmbimport 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-1.3.2/examples/functional/__init__.py0000644000175000017500000000000013150360615017572 0ustar wmbwmbpymodbus-1.3.2/examples/functional/README.rst0000644000175000017500000000177613150360615017175 0ustar wmbwmb============================================================================ 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-1.3.2/examples/functional/redis-slave-context.py0000644000175000017500000000161013150360615021743 0ustar wmbwmb#!/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-1.3.2/examples/functional/asynchronous-udp-client.py0000644000175000017500000000140613150360615022643 0ustar wmbwmb#!/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-1.3.2/examples/functional/memory-slave-context.py0000755000175000017500000000202113150360615022145 0ustar wmbwmb#!/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-1.3.2/examples/functional/database-slave-context.py0000644000175000017500000000170413150360615022405 0ustar wmbwmb#!/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-1.3.2/examples/functional/asynchronous-tcp-client.py0000644000175000017500000000221613150360615022641 0ustar wmbwmb#!/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-1.3.2/examples/functional/synchronous-udp-client.py0000644000175000017500000000140413150360615022500 0ustar wmbwmb#!/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-1.3.2/examples/functional/base_context.py0000644000175000017500000000414213150360615020524 0ustar wmbwmbimport 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-1.3.2/examples/functional/synchronous-rtu-client.py0000644000175000017500000000165713150360615022534 0ustar wmbwmb#!/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-1.3.2/examples/functional/asynchronous-rtu-client.py0000644000175000017500000000143513150360615022667 0ustar wmbwmb#!/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-1.3.2/examples/common/0000755000175000017500000000000013150360615014621 5ustar wmbwmbpymodbus-1.3.2/examples/common/changing-framers.py0000755000175000017500000000512313150360615020412 0ustar wmbwmb#!/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-1.3.2/examples/common/asynchronous-server.py0000755000175000017500000001210313150360615021232 0ustar wmbwmb#!/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 from 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-1.3.2/examples/common/callback-server.py0000755000175000017500000001172013150360615020237 0ustar wmbwmb#!/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.keys()} 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-1.3.2/examples/common/custom-datablock.py0000755000175000017500000000610713150360615020436 0ustar wmbwmb#!/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 __future__ import print_function 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([0]*100) 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-1.3.2/examples/common/synchronous-client.py0000755000175000017500000001465613150360615021060 0ustar wmbwmb#!/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=5020) #client = ModbusClient(method='ascii', port='/dev/pts/2', timeout=1) # client = ModbusClient(method='rtu', port='/dev/ttyp0', 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` #---------------------------------------------------------------------------# log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=0x01) #---------------------------------------------------------------------------# # 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). #---------------------------------------------------------------------------# log.debug("Write to a Coil and read back") rq = client.write_coil(0, True, unit=1) rr = client.read_coils(0, 1, unit=1) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = client.write_coils(1, [True]*8, unit=1) assert(rq.function_code < 0x80) # test that we are not an error rr = client.read_coils(1, 21, unit=1) assert(rr.function_code < 0x80) # test that we are not an error resp = [True]*21 # If the returned output quantity is not a multiple of eight, # the remaining bits in the final data byte will be padded with zeros # (toward the high order end of the byte). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = client.write_coils(1, [False]*8, unit=1) rr = client.read_coils(1, 8, unit=1) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = client.read_discrete_inputs(0, 8, unit=1) assert(rq.function_code < 0x80) # test that we are not an error log.debug("Write to a holding register and read back") rq = client.write_register(1, 10, unit=1) rr = client.read_holding_registers(1, 1, unit=1) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = client.write_registers(1, [10]*8, unit=1) rr = client.read_holding_registers(1, 8, unit=1) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = client.read_input_registers(1, 8, unit=1) assert(rq.function_code < 0x80) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = client.readwrite_registers(unit=1, **arguments) rr = client.read_holding_registers(1, 8, unit=1) 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-1.3.2/examples/common/README.rst0000644000175000017500000000737413150360615016323 0ustar wmbwmb============================================================ 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-1.3.2/examples/common/custom-message.py0000755000175000017500000000663113150360615020140 0ustar wmbwmb#!/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-1.3.2/examples/common/modbus-payload.py0000755000175000017500000000630513150360615020122 0ustar wmbwmb#!/usr/bin/env python ''' Pymodbus Payload Building/Decoding Example -------------------------------------------------------------------------- # Run modbus-payload-server.py or synchronous-server.py to check the behavior ''' from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.compat import iteritems #---------------------------------------------------------------------------# # 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', port=5020) 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 # - another 16 bit unsigned int 0x5678 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] #---------------------------------------------------------------------------# builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_string('abcdefgh') builder.add_32bit_float(22.34) builder.add_16bit_uint(0x1234) builder.add_16bit_uint(0x5678) builder.add_8bit_int(0x12) builder.add_bits([0,1,0,1,1,0,1,0]) payload = builder.build() address = 0 result = client.write_registers(address, payload, skip_encode=True, unit=1) #---------------------------------------------------------------------------# # 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 # - another 16 bit unsigned int which we will ignore # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] #---------------------------------------------------------------------------# address = 0x00 count = 8 result = client.read_holding_registers(address, count, unit=1) decoder = BinaryPayloadDecoder.fromRegisters(result.registers, endian=Endian.Big) decoded = { 'string': decoder.decode_string(8), 'float': decoder.decode_32bit_float(), '16uint': decoder.decode_16bit_uint(), 'ignored': decoder.skip_bytes(2), '8int': decoder.decode_8bit_int(), 'bits': decoder.decode_bits(), } print("-" * 60) print("Decoded Data") print("-" * 60) for name, value in iteritems(decoded): print ("%s\t" % name, value) #---------------------------------------------------------------------------# # close the client #---------------------------------------------------------------------------# client.close() pymodbus-1.3.2/examples/common/modbus-logging.py0000755000175000017500000000344613150360615020122 0ustar wmbwmb#!/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-1.3.2/examples/common/synchronous-client-ext.py0000755000175000017500000001717213150360615021652 0ustar wmbwmb#!/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(method='rtu', port="/dev/ttyp0") # client = ModbusClient('127.0.0.1', port=5020) 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(unit=1) 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] == b'Pymodbus') # test the vendor name assert(rr.information[1] == b'PM') # test the product code assert(rr.information[2] == b'1.0') # test the code revision rq = ReportSlaveIdRequest(unit=1) 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(unit=1) 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(unit=1) 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(unit=1) 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(unit=1) rr = client.execute(rq) # assert(rr == None) # not supported by reference #assert(rr.message[0] == 0x0000) # test the resulting message rq = RestartCommunicationsOptionRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference #assert(rr.message == 0x0000) # test the resulting message rq = ReturnDiagnosticRegisterRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ChangeAsciiInputDelimiterRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ForceListenOnlyModeRequest(unit=1) client.execute(rq) # does not send a response rq = ClearCountersRequest() rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnBusCommunicationErrorCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnBusExceptionErrorCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnSlaveMessageCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnSlaveNoResponseCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnSlaveNAKCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnSlaveBusyCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnSlaveBusCharacterOverrunCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ReturnIopOverrunCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = ClearOverrunCountRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference rq = GetClearModbusPlusRequest(unit=1) rr = client.execute(rq) #assert(rr == None) # not supported by reference #---------------------------------------------------------------------------# # close the client #---------------------------------------------------------------------------# client.close() pymodbus-1.3.2/examples/common/asynchronous-client.py0000755000175000017500000001343313150360615021211 0ustar wmbwmb#!/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", 5020) defer.addCallback(beginAsynchronousTest) reactor.run() pymodbus-1.3.2/examples/common/performance.py0000755000175000017500000000732313150360615017504 0ustar wmbwmb#!/usr/bin/env python ''' Pymodbus Performance Example -------------------------------------------------------------------------- The following is an quick performance check of the synchronous modbus client. ''' #---------------------------------------------------------------------------# # import the necessary modules #---------------------------------------------------------------------------# from __future__ import print_function import logging, os from time import time from multiprocessing import log_to_stderr # from pymodbus.client.sync import ModbusTcpClient from pymodbus.client.sync import ModbusSerialClient #---------------------------------------------------------------------------# # choose between threads or processes #---------------------------------------------------------------------------# #from multiprocessing import Process as Worker from threading import Thread as Worker from threading import Lock _thread_lock = Lock() #---------------------------------------------------------------------------# # 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 = 10 cycles = 1000 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, port=5020) client = ModbusSerialClient(method="rtu", port="/dev/ttyp0", baudrate=9600) while count < cycles: with _thread_lock: client.read_holding_registers(10, 1, unit=1).registers[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. # RTU 32 requests/second @9600 # TCP 31430 requests/second #---------------------------------------------------------------------------# 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))) print("time taken to complete %s cycle by %s workers is %s seconds" % (cycles, workers, stop-start)) pymodbus-1.3.2/examples/common/device-mapping0000644000175000017500000000004313150360615017431 0ustar wmbwmb0x0001,/dev/ptyp0 0x0002,/dev/ptyp1pymodbus-1.3.2/examples/common/updating-server.py0000755000175000017500000000671313150360615020324 0ustar wmbwmb#!/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-1.3.2/examples/common/modbus-payload-server.py0000755000175000017500000000605513150360615021430 0ustar wmbwmb#!/usr/bin/env python ''' Pymodbus Server Payload Example -------------------------------------------------------------------------- If you want to initialize a server context with a complicated memory layout, you can actually use the payload builder. ''' #---------------------------------------------------------------------------# # import the various server implementations #---------------------------------------------------------------------------# from pymodbus.server.sync import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext #---------------------------------------------------------------------------# # import the payload builder #---------------------------------------------------------------------------# from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder #---------------------------------------------------------------------------# # configure the service logging #---------------------------------------------------------------------------# import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) #---------------------------------------------------------------------------# # build your payload #---------------------------------------------------------------------------# builder = BinaryPayloadBuilder(endian=Endian.Little) # builder.add_string('abcdefgh') # builder.add_32bit_float(22.34) # builder.add_16bit_uint(4660) # builder.add_8bit_int(18) builder.add_bits([0,1,0,1,1,0,1,0]) #---------------------------------------------------------------------------# # use that payload in the data store #---------------------------------------------------------------------------# # Here we use the same reference block for each underlying store. #---------------------------------------------------------------------------# block = ModbusSequentialDataBlock(1, builder.to_registers()) store = ModbusSlaveContext(di = block, co = block, hr = block, ir = block) 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)) pymodbus-1.3.2/examples/common/asynchronous-processor.py0000755000175000017500000001611013150360615021745 0ustar wmbwmb#!/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-1.3.2/examples/common/synchronous-server.py0000755000175000017500000001225113150360615021075 0ustar wmbwmb#!/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/riptideio/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/ptyp0', timeout=.005, baudrate=9600) pymodbus-1.3.2/examples/twisted/0000755000175000017500000000000013150360615015014 5ustar wmbwmbpymodbus-1.3.2/examples/twisted/modbus-udp.tac0000644000175000017500000000175013150360615017567 0ustar wmbwmb''' 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-1.3.2/examples/twisted/modbus-tcp.tac0000644000175000017500000000175713150360615017574 0ustar wmbwmb''' 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-1.3.2/examples/twisted/plugins/0000755000175000017500000000000013150360615016475 5ustar wmbwmbpymodbus-1.3.2/examples/twisted/plugins/pymodbus_plugin.py0000644000175000017500000000364113150360615022273 0ustar wmbwmb''' ''' 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-1.3.2/examples/gui/0000755000175000017500000000000013150360615014115 5ustar wmbwmbpymodbus-1.3.2/examples/gui/gtk/0000755000175000017500000000000013150360615014702 5ustar wmbwmbpymodbus-1.3.2/examples/gui/gtk/sims/0000755000175000017500000000000013150360615015655 5ustar wmbwmbpymodbus-1.3.2/examples/gui/gtk/sims/example.sim0000644000175000017500000000755213150360615020033 0ustar wmbwmb(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-1.3.2/examples/gui/gtk/simulator.glade0000644000175000017500000002116613150360615017725 0ustar wmbwmb True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Modbus Simulator False GTK_WIN_POS_CENTER 400 200 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Device to Simulate True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 220 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False False 20 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Starting Address 230 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 20 1 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Number of Devices 230 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 0 2000 1 10 0 False 20 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_SPREAD True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-help True 0 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True 0 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-stop True 0 2 3 pymodbus-1.3.2/examples/gui/gtk/simulator.py0000755000175000017500000003045113150360615017301 0ustar wmbwmb#!/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-1.3.2/examples/gui/gui-common.py0000755000175000017500000001015613150360615016547 0ustar wmbwmb#!/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-1.3.2/examples/gui/bottle/0000755000175000017500000000000013150641044015404 5ustar wmbwmbpymodbus-1.3.2/examples/gui/bottle/frontend.py0000644000175000017500000002550313150360615017604 0ustar wmbwmb''' Pymodbus Web Frontend ======================================= This is a simple web frontend using bottle as the web framework. This can be hosted using any wsgi adapter. ''' from __future__ import print_function 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 as 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 as 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-1.3.2/examples/gui/bottle/requirements.txt0000644000175000017500000000031613150360615020672 0ustar wmbwmb# ------------------------------------------------------------------- # if you want to use this frontend uncomment these # ------------------------------------------------------------------- bottle==0.11.2 pymodbus-1.3.2/examples/gui/bottle/views/0000755000175000017500000000000013150360615016543 5ustar wmbwmbpymodbus-1.3.2/examples/gui/bottle/views/index.html0000644000175000017500000001043613150360615020544 0ustar wmbwmb {% block title %}pymodbus | server{% endblock %}

Device Identity

Device Counters

Device Settings

pymodbus-1.3.2/examples/gui/tk/0000755000175000017500000000000013150360615014533 5ustar wmbwmbpymodbus-1.3.2/examples/gui/tk/fileopen.gif0000644000175000017500000000216013150360615017022 0ustar wmbwmbGIF89aç¢>?>LMJLNJMOLNOLOQMQRNRTPSURTVRUWSWYV[]X[]Y^`Zegegh[gi`lnhprdqsdtvguwhxyrxzkxzsy{lz|l{|n|~o~€p€‚rƒs†‡w‹ŒzŒ{ŒŽ|Ž}„Ž‚ƒ‘€’„’“’“‚’“…’“†“”ƒ“•ƒ”•‡“•‹”–„•–„”–ˆ•—‰—˜Ž—™†˜™‡˜š‹š›‰š›Ššœ‡›œœœž‹œžŽžŸ‹ŸŸŽžŸŸ ž •Ÿ¡ ¡Ž  ™ ¢Ž¡£¡£’¢¤£¥“¤¦‘¤¦”¥¦”¥§”¦§“¥¨”§¨–¨ª”©ª—©«•©«–ª«™ª¬š«¬˜ª¬ž«®›¬® ­®¡­¯š­¯œ­¯¡®¯¤®°¯±œ¯±°±ž¯±¡°±¡°²Ÿ±²Ÿ±³±³ž±³¢²´ ²´¡³´¡´µ¡´µ¤´¶£¶·¢¶·¥·¹¤¸º§¸º¨¹»¦¹»§¹»©»¼¨»½©»¾«¼¾©½¿«¾¾¼½À­¾Á¬À¯Á®À²ÁîÁóÂÄ®ÂıÂÄ´Ãŵį´ÅDzÅǶÈÉ·ÈÉ»Ê̺ËÍ»ÍκÎϽÎнÏѾÑÓÀÓÔÂÔÔÂÓÔÆîîì÷÷öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þCreated with GIMP!ù ÿ,þÿ H° Á *Tpð ‚2"F,ð!AzhÔ ÂÂÿa$A²d™“(Qš‰ˆ ?F ˆ’K¢rêÜ)JA–>|À ª¨Q£=¿$@øÈCœ…¢J•št©ÀŠ8l zôhÏ0VE2Ò€êÔ©_Y>²Pk×¢i™R˜  T̘"$Ø‹àÁ’O˜"9BdHŸ= 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 filename = self.tdevice_value.get() if os.path.exists(filename): self.show_buttons(state=False) try: handle = Simulator(config=filename) 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): ''' 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): ''' Callback for close button ''' #self.destroy_interfaces() reactor.stop() def file_clicked(self): ''' Callback for the filename change ''' file = OpenFilename() self.tdevice_value.set(file) class SimulatorApp(object): ''' The main wx application handle for our simulator ''' def __init__(self, master): ''' Called by wxWindows to initialize our application :param master: The master window to connect to ''' font = ('Helvetica', 12, 'normal') frame = SimulatorFrame(master, font) frame.pack() #---------------------------------------------------------------------------# # 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(root) root.title("Modbus Simulator") reactor.run() #---------------------------------------------------------------------------# # Library/Console Test #---------------------------------------------------------------------------# # If this is called from console, we start main #---------------------------------------------------------------------------# if __name__ == "__main__": main() pymodbus-1.3.2/examples/gui/wx/0000755000175000017500000000000013150360615014553 5ustar wmbwmbpymodbus-1.3.2/examples/gui/wx/simulator.py0000755000175000017500000002642513150360615017160 0ustar wmbwmb#!/usr/bin/env python ''' Note that this is not finished ''' #---------------------------------------------------------------------------# # System #---------------------------------------------------------------------------# import os import getpass import pickle from threading import Thread #---------------------------------------------------------------------------# # For Gui #---------------------------------------------------------------------------# import wx from twisted.internet import wxreactor wxreactor.install() #---------------------------------------------------------------------------# # 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 ''' 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())) 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): ''' Initializes a new instance of the network reset thread ''' Thread.__init__(self) self.setDaemon(True) def run(self): ''' Run the network reset ''' os.system("/etc/init.d/networking restart") #---------------------------------------------------------------------------# # Main Gui Class #---------------------------------------------------------------------------# class SimulatorFrame(wx.Frame): ''' This class implements the GUI for the flasher application ''' subnet = 205 number = 1 restart = 0 def __init__(self, parent, id, title): ''' Sets up the gui, callback, and widget handles ''' wx.Frame.__init__(self, parent, id, title) wx.EVT_CLOSE(self, self.close_clicked) #---------------------------------------------------------------------------# # Add button row #---------------------------------------------------------------------------# panel = wx.Panel(self, -1) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(wx.Button(panel, 1, 'Apply'), 1) box.Add(wx.Button(panel, 2, 'Help'), 1) box.Add(wx.Button(panel, 3, 'Close'), 1) panel.SetSizer(box) #---------------------------------------------------------------------------# # Add input boxes #---------------------------------------------------------------------------# #self.tdevice = self.tree.get_widget("fileTxt") #self.tsubnet = self.tree.get_widget("addressTxt") #self.tnumber = self.tree.get_widget("deviceTxt") #---------------------------------------------------------------------------# # Tie callbacks #---------------------------------------------------------------------------# self.Bind(wx.EVT_BUTTON, self.start_clicked, id=1) self.Bind(wx.EVT_BUTTON, self.help_clicked, id=2) self.Bind(wx.EVT_BUTTON, self.close_clicked, id=3) #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 ''' log.debug("error event called") dialog = wx.MessageDialog(self, message, 'Error', wx.OK | wx.ICON_ERROR) dialog.ShowModel() if quit: self.Destroy() dialog.Destroy() #---------------------------------------------------------------------------# # 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, event): ''' Callback for close button ''' log.debug("close event called") reactor.stop() def file_changed(self, event): ''' Callback for the filename change ''' self.file = widget.get_filename() class SimulatorApp(wx.App): ''' The main wx application handle for our simulator ''' def OnInit(self): ''' Called by wxWindows to initialize our application :returns: Always True ''' log.debug("application initialize event called") reactor.registerWxApp(self) frame = SimulatorFrame(None, -1, "Pymodbus Simulator") frame.CenterOnScreen() frame.Show(True) self.SetTopWindow(frame) return True #---------------------------------------------------------------------------# # 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(0) reactor.run() #---------------------------------------------------------------------------# # Library/Console Test #---------------------------------------------------------------------------# # If this is called from console, we start main #---------------------------------------------------------------------------# if __name__ == "__main__": main() pymodbus-1.3.2/requirements-tests.txt0000644000175000017500000000030013150360615016130 0ustar wmbwmbcapturer >= 2.2 coverage >= 4.2 mock >= 1.0.1 nose>=1.3.7 pep8>=1.7.0 verboselogs >= 1.5 Twisted>=17.1.0 zope.interface>=4.4.0 pyasn1>=0.2.3 pycrypto>=2.6.1 #wsgiref>=0.1.2 cryptography>=1.8.1pymodbus-1.3.2/tox.ini0000644000175000017500000000065713150360615013036 0ustar wmbwmb# Tox (http://tox.testrun.org/) is a tool for running tests in multiple # virtualenvs. This configuration file will run the test suite on all supported # python versions. To use it, "pip install tox" and then run "tox" from this # directory. [tox] envlist = py27, py34, py35, py36, pypy [testenv] deps = -requirements-tests.txt commands = nostests {posargs} [flake8] exclude = .tox ignore = D211,D400,E731 max-line-length = 120 pymodbus-1.3.2/.gitignore0000644000175000017500000000045513150360615013507 0ustar wmbwmb*.pyc *.swp build/ dist/ pymodbus.egg-info/ .coverage .vscode .idea .noseids .idea/ .tox/ doc/api/epydoc/html/ .vscode/ .venv __pycache__/ pymodbus/__pycache__/ pymodbus/client/__pycache__/ pymodbus/datastore/__pycache__/ pymodbus/internal/__pycache__/ pymodbus/server/__pycache__/ test/__pycache__/ pymodbus-1.3.2/.coveragerc0000644000175000017500000000003413150360615013631 0ustar wmbwmb[report] show_missing = Truepymodbus-1.3.2/.pydevproject0000644000175000017500000000065113150360615014234 0ustar wmbwmb /pymodbus python 2.7 Pymodbus Environment pymodbus-1.3.2/README.rst0000644000175000017500000001510113150360615013200 0ustar wmbwmb.. image:: https://travis-ci.org/riptideio/pymodbus.svg?branch=master :target: https://travis-ci.org/riptideio/pymodbus .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/pymodbus_dev/Lobby .. image:: https://readthedocs.org/projects/pymodbus-n/badge/?version=latest :target: http://pymodbus-n.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status ============================================================ Summary ============================================================ Pymodbus is a full Modbus protocol implementation using twisted for its asynchronous communications core. It can also be used without any third party dependencies (aside from pyserial) if a more lightweight project is needed. Furthermore, it should work fine under any python version > 2.3 with a python 3.0 branch currently being maintained as well. ============================================================ Features ============================================================ ------------------------------------------------------------ Client Features ------------------------------------------------------------ * Full read/write protocol on discrete and register * Most of the extended protocol (diagnostic/file/pipe/setting/information) * TCP, UDP, Serial ASCII, Serial RTU, and Serial Binary * asynchronous(powered by twisted) and synchronous versions * Payload builder/decoder utilities ------------------------------------------------------------ Server Features ------------------------------------------------------------ * Can function as a fully implemented modbus server * TCP, UDP, Serial ASCII, Serial RTU, and Serial Binary * asynchronous(powered by twisted) and synchronous versions * Full server control context (device information, counters, etc) * A number of backing contexts (database, redis, a slave device) ============================================================ Use Cases ============================================================ Although most system administrators will find little need for a Modbus server on any modern hardware, they may find the need to query devices on their network for status (PDU, PDR, UPS, etc). Since the library is written in python, it allows for easy scripting and/or integration into their existing solutions. Continuing, most monitoring software needs to be stress tested against hundreds or even thousands of devices (why this was originally written), but getting access to that many is unwieldy at best. The pymodbus server will allow a user to test as many devices as their base operating system will allow (*allow* in this case means how many Virtual IP addresses are allowed). For more information please browse the project documentation: http://riptideio.github.io/pymodbus/ or http://readthedocs.org/docs/pymodbus/en/latest/index.html ------------------------------------------------------------ Example Code ------------------------------------------------------------ For those of you that just want to get started fast, here you go:: from pymodbus.client.sync import ModbusTcpClient client = ModbusTcpClient('127.0.0.1') client.write_coil(1, True) result = client.read_coils(1,1) print result.bits[0] client.close() For more advanced examples, check out the examples included in the respository. If you have created any utilities that meet a specific need, feel free to submit them so others can benefit. Also, if you have questions, please ask them on the mailing list so that others can benefit from the results and so that I can trace them. I get a lot of email and sometimes these requests get lost in the noise: http://groups.google.com/group/pymodbus or at gitter: https://gitter.im/pymodbus_dev/Lobby ------------------------------------------------------------ Installing ------------------------------------------------------------ You can install using pip or easy install by issuing the following commands in a terminal window (make sure you have correct permissions or a virtualenv currently running):: easy_install -U pymodbus pip install -U pymodbus Otherwise you can pull the trunk source and install from there:: git clone git://github.com/bashwork/pymodbus.git cd pymodbus python setup.py install Either method will install all the required dependencies (at their appropriate versions) for your current python distribution. If you would like to install pymodbus without the twisted dependency, simply edit the setup.py file before running easy_install and comment out all mentions of twisted. It should be noted that without twisted, one will only be able to run the synchronized version as the asynchronous versions uses twisted for its event loop. ------------------------------------------------------------ Current Work In Progress ------------------------------------------------------------ Since I don't have access to any live modbus devices anymore it is a bit hard to test on live hardware. However, if you would like your device tested, I accept devices via mail or by IP address. That said, the current work mainly involves polishing the library as I get time doing such tasks as: * Make PEP-8 compatible and flake8 ready * Fixing bugs/feature requests * Architecture documentation * Functional testing against any reference I can find * The remaining edges of the protocol (that I think no one uses) ------------------------------------------------------------ Development Instructions ------------------------------------------------------------ The current code base is compatible with both py2 and py3. Use make to perform a range of activities :: $ make Makefile for pymodbus Usage: make install install the package in a virtual environment make reset recreate the virtual environment make check check coding style (PEP-8, PEP-257) make test run the test suite, report coverage make tox run the tests on all Python versions make clean cleanup all temporary files ------------------------------------------------------------ Contributing ------------------------------------------------------------ Just fork the repo and raise your PR against `dev` branch. ------------------------------------------------------------ License Information ------------------------------------------------------------ Pymodbus is built on top of code developed from/by: * Copyright (c) 2001-2005 S.W.A.C. GmbH, Germany. * Copyright (c) 2001-2005 S.W.A.C. Bohemia s.r.o., Czech Republic. * Hynek Petrak, https://github.com/HynekPetrak * Twisted Matrix Released under the BSD License pymodbus-1.3.2/test/0000755000175000017500000000000013150360615012472 5ustar wmbwmbpymodbus-1.3.2/test/test_file_message.py0000644000175000017500000002526313150360615016536 0ustar wmbwmb#!/usr/bin/env python ''' Bit Message Test Fixture -------------------------------- This fixture tests the functionality of all the bit based request/response messages: * Read/Write Discretes * Read Coils ''' import unittest from pymodbus.file_message import * from pymodbus.exceptions import * from pymodbus.pdu import ModbusExceptions from .modbus_mocks import MockContext #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusBitMessageTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass #-----------------------------------------------------------------------# # Read Fifo Queue #-----------------------------------------------------------------------# def testReadFifoQueueRequestEncode(self): ''' Test basic bit message encoding/decoding ''' handle = ReadFifoQueueRequest(0x1234) result = handle.encode() self.assertEqual(result, b'\x12\x34') def testReadFifoQueueRequestDecode(self): ''' Test basic bit message encoding/decoding ''' handle = ReadFifoQueueRequest(0x0000) handle.decode(b'\x12\x34') self.assertEqual(handle.address, 0x1234) def testReadFifoQueueRequest(self): ''' Test basic bit message encoding/decoding ''' context = MockContext() handle = ReadFifoQueueRequest(0x1234) result = handle.execute(context) self.assertTrue(isinstance(result, ReadFifoQueueResponse)) handle.address = -1 result = handle.execute(context) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) handle.values = [0x00]*33 result = handle.execute(context) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) def testReadFifoQueueRequestError(self): ''' Test basic bit message encoding/decoding ''' context = MockContext() handle = ReadFifoQueueRequest(0x1234) handle.values = [0x00]*32 result = handle.execute(context) self.assertEqual(result.function_code, 0x98) def testReadFifoQueueResponseEncode(self): ''' Test that the read fifo queue response can encode ''' message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04' handle = ReadFifoQueueResponse([1,2,3,4]) result = handle.encode() self.assertEqual(result, message) def testReadFifoQueueResponseDecode(self): ''' Test that the read fifo queue response can decode ''' message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04' handle = ReadFifoQueueResponse([1,2,3,4]) handle.decode(message) self.assertEqual(handle.values, [1,2,3,4]) def testRtuFrameSize(self): ''' Test that the read fifo queue response can decode ''' message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04' result = ReadFifoQueueResponse.calculateRtuFrameSize(message) self.assertEqual(result, 14) #-----------------------------------------------------------------------# # File Record #-----------------------------------------------------------------------# def testFileRecordLength(self): ''' Test file record length generation ''' record = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x04') self.assertEqual(record.record_length, 0x02) self.assertEqual(record.response_length, 0x05) def testFileRecordComapre(self): ''' Test file record comparison operations ''' record1 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x04') record2 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x0a\x0e\x04') record3 = FileRecord(file_number=0x02, record_number=0x03, record_data=b'\x00\x01\x02\x04') record4 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x04') self.assertTrue(record1 == record4) self.assertTrue(record1 != record2) self.assertNotEqual(record1, record2) self.assertNotEqual(record1, record3) self.assertNotEqual(record2, record3) self.assertEqual(record1, record4) self.assertEqual(str(record1), "FileRecord(file=1, record=2, length=2)") self.assertEqual(str(record2), "FileRecord(file=1, record=2, length=2)") self.assertEqual(str(record3), "FileRecord(file=2, record=3, length=2)") #-----------------------------------------------------------------------# # Read File Record Request #-----------------------------------------------------------------------# def testReadFileRecordRequestEncode(self): ''' Test basic bit message encoding/decoding ''' records = [FileRecord(file_number=0x01, record_number=0x02)] handle = ReadFileRecordRequest(records) result = handle.encode() self.assertEqual(result, b'\x07\x06\x00\x01\x00\x02\x00\x00') def testReadFileRecordRequestDecode(self): ''' Test basic bit message encoding/decoding ''' record = FileRecord(file_number=0x04, record_number=0x01, record_length=0x02) request = b'\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\x09\x00\x02' handle = ReadFileRecordRequest() handle.decode(request) self.assertEqual(handle.records[0], record) def testReadFileRecordRequestRtuFrameSize(self): ''' Test basic bit message encoding/decoding ''' request = b'\x00\x00\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\x09\x00\x02' handle = ReadFileRecordRequest() size = handle.calculateRtuFrameSize(request) self.assertEqual(size, 0x0e + 5) def testReadFileRecordRequestExecute(self): ''' Test basic bit message encoding/decoding ''' handle = ReadFileRecordRequest() result = handle.execute(None) self.assertTrue(isinstance(result, ReadFileRecordResponse)) #-----------------------------------------------------------------------# # Read File Record Response #-----------------------------------------------------------------------# def testReadFileRecordResponseEncode(self): ''' Test basic bit message encoding/decoding ''' records = [FileRecord(record_data=b'\x00\x01\x02\x03')] handle = ReadFileRecordResponse(records) result = handle.encode() self.assertEqual(result, b'\x06\x06\x02\x00\x01\x02\x03') def testReadFileRecordResponseDecode(self): ''' Test basic bit message encoding/decoding ''' record = FileRecord(file_number=0x00, record_number=0x00, record_data=b'\x0d\xfe\x00\x20') request = b'\x0c\x05\x06\x0d\xfe\x00\x20\x05\x05\x06\x33\xcd\x00\x40' handle = ReadFileRecordResponse() handle.decode(request) self.assertEqual(handle.records[0], record) def testReadFileRecordResponseRtuFrameSize(self): ''' Test basic bit message encoding/decoding ''' request = b'\x00\x00\x0c\x05\x06\x0d\xfe\x00\x20\x05\x05\x06\x33\xcd\x00\x40' handle = ReadFileRecordResponse() size = handle.calculateRtuFrameSize(request) self.assertEqual(size, 0x0c + 5) #-----------------------------------------------------------------------# # Write File Record Request #-----------------------------------------------------------------------# def testWriteFileRecordRequestEncode(self): ''' Test basic bit message encoding/decoding ''' records = [FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x03')] handle = WriteFileRecordRequest(records) result = handle.encode() self.assertEqual(result, b'\x0b\x06\x00\x01\x00\x02\x00\x02\x00\x01\x02\x03') def testWriteFileRecordRequestDecode(self): ''' Test basic bit message encoding/decoding ''' record = FileRecord(file_number=0x04, record_number=0x07, record_data=b'\x06\xaf\x04\xbe\x10\x0d') request = b'\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d' handle = WriteFileRecordRequest() handle.decode(request) self.assertEqual(handle.records[0], record) def testWriteFileRecordRequestRtuFrameSize(self): ''' Test write file record request rtu frame size calculation ''' request = b'\x00\x00\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d' handle = WriteFileRecordRequest() size = handle.calculateRtuFrameSize(request) self.assertEqual(size, 0x0d + 5) def testWriteFileRecordRequestExecute(self): ''' Test basic bit message encoding/decoding ''' handle = WriteFileRecordRequest() result = handle.execute(None) self.assertTrue(isinstance(result, WriteFileRecordResponse)) #-----------------------------------------------------------------------# # Write File Record Response #-----------------------------------------------------------------------# def testWriteFileRecordResponseEncode(self): ''' Test basic bit message encoding/decoding ''' records = [FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x03')] handle = WriteFileRecordResponse(records) result = handle.encode() self.assertEqual(result, b'\x0b\x06\x00\x01\x00\x02\x00\x02\x00\x01\x02\x03') def testWriteFileRecordResponseDecode(self): ''' Test basic bit message encoding/decoding ''' record = FileRecord(file_number=0x04, record_number=0x07, record_data=b'\x06\xaf\x04\xbe\x10\x0d') request = b'\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d' handle = WriteFileRecordResponse() handle.decode(request) self.assertEqual(handle.records[0], record) def testWriteFileRecordResponseRtuFrameSize(self): ''' Test write file record response rtu frame size calculation ''' request = b'\x00\x00\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d' handle = WriteFileRecordResponse() size = handle.calculateRtuFrameSize(request) self.assertEqual(size, 0x0d + 5) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_all_messages.py0000644000175000017500000000663213150360615016551 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.constants import Defaults from pymodbus.bit_read_message import * from pymodbus.bit_write_message import * from pymodbus.register_read_message import * from pymodbus.register_write_message import * #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusAllMessagesTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' arguments = { 'read_address': 1, 'read_count': 1, 'write_address': 1, 'write_registers': 1 } self.requests = [ lambda unit: ReadCoilsRequest(1, 5, unit=unit), lambda unit: ReadDiscreteInputsRequest(1, 5, unit=unit), lambda unit: WriteSingleCoilRequest(1, 1, unit=unit), lambda unit: WriteMultipleCoilsRequest(1, [1], unit=unit), lambda unit: ReadHoldingRegistersRequest(1, 5, unit=unit), lambda unit: ReadInputRegistersRequest(1, 5, unit=unit), lambda unit: ReadWriteMultipleRegistersRequest(unit=unit, **arguments), lambda unit: WriteSingleRegisterRequest(1, 1, unit=unit), lambda unit: WriteMultipleRegistersRequest(1, [1], unit=unit), ] self.responses = [ lambda unit: ReadCoilsResponse([1], unit=unit), lambda unit: ReadDiscreteInputsResponse([1], unit=unit), lambda unit: WriteSingleCoilResponse(1, 1, unit=unit), lambda unit: WriteMultipleCoilsResponse(1, [1], unit=unit), lambda unit: ReadHoldingRegistersResponse([1], unit=unit), lambda unit: ReadInputRegistersResponse([1], unit=unit), lambda unit: ReadWriteMultipleRegistersResponse([1], unit=unit), lambda unit: WriteSingleRegisterResponse(1, 1, unit=unit), lambda unit: WriteMultipleRegistersResponse(1, 1, unit=unit), ] def tearDown(self): ''' Cleans up the test environment ''' pass def testInitializingSlaveAddressRequest(self): ''' Test that every request can initialize the unit id ''' unit_id = 0x12 for factory in self.requests: request = factory(unit_id) self.assertEqual(request.unit_id, unit_id) def testInitializingSlaveAddressResponse(self): ''' Test that every response can initialize the unit id ''' unit_id = 0x12 for factory in self.responses: response = factory(unit_id) self.assertEqual(response.unit_id, unit_id) def testForwardingKwargsToPdu(self): ''' Test that the kwargs are forwarded to the pdu correctly ''' request = ReadCoilsRequest(1,5, unit=0x12, transaction=0x12, protocol=0x12) self.assertEqual(request.unit_id, 0x12) self.assertEqual(request.transaction_id, 0x12) self.assertEqual(request.protocol_id, 0x12) request = ReadCoilsRequest(1,5) self.assertEqual(request.unit_id, Defaults.UnitId) self.assertEqual(request.transaction_id, Defaults.TransactionId) self.assertEqual(request.protocol_id, Defaults.ProtocolId) pymodbus-1.3.2/test/test_transaction.py0000644000175000017500000004061013150360615016431 0ustar wmbwmb#!/usr/bin/env python import unittest from binascii import a2b_hex from pymodbus.pdu import * from pymodbus.transaction import * from pymodbus.factory import ServerDecoder from pymodbus.compat import byte2int class ModbusTransactionTest(unittest.TestCase): ''' This is the unittest for the pymodbus.transaction module ''' #---------------------------------------------------------------------------# # Test Construction #---------------------------------------------------------------------------# def setUp(self): ''' Sets up the test environment ''' self.client = None self.decoder = ServerDecoder() self._tcp = ModbusSocketFramer(decoder=self.decoder) self._rtu = ModbusRtuFramer(decoder=self.decoder) self._ascii = ModbusAsciiFramer(decoder=self.decoder) self._binary = ModbusBinaryFramer(decoder=self.decoder) self._manager = DictTransactionManager(self.client) self._queue_manager = FifoTransactionManager(self.client) def tearDown(self): ''' Cleans up the test environment ''' del self._manager del self._tcp del self._rtu del self._ascii #---------------------------------------------------------------------------# # Dictionary based transaction manager #---------------------------------------------------------------------------# def testDictTransactionManagerTID(self): ''' Test the dict transaction manager TID ''' for tid in range(1, self._manager.getNextTID() + 10): self.assertEqual(tid+1, self._manager.getNextTID()) self._manager.reset() self.assertEqual(1, self._manager.getNextTID()) def testGetDictTransactionManagerTransaction(self): ''' Test the dict transaction manager ''' class Request: pass self._manager.reset() handle = Request() handle.transaction_id = self._manager.getNextTID() handle.message = b"testing" self._manager.addTransaction(handle) result = self._manager.getTransaction(handle.transaction_id) self.assertEqual(handle.message, result.message) def testDeleteDictTransactionManagerTransaction(self): ''' Test the dict transaction manager ''' class Request: pass self._manager.reset() handle = Request() handle.transaction_id = self._manager.getNextTID() handle.message = b"testing" self._manager.addTransaction(handle) self._manager.delTransaction(handle.transaction_id) self.assertEqual(None, self._manager.getTransaction(handle.transaction_id)) #---------------------------------------------------------------------------# # Queue based transaction manager #---------------------------------------------------------------------------# def testFifoTransactionManagerTID(self): ''' Test the fifo transaction manager TID ''' for tid in range(1, self._queue_manager.getNextTID() + 10): self.assertEqual(tid+1, self._queue_manager.getNextTID()) self._queue_manager.reset() self.assertEqual(1, self._queue_manager.getNextTID()) def testGetFifoTransactionManagerTransaction(self): ''' Test the fifo transaction manager ''' class Request: pass self._queue_manager.reset() handle = Request() handle.transaction_id = self._queue_manager.getNextTID() handle.message = b"testing" self._queue_manager.addTransaction(handle) result = self._queue_manager.getTransaction(handle.transaction_id) self.assertEqual(handle.message, result.message) def testDeleteFifoTransactionManagerTransaction(self): ''' Test the fifo transaction manager ''' class Request: pass self._queue_manager.reset() handle = Request() handle.transaction_id = self._queue_manager.getNextTID() handle.message = b"testing" self._queue_manager.addTransaction(handle) self._queue_manager.delTransaction(handle.transaction_id) self.assertEqual(None, self._queue_manager.getTransaction(handle.transaction_id)) #---------------------------------------------------------------------------# # TCP tests #---------------------------------------------------------------------------# def testTCPFramerTransactionReady(self): ''' Test a tcp frame transaction ''' msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34" self.assertFalse(self._tcp.isFrameReady()) self.assertFalse(self._tcp.checkFrame()) self._tcp.addToFrame(msg) self.assertTrue(self._tcp.isFrameReady()) self.assertTrue(self._tcp.checkFrame()) self._tcp.advanceFrame() self.assertFalse(self._tcp.isFrameReady()) self.assertFalse(self._tcp.checkFrame()) self.assertEqual(b'', self._ascii.getFrame()) def testTCPFramerTransactionFull(self): ''' Test a full tcp frame transaction ''' msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34" self._tcp.addToFrame(msg) self.assertTrue(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg[7:], result) self._tcp.advanceFrame() def testTCPFramerTransactionHalf(self): ''' Test a half completed tcp frame transaction ''' msg1 = b"\x00\x01\x12\x34\x00" msg2 = b"\x04\xff\x02\x12\x34" self._tcp.addToFrame(msg1) self.assertFalse(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(b'', result) self._tcp.addToFrame(msg2) self.assertTrue(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg2[2:], result) self._tcp.advanceFrame() def testTCPFramerTransactionHalf2(self): ''' Test a half completed tcp frame transaction ''' msg1 = b"\x00\x01\x12\x34\x00\x04\xff" msg2 = b"\x02\x12\x34" self._tcp.addToFrame(msg1) self.assertFalse(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(b'', result) self._tcp.addToFrame(msg2) self.assertTrue(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg2, result) self._tcp.advanceFrame() def testTCPFramerTransactionHalf3(self): ''' Test a half completed tcp frame transaction ''' msg1 = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12" msg2 = b"\x34" self._tcp.addToFrame(msg1) self.assertFalse(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg1[7:], result) self._tcp.addToFrame(msg2) self.assertTrue(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg1[7:] + msg2, result) self._tcp.advanceFrame() def testTCPFramerTransactionShort(self): ''' Test that we can get back on track after an invalid message ''' msg1 = b"\x99\x99\x99\x99\x00\x01\x00\x01" msg2 = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34" self._tcp.addToFrame(msg1) self.assertFalse(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(b'', result) self._tcp.advanceFrame() self._tcp.addToFrame(msg2) self.assertEqual(10, len(self._tcp._ModbusSocketFramer__buffer)) self.assertTrue(self._tcp.checkFrame()) result = self._tcp.getFrame() self.assertEqual(msg2[7:], result) self._tcp.advanceFrame() def testTCPFramerPopulate(self): ''' Test a tcp frame packet build ''' expected = ModbusRequest() expected.transaction_id = 0x0001 expected.protocol_id = 0x1234 expected.unit_id = 0xff msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34" self._tcp.addToFrame(msg) self.assertTrue(self._tcp.checkFrame()) actual = ModbusRequest() self._tcp.populateResult(actual) for name in ['transaction_id', 'protocol_id', 'unit_id']: self.assertEqual(getattr(expected, name), getattr(actual, name)) self._tcp.advanceFrame() def testTCPFramerPacket(self): ''' Test a tcp frame packet build ''' old_encode = ModbusRequest.encode ModbusRequest.encode = lambda self: b'' message = ModbusRequest() message.transaction_id = 0x0001 message.protocol_id = 0x1234 message.unit_id = 0xff message.function_code = 0x01 expected = b"\x00\x01\x12\x34\x00\x02\xff\x01" actual = self._tcp.buildPacket(message) self.assertEqual(expected, actual) ModbusRequest.encode = old_encode #---------------------------------------------------------------------------# # RTU tests #---------------------------------------------------------------------------# def testRTUFramerTransactionReady(self): ''' Test if the checks for a complete frame work ''' self.assertFalse(self._rtu.isFrameReady()) msg_parts = [b"\x00\x01\x00", b"\x00\x00\x01\xfc\x1b"] self._rtu.addToFrame(msg_parts[0]) self.assertTrue(self._rtu.isFrameReady()) self.assertFalse(self._rtu.checkFrame()) self._rtu.addToFrame(msg_parts[1]) self.assertTrue(self._rtu.isFrameReady()) self.assertTrue(self._rtu.checkFrame()) def testRTUFramerTransactionFull(self): ''' Test a full rtu frame transaction ''' msg = b"\x00\x01\x00\x00\x00\x01\xfc\x1b" stripped_msg = msg[1:-2] self._rtu.addToFrame(msg) self.assertTrue(self._rtu.checkFrame()) result = self._rtu.getFrame() self.assertEqual(stripped_msg, result) self._rtu.advanceFrame() def testRTUFramerTransactionHalf(self): ''' Test a half completed rtu frame transaction ''' msg_parts = [b"\x00\x01\x00", b"\x00\x00\x01\xfc\x1b"] stripped_msg = b"".join(msg_parts)[1:-2] self._rtu.addToFrame(msg_parts[0]) self.assertFalse(self._rtu.checkFrame()) self._rtu.addToFrame(msg_parts[1]) self.assertTrue(self._rtu.isFrameReady()) self.assertTrue(self._rtu.checkFrame()) result = self._rtu.getFrame() self.assertEqual(stripped_msg, result) self._rtu.advanceFrame() def testRTUFramerPopulate(self): ''' Test a rtu frame packet build ''' request = ModbusRequest() msg = b"\x00\x01\x00\x00\x00\x01\xfc\x1b" self._rtu.addToFrame(msg) self._rtu.populateHeader() self._rtu.populateResult(request) header_dict = self._rtu._ModbusRtuFramer__header self.assertEqual(len(msg), header_dict['len']) self.assertEqual(byte2int(msg[0]), header_dict['uid']) self.assertEqual(msg[-2:], header_dict['crc']) self.assertEqual(0x00, request.unit_id) def testRTUFramerPacket(self): ''' Test a rtu frame packet build ''' old_encode = ModbusRequest.encode ModbusRequest.encode = lambda self: b'' message = ModbusRequest() message.unit_id = 0xff message.function_code = 0x01 expected = b"\xff\x01\x81\x80" # only header + CRC - no data actual = self._rtu.buildPacket(message) self.assertEqual(expected, actual) ModbusRequest.encode = old_encode def testRTUDecodeException(self): ''' Test that the RTU framer can decode errors ''' message = b"\x00\x90\x02\x9c\x01" actual = self._rtu.addToFrame(message) result = self._rtu.checkFrame() self.assertTrue(result) #---------------------------------------------------------------------------# # ASCII tests #---------------------------------------------------------------------------# def testASCIIFramerTransactionReady(self): ''' Test a ascii frame transaction ''' msg = b':F7031389000A60\r\n' self.assertFalse(self._ascii.isFrameReady()) self.assertFalse(self._ascii.checkFrame()) self._ascii.addToFrame(msg) self.assertTrue(self._ascii.isFrameReady()) self.assertTrue(self._ascii.checkFrame()) self._ascii.advanceFrame() self.assertFalse(self._ascii.isFrameReady()) self.assertFalse(self._ascii.checkFrame()) self.assertEqual(b'', self._ascii.getFrame()) def testASCIIFramerTransactionFull(self): ''' Test a full ascii frame transaction ''' msg = b'sss:F7031389000A60\r\n' pack = a2b_hex(msg[6:-4]) self._ascii.addToFrame(msg) self.assertTrue(self._ascii.checkFrame()) result = self._ascii.getFrame() self.assertEqual(pack, result) self._ascii.advanceFrame() def testASCIIFramerTransactionHalf(self): ''' Test a half completed ascii frame transaction ''' msg1 = b'sss:F7031389' msg2 = b'000A60\r\n' pack = a2b_hex(msg1[6:] + msg2[:-4]) self._ascii.addToFrame(msg1) self.assertFalse(self._ascii.checkFrame()) result = self._ascii.getFrame() self.assertEqual(b'', result) self._ascii.addToFrame(msg2) self.assertTrue(self._ascii.checkFrame()) result = self._ascii.getFrame() self.assertEqual(pack, result) self._ascii.advanceFrame() def testASCIIFramerPopulate(self): ''' Test a ascii frame packet build ''' request = ModbusRequest() self._ascii.populateResult(request) self.assertEqual(0x00, request.unit_id) def testASCIIFramerPacket(self): ''' Test a ascii frame packet build ''' old_encode = ModbusRequest.encode ModbusRequest.encode = lambda self: b'' message = ModbusRequest() message.unit_id = 0xff message.function_code = 0x01 expected = b":FF0100\r\n" actual = self._ascii.buildPacket(message) self.assertEqual(expected, actual) ModbusRequest.encode = old_encode #---------------------------------------------------------------------------# # Binary tests #---------------------------------------------------------------------------# def testBinaryFramerTransactionReady(self): ''' Test a binary frame transaction ''' msg = b'\x7b\x01\x03\x00\x00\x00\x05\x85\xC9\x7d' self.assertFalse(self._binary.isFrameReady()) self.assertFalse(self._binary.checkFrame()) self._binary.addToFrame(msg) self.assertTrue(self._binary.isFrameReady()) self.assertTrue(self._binary.checkFrame()) self._binary.advanceFrame() self.assertFalse(self._binary.isFrameReady()) self.assertFalse(self._binary.checkFrame()) self.assertEqual(b'', self._binary.getFrame()) def testBinaryFramerTransactionFull(self): ''' Test a full binary frame transaction ''' msg = b'\x7b\x01\x03\x00\x00\x00\x05\x85\xC9\x7d' pack = msg[3:-3] self._binary.addToFrame(msg) self.assertTrue(self._binary.checkFrame()) result = self._binary.getFrame() self.assertEqual(pack, result) self._binary.advanceFrame() def testBinaryFramerTransactionHalf(self): ''' Test a half completed binary frame transaction ''' msg1 = b'\x7b\x01\x03\x00' msg2 = b'\x00\x00\x05\x85\xC9\x7d' pack = msg1[3:] + msg2[:-3] self._binary.addToFrame(msg1) self.assertFalse(self._binary.checkFrame()) result = self._binary.getFrame() self.assertEqual(b'', result) self._binary.addToFrame(msg2) self.assertTrue(self._binary.checkFrame()) result = self._binary.getFrame() self.assertEqual(pack, result) self._binary.advanceFrame() def testBinaryFramerPopulate(self): ''' Test a binary frame packet build ''' request = ModbusRequest() self._binary.populateResult(request) self.assertEqual(0x00, request.unit_id) def testBinaryFramerPacket(self): ''' Test a binary frame packet build ''' old_encode = ModbusRequest.encode ModbusRequest.encode = lambda self: b'' message = ModbusRequest() message.unit_id = 0xff message.function_code = 0x01 expected = b'\x7b\xff\x01\x81\x80\x7d' actual = self._binary.buildPacket(message) self.assertEqual(expected, actual) ModbusRequest.encode = old_encode #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_version.py0000644000175000017500000000146413150360615015575 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.version import Version class ModbusVersionTest(unittest.TestCase): ''' This is the unittest for the pymodbus._version code ''' def setUp(self): ''' Initializes the test environment ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass def testVersionClass(self): version = Version('test', 1,2,3, "sometag") short = version.short() self.assertEqual(version.short(), '1.2.3.sometag') self.assertEqual(str(version), '[test, version 1.2.3.sometag]') #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_device.py0000644000175000017500000002760413150360615015353 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.device import * from pymodbus.events import ModbusEvent, RemoteReceiveEvent from pymodbus.constants import DeviceInformation #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class SimpleDataStoreTest(unittest.TestCase): ''' This is the unittest for the pymodbus.device module ''' #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): self.info = { 0x00: 'Bashwork', # VendorName 0x01: 'PTM', # ProductCode 0x02: '1.0', # MajorMinorRevision 0x03: 'http://internets.com', # VendorUrl 0x04: 'pymodbus', # ProductName 0x05: 'bashwork', # ModelName 0x06: 'unittest', # UserApplicationName 0x07: 'x', # reserved 0x08: 'x', # reserved 0x10: 'private' # private data } self.ident = ModbusDeviceIdentification(self.info) self.control = ModbusControlBlock() self.access = ModbusAccessControl() self.control.reset() def tearDown(self): ''' Cleans up the test environment ''' del self.ident del self.control del self.access def testUpdateIdentity(self): ''' Test device identification reading ''' self.control.Identity.update(self.ident) self.assertEqual(self.control.Identity.VendorName, 'Bashwork') self.assertEqual(self.control.Identity.ProductCode, 'PTM') self.assertEqual(self.control.Identity.MajorMinorRevision, '1.0') self.assertEqual(self.control.Identity.VendorUrl, 'http://internets.com') self.assertEqual(self.control.Identity.ProductName, 'pymodbus') self.assertEqual(self.control.Identity.ModelName, 'bashwork') self.assertEqual(self.control.Identity.UserApplicationName, 'unittest') def testDeviceInformationFactory(self): ''' Test device identification reading ''' self.control.Identity.update(self.ident) result = DeviceInformationFactory.get(self.control, DeviceInformation.Specific, 0x00) self.assertEqual(result[0x00], 'Bashwork') result = DeviceInformationFactory.get(self.control, DeviceInformation.Basic, 0x00) self.assertEqual(result[0x00], 'Bashwork') self.assertEqual(result[0x01], 'PTM') self.assertEqual(result[0x02], '1.0') result = DeviceInformationFactory.get(self.control, DeviceInformation.Regular, 0x00) self.assertEqual(result[0x00], 'Bashwork') self.assertEqual(result[0x01], 'PTM') self.assertEqual(result[0x02], '1.0') self.assertEqual(result[0x03], 'http://internets.com') self.assertEqual(result[0x04], 'pymodbus') self.assertEqual(result[0x05], 'bashwork') self.assertEqual(result[0x06], 'unittest') def testBasicCommands(self): ''' Test device identification reading ''' self.assertEqual(str(self.ident), "DeviceIdentity") self.assertEqual(str(self.control), "ModbusControl") def testModbusDeviceIdentificationGet(self): ''' Test device identification reading ''' self.assertEqual(self.ident[0x00], 'Bashwork') self.assertEqual(self.ident[0x01], 'PTM') self.assertEqual(self.ident[0x02], '1.0') self.assertEqual(self.ident[0x03], 'http://internets.com') self.assertEqual(self.ident[0x04], 'pymodbus') self.assertEqual(self.ident[0x05], 'bashwork') self.assertEqual(self.ident[0x06], 'unittest') self.assertNotEqual(self.ident[0x07], 'x') self.assertNotEqual(self.ident[0x08], 'x') self.assertEqual(self.ident[0x10], 'private') self.assertEqual(self.ident[0x54], '') def testModbusDeviceIdentificationSummary(self): ''' Test device identification summary creation ''' summary = sorted(self.ident.summary().values()) expected = sorted(list(self.info.values())[:-3]) # remove private self.assertEqual(summary, expected) def testModbusDeviceIdentificationSet(self): ''' Test a device identification writing ''' self.ident[0x07] = 'y' self.ident[0x08] = 'y' self.ident[0x10] = 'public' self.ident[0x54] = 'testing' self.assertNotEqual('y', self.ident[0x07]) self.assertNotEqual('y', self.ident[0x08]) self.assertEqual('public', self.ident[0x10]) self.assertEqual('testing', self.ident[0x54]) def testModbusControlBlockAsciiModes(self): ''' Test a server control block ascii mode ''' self.assertEqual(id(self.control), id(ModbusControlBlock())) self.control.Mode = 'RTU' self.assertEqual('RTU', self.control.Mode) self.control.Mode = 'FAKE' self.assertNotEqual('FAKE', self.control.Mode) def testModbusControlBlockCounters(self): ''' Tests the MCB counters methods ''' self.assertEqual(0x0, self.control.Counter.BusMessage) for _ in range(10): self.control.Counter.BusMessage += 1 self.control.Counter.SlaveMessage += 1 self.assertEqual(10, self.control.Counter.BusMessage) self.control.Counter.BusMessage = 0x00 self.assertEqual(0, self.control.Counter.BusMessage) self.assertEqual(10, self.control.Counter.SlaveMessage) self.control.Counter.reset() self.assertEqual(0, self.control.Counter.SlaveMessage) def testModbusControlBlockUpdate(self): ''' Tests the MCB counters upate methods ''' values = {'SlaveMessage':5, 'BusMessage':5} self.control.Counter.BusMessage += 1 self.control.Counter.SlaveMessage += 1 self.control.Counter.update(values) self.assertEqual(6, self.control.Counter.SlaveMessage) self.assertEqual(6, self.control.Counter.BusMessage) def testModbusControlBlockIterator(self): ''' Tests the MCB counters iterator ''' self.control.Counter.reset() for _,count in self.control: self.assertEqual(0, count) def testModbusCountersHandlerIterator(self): ''' Tests the MCB counters iterator ''' self.control.Counter.reset() for _,count in self.control.Counter: self.assertEqual(0, count) def testModbusControlBlockCounterSummary(self): ''' Tests retrieving the current counter summary ''' self.assertEqual(0x00, self.control.Counter.summary()) for _ in range(10): self.control.Counter.BusMessage += 1 self.control.Counter.SlaveMessage += 1 self.control.Counter.SlaveNAK += 1 self.control.Counter.BusCharacterOverrun += 1 self.assertEqual(0xa9, self.control.Counter.summary()) self.control.Counter.reset() self.assertEqual(0x00, self.control.Counter.summary()) def testModbusControlBlockListen(self): ''' Tests the MCB listen flag methods ''' self.control.ListenOnly = False self.assertEqual(self.control.ListenOnly, False) self.control.ListenOnly = not self.control.ListenOnly self.assertEqual(self.control.ListenOnly, True) def testModbusControlBlockDelimiter(self): ''' Tests the MCB delimiter setting methods ''' self.control.Delimiter = b'\r' self.assertEqual(self.control.Delimiter, b'\r') self.control.Delimiter = '=' self.assertEqual(self.control.Delimiter, b'=') self.control.Delimiter = 61 self.assertEqual(self.control.Delimiter, b'=') def testModbusControlBlockDiagnostic(self): ''' Tests the MCB delimiter setting methods ''' self.assertEqual([False] * 16, self.control.getDiagnosticRegister()) for i in [1,3,4,6]: self.control.setDiagnostic({i:True}); self.assertEqual(True, self.control.getDiagnostic(1)) self.assertEqual(False, self.control.getDiagnostic(2)) actual = [False, True, False, True, True, False, True] + [False] * 9 self.assertEqual(actual, self.control.getDiagnosticRegister()) for i in range(16): self.control.setDiagnostic({i:False}); def testModbusControlBlockInvalidDiagnostic(self): ''' Tests querying invalid MCB counters methods ''' self.assertEqual(None, self.control.getDiagnostic(-1)) self.assertEqual(None, self.control.getDiagnostic(17)) self.assertEqual(None, self.control.getDiagnostic(None)) self.assertEqual(None, self.control.getDiagnostic([1,2,3])) def testAddRemoveSingleClients(self): ''' Test adding and removing a host ''' self.assertFalse(self.access.check("192.168.1.1")) self.access.add("192.168.1.1") self.assertTrue(self.access.check("192.168.1.1")) self.access.add("192.168.1.1") self.access.remove("192.168.1.1") self.assertFalse(self.access.check("192.168.1.1")) def testAddRemoveMultipleClients(self): ''' Test adding and removing a host ''' clients = ["192.168.1.1", "192.168.1.2", "192.168.1.3"] self.access.add(clients) for host in clients: self.assertTrue(self.access.check(host)) self.access.remove(clients) def testNetworkAccessListIterator(self): ''' Test adding and removing a host ''' clients = ["127.0.0.1", "192.168.1.1", "192.168.1.2", "192.168.1.3"] self.access.add(clients) for host in self.access: self.assertTrue(host in clients) for host in clients: self.assertTrue(host in self.access) def testClearingControlEvents(self): ''' Test adding and clearing modbus events ''' self.assertEqual(self.control.Events, []) event = ModbusEvent() self.control.addEvent(event) self.assertEqual(self.control.Events, [event]) self.assertEqual(self.control.Counter.Event, 1) self.control.clearEvents() self.assertEqual(self.control.Events, []) self.assertEqual(self.control.Counter.Event, 1) def testRetrievingControlEvents(self): ''' Test adding and removing a host ''' self.assertEqual(self.control.Events, []) event = RemoteReceiveEvent() self.control.addEvent(event) self.assertEqual(self.control.Events, [event]) packet = self.control.getEvents() self.assertEqual(packet, b'\x40') def testModbusPlusStatistics(self): ''' Test device identification reading ''' default = [0x0000] * 55 statistics = ModbusPlusStatistics() self.assertEqual(default, statistics.encode()) statistics.reset() self.assertEqual(default, statistics.encode()) self.assertEqual(default, self.control.Plus.encode()) def testModbusPlusStatisticsHelpers(self): ''' Test modbus plus statistics helper methods ''' statistics = ModbusPlusStatistics() summary = [ [0],[0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0],[0], [0,0,0,0,0,0,0,0],[0],[0],[0],[0],[0,0],[0],[0],[0],[0], [0],[0],[0],[0,0],[0],[0],[0],[0],[0,0,0,0,0,0,0,0],[0], [0,0,0,0,0,0,0,0],[0,0],[0],[0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0],[0],[0],[0,0],[0],[0],[0],[0],[0,0], [0],[0],[0],[0],[0],[0,0],[0],[0,0,0,0,0,0,0,0]] stats_summary = [x for x in statistics.summary()] self.assertEqual(sorted(summary), sorted(stats_summary)) self.assertEqual(0x00, sum(sum(value[1]) for value in statistics)) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_fixes.py0000644000175000017500000000152013150360615015217 0ustar wmbwmb#!/usr/bin/env python import unittest class ModbusFixesTest(unittest.TestCase): ''' This is the unittest for the pymodbus._version code ''' def testTrueFalseDefined(self): ''' Test that True and False are defined on all versions''' try: True,False except NameError: import pymodbus self.assertEqual(True, 1) self.assertEqual(False, 1) def testNullLoggerAttached(self): ''' Test that the null logger is attached''' import logging logger = logging.getLogger('pymodbus') self.assertEqual(len(logger.handlers), 1) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_mei_messages.py0000644000175000017500000001124513150360615016547 0ustar wmbwmb#!/usr/bin/env python ''' MEI Message Test Fixture -------------------------------- This fixture tests the functionality of all the mei based request/response messages: ''' import unittest from pymodbus.mei_message import * from pymodbus.constants import DeviceInformation, MoreData from pymodbus.pdu import ModbusExceptions from pymodbus.device import ModbusControlBlock #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusMeiMessageTest(unittest.TestCase): ''' This is the unittest for the pymodbus.mei_message module ''' #-----------------------------------------------------------------------# # Read Device Information #-----------------------------------------------------------------------# def testReadDeviceInformationRequestEncode(self): ''' Test basic bit message encoding/decoding ''' params = {'read_code':DeviceInformation.Basic, 'object_id':0x00 } handle = ReadDeviceInformationRequest(**params) result = handle.encode() self.assertEqual(result, b'\x0e\x01\x00') self.assertEqual("ReadDeviceInformationRequest(1,0)", str(handle)) def testReadDeviceInformationRequestDecode(self): ''' Test basic bit message encoding/decoding ''' handle = ReadDeviceInformationRequest() handle.decode(b'\x0e\x01\x00') self.assertEqual(handle.read_code, DeviceInformation.Basic) self.assertEqual(handle.object_id, 0x00) def testReadDeviceInformationRequest(self): ''' Test basic bit message encoding/decoding ''' context = None control = ModbusControlBlock() control.Identity.VendorName = "Company" control.Identity.ProductCode = "Product" control.Identity.MajorMinorRevision = "v2.1.12" handle = ReadDeviceInformationRequest() result = handle.execute(context) self.assertTrue(isinstance(result, ReadDeviceInformationResponse)) self.assertTrue(result.information[0x00], "Company") self.assertTrue(result.information[0x01], "Product") self.assertTrue(result.information[0x02], "v2.1.12") def testReadDeviceInformationRequestError(self): ''' Test basic bit message encoding/decoding ''' handle = ReadDeviceInformationRequest() handle.read_code = -1 self.assertEqual(handle.execute(None).function_code, 0xab) handle.read_code = 0x05 self.assertEqual(handle.execute(None).function_code, 0xab) handle.object_id = -1 self.assertEqual(handle.execute(None).function_code, 0xab) handle.object_id = 0x100 self.assertEqual(handle.execute(None).function_code, 0xab) def testReadDeviceInformationResponseEncode(self): ''' Test that the read fifo queue response can encode ''' message = b'\x0e\x01\x83\x00\x00\x03' message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12' dataset = { 0x00: 'Company', 0x01: 'Product', 0x02: 'v2.1.12', } handle = ReadDeviceInformationResponse( read_code=DeviceInformation.Basic, information=dataset) result = handle.encode() self.assertEqual(result, message) self.assertEqual("ReadDeviceInformationResponse(1)", str(handle)) def testReadDeviceInformationResponseDecode(self): ''' Test that the read device information response can decode ''' message = b'\x0e\x01\x01\x00\x00\x03' message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12' handle = ReadDeviceInformationResponse(read_code=0x00, information=[]) handle.decode(message) self.assertEqual(handle.read_code, DeviceInformation.Basic) self.assertEqual(handle.conformity, 0x01) self.assertEqual(handle.information[0x00], b'Company') self.assertEqual(handle.information[0x01], b'Product') self.assertEqual(handle.information[0x02], b'v2.1.12') def testRtuFrameSize(self): ''' Test that the read device information response can decode ''' message = b'\x04\x2B\x0E\x01\x81\x00\x01\x01\x00\x06\x66\x6F\x6F\x62\x61\x72\xD7\x3B' result = ReadDeviceInformationResponse.calculateRtuFrameSize(message) self.assertEqual(result, 18) message = b'\x00\x2B\x0E\x02\x00\x4D\x47' result = ReadDeviceInformationRequest.calculateRtuFrameSize(message) self.assertEqual(result, 7) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_bit_write_messages.py0000644000175000017500000001126713150360615017771 0ustar wmbwmb#!/usr/bin/env python ''' Bit Message Test Fixture -------------------------------- This fixture tests the functionality of all the bit based request/response messages: * Read/Write Discretes * Read Coils ''' import unittest from pymodbus.bit_write_message import * from pymodbus.exceptions import * from pymodbus.pdu import ModbusExceptions from pymodbus.compat import iteritems from .modbus_mocks import MockContext, FakeList #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusBitMessageTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass def testBitWriteBaseRequests(self): messages = { WriteSingleCoilRequest(1, 0xabcd) : b'\x00\x01\xff\x00', WriteSingleCoilResponse(1, 0xabcd) : b'\x00\x01\xff\x00', WriteMultipleCoilsRequest(1, [True]*5) : b'\x00\x01\x00\x05\x01\x1f', WriteMultipleCoilsResponse(1, 5) : b'\x00\x01\x00\x05', } for request, expected in iteritems(messages): self.assertEqual(request.encode(), expected) def testBitWriteMessageGetResponsePDU(self): requests = { WriteSingleCoilRequest(1, 0xabcd): 5 } for request, expected in iteritems(requests): pdu_len = request.get_response_pdu_size() self.assertEqual(pdu_len, expected) def testWriteMultipleCoilsRequest(self): request = WriteMultipleCoilsRequest(1, [True]*5) request.decode(b'\x00\x01\x00\x05\x01\x1f') self.assertEqual(request.byte_count, 1) self.assertEqual(request.address, 1) self.assertEqual(request.values, [True]*5) def testInvalidWriteMultipleCoilsRequest(self): request = WriteMultipleCoilsRequest(1, None) self.assertEqual(request.values, []) def testWriteSingleCoilRequestEncode(self): request = WriteSingleCoilRequest(1, False) self.assertEqual(request.encode(), b'\x00\x01\x00\x00') def testWriteSingleCoilExecute(self): context = MockContext(False, default=True) request = WriteSingleCoilRequest(2, True) result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress) context.valid = True result = request.execute(context) self.assertEqual(result.encode(), b'\x00\x02\xff\x00') context = MockContext(True, default=False) request = WriteSingleCoilRequest(2, False) result = request.execute(context) self.assertEqual(result.encode(), b'\x00\x02\x00\x00') def testWriteMultipleCoilsExecute(self): context = MockContext(False) # too many values request = WriteMultipleCoilsRequest(2, FakeList(0x123456)) result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue) # bad byte count request = WriteMultipleCoilsRequest(2, [0x00]*4) request.byte_count = 0x00 result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue) # does not validate context.valid = False request = WriteMultipleCoilsRequest(2, [0x00]*4) result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress) # validated request context.valid = True result = request.execute(context) self.assertEqual(result.encode(), b'\x00\x02\x00\x04') def testWriteMultipleCoilsResponse(self): response = WriteMultipleCoilsResponse() response.decode(b'\x00\x80\x00\x08') self.assertEqual(response.address, 0x80) self.assertEqual(response.count, 0x08) def testSerializingToString(self): requests = [ WriteSingleCoilRequest(1, 0xabcd), WriteSingleCoilResponse(1, 0xabcd), WriteMultipleCoilsRequest(1, [True]*5), WriteMultipleCoilsResponse(1, 5), ] for request in requests: self.assertTrue(str(request) != None) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/__init__.py0000644000175000017500000000000013150360615014571 0ustar wmbwmbpymodbus-1.3.2/test/test_payload.py0000644000175000017500000002062113150360615015535 0ustar wmbwmb#!/usr/bin/env python ''' Payload Utilities Test Fixture -------------------------------- This fixture tests the functionality of the payload utilities. * PayloadBuilder * PayloadDecoder ''' import unittest from pymodbus.exceptions import ParameterException from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusPayloadUtilityTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' self.little_endian_payload = \ b'\x01\x02\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00' \ b'\x00\x00\x00\xff\xfe\xff\xfd\xff\xff\xff\xfc\xff' \ b'\xff\xff\xff\xff\xff\xff\x00\x00\xa0\x3f\x00\x00' \ b'\x00\x00\x00\x00\x19\x40\x01\x00\x74\x65\x73\x74' \ b'\x11' self.big_endian_payload = \ b'\x01\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00' \ b'\x00\x00\x04\xff\xff\xfe\xff\xff\xff\xfd\xff\xff' \ b'\xff\xff\xff\xff\xff\xfc\x3f\xa0\x00\x00\x40\x19' \ b'\x00\x00\x00\x00\x00\x00\x00\x01\x74\x65\x73\x74' \ b'\x11' self.bitstring = [True, False, False, False, True, False, False, False] def tearDown(self): ''' Cleans up the test environment ''' pass #-----------------------------------------------------------------------# # Payload Builder Tests #-----------------------------------------------------------------------# def testLittleEndianPayloadBuilder(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder(endian=Endian.Little) builder.add_8bit_uint(1) builder.add_16bit_uint(2) builder.add_32bit_uint(3) builder.add_64bit_uint(4) builder.add_8bit_int(-1) builder.add_16bit_int(-2) builder.add_32bit_int(-3) builder.add_64bit_int(-4) builder.add_32bit_float(1.25) builder.add_64bit_float(6.25) builder.add_16bit_uint(1) # placeholder builder.add_string(b'test') builder.add_bits(self.bitstring) self.assertEqual(self.little_endian_payload, builder.to_string()) def testBigEndianPayloadBuilder(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_8bit_uint(1) builder.add_16bit_uint(2) builder.add_32bit_uint(3) builder.add_64bit_uint(4) builder.add_8bit_int(-1) builder.add_16bit_int(-2) builder.add_32bit_int(-3) builder.add_64bit_int(-4) builder.add_32bit_float(1.25) builder.add_64bit_float(6.25) builder.add_16bit_uint(1) # placeholder builder.add_string('test') builder.add_bits(self.bitstring) self.assertEqual(self.big_endian_payload, builder.to_string()) def testPayloadBuilderReset(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder() builder.add_8bit_uint(0x12) builder.add_8bit_uint(0x34) builder.add_8bit_uint(0x56) builder.add_8bit_uint(0x78) self.assertEqual(b'\x12\x34\x56\x78', builder.to_string()) self.assertEqual([b'\x12\x34', b'\x56\x78'], builder.build()) builder.reset() self.assertEqual(b'', builder.to_string()) self.assertEqual([], builder.build()) def testPayloadBuilderWithRawPayload(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder([b'\x12', b'\x34', b'\x56', b'\x78']) self.assertEqual(b'\x12\x34\x56\x78', builder.to_string()) self.assertEqual([13330, 30806], builder.to_registers()) builder = BinaryPayloadBuilder([b'\x12', b'\x34', b'\x56', b'\x78'], endian=Endian.Big) self.assertEqual(b'\x12\x34\x56\x78', builder.to_string()) self.assertEqual([4660, 22136], builder.to_registers()) self.assertEqual('\x12\x34\x56\x78', str(builder)) #-----------------------------------------------------------------------# # Payload Decoder Tests #-----------------------------------------------------------------------# def testLittleEndianPayloadDecoder(self): ''' Test basic bit message encoding/decoding ''' decoder = BinaryPayloadDecoder(self.little_endian_payload, endian=Endian.Little) self.assertEqual(1, decoder.decode_8bit_uint()) self.assertEqual(2, decoder.decode_16bit_uint()) self.assertEqual(3, decoder.decode_32bit_uint()) self.assertEqual(4, decoder.decode_64bit_uint()) self.assertEqual(-1, decoder.decode_8bit_int()) self.assertEqual(-2, decoder.decode_16bit_int()) self.assertEqual(-3, decoder.decode_32bit_int()) self.assertEqual(-4, decoder.decode_64bit_int()) self.assertEqual(1.25, decoder.decode_32bit_float()) self.assertEqual(6.25, decoder.decode_64bit_float()) self.assertEqual(None, decoder.skip_bytes(2)) self.assertEqual('test', decoder.decode_string(4).decode()) self.assertEqual(self.bitstring, decoder.decode_bits()) def testBigEndianPayloadDecoder(self): ''' Test basic bit message encoding/decoding ''' decoder = BinaryPayloadDecoder(self.big_endian_payload, endian=Endian.Big) self.assertEqual(1, decoder.decode_8bit_uint()) self.assertEqual(2, decoder.decode_16bit_uint()) self.assertEqual(3, decoder.decode_32bit_uint()) self.assertEqual(4, decoder.decode_64bit_uint()) self.assertEqual(-1, decoder.decode_8bit_int()) self.assertEqual(-2, decoder.decode_16bit_int()) self.assertEqual(-3, decoder.decode_32bit_int()) self.assertEqual(-4, decoder.decode_64bit_int()) self.assertEqual(1.25, decoder.decode_32bit_float()) self.assertEqual(6.25, decoder.decode_64bit_float()) self.assertEqual(None, decoder.skip_bytes(2)) self.assertEqual(b'test', decoder.decode_string(4)) self.assertEqual(self.bitstring, decoder.decode_bits()) def testPayloadDecoderReset(self): ''' Test the payload decoder reset functionality ''' decoder = BinaryPayloadDecoder(b'\x12\x34') self.assertEqual(0x12, decoder.decode_8bit_uint()) self.assertEqual(0x34, decoder.decode_8bit_uint()) decoder.reset() self.assertEqual(0x3412, decoder.decode_16bit_uint()) def testPayloadDecoderRegisterFactory(self): ''' Test the payload decoder reset functionality ''' payload = [1,2,3,4] decoder = BinaryPayloadDecoder.fromRegisters(payload, endian=Endian.Little) encoded = b'\x01\x00\x02\x00\x03\x00\x04\x00' self.assertEqual(encoded, decoder.decode_string(8)) decoder = BinaryPayloadDecoder.fromRegisters(payload, endian=Endian.Big) encoded = b'\x00\x01\x00\x02\x00\x03\x00\x04' self.assertEqual(encoded, decoder.decode_string(8)) self.assertRaises(ParameterException, lambda: BinaryPayloadDecoder.fromRegisters('abcd')) def testPayloadDecoderCoilFactory(self): ''' Test the payload decoder reset functionality ''' payload = [1,0,0,0, 1,0,0,0, 0,0,0,1, 0,0,0,1] decoder = BinaryPayloadDecoder.fromCoils(payload, endian=Endian.Little) encoded = b'\x11\x88' self.assertEqual(encoded, decoder.decode_string(2)) decoder = BinaryPayloadDecoder.fromCoils(payload, endian=Endian.Big) encoded = b'\x11\x88' self.assertEqual(encoded, decoder.decode_string(2)) self.assertRaises(ParameterException, lambda: BinaryPayloadDecoder.fromCoils('abcd')) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_register_read_messages.py0000644000175000017500000001615313150360615020617 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.register_read_message import * from pymodbus.register_read_message import ReadRegistersRequestBase from pymodbus.register_read_message import ReadRegistersResponseBase from pymodbus.exceptions import * from pymodbus.pdu import ModbusExceptions from pymodbus.compat import iteritems, iterkeys, get_next from .modbus_mocks import MockContext, FakeList #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ReadRegisterMessagesTest(unittest.TestCase): ''' Register Message Test Fixture -------------------------------- This fixture tests the functionality of all the register based request/response messages: * Read/Write Input Registers * Read Holding Registers ''' def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' arguments = { 'read_address': 1, 'read_count': 5, 'write_address': 1, 'write_registers': [0x00]*5, } self.value = 0xabcd self.values = [0xa, 0xb, 0xc] self.request_read = { ReadRegistersRequestBase(1, 5) :b'\x00\x01\x00\x05', ReadHoldingRegistersRequest(1, 5) :b'\x00\x01\x00\x05', ReadInputRegistersRequest(1,5) :b'\x00\x01\x00\x05', ReadWriteMultipleRegistersRequest(**arguments) :b'\x00\x01\x00\x05\x00\x01\x00' b'\x05\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', } self.response_read = { ReadRegistersResponseBase(self.values) :b'\x06\x00\x0a\x00\x0b\x00\x0c', ReadHoldingRegistersResponse(self.values) :b'\x06\x00\x0a\x00\x0b\x00\x0c', ReadInputRegistersResponse(self.values) :b'\x06\x00\x0a\x00\x0b\x00\x0c', ReadWriteMultipleRegistersResponse(self.values) :b'\x06\x00\x0a\x00\x0b\x00\x0c', } def tearDown(self): ''' Cleans up the test environment ''' del self.request_read del self.response_read def testReadRegisterResponseBase(self): response = ReadRegistersResponseBase(list(range(10))) for index in range(10): self.assertEqual(response.getRegister(index), index) def testRegisterReadRequests(self): for request, response in iteritems(self.request_read): self.assertEqual(request.encode(), response) def testRegisterReadResponses(self): for request, response in iteritems(self.response_read): self.assertEqual(request.encode(), response) def testRegisterReadResponseDecode(self): registers = [ [0x0a,0x0b,0x0c], [0x0a,0x0b,0x0c], [0x0a,0x0b,0x0c], [0x0a,0x0b,0x0c, 0x0a,0x0b,0x0c], ] values = sorted(self.response_read.items(), key=lambda x: str(x)) for packet, register in zip(values, registers): request, response = packet request.decode(response) self.assertEqual(request.registers, register) def testRegisterReadRequestsCountErrors(self): ''' This tests that the register request messages will break on counts that are out of range ''' mock = FakeList(0x800) requests = [ ReadHoldingRegistersRequest(1, 0x800), ReadInputRegistersRequest(1,0x800), ReadWriteMultipleRegistersRequest(read_address=1, read_count=0x800, write_address=1, write_registers=5), ReadWriteMultipleRegistersRequest(read_address=1, read_count=5, write_address=1, write_registers=mock), ] for request in requests: result = request.execute(None) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) def testRegisterReadRequestsValidateErrors(self): ''' This tests that the register request messages will break on counts that are out of range ''' context = MockContext() requests = [ ReadHoldingRegistersRequest(-1, 5), ReadInputRegistersRequest(-1,5), #ReadWriteMultipleRegistersRequest(-1,5,1,5), #ReadWriteMultipleRegistersRequest(1,5,-1,5), ] for request in requests: result = request.execute(context) self.assertEqual(ModbusExceptions.IllegalAddress, result.exception_code) def testRegisterReadRequestsExecute(self): ''' This tests that the register request messages will break on counts that are out of range ''' context = MockContext(True) requests = [ ReadHoldingRegistersRequest(-1, 5), ReadInputRegistersRequest(-1,5), ] for request in requests: response = request.execute(context) self.assertEqual(request.function_code, response.function_code) def testReadWriteMultipleRegistersRequest(self): context = MockContext(True) request = ReadWriteMultipleRegistersRequest(read_address=1, read_count=10, write_address=1, write_registers=[0x00]) response = request.execute(context) self.assertEqual(request.function_code, response.function_code) def testReadWriteMultipleRegistersValidate(self): context = MockContext() context.validate = lambda f,a,c: a == 1 request = ReadWriteMultipleRegistersRequest(read_address=1, read_count=10, write_address=2, write_registers=[0x00]) response = request.execute(context) self.assertEqual(response.exception_code, ModbusExceptions.IllegalAddress) context.validate = lambda f,a,c: a == 2 response = request.execute(context) self.assertEqual(response.exception_code, ModbusExceptions.IllegalAddress) request.write_byte_count = 0x100 response = request.execute(context) self.assertEqual(response.exception_code, ModbusExceptions.IllegalValue) def testReadWriteMultipleRegistersRequestDecode(self): request, response = get_next((k,v) for k,v in self.request_read.items() if getattr(k, 'function_code', 0) == 23) request.decode(response) self.assertEqual(request.read_address, 0x01) self.assertEqual(request.write_address, 0x01) self.assertEqual(request.read_count, 0x05) self.assertEqual(request.write_count, 0x05) self.assertEqual(request.write_byte_count, 0x0a) self.assertEqual(request.write_registers, [0x00]*5) def testSerializingToString(self): for request in iterkeys(self.request_read): self.assertTrue(str(request) != None) for request in iterkeys(self.response_read): self.assertTrue(str(request) != None) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_pdu.py0000644000175000017500000000600613150360615014675 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.pdu import * from pymodbus.exceptions import * from pymodbus.compat import iteritems class SimplePduTest(unittest.TestCase): ''' This is the unittest for the pymod.pdu module ''' def setUp(self): ''' Initializes the test environment ''' self.badRequests = ( # ModbusPDU(), ModbusRequest(), ModbusResponse(), ) self.illegal = IllegalFunctionRequest(1) self.exception = ExceptionResponse(1,1) def tearDown(self): ''' Cleans up the test environment ''' del self.badRequests del self.illegal del self.exception def testNotImpelmented(self): ''' Test a base classes for not implemented funtions ''' for r in self.badRequests: self.assertRaises(NotImplementedException, r.encode) for r in self.badRequests: self.assertRaises(NotImplementedException, r.decode, None) def testErrorMethods(self): ''' Test all error methods ''' self.illegal.decode("12345") self.illegal.execute(None) result = self.exception.encode() self.exception.decode(result) self.assertEqual(result, b'\x01') self.assertEqual(self.exception.exception_code, 1) def testRequestExceptionFactory(self): ''' Test all error methods ''' request = ModbusRequest() request.function_code = 1 errors = dict((ModbusExceptions.decode(c), c) for c in range(1,20)) for error, code in iteritems(errors): result = request.doException(code) self.assertEqual(str(result), "Exception Response(129, 1, %s)" % error) def testCalculateRtuFrameSize(self): ''' Test the calculation of Modbus/RTU frame sizes ''' self.assertRaises(NotImplementedException, ModbusRequest.calculateRtuFrameSize, b'') ModbusRequest._rtu_frame_size = 5 self.assertEqual(ModbusRequest.calculateRtuFrameSize(b''), 5) del ModbusRequest._rtu_frame_size ModbusRequest._rtu_byte_count_pos = 2 self.assertEqual(ModbusRequest.calculateRtuFrameSize( b'\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6'), 0x05 + 5) del ModbusRequest._rtu_byte_count_pos self.assertRaises(NotImplementedException, ModbusResponse.calculateRtuFrameSize, b'') ModbusResponse._rtu_frame_size = 12 self.assertEqual(ModbusResponse.calculateRtuFrameSize(b''), 12) del ModbusResponse._rtu_frame_size ModbusResponse._rtu_byte_count_pos = 2 self.assertEqual(ModbusResponse.calculateRtuFrameSize( b'\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6'), 0x05 + 5) del ModbusResponse._rtu_byte_count_pos #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_datastore.py0000644000175000017500000001231413150360615016072 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.datastore import * from pymodbus.datastore.store import BaseModbusDataBlock from pymodbus.exceptions import NotImplementedException from pymodbus.exceptions import NoSuchSlaveException from pymodbus.exceptions import ParameterException from pymodbus.datastore.remote import RemoteSlaveContext class ModbusDataStoreTest(unittest.TestCase): ''' This is the unittest for the pymodbus.datastore module ''' def setUp(self): pass def tearDown(self): ''' Cleans up the test environment ''' pass def testModbusDataBlock(self): ''' Test a base data block store ''' block = BaseModbusDataBlock() block.default(10, True) self.assertNotEqual(str(block), None) self.assertEqual(block.default_value, True) self.assertEqual(block.values, [True]*10) block.default_value = False block.reset() self.assertEqual(block.values, [False]*10) def testModbusDataBlockIterate(self): ''' Test a base data block store ''' block = BaseModbusDataBlock() block.default(10, False) for idx,value in block: self.assertEqual(value, False) block.values = {0 : False, 2 : False, 3 : False } for idx,value in block: self.assertEqual(value, False) def testModbusDataBlockOther(self): ''' Test a base data block store ''' block = BaseModbusDataBlock() self.assertRaises(NotImplementedException, lambda: block.validate(1,1)) self.assertRaises(NotImplementedException, lambda: block.getValues(1,1)) self.assertRaises(NotImplementedException, lambda: block.setValues(1,1)) def testModbusSequentialDataBlock(self): ''' Test a sequential data block store ''' block = ModbusSequentialDataBlock(0x00, [False]*10) self.assertFalse(block.validate(-1, 0)) self.assertFalse(block.validate(0, 20)) self.assertFalse(block.validate(10, 1)) self.assertTrue(block.validate(0x00, 10)) block.setValues(0x00, True) self.assertEqual(block.getValues(0x00, 1), [True]) block.setValues(0x00, [True]*10) self.assertEqual(block.getValues(0x00, 10), [True]*10) def testModbusSequentialDataBlockFactory(self): ''' Test the sequential data block store factory ''' block = ModbusSequentialDataBlock.create() self.assertEqual(block.getValues(0x00, 65536), [False]*65536) block = ModbusSequentialDataBlock(0x00, 0x01) self.assertEqual(block.values, [0x01]) def testModbusSparseDataBlock(self): ''' Test a sparse data block store ''' values = dict(enumerate([True]*10)) block = ModbusSparseDataBlock(values) self.assertFalse(block.validate(-1, 0)) self.assertFalse(block.validate(0, 20)) self.assertFalse(block.validate(10, 1)) self.assertTrue(block.validate(0x00, 10)) self.assertTrue(block.validate(0x00, 10)) self.assertFalse(block.validate(0, 0)) self.assertFalse(block.validate(5, 0)) block.setValues(0x00, True) self.assertEqual(block.getValues(0x00, 1), [True]) block.setValues(0x00, [True]*10) self.assertEqual(block.getValues(0x00, 10), [True]*10) block.setValues(0x00, dict(enumerate([False]*10))) self.assertEqual(block.getValues(0x00, 10), [False]*10) def testModbusSparseDataBlockFactory(self): ''' Test the sparse data block store factory ''' block = ModbusSparseDataBlock.create() self.assertEqual(block.getValues(0x00, 65536), [False]*65536) def testModbusSparseDataBlockOther(self): block = ModbusSparseDataBlock([True]*10) self.assertEqual(block.getValues(0x00, 10), [True]*10) self.assertRaises(ParameterException, lambda: ModbusSparseDataBlock(True)) def testModbusSlaveContext(self): ''' Test a modbus slave context ''' store = { 'di' : ModbusSequentialDataBlock(0, [False]*10), 'co' : ModbusSequentialDataBlock(0, [False]*10), 'ir' : ModbusSequentialDataBlock(0, [False]*10), 'hr' : ModbusSequentialDataBlock(0, [False]*10), } context = ModbusSlaveContext(**store) self.assertNotEqual(str(context), None) for fx in [1,2,3,4]: context.setValues(fx, 0, [True]*10) self.assertTrue(context.validate(fx, 0,10)) self.assertEqual(context.getValues(fx, 0,10), [True]*10) context.reset() for fx in [1,2,3,4]: self.assertTrue(context.validate(fx, 0,10)) self.assertEqual(context.getValues(fx, 0,10), [False]*10) def testModbusServerContext(self): ''' Test a modbus server context ''' def _set(ctx): ctx[0xffff] = None context = ModbusServerContext(single=False) self.assertRaises(NoSuchSlaveException, lambda: _set(context)) self.assertRaises(NoSuchSlaveException, lambda: context[0xffff]) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_server_sync.py0000644000175000017500000003650213150360615016453 0ustar wmbwmb#!/usr/bin/env python from pymodbus.compat import IS_PYTHON3 import unittest if IS_PYTHON3: # Python 3 from unittest.mock import patch, Mock else: # Python 2 from mock import patch, Mock import serial import socket from pymodbus.device import ModbusDeviceIdentification from pymodbus.server.sync import ModbusBaseRequestHandler from pymodbus.server.sync import ModbusSingleRequestHandler from pymodbus.server.sync import ModbusConnectedRequestHandler from pymodbus.server.sync import ModbusDisconnectedRequestHandler from pymodbus.server.sync import ModbusTcpServer, ModbusUdpServer, ModbusSerialServer from pymodbus.server.sync import StartTcpServer, StartUdpServer, StartSerialServer from pymodbus.exceptions import NotImplementedException from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse import sys from pymodbus.compat import socketserver SERIAL_PORT = "/dev/ptmx" if sys.platform == "darwin": SERIAL_PORT = "/dev/ptyp0" #---------------------------------------------------------------------------# # Mock Classes #---------------------------------------------------------------------------# class MockServer(object): def __init__(self): self.framer = lambda _: "framer" self.decoder = "decoder" self.threads = [] self.context = {} #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class SynchronousServerTest(unittest.TestCase): ''' This is the unittest for the pymodbus.server.sync module ''' #-----------------------------------------------------------------------# # Test Base Request Handler #-----------------------------------------------------------------------# def testBaseHandlerUndefinedMethods(self): ''' Test the base handler undefined methods''' handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusBaseRequestHandler self.assertRaises(NotImplementedException, lambda: handler.send(None)) self.assertRaises(NotImplementedException, lambda: handler.handle()) def testBaseHandlerMethods(self): ''' Test the base class for all the clients ''' request = ReadCoilsRequest(1, 1) address = ('server', 12345) server = MockServer() with patch.object(ModbusBaseRequestHandler, 'handle') as mock_handle: with patch.object(ModbusBaseRequestHandler, 'send') as mock_send: mock_handle.return_value = True mock_send.return_value = True handler = ModbusBaseRequestHandler(request, address, server) self.assertEqual(handler.running, True) self.assertEqual(handler.framer, 'framer') handler.execute(request) self.assertEqual(mock_send.call_count, 1) server.context[0x00] = object() handler.execute(request) self.assertEqual(mock_send.call_count, 2) #-----------------------------------------------------------------------# # Test Single Request Handler #-----------------------------------------------------------------------# def testModbusSingleRequestHandlerSend(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusSingleRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = Mock() request = ReadCoilsResponse([1]) handler.send(request) self.assertEqual(handler.request.send.call_count, 1) request.should_respond = False handler.send(request) self.assertEqual(handler.request.send.call_count, 1) def testModbusSingleRequestHandlerHandle(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusSingleRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = Mock() handler.request.recv.return_value = b"\x12\x34" # exit if we are not running handler.running = False handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 0) # run forever if we are running def _callback1(a, b): handler.running = False # stop infinite loop handler.framer.processIncomingPacket.side_effect = _callback1 handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 1) # exceptions are simply ignored def _callback2(a, b): if handler.framer.processIncomingPacket.call_count == 2: raise Exception("example exception") else: handler.running = False # stop infinite loop handler.framer.processIncomingPacket.side_effect = _callback2 handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 3) #-----------------------------------------------------------------------# # Test Connected Request Handler #-----------------------------------------------------------------------# def testModbusConnectedRequestHandlerSend(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusConnectedRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = Mock() request = ReadCoilsResponse([1]) handler.send(request) self.assertEqual(handler.request.send.call_count, 1) request.should_respond = False handler.send(request) self.assertEqual(handler.request.send.call_count, 1) def testModbusConnectedRequestHandlerHandle(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusConnectedRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = Mock() handler.request.recv.return_value = b"\x12\x34" # exit if we are not running handler.running = False handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 0) # run forever if we are running def _callback(a, b): handler.running = False # stop infinite loop handler.framer.processIncomingPacket.side_effect = _callback handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 1) # socket errors cause the client to disconnect handler.framer.processIncomingPacket.side_effect = socket.error() handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 2) # every other exception causes the client to disconnect handler.framer.processIncomingPacket.side_effect = Exception() handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 3) # receiving no data causes the client to disconnect handler.request.recv.return_value = None handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 3) #-----------------------------------------------------------------------# # Test Disconnected Request Handler #-----------------------------------------------------------------------# def testModbusDisconnectedRequestHandlerSend(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusDisconnectedRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = Mock() request = ReadCoilsResponse([1]) handler.send(request) self.assertEqual(handler.request.sendto.call_count, 1) request.should_respond = False handler.send(request) self.assertEqual(handler.request.sendto.call_count, 1) def testModbusDisconnectedRequestHandlerHandle(self): handler = socketserver.BaseRequestHandler(None, None, None) handler.__class__ = ModbusDisconnectedRequestHandler handler.framer = Mock() handler.framer.buildPacket.return_value = b"message" handler.request = (b"\x12\x34", handler.request) # exit if we are not running handler.running = False handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 0) # run forever if we are running def _callback(a, b): handler.running = False # stop infinite loop handler.framer.processIncomingPacket.side_effect = _callback handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 1) # socket errors cause the client to disconnect handler.request = (b"\x12\x34", handler.request) handler.framer.processIncomingPacket.side_effect = socket.error() handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 2) # every other exception causes the client to disconnect handler.request = (b"\x12\x34", handler.request) handler.framer.processIncomingPacket.side_effect = Exception() handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 3) # receiving no data causes the client to disconnect handler.request = (None, handler.request) handler.running = True handler.handle() self.assertEqual(handler.framer.processIncomingPacket.call_count, 3) #-----------------------------------------------------------------------# # Test TCP Server #-----------------------------------------------------------------------# def testTcpServerClose(self): ''' test that the synchronous TCP server closes correctly ''' with patch.object(socket.socket, 'bind') as mock_socket: identity = ModbusDeviceIdentification(info={0x00: 'VendorName'}) server = ModbusTcpServer(context=None, identity=identity) server.threads.append(Mock(**{'running': True})) server.server_close() self.assertEqual(server.control.Identity.VendorName, 'VendorName') self.assertFalse(server.threads[0].running) def testTcpServerProcess(self): ''' test that the synchronous TCP server processes requests ''' with patch('pymodbus.compat.socketserver.ThreadingTCPServer') as mock_server: server = ModbusTcpServer(None) server.process_request('request', 'client') self.assertTrue(mock_server.process_request.called) #-----------------------------------------------------------------------# # Test UDP Server #-----------------------------------------------------------------------# def testUdpServerClose(self): ''' test that the synchronous UDP server closes correctly ''' with patch.object(socket.socket, 'bind') as mock_socket: identity = ModbusDeviceIdentification(info={0x00: 'VendorName'}) server = ModbusUdpServer(context=None, identity=identity) server.threads.append(Mock(**{'running': True})) server.server_close() self.assertEqual(server.control.Identity.VendorName, 'VendorName') self.assertFalse(server.threads[0].running) def testUdpServerProcess(self): ''' test that the synchronous UDP server processes requests ''' with patch('pymodbus.compat.socketserver.ThreadingUDPServer') as mock_server: server = ModbusUdpServer(None) request = ('data', 'socket') server.process_request(request, 'client') self.assertTrue(mock_server.process_request.called) #-----------------------------------------------------------------------# # Test Serial Server #-----------------------------------------------------------------------# def testSerialServerConnect(self): with patch.object(serial, 'Serial') as mock_serial: # mock_serial.return_value = "socket" mock_serial.write = lambda x: len(x) mock_serial.read = lambda size: '\x00' * size identity = ModbusDeviceIdentification(info={0x00: 'VendorName'}) server = ModbusSerialServer(context=None, identity=identity, port="dummy") # # mock_serial.return_value = "socket" # self.assertEqual(server.socket.port, "dummy") self.assertEquals(server.handler.__class__.__name__, "CustomSingleRequestHandler") self.assertEqual(server.control.Identity.VendorName, 'VendorName') server._connect() # self.assertEqual(server.socket, "socket") with patch.object(serial, 'Serial') as mock_serial: mock_serial.write = lambda x: len(x) mock_serial.read = lambda size: '\x00' * size mock_serial.side_effect = serial.SerialException() server = ModbusSerialServer(None, port="dummy") self.assertEqual(server.socket, None) def testSerialServerServeForever(self): ''' test that the synchronous serial server closes correctly ''' with patch.object(serial, 'Serial') as mock_serial: with patch('pymodbus.server.sync.CustomSingleRequestHandler') as mock_handler: server = ModbusSerialServer(None) instance = mock_handler.return_value instance.handle.side_effect = server.server_close server.serve_forever() instance.handle.assert_any_call() def testSerialServerClose(self): ''' test that the synchronous serial server closes correctly ''' with patch.object(serial, 'Serial') as mock_serial: instance = mock_serial.return_value server = ModbusSerialServer(None) server.server_close() instance.close.assert_any_call() #-----------------------------------------------------------------------# # Test Synchronous Factories #-----------------------------------------------------------------------# def testStartTcpServer(self): ''' Test the tcp server starting factory ''' with patch.object(ModbusTcpServer, 'serve_forever') as mock_server: with patch.object(socketserver.TCPServer, 'server_bind') as mock_binder: StartTcpServer() def testStartUdpServer(self): ''' Test the udp server starting factory ''' with patch.object(ModbusUdpServer, 'serve_forever') as mock_server: with patch.object(socketserver.UDPServer, 'server_bind') as mock_binder: StartUdpServer() def testStartSerialServer(self): ''' Test the serial server starting factory ''' with patch.object(ModbusSerialServer, 'serve_forever') as mock_server: StartSerialServer(port=SERIAL_PORT) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_events.py0000644000175000017500000000477613150360615015425 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.events import * from pymodbus.exceptions import NotImplementedException from pymodbus.exceptions import ParameterException class ModbusEventsTest(unittest.TestCase): ''' This is the unittest for the pymodbus.device module ''' def setUp(self): ''' Sets up the test environment ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass def testModbusEventBaseClass(self): event = ModbusEvent() self.assertRaises(NotImplementedException, event.encode) self.assertRaises(NotImplementedException, lambda: event.decode(None)) def testRemoteReceiveEvent(self): event = RemoteReceiveEvent() event.decode(b'\x70') self.assertTrue(event.overrun) self.assertTrue(event.listen) self.assertTrue(event.broadcast) def testRemoteSentEvent(self): event = RemoteSendEvent() result = event.encode() self.assertEqual(result, b'\x40') event.decode(b'\x7f') self.assertTrue(event.read) self.assertTrue(event.slave_abort) self.assertTrue(event.slave_busy) self.assertTrue(event.slave_nak) self.assertTrue(event.write_timeout) self.assertTrue(event.listen) def testRemoteSentEventEncode(self): arguments = { 'read' : True, 'slave_abort' : True, 'slave_busy' : True, 'slave_nak' : True, 'write_timeout' : True, 'listen' : True, } event = RemoteSendEvent(**arguments) result = event.encode() self.assertEqual(result, b'\x7f') def testEnteredListenModeEvent(self): event = EnteredListenModeEvent() result = event.encode() self.assertEqual(result, b'\x04') event.decode(b'\x04') self.assertEqual(event.value, 0x04) self.assertRaises(ParameterException, lambda: event.decode(b'\x00')) def testCommunicationRestartEvent(self): event = CommunicationRestartEvent() result = event.encode() self.assertEqual(result, b'\x00') event.decode(b'\x00') self.assertEqual(event.value, 0x00) self.assertRaises(ParameterException, lambda: event.decode(b'\x04')) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_remote_datastore.py0000644000175000017500000000504213150360615017445 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.exceptions import NotImplementedException from pymodbus.datastore.remote import RemoteSlaveContext from pymodbus.bit_read_message import * from pymodbus.bit_write_message import * from pymodbus.register_read_message import * from pymodbus.pdu import ExceptionResponse from .modbus_mocks import mock class RemoteModbusDataStoreTest(unittest.TestCase): ''' This is the unittest for the pymodbus.datastore.remote module ''' def testRemoteSlaveContext(self): ''' Test a modbus remote slave context ''' context = RemoteSlaveContext(None) self.assertNotEqual(str(context), None) self.assertRaises(NotImplementedException, lambda: context.reset()) def testRemoteSlaveSetValues(self): ''' Test setting values against a remote slave context ''' client = mock() client.write_coils = lambda a,b: WriteMultipleCoilsResponse() context = RemoteSlaveContext(client) result = context.setValues(1, 0, [1]) self.assertTrue(True) def testRemoteSlaveGetValues(self): ''' Test getting values from a remote slave context ''' client = mock() client.read_coils = lambda a,b: ReadCoilsResponse([1]*10) client.read_input_registers = lambda a,b: ReadInputRegistersResponse([10]*10) client.read_holding_registers = lambda a,b: ExceptionResponse(0x15) context = RemoteSlaveContext(client) result = context.getValues(1, 0, 10) self.assertEqual(result, [1]*10) result = context.getValues(4, 0, 10) self.assertEqual(result, [10]*10) result = context.getValues(3, 0, 10) self.assertNotEqual(result, [10]*10) def testRemoteSlaveValidateValues(self): ''' Test validating against a remote slave context ''' client = mock() client.read_coils = lambda a,b: ReadCoilsResponse([1]*10) client.read_input_registers = lambda a,b: ReadInputRegistersResponse([10]*10) client.read_holding_registers = lambda a,b: ExceptionResponse(0x15) context = RemoteSlaveContext(client) result = context.validate(1, 0, 10) self.assertTrue(result) result = context.validate(4, 0, 10) self.assertTrue(result) result = context.validate(3, 0, 10) self.assertFalse(result) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_client_async.py0000644000175000017500000001635413150360615016567 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.compat import IS_PYTHON3 if IS_PYTHON3: # Python 3 from unittest.mock import patch, Mock else: # Python 2 from mock import patch, Mock from pymodbus.client.async import ModbusClientProtocol, ModbusUdpClientProtocol from pymodbus.client.async import ModbusClientFactory from pymodbus.exceptions import ConnectionException from pymodbus.transaction import ModbusSocketFramer from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class AsynchronousClientTest(unittest.TestCase): ''' This is the unittest for the pymodbus.client.async module ''' #-----------------------------------------------------------------------# # Test Client Protocol #-----------------------------------------------------------------------# def testClientProtocolInit(self): ''' Test the client protocol initialize ''' protocol = ModbusClientProtocol() self.assertEqual(0, len(list(protocol.transaction))) self.assertFalse(protocol._connected) self.assertTrue(isinstance(protocol.framer, ModbusSocketFramer)) framer = object() protocol = ModbusClientProtocol(framer=framer) self.assertEqual(0, len(list(protocol.transaction))) self.assertFalse(protocol._connected) self.assertTrue(framer is protocol.framer) def testClientProtocolConnect(self): ''' Test the client protocol connect ''' protocol = ModbusClientProtocol() self.assertFalse(protocol._connected) protocol.connectionMade() self.assertTrue(protocol._connected) def testClientProtocolDisconnect(self): ''' Test the client protocol disconnect ''' protocol = ModbusClientProtocol() protocol.connectionMade() def handle_failure(failure): self.assertTrue(isinstance(failure.value, ConnectionException)) d = protocol._buildResponse(0x00) d.addErrback(handle_failure) self.assertTrue(protocol._connected) protocol.connectionLost('because') self.assertFalse(protocol._connected) def testClientProtocolDataReceived(self): ''' Test the client protocol data received ''' protocol = ModbusClientProtocol() protocol.connectionMade() out = [] data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04' # setup existing request d = protocol._buildResponse(0x00) d.addCallback(lambda v: out.append(v)) protocol.dataReceived(data) self.assertTrue(isinstance(out[0], ReadCoilsResponse)) def testClientProtocolExecute(self): ''' Test the client protocol execute method ''' protocol = ModbusClientProtocol() protocol.connectionMade() protocol.transport = Mock() protocol.transport.write = Mock() request = ReadCoilsRequest(1, 1) d = protocol.execute(request) tid = request.transaction_id self.assertEqual(d, protocol.transaction.getTransaction(tid)) def testClientProtocolHandleResponse(self): ''' Test the client protocol handles responses ''' protocol = ModbusClientProtocol() protocol.connectionMade() out = [] reply = ReadCoilsRequest(1, 1) reply.transaction_id = 0x00 # handle skipped cases protocol._handleResponse(None) protocol._handleResponse(reply) # handle existing cases d = protocol._buildResponse(0x00) d.addCallback(lambda v: out.append(v)) protocol._handleResponse(reply) self.assertEqual(out[0], reply) def testClientProtocolBuildResponse(self): ''' Test the udp client protocol builds responses ''' protocol = ModbusClientProtocol() self.assertEqual(0, len(list(protocol.transaction))) def handle_failure(failure): self.assertTrue(isinstance(failure.value, ConnectionException)) d = protocol._buildResponse(0x00) d.addErrback(handle_failure) self.assertEqual(0, len(list(protocol.transaction))) protocol._connected = True d = protocol._buildResponse(0x00) self.assertEqual(1, len(list(protocol.transaction))) #-----------------------------------------------------------------------# # Test Udp Client Protocol #-----------------------------------------------------------------------# def testUdpClientProtocolInit(self): ''' Test the udp client protocol initialize ''' protocol = ModbusUdpClientProtocol() self.assertEqual(0, len(list(protocol.transaction))) self.assertTrue(isinstance(protocol.framer, ModbusSocketFramer)) framer = object() protocol = ModbusClientProtocol(framer=framer) self.assertTrue(framer is protocol.framer) def testUdpClientProtocolDataReceived(self): ''' Test the udp client protocol data received ''' protocol = ModbusUdpClientProtocol() out = [] data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04' server = ('127.0.0.1', 12345) # setup existing request d = protocol._buildResponse(0x00) d.addCallback(lambda v: out.append(v)) protocol.datagramReceived(data, server) self.assertTrue(isinstance(out[0], ReadCoilsResponse)) def testUdpClientProtocolExecute(self): ''' Test the udp client protocol execute method ''' protocol = ModbusUdpClientProtocol() protocol.transport = Mock() protocol.transport.write = Mock() request = ReadCoilsRequest(1, 1) d = protocol.execute(request) tid = request.transaction_id self.assertEqual(d, protocol.transaction.getTransaction(tid)) def testUdpClientProtocolHandleResponse(self): ''' Test the udp client protocol handles responses ''' protocol = ModbusUdpClientProtocol() out = [] reply = ReadCoilsRequest(1, 1) reply.transaction_id = 0x00 # handle skipped cases protocol._handleResponse(None) protocol._handleResponse(reply) # handle existing cases d = protocol._buildResponse(0x00) d.addCallback(lambda v: out.append(v)) protocol._handleResponse(reply) self.assertEqual(out[0], reply) def testUdpClientProtocolBuildResponse(self): ''' Test the udp client protocol builds responses ''' protocol = ModbusUdpClientProtocol() self.assertEqual(0, len(list(protocol.transaction))) d = protocol._buildResponse(0x00) self.assertEqual(1, len(list(protocol.transaction))) #-----------------------------------------------------------------------# # Test Client Factories #-----------------------------------------------------------------------# def testModbusClientFactory(self): ''' Test the base class for all the clients ''' factory = ModbusClientFactory() self.assertTrue(factory is not None) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_client_sync.py0000644000175000017500000002551713150360615016427 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.compat import IS_PYTHON3 if IS_PYTHON3: # Python 3 from unittest.mock import patch, Mock else: # Python 2 from mock import patch, Mock import socket import serial from pymodbus.client.sync import ModbusTcpClient, ModbusUdpClient from pymodbus.client.sync import ModbusSerialClient, BaseModbusClient from pymodbus.exceptions import ConnectionException, NotImplementedException from pymodbus.exceptions import ParameterException from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer from pymodbus.transaction import ModbusBinaryFramer #---------------------------------------------------------------------------# # Mock Classes #---------------------------------------------------------------------------# class mockSocket(object): def close(self): return True def recv(self, size): return '\x00'*size def read(self, size): return '\x00'*size def send(self, msg): return len(msg) def write(self, msg): return len(msg) def recvfrom(self, size): return ['\x00'*size] def sendto(self, msg, *args): return len(msg) def in_waiting(self): return None #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class SynchronousClientTest(unittest.TestCase): ''' This is the unittest for the pymodbus.client.sync module ''' #-----------------------------------------------------------------------# # Test Base Client #-----------------------------------------------------------------------# def testBaseModbusClient(self): ''' Test the base class for all the clients ''' client = BaseModbusClient(None) client.transaction = None self.assertRaises(NotImplementedException, lambda: client.connect()) self.assertRaises(NotImplementedException, lambda: client._send(None)) self.assertRaises(NotImplementedException, lambda: client._recv(None)) self.assertRaises(NotImplementedException, lambda: client.__enter__()) self.assertRaises(NotImplementedException, lambda: client.execute()) self.assertEqual("Null Transport", str(client)) client.close() client.__exit__(0,0,0) # a successful execute client.connect = lambda: True client.transaction = Mock(**{'execute.return_value': True}) self.assertEqual(client, client.__enter__()) self.assertTrue(client.execute()) # a unsuccessful connect client.connect = lambda: False self.assertRaises(ConnectionException, lambda: client.__enter__()) self.assertRaises(ConnectionException, lambda: client.execute()) #-----------------------------------------------------------------------# # Test UDP Client #-----------------------------------------------------------------------# def testSyncUdpClientInstantiation(self): client = ModbusUdpClient() self.assertNotEqual(client, None) def testBasicSyncUdpClient(self): ''' Test the basic methods for the udp sync client''' # receive/send client = ModbusUdpClient() client.socket = mockSocket() self.assertEqual(0, client._send(None)) self.assertEqual(1, client._send('\x00')) self.assertEqual('\x00', client._recv(1)) # connect/disconnect self.assertTrue(client.connect()) client.close() # already closed socket client.socket = False client.close() self.assertEqual("127.0.0.1:502", str(client)) def testUdpClientAddressFamily(self): ''' Test the Udp client get address family method''' client = ModbusUdpClient() self.assertEqual(socket.AF_INET, client._get_address_family('127.0.0.1')) self.assertEqual(socket.AF_INET6, client._get_address_family('::1')) def testUdpClientConnect(self): ''' Test the Udp client connection method''' with patch.object(socket, 'socket') as mock_method: class DummySocket(object): def settimeout(self, *a, **kwa): pass mock_method.return_value = DummySocket() client = ModbusUdpClient() self.assertTrue(client.connect()) with patch.object(socket, 'socket') as mock_method: mock_method.side_effect = socket.error() client = ModbusUdpClient() self.assertFalse(client.connect()) def testUdpClientSend(self): ''' Test the udp client send method''' client = ModbusUdpClient() self.assertRaises(ConnectionException, lambda: client._send(None)) client.socket = mockSocket() self.assertEqual(0, client._send(None)) self.assertEqual(4, client._send('1234')) def testUdpClientRecv(self): ''' Test the udp client receive method''' client = ModbusUdpClient() self.assertRaises(ConnectionException, lambda: client._recv(1024)) client.socket = mockSocket() self.assertEqual('', client._recv(0)) self.assertEqual('\x00'*4, client._recv(4)) #-----------------------------------------------------------------------# # Test TCP Client #-----------------------------------------------------------------------# def testSyncTcpClientInstantiation(self): client = ModbusTcpClient() self.assertNotEqual(client, None) def testBasicSyncTcpClient(self): ''' Test the basic methods for the tcp sync client''' # receive/send client = ModbusTcpClient() client.socket = mockSocket() self.assertEqual(0, client._send(None)) self.assertEqual(1, client._send('\x00')) self.assertEqual('\x00', client._recv(1)) # connect/disconnect self.assertTrue(client.connect()) client.close() # already closed socket client.socket = False client.close() self.assertEqual("127.0.0.1:502", str(client)) def testTcpClientConnect(self): ''' Test the tcp client connection method''' with patch.object(socket, 'create_connection') as mock_method: mock_method.return_value = object() client = ModbusTcpClient() self.assertTrue(client.connect()) with patch.object(socket, 'create_connection') as mock_method: mock_method.side_effect = socket.error() client = ModbusTcpClient() self.assertFalse(client.connect()) def testTcpClientSend(self): ''' Test the tcp client send method''' client = ModbusTcpClient() self.assertRaises(ConnectionException, lambda: client._send(None)) client.socket = mockSocket() self.assertEqual(0, client._send(None)) self.assertEqual(4, client._send('1234')) def testTcpClientRecv(self): ''' Test the tcp client receive method''' client = ModbusTcpClient() self.assertRaises(ConnectionException, lambda: client._recv(1024)) client.socket = mockSocket() self.assertEqual('', client._recv(0)) self.assertEqual('\x00'*4, client._recv(4)) #-----------------------------------------------------------------------# # Test Serial Client #-----------------------------------------------------------------------# def testSyncSerialClientInstantiation(self): client = ModbusSerialClient() self.assertNotEqual(client, None) self.assertTrue(isinstance(ModbusSerialClient(method='ascii').framer, ModbusAsciiFramer)) self.assertTrue(isinstance(ModbusSerialClient(method='rtu').framer, ModbusRtuFramer)) self.assertTrue(isinstance(ModbusSerialClient(method='binary').framer, ModbusBinaryFramer)) self.assertRaises(ParameterException, lambda: ModbusSerialClient(method='something')) def testSyncSerialRTUClientTimeouts(self): client = ModbusSerialClient(method="rtu", baudrate=9600) assert client._silent_interval == (3.5 * 11/9600) client = ModbusSerialClient(method="rtu", baudrate=38400) assert client._silent_interval == (1.75/1000) @patch("serial.Serial") def testBasicSyncSerialClient(self, mock_serial): ''' Test the basic methods for the serial sync client''' # receive/send mock_serial.in_waiting = 0 mock_serial.write = lambda x: len(x) mock_serial.read = lambda size: '\x00' * size client = ModbusSerialClient() client.socket = mock_serial self.assertEqual(0, client._send(None)) self.assertEqual(1, client._send('\x00')) self.assertEqual('\x00', client._recv(1)) # connect/disconnect self.assertTrue(client.connect()) client.close() # already closed socket client.socket = False client.close() self.assertEqual('ascii baud[19200]', str(client)) def testSerialClientConnect(self): ''' Test the serial client connection method''' with patch.object(serial, 'Serial') as mock_method: mock_method.return_value = object() client = ModbusSerialClient() self.assertTrue(client.connect()) with patch.object(serial, 'Serial') as mock_method: mock_method.side_effect = serial.SerialException() client = ModbusSerialClient() self.assertFalse(client.connect()) @patch("serial.Serial") def testSerialClientSend(self, mock_serial): ''' Test the serial client send method''' mock_serial.in_waiting = None mock_serial.write = lambda x: len(x) client = ModbusSerialClient() self.assertRaises(ConnectionException, lambda: client._send(None)) # client.connect() client.socket = mock_serial self.assertEqual(0, client._send(None)) self.assertEqual(4, client._send('1234')) @patch("serial.Serial") def testSerialClientCleanupBufferBeforeSend(self, mock_serial): ''' Test the serial client send method''' mock_serial.in_waiting = 4 mock_serial.read = lambda x: b'1'*x mock_serial.write = lambda x: len(x) client = ModbusSerialClient() self.assertRaises(ConnectionException, lambda: client._send(None)) # client.connect() client.socket = mock_serial self.assertEqual(0, client._send(None)) self.assertEqual(4, client._send('1234')) def testSerialClientRecv(self): ''' Test the serial client receive method''' client = ModbusSerialClient() self.assertRaises(ConnectionException, lambda: client._recv(1024)) client.socket = mockSocket() self.assertEqual('', client._recv(0)) self.assertEqual('\x00'*4, client._recv(4)) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/modbus_mocks.py0000644000175000017500000000146713150360615015541 0ustar wmbwmbfrom pymodbus.interfaces import IModbusSlaveContext #---------------------------------------------------------------------------# # Mocks #---------------------------------------------------------------------------# class mock(object): pass class MockContext(IModbusSlaveContext): def __init__(self, valid=False, default=True): self.valid = valid self.default = default def validate(self, fx, address, count): return self.valid def getValues(self, fx, address, count): return [self.default] * count def setValues(self, fx, address, count): pass class FakeList(object): ''' todo, replace with magic mock ''' def __init__(self, size): self.size = size def __len__(self): return self.size def __iter__(self): return [] pymodbus-1.3.2/test/test_server_context.py0000644000175000017500000000702613150360615017162 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.datastore import * from pymodbus.exceptions import NoSuchSlaveException from pymodbus.compat import iteritems class ModbusServerSingleContextTest(unittest.TestCase): ''' This is the unittest for the pymodbus.datastore.ModbusServerContext using a single slave context. ''' def setUp(self): ''' Sets up the test environment ''' self.slave = ModbusSlaveContext() self.context = ModbusServerContext(slaves=self.slave, single=True) def tearDown(self): ''' Cleans up the test environment ''' del self.context def testSingleContextGets(self): ''' Test getting on a single context ''' for id in range(0, 0xff): self.assertEqual(self.slave, self.context[id]) def testSingleContextDeletes(self): ''' Test removing on multiple context ''' def _test(): del self.context[0x00] self.assertRaises(NoSuchSlaveException, _test) def testSingleContextIter(self): ''' Test iterating over a single context ''' expected = (0, self.slave) for slave in self.context: self.assertEqual(slave, expected) def testSingleContextDefault(self): ''' Test that the single context default values work ''' self.context = ModbusServerContext() slave = self.context[0x00] self.assertEqual(slave, {}) def testSingleContextSet(self): ''' Test a setting a single slave context ''' slave = ModbusSlaveContext() self.context[0x00] = slave actual = self.context[0x00] self.assertEqual(slave, actual) class ModbusServerMultipleContextTest(unittest.TestCase): ''' This is the unittest for the pymodbus.datastore.ModbusServerContext using multiple slave contexts. ''' def setUp(self): ''' Sets up the test environment ''' self.slaves = dict((id, ModbusSlaveContext()) for id in range(10)) self.context = ModbusServerContext(slaves=self.slaves, single=False) def tearDown(self): ''' Cleans up the test environment ''' del self.context def testMultipleContextGets(self): ''' Test getting on multiple context ''' for id in range(0, 10): self.assertEqual(self.slaves[id], self.context[id]) def testMultipleContextDeletes(self): ''' Test removing on multiple context ''' del self.context[0x00] self.assertRaises(NoSuchSlaveException, lambda: self.context[0x00]) def testMultipleContextIter(self): ''' Test iterating over multiple context ''' for id, slave in self.context: self.assertEqual(slave, self.slaves[id]) self.assertTrue(id in self.context) def testMultipleContextDefault(self): ''' Test that the multiple context default values work ''' self.context = ModbusServerContext(single=False) self.assertRaises(NoSuchSlaveException, lambda: self.context[0x00]) def testMultipleContextSet(self): ''' Test a setting multiple slave contexts ''' slaves = dict((id, ModbusSlaveContext()) for id in range(10)) for id, slave in iteritems(slaves): self.context[id] = slave for id, slave in iteritems(slaves): actual = self.context[id] self.assertEqual(slave, actual) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_diag_messages.py0000644000175000017500000001747213150360615016711 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.exceptions import * from pymodbus.constants import ModbusPlusOperation from pymodbus.diag_message import * from pymodbus.diag_message import DiagnosticStatusRequest from pymodbus.diag_message import DiagnosticStatusResponse from pymodbus.diag_message import DiagnosticStatusSimpleRequest from pymodbus.diag_message import DiagnosticStatusSimpleResponse class SimpleDataStoreTest(unittest.TestCase): ''' This is the unittest for the pymodbus.diag_message module ''' def setUp(self): self.requests = [ #(DiagnosticStatusRequest, b'\x00\x00\x00\x00'), #(DiagnosticStatusSimpleRequest, b'\x00\x00\x00\x00'), (RestartCommunicationsOptionRequest, b'\x00\x01\x00\x00', b'\x00\x01\xff\x00'), (ReturnDiagnosticRegisterRequest, b'\x00\x02\x00\x00', b'\x00\x02\x00\x00'), (ChangeAsciiInputDelimiterRequest, b'\x00\x03\x00\x00', b'\x00\x03\x00\x00'), (ForceListenOnlyModeRequest, b'\x00\x04\x00\x00', b'\x00\x04'), (ReturnQueryDataRequest, b'\x00\x00\x00\x00', b'\x00\x00\x00\x00'), (ClearCountersRequest, b'\x00\x0a\x00\x00', b'\x00\x0a\x00\x00'), (ReturnBusMessageCountRequest, b'\x00\x0b\x00\x00', b'\x00\x0b\x00\x00'), (ReturnBusCommunicationErrorCountRequest, b'\x00\x0c\x00\x00', b'\x00\x0c\x00\x00'), (ReturnBusExceptionErrorCountRequest, b'\x00\x0d\x00\x00', b'\x00\x0d\x00\x00'), (ReturnSlaveMessageCountRequest, b'\x00\x0e\x00\x00', b'\x00\x0e\x00\x00'), (ReturnSlaveNoResponseCountRequest, b'\x00\x0f\x00\x00', b'\x00\x0f\x00\x00'), (ReturnSlaveNAKCountRequest, b'\x00\x10\x00\x00', b'\x00\x10\x00\x00'), (ReturnSlaveBusyCountRequest, b'\x00\x11\x00\x00', b'\x00\x11\x00\x00'), (ReturnSlaveBusCharacterOverrunCountRequest, b'\x00\x12\x00\x00', b'\x00\x12\x00\x00'), (ReturnIopOverrunCountRequest, b'\x00\x13\x00\x00', b'\x00\x13\x00\x00'), (ClearOverrunCountRequest, b'\x00\x14\x00\x00', b'\x00\x14\x00\x00'), (GetClearModbusPlusRequest, b'\x00\x15\x00\x00', b'\x00\x15\x00\x00' + b'\x00\x00' * 55), ] self.responses = [ #(DiagnosticStatusResponse, b'\x00\x00\x00\x00'), #(DiagnosticStatusSimpleResponse, b'\x00\x00\x00\x00'), (ReturnQueryDataResponse, b'\x00\x00\x00\x00'), (RestartCommunicationsOptionResponse, b'\x00\x01\x00\x00'), (ReturnDiagnosticRegisterResponse, b'\x00\x02\x00\x00'), (ChangeAsciiInputDelimiterResponse, b'\x00\x03\x00\x00'), (ForceListenOnlyModeResponse, b'\x00\x04'), (ReturnQueryDataResponse, b'\x00\x00\x00\x00'), (ClearCountersResponse, b'\x00\x0a\x00\x00'), (ReturnBusMessageCountResponse, b'\x00\x0b\x00\x00'), (ReturnBusCommunicationErrorCountResponse, b'\x00\x0c\x00\x00'), (ReturnBusExceptionErrorCountResponse, b'\x00\x0d\x00\x00'), (ReturnSlaveMessageCountResponse, b'\x00\x0e\x00\x00'), (ReturnSlaveNoReponseCountResponse, b'\x00\x0f\x00\x00'), (ReturnSlaveNAKCountResponse, b'\x00\x10\x00\x00'), (ReturnSlaveBusyCountResponse, b'\x00\x11\x00\x00'), (ReturnSlaveBusCharacterOverrunCountResponse, b'\x00\x12\x00\x00'), (ReturnIopOverrunCountResponse, b'\x00\x13\x00\x00'), (ClearOverrunCountResponse, b'\x00\x14\x00\x00'), (GetClearModbusPlusResponse, b'\x00\x15\x00\x04' + b'\x00\x00' * 55), ] def tearDown(self): ''' Cleans up the test environment ''' del self.requests del self.responses def testDiagnosticRequestsDecode(self): ''' Testing diagnostic request messages encoding ''' for msg,enc,exe in self.requests: handle = DiagnosticStatusRequest() handle.decode(enc) self.assertEqual(handle.sub_function_code, msg.sub_function_code) def testDiagnosticSimpleRequests(self): ''' Testing diagnostic request messages encoding ''' request = DiagnosticStatusSimpleRequest(b'\x12\x34') request.sub_function_code = 0x1234 self.assertRaises(NotImplementedException, lambda: request.execute()) self.assertEqual(request.encode(), b'\x12\x34\x12\x34') response = DiagnosticStatusSimpleResponse(None) def testDiagnosticResponseDecode(self): ''' Testing diagnostic request messages encoding ''' for msg,enc,exe in self.requests: handle = DiagnosticStatusResponse() handle.decode(enc) self.assertEqual(handle.sub_function_code, msg.sub_function_code) def testDiagnosticRequestsEncode(self): ''' Testing diagnostic request messages encoding ''' for msg,enc,exe in self.requests: self.assertEqual(msg().encode(), enc) #def testDiagnosticResponse(self): # ''' Testing diagnostic request messages ''' # for msg,enc in self.responses: # self.assertEqual(msg().encode(), enc) def testDiagnosticExecute(self): ''' Testing diagnostic message execution ''' for message, encoded, executed in self.requests: encoded = message().execute().encode() self.assertEqual(encoded, executed) def testReturnQueryDataRequest(self): ''' Testing diagnostic message execution ''' message = ReturnQueryDataRequest([0x0000]*2) self.assertEqual(message.encode(), b'\x00\x00\x00\x00\x00\x00'); message = ReturnQueryDataRequest(0x0000) self.assertEqual(message.encode(), b'\x00\x00\x00\x00'); def testReturnQueryDataResponse(self): ''' Testing diagnostic message execution ''' message = ReturnQueryDataResponse([0x0000]*2) self.assertEqual(message.encode(), b'\x00\x00\x00\x00\x00\x00'); message = ReturnQueryDataResponse(0x0000) self.assertEqual(message.encode(), b'\x00\x00\x00\x00'); def testRestartCommunicationsOption(self): ''' Testing diagnostic message execution ''' request = RestartCommunicationsOptionRequest(True); self.assertEqual(request.encode(), b'\x00\x01\xff\x00') request = RestartCommunicationsOptionRequest(False); self.assertEqual(request.encode(), b'\x00\x01\x00\x00') response = RestartCommunicationsOptionResponse(True); self.assertEqual(response.encode(), b'\x00\x01\xff\x00') response = RestartCommunicationsOptionResponse(False); self.assertEqual(response.encode(), b'\x00\x01\x00\x00') def testGetClearModbusPlusRequestExecute(self): ''' Testing diagnostic message execution ''' request = GetClearModbusPlusRequest(data=ModbusPlusOperation.ClearStatistics); response = request.execute() self.assertEqual(response.message, ModbusPlusOperation.ClearStatistics) request = GetClearModbusPlusRequest(data=ModbusPlusOperation.GetStatistics); response = request.execute() resp = [ModbusPlusOperation.GetStatistics] self.assertEqual(response.message, resp+[0x00] * 55) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_factory.py0000644000175000017500000002121513150360615015553 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.factory import ServerDecoder, ClientDecoder from pymodbus.exceptions import ModbusException def _raise_exception(_): raise ModbusException('something') class SimpleFactoryTest(unittest.TestCase): ''' This is the unittest for the pymod.exceptions module ''' def setUp(self): ''' Initializes the test environment ''' self.client = ClientDecoder() self.server = ServerDecoder() self.request = ( (0x01, b'\x01\x00\x01\x00\x01'), # read coils (0x02, b'\x02\x00\x01\x00\x01'), # read discrete inputs (0x03, b'\x03\x00\x01\x00\x01'), # read holding registers (0x04, b'\x04\x00\x01\x00\x01'), # read input registers (0x05, b'\x05\x00\x01\x00\x01'), # write single coil (0x06, b'\x06\x00\x01\x00\x01'), # write single register (0x07, b'\x07'), # read exception status (0x08, b'\x08\x00\x00\x00\x00'), # read diagnostic (0x0b, b'\x0b'), # get comm event counters (0x0c, b'\x0c'), # get comm event log (0x0f, b'\x0f\x00\x01\x00\x08\x01\x00\xff'), # write multiple coils (0x10, b'\x10\x00\x01\x00\x02\x04\0xff\xff'), # write multiple registers (0x11, b'\x11'), # report slave id (0x14, b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02' \ b'\x06\x00\x03\x00\x09\x00\x02'), # read file record (0x15, b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03' \ b'\x06\xaf\x04\xbe\x10\x0d'), # write file record (0x16, b'\x16\x00\x01\x00\xff\xff\x00'), # mask write register (0x17, b'\x17\x00\x01\x00\x01\x00\x01\x00\x01\x02\x12\x34'),# read/write multiple registers (0x18, b'\x18\x00\x01'), # read fifo queue (0x2b, b'\x2b\x0e\x01\x00'), # read device identification ) self.response = ( (0x01, b'\x01\x01\x01'), # read coils (0x02, b'\x02\x01\x01'), # read discrete inputs (0x03, b'\x03\x02\x01\x01'), # read holding registers (0x04, b'\x04\x02\x01\x01'), # read input registers (0x05, b'\x05\x00\x01\x00\x01'), # write single coil (0x06, b'\x06\x00\x01\x00\x01'), # write single register (0x07, b'\x07\x00'), # read exception status (0x08, b'\x08\x00\x00\x00\x00'), # read diagnostic (0x0b, b'\x0b\x00\x00\x00\x00'), # get comm event counters (0x0c, b'\x0c\x08\x00\x00\x01\x08\x01\x21\x20\x00'), # get comm event log (0x0f, b'\x0f\x00\x01\x00\x08'), # write multiple coils (0x10, b'\x10\x00\x01\x00\x02'), # write multiple registers (0x11, b'\x11\x03\x05\x01\x54'), # report slave id (device specific) (0x14, b'\x14\x0c\x05\x06\x0d\xfe\x00\x20\x05' \ b'\x06\x33\xcd\x00\x40'), # read file record (0x15, b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03' \ b'\x06\xaf\x04\xbe\x10\x0d'), # write file record (0x16, b'\x16\x00\x01\x00\xff\xff\x00'), # mask write register (0x17, b'\x17\x02\x12\x34'), # read/write multiple registers (0x18, b'\x18\x00\x01\x00\x01\x00\x00'), # read fifo queue (0x2b, b'\x2b\x0e\x01\x01\x00\x00\x01\x00\x01\x77'), # read device identification ) self.exception = ( (0x81, b'\x81\x01\xd0\x50'), # illegal function exception (0x82, b'\x82\x02\x90\xa1'), # illegal data address exception (0x83, b'\x83\x03\x50\xf1'), # illegal data value exception (0x84, b'\x84\x04\x13\x03'), # skave device failure exception (0x85, b'\x85\x05\xd3\x53'), # acknowledge exception (0x86, b'\x86\x06\x93\xa2'), # slave device busy exception (0x87, b'\x87\x08\x53\xf2'), # memory parity exception (0x88, b'\x88\x0a\x16\x06'), # gateway path unavailable exception (0x89, b'\x89\x0b\xd6\x56'), # gateway target failed exception ) self.bad = ( (0x80, b'\x80\x00\x00\x00'), # Unknown Function (0x81, b'\x81\x00\x00\x00'), # error message ) def tearDown(self): ''' Cleans up the test environment ''' del self.bad del self.request del self.response def testExceptionLookup(self): ''' Test that we can look up exception messages ''' for func, _ in self.exception: response = self.client.lookupPduClass(func) self.assertNotEqual(response, None) for func, _ in self.exception: response = self.server.lookupPduClass(func) self.assertNotEqual(response, None) def testResponseLookup(self): ''' Test a working response factory lookup ''' for func, _ in self.response: response = self.client.lookupPduClass(func) self.assertNotEqual(response, None) def testRequestLookup(self): ''' Test a working request factory lookup ''' for func, _ in self.request: request = self.client.lookupPduClass(func) self.assertNotEqual(request, None) def testResponseWorking(self): ''' Test a working response factory decoders ''' for func, msg in self.response: try: self.client.decode(msg) except ModbusException: self.fail("Failed to Decode Response Message", func) def testResponseErrors(self): ''' Test a response factory decoder exceptions ''' self.assertRaises(ModbusException, self.client._helper, self.bad[0][1]) self.assertEqual(self.client.decode(self.bad[1][1]).function_code, self.bad[1][0], "Failed to decode error PDU") def testRequestsWorking(self): ''' Test a working request factory decoders ''' for func, msg in self.request: try: self.server.decode(msg) except ModbusException: self.fail("Failed to Decode Request Message", func) def testClientFactoryFails(self): ''' Tests that a client factory will fail to decode a bad message ''' self.client._helper = _raise_exception actual = self.client.decode(None) self.assertEqual(actual, None) def testServerFactoryFails(self): ''' Tests that a server factory will fail to decode a bad message ''' self.server._helper = _raise_exception actual = self.server.decode(None) self.assertEqual(actual, None) #---------------------------------------------------------------------------# # I don't actually know what is supposed to be returned here, I assume that # since the high bit is set, it will simply echo the resulting message #---------------------------------------------------------------------------# def testRequestErrors(self): ''' Test a request factory decoder exceptions ''' for func, msg in self.bad: result = self.server.decode(msg) self.assertEqual(result.ErrorCode, 1, "Failed to decode invalid requests") self.assertEqual(result.execute(None).function_code, func, "Failed to create correct response message") #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_interfaces.py0000644000175000017500000000525013150360615016230 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.interfaces import * from pymodbus.exceptions import NotImplementedException class _SingleInstance(Singleton): pass class ModbusInterfaceTestsTest(unittest.TestCase): ''' This is the unittest for the pymodbus.interfaces module ''' def setUp(self): ''' Initializes the test environment ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass def testSingletonInterface(self): ''' Test that the singleton interface works ''' first = _SingleInstance() second = _SingleInstance() self.assertEqual(first, second) def testModbusDecoderInterface(self): ''' Test that the base class isn't implemented ''' x = None instance = IModbusDecoder() self.assertRaises(NotImplementedException, lambda: instance.decode(x)) self.assertRaises(NotImplementedException, lambda: instance.lookupPduClass(x)) def testModbusFramerInterface(self): ''' Test that the base class isn't implemented ''' x = None instance = IModbusFramer() self.assertRaises(NotImplementedException, instance.checkFrame) self.assertRaises(NotImplementedException, instance.advanceFrame) self.assertRaises(NotImplementedException, instance.isFrameReady) self.assertRaises(NotImplementedException, instance.getFrame) self.assertRaises(NotImplementedException, lambda: instance.addToFrame(x)) self.assertRaises(NotImplementedException, lambda: instance.populateResult(x)) self.assertRaises(NotImplementedException, lambda: instance.processIncomingPacket(x,x)) self.assertRaises(NotImplementedException, lambda: instance.buildPacket(x)) def testModbusSlaveContextInterface(self): ''' Test that the base class isn't implemented ''' x = None instance = IModbusSlaveContext() self.assertRaises(NotImplementedException, instance.reset) self.assertRaises(NotImplementedException, lambda: instance.validate(x,x,x)) self.assertRaises(NotImplementedException, lambda: instance.getValues(x,x,x)) self.assertRaises(NotImplementedException, lambda: instance.setValues(x,x,x)) def testModbusPayloadBuilderInterface(self): ''' Test that the base class isn't implemented ''' x = None instance = IPayloadBuilder() self.assertRaises(NotImplementedException, lambda: instance.build()) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_other_messages.py0000644000175000017500000000767313150360615017130 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.other_message import * class ModbusOtherMessageTest(unittest.TestCase): ''' This is the unittest for the pymodbus.other_message module ''' def setUp(self): self.requests = [ ReadExceptionStatusRequest, GetCommEventCounterRequest, GetCommEventLogRequest, ReportSlaveIdRequest, ] self.responses = [ lambda: ReadExceptionStatusResponse(0x12), lambda: GetCommEventCounterResponse(0x12), GetCommEventLogResponse, lambda: ReportSlaveIdResponse(0x12), ] def tearDown(self): ''' Cleans up the test environment ''' del self.requests del self.responses def testOtherMessagesToString(self): for message in self.requests: self.assertNotEqual(str(message()), None) for message in self.responses: self.assertNotEqual(str(message()), None) def testReadExceptionStatus(self): request = ReadExceptionStatusRequest() request.decode(b'\x12') self.assertEqual(request.encode(), b'') self.assertEqual(request.execute().function_code, 0x07) response = ReadExceptionStatusResponse(0x12) self.assertEqual(response.encode(), b'\x12') response.decode(b'\x12') self.assertEqual(response.status, 0x12) def testGetCommEventCounter(self): request = GetCommEventCounterRequest() request.decode(b'\x12') self.assertEqual(request.encode(), b'') self.assertEqual(request.execute().function_code, 0x0b) response = GetCommEventCounterResponse(0x12) self.assertEqual(response.encode(), b'\x00\x00\x00\x12') response.decode(b'\x00\x00\x00\x12') self.assertEqual(response.status, True) self.assertEqual(response.count, 0x12) response.status = False self.assertEqual(response.encode(), b'\xFF\xFF\x00\x12') def testGetCommEventLog(self): request = GetCommEventLogRequest() request.decode(b'\x12') self.assertEqual(request.encode(), b'') self.assertEqual(request.execute().function_code, 0x0c) response = GetCommEventLogResponse() self.assertEqual(response.encode(), b'\x06\x00\x00\x00\x00\x00\x00') response.decode(b'\x06\x00\x00\x00\x12\x00\x12') self.assertEqual(response.status, True) self.assertEqual(response.message_count, 0x12) self.assertEqual(response.event_count, 0x12) self.assertEqual(response.events, []) response.status = False self.assertEqual(response.encode(), b'\x06\xff\xff\x00\x12\x00\x12') def testGetCommEventLogWithEvents(self): response = GetCommEventLogResponse(events=[0x12,0x34,0x56]) self.assertEqual(response.encode(), b'\x09\x00\x00\x00\x00\x00\x00\x12\x34\x56') response.decode(b'\x09\x00\x00\x00\x12\x00\x12\x12\x34\x56') self.assertEqual(response.status, True) self.assertEqual(response.message_count, 0x12) self.assertEqual(response.event_count, 0x12) self.assertEqual(response.events, [0x12,0x34,0x56]) def testReportSlaveId(self): request = ReportSlaveIdRequest() request.decode(b'\x12') self.assertEqual(request.encode(), b'') self.assertEqual(request.execute().function_code, 0x11) response = ReportSlaveIdResponse(request.execute().identifier, True) self.assertEqual(response.encode(), b'\tPymodbus\xff') response.decode(b'\x03\x12\x00') self.assertEqual(response.status, False) self.assertEqual(response.identifier, b'\x12\x00') response.status = False self.assertEqual(response.encode(), b'\x03\x12\x00\x00') #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_server_async.py0000644000175000017500000001045113150360615016607 0ustar wmbwmb#!/usr/bin/env python from pymodbus.compat import IS_PYTHON3 import unittest if IS_PYTHON3: # Python 3 from unittest.mock import patch, Mock else: # Python 2 from mock import patch, Mock from pymodbus.device import ModbusDeviceIdentification from pymodbus.server.async import ModbusTcpProtocol, ModbusUdpProtocol from pymodbus.server.async import ModbusServerFactory from pymodbus.server.async import StartTcpServer, StartUdpServer, StartSerialServer import sys #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# SERIAL_PORT = "/dev/ptmx" if sys.platform == "darwin": SERIAL_PORT = "/dev/ptyp0" class AsynchronousServerTest(unittest.TestCase): ''' This is the unittest for the pymodbus.server.async module ''' #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment ''' values = dict((i, '') for i in range(10)) identity = ModbusDeviceIdentification(info=values) def tearDown(self): ''' Cleans up the test environment ''' pass #-----------------------------------------------------------------------# # Test Modbus Server Factory #-----------------------------------------------------------------------# def testModbusServerFactory(self): ''' Test the base class for all the clients ''' factory = ModbusServerFactory(store=None) self.assertEqual(factory.control.Identity.VendorName, '') identity = ModbusDeviceIdentification(info={0x00: 'VendorName'}) factory = ModbusServerFactory(store=None, identity=identity) self.assertEqual(factory.control.Identity.VendorName, 'VendorName') #-----------------------------------------------------------------------# # Test Modbus TCP Server #-----------------------------------------------------------------------# def testTCPServerDisconnect(self): protocol = ModbusTcpProtocol() protocol.connectionLost('because of an error') #-----------------------------------------------------------------------# # Test Modbus UDP Server #-----------------------------------------------------------------------# def testUdpServerInitialize(self): protocol = ModbusUdpProtocol(store=None) self.assertEqual(protocol.control.Identity.VendorName, '') identity = ModbusDeviceIdentification(info={0x00: 'VendorName'}) protocol = ModbusUdpProtocol(store=None, identity=identity) self.assertEqual(protocol.control.Identity.VendorName, 'VendorName') #-----------------------------------------------------------------------# # Test Modbus Server Startups #-----------------------------------------------------------------------# def testTcpServerStartup(self): ''' Test that the modbus tcp async server starts correctly ''' with patch('twisted.internet.reactor') as mock_reactor: if IS_PYTHON3: console = False call_count = 1 else: console = True call_count = 2 StartTcpServer(context=None, console=console) self.assertEqual(mock_reactor.listenTCP.call_count, call_count) self.assertEqual(mock_reactor.run.call_count, 1) def testUdpServerStartup(self): ''' Test that the modbus udp async server starts correctly ''' with patch('twisted.internet.reactor') as mock_reactor: StartUdpServer(context=None) self.assertEqual(mock_reactor.listenUDP.call_count, 1) self.assertEqual(mock_reactor.run.call_count, 1) def testSerialServerStartup(self): ''' Test that the modbus serial async server starts correctly ''' with patch('twisted.internet.reactor') as mock_reactor: StartSerialServer(context=None, port=SERIAL_PORT) self.assertEqual(mock_reactor.run.call_count, 1) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_ptwisted.py0000644000175000017500000000147613150360615015756 0ustar wmbwmb#!/usr/bin/env python import unittest #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class TwistedInternalCodeTest(unittest.TestCase): ''' This is the unittest for the pymodbus.internal.ptwisted code ''' #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def testInstallConch(self): ''' Test that we can install the conch backend ''' pass #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_client_common.py0000644000175000017500000000513213150360615016732 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.client.common import ModbusClientMixin from pymodbus.bit_read_message import * from pymodbus.bit_write_message import * from pymodbus.file_message import * from pymodbus.register_read_message import * from pymodbus.register_write_message import * #---------------------------------------------------------------------------# # Mocks #---------------------------------------------------------------------------# class MockClient(ModbusClientMixin): def execute(self, request): return request #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusCommonClientTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' self.client = MockClient() def tearDown(self): ''' Cleans up the test environment ''' del self.client #-----------------------------------------------------------------------# # Tests #-----------------------------------------------------------------------# def testModbusClientMixinMethods(self): ''' This tests that the mixing returns the correct request object ''' arguments = { 'read_address': 1, 'read_count': 1, 'write_address': 1, 'write_registers': 1 } self.assertTrue(isinstance(self.client.read_coils(1,1), ReadCoilsRequest)) self.assertTrue(isinstance(self.client.read_discrete_inputs(1,1), ReadDiscreteInputsRequest)) self.assertTrue(isinstance(self.client.write_coil(1,True), WriteSingleCoilRequest)) self.assertTrue(isinstance(self.client.write_coils(1,[True]), WriteMultipleCoilsRequest)) self.assertTrue(isinstance(self.client.write_register(1,0x00), WriteSingleRegisterRequest)) self.assertTrue(isinstance(self.client.write_registers(1,[0x00]), WriteMultipleRegistersRequest)) self.assertTrue(isinstance(self.client.read_holding_registers(1,1), ReadHoldingRegistersRequest)) self.assertTrue(isinstance(self.client.read_input_registers(1,1), ReadInputRegistersRequest)) self.assertTrue(isinstance(self.client.readwrite_registers(**arguments), ReadWriteMultipleRegistersRequest)) self.assertTrue(isinstance(self.client.mask_write_register(1,0,0), MaskWriteRegisterRequest)) pymodbus-1.3.2/test/test_utilities.py0000644000175000017500000000573413150360615016127 0ustar wmbwmb#!/usr/bin/env python import unittest import struct from pymodbus.utilities import pack_bitstring, unpack_bitstring from pymodbus.utilities import checkCRC, checkLRC from pymodbus.utilities import dict_property, default _test_master = {4 : 'd'} class DictPropertyTester(object): def __init__(self): self.test = {1 : 'a'} self._test = {2 : 'b'} self.__test = {3 : 'c'} l1 = dict_property(lambda s: s.test, 1) l2 = dict_property(lambda s: s._test, 2) l3 = dict_property(lambda s: s.__test, 3) s1 = dict_property('test', 1) s2 = dict_property('_test', 2) g1 = dict_property(_test_master, 4) class SimpleUtilityTest(unittest.TestCase): ''' This is the unittest for the pymod.utilities module ''' def setUp(self): ''' Initializes the test environment ''' self.data = struct.pack('>HHHH', 0x1234, 0x2345, 0x3456, 0x4567) self.string = b"test the computation" self.bits = [True, False, True, False, True, False, True, False] def tearDown(self): ''' Cleans up the test environment ''' del self.bits del self.string def testDictProperty(self): ''' Test all string <=> bit packing functions ''' d = DictPropertyTester() self.assertEqual(d.l1, 'a') self.assertEqual(d.l2, 'b') self.assertEqual(d.l3, 'c') self.assertEqual(d.s1, 'a') self.assertEqual(d.s2, 'b') self.assertEqual(d.g1, 'd') for store in 'l1 l2 l3 s1 s2 g1'.split(' '): setattr(d, store, 'x') self.assertEqual(d.l1, 'x') self.assertEqual(d.l2, 'x') self.assertEqual(d.l3, 'x') self.assertEqual(d.s1, 'x') self.assertEqual(d.s2, 'x') self.assertEqual(d.g1, 'x') def testDefaultValue(self): ''' Test all string <=> bit packing functions ''' self.assertEqual(default(1), 0) self.assertEqual(default(1.1), 0.0) self.assertEqual(default(1+1j), 0j) self.assertEqual(default('string'), '') self.assertEqual(default([1,2,3]), []) self.assertEqual(default({1:1}), {}) self.assertEqual(default(True), False) def testBitPacking(self): ''' Test all string <=> bit packing functions ''' self.assertEqual(unpack_bitstring(b'\x55'), self.bits) self.assertEqual(pack_bitstring(self.bits), b'\x55') def testLongitudinalRedundancyCheck(self): ''' Test the longitudinal redundancy check code ''' self.assertTrue(checkLRC(self.data, 0x1c)) self.assertTrue(checkLRC(self.string, 0x0c)) def testCyclicRedundancyCheck(self): ''' Test the cyclic redundancy check code ''' self.assertTrue(checkCRC(self.data, 0xe2db)) self.assertTrue(checkCRC(self.string, 0x889e)) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_bit_read_messages.py0000644000175000017500000001156313150360615017551 0ustar wmbwmb#!/usr/bin/env python ''' Bit Message Test Fixture -------------------------------- This fixture tests the functionality of all the bit based request/response messages: * Read/Write Discretes * Read Coils ''' import unittest, struct from pymodbus.bit_read_message import * from pymodbus.bit_read_message import ReadBitsRequestBase from pymodbus.bit_read_message import ReadBitsResponseBase from pymodbus.exceptions import * from pymodbus.pdu import ModbusExceptions from pymodbus.compat import iteritems from .modbus_mocks import MockContext res = [True] * 21 res.extend([False] * 3) #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class ModbusBitMessageTests(unittest.TestCase): #-----------------------------------------------------------------------# # Setup/TearDown #-----------------------------------------------------------------------# def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' pass def tearDown(self): ''' Cleans up the test environment ''' pass def testReadBitBaseClassMethods(self): ''' Test basic bit message encoding/decoding ''' handle = ReadBitsRequestBase(1, 1) msg = "ReadBitRequest(1,1)" self.assertEqual(msg, str(handle)) handle = ReadBitsResponseBase([1,1]) msg = "ReadBitResponse(2)" self.assertEqual(msg, str(handle)) def testBitReadBaseRequestEncoding(self): ''' Test basic bit message encoding/decoding ''' for i in range(20): handle = ReadBitsRequestBase(i, i) result = struct.pack('>HH',i, i) self.assertEqual(handle.encode(), result) handle.decode(result) self.assertEqual((handle.address, handle.count), (i,i)) def testBitReadBaseResponseEncoding(self): ''' Test basic bit message encoding/decoding ''' for i in range(20): input = [True] * i handle = ReadBitsResponseBase(input) result = handle.encode() handle.decode(result) self.assertEqual(handle.bits[:i], input) def testBitReadBaseResponseHelperMethods(self): ''' Test the extra methods on a ReadBitsResponseBase ''' input = [False] * 8 handle = ReadBitsResponseBase(input) for i in [1,3,5]: handle.setBit(i, True) for i in [1,3,5]: handle.resetBit(i) for i in range(8): self.assertEqual(handle.getBit(i), False) def testBitReadBaseRequests(self): ''' Test bit read request encoding ''' messages = { ReadBitsRequestBase(12, 14) : b'\x00\x0c\x00\x0e', ReadBitsResponseBase([1,0,1,1,0]) : b'\x01\x0d' } for request, expected in iteritems(messages): self.assertEqual(request.encode(), expected) def testBitReadMessageExecuteValueErrors(self): ''' Test bit read request encoding ''' context = MockContext() requests = [ ReadCoilsRequest(1,0x800), ReadDiscreteInputsRequest(1,0x800), ] for request in requests: result = request.execute(context) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) def testBitReadMessageExecuteAddressErrors(self): ''' Test bit read request encoding ''' context = MockContext() requests = [ ReadCoilsRequest(1,5), ReadDiscreteInputsRequest(1,5), ] for request in requests: result = request.execute(context) self.assertEqual(ModbusExceptions.IllegalAddress, result.exception_code) def testBitReadMessageExecuteSuccess(self): ''' Test bit read request encoding ''' context = MockContext() context.validate = lambda a,b,c: True requests = [ ReadCoilsRequest(1,5), ReadDiscreteInputsRequest(1,5), ] for request in requests: result = request.execute(context) self.assertEqual(result.bits, [True] * 5) def testBitReadMessageGetResponsePDU(self): requests = { ReadCoilsRequest(1,5): 3, ReadCoilsRequest(1, 8): 3, ReadCoilsRequest(0, 16): 4, ReadDiscreteInputsRequest(1, 21): 5, ReadDiscreteInputsRequest(1, 24): 5, ReadDiscreteInputsRequest(1, 1900): 240, } for request, expected in iteritems(requests): pdu_len = request.get_response_pdu_size() self.assertEqual(pdu_len, expected) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_register_write_messages.py0000644000175000017500000001570313150360615021036 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.register_write_message import * from pymodbus.exceptions import ParameterException from pymodbus.pdu import ModbusExceptions from pymodbus.compat import iteritems, iterkeys from pymodbus.payload import BinaryPayloadBuilder from pymodbus.payload import Endian from .modbus_mocks import MockContext #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# class WriteRegisterMessagesTest(unittest.TestCase): ''' Register Message Test Fixture -------------------------------- This fixture tests the functionality of all the register based request/response messages: * Read/Write Input Registers * Read Holding Registers ''' def setUp(self): ''' Initializes the test environment and builds request/result encoding pairs ''' self.value = 0xabcd self.values = [0xa, 0xb, 0xc] builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_16bit_uint(0x1234) self.payload = builder.build() self.write = { WriteSingleRegisterRequest(1, self.value) : b'\x00\x01\xab\xcd', WriteSingleRegisterResponse(1, self.value) : b'\x00\x01\xab\xcd', WriteMultipleRegistersRequest(1, self.values) : b'\x00\x01\x00\x03\x06\x00\n\x00\x0b\x00\x0c', WriteMultipleRegistersResponse(1, 5) : b'\x00\x01\x00\x05', WriteSingleRegisterRequest(1, self.payload[0], skip_encode=True): b'\x00\x01\x12\x34', WriteMultipleRegistersRequest(1, self.payload, skip_encode=True): b'\x00\x01\x00\x01\x02\x12\x34', } def tearDown(self): ''' Cleans up the test environment ''' del self.write def testRegisterWriteRequestsEncode(self): for request, response in iteritems(self.write): self.assertEqual(request.encode(), response) def testRegisterWriteRequestsDecode(self): addresses = [1,1,1,1] values = sorted(self.write.items(), key=lambda x: str(x)) for packet, address in zip(values, addresses): request, response = packet request.decode(response) self.assertEqual(request.address, address) def testInvalidWriteMultipleRegistersRequest(self): request = WriteMultipleRegistersRequest(0, None) self.assertEqual(request.values, []) def testSerializingToString(self): for request in iterkeys(self.write): self.assertTrue(str(request) != None) def testWriteSingleRegisterRequest(self): context = MockContext() request = WriteSingleRegisterRequest(0x00, 0xf0000) result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue) request.value = 0x00ff result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress) context.valid = True result = request.execute(context) self.assertEqual(result.function_code, request.function_code) def testWriteMultipleRegisterRequest(self): context = MockContext() request = WriteMultipleRegistersRequest(0x00, [0x00]*10) result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress) request.count = 0x05 # bytecode != code * 2 result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue) request.count = 0x800 # outside of range result = request.execute(context) self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue) context.valid = True request = WriteMultipleRegistersRequest(0x00, [0x00]*10) result = request.execute(context) self.assertEqual(result.function_code, request.function_code) # -----------------------------------------------------------------------# # Mask Write Register Request # -----------------------------------------------------------------------# def testMaskWriteRegisterRequestEncode(self): ''' Test basic bit message encoding/decoding ''' handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010) result = handle.encode() self.assertEqual(result, b'\x00\x00\x01\x01\x10\x10') def testMaskWriteRegisterRequestDecode(self): ''' Test basic bit message encoding/decoding ''' request = b'\x00\x04\x00\xf2\x00\x25' handle = MaskWriteRegisterRequest() handle.decode(request) self.assertEqual(handle.address, 0x0004) self.assertEqual(handle.and_mask, 0x00f2) self.assertEqual(handle.or_mask, 0x0025) def testMaskWriteRegisterRequestExecute(self): ''' Test write register request valid execution ''' context = MockContext(valid=True, default=0x0000) handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010) result = handle.execute(context) self.assertTrue(isinstance(result, MaskWriteRegisterResponse)) def testMaskWriteRegisterRequestInvalidExecute(self): ''' Test write register request execute with invalid data ''' context = MockContext(valid=False, default=0x0000) handle = MaskWriteRegisterRequest(0x0000, -1, 0x1010) result = handle.execute(context) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) handle = MaskWriteRegisterRequest(0x0000, 0x0101, -1) result = handle.execute(context) self.assertEqual(ModbusExceptions.IllegalValue, result.exception_code) handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010) result = handle.execute(context) self.assertEqual(ModbusExceptions.IllegalAddress, result.exception_code) # -----------------------------------------------------------------------# # Mask Write Register Response # -----------------------------------------------------------------------# def testMaskWriteRegisterResponseEncode(self): ''' Test basic bit message encoding/decoding ''' handle = MaskWriteRegisterResponse(0x0000, 0x0101, 0x1010) result = handle.encode() self.assertEqual(result, b'\x00\x00\x01\x01\x10\x10') def testMaskWriteRegisterResponseDecode(self): ''' Test basic bit message encoding/decoding ''' request = b'\x00\x04\x00\xf2\x00\x25' handle = MaskWriteRegisterResponse() handle.decode(request) self.assertEqual(handle.address, 0x0004) self.assertEqual(handle.and_mask, 0x00f2) self.assertEqual(handle.or_mask, 0x0025) #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/test/test_exceptions.py0000644000175000017500000000225313150360615016266 0ustar wmbwmb#!/usr/bin/env python import unittest from pymodbus.exceptions import * class SimpleExceptionsTest(unittest.TestCase): ''' This is the unittest for the pymodbus.exceptions module ''' def setUp(self): ''' Initializes the test environment ''' self.exceptions = [ ModbusException("bad base"), ModbusIOException("bad register"), ParameterException("bad paramater"), NotImplementedException("bad function"), ConnectionException("bad connection"), ] def tearDown(self): ''' Cleans up the test environment ''' pass def testExceptions(self): ''' Test all module exceptions ''' for ex in self.exceptions: try: raise ex except ModbusException as ex: self.assertTrue("Modbus Error:" in str(ex)) pass else: self.fail("Excepted a ModbusExceptions") #---------------------------------------------------------------------------# # Main #---------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() pymodbus-1.3.2/.project0000644000175000017500000000055213150360615013164 0ustar wmbwmb pymodbus org.python.pydev.PyDevBuilder org.python.pydev.pythonNature pymodbus-1.3.2/CHANGELOG.rst0000644000175000017500000000634113150360615013540 0ustar wmbwmb Version 1.3.2 ------------------------------------------------------------ * ModbusSerialServer could now be stopped when running on a seperate thread. * Fix issue with server and client where in the frame buffer had values from previous unsuccesful transaction * Fix response length calculation for ModbusASCII protocol * Fix response length calculation ReportSlaveIdResponse, DiagnosticStatusResponse * Fix never ending transaction case when response is recieved without header and CRC * Fix tests Version 1.3.1 ------------------------------------------------------------ * Recall socket recv until get a complete response * Register_write_message.py: Observe skip_encode option when encoding a single register request * Fix wrong expected response length for coils and discrete inputs * Fix decode errors with ReadDeviceInformationRequest and ReportSlaveIdRequest on Python3 * Move MaskWriteRegisterRequest/MaskWriteRegisterResponse to register_write_message.py from file_message.py * Python3 compatible examples [WIP] * Misc updates with examples Version 1.3.0.rc2 ------------------------------------------------------------ * Fix encoding problem for ReadDeviceInformationRequest method on python3 * Fix problem with the usage of ord in python3 while cleaning up receive buffer * Fix struct unpack errors with BinaryPayloadDecoder on python3 - string vs bytestring error * Calculate expected response size for ReadWriteMultipleRegistersRequest * Enhancement for ModbusTcpClient, ModbusTcpClient can now accept connection timeout as one of the parameter * Misc updates Version 1.3.0.rc1 ------------------------------------------------------------ * Timing improvements over MODBUS Serial interface * Modbus RTU use 3.5 char silence before and after transactions * Bug fix on FifoTransactionManager , flush stray data before transaction * Update repository information * Added ability to ignore missing slaves * Added ability to revert to ZeroMode * Passed a number of extra options through the stack * Fixed documenation and added a number of examples Version 1.2.0 ------------------------------------------------------------ * Reworking the transaction managers to be more explicit and to handle modbus RTU over TCP. * Adding examples for a number of unique requested use cases * Allow RTU framers to fail fast instead of staying at fault * Working on datastore saving and loading Version 1.1.0 ------------------------------------------------------------ * Fixing memory leak in clients and servers (removed __del__) * Adding the ability to override the client framers * Working on web page api and GUI * Moving examples and extra code to contrib sections * Adding more documentation Version 1.0.0 ------------------------------------------------------------ * Adding support for payload builders to form complex encoding and decoding of messages. * Adding BCD and binary payload builders * Adding support for pydev * Cleaning up the build tools * Adding a message encoding generator for testing. * Now passing kwargs to base of PDU so arguments can be used correctly at all levels of the protocol. * A number of bug fixes (see bug tracker and commit messages) Version 0.9.0 ------------------------------------------------------------ Please view the git commit log pymodbus-1.3.2/setup.cfg0000644000175000017500000000073013150360615013334 0ustar wmbwmb[aliases] upload_docs = build_sphinx upload_docs package = build_apidocs build_sphinx sdist [egg_info] #tag_build = dev tag_svn_revision = false [nosetests] verbosity=0 detailed-errors=1 with-coverage=1 cover-html=1 cover-html-dir=build/coverage/ cover-package=pymodbus #debug=nose.loader #pdb=1 #pdb-failures=1 [build-sphinx] source-dir = doc/sphinx/ build-dir = doc/sphinx/build all_files = 1 [upload_docs] upload-dir = build/sphinx/html [bdist_wheel] universal=1pymodbus-1.3.2/setup.py0000644000175000017500000000531213150360615013226 0ustar wmbwmb#!/usr/bin/env python ''' Installs pymodbus using distutils Run: python setup.py install to install the package from the source archive. For information about setuptools http://peak.telecommunity.com/DevCenter/setuptools#new-and-changed-setup-keywords ''' #---------------------------------------------------------------------------# # initialization #---------------------------------------------------------------------------# try: # if not installed, install and proceed from setuptools import setup, find_packages except ImportError: from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages try: from setup_commands import command_classes except ImportError: command_classes = {} from pymodbus import __version__, __author__, __maintainer__ #---------------------------------------------------------------------------# # configuration #---------------------------------------------------------------------------# setup(name = 'pymodbus', version = __version__, description = 'A fully featured modbus protocol stack in python', long_description=''' Pymodbus aims to be a fully implemented modbus protocol stack implemented using twisted. Its orignal goal was to allow simulation of thousands of modbus devices on a single machine for monitoring software testing. ''', classifiers = [ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Environment :: X11 Applications :: GTK', 'Framework :: Twisted', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', 'Programming Language :: Python', 'Topic :: System :: Networking', 'Topic :: Utilities' ], keywords = 'modbus, twisted, scada', author = __author__, author_email = 'bashwork@gmail.com', maintainer = __maintainer__, maintainer_email = 'otlasanju@gmail.com', url='https://github.com/riptideio/pymodbus/', license = 'BSD', packages = find_packages(exclude=['examples', 'test']), exclude_package_data = {'' : ['examples', 'test', 'tools', 'doc']}, py_modules = ['ez_setup'], platforms = ['Linux', 'Mac OS X', 'Win'], include_package_data = True, zip_safe = True, install_requires = [ 'twisted >= 12.2.0', 'pyserial >= 2.6' ], extras_require = { 'quality' : [ 'coverage >= 3.5.3', 'nose >= 1.2.1', 'mock >= 1.0.0', 'pep8 >= 1.3.3' ], 'documents' : [ 'sphinx >= 1.1.3' ], 'twisted' : [ 'pyasn1 >= 0.1.4', 'pycrypto >= 2.6' ], }, test_suite = 'nose.collector', cmdclass = command_classes, ) pymodbus-1.3.2/pymodbus/0000755000175000017500000000000013150360615013355 5ustar wmbwmbpymodbus-1.3.2/pymodbus/mei_message.py0000644000175000017500000001360513150360615016212 0ustar wmbwmb''' Encapsulated Interface (MEI) Transport Messages ----------------------------------------------- ''' import struct from pymodbus.constants import DeviceInformation, MoreData from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.device import ModbusControlBlock from pymodbus.device import DeviceInformationFactory from pymodbus.pdu import ModbusExceptions as merror from pymodbus.compat import iteritems, byte2int, IS_PYTHON3 _MCB = ModbusControlBlock() #---------------------------------------------------------------------------# # Read Device Information #---------------------------------------------------------------------------# class ReadDeviceInformationRequest(ModbusRequest): ''' This function code allows reading the identification and additional information relative to the physical and functional description of a remote device, only. The Read Device Identification interface is modeled as an address space composed of a set of addressable data elements. The data elements are called objects and an object Id identifies them. ''' function_code = 0x2b sub_function_code = 0x0e _rtu_frame_size = 7 def __init__(self, read_code=None, object_id=0x00, **kwargs): ''' Initializes a new instance :param read_code: The device information read code :param object_id: The object to read from ''' ModbusRequest.__init__(self, **kwargs) self.read_code = read_code or DeviceInformation.Basic self.object_id = object_id def encode(self): ''' Encodes the request packet :returns: The byte encoded packet ''' packet = struct.pack('>BBB', self.sub_function_code, self.read_code, self.object_id) return packet def decode(self, data): ''' Decodes data part of the message. :param data: The incoming data ''' params = struct.unpack('>BBB', data) self.sub_function_code, self.read_code, self.object_id = params def execute(self, context): ''' Run a read exeception status request against the store :param context: The datastore to request from :returns: The populated response ''' if not (0x00 <= self.object_id <= 0xff): return self.doException(merror.IllegalValue) if not (0x00 <= self.read_code <= 0x04): return self.doException(merror.IllegalValue) information = DeviceInformationFactory.get(_MCB, self.read_code, self.object_id) return ReadDeviceInformationResponse(self.read_code, information) def __str__(self): ''' Builds a representation of the request :returns: The string representation of the request ''' params = (self.read_code, self.object_id) return "ReadDeviceInformationRequest(%d,%d)" % params class ReadDeviceInformationResponse(ModbusResponse): ''' ''' function_code = 0x2b sub_function_code = 0x0e @classmethod def calculateRtuFrameSize(cls, buffer): ''' Calculates the size of the message :param buffer: A buffer containing the data that have been received. :returns: The number of bytes in the response. ''' size = 8 # skip the header information count = byte2int(buffer[7]) while count > 0: _, object_length = struct.unpack('>BB', buffer[size:size+2]) size += object_length + 2 count -= 1 return size + 2 def __init__(self, read_code=None, information=None, **kwargs): ''' Initializes a new instance :param read_code: The device information read code :param information: The requested information request ''' ModbusResponse.__init__(self, **kwargs) self.read_code = read_code or DeviceInformation.Basic self.information = information or {} self.number_of_objects = len(self.information) self.conformity = 0x83 # I support everything right now # TODO calculate self.next_object_id = 0x00 # self.information[-1](0) self.more_follows = MoreData.Nothing def encode(self): ''' Encodes the response :returns: The byte encoded message ''' packet = struct.pack('>BBBBBB', self.sub_function_code, self.read_code, self.conformity, self.more_follows, self.next_object_id, self.number_of_objects) for (object_id, data) in iteritems(self.information): packet += struct.pack('>BB', object_id, len(data)) if IS_PYTHON3: if isinstance(data, bytes): packet += data else: packet += data.encode() else: packet += data.encode() return packet def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' params = struct.unpack('>BBBBBB', data[0:6]) self.sub_function_code, self.read_code = params[0:2] self.conformity, self.more_follows = params[2:4] self.next_object_id, self.number_of_objects = params[4:6] self.information, count = {}, 6 # skip the header information while count < len(data): object_id, object_length = struct.unpack('>BB', data[count:count+2]) count += object_length + 2 self.information[object_id] = data[count-object_length:count] def __str__(self): ''' Builds a representation of the response :returns: The string representation of the response ''' return "ReadDeviceInformationResponse(%d)" % self.read_code #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "ReadDeviceInformationRequest", "ReadDeviceInformationResponse", ] pymodbus-1.3.2/pymodbus/pdu.py0000644000175000017500000001732513150360615014527 0ustar wmbwmb''' Contains base classes for modbus request/response/error packets ''' from pymodbus.interfaces import Singleton from pymodbus.exceptions import NotImplementedException from pymodbus.constants import Defaults from pymodbus.utilities import rtuFrameSize from pymodbus.compat import iteritems, int2byte, byte2int #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# import logging _logger = logging.getLogger(__name__) #---------------------------------------------------------------------------# # Base PDU's #---------------------------------------------------------------------------# class ModbusPDU(object): ''' Base class for all Modbus mesages .. attribute:: transaction_id This value is used to uniquely identify a request response pair. It can be implemented as a simple counter .. attribute:: protocol_id This is a constant set at 0 to indicate Modbus. It is put here for ease of expansion. .. attribute:: unit_id This is used to route the request to the correct child. In the TCP modbus, it is used for routing (or not used at all. However, for the serial versions, it is used to specify which child to perform the requests against. The value 0x00 represents the broadcast address (also 0xff). .. attribute:: check This is used for LRC/CRC in the serial modbus protocols .. attribute:: skip_encode This is used when the message payload has already been encoded. Generally this will occur when the PayloadBuilder is being used to create a complicated message. By setting this to True, the request will pass the currently encoded message through instead of encoding it again. ''' def __init__(self, **kwargs): ''' Initializes the base data for a modbus request ''' self.transaction_id = kwargs.get('transaction', Defaults.TransactionId) self.protocol_id = kwargs.get('protocol', Defaults.ProtocolId) self.unit_id = kwargs.get('unit', Defaults.UnitId) self.skip_encode = kwargs.get('skip_encode', False) self.check = 0x0000 def encode(self): ''' Encodes the message :raises: A not implemented exception ''' raise NotImplementedException() def decode(self, data): ''' Decodes data part of the message. :param data: is a string object :raises: A not implemented exception ''' raise NotImplementedException() @classmethod def calculateRtuFrameSize(cls, buffer): ''' Calculates the size of a PDU. :param buffer: A buffer containing the data that have been received. :returns: The number of bytes in the PDU. ''' if hasattr(cls, '_rtu_frame_size'): return cls._rtu_frame_size elif hasattr(cls, '_rtu_byte_count_pos'): return rtuFrameSize(buffer, cls._rtu_byte_count_pos) else: raise NotImplementedException( "Cannot determine RTU frame size for %s" % cls.__name__) class ModbusRequest(ModbusPDU): ''' Base class for a modbus request PDU ''' def __init__(self, **kwargs): ''' Proxy to the lower level initializer ''' ModbusPDU.__init__(self, **kwargs) def doException(self, exception): ''' Builds an error response based on the function :param exception: The exception to return :raises: An exception response ''' _logger.error("Exception Response F(%d) E(%d)" % (self.function_code, exception)) return ExceptionResponse(self.function_code, exception) class ModbusResponse(ModbusPDU): ''' Base class for a modbus response PDU .. attribute:: should_respond A flag that indicates if this response returns a result back to the client issuing the request .. attribute:: _rtu_frame_size Indicates the size of the modbus rtu response used for calculating how much to read. ''' should_respond = True def __init__(self, **kwargs): ''' Proxy to the lower level initializer ''' ModbusPDU.__init__(self, **kwargs) #---------------------------------------------------------------------------# # Exception PDU's #---------------------------------------------------------------------------# class ModbusExceptions(Singleton): ''' An enumeration of the valid modbus exceptions ''' IllegalFunction = 0x01 IllegalAddress = 0x02 IllegalValue = 0x03 SlaveFailure = 0x04 Acknowledge = 0x05 SlaveBusy = 0x06 MemoryParityError = 0x08 GatewayPathUnavailable = 0x0A GatewayNoResponse = 0x0B @classmethod def decode(cls, code): ''' Given an error code, translate it to a string error name. :param code: The code number to translate ''' values = dict((v, k) for k, v in iteritems(cls.__dict__) if not k.startswith('__') and not callable(v)) return values.get(code, None) class ExceptionResponse(ModbusResponse): ''' Base class for a modbus exception PDU ''' ExceptionOffset = 0x80 _rtu_frame_size = 5 def __init__(self, function_code, exception_code=None, **kwargs): ''' Initializes the modbus exception response :param function_code: The function to build an exception response for :param exception_code: The specific modbus exception to return ''' ModbusResponse.__init__(self, **kwargs) self.original_code = function_code self.function_code = function_code | self.ExceptionOffset self.exception_code = exception_code def encode(self): ''' Encodes a modbus exception response :returns: The encoded exception packet ''' return int2byte(self.exception_code) def decode(self, data): ''' Decodes a modbus exception response :param data: The packet data to decode ''' self.exception_code = byte2int(data[0]) def __str__(self): ''' Builds a representation of an exception response :returns: The string representation of an exception response ''' message = ModbusExceptions.decode(self.exception_code) parameters = (self.function_code, self.original_code, message) return "Exception Response(%d, %d, %s)" % parameters class IllegalFunctionRequest(ModbusRequest): ''' Defines the Modbus slave exception type 'Illegal Function' This exception code is returned if the slave:: - does not implement the function code **or** - is not in a state that allows it to process the function ''' ErrorCode = 1 def __init__(self, function_code, **kwargs): ''' Initializes a IllegalFunctionRequest :param function_code: The function we are erroring on ''' ModbusRequest.__init__(self, **kwargs) self.function_code = function_code def decode(self, data): ''' This is here so this failure will run correctly :param data: Not used ''' pass def execute(self, context): ''' Builds an illegal function request error response :param context: The current context for the message :returns: The error response packet ''' return ExceptionResponse(self.function_code, self.ErrorCode) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ 'ModbusRequest', 'ModbusResponse', 'ModbusExceptions', 'ExceptionResponse', 'IllegalFunctionRequest', ] pymodbus-1.3.2/pymodbus/file_message.py0000644000175000017500000003363513150360615016364 0ustar wmbwmb''' File Record Read/Write Messages ------------------------------- Currently none of these messages are implemented ''' import struct from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.pdu import ModbusExceptions as merror from pymodbus.compat import byte2int #---------------------------------------------------------------------------# # File Record Types #---------------------------------------------------------------------------# class FileRecord(object): ''' Represents a file record and its relevant data. ''' def __init__(self, **kwargs): ''' Initializes a new instance :params reference_type: Defaults to 0x06 (must be) :params file_number: Indicates which file number we are reading :params record_number: Indicates which record in the file :params record_data: The actual data of the record :params record_length: The length in registers of the record :params response_length: The length in bytes of the record ''' self.reference_type = kwargs.get('reference_type', 0x06) self.file_number = kwargs.get('file_number', 0x00) self.record_number = kwargs.get('record_number', 0x00) self.record_data = kwargs.get('record_data', '') self.record_length = kwargs.get('record_length', len(self.record_data) // 2) self.response_length = kwargs.get('response_length', len(self.record_data) + 1) def __eq__(self, relf): ''' Compares the left object to the right ''' return self.reference_type == relf.reference_type \ and self.file_number == relf.file_number \ and self.record_number == relf.record_number \ and self.record_length == relf.record_length \ and self.record_data == relf.record_data def __ne__(self, relf): ''' Compares the left object to the right ''' return not self.__eq__(relf) def __repr__(self): ''' Gives a representation of the file record ''' params = (self.file_number, self.record_number, self.record_length) return 'FileRecord(file=%d, record=%d, length=%d)' % params #---------------------------------------------------------------------------# # File Requests/Responses #---------------------------------------------------------------------------# class ReadFileRecordRequest(ModbusRequest): ''' This function code is used to perform a file record read. All request data lengths are provided in terms of number of bytes and all record lengths are provided in terms of registers. A file is an organization of records. Each file contains 10000 records, addressed 0000 to 9999 decimal or 0x0000 to 0x270f. For example, record 12 is addressed as 12. The function can read multiple groups of references. The groups can be separating (non-contiguous), but the references within each group must be sequential. Each group is defined in a seperate 'sub-request' field that contains seven bytes:: The reference type: 1 byte (must be 0x06) The file number: 2 bytes The starting record number within the file: 2 bytes The length of the record to be read: 2 bytes The quantity of registers to be read, combined with all other fields in the expected response, must not exceed the allowable length of the MODBUS PDU: 235 bytes. ''' function_code = 0x14 _rtu_byte_count_pos = 2 def __init__(self, records=None, **kwargs): ''' Initializes a new instance :param records: The file record requests to be read ''' ModbusRequest.__init__(self, **kwargs) self.records = records or [] def encode(self): ''' Encodes the request packet :returns: The byte encoded packet ''' packet = struct.pack('B', len(self.records) * 7) for record in self.records: packet += struct.pack('>BHHH', 0x06, record.file_number, record.record_number, record.record_length) return packet def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' self.records = [] byte_count = byte2int(data[0]) for count in range(1, byte_count, 7): decoded = struct.unpack('>BHHH', data[count:count+7]) record = FileRecord(file_number=decoded[1], record_number=decoded[2], record_length=decoded[3]) if decoded[0] == 0x06: self.records.append(record) def execute(self, context): ''' Run a read exeception status request against the store :param context: The datastore to request from :returns: The populated response ''' # TODO do some new context operation here # if file number, record number, or address + length # is too big, return an error. files = [] return ReadFileRecordResponse(files) class ReadFileRecordResponse(ModbusResponse): ''' The normal response is a series of 'sub-responses,' one for each 'sub-request.' The byte count field is the total combined count of bytes in all 'sub-responses.' In addition, each 'sub-response' contains a field that shows its own byte count. ''' function_code = 0x14 _rtu_byte_count_pos = 2 def __init__(self, records=None, **kwargs): ''' Initializes a new instance :param records: The requested file records ''' ModbusResponse.__init__(self, **kwargs) self.records = records or [] def encode(self): ''' Encodes the response :returns: The byte encoded message ''' total = sum(record.response_length + 1 for record in self.records) packet = struct.pack('B', total) for record in self.records: packet += struct.pack('>BB', 0x06, record.record_length) packet += record.record_data return packet def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' count, self.records = 1, [] byte_count = byte2int(data[0]) while count < byte_count: response_length, reference_type = struct.unpack('>BB', data[count:count+2]) count += response_length + 1 # the count is not included record = FileRecord(response_length=response_length, record_data=data[count - response_length + 1:count]) if reference_type == 0x06: self.records.append(record) class WriteFileRecordRequest(ModbusRequest): ''' This function code is used to perform a file record write. All request data lengths are provided in terms of number of bytes and all record lengths are provided in terms of the number of 16 bit words. ''' function_code = 0x15 _rtu_byte_count_pos = 2 def __init__(self, records=None, **kwargs): ''' Initializes a new instance :param records: The file record requests to be read ''' ModbusRequest.__init__(self, **kwargs) self.records = records or [] def encode(self): ''' Encodes the request packet :returns: The byte encoded packet ''' total_length = sum((record.record_length * 2) + 7 for record in self.records) packet = struct.pack('B', total_length) for record in self.records: packet += struct.pack('>BHHH', 0x06, record.file_number, record.record_number, record.record_length) packet += record.record_data return packet def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' count, self.records = 1, [] byte_count = byte2int(data[0]) while count < byte_count: decoded = struct.unpack('>BHHH', data[count:count+7]) response_length = decoded[3] * 2 count += response_length + 7 record = FileRecord(record_length=decoded[3], file_number=decoded[1], record_number=decoded[2], record_data=data[count - response_length:count]) if decoded[0] == 0x06: self.records.append(record) def execute(self, context): ''' Run the write file record request against the context :param context: The datastore to request from :returns: The populated response ''' # TODO do some new context operation here # if file number, record number, or address + length # is too big, return an error. return WriteFileRecordResponse(self.records) class WriteFileRecordResponse(ModbusResponse): ''' The normal response is an echo of the request. ''' function_code = 0x15 _rtu_byte_count_pos = 2 def __init__(self, records=None, **kwargs): ''' Initializes a new instance :param records: The file record requests to be read ''' ModbusResponse.__init__(self, **kwargs) self.records = records or [] def encode(self): ''' Encodes the response :returns: The byte encoded message ''' total_length = sum((record.record_length * 2) + 7 for record in self.records) packet = struct.pack('B', total_length) for record in self.records: packet += struct.pack('>BHHH', 0x06, record.file_number, record.record_number, record.record_length) packet += record.record_data return packet def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' count, self.records = 1, [] byte_count = byte2int(data[0]) while count < byte_count: decoded = struct.unpack('>BHHH', data[count:count+7]) response_length = decoded[3] * 2 count += response_length + 7 record = FileRecord(record_length=decoded[3], file_number=decoded[1], record_number=decoded[2], record_data=data[count - response_length:count]) if decoded[0] == 0x06: self.records.append(record) class ReadFifoQueueRequest(ModbusRequest): ''' This function code allows to read the contents of a First-In-First-Out (FIFO) queue of register in a remote device. The function returns a count of the registers in the queue, followed by the queued data. Up to 32 registers can be read: the count, plus up to 31 queued data registers. The queue count register is returned first, followed by the queued data registers. The function reads the queue contents, but does not clear them. ''' function_code = 0x18 _rtu_frame_size = 6 def __init__(self, address=0x0000, **kwargs): ''' Initializes a new instance :param address: The fifo pointer address (0x0000 to 0xffff) ''' ModbusRequest.__init__(self, **kwargs) self.address = address self.values = [] # this should be added to the context def encode(self): ''' Encodes the request packet :returns: The byte encoded packet ''' return struct.pack('>H', self.address) def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' self.address = struct.unpack('>H', data)[0] def execute(self, context): ''' Run a read exeception status request against the store :param context: The datastore to request from :returns: The populated response ''' if not (0x0000 <= self.address <= 0xffff): return self.doException(merror.IllegalValue) if len(self.values) > 31: return self.doException(merror.IllegalValue) # TODO pull the values from some context return ReadFifoQueueResponse(self.values) class ReadFifoQueueResponse(ModbusResponse): ''' In a normal response, the byte count shows the quantity of bytes to follow, including the queue count bytes and value register bytes (but not including the error check field). The queue count is the quantity of data registers in the queue (not including the count register). If the queue count exceeds 31, an exception response is returned with an error code of 03 (Illegal Data Value). ''' function_code = 0x18 @classmethod def calculateRtuFrameSize(cls, buffer): ''' Calculates the size of the message :param buffer: A buffer containing the data that have been received. :returns: The number of bytes in the response. ''' hi_byte = byte2int(buffer[2]) lo_byte = byte2int(buffer[3]) return (hi_byte << 16) + lo_byte + 6 def __init__(self, values=None, **kwargs): ''' Initializes a new instance :param values: The list of values of the fifo to return ''' ModbusResponse.__init__(self, **kwargs) self.values = values or [] def encode(self): ''' Encodes the response :returns: The byte encoded message ''' length = len(self.values) * 2 packet = struct.pack('>HH', 2 + length, length) for value in self.values: packet += struct.pack('>H', value) return packet def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' self.values = [] _, count = struct.unpack('>HH', data[0:4]) for index in range(0, count - 4): idx = 4 + index * 2 self.values.append(struct.unpack('>H', data[idx:idx + 2])[0]) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "FileRecord", "ReadFileRecordRequest", "ReadFileRecordResponse", "WriteFileRecordRequest", "WriteFileRecordResponse", "ReadFifoQueueRequest", "ReadFifoQueueResponse", ] pymodbus-1.3.2/pymodbus/bit_write_message.py0000644000175000017500000002146713150360615017435 0ustar wmbwmb""" Bit Writing Request/Response ------------------------------ TODO write mask request/response """ import struct from pymodbus.constants import ModbusStatus from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.pdu import ModbusExceptions as merror from pymodbus.utilities import pack_bitstring, unpack_bitstring #---------------------------------------------------------------------------# # Local Constants #---------------------------------------------------------------------------# # These are defined in the spec to turn a coil on/off #---------------------------------------------------------------------------# _turn_coil_on = struct.pack(">H", ModbusStatus.On) _turn_coil_off = struct.pack(">H", ModbusStatus.Off) class WriteSingleCoilRequest(ModbusRequest): ''' This function code is used to write a single output to either ON or OFF in a remote device. The requested ON/OFF state is specified by a constant in the request data field. A value of FF 00 hex requests the output to be ON. A value of 00 00 requests it to be OFF. All other values are illegal and will not affect the output. The Request PDU specifies the address of the coil to be forced. Coils are addressed starting at zero. Therefore coil numbered 1 is addressed as 0. The requested ON/OFF state is specified by a constant in the Coil Value field. A value of 0XFF00 requests the coil to be ON. A value of 0X0000 requests the coil to be off. All other values are illegal and will not affect the coil. ''' function_code = 5 _rtu_frame_size = 8 def __init__(self, address=None, value=None, **kwargs): ''' Initializes a new instance :param address: The variable address to write :param value: The value to write at address ''' ModbusRequest.__init__(self, **kwargs) self.address = address self.value = bool(value) def encode(self): ''' Encodes write coil request :returns: The byte encoded message ''' result = struct.pack('>H', self.address) if self.value: result += _turn_coil_on else: result += _turn_coil_off return result def decode(self, data): ''' Decodes a write coil request :param data: The packet data to decode ''' self.address, value = struct.unpack('>HH', data) self.value = (value == ModbusStatus.On) def execute(self, context): ''' Run a write coil request against a datastore :param context: The datastore to request from :returns: The populated response or exception message ''' #if self.value not in [ModbusStatus.Off, ModbusStatus.On]: # return self.doException(merror.IllegalValue) if not context.validate(self.function_code, self.address, 1): return self.doException(merror.IllegalAddress) context.setValues(self.function_code, self.address, [self.value]) values = context.getValues(self.function_code, self.address, 1) return WriteSingleCoilResponse(self.address, values[0]) def get_response_pdu_size(self): """ Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) :return: """ return 1 + 2 + 2 def __str__(self): ''' Returns a string representation of the instance :return: A string representation of the instance ''' return "WriteCoilRequest(%d, %s) => " % (self.address, self.value) class WriteSingleCoilResponse(ModbusResponse): ''' The normal response is an echo of the request, returned after the coil state has been written. ''' function_code = 5 _rtu_frame_size = 8 def __init__(self, address=None, value=None, **kwargs): ''' Initializes a new instance :param address: The variable address written to :param value: The value written at address ''' ModbusResponse.__init__(self, **kwargs) self.address = address self.value = value def encode(self): ''' Encodes write coil response :return: The byte encoded message ''' result = struct.pack('>H', self.address) if self.value: result += _turn_coil_on else: result += _turn_coil_off return result def decode(self, data): ''' Decodes a write coil response :param data: The packet data to decode ''' self.address, value = struct.unpack('>HH', data) self.value = (value == ModbusStatus.On) def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "WriteCoilResponse(%d) => %d" % (self.address, self.value) class WriteMultipleCoilsRequest(ModbusRequest): ''' "This function code is used to force each coil in a sequence of coils to either ON or OFF in a remote device. The Request PDU specifies the coil references to be forced. Coils are addressed starting at zero. Therefore coil numbered 1 is addressed as 0. The requested ON/OFF states are specified by contents of the request data field. A logical '1' in a bit position of the field requests the corresponding output to be ON. A logical '0' requests it to be OFF." ''' function_code = 15 _rtu_byte_count_pos = 6 def __init__(self, address=None, values=None, **kwargs): ''' Initializes a new instance :param address: The starting request address :param values: The values to write ''' ModbusRequest.__init__(self, **kwargs) self.address = address if not values: values = [] elif not hasattr(values, '__iter__'): values = [values] self.values = values self.byte_count = (len(self.values) + 7) // 8 def encode(self): ''' Encodes write coils request :returns: The byte encoded message ''' count = len(self.values) self.byte_count = (count + 7) // 8 packet = struct.pack('>HHB', self.address, count, self.byte_count) packet += pack_bitstring(self.values) return packet def decode(self, data): ''' Decodes a write coils request :param data: The packet data to decode ''' self.address, count, self.byte_count = struct.unpack('>HHB', data[0:5]) values = unpack_bitstring(data[5:]) self.values = values[:count] def execute(self, context): ''' Run a write coils request against a datastore :param context: The datastore to request from :returns: The populated response or exception message ''' count = len(self.values) if not (1 <= count <= 0x07b0): return self.doException(merror.IllegalValue) if (self.byte_count != (count + 7) // 8): return self.doException(merror.IllegalValue) if not context.validate(self.function_code, self.address, count): return self.doException(merror.IllegalAddress) context.setValues(self.function_code, self.address, self.values) return WriteMultipleCoilsResponse(self.address, count) def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' params = (self.address, len(self.values)) return "WriteNCoilRequest (%d) => %d " % params class WriteMultipleCoilsResponse(ModbusResponse): ''' The normal response returns the function code, starting address, and quantity of coils forced. ''' function_code = 15 _rtu_frame_size = 8 def __init__(self, address=None, count=None, **kwargs): ''' Initializes a new instance :param address: The starting variable address written to :param count: The number of values written ''' ModbusResponse.__init__(self, **kwargs) self.address = address self.count = count def encode(self): ''' Encodes write coils response :returns: The byte encoded message ''' return struct.pack('>HH', self.address, self.count) def decode(self, data): ''' Decodes a write coils response :param data: The packet data to decode ''' self.address, self.count = struct.unpack('>HH', data) def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "WriteNCoilResponse(%d, %d)" % (self.address, self.count) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "WriteSingleCoilRequest", "WriteSingleCoilResponse", "WriteMultipleCoilsRequest", "WriteMultipleCoilsResponse", ] pymodbus-1.3.2/pymodbus/diag_message.py0000644000175000017500000007105513150360615016347 0ustar wmbwmb''' Diagnostic Record Read/Write ------------------------------ These need to be tied into a the current server context or linked to the appropriate data ''' import struct from pymodbus.constants import ModbusStatus, ModbusPlusOperation from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.device import ModbusControlBlock from pymodbus.exceptions import NotImplementedException from pymodbus.utilities import pack_bitstring _MCB = ModbusControlBlock() #---------------------------------------------------------------------------# # Diagnostic Function Codes Base Classes # diagnostic 08, 00-18,20 #---------------------------------------------------------------------------# # TODO Make sure all the data is decoded from the response #---------------------------------------------------------------------------# class DiagnosticStatusRequest(ModbusRequest): ''' This is a base class for all of the diagnostic request functions ''' function_code = 0x08 _rtu_frame_size = 8 def __init__(self, **kwargs): ''' Base initializer for a diagnostic request ''' ModbusRequest.__init__(self, **kwargs) self.message = None def encode(self): ''' Base encoder for a diagnostic response we encode the data set in self.message :returns: The encoded packet ''' packet = struct.pack('>H', self.sub_function_code) if self.message is not None: if isinstance(self.message, str): packet += self.message.encode() elif isinstance(self.message, bytes): packet += self.message elif isinstance(self.message, list): for piece in self.message: packet += struct.pack('>H', piece) elif isinstance(self.message, int): packet += struct.pack('>H', self.message) return packet def decode(self, data): ''' Base decoder for a diagnostic request :param data: The data to decode into the function code ''' self.sub_function_code, self.message = struct.unpack('>HH', data) def get_response_pdu_size(self): """ Func_code (1 byte) + Sub function code (2 byte) + Data (2 * N bytes) :return: """ if not isinstance(self.message,list): self.message = [self.message] return 1 + 2 + 2 * len(self.message) class DiagnosticStatusResponse(ModbusResponse): ''' This is a base class for all of the diagnostic response functions It works by performing all of the encoding and decoding of variable data and lets the higher classes define what extra data to append and how to execute a request ''' function_code = 0x08 _rtu_frame_size = 8 def __init__(self, **kwargs): ''' Base initializer for a diagnostic response ''' ModbusResponse.__init__(self, **kwargs) self.message = None def encode(self): ''' Base encoder for a diagnostic response we encode the data set in self.message :returns: The encoded packet ''' packet = struct.pack('>H', self.sub_function_code) if self.message is not None: if isinstance(self.message, str): packet += self.message.encode() elif isinstance(self.message, bytes): packet += self.message elif isinstance(self.message, list): for piece in self.message: packet += struct.pack('>H', piece) elif isinstance(self.message, int): packet += struct.pack('>H', self.message) return packet def decode(self, data): ''' Base decoder for a diagnostic response :param data: The data to decode into the function code ''' word_len = len(data)//2 if len(data) % 2: word_len += 1 data = struct.unpack('>' + 'H'*word_len, data) self.sub_function_code, self.message = data[0], data[1:] class DiagnosticStatusSimpleRequest(DiagnosticStatusRequest): ''' A large majority of the diagnostic functions are simple status request functions. They work by sending 0x0000 as data and their function code and they are returned 2 bytes of data. If a function inherits this, they only need to implement the execute method ''' def __init__(self, data=0x0000, **kwargs): ''' General initializer for a simple diagnostic request The data defaults to 0x0000 if not provided as over half of the functions require it. :param data: The data to send along with the request ''' DiagnosticStatusRequest.__init__(self, **kwargs) self.message = data def execute(self, *args): ''' Base function to raise if not implemented ''' raise NotImplementedException("Diagnostic Message Has No Execute Method") class DiagnosticStatusSimpleResponse(DiagnosticStatusResponse): ''' A large majority of the diagnostic functions are simple status request functions. They work by sending 0x0000 as data and their function code and they are returned 2 bytes of data. ''' def __init__(self, data=0x0000, **kwargs): ''' General initializer for a simple diagnostic response :param data: The resulting data to return to the client ''' DiagnosticStatusResponse.__init__(self, **kwargs) self.message = data #---------------------------------------------------------------------------# # Diagnostic Sub Code 00 #---------------------------------------------------------------------------# class ReturnQueryDataRequest(DiagnosticStatusRequest): ''' The data passed in the request data field is to be returned (looped back) in the response. The entire response message should be identical to the request. ''' sub_function_code = 0x0000 def __init__(self, message=0x0000, **kwargs): ''' Initializes a new instance of the request :param message: The message to send to loopback ''' DiagnosticStatusRequest.__init__(self, **kwargs) if isinstance(message, list): self.message = message else: self.message = [message] def execute(self, *args): ''' Executes the loopback request (builds the response) :returns: The populated loopback response message ''' return ReturnQueryDataResponse(self.message) class ReturnQueryDataResponse(DiagnosticStatusResponse): ''' The data passed in the request data field is to be returned (looped back) in the response. The entire response message should be identical to the request. ''' sub_function_code = 0x0000 def __init__(self, message=0x0000, **kwargs): ''' Initializes a new instance of the response :param message: The message to loopback ''' DiagnosticStatusResponse.__init__(self, **kwargs) if isinstance(message, list): self.message = message else: self.message = [message] #---------------------------------------------------------------------------# # Diagnostic Sub Code 01 #---------------------------------------------------------------------------# class RestartCommunicationsOptionRequest(DiagnosticStatusRequest): ''' The remote device serial line port must be initialized and restarted, and all of its communications event counters are cleared. If the port is currently in Listen Only Mode, no response is returned. This function is the only one that brings the port out of Listen Only Mode. If the port is not currently in Listen Only Mode, a normal response is returned. This occurs before the restart is executed. ''' sub_function_code = 0x0001 def __init__(self, toggle=False, **kwargs): ''' Initializes a new request :param toggle: Set to True to toggle, False otherwise ''' DiagnosticStatusRequest.__init__(self, **kwargs) if toggle: self.message = [ModbusStatus.On] else: self.message = [ModbusStatus.Off] def execute(self, *args): ''' Clear event log and restart :returns: The initialized response message ''' #if _MCB.ListenOnly: return RestartCommunicationsOptionResponse(self.message) class RestartCommunicationsOptionResponse(DiagnosticStatusResponse): ''' The remote device serial line port must be initialized and restarted, and all of its communications event counters are cleared. If the port is currently in Listen Only Mode, no response is returned. This function is the only one that brings the port out of Listen Only Mode. If the port is not currently in Listen Only Mode, a normal response is returned. This occurs before the restart is executed. ''' sub_function_code = 0x0001 def __init__(self, toggle=False, **kwargs): ''' Initializes a new response :param toggle: Set to True if we toggled, False otherwise ''' DiagnosticStatusResponse.__init__(self, **kwargs) if toggle: self.message = [ModbusStatus.On] else: self.message = [ModbusStatus.Off] #---------------------------------------------------------------------------# # Diagnostic Sub Code 02 #---------------------------------------------------------------------------# class ReturnDiagnosticRegisterRequest(DiagnosticStatusSimpleRequest): ''' The contents of the remote device's 16-bit diagnostic register are returned in the response ''' sub_function_code = 0x0002 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' #if _MCB.isListenOnly(): register = pack_bitstring(_MCB.getDiagnosticRegister()) return ReturnDiagnosticRegisterResponse(register) class ReturnDiagnosticRegisterResponse(DiagnosticStatusSimpleResponse): ''' The contents of the remote device's 16-bit diagnostic register are returned in the response ''' sub_function_code = 0x0002 #---------------------------------------------------------------------------# # Diagnostic Sub Code 03 #---------------------------------------------------------------------------# class ChangeAsciiInputDelimiterRequest(DiagnosticStatusSimpleRequest): ''' The character 'CHAR' passed in the request data field becomes the end of message delimiter for future messages (replacing the default LF character). This function is useful in cases of a Line Feed is not required at the end of ASCII messages. ''' sub_function_code = 0x0003 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' char = (self.message & 0xff00) >> 8 _MCB.Delimiter = char return ChangeAsciiInputDelimiterResponse(self.message) class ChangeAsciiInputDelimiterResponse(DiagnosticStatusSimpleResponse): ''' The character 'CHAR' passed in the request data field becomes the end of message delimiter for future messages (replacing the default LF character). This function is useful in cases of a Line Feed is not required at the end of ASCII messages. ''' sub_function_code = 0x0003 #---------------------------------------------------------------------------# # Diagnostic Sub Code 04 #---------------------------------------------------------------------------# class ForceListenOnlyModeRequest(DiagnosticStatusSimpleRequest): ''' Forces the addressed remote device to its Listen Only Mode for MODBUS communications. This isolates it from the other devices on the network, allowing them to continue communicating without interruption from the addressed remote device. No response is returned. ''' sub_function_code = 0x0004 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' _MCB.ListenOnly = True return ForceListenOnlyModeResponse() class ForceListenOnlyModeResponse(DiagnosticStatusResponse): ''' Forces the addressed remote device to its Listen Only Mode for MODBUS communications. This isolates it from the other devices on the network, allowing them to continue communicating without interruption from the addressed remote device. No response is returned. This does not send a response ''' sub_function_code = 0x0004 should_respond = False def __init__(self, **kwargs): ''' Initializer to block a return response ''' DiagnosticStatusResponse.__init__(self, **kwargs) self.message = [] #---------------------------------------------------------------------------# # Diagnostic Sub Code 10 #---------------------------------------------------------------------------# class ClearCountersRequest(DiagnosticStatusSimpleRequest): ''' The goal is to clear ll counters and the diagnostic register. Also, counters are cleared upon power-up ''' sub_function_code = 0x000A def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' _MCB.reset() return ClearCountersResponse(self.message) class ClearCountersResponse(DiagnosticStatusSimpleResponse): ''' The goal is to clear ll counters and the diagnostic register. Also, counters are cleared upon power-up ''' sub_function_code = 0x000A #---------------------------------------------------------------------------# # Diagnostic Sub Code 11 #---------------------------------------------------------------------------# class ReturnBusMessageCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages that the remote device has detected on the communications systems since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000B def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusMessage return ReturnBusMessageCountResponse(count) class ReturnBusMessageCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages that the remote device has detected on the communications systems since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000B #---------------------------------------------------------------------------# # Diagnostic Sub Code 12 #---------------------------------------------------------------------------# class ReturnBusCommunicationErrorCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of CRC errors encountered by the remote device since its last restart, clear counter operation, or power-up ''' sub_function_code = 0x000C def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCommunicationError return ReturnBusCommunicationErrorCountResponse(count) class ReturnBusCommunicationErrorCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of CRC errors encountered by the remote device since its last restart, clear counter operation, or power-up ''' sub_function_code = 0x000C #---------------------------------------------------------------------------# # Diagnostic Sub Code 13 #---------------------------------------------------------------------------# class ReturnBusExceptionErrorCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of modbus exception responses returned by the remote device since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000D def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusExceptionError return ReturnBusExceptionErrorCountResponse(count) class ReturnBusExceptionErrorCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of modbus exception responses returned by the remote device since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000D #---------------------------------------------------------------------------# # Diagnostic Sub Code 14 #---------------------------------------------------------------------------# class ReturnSlaveMessageCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages addressed to the remote device, or broadcast, that the remote device has processed since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000E def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveMessage return ReturnSlaveMessageCountResponse(count) class ReturnSlaveMessageCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the remote device, or broadcast, that the remote device has processed since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000E #---------------------------------------------------------------------------# # Diagnostic Sub Code 15 #---------------------------------------------------------------------------# class ReturnSlaveNoResponseCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages addressed to the remote device, or broadcast, that the remote device has processed since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000F def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveNoResponse return ReturnSlaveNoReponseCountResponse(count) class ReturnSlaveNoReponseCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the remote device, or broadcast, that the remote device has processed since its last restart, clear counters operation, or power-up ''' sub_function_code = 0x000F #---------------------------------------------------------------------------# # Diagnostic Sub Code 16 #---------------------------------------------------------------------------# class ReturnSlaveNAKCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages addressed to the remote device for which it returned a Negative Acknowledge (NAK) exception response, since its last restart, clear counters operation, or power-up. Exception responses are described and listed in section 7 . ''' sub_function_code = 0x0010 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveNAK return ReturnSlaveNAKCountResponse(count) class ReturnSlaveNAKCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the remote device for which it returned a Negative Acknowledge (NAK) exception response, since its last restart, clear counters operation, or power-up. Exception responses are described and listed in section 7. ''' sub_function_code = 0x0010 #---------------------------------------------------------------------------# # Diagnostic Sub Code 17 #---------------------------------------------------------------------------# class ReturnSlaveBusyCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages addressed to the remote device for which it returned a Slave Device Busy exception response, since its last restart, clear counters operation, or power-up. ''' sub_function_code = 0x0011 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveBusy return ReturnSlaveBusyCountResponse(count) class ReturnSlaveBusyCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the remote device for which it returned a Slave Device Busy exception response, since its last restart, clear counters operation, or power-up. ''' sub_function_code = 0x0011 #---------------------------------------------------------------------------# # Diagnostic Sub Code 18 #---------------------------------------------------------------------------# class ReturnSlaveBusCharacterOverrunCountRequest(DiagnosticStatusSimpleRequest): ''' The response data field returns the quantity of messages addressed to the remote device that it could not handle due to a character overrun condition, since its last restart, clear counters operation, or power-up. A character overrun is caused by data characters arriving at the port faster than they can be stored, or by the loss of a character due to a hardware malfunction. ''' sub_function_code = 0x0012 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCharacterOverrun return ReturnSlaveBusCharacterOverrunCountResponse(count) class ReturnSlaveBusCharacterOverrunCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the remote device that it could not handle due to a character overrun condition, since its last restart, clear counters operation, or power-up. A character overrun is caused by data characters arriving at the port faster than they can be stored, or by the loss of a character due to a hardware malfunction. ''' sub_function_code = 0x0012 #---------------------------------------------------------------------------# # Diagnostic Sub Code 19 #---------------------------------------------------------------------------# class ReturnIopOverrunCountRequest(DiagnosticStatusSimpleRequest): ''' An IOP overrun is caused by data characters arriving at the port faster than they can be stored, or by the loss of a character due to a hardware malfunction. This function is specific to the 884. ''' sub_function_code = 0x0013 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCharacterOverrun return ReturnIopOverrunCountResponse(count) class ReturnIopOverrunCountResponse(DiagnosticStatusSimpleResponse): ''' The response data field returns the quantity of messages addressed to the slave that it could not handle due to an 884 IOP overrun condition, since its last restart, clear counters operation, or power-up. ''' sub_function_code = 0x0013 #---------------------------------------------------------------------------# # Diagnostic Sub Code 20 #---------------------------------------------------------------------------# class ClearOverrunCountRequest(DiagnosticStatusSimpleRequest): ''' Clears the overrun error counter and reset the error flag An error flag should be cleared, but nothing else in the specification mentions is, so it is ignored. ''' sub_function_code = 0x0014 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' _MCB.Counter.BusCharacterOverrun = 0x0000 return ClearOverrunCountResponse(self.message) class ClearOverrunCountResponse(DiagnosticStatusSimpleResponse): ''' Clears the overrun error counter and reset the error flag ''' sub_function_code = 0x0014 #---------------------------------------------------------------------------# # Diagnostic Sub Code 21 #---------------------------------------------------------------------------# class GetClearModbusPlusRequest(DiagnosticStatusSimpleRequest): ''' In addition to the Function code (08) and Subfunction code (00 15 hex) in the query, a two-byte Operation field is used to specify either a 'Get Statistics' or a 'Clear Statistics' operation. The two operations are exclusive - the 'Get' operation cannot clear the statistics, and the 'Clear' operation does not return statistics prior to clearing them. Statistics are also cleared on power-up of the slave device. ''' sub_function_code = 0x0015 def __init__(self, **kwargs): super(GetClearModbusPlusRequest, self).__init__(**kwargs) def get_response_pdu_size(self): """ Returns a series of 54 16-bit words (108 bytes) in the data field of the response (this function differs from the usual two-byte length of the data field). The data contains the statistics for the Modbus Plus peer processor in the slave device. Func_code (1 byte) + Sub function code (2 byte) + Operation (2 byte) + Data (108 bytes) :return: """ if self.message == ModbusPlusOperation.GetStatistics: data = 2 + 108 # byte count(2) + data (54*2) else: data = 0 return 1 + 2 + 2 + 2+ data def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' message = None # the clear operation does not return info if self.message == ModbusPlusOperation.ClearStatistics: _MCB.Plus.reset() message = self.message else: message = [self.message] message += _MCB.Plus.encode() return GetClearModbusPlusResponse(message) def encode(self): ''' Base encoder for a diagnostic response we encode the data set in self.message :returns: The encoded packet ''' packet = struct.pack('>H', self.sub_function_code) packet += struct.pack('>H', self.message) return packet class GetClearModbusPlusResponse(DiagnosticStatusSimpleResponse): ''' Returns a series of 54 16-bit words (108 bytes) in the data field of the response (this function differs from the usual two-byte length of the data field). The data contains the statistics for the Modbus Plus peer processor in the slave device. ''' sub_function_code = 0x0015 #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "DiagnosticStatusRequest", "DiagnosticStatusResponse", "ReturnQueryDataRequest", "ReturnQueryDataResponse", "RestartCommunicationsOptionRequest", "RestartCommunicationsOptionResponse", "ReturnDiagnosticRegisterRequest", "ReturnDiagnosticRegisterResponse", "ChangeAsciiInputDelimiterRequest", "ChangeAsciiInputDelimiterResponse", "ForceListenOnlyModeRequest", "ForceListenOnlyModeResponse", "ClearCountersRequest", "ClearCountersResponse", "ReturnBusMessageCountRequest", "ReturnBusMessageCountResponse", "ReturnBusCommunicationErrorCountRequest", "ReturnBusCommunicationErrorCountResponse", "ReturnBusExceptionErrorCountRequest", "ReturnBusExceptionErrorCountResponse", "ReturnSlaveMessageCountRequest", "ReturnSlaveMessageCountResponse", "ReturnSlaveNoResponseCountRequest", "ReturnSlaveNoReponseCountResponse", "ReturnSlaveNAKCountRequest", "ReturnSlaveNAKCountResponse", "ReturnSlaveBusyCountRequest", "ReturnSlaveBusyCountResponse", "ReturnSlaveBusCharacterOverrunCountRequest", "ReturnSlaveBusCharacterOverrunCountResponse", "ReturnIopOverrunCountRequest", "ReturnIopOverrunCountResponse", "ClearOverrunCountRequest", "ClearOverrunCountResponse", "GetClearModbusPlusRequest", "GetClearModbusPlusResponse", ] pymodbus-1.3.2/pymodbus/register_read_message.py0000644000175000017500000003122513150360615020255 0ustar wmbwmb''' Register Reading Request/Response --------------------------------- ''' import struct from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.pdu import ModbusExceptions as merror from pymodbus.compat import int2byte, byte2int class ReadRegistersRequestBase(ModbusRequest): ''' Base class for reading a modbus register ''' _rtu_frame_size = 8 def __init__(self, address, count, **kwargs): ''' Initializes a new instance :param address: The address to start the read from :param count: The number of registers to read ''' ModbusRequest.__init__(self, **kwargs) self.address = address self.count = count def encode(self): ''' Encodes the request packet :return: The encoded packet ''' return struct.pack('>HH', self.address, self.count) def decode(self, data): ''' Decode a register request packet :param data: The request to decode ''' self.address, self.count = struct.unpack('>HH', data) def get_response_pdu_size(self): """ Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) :return: """ return 1 + 1 + 2 * self.count def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "ReadRegisterRequest (%d,%d)" % (self.address, self.count) class ReadRegistersResponseBase(ModbusResponse): ''' Base class for responsing to a modbus register read ''' _rtu_byte_count_pos = 2 def __init__(self, values, **kwargs): ''' Initializes a new instance :param values: The values to write to ''' ModbusResponse.__init__(self, **kwargs) self.registers = values or [] def encode(self): ''' Encodes the response packet :returns: The encoded packet ''' result = int2byte(len(self.registers) * 2) for register in self.registers: result += struct.pack('>H', register) return result def decode(self, data): ''' Decode a register response packet :param data: The request to decode ''' byte_count = byte2int(data[0]) self.registers = [] for i in range(1, byte_count + 1, 2): self.registers.append(struct.unpack('>H', data[i:i + 2])[0]) def getRegister(self, index): ''' Get the requested register :param index: The indexed register to retrieve :returns: The request register ''' return self.registers[index] def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "ReadRegisterResponse (%d)" % len(self.registers) class ReadHoldingRegistersRequest(ReadRegistersRequestBase): ''' This function code is used to read the contents of a contiguous block of holding registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore registers numbered 1-16 are addressed as 0-15. ''' function_code = 3 def __init__(self, address=None, count=None, **kwargs): ''' Initializes a new instance of the request :param address: The starting address to read from :param count: The number of registers to read from address ''' ReadRegistersRequestBase.__init__(self, address, count, **kwargs) def execute(self, context): ''' Run a read holding request against a datastore :param context: The datastore to request from :returns: An initialized response, exception message otherwise ''' if not (1 <= self.count <= 0x7d): 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 ReadHoldingRegistersResponse(values) class ReadHoldingRegistersResponse(ReadRegistersResponseBase): ''' This function code is used to read the contents of a contiguous block of holding registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore registers numbered 1-16 are addressed as 0-15. ''' function_code = 3 def __init__(self, values=None, **kwargs): ''' Initializes a new response instance :param values: The resulting register values ''' ReadRegistersResponseBase.__init__(self, values, **kwargs) class ReadInputRegistersRequest(ReadRegistersRequestBase): ''' This function code is used to read from 1 to approx. 125 contiguous input registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore input registers numbered 1-16 are addressed as 0-15. ''' function_code = 4 def __init__(self, address=None, count=None, **kwargs): ''' Initializes a new instance of the request :param address: The starting address to read from :param count: The number of registers to read from address ''' ReadRegistersRequestBase.__init__(self, address, count, **kwargs) def execute(self, context): ''' Run a read input request against a datastore :param context: The datastore to request from :returns: An initialized response, exception message otherwise ''' if not (1 <= self.count <= 0x7d): 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 ReadInputRegistersResponse(values) class ReadInputRegistersResponse(ReadRegistersResponseBase): ''' This function code is used to read from 1 to approx. 125 contiguous input registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore input registers numbered 1-16 are addressed as 0-15. ''' function_code = 4 def __init__(self, values=None, **kwargs): ''' Initializes a new response instance :param values: The resulting register values ''' ReadRegistersResponseBase.__init__(self, values, **kwargs) class ReadWriteMultipleRegistersRequest(ModbusRequest): ''' This function code performs a combination of one read operation and one write operation in a single MODBUS transaction. The write operation is performed before the read. Holding registers are addressed starting at zero. Therefore holding registers 1-16 are addressed in the PDU as 0-15. The request specifies the starting address and number of holding registers to be read as well as the starting address, number of holding registers, and the data to be written. The byte count specifies the number of bytes to follow in the write data field." ''' function_code = 23 _rtu_byte_count_pos = 10 def __init__(self, **kwargs): ''' Initializes a new request message :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 ''' ModbusRequest.__init__(self, **kwargs) self.read_address = kwargs.get('read_address', 0x00) self.read_count = kwargs.get('read_count', 0) self.write_address = kwargs.get('write_address', 0x00) self.write_registers = kwargs.get('write_registers', None) if not hasattr(self.write_registers, '__iter__'): self.write_registers = [self.write_registers] self.write_count = len(self.write_registers) self.write_byte_count = self.write_count * 2 def encode(self): ''' Encodes the request packet :returns: The encoded packet ''' result = struct.pack('>HHHHB', self.read_address, self.read_count, \ self.write_address, self.write_count, self.write_byte_count) for register in self.write_registers: result += struct.pack('>H', register) return result def decode(self, data): ''' Decode the register request packet :param data: The request to decode ''' self.read_address, self.read_count, \ self.write_address, self.write_count, \ self.write_byte_count = struct.unpack('>HHHHB', data[:9]) self.write_registers = [] for i in range(9, self.write_byte_count + 9, 2): register = struct.unpack('>H', data[i:i + 2])[0] self.write_registers.append(register) def execute(self, context): ''' Run a write single register request against a datastore :param context: The datastore to request from :returns: An initialized response, exception message otherwise ''' if not (1 <= self.read_count <= 0x07d): return self.doException(merror.IllegalValue) if not (1 <= self.write_count <= 0x079): return self.doException(merror.IllegalValue) if (self.write_byte_count != self.write_count * 2): return self.doException(merror.IllegalValue) if not context.validate(self.function_code, self.write_address, self.write_count): return self.doException(merror.IllegalAddress) if not context.validate(self.function_code, self.read_address, self.read_count): return self.doException(merror.IllegalAddress) context.setValues(self.function_code, self.write_address, self.write_registers) registers = context.getValues(self.function_code, self.read_address, self.read_count) return ReadWriteMultipleRegistersResponse(registers) def get_response_pdu_size(self): """ Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) :return: """ return 1 + 1 + 2 * self.read_count def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' params = (self.read_address, self.read_count, self.write_address, self.write_count) return "ReadWriteNRegisterRequest R(%d,%d) W(%d,%d)" % params class ReadWriteMultipleRegistersResponse(ModbusResponse): ''' The normal response contains the data from the group of registers that were read. The byte count field specifies the quantity of bytes to follow in the read data field. ''' function_code = 23 _rtu_byte_count_pos = 2 def __init__(self, values=None, **kwargs): ''' Initializes a new instance :param values: The register values to write ''' ModbusResponse.__init__(self, **kwargs) self.registers = values or [] def encode(self): ''' Encodes the response packet :returns: The encoded packet ''' result = int2byte(len(self.registers) * 2) for register in self.registers: result += struct.pack('>H', register) return result def decode(self, data): ''' Decode the register response packet :param data: The response to decode ''' bytecount = byte2int(data[0]) for i in range(1, bytecount, 2): self.registers.append(struct.unpack('>H', data[i:i + 2])[0]) def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "ReadWriteNRegisterResponse (%d)" % len(self.registers) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "ReadHoldingRegistersRequest", "ReadHoldingRegistersResponse", "ReadInputRegistersRequest", "ReadInputRegistersResponse", "ReadWriteMultipleRegistersRequest", "ReadWriteMultipleRegistersResponse", ] pymodbus-1.3.2/pymodbus/utilities.py0000644000175000017500000001402413150360615015743 0ustar wmbwmb''' Modbus Utilities ----------------- A collection of utilities for packing data, unpacking data computing checksums, and decode checksums. ''' from pymodbus.compat import int2byte, byte2int, IS_PYTHON3 from six import string_types #---------------------------------------------------------------------------# # Helpers #---------------------------------------------------------------------------# def default(value): ''' Given a python object, return the default value of that object. :param value: The value to get the default of :returns: The default value ''' return type(value)() def dict_property(store, index): ''' Helper to create class properties from a dictionary. Basically this allows you to remove a lot of possible boilerplate code. :param store: The store store to pull from :param index: The index into the store to close over :returns: An initialized property set ''' if hasattr(store, '__call__'): getter = lambda self: store(self)[index] setter = lambda self, value: store(self).__setitem__(index, value) elif isinstance(store, str): getter = lambda self: self.__getattribute__(store)[index] setter = lambda self, value: self.__getattribute__(store).__setitem__( index, value) else: getter = lambda self: store[index] setter = lambda self, value: store.__setitem__(index, value) return property(getter, setter) #---------------------------------------------------------------------------# # Bit packing functions #---------------------------------------------------------------------------# def pack_bitstring(bits): ''' Creates a string out of an array of bits :param bits: A bit array example:: bits = [False, True, False, True] result = pack_bitstring(bits) ''' ret = b'' i = packed = 0 for bit in bits: if bit: packed += 128 i += 1 if i == 8: ret += int2byte(packed) i = packed = 0 else: packed >>= 1 if i > 0 and i < 8: packed >>= (7 - i) ret += int2byte(packed) return ret def unpack_bitstring(string): ''' Creates bit array out of a string :param string: The modbus data packet to decode example:: bytes = 'bytes to decode' result = unpack_bitstring(bytes) ''' byte_count = len(string) bits = [] for byte in range(byte_count): value = byte2int(string[byte]) for _ in range(8): bits.append((value & 1) == 1) value >>= 1 return bits def make_byte_string(s): """ Returns byte string from a given string, python3 specific fix :param s: :return: """ if IS_PYTHON3 and isinstance(s, string_types): s = s.encode() return s #---------------------------------------------------------------------------# # Error Detection Functions #---------------------------------------------------------------------------# def __generate_crc16_table(): ''' Generates a crc16 lookup table .. note:: This will only be generated once ''' result = [] for byte in range(256): crc = 0x0000 for _ in range(8): if (byte ^ crc) & 0x0001: crc = (crc >> 1) ^ 0xa001 else: crc >>= 1 byte >>= 1 result.append(crc) return result __crc16_table = __generate_crc16_table() def computeCRC(data): ''' Computes a crc16 on the passed in string. For modbus, this is only used on the binary serial protocols (in this case RTU). The difference between modbus's crc16 and a normal crc16 is that modbus starts the crc value out at 0xffff. :param data: The data to create a crc16 of :returns: The calculated CRC ''' crc = 0xffff for a in data: idx = __crc16_table[(crc ^ byte2int(a)) & 0xff] crc = ((crc >> 8) & 0xff) ^ idx swapped = ((crc << 8) & 0xff00) | ((crc >> 8) & 0x00ff) return swapped def checkCRC(data, check): ''' Checks if the data matches the passed in CRC :param data: The data to create a crc16 of :param check: The CRC to validate :returns: True if matched, False otherwise ''' return computeCRC(data) == check def computeLRC(data): ''' Used to compute the longitudinal redundancy check against a string. This is only used on the serial ASCII modbus protocol. A full description of this implementation can be found in appendex B of the serial line modbus description. :param data: The data to apply a lrc to :returns: The calculated LRC ''' lrc = sum(byte2int(a) for a in data) & 0xff lrc = (lrc ^ 0xff) + 1 return lrc & 0xff def checkLRC(data, check): ''' Checks if the passed in data matches the LRC :param data: The data to calculate :param check: The LRC to validate :returns: True if matched, False otherwise ''' return computeLRC(data) == check def rtuFrameSize(data, byte_count_pos): ''' Calculates the size of the frame based on the byte count. :param data: The buffer containing the frame. :param byte_count_pos: The index of the byte count in the buffer. :returns: The size of the frame. The structure of frames with a byte count field is always the same: - first, there are some header fields - then the byte count field - then as many data bytes as indicated by the byte count, - finally the CRC (two bytes). To calculate the frame size, it is therefore sufficient to extract the contents of the byte count field, add the position of this field, and finally increment the sum by three (one byte for the byte count field, two for the CRC). ''' return byte2int(data[byte_count_pos]) + byte_count_pos + 3 #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ 'pack_bitstring', 'unpack_bitstring', 'default', 'computeCRC', 'checkCRC', 'computeLRC', 'checkLRC', 'rtuFrameSize' ] pymodbus-1.3.2/pymodbus/__init__.py0000644000175000017500000000160313150360615015466 0ustar wmbwmb''' Pymodbus: Modbus Protocol Implementation ----------------------------------------- TwistedModbus is built on top of the code developed by: Copyright (c) 2001-2005 S.W.A.C. GmbH, Germany. Copyright (c) 2001-2005 S.W.A.C. Bohemia s.r.o., Czech Republic. Hynek Petrak Released under the the BSD license ''' import pymodbus.version as __version __version__ = __version.version.short() __author__ = 'Galen Collins' __maintainer__ = 'dhoomakethu' #---------------------------------------------------------------------------# # Block unhandled logging #---------------------------------------------------------------------------# import logging as __logging try: from logging import NullHandler as __null except ImportError: class __null(__logging.Handler): def emit(self, record): pass __logging.getLogger(__name__).addHandler(__null()) pymodbus-1.3.2/pymodbus/version.py0000644000175000017500000000301313150360615015411 0ustar wmbwmb''' Handle the version information here; you should only have to change the version tuple. Since we are using twisted's version class, we can also query the svn version as well using the local .entries file. ''' class Version(object): def __init__(self, package, major, minor, micro, pre=None): ''' :param package: Name of the package that this is a version of. :param major: The major version number. :param minor: The minor version number. :param micro: The micro version number. :param pre: The pre release tag ''' self.package = package self.major = major self.minor = minor self.micro = micro self.pre = pre def short(self): ''' Return a string in canonical short version format ...
        '''
        if self.pre:
            return '%d.%d.%d.%s' % (self.major, self.minor, self.micro, self.pre)
        else:
            return '%d.%d.%d' % (self.major, self.minor, self.micro)

    def __str__(self):
        ''' Returns a string representation of the object

        :returns: A string representation of this object
        '''
        return '[%s, version %s]' % (self.package, self.short())


version = Version('pymodbus', 1, 3, 2)



version.__name__ = 'pymodbus'  # fix epydoc error

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = ["version"]
pymodbus-1.3.2/pymodbus/interfaces.py0000644000175000017500000001750413150360615016061 0ustar  wmbwmb'''
Pymodbus Interfaces
---------------------

A collection of base classes that are used throughout
the pymodbus library.
'''
from pymodbus.exceptions import NotImplementedException


#---------------------------------------------------------------------------#
# Generic
#---------------------------------------------------------------------------#
class Singleton(object):
    '''
    Singleton base class
    http://mail.python.org/pipermail/python-list/2007-July/450681.html
    '''
    def __new__(cls, *args, **kwargs):
        ''' Create a new instance
        '''
        if '_inst' not in vars(cls):
            cls._inst = object.__new__(cls)
        return cls._inst


#---------------------------------------------------------------------------#
# Project Specific
#---------------------------------------------------------------------------#
class IModbusDecoder(object):
    ''' Modbus Decoder Base Class

    This interface must be implemented by a modbus message
    decoder factory. These factories are responsible for
    abstracting away converting a raw packet into a request / response
    message object.
    '''

    def decode(self, message):
        ''' Wrapper to decode a given packet

        :param message: The raw modbus request packet
        :return: The decoded modbus message or None if error
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def lookupPduClass(self, function_code):
        ''' Use `function_code` to determine the class of the PDU.

        :param function_code: The function code specified in a frame.
        :returns: The class of the PDU that has a matching `function_code`.
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")


class IModbusFramer(object):
    '''
    A framer strategy interface. The idea is that we abstract away all the
    detail about how to detect if a current message frame exists, decoding
    it, sending it, etc so that we can plug in a new Framer object (tcp,
    rtu, ascii).
    '''

    def checkFrame(self):
        ''' Check and decode the next frame

        :returns: True if we successful, False otherwise
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def advanceFrame(self):
        ''' Skip over the current framed message
        This allows us to skip over the current message after we have processed
        it or determined that it contains an error. It also has to reset the
        current frame header handle
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def addToFrame(self, message):
        ''' Add the next message to the frame buffer

        This should be used before the decoding while loop to add the received
        data to the buffer handle.

        :param message: The most recent packet
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def isFrameReady(self):
        ''' Check if we should continue decode logic

        This is meant to be used in a while loop in the decoding phase to let
        the decoder know that there is still data in the buffer.

        :returns: True if ready, False otherwise
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def getFrame(self):
        ''' Get the next frame from the buffer

        :returns: The frame data or ''
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def populateResult(self, result):
        ''' Populates the modbus result with current frame header

        We basically copy the data back over from the current header
        to the result header. This may not be needed for serial messages.

        :param result: The response packet
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def processIncomingPacket(self, data, callback):
        ''' The new packet processing pattern

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 / N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")

    def buildPacket(self, message):
        ''' Creates a ready to send modbus packet

        The raw packet is built off of a fully populated modbus
        request / response message.

        :param message: The request/response to send
        :returns: The built packet
        '''
        raise NotImplementedException(
            "Method not implemented by derived class")


class IModbusSlaveContext(object):
    '''
    Interface for a modbus slave data context

    Derived classes must implemented the following methods:
            reset(self)
            validate(self, fx, address, count=1)
            getValues(self, fx, address, count=1)
            setValues(self, fx, address, values)
    '''
    __fx_mapper = {2: 'd', 4: 'i'}
    __fx_mapper.update([(i, 'h') for i in [3, 6, 16, 22, 23]])
    __fx_mapper.update([(i, 'c') for i in [1, 5, 15]])

    def decode(self, fx):
        ''' Converts the function code to the datastore to

        :param fx: The function we are working with
        :returns: one of [d(iscretes),i(inputs),h(oliding),c(oils)
        '''
        return self.__fx_mapper[fx]

    def reset(self):
        ''' Resets all the datastores to their default values
        '''
        raise NotImplementedException("Context Reset")

    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
        '''
        raise NotImplementedException("validate context values")

    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
        '''
        raise NotImplementedException("get context values")

    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
        '''
        raise NotImplementedException("set context values")


class IPayloadBuilder(object):
    '''
    This is an interface to a class that can build a payload
    for a modbus register write command. It should abstract
    the codec for encoding data to the required format
    (bcd, binary, char, etc).
    '''

    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
        '''
        raise NotImplementedException("set context values")

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    'Singleton',
    'IModbusDecoder', 'IModbusFramer', 'IModbusSlaveContext',
    'IPayloadBuilder',
]
pymodbus-1.3.2/pymodbus/device.py0000644000175000017500000005474213150360615015202 0ustar  wmbwmb"""
Modbus Device Controller
-------------------------

These are the device management handlers.  They should be
maintained in the server context and the various methods
should be inserted in the correct locations.
"""
from pymodbus.constants import DeviceInformation
from pymodbus.interfaces import Singleton
from pymodbus.utilities import dict_property
from pymodbus.compat import iteritems, itervalues, izip, int2byte

from collections import OrderedDict

#---------------------------------------------------------------------------#
# Network Access Control
#---------------------------------------------------------------------------#
class ModbusAccessControl(Singleton):
    '''
    This is a simple implementation of a Network Management System table.
    Its purpose is to control access to the server (if it is used).
    We assume that if an entry is in the table, it is allowed accesses to
    resources.  However, if the host does not appear in the table (all
    unknown hosts) its connection will simply be closed.

    Since it is a singleton, only one version can possible exist and all
    instances pull from here.
    '''
    __nmstable = [
            "127.0.0.1",
    ]

    def __iter__(self):
        ''' Iterater over the network access table

        :returns: An iterator of the network access table
        '''
        return self.__nmstable.__iter__()

    def __contains__(self, host):
        ''' Check if a host is allowed to access resources

        :param host: The host to check
        '''
        return host in self.__nmstable

    def add(self, host):
        ''' Add allowed host(s) from the NMS table

        :param host: The host to add
        '''
        if not isinstance(host, list):
            host = [host]
        for entry in host:
            if entry not in self.__nmstable:
                self.__nmstable.append(entry)

    def remove(self, host):
        ''' Remove allowed host(s) from the NMS table

        :param host: The host to remove
        '''
        if not isinstance(host, list):
            host = [host]
        for entry in host:
            if entry in self.__nmstable:
                self.__nmstable.remove(entry)

    def check(self, host):
        ''' Check if a host is allowed to access resources

        :param host: The host to check
        '''
        return host in self.__nmstable


#---------------------------------------------------------------------------#
# Modbus Plus Statistics
#---------------------------------------------------------------------------#
class ModbusPlusStatistics(object):
    '''
    This is used to maintain the current modbus plus statistics count. As of
    right now this is simply a stub to complete the modbus implementation.
    For more information, see the modbus implementation guide page 87.
    '''

    __data = OrderedDict({
        'node_type_id'                   : [0x00] * 2, # 00
        'software_version_number'        : [0x00] * 2, # 01
        'network_address'                : [0x00] * 2, # 02
        'mac_state_variable'             : [0x00] * 2, # 03
        'peer_status_code'               : [0x00] * 2, # 04
        'token_pass_counter'             : [0x00] * 2, # 05
        'token_rotation_time'            : [0x00] * 2, # 06

        'program_master_token_failed'    : [0x00],     # 07 hi
        'data_master_token_failed'       : [0x00],     # 07 lo
        'program_master_token_owner'     : [0x00],     # 08 hi
        'data_master_token_owner'        : [0x00],     # 08 lo
        'program_slave_token_owner'      : [0x00],     # 09 hi
        'data_slave_token_owner'         : [0x00],     # 09 lo
        'data_slave_command_transfer'    : [0x00],     # 10 hi
        '__unused_10_lowbit'             : [0x00],     # 10 lo

        'program_slave_command_transfer' : [0x00],     # 11 hi
        'program_master_rsp_transfer'    : [0x00],     # 11 lo
        'program_slave_auto_logout'      : [0x00],     # 12 hi
        'program_master_connect_status'  : [0x00],     # 12 lo
        'receive_buffer_dma_overrun'     : [0x00],     # 13 hi
        'pretransmit_deferral_error'     : [0x00],     # 13 lo
        'frame_size_error'               : [0x00],     # 14 hi
        'repeated_command_received'      : [0x00],     # 14 lo
        'receiver_alignment_error'       : [0x00],     # 15 hi
        'receiver_collision_abort_error' : [0x00],     # 15 lo
        'bad_packet_length_error'        : [0x00],     # 16 hi
        'receiver_crc_error'             : [0x00],     # 16 lo
        'transmit_buffer_dma_underrun'   : [0x00],     # 17 hi
        'bad_link_address_error'         : [0x00],     # 17 lo

        'bad_mac_function_code_error'    : [0x00],     # 18 hi
        'internal_packet_length_error'   : [0x00],     # 18 lo
        'communication_failed_error'     : [0x00],     # 19 hi
        'communication_retries'          : [0x00],     # 19 lo
        'no_response_error'              : [0x00],     # 20 hi
        'good_receive_packet'            : [0x00],     # 20 lo
        'unexpected_path_error'          : [0x00],     # 21 hi
        'exception_response_error'       : [0x00],     # 21 lo
        'forgotten_transaction_error'    : [0x00],     # 22 hi
        'unexpected_response_error'      : [0x00],     # 22 lo

        'active_station_bit_map'         : [0x00] * 8, # 23-26
        'token_station_bit_map'          : [0x00] * 8, # 27-30
        'global_data_bit_map'            : [0x00] * 8, # 31-34
        'receive_buffer_use_bit_map'     : [0x00] * 8, # 35-37
        'data_master_output_path'        : [0x00] * 8, # 38-41
        'data_slave_input_path'          : [0x00] * 8, # 42-45
        'program_master_outptu_path'     : [0x00] * 8, # 46-49
        'program_slave_input_path'       : [0x00] * 8, # 50-53
    })

    def __init__(self):
        '''
        Initialize the modbus plus statistics with the default
        information.
        '''
        self.reset()

    def __iter__(self):
        ''' Iterater over the statistics

        :returns: An iterator of the modbus plus statistics
        '''
        return iteritems(self.__data)

    def reset(self):
        ''' This clears all of the modbus plus statistics
        '''
        for key in self.__data:
            self.__data[key] = [0x00] * len(self.__data[key])

    def summary(self):
        ''' Returns a summary of the modbus plus statistics

        :returns: 54 16-bit words representing the status
        '''
        return itervalues(self.__data)

    def encode(self):
        ''' Returns a summary of the modbus plus statistics

        :returns: 54 16-bit words representing the status
        '''
        total, values = [], sum(self.__data.values(), [])
        for c in range(0, len(values), 2):
            total.append((values[c] << 8) | values[c+1])
        return total


#---------------------------------------------------------------------------#
# Device Information Control
#---------------------------------------------------------------------------#
class ModbusDeviceIdentification(object):
    '''
    This is used to supply the device identification
    for the readDeviceIdentification function

    For more information read section 6.21 of the modbus
    application protocol.
    '''
    __data = {
        0x00: '',  # VendorName
        0x01: '',  # ProductCode
        0x02: '',  # MajorMinorRevision
        0x03: '',  # VendorUrl
        0x04: '',  # ProductName
        0x05: '',  # ModelName
        0x06: '',  # UserApplicationName
        0x07: '',  # reserved
        0x08: '',  # reserved
        # 0x80 -> 0xFF are private
    }

    __names = [
        'VendorName',
        'ProductCode',
        'MajorMinorRevision',
        'VendorUrl',
        'ProductName',
        'ModelName',
        'UserApplicationName',
    ]

    def __init__(self, info=None):
        '''
        Initialize the datastore with the elements you need.
        (note acceptable range is [0x00-0x06,0x80-0xFF] inclusive)

        :param information: A dictionary of {int:string} of values
        '''
        if isinstance(info, dict):
            for key in info:
                if (0x06 >= key >= 0x00) or (0x80 > key > 0x08):
                    self.__data[key] = info[key]

    def __iter__(self):
        ''' Iterater over the device information

        :returns: An iterator of the device information
        '''
        return iteritems(self.__data)

    def summary(self):
        ''' Return a summary of the main items

        :returns: An dictionary of the main items
        '''
        return dict(zip(self.__names, itervalues(self.__data)))

    def update(self, value):
        ''' Update the values of this identity
        using another identify as the value

        :param value: The value to copy values from
        '''
        self.__data.update(value)

    def __setitem__(self, key, value):
        ''' Wrapper used to access the device information

        :param key: The register to set
        :param value: The new value for referenced register
        '''
        if key not in [0x07, 0x08]:
            self.__data[key] = value

    def __getitem__(self, key):
        ''' Wrapper used to access the device information

        :param key: The register to read
        '''
        return self.__data.setdefault(key, '')

    def __str__(self):
        ''' Build a representation of the device

        :returns: A string representation of the device
        '''
        return "DeviceIdentity"

    #-------------------------------------------------------------------------#
    # Properties
    #-------------------------------------------------------------------------#
    VendorName          = dict_property(lambda s: s.__data, 0)
    ProductCode         = dict_property(lambda s: s.__data, 1)
    MajorMinorRevision  = dict_property(lambda s: s.__data, 2)
    VendorUrl           = dict_property(lambda s: s.__data, 3)
    ProductName         = dict_property(lambda s: s.__data, 4)
    ModelName           = dict_property(lambda s: s.__data, 5)
    UserApplicationName = dict_property(lambda s: s.__data, 6)


class DeviceInformationFactory(Singleton):
    ''' This is a helper factory that really just hides
    some of the complexity of processing the device information
    requests (function code 0x2b 0x0e).
    '''

    __lookup = {
        DeviceInformation.Basic:    lambda c,r,i: c.__gets(r, list(range(0x00, 0x03))),
        DeviceInformation.Regular:  lambda c,r,i: c.__gets(r, list(range(0x00, 0x08))),
        DeviceInformation.Extended: lambda c,r,i: c.__gets(r, list(range(0x80, i))),
        DeviceInformation.Specific: lambda c,r,i: c.__get(r, i),
    }

    @classmethod
    def get(cls, control, read_code=DeviceInformation.Basic, object_id=0x00):
        ''' Get the requested device data from the system

        :param control: The control block to pull data from
        :param read_code: The read code to process
        :param object_id: The specific object_id to read
        :returns: The requested data (id, length, value)
        '''
        identity = control.Identity
        return cls.__lookup[read_code](cls, identity, object_id)

    @classmethod
    def __get(cls, identity, object_id):
        ''' Read a single object_id from the device information

        :param identity: The identity block to pull data from
        :param object_id: The specific object id to read
        :returns: The requested data (id, length, value)
        '''
        return { object_id:identity[object_id] }

    @classmethod
    def __gets(cls, identity, object_ids):
        ''' Read multiple object_ids from the device information

        :param identity: The identity block to pull data from
        :param object_ids: The specific object ids to read
        :returns: The requested data (id, length, value)
        '''
        return dict((oid, identity[oid]) for oid in object_ids)


#---------------------------------------------------------------------------#
# Counters Handler
#---------------------------------------------------------------------------#
class ModbusCountersHandler(object):
    '''
    This is a helper class to simplify the properties for the counters::

    0x0B  1  Return Bus Message Count

             Quantity of messages that the remote
             device has detected on the communications system since its
             last restart, clear counters operation, or power-up.  Messages
             with bad CRC are not taken into account.

    0x0C  2  Return Bus Communication Error Count

             Quantity of CRC errors encountered by the remote device since its
             last restart, clear counters operation, or power-up.  In case of
             an error detected on the character level, (overrun, parity error),
             or in case of a message length < 3 bytes, the receiving device is
             not able to calculate the CRC. In such cases, this counter is
             also incremented.

    0x0D  3  Return Slave Exception Error Count

             Quantity of MODBUS exception error detected by the remote device
             since its last restart, clear counters operation, or power-up.  It
             comprises also the error detected in broadcast messages even if an
             exception message is not returned in this case.
             Exception errors are described and listed in "MODBUS Application
             Protocol Specification" document.

    0xOE  4  Return Slave Message Count

             Quantity of messages addressed to the remote device,  including
             broadcast messages, that the remote device has processed since its
             last restart, clear counters operation, or power-up.

    0x0F  5  Return Slave No Response Count

             Quantity of messages received by the remote device for which it
             returned no response (neither a normal response nor an exception
             response), since its last restart, clear counters operation, or
             power-up. Then, this counter counts the number of broadcast
             messages it has received.

    0x10  6  Return Slave NAK Count

             Quantity of messages addressed to the remote device for which it
             returned a Negative Acknowledge (NAK) exception response, since
             its last restart, clear counters operation, or power-up. Exception
             responses are described and listed in "MODBUS Application Protocol
             Specification" document.

    0x11  7  Return Slave Busy Count

             Quantity of messages addressed to the remote device for which it
             returned a Slave Device Busy exception response, since its last
             restart, clear counters operation, or power-up. Exception
             responses are described and listed in "MODBUS Application
             Protocol Specification" document.

    0x12  8  Return Bus Character Overrun Count

             Quantity of messages addressed to the remote device that it could
             not handle due to a character overrun condition, since its last
             restart, clear counters operation, or power-up. A character
             overrun is caused by data characters arriving at the port faster
             than they can.

    .. note:: I threw the event counter in here for convinience
    '''
    __data = dict([(i, 0x0000) for i in range(9)])
    __names   = [
        'BusMessage',
        'BusCommunicationError',
        'SlaveExceptionError',
        'SlaveMessage',
        'SlaveNoResponse',
        'SlaveNAK',
        'SlaveBusy',
        'BusCharacterOverrun'
        'Event '
    ]

    def __iter__(self):
        ''' Iterater over the device counters

        :returns: An iterator of the device counters
        '''
        return izip(self.__names, itervalues(self.__data))

    def update(self, values):
        ''' Update the values of this identity
        using another identify as the value

        :param values: The value to copy values from
        '''
        for k, v in iteritems(values):
            v += self.__getattribute__(k)
            self.__setattr__(k, v)

    def reset(self):
        ''' This clears all of the system counters
        '''
        self.__data = dict([(i, 0x0000) for i in range(9)])

    def summary(self):
        ''' Returns a summary of the counters current status

        :returns: A byte with each bit representing each counter
        '''
        count, result = 0x01, 0x00
        for i in itervalues(self.__data):
            if i != 0x00: result |= count
            count <<= 1
        return result

    #-------------------------------------------------------------------------#
    # Properties
    #-------------------------------------------------------------------------#
    BusMessage            = dict_property(lambda s: s.__data, 0)
    BusCommunicationError = dict_property(lambda s: s.__data, 1)
    BusExceptionError     = dict_property(lambda s: s.__data, 2)
    SlaveMessage          = dict_property(lambda s: s.__data, 3)
    SlaveNoResponse       = dict_property(lambda s: s.__data, 4)
    SlaveNAK              = dict_property(lambda s: s.__data, 5)
    SlaveBusy             = dict_property(lambda s: s.__data, 6)
    BusCharacterOverrun   = dict_property(lambda s: s.__data, 7)
    Event                 = dict_property(lambda s: s.__data, 8)


#---------------------------------------------------------------------------#
# Main server control block
#---------------------------------------------------------------------------#
class ModbusControlBlock(Singleton):
    '''
    This is a global singleotn that controls all system information

    All activity should be logged here and all diagnostic requests
    should come from here.
    '''

    __mode = 'ASCII'
    __diagnostic = [False] * 16
    __instance = None
    __listen_only = False
    __delimiter = '\r'
    __counters = ModbusCountersHandler()
    __identity = ModbusDeviceIdentification()
    __plus     = ModbusPlusStatistics()
    __events   = []

    #-------------------------------------------------------------------------#
    # Magic
    #-------------------------------------------------------------------------#
    def __str__(self):
        ''' Build a representation of the control block

        :returns: A string representation of the control block
        '''
        return "ModbusControl"

    def __iter__(self):
        ''' Iterater over the device counters

        :returns: An iterator of the device counters
        '''
        return self.__counters.__iter__()

    #-------------------------------------------------------------------------#
    # Events
    #-------------------------------------------------------------------------#
    def addEvent(self, event):
        ''' Adds a new event to the event log

        :param event: A new event to add to the log
        '''
        self.__events.insert(0, event)
        self.__events = self.__events[0:64]  # chomp to 64 entries
        self.Counter.Event += 1

    def getEvents(self):
        ''' Returns an encoded collection of the event log.

        :returns: The encoded events packet
        '''
        events = [event.encode() for event in self.__events]
        return b''.join(events)

    def clearEvents(self):
        ''' Clears the current list of events
        '''
        self.__events = []

    #-------------------------------------------------------------------------#
    # Other Properties
    #-------------------------------------------------------------------------#
    Identity = property(lambda s: s.__identity)
    Counter  = property(lambda s: s.__counters)
    Events   = property(lambda s: s.__events)
    Plus     = property(lambda s: s.__plus)

    def reset(self):
        ''' This clears all of the system counters and the
            diagnostic register
        '''
        self.__events = []
        self.__counters.reset()
        self.__diagnostic = [False] * 16

    #-------------------------------------------------------------------------#
    # Listen Properties
    #-------------------------------------------------------------------------#
    def _setListenOnly(self, value):
        ''' This toggles the listen only status

        :param value: The value to set the listen status to
        '''
        self.__listen_only = bool(value)

    ListenOnly = property(lambda s: s.__listen_only, _setListenOnly)

    #-------------------------------------------------------------------------#
    # Mode Properties
    #-------------------------------------------------------------------------#
    def _setMode(self, mode):
        ''' This toggles the current serial mode

        :param mode: The data transfer method in (RTU, ASCII)
        '''
        if mode in ['ASCII', 'RTU']:
            self.__mode = mode

    Mode = property(lambda s: s.__mode, _setMode)

    #-------------------------------------------------------------------------#
    # Delimiter Properties
    #-------------------------------------------------------------------------#
    def _setDelimiter(self, char):
        ''' This changes the serial delimiter character

        :param char: The new serial delimiter character
        '''
        if isinstance(char, str):
            self.__delimiter = char.encode()
        if isinstance(char, bytes):
            self.__delimiter = char
        elif isinstance(char, int):
            self.__delimiter = int2byte(char)

    Delimiter = property(lambda s: s.__delimiter, _setDelimiter)

    #-------------------------------------------------------------------------#
    # Diagnostic Properties
    #-------------------------------------------------------------------------#
    def setDiagnostic(self, mapping):
        ''' This sets the value in the diagnostic register

        :param mapping: Dictionary of key:value pairs to set
        '''
        for entry in iteritems(mapping):
            if entry[0] >= 0 and entry[0] < len(self.__diagnostic):
                self.__diagnostic[entry[0]] = (entry[1] != 0)

    def getDiagnostic(self, bit):
        ''' This gets the value in the diagnostic register

        :param bit: The bit to get
        :returns: The current value of the requested bit
        '''
        try:
            if bit and bit >= 0 and bit < len(self.__diagnostic):
                return self.__diagnostic[bit]
        except Exception:
            return None

    def getDiagnosticRegister(self):
        ''' This gets the entire diagnostic register

        :returns: The diagnostic register collection
        '''
        return self.__diagnostic

#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
__all__ = [
        "ModbusAccessControl",
        "ModbusPlusStatistics",
        "ModbusDeviceIdentification",
        "DeviceInformationFactory",
        "ModbusControlBlock"
]
pymodbus-1.3.2/pymodbus/datastore/0000755000175000017500000000000013150360615015343 5ustar  wmbwmbpymodbus-1.3.2/pymodbus/datastore/__init__.py0000644000175000017500000000103013150360615017446 0ustar  wmbwmbfrom pymodbus.datastore.store import ModbusSequentialDataBlock
from pymodbus.datastore.store import ModbusSparseDataBlock
from pymodbus.datastore.context import ModbusSlaveContext
from pymodbus.datastore.context import ModbusServerContext

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ModbusSequentialDataBlock", "ModbusSparseDataBlock",
    "ModbusSlaveContext", "ModbusServerContext",
]
pymodbus-1.3.2/pymodbus/datastore/store.py0000644000175000017500000002113413150360615017052 0ustar  wmbwmb"""
Modbus Server Datastore
-------------------------

For each server, you will create a ModbusServerContext and pass
in the default address space for each data access.  The class
will create and manage the data.

Further modification of said data accesses should be performed
with [get,set][access]Values(address, count)

Datastore Implementation
-------------------------

There are two ways that the server datastore can be implemented.
The first is a complete range from 'address' start to 'count'
number of indecies.  This can be thought of as a straight array::

    data = range(1, 1 + count)
    [1,2,3,...,count]

The other way that the datastore can be implemented (and how
many devices implement it) is a associate-array::

    data = {1:'1', 3:'3', ..., count:'count'}
    [1,3,...,count]

The difference between the two is that the latter will allow
arbitrary gaps in its datastore while the former will not.
This is seen quite commonly in some modbus implementations.
What follows is a clear example from the field:

Say a company makes two devices to monitor power usage on a rack.
One works with three-phase and the other with a single phase. The
company will dictate a modbus data mapping such that registers::

    n:      phase 1 power
    n+1:    phase 2 power
    n+2:    phase 3 power

Using this, layout, the first device will implement n, n+1, and n+2,
however, the second device may set the latter two values to 0 or
will simply not implmented the registers thus causing a single read
or a range read to fail.

I have both methods implemented, and leave it up to the user to change
based on their preference.
"""
from pymodbus.exceptions import NotImplementedException, ParameterException
from pymodbus.compat import iteritems, iterkeys, itervalues, get_next

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Datablock Storage
#---------------------------------------------------------------------------#
class BaseModbusDataBlock(object):
    '''
    Base class for a modbus datastore

    Derived classes must create the following fields:
            @address The starting address point
            @defult_value The default value of the datastore
            @values The actual datastore values

    Derived classes must implemented the following methods:
            validate(self, address, count=1)
            getValues(self, address, count=1)
            setValues(self, address, values)
    '''

    def default(self, count, value=False):
        ''' Used to initialize a store to one value

        :param count: The number of fields to set
        :param value: The default value to set to the fields
        '''
        self.default_value = value
        self.values = [self.default_value] * count
        self.address = 0x00

    def reset(self):
        ''' Resets the datastore to the initialized default value '''
        self.values = [self.default_value] * len(self.values)

    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
        '''
        raise NotImplementedException("Datastore Address Check")

    def getValues(self, address, count=1):
        ''' Returns the requested values from the datastore

        :param address: The starting address
        :param count: The number of values to retrieve
        :returns: The requested values from a:a+c
        '''
        raise NotImplementedException("Datastore Value Retrieve")

    def setValues(self, address, values):
        ''' Returns the requested values from the datastore

        :param address: The starting address
        :param values: The values to store
        '''
        raise NotImplementedException("Datastore Value Retrieve")

    def __str__(self):
        ''' Build a representation of the datastore

        :returns: A string representation of the datastore
        '''
        return "DataStore(%d, %d)" % (len(self.values), self.default_value)

    def __iter__(self):
        ''' Iterater over the data block data

        :returns: An iterator of the data block data
        '''
        if isinstance(self.values, dict):
            return iteritems(self.values)
        return enumerate(self.values, self.address)


class ModbusSequentialDataBlock(BaseModbusDataBlock):
    ''' Creates a sequential modbus datastore '''

    def __init__(self, address, values):
        ''' Initializes the datastore

        :param address: The starting address of the datastore
        :param values: Either a list or a dictionary of values
        '''
        self.address = address
        if hasattr(values, '__iter__'):
            self.values = list(values)
        else: self.values = [values]
        self.default_value = self.values[0].__class__()

    @classmethod
    def create(klass):
        ''' Factory method to create a datastore with the
        full address space initialized to 0x00

        :returns: An initialized datastore
        '''
        return klass(0x00, [0x00] * 65536)

    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
        '''
        result  = (self.address <= address)
        result &= ((self.address + len(self.values)) >= (address + count))
        return result

    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
        '''
        start = address - self.address
        return self.values[start:start + 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
        '''
        if not isinstance(values, list):
            values = [values]
        start = address - self.address
        self.values[start:start + len(values)] = values


class ModbusSparseDataBlock(BaseModbusDataBlock):
    ''' Creates a sparse modbus datastore '''

    def __init__(self, values):
        ''' Initializes the datastore

        Using the input values we create the default
        datastore value and the starting address

        :param values: Either a list or a dictionary of values
        '''
        if isinstance(values, dict):
            self.values = values
        elif hasattr(values, '__iter__'):
            self.values = dict(enumerate(values))
        else: raise ParameterException(
            "Values for datastore must be a list or dictionary")
        self.default_value = get_next(itervalues(self.values)).__class__()
        self.address = get_next(iterkeys(self.values))

    @classmethod
    def create(klass):
        ''' Factory method to create a datastore with the
        full address space initialized to 0x00

        :returns: An initialized datastore
        '''
        return klass([0x00] * 65536)

    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
        '''
        if count == 0: return False
        handle = set(range(address, address + count))
        return handle.issubset(set(iterkeys(self.values)))

    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
        '''
        return [self.values[i] for i in range(address, 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
        '''
        if isinstance(values, dict):
            for idx, val in iteritems(values):
                self.values[idx] = val
        else:
            if not isinstance(values, list):
                values = [values]
            for idx, val in enumerate(values):
                self.values[address + idx] = val
pymodbus-1.3.2/pymodbus/datastore/context.py0000644000175000017500000001343213150360615017404 0ustar  wmbwmbfrom pymodbus.exceptions import ParameterException, NoSuchSlaveException
from pymodbus.interfaces import IModbusSlaveContext
from pymodbus.datastore.store import ModbusSequentialDataBlock
from pymodbus.constants import Defaults
from pymodbus.compat import iteritems, itervalues

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Slave Contexts
#---------------------------------------------------------------------------#
class ModbusSlaveContext(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, defaults to fully populated
        sequential data blocks if none are passed in.

        :param kwargs: Each element is a ModbusDataBlock

            'di' - Discrete Inputs initializer
            'co' - Coils initializer
            'hr' - Holding Register initializer
            'ir' - Input Registers iniatializer
        '''
        self.store = {}
        self.store['d'] = kwargs.get('di', ModbusSequentialDataBlock.create())
        self.store['c'] = kwargs.get('co', ModbusSequentialDataBlock.create())
        self.store['i'] = kwargs.get('ir', ModbusSequentialDataBlock.create())
        self.store['h'] = kwargs.get('hr', ModbusSequentialDataBlock.create())
        self.zero_mode  = kwargs.get('zero_mode', Defaults.ZeroMode)

    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 '''
        for datastore in itervalues(self.store):
            datastore.reset()

    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
        '''
        if not self.zero_mode: address = address + 1
        _logger.debug("validate[%d] %d:%d" % (fx, address, count))
        return self.store[self.decode(fx)].validate(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
        '''
        if not self.zero_mode: address = address + 1
        _logger.debug("getValues[%d] %d:%d" % (fx, address, count))
        return self.store[self.decode(fx)].getValues(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
        '''
        if not self.zero_mode: address = address + 1
        _logger.debug("setValues[%d] %d:%d" % (fx, address, len(values)))
        self.store[self.decode(fx)].setValues(address, values)


class ModbusServerContext(object):
    ''' This represents a master collection of slave contexts.
    If single is set to true, it will be treated as a single
    context so every unit-id returns the same context. If single
    is set to false, it will be interpreted as a collection of
    slave contexts.
    '''

    def __init__(self, slaves=None, single=True):
        ''' Initializes a new instance of a modbus server context.

        :param slaves: A dictionary of client contexts
        :param single: Set to true to treat this as a single context
        '''
        self.single   = single
        self.__slaves = slaves or {}
        if self.single:
            self.__slaves = {Defaults.UnitId: self.__slaves}

    def __iter__(self):
        ''' Iterater over the current collection of slave
        contexts.

        :returns: An iterator over the slave contexts
        '''
        return iteritems(self.__slaves)

    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
        '''
        return slave in self.__slaves

    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
        '''
        if self.single: slave = Defaults.UnitId
        if 0xf7 >= slave >= 0x00:
            self.__slaves[slave] = context
        else:
            raise NoSuchSlaveException('slave index :{} out of range'.format(slave))

    def __delitem__(self, slave):
        ''' Wrapper used to access the slave context

        :param slave: The slave context to remove
        '''
        if not self.single and (0xf7 >= slave >= 0x00):
            del self.__slaves[slave]
        else:
            raise NoSuchSlaveException('slave index: {} out of range'.format(slave))

    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 self.single: slave = Defaults.UnitId
        if slave in self.__slaves:
            return self.__slaves.get(slave)
        else:
            raise NoSuchSlaveException("slave - {} does not exist, or is out of range".format(slave))
pymodbus-1.3.2/pymodbus/datastore/remote.py0000644000175000017500000000736013150360615017216 0ustar  wmbwmbfrom pymodbus.exceptions import NotImplementedException
from pymodbus.interfaces import IModbusSlaveContext

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Context
#---------------------------------------------------------------------------#
class RemoteSlaveContext(IModbusSlaveContext):
    ''' TODO
    This creates a modbus data model that connects to
    a remote device (depending on the client used)
    '''

    def __init__(self, client):
        ''' Initializes the datastores

        :param client: The client to retrieve values with
        '''
        self._client = client
        self.__build_mapping()

    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 = self.__get_callbacks[self.decode(fx)](address, count)
        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
        '''
        # TODO deal with deferreds
        _logger.debug("get values[%d] %d:%d" % (fx, address, count))
        result = self.__get_callbacks[self.decode(fx)](address, count)
        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
        '''
        # TODO deal with deferreds
        _logger.debug("set values[%d] %d:%d" % (fx, address, len(values)))
        self.__set_callbacks[self.decode(fx)](address, values)

    def __str__(self):
        ''' Returns a string representation of the context

        :returns: A string representation of the context
        '''
        return "Remote Slave Context(%s)" % self._client

    def __build_mapping(self):
        '''
        A quick helper method to build the function
        code mapper.
        '''
        self.__get_callbacks = {
            'd': lambda a, c: self._client.read_discrete_inputs(a, c),
            'c': lambda a, c: self._client.read_coils(a, c),
            'h': lambda a, c: self._client.read_holding_registers(a, c),
            'i': lambda a, c: self._client.read_input_registers(a, c),
        }
        self.__set_callbacks = {
            'd': lambda a, v: self._client.write_coils(a, v),
            'c': lambda a, v: self._client.write_coils(a, v),
            'h': lambda a, v: self._client.write_registers(a, v),
            'i': lambda a, v: self._client.write_registers(a, v),
        }

    def __extract_result(self, fx, result):
        ''' A helper method to extract the values out of
        a response.  TODO make this consistent (values?)
        '''
        if result.function_code < 0x80:
            if fx in ['d', 'c']: return result.bits
            if fx in ['h', 'i']: return result.registers
        else: return result
pymodbus-1.3.2/pymodbus/bit_read_message.py0000644000175000017500000002060613150360615017210 0ustar  wmbwmb"""
Bit Reading Request/Response messages
--------------------------------------

"""
import struct
from pymodbus.pdu import ModbusRequest
from pymodbus.pdu import ModbusResponse
from pymodbus.pdu import ModbusExceptions as merror
from pymodbus.utilities import pack_bitstring, unpack_bitstring
from pymodbus.compat import byte2int


class ReadBitsRequestBase(ModbusRequest):
    ''' Base class for Messages Requesting bit values '''

    _rtu_frame_size = 8

    def __init__(self, address, count, **kwargs):
        ''' Initializes the read request data

        :param address: The start address to read from
        :param count: The number of bits after 'address' to read
        '''
        ModbusRequest.__init__(self, **kwargs)
        self.address = address
        self.count = count

    def encode(self):
        ''' Encodes a request pdu

        :returns: The encoded pdu
        '''
        return struct.pack('>HH', self.address, self.count)

    def decode(self, data):
        ''' Decodes a request pdu

        :param data: The packet data to decode
        '''
        self.address, self.count = struct.unpack('>HH', data)
    
    def get_response_pdu_size(self):
        """
        Func_code (1 byte) + Byte Count(1 byte) + Quantity of Coils (n Bytes)/8,
        if the remainder is different of 0 then N = N+1
        :return: 
        """
        count = self.count//8
        if self.count % 8:
            count += 1

        return 1 + 1 + count
    
    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        return "ReadBitRequest(%d,%d)" % (self.address, self.count)


class ReadBitsResponseBase(ModbusResponse):
    ''' Base class for Messages responding to bit-reading values '''

    _rtu_byte_count_pos = 2

    def __init__(self, values, **kwargs):
        ''' Initializes a new instance

        :param values: The requested values to be returned
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.bits = values or []

    def encode(self):
        ''' Encodes response pdu

        :returns: The encoded packet message
        '''
        result = pack_bitstring(self.bits)
        packet = struct.pack(">B", len(result)) + result
        return packet

    def decode(self, data):
        ''' Decodes response pdu

        :param data: The packet data to decode
        '''
        self.byte_count = byte2int(data[0])
        self.bits = unpack_bitstring(data[1:])

    def setBit(self, address, value=1):
        ''' Helper function to set the specified bit

        :param address: The bit to set
        :param value: The value to set the bit to
        '''
        self.bits[address] = (value != 0)

    def resetBit(self, address):
        ''' Helper function to set the specified bit to 0

        :param address: The bit to reset
        '''
        self.setBit(address, 0)

    def getBit(self, address):
        ''' Helper function to get the specified bit's value

        :param address: The bit to query
        :returns: The value of the requested bit
        '''
        return self.bits[address]

    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        return "ReadBitResponse(%d)" % len(self.bits)


class ReadCoilsRequest(ReadBitsRequestBase):
    '''
    This function code is used to read from 1 to 2000(0x7d0) contiguous status
    of coils in a remote device. The Request PDU specifies the starting
    address, ie the address of the first coil specified, and the number of
    coils. In the PDU Coils are addressed starting at zero. Therefore coils
    numbered 1-16 are addressed as 0-15.
    '''
    function_code = 1

    def __init__(self, address=None, count=None, **kwargs):
        ''' Initializes a new instance

        :param address: The address to start reading from
        :param count: The number of bits to read
        '''
        ReadBitsRequestBase.__init__(self, address, count, **kwargs)

    def execute(self, context):
        ''' Run a read coils request against a datastore

        Before running the request, we make sure that the request is in
        the max valid range (0x001-0x7d0). Next we make sure that the
        request is valid against the current datastore.

        :param context: The datastore to request from
        :returns: The initializes response message, exception message otherwise
        '''
        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 ReadCoilsResponse(values)


class ReadCoilsResponse(ReadBitsResponseBase):
    '''
    The coils in the response message are packed as one coil per bit of
    the data field. Status is indicated as 1= ON and 0= OFF. The LSB of the
    first data byte contains the output addressed in the query. The other
    coils follow toward the high order end of this byte, and from low order
    to high order in subsequent bytes.

    If the returned output quantity is not a multiple of eight, the
    remaining bits in the final data byte will be padded with zeros
    (toward the high order end of the byte). The Byte Count field specifies
    the quantity of complete bytes of data.
    '''
    function_code = 1

    def __init__(self, values=None, **kwargs):
        ''' Intializes a new instance

        :param values: The request values to respond with
        '''
        ReadBitsResponseBase.__init__(self, values, **kwargs)


class ReadDiscreteInputsRequest(ReadBitsRequestBase):
    '''
    This function code is used to read from 1 to 2000(0x7d0) contiguous status
    of discrete inputs in a remote device. The Request PDU specifies the
    starting address, ie the address of the first input specified, and the
    number of inputs. In the PDU Discrete Inputs are addressed starting at
    zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15.
    '''
    function_code = 2

    def __init__(self, address=None, count=None, **kwargs):
        ''' Intializes a new instance

        :param address: The address to start reading from
        :param count: The number of bits to read
        '''
        ReadBitsRequestBase.__init__(self, address, count, **kwargs)

    def execute(self, context):
        ''' Run a read discrete input request against a datastore

        Before running the request, we make sure that the request is in
        the max valid range (0x001-0x7d0). Next we make sure that the
        request is valid against the current datastore.

        :param context: The datastore to request from
        :returns: The initializes response message, exception message otherwise
        '''
        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 ReadDiscreteInputsResponse(values)


class ReadDiscreteInputsResponse(ReadBitsResponseBase):
    '''
    The discrete inputs in the response message are packed as one input per
    bit of the data field. Status is indicated as 1= ON; 0= OFF. The LSB of
    the first data byte contains the input addressed in the query. The other
    inputs follow toward the high order end of this byte, and from low order
    to high order in subsequent bytes.

    If the returned input quantity is not a multiple of eight, the
    remaining bits in the final data byte will be padded with zeros
    (toward the high order end of the byte). The Byte Count field specifies
    the quantity of complete bytes of data.
    '''
    function_code = 2

    def __init__(self, values=None, **kwargs):
        ''' Intializes a new instance

        :param values: The request values to respond with
        '''
        ReadBitsResponseBase.__init__(self, values, **kwargs)

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ReadCoilsRequest", "ReadCoilsResponse",
    "ReadDiscreteInputsRequest", "ReadDiscreteInputsResponse",
]
pymodbus-1.3.2/pymodbus/register_write_message.py0000644000175000017500000002712713150360615020502 0ustar  wmbwmb'''
Register Writing Request/Response Messages
-------------------------------------------
'''
import struct
from pymodbus.pdu import ModbusRequest
from pymodbus.pdu import ModbusResponse
from pymodbus.pdu import ModbusExceptions as merror


class WriteSingleRegisterRequest(ModbusRequest):
    '''
    This function code is used to write a single holding register in a
    remote device.

    The Request PDU specifies the address of the register to
    be written. Registers are addressed starting at zero. Therefore register
    numbered 1 is addressed as 0.
    '''
    function_code = 6
    _rtu_frame_size = 8
    
    def __init__(self, address=None, value=None, **kwargs):
        ''' Initializes a new instance

        :param address: The address to start writing add
        :param value: The values to write
        '''
        ModbusRequest.__init__(self, **kwargs)
        self.address = address
        self.value = value

    def encode(self):
        ''' Encode a write single register packet packet request

        :returns: The encoded packet
        '''
        packet = struct.pack('>H', self.address)
        if self.skip_encode:
            packet += self.value
        else:
            packet += struct.pack('>H', self.value)
        return packet

    def decode(self, data):
        ''' Decode a write single register packet packet request

        :param data: The request to decode
        '''
        self.address, self.value = struct.unpack('>HH', data)
    
    def execute(self, context):
        ''' Run a write single register request against a datastore

        :param context: The datastore to request from
        :returns: An initialized response, exception message otherwise
        '''
        if not (0 <= self.value <= 0xffff):
            return self.doException(merror.IllegalValue)
        if not context.validate(self.function_code, self.address, 1):
            return self.doException(merror.IllegalAddress)

        context.setValues(self.function_code, self.address, [self.value])
        values = context.getValues(self.function_code, self.address, 1)
        return WriteSingleRegisterResponse(self.address, values[0])

    def get_response_pdu_size(self):
        """
        Func_code (1 byte) + Register Address(2 byte) + Register Value (2 bytes)
        :return: 
        """
        return 1 + 2 + 2
    
    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        return "WriteRegisterRequest %d" % self.address


class WriteSingleRegisterResponse(ModbusResponse):
    '''
    The normal response is an echo of the request, returned after the
    register contents have been written.
    '''
    function_code = 6
    _rtu_frame_size = 8

    def __init__(self, address=None, value=None, **kwargs):
        ''' Initializes a new instance

        :param address: The address to start writing add
        :param value: The values to write
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.address = address
        self.value = value

    def encode(self):
        ''' Encode a write single register packet packet request

        :returns: The encoded packet
        '''
        return struct.pack('>HH', self.address, self.value)

    def decode(self, data):
        ''' Decode a write single register packet packet request

        :param data: The request to decode
        '''
        self.address, self.value = struct.unpack('>HH', data)

    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        params = (self.address, self.value)
        return "WriteRegisterResponse %d => %d" % params


#---------------------------------------------------------------------------#
# Write Multiple Registers
#---------------------------------------------------------------------------#
class WriteMultipleRegistersRequest(ModbusRequest):
    '''
    This function code is used to write a block of contiguous registers (1
    to approx. 120 registers) in a remote device.

    The requested written values are specified in the request data field.
    Data is packed as two bytes per register.
    '''
    function_code = 16
    _rtu_byte_count_pos = 6
    _pdu_length = 5  #func + adress1 + adress2 + outputQuant1 + outputQuant2

    def __init__(self, address=None, values=None, **kwargs):
        ''' Initializes a new instance

        :param address: The address to start writing to
        :param values: The values to write
        '''
        ModbusRequest.__init__(self, **kwargs)
        self.address = address
        if values is None:
            values = []
        elif not hasattr(values, '__iter__'):
            values = [values]
        self.values = values
        self.count = len(self.values)
        self.byte_count = self.count * 2

    def encode(self):
        ''' Encode a write single register packet packet request

        :returns: The encoded packet
        '''
        packet = struct.pack('>HHB', self.address, self.count, self.byte_count)
        if self.skip_encode:
            return packet + b''.join(self.values)
        
        for value in self.values:
            packet += struct.pack('>H', value)

        return packet

    def decode(self, data):
        ''' Decode a write single register packet packet request

        :param data: The request to decode
        '''
        self.address, self.count, \
        self.byte_count = struct.unpack('>HHB', data[:5])
        self.values = []  # reset
        for idx in range(5, (self.count * 2) + 5, 2):
            self.values.append(struct.unpack('>H', data[idx:idx + 2])[0])

    def execute(self, context):
        ''' Run a write single register request against a datastore

        :param context: The datastore to request from
        :returns: An initialized response, exception message otherwise
        '''
        if not (1 <= self.count <= 0x07b):
            return self.doException(merror.IllegalValue)
        if (self.byte_count != self.count * 2):
            return self.doException(merror.IllegalValue)
        if not context.validate(self.function_code, self.address, self.count):
            return self.doException(merror.IllegalAddress)

        context.setValues(self.function_code, self.address, self.values)
        return WriteMultipleRegistersResponse(self.address, self.count)

    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        params = (self.address, self.count)
        return "WriteMultipleRegisterRequest %d => %d" % params


class WriteMultipleRegistersResponse(ModbusResponse):
    '''
    "The normal response returns the function code, starting address, and
    quantity of registers written.
    '''
    function_code = 16
    _rtu_frame_size = 8

    def __init__(self, address=None, count=None, **kwargs):
        ''' Initializes a new instance

        :param address: The address to start writing to
        :param count: The number of registers to write to
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.address = address
        self.count = count

    def encode(self):
        ''' Encode a write single register packet packet request

        :returns: The encoded packet
        '''
        return struct.pack('>HH', self.address, self.count)

    def decode(self, data):
        ''' Decode a write single register packet packet request

        :param data: The request to decode
        '''
        self.address, self.count = struct.unpack('>HH', data)

    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        params = (self.address, self.count)
        return "WriteMultipleRegisterResponse (%d,%d)" % params

class MaskWriteRegisterRequest(ModbusRequest):
    '''
    This function code is used to modify the contents of a specified holding
    register using a combination of an AND mask, an OR mask, and the
    register's current contents. The function can be used to set or clear
    individual bits in the register.
    '''
    function_code = 0x16
    _rtu_frame_size = 10

    def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000,
                 **kwargs):
        ''' Initializes a new instance

        :param address: The mask pointer address (0x0000 to 0xffff)
        :param and_mask: The and bitmask to apply to the register address
        :param or_mask: The or bitmask to apply to the register address
        '''
        ModbusRequest.__init__(self, **kwargs)
        self.address = address
        self.and_mask = and_mask
        self.or_mask = or_mask

    def encode(self):
        ''' Encodes the request packet

        :returns: The byte encoded packet
        '''
        return struct.pack('>HHH', self.address, self.and_mask,
                           self.or_mask)

    def decode(self, data):
        ''' Decodes the incoming request

        :param data: The data to decode into the address
        '''
        self.address, self.and_mask, self.or_mask = struct.unpack('>HHH',
                                                                  data)

    def execute(self, context):
        ''' Run a mask write register request against the store

        :param context: The datastore to request from
        :returns: The populated response
        '''
        if not (0x0000 <= self.and_mask <= 0xffff):
            return self.doException(merror.IllegalValue)
        if not (0x0000 <= self.or_mask <= 0xffff):
            return self.doException(merror.IllegalValue)
        if not context.validate(self.function_code, self.address, 1):
            return self.doException(merror.IllegalAddress)
        values = context.getValues(self.function_code, self.address, 1)[0]
        values = ((values & self.and_mask) | self.or_mask)
        context.setValues(self.function_code, self.address, [values])
        return MaskWriteRegisterResponse(self.address, self.and_mask,
                                         self.or_mask)


class MaskWriteRegisterResponse(ModbusResponse):
    '''
    The normal response is an echo of the request. The response is returned
    after the register has been written.
    '''
    function_code = 0x16
    _rtu_frame_size = 10

    def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000,
                 **kwargs):
        ''' Initializes a new instance

        :param address: The mask pointer address (0x0000 to 0xffff)
        :param and_mask: The and bitmask applied to the register address
        :param or_mask: The or bitmask applied to the register address
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.address = address
        self.and_mask = and_mask
        self.or_mask = or_mask

    def encode(self):
        ''' Encodes the response

        :returns: The byte encoded message
        '''
        return struct.pack('>HHH', self.address, self.and_mask,
                           self.or_mask)

    def decode(self, data):
        ''' Decodes a the response

        :param data: The packet data to decode
        '''
        self.address, self.and_mask, self.or_mask = struct.unpack('>HHH',
                                                                  data)


#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "WriteSingleRegisterRequest", "WriteSingleRegisterResponse",
    "WriteMultipleRegistersRequest", "WriteMultipleRegistersResponse",
    "MaskWriteRegisterRequest", "MaskWriteRegisterResponse"
]
pymodbus-1.3.2/pymodbus/internal/0000755000175000017500000000000013150360615015171 5ustar  wmbwmbpymodbus-1.3.2/pymodbus/internal/__init__.py0000644000175000017500000000000013150360615017270 0ustar  wmbwmbpymodbus-1.3.2/pymodbus/internal/ptwisted.py0000644000175000017500000000272013150360615017407 0ustar  wmbwmb'''
A collection of twisted utility code
'''
from pymodbus.compat import IS_PYTHON2, IS_PYTHON3
if IS_PYTHON2:
    from twisted.cred import portal, checkers
    from twisted.conch import manhole, manhole_ssh
    from twisted.conch.insults import insults

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Twisted Helper Methods
#---------------------------------------------------------------------------#
def InstallManagementConsole(namespace, users={'admin': 'admin'}, port=503):
    ''' Helper method to start an ssh management console
        for the modbus server.

    :param namespace: The data to constrain the server to
    :param users: The users to login with
    :param port: The port to host the server on
    '''
    if IS_PYTHON3:
        raise NotImplemented("This code currently doesn't work on python3")
    from twisted.internet import reactor

    def build_protocol():
        p = insults.ServerProtocol(manhole.ColoredManhole, namespace)
        return p

    r = manhole_ssh.TerminalRealm()
    r.chainedProtocolFactory = build_protocol
    c = checkers.InMemoryUsernamePasswordDatabaseDontUse(**users)
    p = portal.Portal(r, [c])
    factory = manhole_ssh.ConchFactory(p)
    reactor.listenTCP(port, factory)

pymodbus-1.3.2/pymodbus/other_message.py0000644000175000017500000003440313150360615016560 0ustar  wmbwmb'''
Diagnostic record read/write

Currently not all implemented
'''
import struct
from pymodbus.constants import ModbusStatus
from pymodbus.pdu import ModbusRequest
from pymodbus.pdu import ModbusResponse
from pymodbus.device import ModbusControlBlock
from pymodbus.compat import byte2int, int2byte

_MCB = ModbusControlBlock()


#---------------------------------------------------------------------------#
# TODO Make these only work on serial
#---------------------------------------------------------------------------#
class ReadExceptionStatusRequest(ModbusRequest):
    '''
    This function code is used to read the contents of eight Exception Status
    outputs in a remote device.  The function provides a simple method for
    accessing this information, because the Exception Output references are
    known (no output reference is needed in the function).
    '''
    function_code = 0x07
    _rtu_frame_size = 4

    def __init__(self, **kwargs):
        ''' Initializes a new instance
        '''
        ModbusRequest.__init__(self, **kwargs)

    def encode(self):
        ''' Encodes the message
        '''
        return b''

    def decode(self, data):
        ''' Decodes data part of the message.

        :param data: The incoming data
        '''
        pass

    def execute(self, context=None):
        ''' Run a read exeception status request against the store

        :returns: The populated response
        '''
        status = _MCB.Counter.summary()
        return ReadExceptionStatusResponse(status)

    def __str__(self):
        ''' Builds a representation of the request

        :returns: The string representation of the request
        '''
        return "ReadExceptionStatusRequest(%d)" % (self.function_code)


class ReadExceptionStatusResponse(ModbusResponse):
    '''
    The normal response contains the status of the eight Exception Status
    outputs. The outputs are packed into one data byte, with one bit
    per output. The status of the lowest output reference is contained
    in the least significant bit of the byte.  The contents of the eight
    Exception Status outputs are device specific.
    '''
    function_code = 0x07
    _rtu_frame_size = 5

    def __init__(self, status=0x00, **kwargs):
        ''' Initializes a new instance

        :param status: The status response to report
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.status = status

    def encode(self):
        ''' Encodes the response

        :returns: The byte encoded message
        '''
        return struct.pack('>B', self.status)

    def decode(self, data):
        ''' Decodes a the response

        :param data: The packet data to decode
        '''
        self.status = byte2int(data[0])

    def __str__(self):
        ''' Builds a representation of the response

        :returns: The string representation of the response
        '''
        arguments = (self.function_code, self.status)
        return "ReadExceptionStatusResponse(%d, %s)" % arguments

# Encapsulate interface transport 43, 14
# CANopen general reference 43, 13


#---------------------------------------------------------------------------#
# TODO Make these only work on serial
#---------------------------------------------------------------------------#
class GetCommEventCounterRequest(ModbusRequest):
    '''
    This function code is used to get a status word and an event count from
    the remote device's communication event counter.

    By fetching the current count before and after a series of messages, a
    client can determine whether the messages were handled normally by the
    remote device.

    The device's event counter is incremented once  for each successful
    message completion. It is not incremented for exception responses,
    poll commands, or fetch event counter commands.

    The event counter can be reset by means of the Diagnostics function
    (code 08), with a subfunction of Restart Communications Option
    (code 00 01) or Clear Counters and Diagnostic Register (code 00 0A).
    '''
    function_code = 0x0b
    _rtu_frame_size = 4

    def __init__(self, **kwargs):
        ''' Initializes a new instance
        '''
        ModbusRequest.__init__(self, **kwargs)

    def encode(self):
        ''' Encodes the message
        '''
        return b''

    def decode(self, data):
        ''' Decodes data part of the message.

        :param data: The incoming data
        '''
        pass

    def execute(self, context=None):
        ''' Run a read exeception status request against the store

        :returns: The populated response
        '''
        status = _MCB.Counter.Event
        return GetCommEventCounterResponse(status)

    def __str__(self):
        ''' Builds a representation of the request

        :returns: The string representation of the request
        '''
        return "GetCommEventCounterRequest(%d)" % (self.function_code)


class GetCommEventCounterResponse(ModbusResponse):
    '''
    The normal response contains a two-byte status word, and a two-byte
    event count. The status word will be all ones (FF FF hex) if a
    previously-issued program command is still being processed by the
    remote device (a busy condition exists). Otherwise, the status word
    will be all zeros.
    '''
    function_code = 0x0b
    _rtu_frame_size = 8

    def __init__(self, count=0x0000, **kwargs):
        ''' Initializes a new instance

        :param count: The current event counter value
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.count = count
        self.status = True  # this means we are ready, not waiting

    def encode(self):
        ''' Encodes the response

        :returns: The byte encoded message
        '''
        if self.status: ready = ModbusStatus.Ready
        else: ready = ModbusStatus.Waiting
        return struct.pack('>HH', ready, self.count)

    def decode(self, data):
        ''' Decodes a the response

        :param data: The packet data to decode
        '''
        ready, self.count = struct.unpack('>HH', data)
        self.status = (ready == ModbusStatus.Ready)

    def __str__(self):
        ''' Builds a representation of the response

        :returns: The string representation of the response
        '''
        arguments = (self.function_code, self.count, self.status)
        return "GetCommEventCounterResponse(%d, %d, %d)" % arguments


#---------------------------------------------------------------------------#
# TODO Make these only work on serial
#---------------------------------------------------------------------------#
class GetCommEventLogRequest(ModbusRequest):
    '''
    This function code is used to get a status word, event count, message
    count, and a field of event bytes from the remote device.

    The status word and event counts are identical  to that returned by
    the Get Communications Event Counter function (11, 0B hex).

    The message counter contains the quantity of  messages processed by the
    remote device since its last restart, clear counters operation, or
    power-up.  This count is identical to that returned by the Diagnostic
    function (code 08), sub-function Return Bus Message Count (code 11,
    0B hex).

    The event bytes field contains 0-64 bytes, with each byte corresponding
    to the status of one MODBUS send or receive operation for the remote
    device.  The remote device enters the events into the field in
    chronological order.  Byte 0 is the most recent event. Each new byte
    flushes the oldest byte from the field.
    '''
    function_code = 0x0c
    _rtu_frame_size = 4

    def __init__(self, **kwargs):
        ''' Initializes a new instance
        '''
        ModbusRequest.__init__(self, **kwargs)

    def encode(self):
        ''' Encodes the message
        '''
        return b''

    def decode(self, data):
        ''' Decodes data part of the message.

        :param data: The incoming data
        '''
        pass

    def execute(self, context=None):
        ''' Run a read exeception status request against the store

        :returns: The populated response
        '''
        results = {
            'status'        : True,
            'message_count' : _MCB.Counter.BusMessage,
            'event_count'   : _MCB.Counter.Event,
            'events'        : _MCB.getEvents(),
        }
        return GetCommEventLogResponse(**results)

    def __str__(self):
        ''' Builds a representation of the request

        :returns: The string representation of the request
        '''
        return "GetCommEventLogRequest(%d)" % self.function_code


class GetCommEventLogResponse(ModbusResponse):
    '''
    The normal response contains a two-byte status word field,
    a two-byte event count field, a two-byte message count field,
    and a field containing 0-64 bytes of events. A byte count
    field defines the total length of the data in these four field
    '''
    function_code = 0x0c
    _rtu_byte_count_pos = 3

    def __init__(self, **kwargs):
        ''' Initializes a new instance

        :param status: The status response to report
        :param message_count: The current message count
        :param event_count: The current event count
        :param events: The collection of events to send
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.status = kwargs.get('status', True)
        self.message_count = kwargs.get('message_count', 0)
        self.event_count = kwargs.get('event_count', 0)
        self.events = kwargs.get('events', [])

    def encode(self):
        ''' Encodes the response

        :returns: The byte encoded message
        '''
        if self.status: ready = ModbusStatus.Ready
        else: ready = ModbusStatus.Waiting
        packet  = struct.pack('>B', 6 + len(self.events))
        packet += struct.pack('>H', ready)
        packet += struct.pack('>HH', self.event_count, self.message_count)
        packet += b''.join(struct.pack('>B', e) for e in self.events)
        return packet

    def decode(self, data):
        ''' Decodes a the response

        :param data: The packet data to decode
        '''
        length = byte2int(data[0])
        status = struct.unpack('>H', data[1:3])[0]
        self.status = (status == ModbusStatus.Ready)
        self.event_count = struct.unpack('>H', data[3:5])[0]
        self.message_count = struct.unpack('>H', data[5:7])[0]

        self.events = []
        for e in range(7, length + 1):
            self.events.append(byte2int(data[e]))

    def __str__(self):
        ''' Builds a representation of the response

        :returns: The string representation of the response
        '''
        arguments = (self.function_code, self.status, self.message_count, self.event_count)
        return "GetCommEventLogResponse(%d, %d, %d, %d)" % arguments


#---------------------------------------------------------------------------#
# TODO Make these only work on serial
#---------------------------------------------------------------------------#
class ReportSlaveIdRequest(ModbusRequest):
    '''
    This function code is used to read the description of the type, the
    current status, and other information specific to a remote device.
    '''
    function_code = 0x11
    _rtu_frame_size = 4

    def __init__(self, **kwargs):
        ''' Initializes a new instance
        '''
        ModbusRequest.__init__(self, **kwargs)

    def encode(self):
        ''' Encodes the message
        '''
        return b''

    def decode(self, data):
        ''' Decodes data part of the message.

        :param data: The incoming data
        '''
        pass

    def execute(self, context=None):
        ''' Run a read exeception status request against the store

        :returns: The populated response
        '''
        identifier = b'Pymodbus'
        return ReportSlaveIdResponse(identifier)

    def __str__(self):
        ''' Builds a representation of the request

        :returns: The string representation of the request
        '''
        return "ResportSlaveIdRequest(%d)" % self.function_code


class ReportSlaveIdResponse(ModbusResponse):
    '''
    The format of a normal response is shown in the following example.
    The data contents are specific to each type of device.
    '''
    function_code = 0x11
    _rtu_byte_count_pos = 2

    def __init__(self, identifier=b'\x00', status=True, **kwargs):
        ''' Initializes a new instance

        :param identifier: The identifier of the slave
        :param status: The status response to report
        '''
        ModbusResponse.__init__(self, **kwargs)
        self.identifier = identifier
        self.status = status
        self.byte_count = None

    def encode(self):
        ''' Encodes the response

        :returns: The byte encoded message
        '''
        if self.status:
            status = ModbusStatus.SlaveOn
        else:
            status = ModbusStatus.SlaveOff
        length  = len(self.identifier) + 1
        packet  = int2byte(length)
        packet += self.identifier  # we assume it is already encoded
        packet += int2byte(status)
        return packet

    def decode(self, data):
        ''' Decodes a the response

        Since the identifier is device dependent, we just return the
        raw value that a user can decode to whatever it should be.

        :param data: The packet data to decode
        '''
        self.byte_count = byte2int(data[0])
        self.identifier = data[1:self.byte_count + 1]
        status = byte2int(data[-1])
        self.status = status == ModbusStatus.SlaveOn

    def __str__(self):
        ''' Builds a representation of the response

        :returns: The string representation of the response
        '''
        arguments = (self.function_code, self.identifier, self.status)
        return "ResportSlaveIdResponse(%s, %s, %s)" % arguments

#---------------------------------------------------------------------------#
# TODO Make these only work on serial
#---------------------------------------------------------------------------#
# report device identification 43, 14

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ReadExceptionStatusRequest", "ReadExceptionStatusResponse",
    "GetCommEventCounterRequest", "GetCommEventCounterResponse",
    "GetCommEventLogRequest", "GetCommEventLogResponse",
    "ReportSlaveIdRequest", "ReportSlaveIdResponse",
]
pymodbus-1.3.2/pymodbus/constants.py0000644000175000017500000001471113150360615015747 0ustar  wmbwmb'''
Constants For Modbus Server/Client
----------------------------------

This is the single location for storing default
values for the servers and clients.
'''
from pymodbus.interfaces import Singleton


class Defaults(Singleton):
    ''' A collection of modbus default values

    .. attribute:: Port

       The default modbus tcp server port (502)

    .. attribute:: Retries

       The default number of times a client should retry the given
       request before failing (3)

    .. attribute:: RetryOnEmpty

       A flag indicating if a transaction should be retried in the
       case that an empty response is received. This is useful for
       slow clients that may need more time to process a requst.

    .. attribute:: Timeout

       The default amount of time a client should wait for a request
       to be processed (3 seconds)

    .. attribute:: Reconnects

       The default number of times a client should attempt to reconnect
       before deciding the server is down (0)

    .. attribute:: TransactionId

       The starting transaction identifier number (0)

    .. attribute:: ProtocolId

       The modbus protocol id.  Currently this is set to 0 in all
       but proprietary implementations.

    .. attribute:: UnitId

       The modbus slave addrss.  Currently this is set to 0x00 which
       means this request should be broadcast to all the slave devices
       (really means that all the devices should respons).

    .. attribute:: Baudrate

       The speed at which the data is transmitted over the serial line.
       This defaults to 19200.

    .. attribute:: Parity

       The type of checksum to use to verify data integrity. This can be
       on of the following::

         - (E)ven - 1 0 1 0 | P(0)
         - (O)dd  - 1 0 1 0 | P(1)
         - (N)one - 1 0 1 0 | no parity

       This defaults to (N)one.

    .. attribute:: Bytesize

       The number of bits in a byte of serial data.  This can be one of
       5, 6, 7, or 8. This defaults to 8.

    .. attribute:: Stopbits

       The number of bits sent after each character in a message to
       indicate the end of the byte.  This defaults to 1.

    .. attribute:: ZeroMode

       Indicates if the slave datastore should use indexing at 0 or 1.
       More about this can be read in section 4.4 of the modbus specification.

    .. attribute:: IgnoreMissingSlaves

       In case a request is made to a missing slave, this defines if an error
       should be returned or simply ignored. This is useful for the case of a
       serial server emulater where a request to a non-existant slave on a bus
       will never respond. The client in this case will simply timeout.
    '''
    Port                = 502
    Retries             = 3
    RetryOnEmpty        = False
    Timeout             = 3
    Reconnects          = 0
    TransactionId       = 0
    ProtocolId          = 0
    UnitId              = 0x00
    Baudrate            = 19200
    Parity              = 'N'
    Bytesize            = 8
    Stopbits            = 1
    ZeroMode            = False
    IgnoreMissingSlaves = False


class ModbusStatus(Singleton):
    '''
    These represent various status codes in the modbus
    protocol.

    .. attribute:: Waiting

       This indicates that a modbus device is currently
       waiting for a given request to finish some running task.

    .. attribute:: Ready

       This indicates that a modbus device is currently
       free to perform the next request task.

    .. attribute:: On

       This indicates that the given modbus entity is on

    .. attribute:: Off

       This indicates that the given modbus entity is off

    .. attribute:: SlaveOn

       This indicates that the given modbus slave is running

    .. attribute:: SlaveOff

       This indicates that the given modbus slave is not running
    '''
    Waiting  = 0xffff
    Ready    = 0x0000
    On       = 0xff00
    Off      = 0x0000
    SlaveOn  = 0xff
    SlaveOff = 0x00


class Endian(Singleton):
    ''' An enumeration representing the various byte endianess.

    .. attribute:: Auto

       This indicates that the byte order is chosen by the
       current native environment.

    .. attribute:: Big

       This indicates that the bytes are in little endian format

    .. attribute:: Little

       This indicates that the bytes are in big endian format

    .. note:: I am simply borrowing the format strings from the
       python struct module for my convenience.
    '''
    Auto   = '@'
    Big    = '>'
    Little = '<'


class ModbusPlusOperation(Singleton):
    ''' Represents the type of modbus plus request

    .. attribute:: GetStatistics

       Operation requesting that the current modbus plus statistics
       be returned in the response.

    .. attribute:: ClearStatistics

       Operation requesting that the current modbus plus statistics
       be cleared and not returned in the response.
    '''
    GetStatistics   = 0x0003
    ClearStatistics = 0x0004


class DeviceInformation(Singleton):
    ''' Represents what type of device information to read

    .. attribute:: Basic

       This is the basic (required) device information to be returned.
       This includes VendorName, ProductCode, and MajorMinorRevision
       code.

    .. attribute:: Regular

       In addition to basic data objects, the device provides additional
       and optinoal identification and description data objects. All of
       the objects of this category are defined in the standard but their
       implementation is optional.

    .. attribute:: Extended

       In addition to regular data objects, the device provides additional
       and optional identification and description private data about the
       physical device itself. All of these data are device dependent.

    .. attribute:: Specific

       Request to return a single data object.
    '''
    Basic    = 0x01
    Regular  = 0x02
    Extended = 0x03
    Specific = 0x04


class MoreData(Singleton):
    ''' Represents the more follows condition

    .. attribute:: Nothing

       This indiates that no more objects are going to be returned.

    .. attribute:: KeepReading

       This indicates that there are more objects to be returned.
    '''
    Nothing     = 0x00
    KeepReading = 0xFF

#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
__all__ = [
    "Defaults", "ModbusStatus", "Endian",
    "ModbusPlusOperation",
    "DeviceInformation", "MoreData",
]
pymodbus-1.3.2/pymodbus/payload.py0000644000175000017500000002725413150360615015372 0ustar  wmbwmb'''
Modbus Payload Builders
------------------------

A collection of utilities for building and decoding
modbus messages payloads.
'''
from struct import pack, unpack
from pymodbus.interfaces import IPayloadBuilder
from pymodbus.constants import Endian
from pymodbus.utilities import pack_bitstring
from pymodbus.utilities import unpack_bitstring
from pymodbus.utilities import make_byte_string
from pymodbus.exceptions import ParameterException


class BinaryPayloadBuilder(IPayloadBuilder):
    '''
    A utility that helps build payload messages to be
    written with the various modbus messages. It really is just
    a simple wrapper around the struct module, however it saves
    time looking up the format strings. What follows is a simple
    example::

        builder = BinaryPayloadBuilder(endian=Endian.Little)
        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 binary payload data to initialize with
        :param endian: The endianess of the payload
        '''
        self._payload = payload or []
        self._endian  = endian

    def to_string(self):
        ''' Return the payload buffer as a string

        :returns: The payload buffer as a string
        '''
        return b''.join(self._payload)

    def __str__(self):
        ''' Return the payload buffer as a string

        :returns: The payload buffer as a string
        '''
        return self.to_string().decode('utf-8')

    def reset(self):
        ''' Reset the payload buffer
        '''
        self._payload = []

    def to_registers(self):
        ''' Convert the payload buffer into a register
        layout that can be used as a context block.

        :returns: The register layout to use as a block
        '''
        fstring = self._endian + 'H'
        payload = self.build()
        return [unpack(fstring, value)[0] for value in 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 = self.to_string()
        length = len(string)
        string = string + (b'\x00' * (length % 2))
        return [string[i:i+2] for i in range(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'
        self._payload.append(pack(fstring, value))

    def add_64bit_uint(self, value):
        ''' Adds a 64 bit unsigned int to the buffer

        :param value: The value to add to the buffer
        '''
        fstring = self._endian + 'Q'
        self._payload.append(pack(fstring, value))

    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'
        self._payload.append(pack(fstring, value))

    def add_64bit_int(self, value):
        ''' Adds a 64 bit signed int to the buffer

        :param value: The value to add to the buffer
        '''
        fstring = self._endian + 'q'
        self._payload.append(pack(fstring, value))

    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'
        self._payload.append(pack(fstring, value))

    def add_64bit_float(self, value):
        ''' Adds a 64 bit float(double) to the buffer

        :param value: The value to add to the buffer
        '''
        fstring = self._endian + 'd'
        self._payload.append(pack(fstring, value))

    def add_string(self, value):
        ''' Adds a string to the buffer

        :param value: The value to add to the buffer
        '''
        value = make_byte_string(value)
        fstring = self._endian + str(len(value)) + 's'
        self._payload.append(pack(fstring, value))


class BinaryPayloadDecoder(object):
    '''
    A utility that helps decode payload messages from a modbus
    reponse message.  It really is just a simple wrapper around
    the struct module, however it saves time looking up the format
    strings. What follows is a simple example::

        decoder = BinaryPayloadDecoder(payload)
        first   = decoder.decode_8bit_uint()
        second  = decoder.decode_16bit_uint()
    '''

    def __init__(self, payload, endian=Endian.Little):
        ''' Initialize a new payload decoder

        :param payload: The payload to decode with
        :param endian: The endianess of the payload
        '''
        self._payload = payload
        self._pointer = 0x00
        self._endian  = endian

    @classmethod
    def fromRegisters(klass, 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 = b''.join(pack(endian + 'H', x) for x in registers)
            return klass(payload, endian)
        raise ParameterException('Invalid collection of registers supplied')

    @classmethod
    def fromCoils(klass, 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 klass(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]
        handle = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    def decode_bits(self):
        ''' Decodes a byte worth of bits from the buffer
        '''
        self._pointer += 1
        # fstring = self._endian + 'B'
        handle = self._payload[self._pointer - 1:self._pointer]
        handle = make_byte_string(handle)
        return unpack_bitstring(handle)

    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]
        handle = make_byte_string(handle)
        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 = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    def decode_64bit_uint(self):
        ''' Decodes a 64 bit unsigned int from the buffer
        '''
        self._pointer += 8
        fstring = self._endian + 'Q'
        handle = self._payload[self._pointer - 8:self._pointer]
        handle = make_byte_string(handle)
        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]
        handle = make_byte_string(handle)
        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]
        handle = make_byte_string(handle)
        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 = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    def decode_64bit_int(self):
        ''' Decodes a 64 bit signed int from the buffer
        '''
        self._pointer += 8
        fstring = self._endian + 'q'
        handle = self._payload[self._pointer - 8:self._pointer]
        handle = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    def decode_32bit_float(self):
        ''' Decodes a 32 bit float from the buffer
        '''
        self._pointer += 4
        fstring = self._endian + 'f'
        handle = self._payload[self._pointer - 4:self._pointer]
        handle = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    def decode_64bit_float(self):
        ''' Decodes a 64 bit float(double) from the buffer
        '''
        self._pointer += 8
        fstring = self._endian + 'd'
        handle = self._payload[self._pointer - 8:self._pointer]
        handle = make_byte_string(handle)
        return unpack(fstring, handle)[0]

    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]

    def skip_bytes(self, nbytes):
        ''' Skip n bytes in the buffer

        :param nbytes: The number of bytes to skip
        '''
        self._pointer += nbytes
        return None

#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
__all__ = ["BinaryPayloadBuilder", "BinaryPayloadDecoder"]
pymodbus-1.3.2/pymodbus/server/0000755000175000017500000000000013150360615014663 5ustar  wmbwmbpymodbus-1.3.2/pymodbus/server/async.py0000644000175000017500000002610113150360615016352 0ustar  wmbwmb'''
Implementation of a Twisted Modbus Server
------------------------------------------

'''
from binascii import b2a_hex
from twisted.internet import protocol
from twisted.internet.protocol import ServerFactory

from pymodbus.constants import Defaults
from pymodbus.factory import ServerDecoder
from pymodbus.datastore import ModbusServerContext
from pymodbus.device import ModbusControlBlock
from pymodbus.device import ModbusAccessControl
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.exceptions import NoSuchSlaveException
from pymodbus.transaction import ModbusSocketFramer, ModbusAsciiFramer
from pymodbus.pdu import ModbusExceptions as merror
from pymodbus.internal.ptwisted import InstallManagementConsole
from pymodbus.compat import byte2int
from twisted.internet import reactor

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Modbus TCP Server
#---------------------------------------------------------------------------#
class ModbusTcpProtocol(protocol.Protocol):
    ''' Implements a modbus server in twisted '''

    def connectionMade(self):
        ''' Callback for when a client connects

        ..note:: since the protocol factory cannot be accessed from the
                 protocol __init__, the client connection made is essentially
                 our __init__ method.
        '''
        _logger.debug("Client Connected [%s]" % self.transport.getHost())
        self.framer = self.factory.framer(decoder=self.factory.decoder)

    def connectionLost(self, reason):
        ''' Callback for when a client disconnects

        :param reason: The client's reason for disconnecting
        '''
        _logger.debug("Client Disconnected: %s" % reason)

    def dataReceived(self, data):
        ''' Callback when we receive any data

        :param data: The data sent by the client
        '''
        if _logger.isEnabledFor(logging.DEBUG):
            _logger.debug(' '.join([hex(byte2int(x)) for x in data]))
        if not self.factory.control.ListenOnly:
            self.framer.processIncomingPacket(data, self._execute)

    def _execute(self, request):
        ''' Executes the request and returns the result

        :param request: The decoded request message
        '''
        try:
            context = self.factory.store[request.unit_id]
            response = request.execute(context)
        except NoSuchSlaveException as ex:
            _logger.debug("requested slave does not exist: %s" % request.unit_id )
            if self.factory.ignore_missing_slaves:
                return # the client will simply timeout waiting for a response
            response = request.doException(merror.GatewayNoResponse)
        except Exception as ex:
            _logger.debug("Datastore unable to fulfill request: %s" % ex)
            response = request.doException(merror.SlaveFailure)
        #self.framer.populateResult(response)
        response.transaction_id = request.transaction_id
        response.unit_id = request.unit_id
        self._send(response)

    def _send(self, message):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        '''
        if message.should_respond:
            self.factory.control.Counter.BusMessage += 1
            pdu = self.framer.buildPacket(message)
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug('send: %s' % b2a_hex(pdu))
            return self.transport.write(pdu)


class ModbusServerFactory(ServerFactory):
    '''
    Builder class for a modbus server

    This also holds the server datastore so that it is
    persisted between connections
    '''

    protocol = ModbusTcpProtocol

    def __init__(self, store, framer=None, identity=None, **kwargs):
        ''' Overloaded initializer for the modbus factory

        If the identify structure is not passed in, the ModbusControlBlock
        uses its own empty structure.

        :param store: The ModbusServerContext datastore
        :param framer: The framer strategy to use
        :param identity: An optional identify structure
        :param ignore_missing_slaves: True to not send errors on a request to a missing slave
        '''
        self.decoder = ServerDecoder()
        self.framer = framer or ModbusSocketFramer
        self.store = store or ModbusServerContext()
        self.control = ModbusControlBlock()
        self.access = ModbusAccessControl()
        self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)

        if isinstance(identity, ModbusDeviceIdentification):
            self.control.Identity.update(identity)


#---------------------------------------------------------------------------#
# Modbus UDP Server
#---------------------------------------------------------------------------#
class ModbusUdpProtocol(protocol.DatagramProtocol):
    ''' Implements a modbus udp server in twisted '''

    def __init__(self, store, framer=None, identity=None, **kwargs):
        ''' Overloaded initializer for the modbus factory

        If the identify structure is not passed in, the ModbusControlBlock
        uses its own empty structure.

        :param store: The ModbusServerContext datastore
        :param framer: The framer strategy to use
        :param identity: An optional identify structure
        :param ignore_missing_slaves: True to not send errors on a request to a missing slave
        '''
        framer = framer or ModbusSocketFramer
        self.framer = framer(decoder=ServerDecoder())
        self.store = store or ModbusServerContext()
        self.control = ModbusControlBlock()
        self.access = ModbusAccessControl()
        self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)

        if isinstance(identity, ModbusDeviceIdentification):
            self.control.Identity.update(identity)

    def datagramReceived(self, data, addr):
        ''' Callback when we receive any data

        :param data: The data sent by the client
        '''
        _logger.debug("Client Connected [%s:%s]" % addr)
        if _logger.isEnabledFor(logging.DEBUG):
            _logger.debug(" ".join([hex(byte2int(x)) for x in data]))
        if not self.control.ListenOnly:
            continuation = lambda request: self._execute(request, addr)
            self.framer.processIncomingPacket(data, continuation)

    def _execute(self, request, addr):
        ''' Executes the request and returns the result

        :param request: The decoded request message
        '''
        try:
            context = self.store[request.unit_id]
            response = request.execute(context)
        except NoSuchSlaveException as ex:
            _logger.debug("requested slave does not exist: %s" % request.unit_id )
            if self.ignore_missing_slaves:
                return # the client will simply timeout waiting for a response
            response = request.doException(merror.GatewayNoResponse)
        except Exception as ex:
            _logger.debug("Datastore unable to fulfill request: %s" % ex)
            response = request.doException(merror.SlaveFailure)
        #self.framer.populateResult(response)
        response.transaction_id = request.transaction_id
        response.unit_id = request.unit_id
        self._send(response, addr)

    def _send(self, message, addr):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        :param addr: The (host, port) to send the message to
        '''
        self.control.Counter.BusMessage += 1
        pdu = self.framer.buildPacket(message)
        if _logger.isEnabledFor(logging.DEBUG):
            _logger.debug('send: %s' % b2a_hex(pdu))
        return self.transport.write(pdu, addr)


#---------------------------------------------------------------------------#
# Starting Factories
#---------------------------------------------------------------------------#
def StartTcpServer(context, identity=None, address=None, console=False, **kwargs):
    ''' Helper method to start the Modbus Async TCP server

    :param context: The server data context
    :param identify: The server identity to use (default empty)
    :param address: An optional (interface, port) to bind to.
    :param console: A flag indicating if you want the debug console
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    from twisted.internet import reactor

    address = address or ("", Defaults.Port)
    framer  = ModbusSocketFramer
    factory = ModbusServerFactory(context, framer, identity, **kwargs)
    if console:
        InstallManagementConsole({'factory': factory})

    _logger.info("Starting Modbus TCP Server on %s:%s" % address)
    reactor.listenTCP(address[1], factory, interface=address[0])
    reactor.run()


def StartUdpServer(context, identity=None, address=None, **kwargs):
    ''' Helper method to start the Modbus Async Udp server

    :param context: The server data context
    :param identify: The server identity to use (default empty)
    :param address: An optional (interface, port) to bind to.
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    from twisted.internet import reactor

    address = address or ("", Defaults.Port)
    framer  = ModbusSocketFramer
    server  = ModbusUdpProtocol(context, framer, identity, **kwargs)

    _logger.info("Starting Modbus UDP Server on %s:%s" % address)
    reactor.listenUDP(address[1], server, interface=address[0])
    reactor.run()


def StartSerialServer(context, identity=None,
    framer=ModbusAsciiFramer, **kwargs):
    ''' Helper method to start the Modbus Async Serial server

    :param context: The server data context
    :param identify: The server identity to use (default empty)
    :param framer: The framer to use (default ModbusAsciiFramer)
    :param port: The serial port to attach to
    :param baudrate: The baud rate to use for the serial device
    :param console: A flag indicating if you want the debug console
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    from twisted.internet import reactor
    from twisted.internet.serialport import SerialPort

    port = kwargs.get('port', '/dev/ttyS0')
    baudrate = kwargs.get('baudrate', Defaults.Baudrate)
    console = kwargs.get('console', False)

    _logger.info("Starting Modbus Serial Server on %s" % port)
    factory = ModbusServerFactory(context, framer, identity, **kwargs)
    if console:
        InstallManagementConsole({'factory': factory})

    protocol = factory.buildProtocol(None)
    SerialPort.getHost = lambda self: port # hack for logging
    SerialPort(protocol, port, reactor, baudrate)
    reactor.run()

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "StartTcpServer", "StartUdpServer", "StartSerialServer",
]
pymodbus-1.3.2/pymodbus/server/sync.py0000644000175000017500000005003413150360615016213 0ustar  wmbwmb'''
Implementation of a Threaded Modbus Server
------------------------------------------

'''
from binascii import b2a_hex
import serial
import socket
import traceback

from pymodbus.constants import Defaults
from pymodbus.factory import ServerDecoder
from pymodbus.datastore import ModbusServerContext
from pymodbus.device import ModbusControlBlock
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.transaction import *
from pymodbus.exceptions import NotImplementedException, NoSuchSlaveException
from pymodbus.pdu import ModbusExceptions as merror
from pymodbus.compat import socketserver, byte2int

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Protocol Handlers
#---------------------------------------------------------------------------#

class ModbusBaseRequestHandler(socketserver.BaseRequestHandler):
    ''' Implements the modbus server protocol

    This uses the socketserver.BaseRequestHandler to implement
    the client handler.
    '''

    def setup(self):
        ''' Callback for when a client connects
        '''
        _logger.debug("Client Connected [%s:%s]" % self.client_address)
        self.running = True
        self.framer = self.server.framer(self.server.decoder)
        self.server.threads.append(self)

    def finish(self):
        ''' Callback for when a client disconnects
        '''
        _logger.debug("Client Disconnected [%s:%s]" % self.client_address)
        self.server.threads.remove(self)

    def execute(self, request):
        ''' The callback to call with the resulting message

        :param request: The decoded request message
        '''
        try:
            context = self.server.context[request.unit_id]
            response = request.execute(context)
        except NoSuchSlaveException as ex:
            _logger.debug("requested slave does not exist: %s" % request.unit_id )
            if self.server.ignore_missing_slaves:
                return # the client will simply timeout waiting for a response
            response = request.doException(merror.GatewayNoResponse)
        except Exception as ex:
            _logger.debug("Datastore unable to fulfill request: %s; %s", ex, traceback.format_exc() )
            response = request.doException(merror.SlaveFailure)
        response.transaction_id = request.transaction_id
        response.unit_id = request.unit_id
        self.send(response)

    #---------------------------------------------------------------------------#
    # Base class implementations
    #---------------------------------------------------------------------------#
    def handle(self):
        ''' Callback when we receive any data
        '''
        raise NotImplementedException("Method not implemented by derived class")

    def send(self, message):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        '''
        raise NotImplementedException("Method not implemented by derived class")


class ModbusSingleRequestHandler(ModbusBaseRequestHandler):
    ''' Implements the modbus server protocol

    This uses the socketserver.BaseRequestHandler to implement
    the client handler for a single client(serial clients)
    '''

    def handle(self):
        ''' Callback when we receive any data
        '''
        while self.running:
            try:
                data = self.request.recv(1024)
                if data:
                    if _logger.isEnabledFor(logging.DEBUG):
                        _logger.debug(" ".join([hex(byte2int(x)) for x in data]))
                    self.framer.processIncomingPacket(data, self.execute)
            except Exception as msg:
                # since we only have a single socket, we cannot exit
                # Clear frame buffer
                self.framer.resetFrame()
                _logger.error("Socket error occurred %s" % msg)

    def send(self, message):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        '''
        if message.should_respond:
            #self.server.control.Counter.BusMessage += 1
            pdu = self.framer.buildPacket(message)
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug('send: %s' % b2a_hex(pdu))
            return self.request.send(pdu)


class CustomSingleRequestHandler(ModbusSingleRequestHandler):

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.running = True
        self.setup()


class ModbusConnectedRequestHandler(ModbusBaseRequestHandler):
    ''' Implements the modbus server protocol

    This uses the socketserver.BaseRequestHandler to implement
    the client handler for a connected protocol (TCP).
    '''

    def handle(self):
        '''Callback when we receive any data, until self.running becomes not True.  Blocks indefinitely
        awaiting data.  If shutdown is required, then the global socket.settimeout() may be
        used, to allow timely checking of self.running.  However, since this also affects socket
        connects, if there are outgoing socket connections used in the same program, then these will
        be prevented, if the specfied timeout is too short.  Hence, this is unreliable.

        To respond to Modbus...Server.server_close() (which clears each handler's self.running),
        derive from this class to provide an alternative handler that awakens from time to time when
        no input is available and checks self.running.  Use Modbus...Server( handler=... ) keyword
        to supply the alternative request handler class.

        '''
        reset_frame = False
        while self.running:
            try:
                data = self.request.recv(1024)
                if not data: self.running = False
                if _logger.isEnabledFor(logging.DEBUG):
                    _logger.debug(' '.join([hex(byte2int(x)) for x in data]))
                # if not self.server.control.ListenOnly:
                self.framer.processIncomingPacket(data, self.execute)
            except socket.timeout as msg:
                if _logger.isEnabledFor(logging.DEBUG):
                    _logger.debug("Socket timeout occurred %s", msg)
                reset_frame = True
            except socket.error as msg:
                _logger.error("Socket error occurred %s" % msg)
                self.running = False
            except:
                _logger.error("Socket exception occurred %s" % traceback.format_exc() )
                self.running = False
                reset_frame = True
            finally:
                if reset_frame:
                    self.framer.resetFrame()
                    reset_frame = False

    def send(self, message):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        '''
        if message.should_respond:
            #self.server.control.Counter.BusMessage += 1
            pdu = self.framer.buildPacket(message)
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug('send: %s' % b2a_hex(pdu))
            return self.request.send(pdu)


class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler):
    ''' Implements the modbus server protocol

    This uses the socketserver.BaseRequestHandler to implement
    the client handler for a disconnected protocol (UDP). The
    only difference is that we have to specify who to send the
    resulting packet data to.
    '''

    def handle(self):
        ''' Callback when we receive any data
        '''
        reset_frame = False
        while self.running:
            try:
                data, self.request = self.request
                if not data:
                    self.running = False
                if _logger.isEnabledFor(logging.DEBUG):
                    _logger.debug(' '.join([hex(byte2int(x)) for x in data]))
                # if not self.server.control.ListenOnly:
                self.framer.processIncomingPacket(data, self.execute)
            except socket.timeout: pass
            except socket.error as msg:
                _logger.error("Socket error occurred %s" % msg)
                self.running = False
                reset_frame = True
            except Exception as msg:
                _logger.error(msg)
                self.running = False
                reset_frame = True
            finally:
                if reset_frame:
                    self.framer.resetFrame()
                    reset_frame = False

    def send(self, message):
        ''' Send a request (string) to the network

        :param message: The unencoded modbus response
        '''
        if message.should_respond:
            #self.server.control.Counter.BusMessage += 1
            pdu = self.framer.buildPacket(message)
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug('send: %s' % b2a_hex(pdu))
            return self.request.sendto(pdu, self.client_address)


#---------------------------------------------------------------------------#
# Server Implementations
#---------------------------------------------------------------------------#
class ModbusTcpServer(socketserver.ThreadingTCPServer):
    '''
    A modbus threaded tcp socket server

    We inherit and overload the socket server so that we
    can control the client threads as well as have a single
    server context instance.
    '''

    def __init__(self, context, framer=None, identity=None, address=None, handler=None, **kwargs):
        ''' Overloaded initializer for the socket server

        If the identify structure is not passed in, the ModbusControlBlock
        uses its own empty structure.

        :param context: The ModbusServerContext datastore
        :param framer: The framer strategy to use
        :param identity: An optional identify structure
        :param address: An optional (interface, port) to bind to.
        :param handler: A handler for each client session; default is ModbusConnectedRequestHandler
        :param ignore_missing_slaves: True to not send errors on a request to a missing slave
        '''
        self.threads = []
        self.decoder = ServerDecoder()
        self.framer  = framer  or ModbusSocketFramer
        self.context = context or ModbusServerContext()
        self.control = ModbusControlBlock()
        self.address = address or ("", Defaults.Port)
        self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)

        if isinstance(identity, ModbusDeviceIdentification):
            self.control.Identity.update(identity)

        socketserver.ThreadingTCPServer.__init__(self,
            self.address, ModbusConnectedRequestHandler)

    def process_request(self, request, client):
        ''' Callback for connecting a new client thread

        :param request: The request to handle
        :param client: The address of the client
        '''
        _logger.debug("Started thread to serve client at " + str(client))
        socketserver.ThreadingTCPServer.process_request(self, request, client)

    def shutdown(self):
        ''' Stops the serve_forever loop.

        Overridden to signal handlers to stop.
        '''
        for thread in self.threads:
            thread.running = False
        socketserver.ThreadingTCPServer.shutdown(self)

    def server_close(self):
        ''' Callback for stopping the running server
        '''
        _logger.debug("Modbus server stopped")
        self.socket.close()
        for thread in self.threads:
            thread.running = False


class ModbusUdpServer(socketserver.ThreadingUDPServer):
    '''
    A modbus threaded udp socket server

    We inherit and overload the socket server so that we
    can control the client threads as well as have a single
    server context instance.
    '''

    def __init__(self, context, framer=None, identity=None, address=None, handler=None, **kwargs):
        ''' Overloaded initializer for the socket server

        If the identify structure is not passed in, the ModbusControlBlock
        uses its own empty structure.

        :param context: The ModbusServerContext datastore
        :param framer: The framer strategy to use
        :param identity: An optional identify structure
        :param address: An optional (interface, port) to bind to.
        :param handler: A handler for each client session; default is ModbusDisonnectedRequestHandler
        :param ignore_missing_slaves: True to not send errors on a request to a missing slave
        '''
        self.threads = []
        self.decoder = ServerDecoder()
        self.framer  = framer  or ModbusSocketFramer
        self.context = context or ModbusServerContext()
        self.control = ModbusControlBlock()
        self.address = address or ("", Defaults.Port)
        self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)

        if isinstance(identity, ModbusDeviceIdentification):
            self.control.Identity.update(identity)

        socketserver.ThreadingUDPServer.__init__(self,
            self.address, ModbusDisconnectedRequestHandler)

    def process_request(self, request, client):
        ''' Callback for connecting a new client thread

        :param request: The request to handle
        :param client: The address of the client
        '''
        packet, socket = request # TODO I might have to rewrite
        _logger.debug("Started thread to serve client at " + str(client))
        socketserver.ThreadingUDPServer.process_request(self, request, client)

    def server_close(self):
        ''' Callback for stopping the running server
        '''
        _logger.debug("Modbus server stopped")
        self.socket.close()
        for thread in self.threads:
            thread.running = False


class ModbusSerialServer(object):
    '''
    A modbus threaded serial socket server

    We inherit and overload the socket server so that we
    can control the client threads as well as have a single
    server context instance.
    '''

    handler = None

    def __init__(self, context, framer=None, identity=None, **kwargs):
        ''' Overloaded initializer for the socket server

        If the identify structure is not passed in, the ModbusControlBlock
        uses its own empty structure.

        :param context: The ModbusServerContext datastore
        :param framer: The framer strategy to use
        :param identity: An optional identify structure
        :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
        :param timeout: The timeout to use for the serial device
        :param ignore_missing_slaves: True to not send errors on a request to a missing slave
        '''
        self.threads = []
        self.decoder = ServerDecoder()
        self.framer  = framer  or ModbusAsciiFramer
        self.context = context or ModbusServerContext()
        self.control = ModbusControlBlock()

        if isinstance(identity, ModbusDeviceIdentification):
            self.control.Identity.update(identity)

        self.device   = kwargs.get('port', 0)
        self.stopbits = kwargs.get('stopbits', Defaults.Stopbits)
        self.bytesize = kwargs.get('bytesize', Defaults.Bytesize)
        self.parity   = kwargs.get('parity',   Defaults.Parity)
        self.baudrate = kwargs.get('baudrate', Defaults.Baudrate)
        self.timeout  = kwargs.get('timeout',  Defaults.Timeout)
        self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)
        self.socket   = None
        if self._connect():
            self.is_running = True
            self._build_handler()

    def _connect(self):
        ''' Connect to the serial server

        :returns: True if connection succeeded, False otherwise
        '''
        if self.socket: return True
        try:
            self.socket = serial.Serial(port=self.device, timeout=self.timeout,
                bytesize=self.bytesize, stopbits=self.stopbits,
                baudrate=self.baudrate, parity=self.parity)
        except serial.SerialException as msg:
            _logger.error(msg)
        return self.socket != None

    def _build_handler(self):
        ''' A helper method to create and monkeypatch
            a serial handler.

        :returns: A patched handler
        '''

        request = self.socket
        request.send = request.write
        request.recv = request.read
        self.handler = CustomSingleRequestHandler(request,
                                                  (self.device, self.device),
                                                  self)

    def serve_forever(self):
        ''' Callback for connecting a new client thread

        :param request: The request to handle
        :param client: The address of the client
        '''
        if self._connect():
            _logger.debug("Started thread to serve client")
            if not self.handler:
                self._build_handler()
            while self.is_running:
                self.handler.handle()
        else:
            _logger.error("Error opening serial port , Unable to start server!!")

    def server_close(self):
        ''' Callback for stopping the running server
        '''
        _logger.debug("Modbus server stopped")
        self.is_running = False
        self.handler.finish()
        self.handler.running = False
        self.handler = None
        self.socket.close()


#---------------------------------------------------------------------------#
# Creation Factories
#---------------------------------------------------------------------------#
def StartTcpServer(context=None, identity=None, address=None, **kwargs):
    ''' A factory to start and run a tcp modbus server

    :param context: The ModbusServerContext datastore
    :param identity: An optional identify structure
    :param address: An optional (interface, port) to bind to.
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    framer = ModbusSocketFramer
    server = ModbusTcpServer(context, framer, identity, address, **kwargs)
    server.serve_forever()


def StartUdpServer(context=None, identity=None, address=None, **kwargs):
    ''' A factory to start and run a udp modbus server

    :param context: The ModbusServerContext datastore
    :param identity: An optional identify structure
    :param address: An optional (interface, port) to bind to.
    :param framer: The framer to operate with (default ModbusSocketFramer)
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    framer = kwargs.pop('framer', ModbusSocketFramer)
    server = ModbusUdpServer(context, framer, identity, address, **kwargs)
    server.serve_forever()


def StartSerialServer(context=None, identity=None, **kwargs):
    ''' A factory to start and run a serial modbus server

    :param context: The ModbusServerContext datastore
    :param identity: An optional identify structure
    :param framer: The framer to operate with (default ModbusAsciiFramer)
    :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
    :param timeout: The timeout to use for the serial device
    :param ignore_missing_slaves: True to not send errors on a request to a missing slave
    '''
    framer = kwargs.pop('framer', ModbusAsciiFramer)
    server = ModbusSerialServer(context, framer, identity, **kwargs)
    server.serve_forever()

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "StartTcpServer", "StartUdpServer", "StartSerialServer"
]
pymodbus-1.3.2/pymodbus/server/__init__.py0000644000175000017500000000001013150360615016763 0ustar  wmbwmb'''
'''
pymodbus-1.3.2/pymodbus/events.py0000644000175000017500000001435613150360615015244 0ustar  wmbwmb'''
Modbus Remote Events
------------------------------------------------------------

An event byte returned by the Get Communications Event Log function
can be any one of four types. The type is defined by bit 7
(the high-order bit) in each byte. It may be further defined by bit 6.
'''
from pymodbus.exceptions import NotImplementedException
from pymodbus.exceptions import ParameterException
from pymodbus.utilities import pack_bitstring, unpack_bitstring


class ModbusEvent(object):

    def encode(self):
        ''' Encodes the status bits to an event message

        :returns: The encoded event message
        '''
        raise NotImplementedException()

    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        raise NotImplementedException()


class RemoteReceiveEvent(ModbusEvent):
    ''' Remote device MODBUS Receive Event

    The remote device stores this type of event byte when a query message
    is received. It is stored before the remote device processes the message.
    This event is defined by bit 7 set to logic '1'. The other bits will be
    set to a logic '1' if the corresponding condition is TRUE. The bit layout
    is::

        Bit Contents
        ----------------------------------
        0   Not Used
        2   Not Used
        3   Not Used
        4   Character Overrun
        5   Currently in Listen Only Mode
        6   Broadcast Receive
        7   1
    '''

    def __init__(self, **kwargs):
        ''' Initialize a new event instance
        '''
        self.overrun   = kwargs.get('overrun', False)
        self.listen    = kwargs.get('listen', False)
        self.broadcast = kwargs.get('broadcast', False)

    def encode(self):
        ''' Encodes the status bits to an event message

        :returns: The encoded event message
        '''
        bits  = [False] * 3
        bits += [self.overrun, self.listen, self.broadcast, True]
        packet = pack_bitstring(bits)
        return packet

    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        bits = unpack_bitstring(event)
        self.overrun   = bits[4]
        self.listen    = bits[5]
        self.broadcast = bits[6]


class RemoteSendEvent(ModbusEvent):
    ''' Remote device MODBUS Send Event

    The remote device stores this type of event byte when it finishes
    processing a request message. It is stored if the remote device
    returned a normal or exception response, or no response.

    This event is defined by bit 7 set to a logic '0', with bit 6 set to a '1'.
    The other bits will be set to a logic '1' if the corresponding
    condition is TRUE. The bit layout is::

        Bit Contents
        -----------------------------------------------------------
        0   Read Exception Sent (Exception Codes 1-3)
        1   Slave Abort Exception Sent (Exception Code 4)
        2   Slave Busy Exception Sent (Exception Codes 5-6)
        3   Slave Program NAK Exception Sent (Exception Code 7)
        4   Write Timeout Error Occurred
        5   Currently in Listen Only Mode
        6   1
        7   0
    '''

    def __init__(self, **kwargs):
        ''' Initialize a new event instance
        '''
        self.read          = kwargs.get('read', False)
        self.slave_abort   = kwargs.get('slave_abort', False)
        self.slave_busy    = kwargs.get('slave_busy', False)
        self.slave_nak     = kwargs.get('slave_nak', False)
        self.write_timeout = kwargs.get('write_timeout', False)
        self.listen        = kwargs.get('listen', False)

    def encode(self):
        ''' Encodes the status bits to an event message

        :returns: The encoded event message
        '''
        bits = [self.read, self.slave_abort, self.slave_busy,
            self.slave_nak, self.write_timeout, self.listen]
        bits  += [True, False]
        packet = pack_bitstring(bits)
        return packet

    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        # todo fix the start byte count
        bits = unpack_bitstring(event)
        self.read          = bits[0]
        self.slave_abort   = bits[1]
        self.slave_busy    = bits[2]
        self.slave_nak     = bits[3]
        self.write_timeout = bits[4]
        self.listen        = bits[5]


class EnteredListenModeEvent(ModbusEvent):
    ''' Remote device Entered Listen Only Mode

    The remote device stores this type of event byte when it enters
    the Listen Only Mode. The event is defined by a content of 04 hex.
    '''

    value = 0x04
    __encoded = b'\x04'

    def encode(self):
        ''' Encodes the status bits to an event message

        :returns: The encoded event message
        '''
        return self.__encoded

    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        if event != self.__encoded:
            raise ParameterException('Invalid decoded value')


class CommunicationRestartEvent(ModbusEvent):
    ''' Remote device Initiated Communication Restart

    The remote device stores this type of event byte when its communications
    port is restarted. The remote device can be restarted by the Diagnostics
    function (code 08), with sub-function Restart Communications Option
    (code 00 01).

    That function also places the remote device into a 'Continue on Error'
    or 'Stop on Error' mode. If the remote device is placed  into 'Continue on
    Error' mode, the event byte is added to the existing event log. If the
    remote device is placed into 'Stop on Error' mode, the byte is added to
    the log and the rest of the log is cleared to zeros.

    The event is defined by a content of zero.
    '''

    value = 0x00
    __encoded = b'\x00'

    def encode(self):
        ''' Encodes the status bits to an event message

        :returns: The encoded event message
        '''
        return self.__encoded

    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        if event != self.__encoded:
            raise ParameterException('Invalid decoded value')
pymodbus-1.3.2/pymodbus/transaction.py0000644000175000017500000011576213150360615016270 0ustar  wmbwmb'''
Collection of transaction based abstractions
'''
import sys
import struct
import socket
from binascii import b2a_hex, a2b_hex

from pymodbus.exceptions import ModbusIOException, NotImplementedException
from pymodbus.exceptions import InvalidMessageRecievedException
from pymodbus.constants  import Defaults
from pymodbus.interfaces import IModbusFramer
from pymodbus.utilities  import checkCRC, computeCRC
from pymodbus.utilities  import checkLRC, computeLRC
from pymodbus.compat import iterkeys, imap, byte2int

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# The Global Transaction Manager
#---------------------------------------------------------------------------#
class ModbusTransactionManager(object):
    ''' Impelements a transaction for a manager

    The transaction protocol can be represented by the following pseudo code::

        count = 0
        do
          result = send(message)
          if (timeout or result == bad)
             count++
          else break
        while (count < 3)

    This module helps to abstract this away from the framer and protocol.
    '''

    def __init__(self, client, **kwargs):
        ''' Initializes an instance of the ModbusTransactionManager

        :param client: The client socket wrapper
        :param retry_on_empty: Should the client retry on empty
        :param retries: The number of retries to allow
        '''
        self.tid = Defaults.TransactionId
        self.client = client
        self.retry_on_empty = kwargs.get('retry_on_empty', Defaults.RetryOnEmpty)
        self.retries = kwargs.get('retries', Defaults.Retries)
        if client:
            self._set_adu_size()

    def _set_adu_size(self):
        # base ADU size of modbus frame in bytes
        if isinstance(self.client.framer, ModbusSocketFramer):
            self.base_adu_size = 7 # tid(2), pid(2), length(2), uid(1)
        elif isinstance(self.client.framer, ModbusRtuFramer):
            self.base_adu_size = 3 # address(1), CRC(2)
        elif isinstance(self.client.framer, ModbusAsciiFramer):
            self.base_adu_size = 7 # start(1)+ Address(2), LRC(2) + end(2)
        elif isinstance(self.client.framer, ModbusBinaryFramer):
            self.base_adu_size = 3 #, Address(1), CRC(2)
        else:
            self.base_adu_size = -1

    def _calculate_response_length(self, expected_pdu_size):
        if self.base_adu_size == -1:
            return None
        else:
            return self.base_adu_size + expected_pdu_size

    def _calculate_exception_length(self):
        ''' Returns the length of the Modbus Exception Response according to
        the type of Framer.
        '''
        if isinstance(self.client.framer, ModbusSocketFramer):
            return self.base_adu_size + 2  # Fcode(1), ExcecptionCode(1)
        elif isinstance(self.client.framer, ModbusAsciiFramer):
            return self.base_adu_size + 4  # Fcode(2), ExcecptionCode(2)
        elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)):
            return self.base_adu_size + 2  # Fcode(1), ExcecptionCode(1)

        return None

    def _check_response(self, response):
        ''' Checks if the response is a Modbus Exception.
        '''
        if isinstance(self.client.framer, ModbusSocketFramer):
            if len(response) >= 8 and byte2int(response[7]) > 128:
                return False
        elif isinstance(self.client.framer, ModbusAsciiFramer):
            if len(response) >= 5 and int(response[3:5], 16) > 128:
                return False
        elif isinstance(self.client.framer, (ModbusRtuFramer,
                                             ModbusBinaryFramer)):
            if len(response) >= 2 and byte2int(response[1]) > 128:
                return False

        return True

    def execute(self, request):
        ''' Starts the producer to send the next request to
        consumer.write(Frame(request))
        '''
        retries = self.retries
        request.transaction_id = self.getNextTID()
        _logger.debug("Running transaction %d" % request.transaction_id)
        self.client.framer.resetFrame()
        expected_response_length = None
        if hasattr(request, "get_response_pdu_size"):
            response_pdu_size = request.get_response_pdu_size()
            if isinstance(self.client.framer, ModbusAsciiFramer):
                response_pdu_size = response_pdu_size * 2
            if response_pdu_size:
                expected_response_length = self._calculate_response_length(response_pdu_size)

        while retries > 0:
            try:
                last_exception = None
                self.client.connect()
                packet = self.client.framer.buildPacket(request)
                if _logger.isEnabledFor(logging.DEBUG):
                    _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet]))
                self._send(packet)
                # exception = False
                result = self._recv(expected_response_length or 1024)

                if not result and self.retry_on_empty:
                    retries -= 1
                    continue

                if _logger.isEnabledFor(logging.DEBUG):
                    _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result]))
                self.client.framer.processIncomingPacket(result, self.addTransaction)
                break
            except (socket.error, ModbusIOException, InvalidMessageRecievedException) as msg:
                self.client.close()
                _logger.debug("Transaction failed. (%s) " % msg)
                retries -= 1
                last_exception = msg
        response = self.getTransaction(request.transaction_id)
        if not response:
            if len(self.transactions):
                response = self.getTransaction(tid=0)
            else:
                last_exception = last_exception or ("No Response "
                                                    "received from the remote unit")
                response = ModbusIOException(last_exception)

        return response

    def _send(self, packet):
        return self.client._send(packet)

    def _recv(self, expected_response_length):
        retries = self.retries
        exception = False
        while retries:
            result = self.client._recv(expected_response_length or 1024)
            while result and expected_response_length and len(
                    result) < expected_response_length:
                if not exception and not self._check_response(result):
                    exception = True
                    expected_response_length = self._calculate_exception_length()
                    continue
                if isinstance(self.client.framer, ModbusSocketFramer):
                    break
                r = self.client._recv(expected_response_length - len(result))
                if not r:
                    # If no response being recived there is no point in conitnuing
                    break
                result += r
            if result:
                break
            retries -= 1
        return result


    def addTransaction(self, request, tid=None):
        ''' Adds a transaction to the handler

        This holds the requets in case it needs to be resent.
        After being sent, the request is removed.

        :param request: The request to hold on to
        :param tid: The overloaded transaction id to use
        '''
        raise NotImplementedException("addTransaction")

    def getTransaction(self, tid):
        ''' Returns a transaction matching the referenced tid

        If the transaction does not exist, None is returned

        :param tid: The transaction to retrieve
        '''
        raise NotImplementedException("getTransaction")

    def delTransaction(self, tid):
        ''' Removes a transaction matching the referenced tid

        :param tid: The transaction to remove
        '''
        raise NotImplementedException("delTransaction")

    def getNextTID(self):
        ''' Retrieve the next unique transaction identifier

        This handles incrementing the identifier after
        retrieval

        :returns: The next unique transaction identifier
        '''
        self.tid = (self.tid + 1) & 0xffff
        return self.tid

    def reset(self):
        ''' Resets the transaction identifier '''
        self.tid = Defaults.TransactionId
        self.transactions = type(self.transactions)()


class DictTransactionManager(ModbusTransactionManager):
    ''' Impelements a transaction for a manager where the
    results are keyed based on the supplied transaction id.
    '''

    def __init__(self, client, **kwargs):
        ''' Initializes an instance of the ModbusTransactionManager

        :param client: The client socket wrapper
        '''
        self.transactions = {}
        super(DictTransactionManager, self).__init__(client, **kwargs)

    def __iter__(self):
        ''' Iterater over the current managed transactions

        :returns: An iterator of the managed transactions
        '''
        return iterkeys(self.transactions)

    def addTransaction(self, request, tid=None):
        ''' Adds a transaction to the handler

        This holds the requets in case it needs to be resent.
        After being sent, the request is removed.

        :param request: The request to hold on to
        :param tid: The overloaded transaction id to use
        '''
        tid = tid if tid != None else request.transaction_id
        _logger.debug("adding transaction %d" % tid)
        self.transactions[tid] = request

    def getTransaction(self, tid):
        ''' Returns a transaction matching the referenced tid

        If the transaction does not exist, None is returned

        :param tid: The transaction to retrieve
        '''
        _logger.debug("getting transaction %d" % tid)
        return self.transactions.pop(tid, None)

    def delTransaction(self, tid):
        ''' Removes a transaction matching the referenced tid

        :param tid: The transaction to remove
        '''
        _logger.debug("deleting transaction %d" % tid)
        self.transactions.pop(tid, None)


class FifoTransactionManager(ModbusTransactionManager):
    ''' Impelements a transaction for a manager where the
    results are returned in a FIFO manner.
    '''

    def __init__(self, client, **kwargs):
        ''' Initializes an instance of the ModbusTransactionManager

        :param client: The client socket wrapper
        '''
        super(FifoTransactionManager, self).__init__(client, **kwargs)
        self.transactions = []

    def __iter__(self):
        ''' Iterater over the current managed transactions

        :returns: An iterator of the managed transactions
        '''
        return iter(self.transactions)

    def addTransaction(self, request, tid=None):
        ''' Adds a transaction to the handler

        This holds the requets in case it needs to be resent.
        After being sent, the request is removed.

        :param request: The request to hold on to
        :param tid: The overloaded transaction id to use
        '''
        tid = tid if tid != None else request.transaction_id
        _logger.debug("adding transaction %d" % tid)
        self.transactions.append(request)

    def getTransaction(self, tid):
        ''' Returns a transaction matching the referenced tid

        If the transaction does not exist, None is returned

        :param tid: The transaction to retrieve
        '''
        _logger.debug("getting transaction %s" % str(tid))
        return self.transactions.pop(0) if self.transactions else None

    def delTransaction(self, tid):
        ''' Removes a transaction matching the referenced tid

        :param tid: The transaction to remove
        '''
        _logger.debug("deleting transaction %d" % tid)
        if self.transactions: self.transactions.pop(0)


#---------------------------------------------------------------------------#
# Modbus TCP Message
#---------------------------------------------------------------------------#
class ModbusSocketFramer(IModbusFramer):
    ''' Modbus Socket Frame controller

    Before each modbus TCP message is an MBAP header which is used as a
    message frame.  It allows us to easily separate messages as follows::

        [         MBAP Header         ] [ Function Code] [ Data ]
        [ tid ][ pid ][ length ][ uid ]
          2b     2b     2b        1b           1b           Nb

        while len(message) > 0:
            tid, pid, length`, uid = struct.unpack(">HHHB", message)
            request = message[0:7 + length - 1`]
            message = [7 + length - 1:]

        * length = uid + function code + data
        * The -1 is to account for the uid byte
    '''

    def __init__(self, decoder):
        ''' Initializes a new instance of the framer

        :param decoder: The decoder factory implementation to use
        '''
        self.__buffer = b''
        self.__header = {'tid':0, 'pid':0, 'len':0, 'uid':0}
        self.__hsize  = 0x07
        self.decoder  = decoder

    #-----------------------------------------------------------------------#
    # Private Helper Functions
    #-----------------------------------------------------------------------#
    def checkFrame(self):
        '''
        Check and decode the next frame Return true if we were successful
        '''
        if len(self.__buffer) > self.__hsize:
            self.__header['tid'], self.__header['pid'], \
            self.__header['len'], self.__header['uid'] = struct.unpack(
                    '>HHHB', self.__buffer[0:self.__hsize])

            # someone sent us an error? ignore it
            if self.__header['len'] < 2:
                self.advanceFrame()
            # we have at least a complete message, continue
            elif len(self.__buffer) - self.__hsize + 1 >= self.__header['len']:
                return True
        # we don't have enough of a message yet, wait
        return False

    def advanceFrame(self):
        ''' Skip over the current framed message
        This allows us to skip over the current message after we have processed
        it or determined that it contains an error. It also has to reset the
        current frame header handle
        '''
        length = self.__hsize + self.__header['len'] - 1
        self.__buffer = self.__buffer[length:]
        self.__header = {'tid':0, 'pid':0, 'len':0, 'uid':0}

    def isFrameReady(self):
        ''' Check if we should continue decode logic
        This is meant to be used in a while loop in the decoding phase to let
        the decoder factory know that there is still data in the buffer.

        :returns: True if ready, False otherwise
        '''
        return len(self.__buffer) > self.__hsize

    def addToFrame(self, message):
        ''' Adds new packet data to the current frame buffer

        :param message: The most recent packet
        '''
        self.__buffer += message

    def getFrame(self):
        ''' Return the next frame from the buffered data

        :returns: The next full frame buffer
        '''
        length = self.__hsize + self.__header['len'] - 1
        return self.__buffer[self.__hsize:length]

    def populateResult(self, result):
        '''
        Populates the modbus result with the transport specific header
        information (pid, tid, uid, checksum, etc)

        :param result: The response packet
        '''
        result.transaction_id = self.__header['tid']
        result.protocol_id = self.__header['pid']
        result.unit_id = self.__header['uid']

    #-----------------------------------------------------------------------#
    # Public Member Functions
    #-----------------------------------------------------------------------#
    def processIncomingPacket(self, data, callback):
        ''' The new packet processing pattern

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 / N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        '''
        _logger.debug(' '.join([hex(byte2int(x)) for x in data]))
        self.addToFrame(data)
        while True:
            if self.isFrameReady():
                if self.checkFrame():
                    self._process(callback)
                else: self.resetFrame()
            else:
                if len(self.__buffer):
                    # Possible error ???
                    if self.__header['len'] < 2:
                        self._process(callback, error=True)
                break

    def _process(self, callback, error=False):
        """
        Process incoming packets irrespective error condition 
        """
        data = self.getRawFrame() if error else self.getFrame()
        result = self.decoder.decode(data)
        if result is None:
            raise ModbusIOException("Unable to decode request")
        elif error and result.function_code < 0x80:
            raise InvalidMessageRecievedException(result)
        else:
            self.populateResult(result)
            self.advanceFrame()
            callback(result)  # defer or push to a thread?

    def resetFrame(self):
        ''' Reset the entire message frame.
        This allows us to skip ovver errors that may be in the stream.
        It is hard to know if we are simply out of sync or if there is
        an error in the stream as we have no way to check the start or
        end of the message (python just doesn't have the resolution to
        check for millisecond delays).
        '''
        self.__buffer = b''
        self.__header = {}

    def getRawFrame(self):
        """
        Returns the complete buffer 
        """
        return self.__buffer

    def buildPacket(self, message):
        ''' Creates a ready to send modbus packet

        :param message: The populated request/response to send
        '''
        data = message.encode()
        packet = struct.pack('>HHHBB',
            message.transaction_id,
            message.protocol_id,
            len(data) + 2,
            message.unit_id,
            message.function_code) + data
        return packet


#---------------------------------------------------------------------------#
# Modbus RTU Message
#---------------------------------------------------------------------------#
class ModbusRtuFramer(IModbusFramer):
    '''
    Modbus RTU Frame controller::

        [ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][  End Wait  ]
          3.5 chars     1b         1b               Nb      2b      3.5 chars

    Wait refers to the amount of time required to transmist at least x many
    characters.  In this case it is 3.5 characters.  Also, if we recieve a
    wait of 1.5 characters at any point, we must trigger an error message.
    Also, it appears as though this message is little endian. The logic is
    simplified as the following::

        block-on-read:
            read until 3.5 delay
            check for errors
            decode

    The following table is a listing of the baud wait times for the specified
    baud rates::

        ------------------------------------------------------------------
         Baud  1.5c (18 bits)   3.5c (38 bits)
        ------------------------------------------------------------------
         1200   13333.3 us       31666.7 us
         4800    3333.3 us        7916.7 us
         9600    1666.7 us        3958.3 us
        19200     833.3 us        1979.2 us
        38400     416.7 us         989.6 us
        ------------------------------------------------------------------
        1 Byte = start + 8 bits + parity + stop = 11 bits
        (1/Baud)(bits) = delay seconds
    '''

    def __init__(self, decoder):
        ''' Initializes a new instance of the framer

        :param decoder: The decoder factory implementation to use
        '''
        self.__buffer = b''
        self.__header = {}
        self.__hsize  = 0x01
        self.__end    = b'\x0d\x0a'
        self.__min_frame_size = 4
        self.decoder  = decoder

    #-----------------------------------------------------------------------#
    # Private Helper Functions
    #-----------------------------------------------------------------------#
    def checkFrame(self):
        '''
        Check if the next frame is available. Return True if we were
        successful.
        '''
        try:
            self.populateHeader()
            frame_size = self.__header['len']
            data = self.__buffer[:frame_size - 2]
            crc = self.__buffer[frame_size - 2:frame_size]
            crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1])
            return checkCRC(data, crc_val)
        except (IndexError, KeyError):
            return False

    def advanceFrame(self):
        ''' Skip over the current framed message
        This allows us to skip over the current message after we have processed
        it or determined that it contains an error. It also has to reset the
        current frame header handle
        '''
        try:
            self.__buffer = self.__buffer[self.__header['len']:]
        except KeyError:
            #   Error response, no header len found
            self.resetFrame()
        self.__header = {}

    def resetFrame(self):
        ''' Reset the entire message frame.
        This allows us to skip ovver errors that may be in the stream.
        It is hard to know if we are simply out of sync or if there is
        an error in the stream as we have no way to check the start or
        end of the message (python just doesn't have the resolution to
        check for millisecond delays).
        '''
        self.__buffer = b''
        self.__header = {}

    def isFrameReady(self):
        ''' Check if we should continue decode logic
        This is meant to be used in a while loop in the decoding phase to let
        the decoder know that there is still data in the buffer.

        :returns: True if ready, False otherwise
        '''
        return len(self.__buffer) > self.__hsize

    def populateHeader(self):
        ''' Try to set the headers `uid`, `len` and `crc`.

        This method examines `self.__buffer` and writes meta
        information into `self.__header`. It calculates only the
        values for headers that are not already in the dictionary.

        Beware that this method will raise an IndexError if
        `self.__buffer` is not yet long enough.
        '''
        self.__header['uid'] = byte2int(self.__buffer[0])
        func_code = byte2int(self.__buffer[1])
        pdu_class = self.decoder.lookupPduClass(func_code)
        size = pdu_class.calculateRtuFrameSize(self.__buffer)
        self.__header['len'] = size
        self.__header['crc'] = self.__buffer[size - 2:size]

    def addToFrame(self, message):
        '''
        This should be used before the decoding while loop to add the received
        data to the buffer handle.

        :param message: The most recent packet
        '''
        self.__buffer += message

    def getFrame(self):
        ''' Get the next frame from the buffer

        :returns: The frame data or ''
        '''
        start  = self.__hsize
        end    = self.__header['len'] - 2
        buffer = self.__buffer[start:end]
        if end > 0: return buffer
        return ''

    def populateResult(self, result):
        ''' Populates the modbus result header

        The serial packets do not have any header information
        that is copied.

        :param result: The response packet
        '''
        result.unit_id = self.__header['uid']

    #-----------------------------------------------------------------------#
    # Public Member Functions
    #-----------------------------------------------------------------------#
    def processIncomingPacket(self, data, callback):
        ''' The new packet processing pattern

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 / N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        '''
        self.addToFrame(data)
        while True:
            if self.isFrameReady():
                if self.checkFrame():
                    self._process(callback)
                else:
                    # Could be an error response
                    if len(self.__buffer):
                        # Possible error ???
                       self._process(callback, error=True)
            else:
                if len(self.__buffer):
                    # Possible error ???
                    if self.__header.get('len', 0) < 2:
                        self._process(callback, error=True)
                break

    def buildPacket(self, message):
        ''' Creates a ready to send modbus packet

        :param message: The populated request/response to send
        '''
        data = message.encode()
        packet = struct.pack('>BB',
            message.unit_id,
            message.function_code) + data
        packet += struct.pack(">H", computeCRC(packet))
        return packet

    def _process(self, callback, error=False):
        """
        Process incoming packets irrespective error condition
        """
        data = self.getRawFrame() if error else self.getFrame()
        result = self.decoder.decode(data)
        if result is None:
            raise ModbusIOException("Unable to decode request")
        elif error and result.function_code < 0x80:
            raise InvalidMessageRecievedException(result)
        else:
            self.populateResult(result)
            self.advanceFrame()
            callback(result)  # defer or push to a thread?

    def getRawFrame(self):
        """
        Returns the complete buffer
        """
        return self.__buffer



#---------------------------------------------------------------------------#
# Modbus ASCII Message
#---------------------------------------------------------------------------#
class ModbusAsciiFramer(IModbusFramer):
    '''
    Modbus ASCII Frame Controller::

        [ Start ][Address ][ Function ][ Data ][ LRC ][ End ]
          1c        2c         2c         Nc     2c      2c

        * data can be 0 - 2x252 chars
        * end is '\\r\\n' (Carriage return line feed), however the line feed
          character can be changed via a special command
        * start is ':'

    This framer is used for serial transmission.  Unlike the RTU protocol,
    the data in this framer is transferred in plain text ascii.
    '''

    def __init__(self, decoder):
        ''' Initializes a new instance of the framer

        :param decoder: The decoder implementation to use
        '''
        self.__buffer = b''
        self.__header = {'lrc':'0000', 'len':0, 'uid':0x00}
        self.__hsize  = 0x02
        self.__start  = b':'
        self.__end    = b"\r\n"
        self.decoder  = decoder

    #-----------------------------------------------------------------------#
    # Private Helper Functions
    #-----------------------------------------------------------------------#
    def checkFrame(self):
        ''' Check and decode the next frame

        :returns: True if we successful, False otherwise
        '''
        start = self.__buffer.find(self.__start)
        if start == -1: return False
        if start > 0 :  # go ahead and skip old bad data
            self.__buffer = self.__buffer[start:]
            start = 0

        end = self.__buffer.find(self.__end)
        if (end != -1):
            self.__header['len'] = end
            self.__header['uid'] = int(self.__buffer[1:3], 16)
            self.__header['lrc'] = int(self.__buffer[end - 2:end], 16)
            data = a2b_hex(self.__buffer[start + 1:end - 2])
            return checkLRC(data, self.__header['lrc'])
        return False

    def advanceFrame(self):
        ''' Skip over the current framed message
        This allows us to skip over the current message after we have processed
        it or determined that it contains an error. It also has to reset the
        current frame header handle
        '''
        self.__buffer = self.__buffer[self.__header['len'] + 2:]
        self.__header = {'lrc':'0000', 'len':0, 'uid':0x00}

    def isFrameReady(self):
        ''' Check if we should continue decode logic
        This is meant to be used in a while loop in the decoding phase to let
        the decoder know that there is still data in the buffer.

        :returns: True if ready, False otherwise
        '''
        return len(self.__buffer) > 1

    def addToFrame(self, message):
        ''' Add the next message to the frame buffer
        This should be used before the decoding while loop to add the received
        data to the buffer handle.

        :param message: The most recent packet
        '''
        self.__buffer += message

    def getFrame(self):
        ''' Get the next frame from the buffer

        :returns: The frame data or ''
        '''
        start  = self.__hsize + 1
        end    = self.__header['len'] - 2
        buffer   = self.__buffer[start:end]
        if end > 0: return a2b_hex(buffer)
        return b''

    def resetFrame(self):
        ''' Reset the entire message frame.
        This allows us to skip ovver errors that may be in the stream.
        It is hard to know if we are simply out of sync or if there is
        an error in the stream as we have no way to check the start or
        end of the message (python just doesn't have the resolution to
        check for millisecond delays).
        '''
        self.__buffer = b''
        self.__header = {'lrc':'0000', 'len':0, 'uid':0x00}

    def populateResult(self, result):
        ''' Populates the modbus result header

        The serial packets do not have any header information
        that is copied.

        :param result: The response packet
        '''
        result.unit_id = self.__header['uid']

    #-----------------------------------------------------------------------#
    # Public Member Functions
    #-----------------------------------------------------------------------#
    def processIncomingPacket(self, data, callback):
        ''' The new packet processing pattern

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 / N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        '''
        self.addToFrame(data)
        while self.isFrameReady():
            if self.checkFrame():
                result = self.decoder.decode(self.getFrame())
                if result is None:
                    raise ModbusIOException("Unable to decode response")
                self.populateResult(result)
                self.advanceFrame()
                callback(result)  # defer this
            else:
                break

    def buildPacket(self, message):
        ''' Creates a ready to send modbus packet
        Built off of a  modbus request/response

        :param message: The request/response to send
        :return: The encoded packet
        '''
        encoded  = message.encode()
        buffer     = struct.pack('>BB', message.unit_id, message.function_code)
        checksum = computeLRC(encoded + buffer)

        packet = bytearray()
        params = (message.unit_id, message.function_code)
        packet.extend(self.__start)
        packet.extend(('%02x%02x' % params).encode())
        packet.extend(b2a_hex(encoded))
        packet.extend(('%02x' % checksum).encode())
        packet.extend(self.__end)
        return bytes(packet).upper()


#---------------------------------------------------------------------------#
# Modbus Binary Message
#---------------------------------------------------------------------------#
class ModbusBinaryFramer(IModbusFramer):
    '''
    Modbus Binary Frame Controller::

        [ Start ][Address ][ Function ][ Data ][ CRC ][ End ]
          1b        1b         1b         Nb     2b     1b

        * data can be 0 - 2x252 chars
        * end is   '}'
        * start is '{'

    The idea here is that we implement the RTU protocol, however,
    instead of using timing for message delimiting, we use start
    and end of message characters (in this case { and }). Basically,
    this is a binary framer.

    The only case we have to watch out for is when a message contains
    the { or } characters.  If we encounter these characters, we
    simply duplicate them.  Hopefully we will not encounter those
    characters that often and will save a little bit of bandwitch
    without a real-time system.

    Protocol defined by jamod.sourceforge.net.
    '''

    def __init__(self, decoder):
        ''' Initializes a new instance of the framer

        :param decoder: The decoder implementation to use
        '''
        self.__buffer = b''
        self.__header = {'crc':0x0000, 'len':0, 'uid':0x00}
        self.__hsize  = 0x02
        self.__start  = b'\x7b'  # {
        self.__end    = b'\x7d'  # }
        self.__repeat = [b'}'[0], b'{'[0]] # python3 hack
        self.decoder  = decoder

    #-----------------------------------------------------------------------#
    # Private Helper Functions
    #-----------------------------------------------------------------------#
    def checkFrame(self):
        ''' Check and decode the next frame

        :returns: True if we are successful, False otherwise
        '''
        start = self.__buffer.find(self.__start)
        if start == -1: return False
        if start > 0 :  # go ahead and skip old bad data
            self.__buffer = self.__buffer[start:]

        end = self.__buffer.find(self.__end)
        if (end != -1):
            self.__header['len'] = end
            self.__header['uid'] = struct.unpack('>B', self.__buffer[1:2])
            self.__header['crc'] = struct.unpack('>H', self.__buffer[end - 2:end])[0]
            data = self.__buffer[start + 1:end - 2]
            return checkCRC(data, self.__header['crc'])
        return False

    def advanceFrame(self):
        ''' Skip over the current framed message
        This allows us to skip over the current message after we have processed
        it or determined that it contains an error. It also has to reset the
        current frame header handle
        '''
        self.__buffer = self.__buffer[self.__header['len'] + 2:]
        self.__header = {'crc':0x0000, 'len':0, 'uid':0x00}

    def isFrameReady(self):
        ''' Check if we should continue decode logic
        This is meant to be used in a while loop in the decoding phase to let
        the decoder know that there is still data in the buffer.

        :returns: True if ready, False otherwise
        '''
        return len(self.__buffer) > 1

    def addToFrame(self, message):
        ''' Add the next message to the frame buffer
        This should be used before the decoding while loop to add the received
        data to the buffer handle.

        :param message: The most recent packet
        '''
        self.__buffer += message

    def getFrame(self):
        ''' Get the next frame from the buffer

        :returns: The frame data or ''
        '''
        start  = self.__hsize + 1
        end    = self.__header['len'] - 2
        buffer = self.__buffer[start:end]
        if end > 0: return buffer
        return b''

    def populateResult(self, result):
        ''' Populates the modbus result header

        The serial packets do not have any header information
        that is copied.

        :param result: The response packet
        '''
        result.unit_id = self.__header['uid']

    #-----------------------------------------------------------------------#
    # Public Member Functions
    #-----------------------------------------------------------------------#
    def processIncomingPacket(self, data, callback):
        ''' The new packet processing pattern

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 / N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        '''
        self.addToFrame(data)
        while self.isFrameReady():
            if self.checkFrame():
                result = self.decoder.decode(self.getFrame())
                if result is None:
                    raise ModbusIOException("Unable to decode response")
                self.populateResult(result)
                self.advanceFrame()
                callback(result)  # defer or push to a thread?
            else:
                break

    def buildPacket(self, message):
        ''' Creates a ready to send modbus packet

        :param message: The request/response to send
        :returns: The encoded packet
        '''
        data = self._preflight(message.encode())
        packet = struct.pack('>BB',
            message.unit_id,
            message.function_code) + data
        packet += struct.pack(">H", computeCRC(packet))
        packet  = self.__start + packet + self.__end
        return packet

    def _preflight(self, data):
        ''' Preflight buffer test

        This basically scans the buffer for start and end
        tags and if found, escapes them.

        :param data: The message to escape
        :returns: the escaped packet
        '''
        array = bytearray()
        for d in data:
            if d in self.__repeat:
                array.append(d)
            array.append(d)
        return bytes(array)

    def resetFrame(self):
        ''' Reset the entire message frame.
        This allows us to skip ovver errors that may be in the stream.
        It is hard to know if we are simply out of sync or if there is
        an error in the stream as we have no way to check the start or
        end of the message (python just doesn't have the resolution to
        check for millisecond delays).
        '''
        self.__buffer = b''
        self.__header = {'crc': 0x0000, 'len': 0, 'uid': 0x00}

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "FifoTransactionManager",
    "DictTransactionManager",
    "ModbusSocketFramer", "ModbusRtuFramer",
    "ModbusAsciiFramer", "ModbusBinaryFramer",
]
pymodbus-1.3.2/pymodbus/factory.py0000644000175000017500000002267113150360615015406 0ustar  wmbwmb"""
Modbus Request/Response Decoder Factories
-------------------------------------------

The following factories make it easy to decode request/response messages.
To add a new request/response pair to be decodeable by the library, simply
add them to the respective function lookup table (order doesn't matter, but
it does help keep things organized).

Regardless of how many functions are added to the lookup, O(1) behavior is
kept as a result of a pre-computed lookup dictionary.
"""

from pymodbus.pdu import IllegalFunctionRequest
from pymodbus.pdu import ExceptionResponse
from pymodbus.pdu import ModbusExceptions as ecode
from pymodbus.interfaces import IModbusDecoder
from pymodbus.exceptions import ModbusException
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 *
from pymodbus.compat import byte2int

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Server Decoder
#---------------------------------------------------------------------------#
class ServerDecoder(IModbusDecoder):
    ''' Request Message Factory (Server)

    To add more implemented functions, simply add them to the list
    '''
    __function_table = [
            ReadHoldingRegistersRequest,
            ReadDiscreteInputsRequest,
            ReadInputRegistersRequest,
            ReadCoilsRequest,
            WriteMultipleCoilsRequest,
            WriteMultipleRegistersRequest,
            WriteSingleRegisterRequest,
            WriteSingleCoilRequest,
            ReadWriteMultipleRegistersRequest,

            DiagnosticStatusRequest,

            ReadExceptionStatusRequest,
            GetCommEventCounterRequest,
            GetCommEventLogRequest,
            ReportSlaveIdRequest,

            ReadFileRecordRequest,
            WriteFileRecordRequest,
            MaskWriteRegisterRequest,
            ReadFifoQueueRequest,

            ReadDeviceInformationRequest,
    ]
    __sub_function_table = [
            ReturnQueryDataRequest,
            RestartCommunicationsOptionRequest,
            ReturnDiagnosticRegisterRequest,
            ChangeAsciiInputDelimiterRequest,
            ForceListenOnlyModeRequest,
            ClearCountersRequest,
            ReturnBusMessageCountRequest,
            ReturnBusCommunicationErrorCountRequest,
            ReturnBusExceptionErrorCountRequest,
            ReturnSlaveMessageCountRequest,
            ReturnSlaveNoResponseCountRequest,
            ReturnSlaveNAKCountRequest,
            ReturnSlaveBusyCountRequest,
            ReturnSlaveBusCharacterOverrunCountRequest,
            ReturnIopOverrunCountRequest,
            ClearOverrunCountRequest,
            GetClearModbusPlusRequest,

            ReadDeviceInformationRequest,
    ]

    def __init__(self):
        ''' Initializes the client lookup tables
        '''
        functions = set(f.function_code for f in self.__function_table)
        self.__lookup = dict([(f.function_code, f) for f in self.__function_table])
        self.__sub_lookup = dict((f, {}) for f in functions)
        for f in self.__sub_function_table:
            self.__sub_lookup[f.function_code][f.sub_function_code] = f

    def decode(self, message):
        ''' Wrapper to decode a request packet

        :param message: The raw modbus request packet
        :return: The decoded modbus message or None if error
        '''
        try:
            return self._helper(message)
        except ModbusException as er:
            _logger.warning("Unable to decode request %s" % er)
        return None

    def lookupPduClass(self, function_code):
        ''' Use `function_code` to determine the class of the PDU.

        :param function_code: The function code specified in a frame.
        :returns: The class of the PDU that has a matching `function_code`.
        '''
        return self.__lookup.get(function_code, ExceptionResponse)

    def _helper(self, data):
        '''
        This factory is used to generate the correct request object
        from a valid request packet. This decodes from a list of the
        currently implemented request types.

        :param data: The request packet to decode
        :returns: The decoded request or illegal function request object
        '''
        function_code = byte2int(data[0])
        _logger.debug("Factory Request[%d]" % function_code)
        request = self.__lookup.get(function_code, lambda: None)()
        if not request:
            request = IllegalFunctionRequest(function_code)
        request.decode(data[1:])

        if hasattr(request, 'sub_function_code'):
            lookup = self.__sub_lookup.get(request.function_code, {})
            subtype = lookup.get(request.sub_function_code, None)
            if subtype: request.__class__ = subtype

        return request


#---------------------------------------------------------------------------#
# Client Decoder
#---------------------------------------------------------------------------#
class ClientDecoder(IModbusDecoder):
    ''' Response Message Factory (Client)

    To add more implemented functions, simply add them to the list
    '''
    __function_table = [
            ReadHoldingRegistersResponse,
            ReadDiscreteInputsResponse,
            ReadInputRegistersResponse,
            ReadCoilsResponse,
            WriteMultipleCoilsResponse,
            WriteMultipleRegistersResponse,
            WriteSingleRegisterResponse,
            WriteSingleCoilResponse,
            ReadWriteMultipleRegistersResponse,

            DiagnosticStatusResponse,

            ReadExceptionStatusResponse,
            GetCommEventCounterResponse,
            GetCommEventLogResponse,
            ReportSlaveIdResponse,

            ReadFileRecordResponse,
            WriteFileRecordResponse,
            MaskWriteRegisterResponse,
            ReadFifoQueueResponse,

            ReadDeviceInformationResponse,
    ]
    __sub_function_table = [
            ReturnQueryDataResponse,
            RestartCommunicationsOptionResponse,
            ReturnDiagnosticRegisterResponse,
            ChangeAsciiInputDelimiterResponse,
            ForceListenOnlyModeResponse,
            ClearCountersResponse,
            ReturnBusMessageCountResponse,
            ReturnBusCommunicationErrorCountResponse,
            ReturnBusExceptionErrorCountResponse,
            ReturnSlaveMessageCountResponse,
            ReturnSlaveNoReponseCountResponse,
            ReturnSlaveNAKCountResponse,
            ReturnSlaveBusyCountResponse,
            ReturnSlaveBusCharacterOverrunCountResponse,
            ReturnIopOverrunCountResponse,
            ClearOverrunCountResponse,
            GetClearModbusPlusResponse,

            ReadDeviceInformationResponse,
    ]

    def __init__(self):
        ''' Initializes the client lookup tables
        '''
        functions = set(f.function_code for f in self.__function_table)
        self.__lookup = dict([(f.function_code, f) for f in self.__function_table])
        self.__sub_lookup = dict((f, {}) for f in functions)
        for f in self.__sub_function_table:
            self.__sub_lookup[f.function_code][f.sub_function_code] = f

    def lookupPduClass(self, function_code):
        ''' Use `function_code` to determine the class of the PDU.

        :param function_code: The function code specified in a frame.
        :returns: The class of the PDU that has a matching `function_code`.
        '''
        return self.__lookup.get(function_code, ExceptionResponse)

    def decode(self, message):
        ''' Wrapper to decode a response packet

        :param message: The raw packet to decode
        :return: The decoded modbus message or None if error
        '''
        try:
            return self._helper(message)
        except ModbusException as er:
            _logger.error("Unable to decode response %s" % er)
        return None

    def _helper(self, data):
        '''
        This factory is used to generate the correct response object
        from a valid response packet. This decodes from a list of the
        currently implemented request types.

        :param data: The response packet to decode
        :returns: The decoded request or an exception response object
        '''
        function_code = byte2int(data[0])
        _logger.debug("Factory Response[%d]" % function_code)
        response = self.__lookup.get(function_code, lambda: None)()
        if function_code > 0x80:
            code = function_code & 0x7f  # strip error portion
            response = ExceptionResponse(code, ecode.IllegalFunction)
        if not response:
            raise ModbusException("Unknown response %d" % function_code)
        response.decode(data[1:])

        if hasattr(response, 'sub_function_code'):
            lookup = self.__sub_lookup.get(response.function_code, {})
            subtype = lookup.get(response.sub_function_code, None)
            if subtype: response.__class__ = subtype

        return response

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = ['ServerDecoder', 'ClientDecoder']
pymodbus-1.3.2/pymodbus/client/0000755000175000017500000000000013150360615014633 5ustar  wmbwmbpymodbus-1.3.2/pymodbus/client/async.py0000644000175000017500000001717413150360615016334 0ustar  wmbwmb"""
Implementation of a Modbus Client Using Twisted
--------------------------------------------------

Example run::

    from twisted.internet import reactor, protocol
    from pymodbus.client.async import ModbusClientProtocol

    def printResult(result):
        print "Result: %d" % result.bits[0]

    def process(client):
        result = client.write_coil(1, True)
        result.addCallback(printResult)
        reactor.callLater(1, reactor.stop)

    defer = protocol.ClientCreator(reactor, ModbusClientProtocol
            ).connectTCP("localhost", 502)
    defer.addCallback(process)

Another example::

    from twisted.internet import reactor
    from pymodbus.client.async import ModbusClientFactory

    def process():
        factory = reactor.connectTCP("localhost", 502, ModbusClientFactory())
        reactor.stop()

    if __name__ == "__main__":
       reactor.callLater(1, process)
       reactor.run()
"""
from twisted.internet import defer, protocol
from pymodbus.factory import ClientDecoder
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.transaction import FifoTransactionManager
from pymodbus.transaction import DictTransactionManager
from pymodbus.client.common import ModbusClientMixin
from twisted.python.failure import Failure

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# Connected Client Protocols
#---------------------------------------------------------------------------#
class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin):
    '''
    This represents the base modbus client protocol.  All the application
    layer code is deferred to a higher level wrapper.
    '''

    def __init__(self, framer=None, **kwargs):
        ''' Initializes the framer module

        :param framer: The framer to use for the protocol
        '''
        self._connected = False
        self.framer = framer or ModbusSocketFramer(ClientDecoder())
        if isinstance(self.framer, ModbusSocketFramer):
            self.transaction = DictTransactionManager(self, **kwargs)
        else: self.transaction = FifoTransactionManager(self, **kwargs)

    def connectionMade(self):
        ''' Called upon a successful client connection.
        '''
        _logger.debug("Client connected to modbus server")
        self._connected = True

    def connectionLost(self, reason):
        ''' Called upon a client disconnect

        :param reason: The reason for the disconnect
        '''
        _logger.debug("Client disconnected from modbus server: %s" % reason)
        self._connected = False
        for tid in list(self.transaction):
            self.transaction.getTransaction(tid).errback(Failure(
                ConnectionException('Connection lost during request')))

    def dataReceived(self, data):
        ''' Get response, check for valid message, decode result

        :param data: The data returned from the server
        '''
        self.framer.processIncomingPacket(data, self._handleResponse)

    def execute(self, request):
        ''' Starts the producer to send the next request to
        consumer.write(Frame(request))
        '''
        request.transaction_id = self.transaction.getNextTID()
        packet = self.framer.buildPacket(request)
        self.transport.write(packet)
        return self._buildResponse(request.transaction_id)

    def _handleResponse(self, reply):
        ''' Handle the processed response and link to correct deferred

        :param reply: The reply to process
        '''
        if reply is not None:
            tid = reply.transaction_id
            handler = self.transaction.getTransaction(tid)
            if handler:
                handler.callback(reply)
            else: _logger.debug("Unrequested message: " + str(reply))

    def _buildResponse(self, tid):
        ''' Helper method to return a deferred response
        for the current request.

        :param tid: The transaction identifier for this response
        :returns: A defer linked to the latest request
        '''
        if not self._connected:
            return defer.fail(Failure(
                ConnectionException('Client is not connected')))

        d = defer.Deferred()
        self.transaction.addTransaction(d, tid)
        return d

    #----------------------------------------------------------------------#
    # Extra Functions
    #----------------------------------------------------------------------#
    #if send_failed:
    #       if self.retry > 0:
    #               deferLater(clock, self.delay, send, message)
    #               self.retry -= 1


#---------------------------------------------------------------------------#
# Not Connected Client Protocol
#---------------------------------------------------------------------------#
class ModbusUdpClientProtocol(protocol.DatagramProtocol, ModbusClientMixin):
    '''
    This represents the base modbus client protocol.  All the application
    layer code is deferred to a higher level wrapper.
    '''

    def __init__(self, framer=None, **kwargs):
        ''' Initializes the framer module

        :param framer: The framer to use for the protocol
        '''
        self.framer = framer or ModbusSocketFramer(ClientDecoder())
        if isinstance(self.framer, ModbusSocketFramer):
            self.transaction = DictTransactionManager(self, **kwargs)
        else: self.transaction = FifoTransactionManager(self, **kwargs)

    def datagramReceived(self, data, params):
        ''' Get response, check for valid message, decode result

        :param data: The data returned from the server
        :param params: The host parameters sending the datagram
        '''
        _logger.debug("Datagram from: %s:%d" % params)
        self.framer.processIncomingPacket(data, self._handleResponse)

    def execute(self, request):
        ''' Starts the producer to send the next request to
        consumer.write(Frame(request))
        '''
        request.transaction_id = self.transaction.getNextTID()
        packet = self.framer.buildPacket(request)
        self.transport.write(packet)
        return self._buildResponse(request.transaction_id)

    def _handleResponse(self, reply):
        ''' Handle the processed response and link to correct deferred

        :param reply: The reply to process
        '''
        if reply is not None:
            tid = reply.transaction_id
            handler = self.transaction.getTransaction(tid)
            if handler:
                handler.callback(reply)
            else: _logger.debug("Unrequested message: " + str(reply))

    def _buildResponse(self, tid):
        ''' Helper method to return a deferred response
        for the current request.

        :param tid: The transaction identifier for this response
        :returns: A defer linked to the latest request
        '''
        d = defer.Deferred()
        self.transaction.addTransaction(d, tid)
        return d


#---------------------------------------------------------------------------#
# Client Factories
#---------------------------------------------------------------------------#
class ModbusClientFactory(protocol.ReconnectingClientFactory):
    ''' Simple client protocol factory '''

    protocol = ModbusClientProtocol

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ModbusClientProtocol", "ModbusUdpClientProtocol",
    "ModbusClientFactory",
]
pymodbus-1.3.2/pymodbus/client/sync.py0000644000175000017500000003547113150360615016173 0ustar  wmbwmbimport socket
import serial
import time

from pymodbus.constants import Defaults
from pymodbus.factory import ClientDecoder
from pymodbus.compat import byte2int
from pymodbus.exceptions import NotImplementedException, ParameterException
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import FifoTransactionManager
from pymodbus.transaction import DictTransactionManager
from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.client.common import ModbusClientMixin

#---------------------------------------------------------------------------#
# Logging
#---------------------------------------------------------------------------#
import logging
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# The Synchronous Clients
#---------------------------------------------------------------------------#
class BaseModbusClient(ModbusClientMixin):
    '''
    Inteface for a modbus synchronous client. Defined here are all the
    methods for performing the related request methods.  Derived classes
    simply need to implement the transport methods and set the correct
    framer.
    '''

    def __init__(self, framer, **kwargs):
        ''' Initialize a client instance

        :param framer: The modbus framer implementation to use
        '''
        self.framer = framer
        if isinstance(self.framer, ModbusSocketFramer):
            self.transaction = DictTransactionManager(self, **kwargs)
        else: self.transaction = FifoTransactionManager(self, **kwargs)

    #-----------------------------------------------------------------------#
    # Client interface
    #-----------------------------------------------------------------------#
    def connect(self):
        ''' Connect to the modbus remote host

        :returns: True if connection succeeded, False otherwise
        '''
        raise NotImplementedException("Method not implemented by derived class")

    def close(self):
        ''' Closes the underlying socket connection
        '''
        pass

    def _send(self, request):
        ''' Sends data on the underlying socket

        :param request: The encoded request to send
        :return: The number of bytes written
        '''
        raise NotImplementedException("Method not implemented by derived class")

    def _recv(self, size):
        ''' Reads data from the underlying descriptor

        :param size: The number of bytes to read
        :return: The bytes read
        '''
        raise NotImplementedException("Method not implemented by derived class")

    #-----------------------------------------------------------------------#
    # Modbus client methods
    #-----------------------------------------------------------------------#
    def execute(self, request=None):
        '''
        :param request: The request to process
        :returns: The result of the request execution
        '''
        if not self.connect():
            raise ConnectionException("Failed to connect[%s]" % (self.__str__()))
        return self.transaction.execute(request)

    #-----------------------------------------------------------------------#
    # The magic methods
    #-----------------------------------------------------------------------#
    def __enter__(self):
        ''' Implement the client with enter block

        :returns: The current instance of the client
        '''
        if not self.connect():
            raise ConnectionException("Failed to connect[%s]" % (self.__str__()))
        return self

    def __exit__(self, klass, value, traceback):
        ''' Implement the client with exit block '''
        self.close()

    def __str__(self):
        ''' Builds a string representation of the connection

        :returns: The string representation
        '''
        return "Null Transport"


#---------------------------------------------------------------------------#
# Modbus TCP Client Transport Implementation
#---------------------------------------------------------------------------#
class ModbusTcpClient(BaseModbusClient):
    ''' Implementation of a modbus tcp client
    '''

    def __init__(self, host='127.0.0.1', port=Defaults.Port,
        framer=ModbusSocketFramer, **kwargs):
        ''' Initialize a client instance

        :param host: The host to connect to (default 127.0.0.1)
        :param port: The modbus port to connect to (default 502)
        :param source_address: The source address tuple to bind to (default ('', 0))
        :param timeout: The timeout to use for this socket (default Defaults.Timeout)
        :param framer: The modbus framer to use (default ModbusSocketFramer)

        .. note:: The host argument will accept ipv4 and ipv6 hosts
        '''
        self.host = host
        self.port = port
        self.source_address = kwargs.get('source_address', ('', 0))
        self.socket = None
        self.timeout  = kwargs.get('timeout',  Defaults.Timeout)
        BaseModbusClient.__init__(self, framer(ClientDecoder()), **kwargs)

    def connect(self):
        ''' Connect to the modbus tcp server

        :returns: True if connection succeeded, False otherwise
        '''
        if self.socket: return True
        try:
            self.socket = socket.create_connection((self.host, self.port),
                timeout=self.timeout, source_address=self.source_address)
        except socket.error as msg:
            _logger.error('Connection to (%s, %s) failed: %s' % \
                (self.host, self.port, msg))
            self.close()
        return self.socket != None

    def close(self):
        ''' Closes the underlying socket connection
        '''
        if self.socket:
            self.socket.close()
        self.socket = None

    def _send(self, request):
        ''' Sends data on the underlying socket

        :param request: The encoded request to send
        :return: The number of bytes written
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        if request:
            return self.socket.send(request)
        return 0

    def _recv(self, size):
        ''' Reads data from the underlying descriptor

        :param size: The number of bytes to read
        :return: The bytes read
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        return self.socket.recv(size)

    def __str__(self):
        ''' Builds a string representation of the connection

        :returns: The string representation
        '''
        return "%s:%s" % (self.host, self.port)


#---------------------------------------------------------------------------#
# Modbus UDP Client Transport Implementation
#---------------------------------------------------------------------------#
class ModbusUdpClient(BaseModbusClient):
    ''' Implementation of a modbus udp client
    '''

    def __init__(self, host='127.0.0.1', port=Defaults.Port,
        framer=ModbusSocketFramer, **kwargs):
        ''' Initialize a client instance

        :param host: The host to connect to (default 127.0.0.1)
        :param port: The modbus port to connect to (default 502)
        :param framer: The modbus framer to use (default ModbusSocketFramer)
        :param timeout: The timeout to use for this socket (default None)
        '''
        self.host    = host
        self.port    = port
        self.socket  = None
        self.timeout = kwargs.get('timeout', None)
        BaseModbusClient.__init__(self, framer(ClientDecoder()), **kwargs)

    @classmethod
    def _get_address_family(cls, address):
        ''' A helper method to get the correct address family
        for a given address.

        :param address: The address to get the af for
        :returns: AF_INET for ipv4 and AF_INET6 for ipv6
        '''
        try:
            _ = socket.inet_pton(socket.AF_INET6, address)
        except socket.error: # not a valid ipv6 address
            return socket.AF_INET
        return socket.AF_INET6

    def connect(self):
        ''' Connect to the modbus tcp server

        :returns: True if connection succeeded, False otherwise
        '''
        if self.socket: return True
        try:
            family = ModbusUdpClient._get_address_family(self.host)
            self.socket = socket.socket(family, socket.SOCK_DGRAM)
            self.socket.settimeout(self.timeout)
        except socket.error as ex:
            _logger.error('Unable to create udp socket %s' % ex)
            self.close()
        return self.socket != None

    def close(self):
        ''' Closes the underlying socket connection
        '''
        self.socket = None

    def _send(self, request):
        ''' Sends data on the underlying socket

        :param request: The encoded request to send
        :return: The number of bytes written
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        if request:
            return self.socket.sendto(request, (self.host, self.port))
        return 0

    def _recv(self, size):
        ''' Reads data from the underlying descriptor

        :param size: The number of bytes to read
        :return: The bytes read
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        return self.socket.recvfrom(size)[0]

    def __str__(self):
        ''' Builds a string representation of the connection

        :returns: The string representation
        '''
        return "%s:%s" % (self.host, self.port)


#---------------------------------------------------------------------------#
# Modbus Serial Client Transport Implementation
#---------------------------------------------------------------------------#
class ModbusSerialClient(BaseModbusClient):
    ''' Implementation of a modbus serial client
    '''

    def __init__(self, method='ascii', **kwargs):
        ''' Initialize a serial client instance

        The methods to connect are::

          - ascii
          - rtu
          - binary

        :param method: The method to use for connection
        :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
        :param timeout: The timeout between serial requests (default 3s)
        '''
        self.method   = method
        self.socket   = None
        BaseModbusClient.__init__(self, self.__implementation(method), **kwargs)

        self.port     = kwargs.get('port', 0)
        self.stopbits = kwargs.get('stopbits', Defaults.Stopbits)
        self.bytesize = kwargs.get('bytesize', Defaults.Bytesize)
        self.parity   = kwargs.get('parity',   Defaults.Parity)
        self.baudrate = kwargs.get('baudrate', Defaults.Baudrate)
        self.timeout  = kwargs.get('timeout',  Defaults.Timeout)
        if self.method == "rtu":
            self._last_frame_end = 0.0
            if self.baudrate > 19200:
                self._silent_interval = 1.75/1000  # ms
            else:
                self._silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate

    @staticmethod
    def __implementation(method):
        ''' Returns the requested framer

        :method: The serial framer to instantiate
        :returns: The requested serial framer
        '''
        method = method.lower()
        if   method == 'ascii':  return ModbusAsciiFramer(ClientDecoder())
        elif method == 'rtu':    return ModbusRtuFramer(ClientDecoder())
        elif method == 'binary': return ModbusBinaryFramer(ClientDecoder())
        elif method == 'socket': return ModbusSocketFramer(ClientDecoder())
        raise ParameterException("Invalid framer method requested")

    def connect(self):
        ''' Connect to the modbus serial server

        :returns: True if connection succeeded, False otherwise
        '''
        if self.socket: return True
        try:
            self.socket = serial.Serial(port=self.port, timeout=self.timeout,
                bytesize=self.bytesize, stopbits=self.stopbits,
                baudrate=self.baudrate, parity=self.parity)
        except serial.SerialException as msg:
            _logger.error(msg)
            self.close()
        if self.method == "rtu":
            self._last_frame_end = time.time()
        return self.socket != None

    def close(self):
        ''' Closes the underlying socket connection
        '''
        if self.socket:
            self.socket.close()
        self.socket = None

    def _send(self, request):
        ''' Sends data on the underlying socket

        If receive buffer still holds some data then flush it.

        Sleep if last send finished less than 3.5 character
        times ago.

        :param request: The encoded request to send
        :return: The number of bytes written
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        if request:
            ts = time.time()
            if self.method == "rtu":
                if ts < self._last_frame_end + self._silent_interval:
                    _logger.debug("will sleep to wait for 3.5 char")
                    time.sleep(self._last_frame_end + self._silent_interval - ts)

            try:
                in_waiting = "in_waiting" if hasattr(self.socket, "in_waiting") else "inWaiting"
                if in_waiting == "in_waiting":
                    waitingbytes = getattr(self.socket, in_waiting)
                else:
                    waitingbytes = getattr(self.socket, in_waiting)()
                if waitingbytes:
                    result = self.socket.read(waitingbytes)
                    if _logger.isEnabledFor(logging.WARNING):
                        _logger.warning("cleanup recv buffer before send: " + " ".join([hex(byte2int(x)) for x in result]))
            except NotImplementedError:
                pass

            size = self.socket.write(request)
            if self.method == "rtu":
                self._last_frame_end = time.time()
            return size
        return 0

    def _recv(self, size):
        ''' Reads data from the underlying descriptor

        :param size: The number of bytes to read
        :return: The bytes read
        '''
        if not self.socket:
            raise ConnectionException(self.__str__())
        result = self.socket.read(size)
        if self.method == "rtu":
            self._last_frame_end = time.time()
        return result

    def __str__(self):
        ''' Builds a string representation of the connection

        :returns: The string representation
        '''
        return "%s baud[%s]" % (self.method, self.baudrate)

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ModbusTcpClient", "ModbusUdpClient", "ModbusSerialClient"
]pymodbus-1.3.2/pymodbus/client/__init__.py0000644000175000017500000000000013150360615016732 0ustar  wmbwmbpymodbus-1.3.2/pymodbus/client/common.py0000644000175000017500000001262613150360615016504 0ustar  wmbwmb'''
Modbus Client Common
----------------------------------

This is a common client mixin that can be used by
both the synchronous and asynchronous clients to
simplify the interface.
'''
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *
from pymodbus.diag_message import *
from pymodbus.file_message import *
from pymodbus.other_message import *


class ModbusClientMixin(object):
    '''
    This is a modbus client mixin that provides additional factory
    methods for all the current modbus methods. This can be used
    instead of the normal pattern of::

       # instead of this
       client = ModbusClient(...)
       request = ReadCoilsRequest(1,10)
       response = client.execute(request)

       # now like this
       client = ModbusClient(...)
       response = client.read_coils(1, 10)
    '''

    def read_coils(self, address, count=1, **kwargs):
        '''

        :param address: The starting address to read from
        :param count: The number of coils to read
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = ReadCoilsRequest(address, count, **kwargs)
        return self.execute(request)

    def read_discrete_inputs(self, address, count=1, **kwargs):
        '''

        :param address: The starting address to read from
        :param count: The number of discretes to read
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = ReadDiscreteInputsRequest(address, count, **kwargs)
        return self.execute(request)

    def write_coil(self, address, value, **kwargs):
        '''

        :param address: The starting address to write to
        :param value: The value to write to the specified address
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = WriteSingleCoilRequest(address, value, **kwargs)
        return self.execute(request)

    def write_coils(self, address, values, **kwargs):
        '''

        :param address: The starting address to write to
        :param values: The values to write to the specified address
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = WriteMultipleCoilsRequest(address, values, **kwargs)
        return self.execute(request)

    def write_register(self, address, value, **kwargs):
        '''

        :param address: The starting address to write to
        :param value: The value to write to the specified address
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = WriteSingleRegisterRequest(address, value, **kwargs)
        return self.execute(request)

    def write_registers(self, address, values, **kwargs):
        '''

        :param address: The starting address to write to
        :param values: The values to write to the specified address
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = WriteMultipleRegistersRequest(address, values, **kwargs)
        return self.execute(request)

    def read_holding_registers(self, address, count=1, **kwargs):
        '''

        :param address: The starting address to read from
        :param count: The number of registers to read
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = ReadHoldingRegistersRequest(address, count, **kwargs)
        return self.execute(request)

    def read_input_registers(self, address, count=1, **kwargs):
        '''

        :param address: The starting address to read from
        :param count: The number of registers to read
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = ReadInputRegistersRequest(address, count, **kwargs)
        return self.execute(request)

    def readwrite_registers(self, *args, **kwargs):
        '''

        :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
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = ReadWriteMultipleRegistersRequest(*args, **kwargs)
        return self.execute(request)

    def mask_write_register(self, *args, **kwargs):
        '''

        :param address: The address of the register to write
        :param and_mask: The and bitmask to apply to the register address
        :param or_mask: The or bitmask to apply to the register address
        :param unit: The slave unit this request is targeting
        :returns: A deferred response handle
        '''
        request = MaskWriteRegisterRequest(*args, **kwargs)
        return self.execute(request)

#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [ 'ModbusClientMixin' ]
pymodbus-1.3.2/pymodbus/exceptions.py0000644000175000017500000000543513150360615016117 0ustar  wmbwmb'''
Pymodbus Exceptions
--------------------

Custom exceptions to be used in the Modbus code.
'''


class ModbusException(Exception):
    ''' Base modbus exception '''

    def __init__(self, string):
        ''' Initialize the exception
        :param string: The message to append to the error
        '''
        self.string = string

    def __str__(self):
        return 'Modbus Error: %s' % self.string


class ModbusIOException(ModbusException):
    ''' Error resulting from data i/o '''

    def __init__(self, string=""):
        ''' Initialize the exception
        :param string: The message to append to the error
        '''
        message = "[Input/Output] %s" % string
        ModbusException.__init__(self, message)


class ParameterException(ModbusException):
    ''' Error resulting from invalid parameter '''

    def __init__(self, string=""):
        ''' Initialize the exception

        :param string: The message to append to the error
        '''
        message = "[Invalid Parameter] %s" % string
        ModbusException.__init__(self, message)


class NoSuchSlaveException(ModbusException):
    ''' Error resulting from making a request to a slave
    that does not exist '''

    def __init__(self, string=""):
        ''' Initialize the exception

        :param string: The message to append to the error
        '''
        message = "[No Such Slave] %s" % string
        ModbusException.__init__(self, message)


class NotImplementedException(ModbusException):
    ''' Error resulting from not implemented function '''

    def __init__(self, string=""):
        ''' Initialize the exception
        :param string: The message to append to the error
        '''
        message = "[Not Implemented] %s" % string
        ModbusException.__init__(self, message)


class ConnectionException(ModbusException):
    ''' Error resulting from a bad connection '''

    def __init__(self, string=""):
        ''' Initialize the exception

        :param string: The message to append to the error
        '''
        message = "[Connection] %s" % string
        ModbusException.__init__(self, message)


class InvalidMessageRecievedException(ModbusException):
    """
    Error resulting from invalid response received or decoded
    """

    def __init__(self, string=""):
        ''' Initialize the exception

        :param string: The message to append to the error
        '''
        message = "[Invalid Message] %s" % string
        ModbusException.__init__(self, message)


#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
    "ModbusException", "ModbusIOException",
    "ParameterException", "NotImplementedException",
    "ConnectionException", "NoSuchSlaveException",
]
pymodbus-1.3.2/pymodbus/compat.py0000644000175000017500000000664313150360615015223 0ustar  wmbwmb'''
Python 2.x/3.x Compatibility Layer
-------------------------------------------------

This is mostly based on the jinja2 compat code:

    Some py2/py3 compatibility support based on a stripped down
    version of six so we don't have to depend on a specific version
    of it.

    :copyright: Copyright 2013 by the Jinja team, see AUTHORS.
    :license: BSD, see LICENSE for details.
'''
import sys
import struct

#---------------------------------------------------------------------------#
# python version checks
#---------------------------------------------------------------------------#
IS_PYTHON2 = sys.version_info[0] == 2
IS_PYTHON3 = sys.version_info[0] == 3
IS_PYPY    = hasattr(sys, 'pypy_translation_info')
IS_JYTHON  = sys.platform.startswith('java')

#---------------------------------------------------------------------------#
# python > 3.3 compatability layer
#---------------------------------------------------------------------------#
if not IS_PYTHON2:
    #-----------------------------------------------------------------------#
    # portable builtins
    #-----------------------------------------------------------------------#
    int2byte     = lambda b: struct.pack('B', b)
    byte2int     = lambda b: b
    unichr       = chr
    range_type   = range
    text_type    = str
    string_types = (str,)
    iterkeys     = lambda d: iter(d.keys())
    itervalues   = lambda d: iter(d.values())
    iteritems    = lambda d: iter(d.items())
    get_next     = lambda x: x.__next__()

    #-----------------------------------------------------------------------#
    # module renames
    #-----------------------------------------------------------------------#
    from io import BytesIO, StringIO
    NativeStringIO = StringIO

    ifilter = filter
    imap = map
    izip = zip
    intern = sys.intern

    import socketserver

    #-----------------------------------------------------------------------#
    # decorators
    #-----------------------------------------------------------------------#
    implements_to_string = lambda x: x

#---------------------------------------------------------------------------#
# python > 2.5 compatability layer
#---------------------------------------------------------------------------#
else:
    #-----------------------------------------------------------------------#
    # portable builtins
    #-----------------------------------------------------------------------#
    int2byte     = chr
    byte2int     = ord
    unichr       = unichr
    text_type    = unicode
    range_type   = xrange
    string_types = (str, unicode)
    iterkeys     = lambda d: d.iterkeys()
    itervalues   = lambda d: d.itervalues()
    iteritems    = lambda d: d.iteritems()
    get_next     = lambda x: x.next()

    #-----------------------------------------------------------------------#
    # module renames
    #-----------------------------------------------------------------------#
    from cStringIO import StringIO as BytesIO, StringIO
    NativeStringIO = BytesIO

    from itertools import imap, izip, ifilter
    intern = intern

    import SocketServer as socketserver

    #-----------------------------------------------------------------------#
    # decorators
    #-----------------------------------------------------------------------#
    def implements_to_string(klass):
        klass.__unicode__ = klass.__str__
        klass.__str__ = lambda x: x.__unicode__().encode('utf-8')
        return klass
pymodbus-1.3.2/requirements.txt0000644000175000017500000000207613150360615015004 0ustar  wmbwmbcoloredlogs>=7.0
# -------------------------------------------------------------------
# if want to use the pymodbus serial stack, uncomment these
# -------------------------------------------------------------------
#pyserial==3.3
# -------------------------------------------------------------------
# if you want to run the tests and code coverage, uncomment these
# -------------------------------------------------------------------
#coverage==4.4
#mock==2.0.0
#nose==1.3.7
#pep8==1.7.0
# -------------------------------------------------------------------
# if you want to use the asynchronous version, uncomment these
# -------------------------------------------------------------------
#Twisted==17.1.0
#zope.interface==4.4.0
#pyasn1==0.2.3
#pycrypto==2.6.1
#wsgiref==0.1.2
#cryptography==1.8.1
# -------------------------------------------------------------------
# if you want to build the documentation, uncomment these
# -------------------------------------------------------------------
#Jinja2==2.9.6
#Pygments==2.2.0
#Sphinx==1.5.5
#docutils==0.13.1
#pydoctor==16.3.0

pymodbus-1.3.2/doc/0000755000175000017500000000000013150640276012264 5ustar  wmbwmbpymodbus-1.3.2/doc/quality/0000755000175000017500000000000013150635760013756 5ustar  wmbwmbpymodbus-1.3.2/doc/quality/current.coverage0000644000175000017500000000451113150360615017150 0ustar  wmbwmbName                              Stmts   Miss  Cover   Missing
---------------------------------------------------------------
pymodbus                             15      6    60%   24-27, 36-37
pymodbus.bit_read_message            68      0   100%   
pymodbus.bit_write_message           95      0   100%   
pymodbus.client                       0      0   100%   
pymodbus.client.async                70      0   100%   
pymodbus.client.common               36      0   100%   
pymodbus.client.sync                147      0   100%   
pymodbus.constants                   36      0   100%   
pymodbus.datastore                    5      0   100%   
pymodbus.datastore.context           50      0   100%   
pymodbus.datastore.remote            31      0   100%   
pymodbus.datastore.store             67      0   100%   
pymodbus.device                     159      0   100%   
pymodbus.diag_message               202      0   100%   
pymodbus.events                      60      0   100%   
pymodbus.exceptions                  22      0   100%   
pymodbus.factory                     77      0   100%   
pymodbus.file_message               181      0   100%   
pymodbus.interfaces                  46      0   100%   
pymodbus.internal                     0      0   100%   
pymodbus.internal.ptwisted           16      2    88%   29-30
pymodbus.mei_message                 70      0   100%   
pymodbus.other_message              145      0   100%   
pymodbus.payload                    140      2    99%   205, 224
pymodbus.pdu                         72      0   100%   
pymodbus.register_read_message      124      0   100%   
pymodbus.register_write_message      91      2    98%   39, 148
pymodbus.server                       0      0   100%   
pymodbus.server.async               113     39    65%   55-58, 65-74, 81-86, 151-156, 163-172, 180-184
pymodbus.server.sync                186      0   100%   
pymodbus.transaction                275     53    81%   63-81, 116-117, 259, 263, 403, 433-442, 577-586, 656, 733-742, 768-769
pymodbus.utilities                   67      0   100%   
pymodbus.version                     13      0   100%   
---------------------------------------------------------------
TOTAL                              2679    104    96%   
----------------------------------------------------------------------
Ran 255 tests in 0.981s

OK
pymodbus-1.3.2/doc/quality/current.pep80000644000175000017500000011747613150360615016250 0ustar  wmbwmbrunning pep8
pymodbus/__init__.py:16:11: E221 multiple spaces before operator
pymodbus/bit_read_message.py:26:19: E221 multiple spaces before operator
pymodbus/bit_read_message.py:143:80: E501 line too long (80 characters)
pymodbus/bit_read_message.py:202:80: E501 line too long (80 characters)
pymodbus/bit_write_message.py:19:14: E221 multiple spaces before operator
pymodbus/bit_write_message.py:58:15: E221 multiple spaces before operator
pymodbus/bit_write_message.py:59:22: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:60:13: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:117:15: E221 multiple spaces before operator
pymodbus/bit_write_message.py:118:22: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:119:13: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:160:22: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:161:45: E701 multiple statements on one line (colon)
pymodbus/bit_write_message.py:162:20: E221 multiple spaces before operator
pymodbus/bit_write_message.py:170:14: E221 multiple spaces before operator
pymodbus/bit_write_message.py:172:15: E221 multiple spaces before operator
pymodbus/constants.py:74:9: E221 multiple spaces before operator
pymodbus/constants.py:75:12: E221 multiple spaces before operator
pymodbus/constants.py:76:12: E221 multiple spaces before operator
pymodbus/constants.py:77:15: E221 multiple spaces before operator
pymodbus/constants.py:79:15: E221 multiple spaces before operator
pymodbus/constants.py:80:11: E221 multiple spaces before operator
pymodbus/constants.py:81:13: E221 multiple spaces before operator
pymodbus/constants.py:82:11: E221 multiple spaces before operator
pymodbus/constants.py:83:13: E221 multiple spaces before operator
pymodbus/constants.py:84:13: E221 multiple spaces before operator
pymodbus/constants.py:118:12: E221 multiple spaces before operator
pymodbus/constants.py:119:10: E221 multiple spaces before operator
pymodbus/constants.py:120:7: E221 multiple spaces before operator
pymodbus/constants.py:121:8: E221 multiple spaces before operator
pymodbus/constants.py:122:12: E221 multiple spaces before operator
pymodbus/constants.py:145:9: E221 multiple spaces before operator
pymodbus/constants.py:146:8: E221 multiple spaces before operator
pymodbus/constants.py:163:18: E221 multiple spaces before operator
pymodbus/constants.py:193:10: E221 multiple spaces before operator
pymodbus/constants.py:194:12: E221 multiple spaces before operator
pymodbus/constants.py:210:12: E221 multiple spaces before operator
pymodbus/device.py:30:13: E126 continuation line over-indented for hanging indent
pymodbus/device.py:88:41: E203 whitespace before ':'
pymodbus/device.py:89:41: E203 whitespace before ':'
pymodbus/device.py:90:41: E203 whitespace before ':'
pymodbus/device.py:91:41: E203 whitespace before ':'
pymodbus/device.py:92:41: E203 whitespace before ':'
pymodbus/device.py:93:41: E203 whitespace before ':'
pymodbus/device.py:94:41: E203 whitespace before ':'
pymodbus/device.py:96:41: E203 whitespace before ':'
pymodbus/device.py:97:41: E203 whitespace before ':'
pymodbus/device.py:98:41: E203 whitespace before ':'
pymodbus/device.py:99:41: E203 whitespace before ':'
pymodbus/device.py:100:41: E203 whitespace before ':'
pymodbus/device.py:101:41: E203 whitespace before ':'
pymodbus/device.py:102:41: E203 whitespace before ':'
pymodbus/device.py:103:41: E203 whitespace before ':'
pymodbus/device.py:105:41: E203 whitespace before ':'
pymodbus/device.py:106:41: E203 whitespace before ':'
pymodbus/device.py:107:41: E203 whitespace before ':'
pymodbus/device.py:108:41: E203 whitespace before ':'
pymodbus/device.py:109:41: E203 whitespace before ':'
pymodbus/device.py:110:41: E203 whitespace before ':'
pymodbus/device.py:111:41: E203 whitespace before ':'
pymodbus/device.py:112:41: E203 whitespace before ':'
pymodbus/device.py:113:41: E203 whitespace before ':'
pymodbus/device.py:114:41: E203 whitespace before ':'
pymodbus/device.py:115:41: E203 whitespace before ':'
pymodbus/device.py:116:41: E203 whitespace before ':'
pymodbus/device.py:117:41: E203 whitespace before ':'
pymodbus/device.py:118:41: E203 whitespace before ':'
pymodbus/device.py:120:41: E203 whitespace before ':'
pymodbus/device.py:121:41: E203 whitespace before ':'
pymodbus/device.py:122:41: E203 whitespace before ':'
pymodbus/device.py:123:41: E203 whitespace before ':'
pymodbus/device.py:124:41: E203 whitespace before ':'
pymodbus/device.py:125:41: E203 whitespace before ':'
pymodbus/device.py:126:41: E203 whitespace before ':'
pymodbus/device.py:127:41: E203 whitespace before ':'
pymodbus/device.py:128:41: E203 whitespace before ':'
pymodbus/device.py:129:41: E203 whitespace before ':'
pymodbus/device.py:131:41: E203 whitespace before ':'
pymodbus/device.py:132:41: E203 whitespace before ':'
pymodbus/device.py:133:41: E203 whitespace before ':'
pymodbus/device.py:134:41: E203 whitespace before ':'
pymodbus/device.py:135:41: E203 whitespace before ':'
pymodbus/device.py:136:41: E203 whitespace before ':'
pymodbus/device.py:137:41: E203 whitespace before ':'
pymodbus/device.py:138:41: E203 whitespace before ':'
pymodbus/device.py:88:55: E261 at least two spaces before inline comment
pymodbus/device.py:89:55: E261 at least two spaces before inline comment
pymodbus/device.py:90:55: E261 at least two spaces before inline comment
pymodbus/device.py:91:55: E261 at least two spaces before inline comment
pymodbus/device.py:92:55: E261 at least two spaces before inline comment
pymodbus/device.py:93:55: E261 at least two spaces before inline comment
pymodbus/device.py:94:55: E261 at least two spaces before inline comment
pymodbus/device.py:131:55: E261 at least two spaces before inline comment
pymodbus/device.py:132:55: E261 at least two spaces before inline comment
pymodbus/device.py:133:55: E261 at least two spaces before inline comment
pymodbus/device.py:134:55: E261 at least two spaces before inline comment
pymodbus/device.py:135:55: E261 at least two spaces before inline comment
pymodbus/device.py:136:55: E261 at least two spaces before inline comment
pymodbus/device.py:137:55: E261 at least two spaces before inline comment
pymodbus/device.py:138:55: E261 at least two spaces before inline comment
pymodbus/device.py:175:53: E225 missing whitespace around operator
pymodbus/device.py:273:15: E221 multiple spaces before operator
pymodbus/device.py:274:16: E221 multiple spaces before operator
pymodbus/device.py:275:23: E221 multiple spaces before operator
pymodbus/device.py:276:14: E221 multiple spaces before operator
pymodbus/device.py:277:16: E221 multiple spaces before operator
pymodbus/device.py:278:14: E221 multiple spaces before operator
pymodbus/device.py:289:80: E501 line too long (81 characters)
pymodbus/device.py:290:80: E501 line too long (81 characters)
pymodbus/device.py:289:45: E231 missing whitespace after ','
pymodbus/device.py:289:47: E231 missing whitespace after ','
pymodbus/device.py:290:45: E231 missing whitespace after ','
pymodbus/device.py:290:47: E231 missing whitespace after ','
pymodbus/device.py:291:45: E231 missing whitespace after ','
pymodbus/device.py:291:47: E231 missing whitespace after ','
pymodbus/device.py:292:45: E231 missing whitespace after ','
pymodbus/device.py:292:47: E231 missing whitespace after ','
pymodbus/device.py:289:33: E272 multiple spaces before keyword
pymodbus/device.py:290:35: E272 multiple spaces before keyword
pymodbus/device.py:315:17: E201 whitespace after '{'
pymodbus/device.py:315:47: E202 whitespace before '}'
pymodbus/device.py:315:27: E231 missing whitespace after ':'
pymodbus/device.py:401:12: E221 multiple spaces before operator
pymodbus/device.py:442:25: E701 multiple statements on one line (colon)
pymodbus/device.py:449:15: E221 multiple spaces before operator
pymodbus/device.py:451:22: E221 multiple spaces before operator
pymodbus/device.py:452:17: E221 multiple spaces before operator
pymodbus/device.py:453:20: E221 multiple spaces before operator
pymodbus/device.py:454:13: E221 multiple spaces before operator
pymodbus/device.py:455:14: E221 multiple spaces before operator
pymodbus/device.py:456:24: E221 multiple spaces before operator
pymodbus/device.py:457:10: E221 multiple spaces before operator
pymodbus/device.py:478:11: E221 multiple spaces before operator
pymodbus/device.py:479:13: E221 multiple spaces before operator
pymodbus/device.py:527:12: E221 multiple spaces before operator
pymodbus/device.py:528:11: E221 multiple spaces before operator
pymodbus/device.py:529:9: E221 multiple spaces before operator
pymodbus/device.py:612:9: E126 continuation line over-indented for hanging indent
pymodbus/diag_message.py:135:80: E501 line too long (81 characters)
pymodbus/diag_message.py:174:13: E701 multiple statements on one line (colon)
pymodbus/diag_message.py:200:13: E701 multiple statements on one line (colon)
pymodbus/diag_message.py:224:25: E221 multiple spaces before operator
pymodbus/diag_message.py:225:13: E701 multiple statements on one line (colon)
pymodbus/diag_message.py:254:25: E221 multiple spaces before operator
pymodbus/diag_message.py:255:13: E701 multiple statements on one line (colon)
pymodbus/diag_message.py:349:19: E221 multiple spaces before operator
pymodbus/diag_message.py:593:80: E501 line too long (80 characters)
pymodbus/diag_message.py:596:80: E501 line too long (80 characters)
pymodbus/diag_message.py:612:80: E501 line too long (82 characters)
pymodbus/diag_message.py:615:80: E501 line too long (80 characters)
pymodbus/diag_message.py:702:23: E261 at least two spaces before inline comment
pymodbus/diag_message.py:705:13: E701 multiple statements on one line (colon)
pymodbus/diag_message.py:725:80: E501 line too long (80 characters)
pymodbus/diag_message.py:731:80: E501 line too long (90 characters)
pymodbus/diag_message.py:732:80: E501 line too long (82 characters)
pymodbus/diag_message.py:737:80: E501 line too long (96 characters)
pymodbus/events.py:54:21: E221 multiple spaces before operator
pymodbus/events.py:55:20: E221 multiple spaces before operator
pymodbus/events.py:63:13: E221 multiple spaces before operator
pymodbus/events.py:74:21: E221 multiple spaces before operator
pymodbus/events.py:75:20: E221 multiple spaces before operator
pymodbus/events.py:105:18: E221 multiple spaces before operator
pymodbus/events.py:106:25: E221 multiple spaces before operator
pymodbus/events.py:107:24: E221 multiple spaces before operator
pymodbus/events.py:108:23: E221 multiple spaces before operator
pymodbus/events.py:110:20: E221 multiple spaces before operator
pymodbus/events.py:118:13: E128 continuation line under-indented for visual indent
pymodbus/events.py:119:13: E221 multiple spaces before operator
pymodbus/events.py:130:18: E221 multiple spaces before operator
pymodbus/events.py:131:25: E221 multiple spaces before operator
pymodbus/events.py:132:24: E221 multiple spaces before operator
pymodbus/events.py:133:23: E221 multiple spaces before operator
pymodbus/events.py:135:20: E221 multiple spaces before operator
pymodbus/factory.py:44:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:45:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:46:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:47:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:48:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:49:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:50:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:51:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:52:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:54:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:56:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:57:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:58:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:59:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:61:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:62:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:63:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:64:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:66:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:69:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:70:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:71:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:72:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:73:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:74:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:75:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:76:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:77:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:78:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:79:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:80:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:81:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:82:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:83:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:84:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:85:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:87:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:94:80: E501 line too long (83 characters)
pymodbus/factory.py:138:23: E701 multiple statements on one line (colon)
pymodbus/factory.py:152:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:153:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:154:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:155:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:156:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:157:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:158:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:159:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:160:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:162:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:164:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:165:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:166:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:167:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:169:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:170:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:171:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:172:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:174:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:177:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:178:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:179:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:180:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:181:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:182:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:183:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:184:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:185:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:186:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:187:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:188:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:189:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:190:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:191:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:192:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:193:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:195:13: E126 continuation line over-indented for hanging indent
pymodbus/factory.py:202:80: E501 line too long (83 characters)
pymodbus/factory.py:249:23: E701 multiple statements on one line (colon)
pymodbus/file_message.py:30:28: E221 multiple spaces before operator
pymodbus/file_message.py:31:25: E221 multiple spaces before operator
pymodbus/file_message.py:32:27: E221 multiple spaces before operator
pymodbus/file_message.py:33:25: E221 multiple spaces before operator
pymodbus/file_message.py:34:80: E501 line too long (87 characters)
pymodbus/file_message.py:34:27: E221 multiple spaces before operator
pymodbus/file_message.py:35:80: E501 line too long (87 characters)
pymodbus/file_message.py:41:12: E127 continuation line over-indented for visual indent
pymodbus/file_message.py:42:12: E127 continuation line over-indented for visual indent
pymodbus/file_message.py:43:12: E127 continuation line over-indented for visual indent
pymodbus/file_message.py:44:12: E127 continuation line over-indented for visual indent
pymodbus/file_message.py:41:32: E221 multiple spaces before operator
pymodbus/file_message.py:42:34: E221 multiple spaces before operator
pymodbus/file_message.py:43:34: E221 multiple spaces before operator
pymodbus/file_message.py:44:32: E221 multiple spaces before operator
pymodbus/file_message.py:92:21: E221 multiple spaces before operator
pymodbus/file_message.py:102:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:113:62: E225 missing whitespace around operator
pymodbus/file_message.py:115:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:114:19: E221 multiple spaces before operator
pymodbus/file_message.py:116:34: E701 multiple statements on one line (colon)
pymodbus/file_message.py:154:14: E221 multiple spaces before operator
pymodbus/file_message.py:169:80: E501 line too long (87 characters)
pymodbus/file_message.py:169:84: E225 missing whitespace around operator
pymodbus/file_message.py:170:41: E261 at least two spaces before inline comment
pymodbus/file_message.py:172:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:173:38: E701 multiple statements on one line (colon)
pymodbus/file_message.py:192:21: E221 multiple spaces before operator
pymodbus/file_message.py:199:80: E501 line too long (85 characters)
pymodbus/file_message.py:203:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:215:62: E225 missing whitespace around operator
pymodbus/file_message.py:217:18: E221 multiple spaces before operator
pymodbus/file_message.py:219:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:220:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:218:19: E221 multiple spaces before operator
pymodbus/file_message.py:221:34: E701 multiple statements on one line (colon)
pymodbus/file_message.py:248:21: E221 multiple spaces before operator
pymodbus/file_message.py:255:80: E501 line too long (85 characters)
pymodbus/file_message.py:259:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:271:62: E225 missing whitespace around operator
pymodbus/file_message.py:273:18: E221 multiple spaces before operator
pymodbus/file_message.py:275:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:276:17: E128 continuation line under-indented for visual indent
pymodbus/file_message.py:274:19: E221 multiple spaces before operator
pymodbus/file_message.py:277:34: E701 multiple statements on one line (colon)
pymodbus/file_message.py:298:21: E221 multiple spaces before operator
pymodbus/file_message.py:300:21: E221 multiple spaces before operator
pymodbus/file_message.py:331:80: E501 line too long (83 characters)
pymodbus/file_message.py:350:21: E221 multiple spaces before operator
pymodbus/file_message.py:352:21: E221 multiple spaces before operator
pymodbus/mei_message.py:43:13: E128 continuation line under-indented for visual indent
pymodbus/mei_message.py:66:13: E128 continuation line under-indented for visual indent
pymodbus/mei_message.py:91:17: E261 at least two spaces before inline comment
pymodbus/mei_message.py:94:69: E225 missing whitespace around operator
pymodbus/mei_message.py:108:31: E261 at least two spaces before inline comment
pymodbus/mei_message.py:111:35: E261 at least two spaces before inline comment
pymodbus/mei_message.py:120:13: E128 continuation line under-indented for visual indent
pymodbus/mei_message.py:121:13: E128 continuation line under-indented for visual indent
pymodbus/mei_message.py:138:40: E261 at least two spaces before inline comment
pymodbus/mei_message.py:141:80: E501 line too long (80 characters)
pymodbus/mei_message.py:141:77: E225 missing whitespace around operator
pymodbus/mei_message.py:143:53: E225 missing whitespace around operator
pymodbus/other_message.py:187:23: E701 multiple statements on one line (colon)
pymodbus/other_message.py:188:13: E701 multiple statements on one line (colon)
pymodbus/other_message.py:257:28: E203 whitespace before ':'
pymodbus/other_message.py:258:28: E203 whitespace before ':'
pymodbus/other_message.py:259:28: E203 whitespace before ':'
pymodbus/other_message.py:260:28: E203 whitespace before ':'
pymodbus/other_message.py:301:23: E701 multiple statements on one line (colon)
pymodbus/other_message.py:302:13: E701 multiple statements on one line (colon)
pymodbus/other_message.py:303:15: E221 multiple spaces before operator
pymodbus/other_message.py:329:80: E501 line too long (91 characters)
pymodbus/other_message.py:400:23: E701 multiple statements on one line (colon)
pymodbus/other_message.py:401:13: E701 multiple statements on one line (colon)
pymodbus/payload.py:36:21: E221 multiple spaces before operator
pymodbus/payload.py:61:27: E225 missing whitespace around operator
pymodbus/payload.py:186:21: E221 multiple spaces before operator
pymodbus/pdu.py:79:13: E701 multiple statements on one line (colon)
pymodbus/pdu.py:97:17: E128 continuation line under-indented for visual indent
pymodbus/pdu.py:129:20: E221 multiple spaces before operator
pymodbus/pdu.py:130:19: E221 multiple spaces before operator
pymodbus/pdu.py:131:17: E221 multiple spaces before operator
pymodbus/pdu.py:132:17: E221 multiple spaces before operator
pymodbus/pdu.py:133:16: E221 multiple spaces before operator
pymodbus/pdu.py:134:14: E221 multiple spaces before operator
pymodbus/pdu.py:135:22: E221 multiple spaces before operator
pymodbus/pdu.py:136:27: E221 multiple spaces before operator
pymodbus/pdu.py:137:22: E221 multiple spaces before operator
pymodbus/register_read_message.py:128:80: E501 line too long (80 characters)
pymodbus/register_read_message.py:178:80: E501 line too long (80 characters)
pymodbus/register_read_message.py:226:26: E221 multiple spaces before operator
pymodbus/register_read_message.py:227:24: E221 multiple spaces before operator
pymodbus/register_read_message.py:228:27: E221 multiple spaces before operator
pymodbus/register_read_message.py:241:17: E128 continuation line under-indented for visual indent
pymodbus/register_read_message.py:242:17: E128 continuation line under-indented for visual indent
pymodbus/register_read_message.py:241:54: E502 the backslash is redundant between brackets
pymodbus/register_read_message.py:253:9: E122 continuation line missing indentation or outdented
pymodbus/register_read_message.py:254:9: E122 continuation line missing indentation or outdented
pymodbus/register_read_message.py:255:29: E221 multiple spaces before operator
pymodbus/register_write_message.py:133:22: E701 multiple statements on one line (colon)
pymodbus/register_write_message.py:134:45: E701 multiple statements on one line (colon)
pymodbus/register_write_message.py:155:9: E122 continuation line missing indentation or outdented
pymodbus/transaction.py:9:24: E272 multiple spaces before keyword
pymodbus/transaction.py:11:24: E272 multiple spaces before keyword
pymodbus/transaction.py:12:24: E272 multiple spaces before keyword
pymodbus/transaction.py:66:80: E501 line too long (85 characters)
pymodbus/transaction.py:67:22: E702 multiple statements on one line (semicolon)
pymodbus/transaction.py:146:31: E231 missing whitespace after ':'
pymodbus/transaction.py:146:40: E231 missing whitespace after ':'
pymodbus/transaction.py:146:49: E231 missing whitespace after ':'
pymodbus/transaction.py:146:58: E231 missing whitespace after ':'
pymodbus/transaction.py:147:21: E221 multiple spaces before operator
pymodbus/transaction.py:148:21: E221 multiple spaces before operator
pymodbus/transaction.py:159:13: E122 continuation line missing indentation or outdented
pymodbus/transaction.py:160:21: E126 continuation line over-indented for hanging indent
pymodbus/transaction.py:179:31: E231 missing whitespace after ':'
pymodbus/transaction.py:179:40: E231 missing whitespace after ':'
pymodbus/transaction.py:179:49: E231 missing whitespace after ':'
pymodbus/transaction.py:179:58: E231 missing whitespace after ':'
pymodbus/transaction.py:244:17: E701 multiple statements on one line (colon)
pymodbus/transaction.py:253:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:254:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:255:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:305:21: E221 multiple spaces before operator
pymodbus/transaction.py:306:19: E221 multiple spaces before operator
pymodbus/transaction.py:308:21: E221 multiple spaces before operator
pymodbus/transaction.py:380:14: E221 multiple spaces before operator
pymodbus/transaction.py:381:12: E221 multiple spaces before operator
pymodbus/transaction.py:383:19: E701 multiple statements on one line (colon)
pymodbus/transaction.py:423:17: E701 multiple statements on one line (colon)
pymodbus/transaction.py:432:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:433:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:463:31: E231 missing whitespace after ':'
pymodbus/transaction.py:463:45: E231 missing whitespace after ':'
pymodbus/transaction.py:463:54: E231 missing whitespace after ':'
pymodbus/transaction.py:464:21: E221 multiple spaces before operator
pymodbus/transaction.py:465:21: E221 multiple spaces before operator
pymodbus/transaction.py:466:19: E221 multiple spaces before operator
pymodbus/transaction.py:467:21: E221 multiple spaces before operator
pymodbus/transaction.py:478:23: E701 multiple statements on one line (colon)
pymodbus/transaction.py:479:21: E203 whitespace before ':'
pymodbus/transaction.py:499:31: E231 missing whitespace after ':'
pymodbus/transaction.py:499:45: E231 missing whitespace after ':'
pymodbus/transaction.py:499:54: E231 missing whitespace after ':'
pymodbus/transaction.py:524:14: E221 multiple spaces before operator
pymodbus/transaction.py:525:12: E221 multiple spaces before operator
pymodbus/transaction.py:527:19: E701 multiple statements on one line (colon)
pymodbus/transaction.py:567:17: E701 multiple statements on one line (colon)
pymodbus/transaction.py:576:16: E221 multiple spaces before operator
pymodbus/transaction.py:577:15: E221 multiple spaces before operator
pymodbus/transaction.py:620:31: E231 missing whitespace after ':'
pymodbus/transaction.py:620:45: E231 missing whitespace after ':'
pymodbus/transaction.py:620:54: E231 missing whitespace after ':'
pymodbus/transaction.py:621:21: E221 multiple spaces before operator
pymodbus/transaction.py:622:21: E221 multiple spaces before operator
pymodbus/transaction.py:623:19: E221 multiple spaces before operator
pymodbus/transaction.py:624:21: E221 multiple spaces before operator
pymodbus/transaction.py:635:23: E701 multiple statements on one line (colon)
pymodbus/transaction.py:636:21: E203 whitespace before ':'
pymodbus/transaction.py:643:80: E501 line too long (85 characters)
pymodbus/transaction.py:655:31: E231 missing whitespace after ':'
pymodbus/transaction.py:655:45: E231 missing whitespace after ':'
pymodbus/transaction.py:655:54: E231 missing whitespace after ':'
pymodbus/transaction.py:680:14: E221 multiple spaces before operator
pymodbus/transaction.py:681:12: E221 multiple spaces before operator
pymodbus/transaction.py:683:19: E701 multiple statements on one line (colon)
pymodbus/transaction.py:723:17: E701 multiple statements on one line (colon)
pymodbus/transaction.py:733:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:734:13: E128 continuation line under-indented for visual indent
pymodbus/transaction.py:749:31: E701 multiple statements on one line (colon)
pymodbus/transaction.py:750:17: E701 multiple statements on one line (colon)
pymodbus/utilities.py:64:15: E701 multiple statements on one line (colon)
pymodbus/utilities.py:69:13: E701 multiple statements on one line (colon)
pymodbus/utilities.py:110:17: E701 multiple statements on one line (colon)
pymodbus/utilities.py:131:51: E702 multiple statements on one line (semicolon)
pymodbus/client/async.py:115:17: E701 multiple statements on one line (colon)
pymodbus/client/async.py:130:32: E261 at least two spaces before inline comment
pymodbus/client/async.py:187:17: E701 multiple statements on one line (colon)
pymodbus/client/async.py:198:32: E261 at least two spaces before inline comment
pymodbus/client/sync.py:47:80: E501 line too long (80 characters)
pymodbus/client/sync.py:60:80: E501 line too long (80 characters)
pymodbus/client/sync.py:68:80: E501 line too long (80 characters)
pymodbus/client/sync.py:79:80: E501 line too long (81 characters)
pymodbus/client/sync.py:93:80: E501 line too long (81 characters)
pymodbus/client/sync.py:137:23: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:139:80: E501 line too long (92 characters)
pymodbus/client/sync.py:143:17: E128 continuation line under-indented for visual indent
pymodbus/client/sync.py:142:65: E502 the backslash is redundant between brackets
pymodbus/client/sync.py:145:28: E711 comparison to None should be 'if cond is not None:'
pymodbus/client/sync.py:212:29: E261 at least two spaces before inline comment
pymodbus/client/sync.py:221:23: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:228:28: E711 comparison to None should be 'if cond is not None:'
pymodbus/client/sync.py:289:20: E221 multiple spaces before operator
pymodbus/client/sync.py:290:20: E221 multiple spaces before operator
pymodbus/client/sync.py:293:18: E221 multiple spaces before operator
pymodbus/client/sync.py:296:20: E221 multiple spaces before operator
pymodbus/client/sync.py:298:21: E221 multiple spaces before operator
pymodbus/client/sync.py:308:31: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:308:32: E272 multiple spaces before keyword
pymodbus/client/sync.py:309:29: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:309:30: E272 multiple spaces before keyword
pymodbus/client/sync.py:310:32: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:318:23: E701 multiple statements on one line (colon)
pymodbus/client/sync.py:321:17: E128 continuation line under-indented for visual indent
pymodbus/client/sync.py:322:17: E128 continuation line under-indented for visual indent
pymodbus/client/sync.py:326:28: E711 comparison to None should be 'if cond is not None:'
pymodbus/server/async.py:218:5: E128 continuation line under-indented for visual indent
pymodbus/server/async.py:218:5: E125 continuation line does not distinguish itself from next logical line
pymodbus/server/async.py:236:43: E261 at least two spaces before inline comment
pymodbus/server/sync.py:78:80: E501 line too long (81 characters)
pymodbus/server/sync.py:80:80: E501 line too long (81 characters)
pymodbus/server/sync.py:84:80: E501 line too long (80 characters)
pymodbus/server/sync.py:91:80: E501 line too long (80 characters)
pymodbus/server/sync.py:110:34: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:113:19: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:140:28: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:144:34: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:148:19: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:177:28: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:181:34: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:185:19: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:224:30: E272 multiple spaces before keyword
pymodbus/server/sync.py:224:20: E221 multiple spaces before operator
pymodbus/server/sync.py:232:13: E128 continuation line under-indented for visual indent
pymodbus/server/sync.py:248:35: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:273:30: E272 multiple spaces before keyword
pymodbus/server/sync.py:273:20: E221 multiple spaces before operator
pymodbus/server/sync.py:281:13: E128 continuation line under-indented for visual indent
pymodbus/server/sync.py:289:33: E261 at least two spaces before inline comment
pymodbus/server/sync.py:298:35: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:329:30: E272 multiple spaces before keyword
pymodbus/server/sync.py:329:20: E221 multiple spaces before operator
pymodbus/server/sync.py:336:20: E221 multiple spaces before operator
pymodbus/server/sync.py:339:20: E221 multiple spaces before operator
pymodbus/server/sync.py:341:21: E221 multiple spaces before operator
pymodbus/server/sync.py:342:20: E221 multiple spaces before operator
pymodbus/server/sync.py:350:23: E701 multiple statements on one line (colon)
pymodbus/server/sync.py:353:17: E128 continuation line under-indented for visual indent
pymodbus/server/sync.py:354:17: E128 continuation line under-indented for visual indent
pymodbus/server/sync.py:358:28: E711 comparison to None should be 'if cond is not None:'
pymodbus/server/sync.py:370:13: E128 continuation line under-indented for visual indent
pymodbus/server/sync.py:381:19: E701 multiple statements on one line (colon)
pymodbus/internal/ptwisted.py:53:15: E701 multiple statements on one line (colon)
pymodbus/datastore/context.py:9:15: E702 multiple statements on one line (semicolon)
pymodbus/datastore/context.py:101:20: E221 multiple spaces before operator
pymodbus/datastore/context.py:128:23: E701 multiple statements on one line (colon)
pymodbus/datastore/context.py:131:13: E701 multiple statements on one line (colon)
pymodbus/datastore/context.py:139:23: E701 multiple statements on one line (colon)
pymodbus/datastore/context.py:142:80: E501 line too long (82 characters)
pymodbus/datastore/context.py:142:13: E701 multiple statements on one line (colon)
pymodbus/datastore/database.py:13:15: E702 multiple statements on one line (semicolon)
pymodbus/datastore/database.py:83:80: E501 line too long (80 characters)
pymodbus/datastore/database.py:85:80: E501 line too long (80 characters)
pymodbus/datastore/database.py:95:13: E128 continuation line under-indented for visual indent
pymodbus/datastore/database.py:110:14: E221 multiple spaces before operator
pymodbus/datastore/database.py:128:28: E203 whitespace before ':'
pymodbus/datastore/database.py:129:28: E203 whitespace before ':'
pymodbus/datastore/database.py:130:28: E203 whitespace before ':'
pymodbus/datastore/database.py:142:14: E221 multiple spaces before operator
pymodbus/datastore/database.py:143:15: E221 multiple spaces before operator
pymodbus/datastore/database.py:154:14: E221 multiple spaces before operator
pymodbus/datastore/database.py:155:14: E221 multiple spaces before operator
pymodbus/datastore/database.py:156:31: E221 multiple spaces before operator
pymodbus/datastore/database.py:158:15: E221 multiple spaces before operator
pymodbus/datastore/database.py:168:14: E221 multiple spaces before operator
pymodbus/datastore/modredis.py:8:15: E702 multiple statements on one line (semicolon)
pymodbus/datastore/modredis.py:80:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:82:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:97:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:98:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:99:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:100:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:103:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:104:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:105:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:106:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:109:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:110:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:111:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:112:16: E203 whitespace before ':'
pymodbus/datastore/modredis.py:115:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:117:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:118:15: E221 multiple spaces before operator
pymodbus/datastore/modredis.py:132:16: E221 multiple spaces before operator
pymodbus/datastore/modredis.py:176:80: E501 line too long (92 characters)
pymodbus/datastore/modredis.py:176:14: E221 multiple spaces before operator
pymodbus/datastore/modredis.py:183:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:185:80: E501 line too long (80 characters)
pymodbus/datastore/modredis.py:186:15: E221 multiple spaces before operator
pymodbus/datastore/modredis.py:201:16: E221 multiple spaces before operator
pymodbus/datastore/remote.py:98:32: E701 multiple statements on one line (colon)
pymodbus/datastore/remote.py:99:32: E701 multiple statements on one line (colon)
pymodbus/datastore/remote.py:100:13: E701 multiple statements on one line (colon)
pymodbus/datastore/store.py:144:13: E701 multiple statements on one line (colon)
pymodbus/datastore/store.py:163:15: E221 multiple spaces before operator
pymodbus/datastore/store.py:204:13: E701 multiple statements on one line (colon)
pymodbus/datastore/store.py:216:44: E225 missing whitespace around operator
pymodbus/datastore/store.py:225:22: E701 multiple statements on one line (colon)
4       E122 continuation line missing indentation or outdented
1       E125 continuation line does not distinguish itself from next logical line
77      E126 continuation line over-indented for hanging indent
4       E127 continuation line over-indented for visual indent
34      E128 continuation line under-indented for visual indent
1       E201 whitespace after '{'
1       E202 whitespace before '}'
68      E203 whitespace before ':'
154     E221 multiple spaces before operator
10      E225 missing whitespace around operator
29      E231 missing whitespace after ','
26      E261 at least two spaces before inline comment
10      E272 multiple spaces before keyword
47      E501 line too long (80 characters)
2       E502 the backslash is redundant between brackets
72      E701 multiple statements on one line (colon)
5       E702 multiple statements on one line (semicolon)
4       E711 comparison to None should be 'if cond is not None:'
pymodbus-1.3.2/doc/quality/current.lint0000644000175000017500000000353613150360615016331 0ustar  wmbwmbrunning lint
pymodbus/__init__.py:25: redefinition of unused 'NullHandler' from line 23
pymodbus/factory.py:12: 'from pymodbus.bit_read_message import *' used; unable to detect undefined names
pymodbus/factory.py:13: 'from pymodbus.bit_write_message import *' used; unable to detect undefined names
pymodbus/factory.py:14: 'from pymodbus.diag_message import *' used; unable to detect undefined names
pymodbus/factory.py:15: 'from pymodbus.file_message import *' used; unable to detect undefined names
pymodbus/factory.py:16: 'from pymodbus.other_message import *' used; unable to detect undefined names
pymodbus/factory.py:17: 'from pymodbus.register_read_message import *' used; unable to detect undefined names
pymodbus/factory.py:18: 'from pymodbus.register_write_message import *' used; unable to detect undefined names
pymodbus/server/async.py:230: local variable 'handle' is assigned to but never used
pymodbus/server/sync.py:16: 'from pymodbus.transaction import *' used; unable to detect undefined names
pymodbus/client/common.py:3: 'from pymodbus.bit_read_message import *' used; unable to detect undefined names
pymodbus/client/common.py:4: 'from pymodbus.bit_write_message import *' used; unable to detect undefined names
pymodbus/client/common.py:5: 'from pymodbus.register_read_message import *' used; unable to detect undefined names
pymodbus/client/common.py:6: 'from pymodbus.register_write_message import *' used; unable to detect undefined names
pymodbus/client/common.py:7: 'from pymodbus.diag_message import *' used; unable to detect undefined names
pymodbus/client/common.py:8: 'from pymodbus.file_message import *' used; unable to detect undefined names
pymodbus/client/common.py:9: 'from pymodbus.other_message import *' used; unable to detect undefined names
pymodbus/client/sync.py:8: 'from pymodbus.transaction import *' used; unable to detect undefined names
pymodbus-1.3.2/doc/TODO0000644000175000017500000000352013150360615012750 0ustar  wmbwmb---------------------------------------------------------------------------
General
---------------------------------------------------------------------------

- reorganize code into folder namespaces
- put protocol code in protocol namespace
- make framer read header->read header.length
  - maybe just for sync 
- finish clients (and interface)
- add all modbus control into server
- add a frontend plugin system
  - web frontend (bottle)
- twisted trial / twisted logging (for functional async tests)
- twisted serial server
- add daemonize code / init.d / config (or just use twisted)
- add correct transaction handling (retry, fail, etc)
- finish remaining functions

---------------------------------------------------------------------------
Protocols
---------------------------------------------------------------------------

- Serial RTU -> just use sleep wait
- Test serial against devices (and virtual tty)

---------------------------------------------------------------------------
Utilities
---------------------------------------------------------------------------

- (tcp/serial) forwarder
- (udp/serial) forwarder

---------------------------------------------------------------------------
Client
---------------------------------------------------------------------------

- Rework transaction flow and response data

---------------------------------------------------------------------------
Tools
---------------------------------------------------------------------------

- add functional tests
- add tk and wx gui frontdends (with editable data tables)
- rpm and deb packages (documentation)

--------------------------------------------------------------------------- 
Scratch
--------------------------------------------------------------------------- 
from twisted.python import log
observer = log.PythonLoggingObserver()
observer.start()

pymodbus-1.3.2/doc/api/0000755000175000017500000000000013150360615013031 5ustar  wmbwmbpymodbus-1.3.2/doc/api/pydoc/0000755000175000017500000000000013150360615014147 5ustar  wmbwmbpymodbus-1.3.2/doc/api/pydoc/build.py0000644000175000017500000003637413150360615015635 0ustar  wmbwmb#!/usr/bin/env python
"""
Pydoc sub-class for generating documentation for entire packages.

Taken from: http://pyopengl.sourceforge.net/pydoc/OpenGLContext.pydoc.pydoc2.html
Author: Mike Fletcher
"""
import logging
import pydoc, inspect, os, string, shutil
import sys, imp, os, stat, re, types, inspect
from repr import Repr
from string import expandtabs, find, join, lower, split, strip, rfind, rstrip

_log = logging.getLogger(__name__)

def classify_class_attrs(cls):
	"""Return list of attribute-descriptor tuples.

	For each name in dir(cls), the return list contains a 4-tuple
	with these elements:

		0. The name (a string).

		1. The kind of attribute this is, one of these strings:
			   'class method'    created via classmethod()
			   'static method'   created via staticmethod()
			   'property'        created via property()
			   'method'          any other flavor of method
			   'data'            not a method

		2. The class which defined this attribute (a class).

		3. The object as obtained directly from the defining class's
		   __dict__, not via getattr.  This is especially important for
		   data attributes:  C.data is just a data object, but
		   C.__dict__['data'] may be a data descriptor with additional
		   info, like a __doc__ string.
	
	Note: This version is patched to work with Zope Interface-bearing objects
	"""

	mro = inspect.getmro(cls)
	names = dir(cls)
	result = []
	for name in names:
		# Get the object associated with the name.
		# Getting an obj from the __dict__ sometimes reveals more than
		# using getattr.  Static and class methods are dramatic examples.
		if name in cls.__dict__:
			obj = cls.__dict__[name]
		else:
			try:
				obj = getattr(cls, name)
			except AttributeError, err:
				continue

		# Figure out where it was defined.
		homecls = getattr(obj, "__objclass__", None)
		if homecls is None:
			# search the dicts.
			for base in mro:
				if name in base.__dict__:
					homecls = base
					break

		# Get the object again, in order to get it from the defining
		# __dict__ instead of via getattr (if possible).
		if homecls is not None and name in homecls.__dict__:
			obj = homecls.__dict__[name]

		# Also get the object via getattr.
		obj_via_getattr = getattr(cls, name)

		# Classify the object.
		if isinstance(obj, staticmethod):
			kind = "static method"
		elif isinstance(obj, classmethod):
			kind = "class method"
		elif isinstance(obj, property):
			kind = "property"
		elif (inspect.ismethod(obj_via_getattr) or
			  inspect.ismethoddescriptor(obj_via_getattr)):
			kind = "method"
		else:
			kind = "data"

		result.append((name, kind, homecls, obj))

	return result
inspect.classify_class_attrs = classify_class_attrs


class DefaultFormatter(pydoc.HTMLDoc):
	def docmodule(self, object, name=None, mod=None, packageContext = None, *ignored):
		"""Produce HTML documentation for a module object."""
		name = object.__name__ # ignore the passed-in name
		parts = split(name, '.')
		links = []
		for i in range(len(parts)-1):
			links.append(
				'%s' %
				(join(parts[:i+1], '.'), parts[i]))
		linkedname = join(links + parts[-1:], '.')
		head = '%s' % linkedname
		try:
			path = inspect.getabsfile(object)
			url = path
			if sys.platform == 'win32':
				import nturl2path
				url = nturl2path.pathname2url(path)
			filelink = '%s' % (url, path)
		except TypeError:
			filelink = '(built-in)'
		info = []
		if hasattr(object, '__version__'):
			version = str(object.__version__)
			if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
				version = strip(version[11:-1])
			info.append('version %s' % self.escape(version))
		if hasattr(object, '__date__'):
			info.append(self.escape(str(object.__date__)))
		if info:
			head = head + ' (%s)' % join(info, ', ')
		result = self.heading(
			head, '#ffffff', '#7799ee', 'index
' + filelink) modules = inspect.getmembers(object, inspect.ismodule) classes, cdict = [], {} for key, value in inspect.getmembers(object, inspect.isclass): if (inspect.getmodule(value) or object) is object: classes.append((key, value)) cdict[key] = cdict[value] = '#' + key for key, value in classes: for base in value.__bases__: key, modname = base.__name__, base.__module__ module = sys.modules.get(modname) if modname != name and module and hasattr(module, key): if getattr(module, key) is base: if not cdict.has_key(key): cdict[key] = cdict[base] = modname + '.html#' + key funcs, fdict = [], {} for key, value in inspect.getmembers(object, inspect.isroutine): if inspect.isbuiltin(value) or inspect.getmodule(value) is object: funcs.append((key, value)) fdict[key] = '#-' + key if inspect.isfunction(value): fdict[value] = fdict[key] data = [] for key, value in inspect.getmembers(object, pydoc.isdata): if key not in ['__builtins__', '__doc__']: data.append((key, value)) doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict) doc = doc and '%s' % doc result = result + '

%s

\n' % doc packageContext.clean ( classes, object ) packageContext.clean ( funcs, object ) packageContext.clean ( data, object ) if hasattr(object, '__path__'): modpkgs = [] modnames = [] for file in os.listdir(object.__path__[0]): path = os.path.join(object.__path__[0], file) modname = inspect.getmodulename(file) if modname and modname not in modnames: modpkgs.append((modname, name, 0, 0)) modnames.append(modname) elif pydoc.ispackage(path): modpkgs.append((file, name, 1, 0)) modpkgs.sort() contents = self.multicolumn(modpkgs, self.modpkglink) ## result = result + self.bigsection( ## 'Package Contents', '#ffffff', '#aa55cc', contents) result = result + self.moduleSection( object, packageContext) elif modules: contents = self.multicolumn( modules, lambda (key, value), s=self: s.modulelink(value)) result = result + self.bigsection( 'Modules', '#fffff', '#aa55cc', contents) if classes: classlist = map(lambda (key, value): value, classes) contents = [ self.formattree(inspect.getclasstree(classlist, 1), name)] for key, value in classes: contents.append(self.document(value, key, name, fdict, cdict)) result = result + self.bigsection( 'Classes', '#ffffff', '#ee77aa', join(contents)) if funcs: contents = [] for key, value in funcs: contents.append(self.document(value, key, name, fdict, cdict)) result = result + self.bigsection( 'Functions', '#ffffff', '#eeaa77', join(contents)) if data: contents = [] for key, value in data: try: contents.append(self.document(value, key)) except Exception, err: pass result = result + self.bigsection( 'Data', '#ffffff', '#55aa55', join(contents, '
\n')) if hasattr(object, '__author__'): contents = self.markup(str(object.__author__), self.preformat) result = result + self.bigsection( 'Author', '#ffffff', '#7799ee', contents) if hasattr(object, '__credits__'): contents = self.markup(str(object.__credits__), self.preformat) result = result + self.bigsection( 'Credits', '#ffffff', '#7799ee', contents) return result def classlink(self, object, modname): """Make a link for a class.""" name, module = object.__name__, sys.modules.get(object.__module__) if hasattr(module, name) and getattr(module, name) is object: return '%s' % ( module.__name__, name, name ) return pydoc.classname(object, modname) def moduleSection( self, object, packageContext ): """Create a module-links section for the given object (module)""" modules = inspect.getmembers(object, inspect.ismodule) packageContext.clean ( modules, object ) packageContext.recurseScan( modules ) if hasattr(object, '__path__'): modpkgs = [] modnames = [] for file in os.listdir(object.__path__[0]): path = os.path.join(object.__path__[0], file) modname = inspect.getmodulename(file) if modname and modname not in modnames: modpkgs.append((modname, object.__name__, 0, 0)) modnames.append(modname) elif pydoc.ispackage(path): modpkgs.append((file, object.__name__, 1, 0)) modpkgs.sort() # do more recursion here... for (modname, name, ya,yo) in modpkgs: packageContext.addInteresting( join( (object.__name__, modname), '.')) items = [] for (modname, name, ispackage,isshadowed) in modpkgs: try: # get the actual module object... ## if modname == "events": ## import pdb ## pdb.set_trace() module = pydoc.safeimport( "%s.%s"%(name,modname) ) description, documentation = pydoc.splitdoc( inspect.getdoc( module )) if description: items.append( """%s -- %s"""% ( self.modpkglink( (modname, name, ispackage, isshadowed) ), description, ) ) else: items.append( self.modpkglink( (modname, name, ispackage, isshadowed) ) ) except: items.append( self.modpkglink( (modname, name, ispackage, isshadowed) ) ) contents = string.join( items, '
') result = self.bigsection( 'Package Contents', '#ffffff', '#aa55cc', contents) elif modules: contents = self.multicolumn( modules, lambda (key, value), s=self: s.modulelink(value)) result = self.bigsection( 'Modules', '#fffff', '#aa55cc', contents) else: result = "" return result class AlreadyDone(Exception): pass class PackageDocumentationGenerator: """A package document generator creates documentation for an entire package using pydoc's machinery. baseModules -- modules which will be included and whose included and children modules will be considered fair game for documentation destinationDirectory -- the directory into which the HTML documentation will be written recursion -- whether to add modules which are referenced by and/or children of base modules exclusions -- a list of modules whose contents will not be shown in any other module, commonly such modules as OpenGL.GL, wxPython.wx etc. recursionStops -- a list of modules which will explicitly stop recursion (i.e. they will never be included), even if they are children of base modules. formatter -- allows for passing in a custom formatter see DefaultFormatter for sample implementation. """ def __init__ ( self, baseModules, destinationDirectory = ".", recursion = 1, exclusions = (), recursionStops = (), formatter = None ): self.destinationDirectory = os.path.abspath( destinationDirectory) self.exclusions = {} self.warnings = [] self.baseSpecifiers = {} self.completed = {} self.recursionStops = {} self.recursion = recursion for stop in recursionStops: self.recursionStops[ stop ] = 1 self.pending = [] for exclusion in exclusions: try: self.exclusions[ exclusion ]= pydoc.locate ( exclusion) except pydoc.ErrorDuringImport, value: self.warn( """Unable to import the module %s which was specified as an exclusion module"""% (repr(exclusion))) self.formatter = formatter or DefaultFormatter() for base in baseModules: self.addBase( base ) def warn( self, message ): """Warnings are used for recoverable, but not necessarily ignorable conditions""" self.warnings.append (message) def info (self, message): """Information/status report""" _log.debug(message) def addBase(self, specifier): """Set the base of the documentation set, only children of these modules will be documented""" try: self.baseSpecifiers [specifier] = pydoc.locate ( specifier) self.pending.append (specifier) except pydoc.ErrorDuringImport, value: self.warn( """Unable to import the module %s which was specified as a base module"""% (repr(specifier))) def addInteresting( self, specifier): """Add a module to the list of interesting modules""" if self.checkScope( specifier): self.pending.append (specifier) else: self.completed[ specifier] = 1 def checkScope (self, specifier): """Check that the specifier is "in scope" for the recursion""" if not self.recursion: return 0 items = string.split (specifier, ".") stopCheck = items [:] while stopCheck: name = string.join(items, ".") if self.recursionStops.get( name): return 0 elif self.completed.get (name): return 0 del stopCheck[-1] while items: if self.baseSpecifiers.get( string.join(items, ".")): return 1 del items[-1] # was not within any given scope return 0 def process( self ): """Having added all of the base and/or interesting modules, proceed to generate the appropriate documentation for each module in the appropriate directory, doing the recursion as we go.""" try: while self.pending: try: if self.completed.has_key( self.pending[0] ): raise AlreadyDone( self.pending[0] ) self.info( """Start %s"""% (repr(self.pending[0]))) object = pydoc.locate ( self.pending[0] ) self.info( """ ... found %s"""% (repr(object.__name__))) except AlreadyDone: pass except pydoc.ErrorDuringImport, value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) except (SystemError, SystemExit), value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) except Exception, value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) else: page = self.formatter.page( pydoc.describe(object), self.formatter.docmodule( object, object.__name__, packageContext = self, ) ) file = open ( os.path.join( self.destinationDirectory, self.pending[0] + ".html", ), 'w', ) file.write(page) file.close() self.completed[ self.pending[0]] = object del self.pending[0] finally: for item in self.warnings: _log.info(item) def clean (self, objectList, object): """callback from the formatter object asking us to remove those items in the key, value pairs where the object is imported from one of the excluded modules""" for key, value in objectList[:]: for excludeObject in self.exclusions.values(): if hasattr( excludeObject, key ) and excludeObject is not object: if ( getattr( excludeObject, key) is value or (hasattr( excludeObject, '__name__') and excludeObject.__name__ == "Numeric" ) ): objectList[:] = [ (k,o) for k,o in objectList if k != key ] def recurseScan(self, objectList): """Process the list of modules trying to add each to the list of interesting modules""" for key, value in objectList: self.addInteresting( value.__name__ ) #---------------------------------------------------------------------------# # Main Runner #---------------------------------------------------------------------------# if __name__ == "__main__": if not os.path.exists("./html"): os.mkdir("./html") print "Building Pydoc API Documentation" PackageDocumentationGenerator( baseModules = ['pymodbus', '__builtin__'], destinationDirectory = "./html/", exclusions = ['math', 'string', 'twisted'], recursionStops = [], ).process () if os.path.exists('../../../build'): shutil.move("html", "../../../build/pydoc") pymodbus-1.3.2/doc/api/epydoc/0000755000175000017500000000000013150360615014314 5ustar wmbwmbpymodbus-1.3.2/doc/api/epydoc/build.py0000755000175000017500000000170213150360615015770 0ustar wmbwmb#!/usr/bin/env python ''' Epydoc API Runner ------------------ Using pkg_resources, we attempt to see if epydoc is installed, if so, we use its cli program to compile the documents ''' try: import sys, os, shutil import pkg_resources pkg_resources.require("epydoc") from epydoc.cli import cli sys.argv = '''epydoc.py pymodbus --html --simple-term --quiet --include-log --graph=all --docformat=plaintext --debug --exclude=._ --exclude=tests --output=html/ '''.split() #bugs in trunk for --docformat=restructuredtext if not os.path.exists("./html"): os.mkdir("./html") print "Building Epydoc API Documentation" cli() if os.path.exists('../../../build'): shutil.move("html", "../../../build/epydoc") except Exception, ex: import traceback,sys traceback.print_exc(file=sys.stdout) print "Epydoc not avaliable...not building" pymodbus-1.3.2/doc/api/pydoctor/0000755000175000017500000000000013150360615014674 5ustar wmbwmbpymodbus-1.3.2/doc/api/pydoctor/build.py0000755000175000017500000000144013150360615016347 0ustar wmbwmb#!/usr/bin/env python ''' Pydoctor API Runner --------------------- Using pkg_resources, we attempt to see if pydoctor is installed, if so, we use its cli program to compile the documents ''' try: import sys, os, shutil import pkg_resources pkg_resources.require("pydoctor") from pydoctor.driver import main sys.argv = '''pydoctor.py --quiet --project-name=Pymodbus --project-url=http://code.google.com/p/pymodbus/ --add-package=../../../pymodbus --html-output=html --html-write-function-pages --make-html'''.split() print "Building Pydoctor API Documentation" main(sys.argv[1:]) if os.path.exists('../../../build'): shutil.move("html", "../../../build/pydoctor") except: print "Pydoctor unavailable...not building" pymodbus-1.3.2/doc/api/doxygen/0000755000175000017500000000000013150360615014506 5ustar wmbwmbpymodbus-1.3.2/doc/api/doxygen/build.py0000644000175000017500000000204513150360615016160 0ustar wmbwmb#!/usr/bin/env python ''' Doxygen API Builder --------------------- ''' import os, shutil def is_exe(path): ''' Returns if the program is executable :param path: The path to the file :return: True if it is, False otherwise ''' return os.path.exists(path) and os.access(path, os.X_OK) def which(program): ''' Check to see if an executable exists :param program: The program to check for :return: The full path of the executable or None if not found ''' fpath, name = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None if which('doxygen') is not None: print "Building Doxygen API Documentation" os.system("doxygen .doxygen") if os.path.exists('../../../build'): shutil.move("html", "../../../build/doxygen") else: print "Doxygen not available...not building" pymodbus-1.3.2/doc/api/doxygen/.doxygen0000644000175000017500000017371413150360615016201 0ustar wmbwmb# Doxyfile 1.5.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Pymodbus # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 0.5 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, # Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = NO # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = NO # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = NO # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = ../../../pymodbus # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.py # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Options related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = YES pymodbus-1.3.2/doc/INSTALL0000644000175000017500000000312213150360615013307 0ustar wmbwmbRequirements ------------- * Python 2.3 or later. * Python Twisted * Pyserial On Windows pywin32 is recommended (this is built in to ActivePython, so no need to reinstall if you use it instead of standard Python): http://sourceforge.net/project/showfiles.php?group_id=78018 The Windows IOCP reactor requires pywin32 build 205 or later. Installation ------------- To install the package from pypi, use either easy_install or pip:: pip install -U pymodbus easy_install -U pymodbus As with other Python packages, the standard way of installing from source is (as root or administrator):: python setup.py install Running Tests -------------- The tests can be run with the built in unittest module, however, it is much easier to run with the nose package. With that installed, you can use either of the following:: python setup.py test nosetests Building Documentation ---------------------- The documentation is written in restructured text using the sphinx module. Building it is as simple as:: python setup build_sphinx The API documents can be generated using one of four programs: * epydoc * pydoc * pydoctor * doxygen To bulid these, simply run the following command and the available packages will sipmly be built:: python setup.py build_apidocs Quality Tests ---------------------- There are a number of quality tests that can be run against the code base aside from unit tests:: python setup.py scan_2to3 # run a python3 compatability test python setup.py pep8 # run a pop8 standards test python setup.py lint # run a lint test pymodbus-1.3.2/doc/LICENSE0000644000175000017500000000256113150360615013271 0ustar wmbwmbCopyright (c) 2011 Galen Collins All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pymodbus-1.3.2/doc/sphinx/0000755000175000017500000000000013150640232013565 5ustar wmbwmbpymodbus-1.3.2/doc/sphinx/examples/0000755000175000017500000000000013150640260015404 5ustar wmbwmbpymodbus-1.3.2/doc/sphinx/examples/bottle-frontend.rst0000644000175000017500000000131413150360615021246 0ustar wmbwmb================================================== Bottle Web Frontend Example ================================================== -------------------------------------------------- Summary -------------------------------------------------- This is a simple example of adding a live REST api on top of a running pymodbus server. This uses the bottle microframework to achieve this. The example can be hosted under twisted as well as the bottle internal server and can furthermore be run behind gunicorn, cherrypi, etc wsgi containers. -------------------------------------------------- Main Program -------------------------------------------------- .. literalinclude:: ../../../examples/gui/bottle/frontend.py pymodbus-1.3.2/doc/sphinx/examples/redis-datastore.rst0000644000175000017500000000030113150360615021225 0ustar wmbwmb================================================== Redis Datastore Example ================================================== .. literalinclude:: ../../../examples/contrib/redis-datastore.py pymodbus-1.3.2/doc/sphinx/examples/synchronous-server.rst0000644000175000017500000000030613150360615022036 0ustar wmbwmb================================================== Synchronous Server Example ================================================== .. literalinclude:: ../../../examples/common/synchronous-server.py pymodbus-1.3.2/doc/sphinx/examples/database-datastore.rst0000644000175000017500000000030713150360615021671 0ustar wmbwmb================================================== Database Datastore Example ================================================== .. literalinclude:: ../../../examples/contrib/database-datastore.py pymodbus-1.3.2/doc/sphinx/examples/wx-frontend.rst0000644000175000017500000000112013150360615020406 0ustar wmbwmb================================================== WX Frontend Example ================================================== Main Program -------------------------------------------------- This is an example simulator that is written using the python wx bindings. Although it currently does not have a frontend for modifying the context values, it does allow one to expose N virtual modbus devices to a network which is useful for testing data center monitoring tools. .. note:: The virtual networking code will only work on linux .. literalinclude:: ../../../examples/gui/wx/simulator.py pymodbus-1.3.2/doc/sphinx/examples/gtk-frontend.rst0000644000175000017500000000150513150360615020544 0ustar wmbwmb================================================== Glade/GTK Frontend Example ================================================== Main Program -------------------------------------------------- This is an example simulator that is written using the pygtk bindings. Although it currently does not have a frontend for modifying the context values, it does allow one to expose N virtual modbus devices to a network which is useful for testing data center monitoring tools. .. note:: The virtual networking code will only work on linux .. literalinclude:: ../../../examples/gui/gtk/simulator.py :language: python Glade Layout File -------------------------------------------------- The following is the glade layout file that is used by this script: .. literalinclude:: ../../../examples/gui/gtk/simulator.glade :language: xml pymodbus-1.3.2/doc/sphinx/examples/asynchronous-server.rst0000644000175000017500000000031013150360615022172 0ustar wmbwmb================================================== Asynchronous Server Example ================================================== .. literalinclude:: ../../../examples/common/asynchronous-server.py pymodbus-1.3.2/doc/sphinx/examples/serial-forwarder.rst0000644000175000017500000000030713150360615021411 0ustar wmbwmb================================================== Synchronous Serial Forwarder ================================================== .. literalinclude:: ../../../examples/contrib/serial-forwarder.py pymodbus-1.3.2/doc/sphinx/examples/modbus-payload.rst0000644000175000017500000000032013150360615021054 0ustar wmbwmb================================================== Modbus Payload Building/Decoding Example ================================================== .. literalinclude:: ../../../examples/common/modbus-payload.py pymodbus-1.3.2/doc/sphinx/examples/custom-datablock.rst0000644000175000017500000000030213150360615021370 0ustar wmbwmb================================================== Custom Datablock Example ================================================== .. literalinclude:: ../../../examples/common/custom-datablock.py pymodbus-1.3.2/doc/sphinx/examples/libmodbus-client.rst0000644000175000017500000000030213150360615021370 0ustar wmbwmb================================================== Libmodbus Client Facade ================================================== .. literalinclude:: ../../../examples/contrib/libmodbus-client.py pymodbus-1.3.2/doc/sphinx/examples/modbus-scraper.rst0000644000175000017500000000027713150360615021075 0ustar wmbwmb================================================== Modbus Scraper Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus-scraper.py pymodbus-1.3.2/doc/sphinx/examples/message-generator.rst0000644000175000017500000000147413150360615021557 0ustar wmbwmb================================================== Modbus Message Generator Example ================================================== This is an example of a utility that will build examples of modbus messages in all the available formats in the pymodbus package. -------------------------------------------------- Program Source -------------------------------------------------- .. literalinclude:: ../../../examples/contrib/message-generator.py -------------------------------------------------- Example Request Messages -------------------------------------------------- .. literalinclude:: ../../../examples/contrib/tx-messages -------------------------------------------------- Example Response Messages -------------------------------------------------- .. literalinclude:: ../../../examples/contrib/rx-messages pymodbus-1.3.2/doc/sphinx/examples/remote-server-context.rst0000644000175000017500000000031413150360615022420 0ustar wmbwmb================================================== Remote Single Server Context ================================================== .. literalinclude:: ../../../examples/contrib/remote_server_context.py pymodbus-1.3.2/doc/sphinx/examples/modbus-payload-server.rst0000644000175000017500000000033513150360615022366 0ustar wmbwmb================================================== Modbus Payload Server Context Building Example ================================================== .. literalinclude:: ../../../examples/common/modbus-payload-server.py pymodbus-1.3.2/doc/sphinx/examples/index.rst0000644000175000017500000000212313150360615017246 0ustar wmbwmb Pymodbus Library Examples ==================================== *What follows is a collection of examples using the pymodbus library in various ways* Example Library Code -------------------------------------------------- .. toctree:: :maxdepth: 2 asynchronous-client asynchronous-server asynchronous-processor custom-message custom-datablock modbus-logging modbus-payload modbus-payload-server synchronous-client synchronous-client-ext synchronous-server performance updating-server callback-server changing-framers thread-safe-datastore Custom Pymodbus Code -------------------------------------------------- .. toctree:: :maxdepth: 2 redis-datastore database-datastore bcd-payload modicon-payload message-generator message-parser serial-forwarder modbus-scraper modbus-simulator concurrent-client libmodbus-client remote-server-context Example Frontend Code -------------------------------------------------- .. toctree:: :maxdepth: 2 gtk-frontend tk-frontend wx-frontend bottle-frontend pymodbus-1.3.2/doc/sphinx/examples/modicon-payload.rst0000644000175000017500000000030113150360615021212 0ustar wmbwmb================================================== Modicon Encoded Example ================================================== .. literalinclude:: ../../../examples/contrib/modicon-payload.py pymodbus-1.3.2/doc/sphinx/examples/thread-safe-datastore.rst0000644000175000017500000000031513150360615022307 0ustar wmbwmb================================================== Thread Safe Datastore Example ================================================== .. literalinclude:: ../../../examples/contrib/thread_safe_datastore.py pymodbus-1.3.2/doc/sphinx/examples/updating-server.rst0000644000175000017500000000030013150360615021251 0ustar wmbwmb================================================== Updating Server Example ================================================== .. literalinclude:: ../../../examples/common/updating-server.py pymodbus-1.3.2/doc/sphinx/examples/synchronous-client.rst0000644000175000017500000000157013150360615022012 0ustar wmbwmb================================================== Synchronous Client Example ================================================== It should be noted that each request will block waiting for the result. If asynchronous behaviour is required, please use the asynchronous client implementations. The synchronous client, works against TCP, UDP, serial ASCII, and serial RTU devices. The synchronous client exposes the most popular methods of the modbus protocol, however, if you want to execute other methods against the device, simple create a request instance and pass it to the execute method. Below an synchronous tcp client is demonstrated running against a reference server. If you do not have a device to test with, feel free to run a pymodbus server instance or start the reference tester in the tools directory. .. literalinclude:: ../../../examples/common/synchronous-client.py pymodbus-1.3.2/doc/sphinx/examples/modbus-simulator.rst0000644000175000017500000000030213150360615021442 0ustar wmbwmb================================================== Modbus Simulator Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus-simulator.py pymodbus-1.3.2/doc/sphinx/examples/tk-frontend.rst0000644000175000017500000000111713150360615020374 0ustar wmbwmb================================================== TK Frontend Example ================================================== Main Program -------------------------------------------------- This is an example simulator that is written using the native tk toolkit. Although it currently does not have a frontend for modifying the context values, it does allow one to expose N virtual modbus devices to a network which is useful for testing data center monitoring tools. .. note:: The virtual networking code will only work on linux .. literalinclude:: ../../../examples/gui/tk/simulator.py pymodbus-1.3.2/doc/sphinx/examples/performance.rst0000644000175000017500000000071013150360615020440 0ustar wmbwmb================================================== Synchronous Client Performance Check ================================================== Below is a quick example of how to test the performance of a tcp modbus device using the synchronous tcp client. If you do not have a device to test with, feel free to run a pymodbus server instance or start the reference tester in the tools directory. .. literalinclude:: ../../../examples/common/performance.py pymodbus-1.3.2/doc/sphinx/examples/synchronous-client-ext.rst0000644000175000017500000000032313150360615022603 0ustar wmbwmb================================================== Synchronous Client Extended Example ================================================== .. literalinclude:: ../../../examples/common/synchronous-client-ext.py pymodbus-1.3.2/doc/sphinx/examples/asynchronous-processor.rst0000644000175000017500000000113413150360615022710 0ustar wmbwmb================================================== Asynchronous Processor Example ================================================== Below is a simplified asynchronous client skeleton that was submitted by a user of the library. It can be used as a guide for implementing more complex pollers or state machines. Feel free to test it against whatever device you currently have available. If you do not have a device to test with, feel free to run a pymodbus server instance or start the reference tester in the tools directory. .. literalinclude:: ../../../examples/common/asynchronous-processor.py pymodbus-1.3.2/doc/sphinx/examples/callback-server.rst0000644000175000017500000000030013150360615021172 0ustar wmbwmb================================================== Callback Server Example ================================================== .. literalinclude:: ../../../examples/common/callback-server.py pymodbus-1.3.2/doc/sphinx/examples/modbus-logging.rst0000644000175000017500000000027613150360615021063 0ustar wmbwmb================================================== Modbus Logging Example ================================================== .. literalinclude:: ../../../examples/common/modbus-logging.py pymodbus-1.3.2/doc/sphinx/examples/message-parser.rst0000644000175000017500000000435413150360615021065 0ustar wmbwmb================================================== Modbus Message Parsing Example ================================================== This is an example of a parser to decode raw messages to a readable description. It will attempt to decode a message to the request and response version of a message if possible. Here is an example output:: $./message-parser.py -b -m 000112340006ff076d ================================================================================ Decoding Message 000112340006ff076d ================================================================================ ServerDecoder -------------------------------------------------------------------------------- name = ReadExceptionStatusRequest check = 0x0 unit_id = 0xff transaction_id = 0x1 protocol_id = 0x1234 documentation = This function code is used to read the contents of eight Exception Status outputs in a remote device. The function provides a simple method for accessing this information, because the Exception Output references are known (no output reference is needed in the function). ClientDecoder -------------------------------------------------------------------------------- name = ReadExceptionStatusResponse check = 0x0 status = 0x6d unit_id = 0xff transaction_id = 0x1 protocol_id = 0x1234 documentation = The normal response contains the status of the eight Exception Status outputs. The outputs are packed into one data byte, with one bit per output. The status of the lowest output reference is contained in the least significant bit of the byte. The contents of the eight Exception Status outputs are device specific. -------------------------------------------------- Program Source -------------------------------------------------- .. literalinclude:: ../../../examples/contrib/message-parser.py -------------------------------------------------- Example Messages -------------------------------------------------- See the documentation for the message generator for a collection of messages that can be parsed by this utility. pymodbus-1.3.2/doc/sphinx/examples/concurrent-client.rst0000644000175000017500000000031413150360615021575 0ustar wmbwmb================================================== Modbus Concurrent Client Example ================================================== .. literalinclude:: ../../../examples/contrib/concurrent-client.py pymodbus-1.3.2/doc/sphinx/examples/changing-framers.rst0000644000175000017500000000030213150360615021347 0ustar wmbwmb================================================== Changing Default Framers ================================================== .. literalinclude:: ../../../examples/common/changing-framers.py pymodbus-1.3.2/doc/sphinx/examples/bcd-payload.rst0000644000175000017500000000030213150360615020313 0ustar wmbwmb================================================== Binary Coded Decimal Example ================================================== .. literalinclude:: ../../../examples/contrib/bcd-payload.py pymodbus-1.3.2/doc/sphinx/examples/asynchronous-client.rst0000644000175000017500000000126713150360615022156 0ustar wmbwmb================================================== Asynchronous Client Example ================================================== The asynchronous client functions in the same way as the synchronous client, however, the asynchronous client uses twisted to return deferreds for the response result. Just like the synchronous version, it works against TCP, UDP, serial ASCII, and serial RTU devices. Below an asynchronous tcp client is demonstrated running against a reference server. If you do not have a device to test with, feel free to run a pymodbus server instance or start the reference tester in the tools directory. .. literalinclude:: ../../../examples/common/asynchronous-client.py pymodbus-1.3.2/doc/sphinx/examples/custom-message.rst0000644000175000017500000000027613150360615021102 0ustar wmbwmb================================================== Custom Message Example ================================================== .. literalinclude:: ../../../examples/common/custom-message.py pymodbus-1.3.2/doc/sphinx/library/0000755000175000017500000000000013150360615015235 5ustar wmbwmbpymodbus-1.3.2/doc/sphinx/library/bit-write-message.rst0000644000175000017500000000112313150360615021314 0ustar wmbwmb:mod:`bit_write_message` --- Bit Write Modbus Messages ============================================================ .. module:: bit_write_message :synopsis: Bit Write Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.bit_write_message .. autoclass:: WriteSingleCoilRequest :members: .. autoclass:: WriteSingleCoilResponse :members: .. autoclass:: WriteMultipleCoilsRequest :members: .. autoclass:: WriteMultipleCoilsResponse :members: pymodbus-1.3.2/doc/sphinx/library/constants.rst0000644000175000017500000000112413150360615020001 0ustar wmbwmb:mod:`constants` --- Modbus Default Values ============================================================ .. module:: constants :synopsis: Modbus Default Values .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.constants .. autoclass:: Defaults :members: .. autoclass:: ModbusStatus :members: .. autoclass:: Endian :members: .. autoclass:: ModbusPlusOperation :members: .. autoclass:: DeviceInformation :members: .. autoclass:: MoreData :members: pymodbus-1.3.2/doc/sphinx/library/utilities.rst0000644000175000017500000000120613150360615020001 0ustar wmbwmb:mod:`utilities` --- Extra Modbus Helpers ========================================== .. module:: utilities :synopsis: Extra Modbus Helpers .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.utilities .. autofunction:: default .. autofunction:: dict_property .. autofunction:: pack_bitstring .. autofunction:: unpack_bitstring .. autofunction:: __generate_crc16_table .. autofunction:: computeCRC .. autofunction:: checkCRC .. autofunction:: computeLRC .. autofunction:: checkLRC .. autofunction:: rtuFrameSize pymodbus-1.3.2/doc/sphinx/library/pymodbus.rst0000644000175000017500000000035013150360615017627 0ustar wmbwmb:mod:`pymodbus` --- Pymodbus Library ============================================================ .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins .. automodule:: pymodbus pymodbus-1.3.2/doc/sphinx/library/pdu.rst0000644000175000017500000000111413150360615016554 0ustar wmbwmb:mod:`pdu` --- Base Structures ============================================================ .. module:: pdu :synopsis: Base Structures .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.pdu .. autoclass:: ModbusPDU :members: .. autoclass:: ModbusRequest :members: .. autoclass:: ModbusResponse :members: .. autoclass:: ModbusExceptions :members: .. autoclass:: ExceptionResponse :members: .. autoclass:: IllegalFunctionRequest :members: pymodbus-1.3.2/doc/sphinx/library/async-client.rst0000644000175000017500000000072013150360615020357 0ustar wmbwmb:mod:`client.async` --- Twisted Async Modbus Client ==================================================== .. module:: client.async :synopsis: Twisted Asynchronous Modbus Client .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------ .. automodule:: pymodbus.client.async .. autoclass:: ModbusClientProtocol :members: .. autoclass:: ModbusClientFactory :members: pymodbus-1.3.2/doc/sphinx/library/sync-client.rst0000644000175000017500000000105513150360615020220 0ustar wmbwmb:mod:`client.sync` --- Twisted Synchronous Modbus Client ========================================================= .. module:: client.sync :synopsis: Twisted Synchronous Modbus Client .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------ .. automodule:: pymodbus.client.sync .. autoclass:: BaseModbusClient :members: .. autoclass:: ModbusTcpClient :members: .. autoclass:: ModbusUdpClient :members: .. autoclass:: ModbusSerialClient :members: pymodbus-1.3.2/doc/sphinx/library/other-message.rst0000644000175000017500000000125513150360615020535 0ustar wmbwmb:mod:`other_message` --- Other Modbus Messages ============================================================ .. module:: other_message :synopsis: Other Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.other_message .. autoclass:: ReadExceptionStatusRequest :members: .. autoclass:: ReadExceptionStatusResponse :members: .. autoclass:: GetCommEventCounterRequest :members: .. autoclass:: GetCommEventCounterResponse :members: .. autoclass:: ReportSlaveIdRequest :members: .. autoclass:: ReportSlaveIdResponse :members: pymodbus-1.3.2/doc/sphinx/library/events.rst0000644000175000017500000000110613150360615017271 0ustar wmbwmb:mod:`events` --- Events Used in PyModbus ============================================================ .. module:: events :synopsis: Events Used in PyModbus .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.events .. autoclass:: ModbusEvent :members: .. autoclass:: RemoteReceiveEvent :members: .. autoclass:: RemoteSendEvent :members: .. autoclass:: EnteredListenModeEvent :members: .. autoclass:: CommunicationRestartEvent :members: pymodbus-1.3.2/doc/sphinx/library/bit-read-message.rst0000644000175000017500000000124513150360615021102 0ustar wmbwmb:mod:`bit_read_message` --- Bit Read Modbus Messages ============================================================ .. module:: bit_read_message :synopsis: Bit Read Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.bit_read_message .. autoclass:: ReadBitsRequestBase :members: .. autoclass:: ReadBitsResponseBase :members: .. autoclass:: ReadCoilsRequest :members: .. autoclass:: ReadCoilsResponse :members: .. autoclass:: ReadDiscreteInputsRequest :members: .. autoclass:: ReadDiscreteInputsResponse :members: pymodbus-1.3.2/doc/sphinx/library/file-message.rst0000644000175000017500000000127613150360615020336 0ustar wmbwmb:mod:`file_message` --- File Modbus Messages ============================================================ .. module:: file_message :synopsis: File Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.file_message .. autoclass:: FileRecord :members: .. autoclass:: ReadFileRecordRequest :members: .. autoclass:: ReadFileRecordResponse :members: .. autoclass:: WriteFileRecordRequest :members: .. autoclass:: WriteFileRecordResponse :members: .. autoclass:: ReadFifoQueueRequest :members: .. autoclass:: ReadFifoQueueResponse :members: pymodbus-1.3.2/doc/sphinx/library/datastore/0000755000175000017500000000000013150360615017223 5ustar wmbwmbpymodbus-1.3.2/doc/sphinx/library/datastore/index.rst0000644000175000017500000000033313150360615021063 0ustar wmbwmb Server Datastores and Contexts ==================================== *The following are the API documentation strings taken from the sourcecode* .. toctree:: :maxdepth: 2 store.rst context.rst remote.rst pymodbus-1.3.2/doc/sphinx/library/datastore/store.rst0000644000175000017500000000102013150360615021102 0ustar wmbwmb:mod:`store` --- Datastore for Modbus Server Context ============================================================ .. module:: store :synopsis: Datastore for Modbus Server Context .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.datastore.store .. autoclass:: BaseModbusDataBlock :members: .. autoclass:: ModbusSequentialDataBlock :members: .. autoclass:: ModbusSparseDataBlock :members: pymodbus-1.3.2/doc/sphinx/library/datastore/remote.rst0000644000175000017500000000061113150360615021246 0ustar wmbwmb:mod:`remote` --- Remote Slave Context ============================================================ .. module:: remote :synopsis: Remote Slave Context .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.datastore.remote .. autoclass:: RemoteSlaveContext :members: pymodbus-1.3.2/doc/sphinx/library/datastore/context.rst0000644000175000017500000000070213150360615021440 0ustar wmbwmb:mod:`context` --- Modbus Server Contexts ============================================================ .. module:: context :synopsis: Modbus Server Contexts .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.datastore.context .. autoclass:: ModbusSlaveContext :members: .. autoclass:: ModbusServerContext :members: pymodbus-1.3.2/doc/sphinx/library/client-common.rst0000644000175000017500000000062713150360615020540 0ustar wmbwmb:mod:`client.common` --- Twisted Async Modbus Client ==================================================== .. module:: client.common :synopsis: Modbus common client clode .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------ .. automodule:: pymodbus.client.common .. autoclass:: ModbusClientMixin :members: pymodbus-1.3.2/doc/sphinx/library/index.rst0000644000175000017500000000123113150360615017073 0ustar wmbwmb Pymodbus Library API Documentation ==================================== *The following are the API documentation strings taken from the sourcecode* .. toctree:: :maxdepth: 2 bit-read-message.rst bit-write-message.rst client-common.rst sync-client.rst async-client.rst constants.rst datastore/index.rst diag-message.rst device.rst factory.rst interfaces.rst exceptions.rst other-message.rst mei-message.rst file-message.rst events.rst payload.rst pdu.rst pymodbus.rst register-read-message.rst register-write-message.rst sync-server.rst async-server.rst transaction.rst utilities.rst pymodbus-1.3.2/doc/sphinx/library/sync-server.rst0000644000175000017500000000152213150360615020247 0ustar wmbwmb:mod:`server.sync` --- Twisted Synchronous Modbus Server ============================================================ .. module:: server.sync :synopsis: Twisted Synchronous Modbus Server .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.server.sync .. autoclass:: ModbusBaseRequestHandler :members: .. autoclass:: ModbusSingleRequestHandler :members: .. autoclass:: ModbusConnectedRequestHandler :members: .. autoclass:: ModbusDisconnectedRequestHandler :members: .. autoclass:: ModbusTcpServer :members: .. autoclass:: ModbusUdpServer :members: .. autoclass:: ModbusSerialServer :members: .. autofunction:: StartTcpServer .. autofunction:: StartUdpServer .. autofunction:: StartSerialServer pymodbus-1.3.2/doc/sphinx/library/register-read-message.rst0000644000175000017500000000151713150360615022152 0ustar wmbwmb:mod:`register_read_message` --- Register Read Messages ============================================================ .. module:: register_read_message :synopsis: Register Read Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.register_read_message .. autoclass:: ReadRegistersRequestBase :members: .. autoclass:: ReadRegistersResponseBase :members: .. autoclass:: ReadHoldingRegistersRequest :members: .. autoclass:: ReadHoldingRegistersResponse :members: .. autoclass:: ReadInputRegistersRequest :members: .. autoclass:: ReadInputRegistersResponse :members: .. autoclass:: ReadWriteMultipleRegistersRequest :members: .. autoclass:: ReadWriteMultipleRegistersResponse :members: pymodbus-1.3.2/doc/sphinx/library/transaction.rst0000644000175000017500000000124213150360615020313 0ustar wmbwmb:mod:`transaction` --- Transaction Controllers for Pymodbus ============================================================ .. module:: transaction :synopsis: Transaction controllers for pymodbus .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.transaction .. autoclass:: DictTransactionManager :members: .. autoclass:: FifoTransactionManager :members: .. autoclass:: ModbusSocketFramer :members: .. autoclass:: ModbusRtuFramer :members: .. autoclass:: ModbusAsciiFramer :members: .. autoclass:: ModbusBinaryFramer :members: pymodbus-1.3.2/doc/sphinx/library/diag-message.rst0000644000175000017500000000511013150360615020312 0ustar wmbwmb:mod:`diag_message` --- Diagnostic Modbus Messages ============================================================ .. module:: diag_message :synopsis: Diagnostic Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.diag_message .. autoclass:: DiagnosticStatusRequest :members: .. autoclass:: DiagnosticStatusResponse :members: .. autoclass:: DiagnosticStatusSimpleRequest :members: .. autoclass:: DiagnosticStatusSimpleResponse :members: .. autoclass:: ReturnQueryDataRequest :members: .. autoclass:: ReturnQueryDataResponse :members: .. autoclass:: RestartCommunicationsOptionRequest :members: .. autoclass:: RestartCommunicationsOptionResponse :members: .. autoclass:: ReturnDiagnosticRegisterRequest :members: .. autoclass:: ReturnDiagnosticRegisterResponse :members: .. autoclass:: ChangeAsciiInputDelimiterRequest :members: .. autoclass:: ChangeAsciiInputDelimiterResponse :members: .. autoclass:: ForceListenOnlyModeRequest :members: .. autoclass:: ForceListenOnlyModeResponse :members: .. autoclass:: ClearCountersRequest :members: .. autoclass:: ClearCountersResponse :members: .. autoclass:: ReturnBusMessageCountRequest :members: .. autoclass:: ReturnBusMessageCountResponse :members: .. autoclass:: ReturnBusCommunicationErrorCountRequest :members: .. autoclass:: ReturnBusCommunicationErrorCountResponse :members: .. autoclass:: ReturnBusExceptionErrorCountRequest :members: .. autoclass:: ReturnBusExceptionErrorCountResponse :members: .. autoclass:: ReturnSlaveMessageCountRequest :members: .. autoclass:: ReturnSlaveMessageCountResponse :members: .. autoclass:: ReturnSlaveNoResponseCountRequest :members: .. autoclass:: ReturnSlaveNoReponseCountResponse :members: .. autoclass:: ReturnSlaveNAKCountRequest :members: .. autoclass:: ReturnSlaveNAKCountResponse :members: .. autoclass:: ReturnSlaveBusyCountRequest :members: .. autoclass:: ReturnSlaveBusyCountResponse :members: .. autoclass:: ReturnSlaveBusCharacterOverrunCountRequest :members: .. autoclass:: ReturnSlaveBusCharacterOverrunCountResponse :members: .. autoclass:: ReturnIopOverrunCountRequest :members: .. autoclass:: ReturnIopOverrunCountResponse :members: .. autoclass:: ClearOverrunCountRequest :members: .. autoclass:: ClearOverrunCountResponse :members: .. autoclass:: GetClearModbusPlusRequest :members: .. autoclass:: GetClearModbusPlusResponse :members: pymodbus-1.3.2/doc/sphinx/library/register-write-message.rst0000644000175000017500000000115613150360615022370 0ustar wmbwmb:mod:`register_write_message` --- Register Write Messages ============================================================ .. module:: register_write_message :synopsis: Register Write Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.register_write_message .. autoclass:: WriteSingleRegisterRequest :members: .. autoclass:: WriteSingleRegisterResponse :members: .. autoclass:: WriteMultipleRegistersRequest :members: .. autoclass:: WriteMultipleRegistersResponse :members: pymodbus-1.3.2/doc/sphinx/library/payload.rst0000644000175000017500000000067613150360615017431 0ustar wmbwmb:mod:`payload` --- Modbus Payload Utilities ============================================================ .. module:: payload :synopsis: Modbus Payload Utilities .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.payload .. autoclass:: BinaryPayloadBuilder :members: .. autoclass:: BinaryPayloadDecoder :members: pymodbus-1.3.2/doc/sphinx/library/async-server.rst0000644000175000017500000000116613150360615020414 0ustar wmbwmb:mod:`server.async` --- Twisted Asynchronous Modbus Server ============================================================ .. module:: server.async :synopsis: Twisted Asynchronous Modbus Server .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.server.async .. autoclass:: ModbusTcpProtocol :members: .. autoclass:: ModbusUdpProtocol :members: .. autoclass:: ModbusServerFactory :members: .. autofunction:: StartTcpServer .. autofunction:: StartUdpServer .. autofunction:: StartSerialServer pymodbus-1.3.2/doc/sphinx/library/interfaces.rst0000644000175000017500000000106113150360615020110 0ustar wmbwmb:mod:`interfaces` --- System Interfaces ============================================================ .. module:: interfaces :synopsis: System Interfaces .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.interfaces .. autoclass:: Singleton :members: .. autoclass:: IModbusDecoder :members: .. autoclass:: IModbusFramer :members: .. autoclass:: IModbusSlaveContext :members: .. autoclass:: IPayloadBuilder :members: pymodbus-1.3.2/doc/sphinx/library/factory.rst0000644000175000017500000000066213150360615017442 0ustar wmbwmb:mod:`factory` --- Request/Response Decoders ============================================================ .. module:: factory :synopsis: Request/Response Decoders .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.factory .. autoclass:: ServerDecoder :members: .. autoclass:: ClientDecoder :members: pymodbus-1.3.2/doc/sphinx/library/device.rst0000644000175000017500000000114013150360615017222 0ustar wmbwmb:mod:`device` --- Modbus Device Representation ============================================================ .. module:: device :synopsis: Modbus Device Representation .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.device .. autoclass:: ModbusAccessControl :members: .. autoclass:: ModbusPlusStatistics :members: .. autoclass:: ModbusDeviceIdentification :members: .. autoclass:: DeviceInformationFactory :members: .. autoclass:: ModbusControlBlock :members: pymodbus-1.3.2/doc/sphinx/library/exceptions.rst0000644000175000017500000000105313150360615020147 0ustar wmbwmb:mod:`exceptions` --- Exceptions Used in PyModbus ============================================================ .. module:: exceptions :synopsis: Exceptions Used in PyModbus .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.exceptions .. autoclass:: ModbusException :members: .. autoclass:: ModbusIOException :members: .. autoclass:: ParameterException :members: .. autoclass:: NotImplementedException :members: pymodbus-1.3.2/doc/sphinx/library/mei-message.rst0000644000175000017500000000072113150360615020163 0ustar wmbwmb:mod:`mei_message` --- MEI Modbus Messages ============================================================ .. module:: mei_message :synopsis: MEI Modbus Messages .. moduleauthor:: Galen Collins .. sectionauthor:: Galen Collins API Documentation ------------------- .. automodule:: pymodbus.mei_message .. autoclass:: ReadDeviceInformationRequest :members: .. autoclass:: ReadDeviceInformationResponse :members: pymodbus-1.3.2/doc/sphinx/index.rst0000644000175000017500000000072713150360615015440 0ustar wmbwmb.. PyModbus documentation master file, created by sphinx-quickstart on Tue Apr 14 19:11:16 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to PyModbus's documentation! ==================================== Contents: .. toctree:: :maxdepth: 2 examples/index.rst library/index.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pymodbus-1.3.2/doc/sphinx/Makefile0000644000175000017500000000563713150360615015244 0ustar wmbwmb# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf build/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html @echo @echo "Build finished. The HTML pages are in build/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml @echo @echo "Build finished. The HTML pages are in build/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in build/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in build/qthelp, like this:" @echo "# qcollectiongenerator build/qthelp/PyModbus.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile build/qthelp/PyModbus.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex @echo @echo "Build finished; the LaTeX files are in build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes @echo @echo "The overview file is in build/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in build/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in build/doctest/output.txt." pymodbus-1.3.2/doc/sphinx/static/0000755000175000017500000000000013150360615015060 5ustar wmbwmbpymodbus-1.3.2/doc/sphinx/static/README0000644000175000017500000000004513150360615015737 0ustar wmbwmbinclude any html static content here pymodbus-1.3.2/doc/sphinx/conf.py0000644000175000017500000000713713150360615015100 0ustar wmbwmb# -*- coding: utf-8 -*- # # pymodbus documentation build configuration file, created by # sphinx-quickstart on Fri May 26 10:10:53 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath(os.pardir)) from pymodbus import __version__, __author__, __maintainer__ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. # Sphinx extension module names. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'humanfriendly.sphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # Sort members by the source order instead of alphabetically. autodoc_member_order = 'bysource' # Paths that contain templates, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pymodbus' copyright = u'2017, {}, {}'.format(__author__, __maintainer__) author = u'{}, {}'.format(__author__, __maintainer__) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['build', '_build', 'Thumbs.db', '.DS_Store'] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Refer to the Python standard library. # From: http://twistedmatrix.com/trac/ticket/4582. intersphinx_mapping = dict( python=('https://docs.python.org/2', None), capturer=('https://capturer.readthedocs.io/en/latest', None), humanfriendly=('https://humanfriendly.readthedocs.io/en/latest', None), ) # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Output file base name for HTML help builder. htmlhelp_basename = 'pymodbusdoc' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static'] pymodbus-1.3.2/Makefile0000644000175000017500000000355213150360615013160 0ustar wmbwmb# Makefile for the `pymodbus' package. WORKON_HOME ?= $(HOME)/.virtualenvs VIRTUAL_ENV ?= $(WORKON_HOME)/pymodbus PATH := $(VIRTUAL_ENV)/bin:$(PATH) MAKE := $(MAKE) --no-print-directory SHELL = bash default: @echo 'Makefile for pymodbus' @echo @echo 'Usage:' @echo @echo ' make install install the package in a virtual environment' @echo ' make reset recreate the virtual environment' @echo ' make check check coding style (PEP-8, PEP-257)' @echo ' make test run the test suite, report coverage' @echo ' make tox run the tests on all Python versions' @echo ' make clean cleanup all temporary files' @echo install: @test -d "$(VIRTUAL_ENV)" || mkdir -p "$(VIRTUAL_ENV)" @test -x "$(VIRTUAL_ENV)/bin/python" || virtualenv --quiet "$(VIRTUAL_ENV)" @test -x "$(VIRTUAL_ENV)/bin/pip" || easy_install pip @pip install --quiet --requirement=requirements.txt @pip uninstall --yes pymodbus &>/dev/null || true @pip install --quiet --no-deps --ignore-installed . reset: $(MAKE) clean rm -Rf "$(VIRTUAL_ENV)" $(MAKE) install check: install @pip install --upgrade --quiet --requirement=requirements-checks.txt @flake8 test: install @pip install --quiet --requirement=requirements-tests.txt @nosetests --with-coverage --cover-html @coverage report --fail-under=90 tox: install @pip install --quiet tox && tox docs: install @pip install --quiet sphinx @cd doc/sphinx && sphinx-build -nb html -d doctrees . html publish: install git push origin && git push --tags origin $(MAKE) clean pip install --quiet twine wheel python setup.py sdist bdist_wheel twine upload dist/* $(MAKE) clean clean: @rm -Rf *.egg .cache .coverage .tox build dist docs/build htmlcov @find -depth -type d -name __pycache__ -exec rm -Rf {} \; @find -type f -name '*.pyc' -delete .PHONY: default install reset check test tox docs publish clean pymodbus-1.3.2/scripts/0000755000175000017500000000000013150360615013202 5ustar wmbwmbpymodbus-1.3.2/scripts/travis.sh0000755000175000017500000000034613150360615015054 0ustar wmbwmb#!/bin/bash -e if [ "$TRAVIS_OS_NAME" = osx ]; then VIRTUAL_ENV="$HOME/.virtualenvs/python2.7" if [ ! -x "$VIRTUAL_ENV/bin/python" ]; then virtualenv "$VIRTUAL_ENV" fi source "$VIRTUAL_ENV/bin/activate" fi eval "$@" pymodbus-1.3.2/.travis.yml0000644000175000017500000000142413150360615013625 0ustar wmbwmbsudo: false language: python matrix: include: - os: linux python: "2.7" - os: linux python: "3.4" - os: linux python: "3.5" - os: linux python: "3.6" - os: osx language: generic before_install: - if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi - if [ $TRAVIS_OS_NAME = osx ]; then brew install openssl; fi install: # - scripts/travis.sh pip install pip-accel - scripts/travis.sh pip install coveralls - scripts/travis.sh pip install --requirement=requirements-checks.txt - scripts/travis.sh pip install --requirement=requirements-tests.txt - scripts/travis.sh LC_ALL=C pip install . script: # - scripts/travis.sh make check - scripts/travis.sh make test after_success: - scripts/travis.sh coveralls branches: except: - /^[0-9]/