modbus.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  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. History:
  9. 2010/01/08 - RD: Update master.execute(..) to calculate lengths automatically based on requested command
  10. """
  11. from __future__ import with_statement
  12. import struct
  13. import threading
  14. from modbus_tk import LOGGER
  15. from modbus_tk import defines
  16. from modbus_tk.exceptions import(
  17. ModbusError, ModbusFunctionNotSupportedError, DuplicatedKeyError, MissingKeyError, InvalidModbusBlockError,
  18. InvalidArgumentError, OverlapModbusBlockError, OutOfModbusBlockError, ModbusInvalidResponseError,
  19. ModbusInvalidRequestError
  20. )
  21. from modbus_tk.hooks import call_hooks
  22. from modbus_tk.utils import threadsafe_function, get_log_buffer
  23. # modbus_tk is using the python logging mechanism
  24. # you can define this logger in your app in order to see its prints logs
  25. class Query(object):
  26. """
  27. Interface to be implemented in subclass for every specific modbus MAC layer
  28. """
  29. def __init__(self):
  30. """Constructor"""
  31. pass
  32. def build_request(self, pdu, slave):
  33. """
  34. Get the modbus application protocol request pdu and slave id
  35. Encapsulate with MAC layer information
  36. Returns a string
  37. """
  38. raise NotImplementedError()
  39. def parse_response(self, response):
  40. """
  41. Get the full response and extract the modbus application protocol
  42. response pdu
  43. Returns a string
  44. """
  45. raise NotImplementedError()
  46. def parse_request(self, request):
  47. """
  48. Get the full request and extract the modbus application protocol
  49. request pdu
  50. Returns a string and the slave id
  51. """
  52. raise NotImplementedError()
  53. def build_response(self, response_pdu):
  54. """
  55. Get the modbus application protocol response pdu and encapsulate with
  56. MAC layer information
  57. Returns a string
  58. """
  59. raise NotImplementedError()
  60. class Master(object):
  61. """
  62. This class implements the Modbus Application protocol for a master
  63. To be subclassed with a class implementing the MAC layer
  64. """
  65. def __init__(self, timeout_in_sec, hooks=None):
  66. """Constructor: can define a timeout"""
  67. self._timeout = timeout_in_sec
  68. self._verbose = False
  69. self._is_opened = False
  70. def __del__(self):
  71. """Destructor: close the connection"""
  72. self.close()
  73. def set_verbose(self, verbose):
  74. """print some more log prints for debug purpose"""
  75. self._verbose = verbose
  76. def open(self):
  77. """open the communication with the slave"""
  78. if not self._is_opened:
  79. self._do_open()
  80. self._is_opened = True
  81. def close(self):
  82. """close the communication with the slave"""
  83. if self._is_opened:
  84. ret = self._do_close()
  85. if ret:
  86. self._is_opened = False
  87. def _do_open(self):
  88. """Open the MAC layer"""
  89. raise NotImplementedError()
  90. def _do_close(self):
  91. """Close the MAC layer"""
  92. raise NotImplementedError()
  93. def _send(self, buf):
  94. """Send data to a slave on the MAC layer"""
  95. raise NotImplementedError()
  96. def _recv(self, expected_length):
  97. """
  98. Receive data from a slave on the MAC layer
  99. if expected_length is >=0 then consider that the response is done when this
  100. number of bytes is received
  101. """
  102. raise NotImplementedError()
  103. def _make_query(self):
  104. """
  105. Returns an instance of a Query subclass implementing
  106. the MAC layer protocol
  107. """
  108. raise NotImplementedError()
  109. def _handle(self):
  110. """
  111. Returns an instance of a Query subclass implementing
  112. the MAC layer protocol
  113. """
  114. raise NotImplementedError()
  115. def _result(self, response):
  116. data = response.hex()
  117. return data
  118. @threadsafe_function
  119. def execute(
  120. self, slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="", expected_length=-1):
  121. """
  122. Execute a modbus query and returns the data part of the answer as a tuple
  123. The returned tuple depends on the query function code. see modbus protocol
  124. specification for details
  125. data_format makes possible to extract the data like defined in the
  126. struct python module documentation
  127. """
  128. if isinstance(function_code, str):
  129. request = bytes.fromhex(function_code)
  130. #request = function_code.encode()
  131. response = self._handle(request, expected_length);
  132. return self._result(response)
  133. pdu = ""
  134. is_read_function = False
  135. nb_of_digits = 0
  136. # open the connection if it is not already done
  137. self.open()
  138. # Build the modbus pdu and the format of the expected data.
  139. # It depends of function code. see modbus specifications for details.
  140. if function_code == defines.READ_COILS or function_code == defines.READ_DISCRETE_INPUTS:
  141. is_read_function = True
  142. pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x)
  143. byte_count = quantity_of_x // 8
  144. if (quantity_of_x % 8) > 0:
  145. byte_count += 1
  146. nb_of_digits = quantity_of_x
  147. if not data_format:
  148. data_format = ">" + (byte_count * "B")
  149. if expected_length < 0:
  150. # No length was specified and calculated length can be used:
  151. # slave + func + bytcodeLen + bytecode + crc1 + crc2
  152. expected_length = byte_count + 5
  153. elif function_code == defines.READ_INPUT_REGISTERS or function_code == defines.READ_HOLDING_REGISTERS:
  154. is_read_function = True
  155. pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x)
  156. if not data_format:
  157. data_format = ">" + (quantity_of_x * "H")
  158. if expected_length < 0:
  159. # No length was specified and calculated length can be used:
  160. # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2
  161. expected_length = 2 * quantity_of_x + 5
  162. elif (function_code == defines.WRITE_SINGLE_COIL) or (function_code == defines.WRITE_SINGLE_REGISTER):
  163. if function_code == defines.WRITE_SINGLE_COIL:
  164. if output_value != 0:
  165. output_value = 0xff00
  166. fmt = ">BHH"
  167. else:
  168. fmt = ">BH"+("H" if output_value >= 0 else "h")
  169. pdu = struct.pack(fmt, function_code, starting_address, output_value)
  170. if not data_format:
  171. data_format = ">HH"
  172. if expected_length < 0:
  173. # No length was specified and calculated length can be used:
  174. # slave + func + adress1 + adress2 + value1+value2 + crc1 + crc2
  175. expected_length = 8
  176. elif function_code == defines.WRITE_MULTIPLE_COILS:
  177. byte_count = len(output_value) // 8
  178. if (len(output_value) % 8) > 0:
  179. byte_count += 1
  180. pdu = struct.pack(">BHHB", function_code, starting_address, len(output_value), byte_count)
  181. i, byte_value = 0, 0
  182. for j in output_value:
  183. if j > 0:
  184. byte_value += pow(2, i)
  185. if i == 7:
  186. pdu += struct.pack(">B", byte_value)
  187. i, byte_value = 0, 0
  188. else:
  189. i += 1
  190. if i > 0:
  191. pdu += struct.pack(">B", byte_value)
  192. if not data_format:
  193. data_format = ">HH"
  194. if expected_length < 0:
  195. # No length was specified and calculated length can be used:
  196. # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2
  197. expected_length = 8
  198. elif function_code == defines.WRITE_MULTIPLE_REGISTERS:
  199. if output_value and data_format:
  200. byte_count = struct.calcsize(data_format)
  201. else:
  202. byte_count = 2 * len(output_value)
  203. pdu = struct.pack(">BHHB", function_code, starting_address, byte_count // 2, byte_count)
  204. if output_value and data_format:
  205. pdu += struct.pack(data_format, *output_value)
  206. else:
  207. for j in output_value:
  208. fmt = "H" if j >= 0 else "h"
  209. pdu += struct.pack(">" + fmt, j)
  210. # data_format is now used to process response which is always 2 registers:
  211. # 1) data address of first register, 2) number of registers written
  212. data_format = ">HH"
  213. if expected_length < 0:
  214. # No length was specified and calculated length can be used:
  215. # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2
  216. expected_length = 8
  217. elif function_code == defines.READ_EXCEPTION_STATUS:
  218. pdu = struct.pack(">B", function_code)
  219. data_format = ">B"
  220. if expected_length < 0:
  221. # No length was specified and calculated length can be used:
  222. expected_length = 5
  223. elif function_code == defines.DIAGNOSTIC:
  224. # SubFuncCode are in starting_address
  225. pdu = struct.pack(">BH", function_code, starting_address)
  226. if len(output_value) > 0:
  227. for j in output_value:
  228. # copy data in pdu
  229. pdu += struct.pack(">B", j)
  230. if not data_format:
  231. data_format = ">" + (len(output_value) * "B")
  232. if expected_length < 0:
  233. # No length was specified and calculated length can be used:
  234. # slave + func + SubFunc1 + SubFunc2 + Data + crc1 + crc2
  235. expected_length = len(output_value) + 6
  236. elif function_code == defines.READ_WRITE_MULTIPLE_REGISTERS:
  237. is_read_function = True
  238. byte_count = 2 * len(output_value)
  239. pdu = struct.pack(
  240. ">BHHHHB",
  241. function_code, starting_address, quantity_of_x, defines.READ_WRITE_MULTIPLE_REGISTERS,
  242. len(output_value), byte_count
  243. )
  244. for j in output_value:
  245. fmt = "H" if j >= 0 else "h"
  246. # copy data in pdu
  247. pdu += struct.pack(">"+fmt, j)
  248. if not data_format:
  249. data_format = ">" + (quantity_of_x * "H")
  250. if expected_length < 0:
  251. # No lenght was specified and calculated length can be used:
  252. # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2
  253. expected_length = 2 * quantity_of_x + 5
  254. else:
  255. raise ModbusFunctionNotSupportedError("The {0} function code is not supported. ".format(function_code))
  256. # instantiate a query which implements the MAC (TCP or RTU) part of the protocol
  257. query = self._make_query()
  258. # add the mac part of the protocol to the request
  259. request = query.build_request(pdu, slave)
  260. # send the request to the slave
  261. retval = call_hooks("modbus.Master.before_send", (self, request))
  262. if retval is not None:
  263. request = retval
  264. if self._verbose:
  265. LOGGER.debug(get_log_buffer("-> ", request))
  266. self._send(request)
  267. call_hooks("modbus.Master.after_send", (self, ))
  268. if slave != 0:
  269. # receive the data from the slave
  270. response = self._recv(expected_length)
  271. retval = call_hooks("modbus.Master.after_recv", (self, response))
  272. if retval is not None:
  273. response = retval
  274. if self._verbose:
  275. LOGGER.debug(get_log_buffer("<- ", response))
  276. # extract the pdu part of the response
  277. response_pdu = query.parse_response(response)
  278. # analyze the received data
  279. (return_code, byte_2) = struct.unpack(">BB", response_pdu[0:2])
  280. if return_code > 0x80:
  281. # the slave has returned an error
  282. exception_code = byte_2
  283. raise ModbusError(exception_code)
  284. else:
  285. if is_read_function:
  286. # get the values returned by the reading function
  287. byte_count = byte_2
  288. data = response_pdu[2:]
  289. if byte_count != len(data):
  290. # the byte count in the pdu is invalid
  291. raise ModbusInvalidResponseError(
  292. "Byte count is {0} while actual number of bytes is {1}. ".format(byte_count, len(data))
  293. )
  294. else:
  295. # returns what is returned by the slave after a writing function
  296. data = response_pdu[1:]
  297. # returns the data as a tuple according to the data_format
  298. # (calculated based on the function or user-defined)
  299. result = struct.unpack(data_format, data)
  300. if nb_of_digits > 0:
  301. digits = []
  302. for byte_val in result:
  303. for i in range(8):
  304. if len(digits) >= nb_of_digits:
  305. break
  306. digits.append(byte_val % 2)
  307. byte_val = byte_val >> 1
  308. result = tuple(digits)
  309. return result
  310. def set_timeout(self, timeout_in_sec):
  311. """Defines a timeout on the MAC layer"""
  312. self._timeout = timeout_in_sec
  313. def get_timeout(self):
  314. """Gets the current value of the MAC layer timeout"""
  315. return self._timeout
  316. class ModbusBlock(object):
  317. """This class represents the values for a range of addresses"""
  318. def __init__(self, starting_address, size, name=''):
  319. """
  320. Contructor: defines the address range and creates the array of values
  321. """
  322. self.starting_address = starting_address
  323. self._data = [0] * size
  324. self.size = len(self._data)
  325. def is_in(self, starting_address, size):
  326. """
  327. Returns true if a block with the given address and size
  328. would overlap this block
  329. """
  330. if starting_address > self.starting_address:
  331. return (self.starting_address + self.size) > starting_address
  332. elif starting_address < self.starting_address:
  333. return (starting_address + size) > self.starting_address
  334. return True
  335. def __getitem__(self, item):
  336. """"""
  337. return self._data.__getitem__(item)
  338. def __setitem__(self, item, value):
  339. """"""
  340. call_hooks("modbus.ModbusBlock.setitem", (self, item, value))
  341. return self._data.__setitem__(item, value)
  342. class Slave(object):
  343. """
  344. This class define a modbus slave which is in charge of making the action
  345. asked by a modbus query
  346. """
  347. def __init__(self, slave_id, unsigned=True, memory=None):
  348. """Constructor"""
  349. self._id = slave_id
  350. # treat every value written to/read from register as an unsigned value
  351. self.unsigned = unsigned
  352. # the map registring all blocks of the slave
  353. self._blocks = {}
  354. # a shortcut to find blocks per type
  355. if memory is None:
  356. self._memory = {
  357. defines.COILS: [],
  358. defines.DISCRETE_INPUTS: [],
  359. defines.HOLDING_REGISTERS: [],
  360. defines.ANALOG_INPUTS: [],
  361. }
  362. else:
  363. self._memory = memory
  364. # a lock for mutual access to the _blocks and _memory maps
  365. self._data_lock = threading.RLock()
  366. # map modbus function code to a function:
  367. self._fn_code_map = {
  368. defines.READ_COILS: self._read_coils,
  369. defines.READ_DISCRETE_INPUTS: self._read_discrete_inputs,
  370. defines.READ_INPUT_REGISTERS: self._read_input_registers,
  371. defines.READ_HOLDING_REGISTERS: self._read_holding_registers,
  372. defines.WRITE_SINGLE_COIL: self._write_single_coil,
  373. defines.WRITE_SINGLE_REGISTER: self._write_single_register,
  374. defines.WRITE_MULTIPLE_COILS: self._write_multiple_coils,
  375. defines.WRITE_MULTIPLE_REGISTERS: self._write_multiple_registers,
  376. }
  377. def _get_block_and_offset(self, block_type, address, length):
  378. """returns the block and offset corresponding to the given address"""
  379. for block in self._memory[block_type]:
  380. if address >= block.starting_address:
  381. offset = address - block.starting_address
  382. if block.size >= offset + length:
  383. return block, offset
  384. raise ModbusError(defines.ILLEGAL_DATA_ADDRESS)
  385. def _read_digital(self, block_type, request_pdu):
  386. """read the value of coils and discrete inputs"""
  387. (starting_address, quantity_of_x) = struct.unpack(">HH", request_pdu[1:5])
  388. if (quantity_of_x <= 0) or (quantity_of_x > 2000):
  389. # maximum allowed size is 2000 bits in one reading
  390. raise ModbusError(defines.ILLEGAL_DATA_VALUE)
  391. block, offset = self._get_block_and_offset(block_type, starting_address, quantity_of_x)
  392. values = block[offset:offset+quantity_of_x]
  393. # pack bits in bytes
  394. byte_count = quantity_of_x // 8
  395. if (quantity_of_x % 8) > 0:
  396. byte_count += 1
  397. # write the response header
  398. response = struct.pack(">B", byte_count)
  399. i, byte_value = 0, 0
  400. for coil in values:
  401. if coil:
  402. byte_value += (1 << i)
  403. if i >= 7:
  404. # write the values of 8 bits in a byte
  405. response += struct.pack(">B", byte_value)
  406. # reset the counters
  407. i, byte_value = 0, 0
  408. else:
  409. i += 1
  410. # if there is remaining bits: add one more byte with their values
  411. if i > 0:
  412. fmt = "B" if self.unsigned else "b"
  413. response += struct.pack(">"+fmt, byte_value)
  414. return response
  415. def _read_coils(self, request_pdu):
  416. """handle read coils modbus function"""
  417. call_hooks("modbus.Slave.handle_read_coils_request", (self, request_pdu))
  418. return self._read_digital(defines.COILS, request_pdu)
  419. def _read_discrete_inputs(self, request_pdu):
  420. """handle read discrete inputs modbus function"""
  421. call_hooks("modbus.Slave.handle_read_discrete_inputs_request", (self, request_pdu))
  422. return self._read_digital(defines.DISCRETE_INPUTS, request_pdu)
  423. def _read_registers(self, block_type, request_pdu):
  424. """read the value of holding and input registers"""
  425. (starting_address, quantity_of_x) = struct.unpack(">HH", request_pdu[1:5])
  426. if (quantity_of_x <= 0) or (quantity_of_x > 125):
  427. # maximum allowed size is 125 registers in one reading
  428. LOGGER.debug("quantity_of_x is %d", quantity_of_x)
  429. raise ModbusError(defines.ILLEGAL_DATA_VALUE)
  430. # look for the block corresponding to the request
  431. block, offset = self._get_block_and_offset(block_type, starting_address, quantity_of_x)
  432. # get the values
  433. values = block[offset:offset+quantity_of_x]
  434. # write the response header
  435. response = struct.pack(">B", 2 * quantity_of_x)
  436. # add the values of every register on 2 bytes
  437. for reg in values:
  438. fmt = "H" if self.unsigned else "h"
  439. response += struct.pack(">"+fmt, reg)
  440. return response
  441. def _read_holding_registers(self, request_pdu):
  442. """handle read coils modbus function"""
  443. call_hooks("modbus.Slave.handle_read_holding_registers_request", (self, request_pdu))
  444. return self._read_registers(defines.HOLDING_REGISTERS, request_pdu)
  445. def _read_input_registers(self, request_pdu):
  446. """handle read coils modbus function"""
  447. call_hooks("modbus.Slave.handle_read_input_registers_request", (self, request_pdu))
  448. return self._read_registers(defines.ANALOG_INPUTS, request_pdu)
  449. def _write_multiple_registers(self, request_pdu):
  450. """execute modbus function 16"""
  451. call_hooks("modbus.Slave.handle_write_multiple_registers_request", (self, request_pdu))
  452. # get the starting address and the number of items from the request pdu
  453. (starting_address, quantity_of_x, byte_count) = struct.unpack(">HHB", request_pdu[1:6])
  454. if (quantity_of_x <= 0) or (quantity_of_x > 123) or (byte_count != (quantity_of_x * 2)):
  455. # maximum allowed size is 123 registers in one reading
  456. raise ModbusError(defines.ILLEGAL_DATA_VALUE)
  457. # look for the block corresponding to the request
  458. block, offset = self._get_block_and_offset(defines.HOLDING_REGISTERS, starting_address, quantity_of_x)
  459. count = 0
  460. for i in range(quantity_of_x):
  461. count += 1
  462. fmt = "H" if self.unsigned else "h"
  463. block[offset+i] = struct.unpack(">"+fmt, request_pdu[6+2*i:8+2*i])[0]
  464. return struct.pack(">HH", starting_address, count)
  465. def _write_multiple_coils(self, request_pdu):
  466. """execute modbus function 15"""
  467. call_hooks("modbus.Slave.handle_write_multiple_coils_request", (self, request_pdu))
  468. # get the starting address and the number of items from the request pdu
  469. (starting_address, quantity_of_x, byte_count) = struct.unpack(">HHB", request_pdu[1:6])
  470. expected_byte_count = quantity_of_x // 8
  471. if (quantity_of_x % 8) > 0:
  472. expected_byte_count += 1
  473. if (quantity_of_x <= 0) or (quantity_of_x > 1968) or (byte_count != expected_byte_count):
  474. # maximum allowed size is 1968 coils
  475. raise ModbusError(defines.ILLEGAL_DATA_VALUE)
  476. # look for the block corresponding to the request
  477. block, offset = self._get_block_and_offset(defines.COILS, starting_address, quantity_of_x)
  478. count = 0
  479. for i in range(byte_count):
  480. if count >= quantity_of_x:
  481. break
  482. fmt = "B" if self.unsigned else "b"
  483. (byte_value, ) = struct.unpack(">"+fmt, request_pdu[6+i:7+i])
  484. for j in range(8):
  485. if count >= quantity_of_x:
  486. break
  487. if byte_value & (1 << j):
  488. block[offset+i*8+j] = 1
  489. else:
  490. block[offset+i*8+j] = 0
  491. count += 1
  492. return struct.pack(">HH", starting_address, count)
  493. def _write_single_register(self, request_pdu):
  494. """execute modbus function 6"""
  495. call_hooks("modbus.Slave.handle_write_single_register_request", (self, request_pdu))
  496. fmt = "H" if self.unsigned else "h"
  497. (data_address, value) = struct.unpack(">H"+fmt, request_pdu[1:5])
  498. block, offset = self._get_block_and_offset(defines.HOLDING_REGISTERS, data_address, 1)
  499. block[offset] = value
  500. # returns echo of the command
  501. return request_pdu[1:]
  502. def _write_single_coil(self, request_pdu):
  503. """execute modbus function 5"""
  504. call_hooks("modbus.Slave.handle_write_single_coil_request", (self, request_pdu))
  505. (data_address, value) = struct.unpack(">HH", request_pdu[1:5])
  506. block, offset = self._get_block_and_offset(defines.COILS, data_address, 1)
  507. if value == 0:
  508. block[offset] = 0
  509. elif value == 0xff00:
  510. block[offset] = 1
  511. else:
  512. raise ModbusError(defines.ILLEGAL_DATA_VALUE)
  513. # returns echo of the command
  514. return request_pdu[1:]
  515. def handle_request(self, request_pdu, broadcast=False):
  516. """
  517. parse the request pdu, makes the corresponding action
  518. and returns the response pdu
  519. """
  520. # thread-safe
  521. with self._data_lock:
  522. try:
  523. retval = call_hooks("modbus.Slave.handle_request", (self, request_pdu))
  524. if retval is not None:
  525. return retval
  526. # get the function code
  527. (function_code, ) = struct.unpack(">B", request_pdu[0:1])
  528. # check if the function code is valid. If not returns error response
  529. if function_code not in self._fn_code_map:
  530. raise ModbusError(defines.ILLEGAL_FUNCTION)
  531. # if read query is broadcasted raises an error
  532. cant_be_broadcasted = (
  533. defines.READ_COILS,
  534. defines.READ_DISCRETE_INPUTS,
  535. defines.READ_INPUT_REGISTERS,
  536. defines.READ_HOLDING_REGISTERS
  537. )
  538. if broadcast and (function_code in cant_be_broadcasted):
  539. raise ModbusInvalidRequestError("Function %d can not be broadcasted" % function_code)
  540. # execute the corresponding function
  541. response_pdu = self._fn_code_map[function_code](request_pdu)
  542. if response_pdu:
  543. if broadcast:
  544. call_hooks("modbus.Slave.on_handle_broadcast", (self, response_pdu))
  545. LOGGER.debug("broadcast: %s", get_log_buffer("!!", response_pdu))
  546. return ""
  547. else:
  548. return struct.pack(">B", function_code) + response_pdu
  549. raise Exception("No response for function %d" % function_code)
  550. except ModbusError as excpt:
  551. LOGGER.debug(str(excpt))
  552. call_hooks("modbus.Slave.on_exception", (self, function_code, excpt))
  553. return struct.pack(">BB", function_code+128, excpt.get_exception_code())
  554. def add_block(self, block_name, block_type, starting_address, size):
  555. """Add a new block identified by its name"""
  556. # thread-safe
  557. with self._data_lock:
  558. if size <= 0:
  559. raise InvalidArgumentError("size must be a positive number")
  560. if starting_address < 0:
  561. raise InvalidArgumentError("starting address must be zero or positive number")
  562. if block_name in self._blocks:
  563. raise DuplicatedKeyError("Block {0} already exists. ".format(block_name))
  564. if block_type not in self._memory:
  565. raise InvalidModbusBlockError("Invalid block type {0}".format(block_type))
  566. # check that the new block doesn't overlap an existing block
  567. # it means that only 1 block per type must correspond to a given address
  568. # for example: it must not have 2 holding registers at address 100
  569. index = 0
  570. for i in range(len(self._memory[block_type])):
  571. block = self._memory[block_type][i]
  572. if block.is_in(starting_address, size):
  573. raise OverlapModbusBlockError(
  574. "Overlap block at {0} size {1}".format(block.starting_address, block.size)
  575. )
  576. if block.starting_address > starting_address:
  577. index = i
  578. break
  579. # if the block is ok: register it
  580. self._blocks[block_name] = (block_type, starting_address)
  581. # add it in the 'per type' shortcut
  582. self._memory[block_type].insert(index, ModbusBlock(starting_address, size, block_name))
  583. def remove_block(self, block_name):
  584. """
  585. Remove the block with the given name.
  586. Raise an exception if not found
  587. """
  588. # thread safe
  589. with self._data_lock:
  590. block = self._get_block(block_name)
  591. # the block has been found: remove it from the shortcut
  592. block_type = self._blocks.pop(block_name)[0]
  593. self._memory[block_type].remove(block)
  594. def remove_all_blocks(self):
  595. """
  596. Remove all the blocks
  597. """
  598. # thread safe
  599. with self._data_lock:
  600. self._blocks.clear()
  601. for key in self._memory:
  602. self._memory[key] = []
  603. def _get_block(self, block_name):
  604. """Find a block by its name and raise and exception if not found"""
  605. if block_name not in self._blocks:
  606. raise MissingKeyError("block {0} not found".format(block_name))
  607. (block_type, starting_address) = self._blocks[block_name]
  608. for block in self._memory[block_type]:
  609. if block.starting_address == starting_address:
  610. return block
  611. raise Exception("Bug?: the block {0} is not registered properly in memory".format(block_name))
  612. def set_values(self, block_name, address, values):
  613. """
  614. Set the values of the items at the given address
  615. If values is a list or a tuple, the value of every item is written
  616. If values is a number, only one value is written
  617. """
  618. # thread safe
  619. with self._data_lock:
  620. block = self._get_block(block_name)
  621. # the block has been found
  622. # check that it doesn't write out of the block
  623. offset = address-block.starting_address
  624. size = 1
  625. if isinstance(values, list) or isinstance(values, tuple):
  626. size = len(values)
  627. if (offset < 0) or ((offset + size) > block.size):
  628. raise OutOfModbusBlockError(
  629. "address {0} size {1} is out of block {2}".format(address, size, block_name)
  630. )
  631. # if Ok: write the values
  632. if isinstance(values, list) or isinstance(values, tuple):
  633. block[offset:offset+len(values)] = values
  634. else:
  635. block[offset] = values
  636. def get_values(self, block_name, address, size=1):
  637. """
  638. return the values of n items at the given address of the given block
  639. """
  640. # thread safe
  641. with self._data_lock:
  642. block = self._get_block(block_name)
  643. # the block has been found
  644. # check that it doesn't write out of the block
  645. offset = address - block.starting_address
  646. if (offset < 0) or ((offset + size) > block.size):
  647. raise OutOfModbusBlockError(
  648. "address {0} size {1} is out of block {2}".format(address, size, block_name)
  649. )
  650. # returns the values
  651. if size == 1:
  652. return tuple([block[offset], ])
  653. else:
  654. return tuple(block[offset:offset+size])
  655. class Databank(object):
  656. """A databank is a shared place containing the data of all slaves"""
  657. def __init__(self, error_on_missing_slave=True):
  658. """Constructor"""
  659. # the map of slaves by ids
  660. self._slaves = {}
  661. # protect access to the map of slaves
  662. self._lock = threading.RLock()
  663. self.error_on_missing_slave = error_on_missing_slave
  664. def add_slave(self, slave_id, unsigned=True, memory=None):
  665. """Add a new slave with the given id"""
  666. with self._lock:
  667. if (slave_id <= 0) or (slave_id > 255):
  668. raise Exception("Invalid slave id {0}".format(slave_id))
  669. if slave_id not in self._slaves:
  670. self._slaves[slave_id] = Slave(slave_id, unsigned, memory)
  671. return self._slaves[slave_id]
  672. else:
  673. raise DuplicatedKeyError("Slave {0} already exists".format(slave_id))
  674. def get_slave(self, slave_id):
  675. """Get the slave with the given id"""
  676. with self._lock:
  677. if slave_id in self._slaves:
  678. return self._slaves[slave_id]
  679. else:
  680. raise MissingKeyError("Slave {0} doesn't exist".format(slave_id))
  681. def remove_slave(self, slave_id):
  682. """Remove the slave with the given id"""
  683. with self._lock:
  684. if slave_id in self._slaves:
  685. self._slaves.pop(slave_id)
  686. else:
  687. raise MissingKeyError("Slave {0} already exists".format(slave_id))
  688. def remove_all_slaves(self):
  689. """clean the list of slaves"""
  690. with self._lock:
  691. self._slaves.clear()
  692. def handle_request(self, query, request):
  693. """
  694. when a request is received, handle it and returns the response pdu
  695. """
  696. request_pdu = ""
  697. try:
  698. # extract the pdu and the slave id
  699. (slave_id, request_pdu) = query.parse_request(request)
  700. # get the slave and let him executes the action
  701. if slave_id == 0:
  702. # broadcast
  703. for key in self._slaves:
  704. self._slaves[key].handle_request(request_pdu, broadcast=True)
  705. return
  706. else:
  707. try:
  708. slave = self.get_slave(slave_id)
  709. except MissingKeyError:
  710. if self.error_on_missing_slave:
  711. raise
  712. else:
  713. return ""
  714. response_pdu = slave.handle_request(request_pdu)
  715. # make the full response
  716. response = query.build_response(response_pdu)
  717. return response
  718. except ModbusInvalidRequestError as excpt:
  719. # Request is invalid, do not send any response
  720. LOGGER.error("invalid request: " + str(excpt))
  721. return ""
  722. except MissingKeyError as excpt:
  723. # No slave with this ID in server, do not send any response
  724. LOGGER.error("handle request failed: " + str(excpt))
  725. return ""
  726. except Exception as excpt:
  727. call_hooks("modbus.Databank.on_error", (self, excpt, request_pdu))
  728. LOGGER.error("handle request failed: " + str(excpt))
  729. # If the request was not handled correctly, return a server error response
  730. func_code = 1
  731. if len(request_pdu) > 0:
  732. (func_code, ) = struct.unpack(">B", request_pdu[0:1])
  733. return struct.pack(">BB", func_code + 0x80, defines.SLAVE_DEVICE_FAILURE)
  734. class Server(object):
  735. """
  736. This class owns several slaves and defines an interface
  737. to be implemented for a TCP or RTU server
  738. """
  739. def __init__(self, databank=None):
  740. """Constructor"""
  741. # never use a mutable type as default argument
  742. self._databank = databank if databank else Databank()
  743. self._verbose = False
  744. self._thread = None
  745. self._go = None
  746. self._make_thread()
  747. def _do_init(self):
  748. """executed before the server starts: to be overridden"""
  749. pass
  750. def _do_exit(self):
  751. """executed after the server stops: to be overridden"""
  752. pass
  753. def _do_run(self):
  754. """main function of the server: to be overridden"""
  755. pass
  756. def _make_thread(self):
  757. """create the main thread of the server"""
  758. self._thread = threading.Thread(target=Server._run_server, args=(self,))
  759. self._go = threading.Event()
  760. def set_verbose(self, verbose):
  761. """if verbose is true the sent and received packets will be logged"""
  762. self._verbose = verbose
  763. def get_db(self):
  764. """returns the databank"""
  765. return self._databank
  766. def add_slave(self, slave_id, unsigned=True, memory=None):
  767. """add slave to the server"""
  768. return self._databank.add_slave(slave_id, unsigned, memory)
  769. def get_slave(self, slave_id):
  770. """get the slave with the given id"""
  771. return self._databank.get_slave(slave_id)
  772. def remove_slave(self, slave_id):
  773. """remove the slave with the given id"""
  774. self._databank.remove_slave(slave_id)
  775. def remove_all_slaves(self):
  776. """remove the slave with the given id"""
  777. self._databank.remove_all_slaves()
  778. def _make_query(self):
  779. """
  780. Returns an instance of a Query subclass implementing
  781. the MAC layer protocol
  782. """
  783. raise NotImplementedError()
  784. def start(self):
  785. """Start the server. It will handle request"""
  786. self._go.set()
  787. self._thread.start()
  788. def stop(self):
  789. """stop the server. It doesn't handle request anymore"""
  790. if self._thread.isAlive():
  791. self._go.clear()
  792. self._thread.join()
  793. def _run_server(self):
  794. """main function of the main thread"""
  795. try:
  796. self._do_init()
  797. while self._go.isSet():
  798. self._do_run()
  799. LOGGER.info("%s has stopped", self.__class__)
  800. self._do_exit()
  801. except Exception as excpt:
  802. LOGGER.error("server error: %s", str(excpt))
  803. # make possible to rerun in future
  804. self._make_thread()
  805. def _handle(self, request):
  806. """handle a received sentence"""
  807. if self._verbose:
  808. LOGGER.debug(get_log_buffer("-->", request))
  809. # gets a query for analyzing the request
  810. query = self._make_query()
  811. retval = call_hooks("modbus.Server.before_handle_request", (self, request))
  812. if retval:
  813. request = retval
  814. response = self._databank.handle_request(query, request)
  815. retval = call_hooks("modbus.Server.after_handle_request", (self, response))
  816. if retval:
  817. response = retval
  818. if response and self._verbose:
  819. LOGGER.debug(get_log_buffer("<--", response))
  820. return response