simulator.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Modbus TestKit: Implementation of Modbus protocol in python
  5. (C)2009 - Luc Jean - luc.jean@gmail.com
  6. (C)2009 - Apidev - http://www.apidev.fr
  7. This is distributed under GNU LGPL license, see license.txt
  8. The modbus_tk simulator is a console application which is running a server with TCP and RTU communication
  9. It is possible to interact with the server from the command line or from a RPC (Remote Process Call)
  10. """
  11. from __future__ import print_function
  12. import ctypes
  13. import os
  14. import sys
  15. import select
  16. import serial
  17. import threading
  18. import time
  19. import modbus_tk
  20. from modbus_tk import hooks
  21. from modbus_tk import modbus
  22. from modbus_tk import modbus_tcp
  23. from modbus_tk import modbus_rtu
  24. if modbus_tk.utils.PY2:
  25. import Queue as queue
  26. import SocketServer
  27. else:
  28. import queue
  29. import socketserver as SocketServer
  30. # add logging capability
  31. LOGGER = modbus_tk.utils.create_logger(name="console", record_format="%(message)s")
  32. # The communication between the server and the user interfaces (console or rpc) are done through queues
  33. # command received from the interfaces
  34. INPUT_QUEUE = queue.Queue()
  35. # response to be sent back by the interfaces
  36. OUTPUT_QUEUE = queue.Queue()
  37. class CompositeServer(modbus.Server):
  38. """make possible to have several servers sharing the same databank"""
  39. def __init__(self, list_of_server_classes, list_of_server_args, databank=None):
  40. """Constructor"""
  41. super(CompositeServer, self).__init__(databank)
  42. self._servers = [
  43. the_class(*the_args, **{"databank": self.get_db()})
  44. for the_class, the_args in zip(list_of_server_classes, list_of_server_args)
  45. if issubclass(the_class, modbus.Server)
  46. ]
  47. def set_verbose(self, verbose):
  48. """if verbose is true the sent and received packets will be logged"""
  49. for srv in self._servers:
  50. srv.set_verbose(verbose)
  51. def _make_thread(self):
  52. """should initialize the main thread of the server. You don't need it here"""
  53. pass
  54. def _make_query(self):
  55. """Returns an instance of a Query subclass implementing the MAC layer protocol"""
  56. raise NotImplementedError()
  57. def start(self):
  58. """Start the server. It will handle request"""
  59. for srv in self._servers:
  60. srv.start()
  61. def stop(self):
  62. """stop the server. It doesn't handle request anymore"""
  63. for srv in self._servers:
  64. srv.stop()
  65. class RpcHandler(SocketServer.BaseRequestHandler):
  66. """An instance of this class is created every time an RPC call is received by the server"""
  67. def handle(self):
  68. """This function is called automatically by the SocketServer"""
  69. # self.request is the TCP socket connected to the client
  70. # read the incoming command
  71. request = self.request.recv(1024).strip()
  72. # write to the queue waiting to be processed by the server
  73. INPUT_QUEUE.put(request)
  74. # wait for the server answer in the output queue
  75. response = OUTPUT_QUEUE.get(timeout=5.0)
  76. # send back the answer
  77. self.request.send(response)
  78. class RpcInterface(threading.Thread):
  79. """Manage RPC call over TCP/IP thanks to the SocketServer module"""
  80. def __init__(self):
  81. """Constructor"""
  82. super(RpcInterface, self).__init__()
  83. self.rpc_server = SocketServer.TCPServer(("", 2711), RpcHandler)
  84. def run(self):
  85. """run the server and wait that it returns"""
  86. self.rpc_server.serve_forever(0.5)
  87. def close(self):
  88. """force the socket server to exit"""
  89. try:
  90. self.rpc_server.shutdown()
  91. self.join(1.0)
  92. except Exception:
  93. LOGGER.warning("An error occurred while closing RPC interface")
  94. class ConsoleInterface(threading.Thread):
  95. """Manage user actions from the console"""
  96. def __init__(self):
  97. """constructor: initialize communication with the console"""
  98. super(ConsoleInterface, self).__init__()
  99. self.inq = INPUT_QUEUE
  100. self.outq = OUTPUT_QUEUE
  101. if os.name == "nt":
  102. ctypes.windll.Kernel32.GetStdHandle.restype = ctypes.c_ulong
  103. self.console_handle = ctypes.windll.Kernel32.GetStdHandle(ctypes.c_ulong(0xfffffff5))
  104. ctypes.windll.Kernel32.WaitForSingleObject.restype = ctypes.c_ulong
  105. elif os.name == "posix":
  106. # select already imported
  107. pass
  108. else:
  109. raise Exception("%s platform is not supported yet" % os.name)
  110. self._go = threading.Event()
  111. self._go.set()
  112. def _check_console_input(self):
  113. """test if there is something to read on the console"""
  114. if os.name == "nt":
  115. if 0 == ctypes.windll.Kernel32.WaitForSingleObject(self.console_handle, 500):
  116. return True
  117. elif os.name == "posix":
  118. (inputready, abcd, efgh) = select.select([sys.stdin], [], [], 0.5)
  119. if len(inputready) > 0:
  120. return True
  121. else:
  122. raise Exception("%s platform is not supported yet" % os.name)
  123. return False
  124. def run(self):
  125. """read from the console, transfer to the server and write the answer"""
  126. while self._go.isSet(): #while app is running
  127. if self._check_console_input(): #if something to read on the console
  128. cmd = sys.stdin.readline() #read it
  129. self.inq.put(cmd) #dispatch it tpo the server
  130. response = self.outq.get(timeout=2.0) #wait for an answer
  131. sys.stdout.write(response) #write the answer on the console
  132. def close(self):
  133. """terminates the thread"""
  134. self._go.clear()
  135. self.join(1.0)
  136. class Simulator(object):
  137. """The main class of the app in charge of running everything"""
  138. def __init__(self, server=None):
  139. """Constructor"""
  140. if server is None:
  141. self.server = CompositeServer([modbus_rtu.RtuServer, modbus_tcp.TcpServer], [(serial.Serial(0),), ()])
  142. else:
  143. self.server = server
  144. self.rpc = RpcInterface()
  145. self.console = ConsoleInterface()
  146. self.inq, self.outq = INPUT_QUEUE, OUTPUT_QUEUE
  147. self._hooks_fct = {}
  148. self.cmds = {
  149. "add_slave": self._do_add_slave,
  150. "has_slave": self._do_has_slave,
  151. "remove_slave": self._do_remove_slave,
  152. "remove_all_slaves": self._do_remove_all_slaves,
  153. "add_block": self._do_add_block,
  154. "remove_block": self._do_remove_block,
  155. "remove_all_blocks": self._do_remove_all_blocks,
  156. "set_values": self._do_set_values,
  157. "get_values": self._do_get_values,
  158. "install_hook": self._do_install_hook,
  159. "uninstall_hook": self._do_uninstall_hook,
  160. "set_verbose": self._do_set_verbose,
  161. }
  162. def add_command(self, name, fct):
  163. """add a custom command"""
  164. self.cmds[name] = fct
  165. def start(self):
  166. """run the servers"""
  167. self.server.start()
  168. self.console.start()
  169. self.rpc.start()
  170. LOGGER.info("modbus_tk.simulator is running...")
  171. self._handle()
  172. def declare_hook(self, fct_name, fct):
  173. """declare a hook function by its name. It must be installed by an install hook command"""
  174. self._hooks_fct[fct_name] = fct
  175. def _tuple_to_str(self, the_tuple):
  176. """convert a tuple to a string"""
  177. ret = ""
  178. for item in the_tuple:
  179. ret += (" " + str(item))
  180. return ret[1:]
  181. def _do_add_slave(self, args):
  182. """execute the add_slave command"""
  183. slave_id = int(args[1])
  184. self.server.add_slave(slave_id)
  185. return "{0}".format(slave_id)
  186. def _do_has_slave(self, args):
  187. """execute the has_slave command"""
  188. slave_id = int(args[1])
  189. try:
  190. self.server.get_slave(slave_id)
  191. except Exception:
  192. return "0"
  193. return "1"
  194. def _do_remove_slave(self, args):
  195. """execute the remove_slave command"""
  196. slave_id = int(args[1])
  197. self.server.remove_slave(slave_id)
  198. return ""
  199. def _do_remove_all_slaves(self, args):
  200. """execute the remove_slave command"""
  201. self.server.remove_all_slaves()
  202. return ""
  203. def _do_add_block(self, args):
  204. """execute the add_block command"""
  205. slave_id = int(args[1])
  206. name = args[2]
  207. block_type = int(args[3])
  208. starting_address = int(args[4])
  209. length = int(args[5])
  210. slave = self.server.get_slave(slave_id)
  211. slave.add_block(name, block_type, starting_address, length)
  212. return name
  213. def _do_remove_block(self, args):
  214. """execute the remove_block command"""
  215. slave_id = int(args[1])
  216. name = args[2]
  217. slave = self.server.get_slave(slave_id)
  218. slave.remove_block(name)
  219. def _do_remove_all_blocks(self, args):
  220. """execute the remove_all_blocks command"""
  221. slave_id = int(args[1])
  222. slave = self.server.get_slave(slave_id)
  223. slave.remove_all_blocks()
  224. def _do_set_values(self, args):
  225. """execute the set_values command"""
  226. slave_id = int(args[1])
  227. name = args[2]
  228. address = int(args[3])
  229. values = []
  230. for val in args[4:]:
  231. values.append(int(val))
  232. slave = self.server.get_slave(slave_id)
  233. slave.set_values(name, address, values)
  234. values = slave.get_values(name, address, len(values))
  235. return self._tuple_to_str(values)
  236. def _do_get_values(self, args):
  237. """execute the get_values command"""
  238. slave_id = int(args[1])
  239. name = args[2]
  240. address = int(args[3])
  241. length = int(args[4])
  242. slave = self.server.get_slave(slave_id)
  243. values = slave.get_values(name, address, length)
  244. return self._tuple_to_str(values)
  245. def _do_install_hook(self, args):
  246. """install a function as a hook"""
  247. hook_name = args[1]
  248. fct_name = args[2]
  249. hooks.install_hook(hook_name, self._hooks_fct[fct_name])
  250. def _do_uninstall_hook(self, args):
  251. """
  252. uninstall a function as a hook.
  253. If no function is given, uninstall all functions
  254. """
  255. hook_name = args[1]
  256. try:
  257. hooks.uninstall_hook(hook_name)
  258. except KeyError as exception:
  259. LOGGER.error(str(exception))
  260. def _do_set_verbose(self, args):
  261. """change the verbosity of the server"""
  262. verbose = int(args[1])
  263. self.server.set_verbose(verbose)
  264. return "%d" % verbose
  265. def _handle(self):
  266. """almost-for-ever loop in charge of listening for command and executing it"""
  267. while True:
  268. cmd = self.inq.get()
  269. args = cmd.strip('\r\n').split(' ')
  270. if cmd.find('quit') == 0:
  271. self.outq.put('bye-bye\r\n')
  272. break
  273. elif args[0] in self.cmds:
  274. try:
  275. answer = self.cmds[args[0]](args)
  276. self.outq.put("%s done: %s\r\n" % (args[0], answer))
  277. except Exception as msg:
  278. self.outq.put("%s error: %s\r\n" % (args[0], msg))
  279. else:
  280. self.outq.put("error: unknown command %s\r\n" % (args[0]))
  281. def close(self):
  282. """close every server"""
  283. self.console.close()
  284. self.rpc.close()
  285. self.server.stop()
  286. def print_me(args):
  287. """hook function example"""
  288. request = args[1]
  289. print("print_me: len = ", len(request))
  290. def run_simulator():
  291. """run simulator"""
  292. simulator = Simulator()
  293. try:
  294. LOGGER.info("'quit' for closing the server")
  295. simulator.declare_hook("print_me", print_me)
  296. simulator.start()
  297. except Exception as exception:
  298. print(exception)
  299. finally:
  300. simulator.close()
  301. LOGGER.info("modbus_tk.simulator has stopped!")
  302. # In python 2.5, the SocketServer shutdown is not working Ok
  303. # The 2 lines below are an ugly temporary workaround
  304. time.sleep(1.0)
  305. sys.exit()
  306. if __name__ == "__main__":
  307. run_simulator()