modbus.py 39 KB

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