Skip to content

Commit

Permalink
Merge 9cf56c9 into 02014a8
Browse files Browse the repository at this point in the history
  • Loading branch information
lutostag committed May 19, 2018
2 parents 02014a8 + 9cf56c9 commit e3bf3b8
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 21 deletions.
11 changes: 8 additions & 3 deletions tests/unit/client/serial/test_rtu.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import pytest
from serial import serial_for_url

from umodbus.client.serial.rtu import send_message
from umodbus.client.serial.rtu import send_message, read_coils


def test_send_message_with_timeout():
""" Test if TimoutError is raised when serial port doesn't receive data."""
""" Test if TimoutError is raised when serial port doesn't receive enough
data.
"""
s = serial_for_url('loop://', timeout=0)
# as we are using a loop, we need the request will be read back as response
# to test timeout we need a request with a response that needs more bytes
message = read_coils(slave_id=0, starting_address=1, quantity=40)

with pytest.raises(ValueError):
send_message(b'', s)
send_message(message, s)
20 changes: 13 additions & 7 deletions umodbus/client/serial/rtu.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@
8
"""
import io
import struct

from umodbus.client.serial.redundancy_check import get_crc, validate_crc
from umodbus.functions import (create_function_from_response_pdu, ReadCoils,
ReadDiscreteInputs, ReadHoldingRegisters,
ReadInputRegisters, WriteSingleCoil,
WriteSingleRegister, WriteMultipleCoils,
WriteMultipleRegisters)
from umodbus.functions import (create_function_from_response_pdu,
expected_response_pdu_size_from_request_pdu,
ReadCoils, ReadDiscreteInputs,
ReadHoldingRegisters, ReadInputRegisters,
WriteSingleCoil, WriteSingleRegister,
WriteMultipleCoils, WriteMultipleRegisters)


def _create_request_adu(slave_id, req_pdu):
Expand Down Expand Up @@ -191,10 +193,14 @@ def parse_response_adu(resp_adu, req_adu=None):

def send_message(adu, serial_port):
""" Send Modbus message over serial port and parse response. """
expected_response_size = \
expected_response_pdu_size_from_request_pdu(adu[1:-2]) + 3
serial_port.write(adu)
response = serial_port.read(serial_port.in_waiting)
serial_port.flush()
bio = io.BufferedReader(serial_port, buffer_size=expected_response_size)
response = bio.read(expected_response_size)

if len(response) == 0:
if len(response) < expected_response_size:
raise ValueError

return parse_response_adu(response, adu)
25 changes: 18 additions & 7 deletions umodbus/client/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@
byte) + PDU (5 bytes).
"""
import io
import struct
from random import randint

from umodbus.functions import (create_function_from_response_pdu, ReadCoils,
ReadDiscreteInputs, ReadHoldingRegisters,
ReadInputRegisters, WriteSingleCoil,
WriteSingleRegister, WriteMultipleCoils,
WriteMultipleRegisters)
from umodbus.functions import (create_function_from_response_pdu,
expected_response_pdu_size_from_request_pdu,
ReadCoils, ReadDiscreteInputs,
ReadHoldingRegisters, ReadInputRegisters,
WriteSingleCoil, WriteSingleRegister,
WriteMultipleCoils, WriteMultipleRegisters)


def _create_request_adu(slave_id, pdu):
Expand Down Expand Up @@ -241,6 +243,15 @@ def send_message(adu, sock):
:param sock: Socket instance.
:return: Parsed response from server.
"""
sock.send(adu)
response = sock.recv(1024)
expected_response_size = \
expected_response_pdu_size_from_request_pdu(adu[7:]) + 7
fd = sock.makefile('rwb')
fd.write(adu)
fd.flush()
bio = io.BufferedReader(fd, buffer_size=expected_response_size)
response = bio.read(expected_response_size)

if len(response) < expected_response_size:
raise ValueError

return parse_response_adu(response, adu)
82 changes: 78 additions & 4 deletions umodbus/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"""
import struct
import inspect
import math
try:
from functools import reduce
except ImportError:
Expand Down Expand Up @@ -136,6 +137,15 @@ def create_function_from_request_pdu(pdu):
return function_class.create_from_request_pdu(pdu)


def expected_response_pdu_size_from_request_pdu(pdu):
""" Return number of bytes expected for response PDU, based on request PDU.
:param pdu: Array of bytes.
:return: number of bytes.
"""
return create_function_from_request_pdu(pdu).expected_response_pdu_size


class ModbusFunction(object):
function_code = None

Expand Down Expand Up @@ -189,8 +199,8 @@ class ReadCoils(ModbusFunction):
The reponse PDU varies in length, depending on the request. Each 8 coils
require 1 byte. The amount of bytes needed represent status of the coils to
can be calculated with: bytes = round(quantity / 8) + 1. This response
contains (3 / 8 + 1) = 1 byte to describe the status of the coils. The
can be calculated with: bytes = ceil(quantity / 8). This response
contains ceil(3 / 8) = 1 byte to describe the status of the coils. The
structure of a compleet response PDU looks like this:
================ ===============
Expand Down Expand Up @@ -264,6 +274,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 2 + math.ceil(self.quantity / 8)

def create_response_pdu(self, data):
""" Create response pdu.
Expand Down Expand Up @@ -394,8 +412,8 @@ class ReadDiscreteInputs(ModbusFunction):
The reponse PDU varies in length, depending on the request. 8 inputs
require 1 byte. The amount of bytes needed represent status of the inputs
to can be calculated with: bytes = round(quantity / 8) + 1. This response
contains (3 / 8 + 1) = 1 byte to describe the status of the inputs. The
to can be calculated with: bytes = ceil(quantity / 8). This response
contains ceil(3 / 8) = 1 byte to describe the status of the inputs. The
structure of a compleet response PDU looks like this:
================ ===============
Expand Down Expand Up @@ -469,6 +487,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 2 + math.ceil(self.quantity / 8)

def create_response_pdu(self, data):
""" Create response pdu.
Expand Down Expand Up @@ -665,6 +691,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 2 + self.quantity * 2

def create_response_pdu(self, data):
""" Create response pdu.
Expand Down Expand Up @@ -837,6 +871,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 2 + self.quantity * 2

def create_response_pdu(self, data):
""" Create response pdu.
Expand Down Expand Up @@ -999,6 +1041,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 5

def create_response_pdu(self):
""" Create response pdu.
Expand Down Expand Up @@ -1141,6 +1191,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 5

def create_response_pdu(self):
fmt = '>BH' + conf.TYPE_CHAR
return struct.pack(fmt, self.function_code, self.address, self.value)
Expand Down Expand Up @@ -1347,6 +1405,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 5

def create_response_pdu(self):
""" Create response pdu.
Expand Down Expand Up @@ -1491,6 +1557,14 @@ def create_from_request_pdu(pdu):

return instance

@property
def expected_response_pdu_size(self):
""" Return number of bytes expected for response PDU.
:return: number of bytes.
"""
return 5

def create_response_pdu(self):
""" Create response pdu.
Expand Down

0 comments on commit e3bf3b8

Please sign in to comment.