Exemplo n.º 1
0
class modbusport(object):
    """ this class handles the communications with all the modbus devices"""
    def __init__(self, sendmessage, adderror, addserial, aw):
        self.sendmessage = sendmessage  # function to create an Artisan message to the user in the message line
        self.adderror = adderror  # signal an error to the user
        self.addserial = addserial  # add to serial log
        self.aw = aw

        # retries
        self.readRetries = 1
        #default initial settings. They are changed by settingsload() at initiation of program acording to the device chosen
        self.comport = "COM5"  #NOTE: this string should not be translated.
        self.baudrate = 115200
        self.bytesize = 8
        self.parity = 'N'
        self.stopbits = 1
        self.timeout = 1.0
        self.PID_slave_ID = 0
        self.PID_SV_register = 0
        self.PID_p_register = 0
        self.PID_i_register = 0
        self.PID_d_register = 0
        self.PID_ON_action = ""
        self.PID_OFF_action = ""

        self.channels = 8
        self.inputSlaves = [0] * self.channels
        self.inputRegisters = [0] * self.channels
        self.inputFloats = [False] * self.channels
        self.inputBCDs = [False] * self.channels
        self.inputCodes = [3] * self.channels
        self.inputDivs = [0] * self.channels  # 0: none, 1: 1/10, 2:1/100
        self.inputModes = ["C"] * self.channels

        self.optimizer = True  # if set, values of consecutive register addresses are requested in single requests
        # MODBUS functions associated to dicts associating MODBUS slave ids to registers in use
        # for optimized read of full register segments with single requests
        # this dict is re-computed on each connect() by a call to updateActiveRegisters()
        # NOTE: for registers of type float and BCD (32bit = 2x16bit) also the succeeding register is registered here
        self.activeRegisters = {}
        # the readings cache that is filled by requesting sequences of values in blocks
        self.readingsCache = {}

        self.SVmultiplier = 0
        self.PIDmultiplier = 0
        self.byteorderLittle = False
        self.wordorderLittle = True
        self.master = None
        self.COMsemaphore = QSemaphore(1)
        self.host = '127.0.0.1'  # the TCP/UDP host
        self.port = 502  # the TCP/UDP port
        self.type = 0
        # type =
        #    0: Serial RTU
        #    1: Serial ASCII
        #    2: Serial Binary
        #    3: TCP
        #    4: UDP
        self.lastReadResult = 0  # this is set by eventaction following some custom button/slider Modbus actions with "read" command

        self.commError = False  # True after a communication error was detected and not yet cleared by receiving proper data

    # this garantees a minimum of 30 miliseconds between readings and 80ms between writes (according to the Modbus spec) on serial connections
    # this sleep delays between requests seems to be beneficial on slow RTU serial connections like those of the FZ-94
    def sleepBetween(self, write=False):
        if write:
            #            if self.type in [3,4]: # TCP or UDP
            #                time.sleep(0.040)
            pass  # handled in MODBUS lib
            #            else:
            time.sleep(0.035)
        else:
            if self.type in [
                    3, 4
            ]:  # delay between writes only on serial connections
                pass
            else:
                time.sleep(0.035)

    def address2register(self, addr, code=3):
        if code == 3 or code == 6:
            return addr - 40001
        else:
            return addr - 30001

    def isConnected(self):
        return not (self.master is None) and self.master.socket

    def disconnect(self):
        try:
            self.master.close()
        except Exception:
            pass
        self.master = None

    def connect(self):
        #        if self.master and not self.master.socket:
        #            self.master = None
        if self.master is None:
            self.commError = False
            try:
                # as in the following the port is None, no port is opened on creation of the (py)serial object
                if self.type == 1:  # Serial ASCII
                    from pymodbus.client.sync import ModbusSerialClient
                    self.master = ModbusSerialClient(method='ascii',
                                                     port=self.comport,
                                                     baudrate=self.baudrate,
                                                     bytesize=self.bytesize,
                                                     parity=self.parity,
                                                     stopbits=self.stopbits,
                                                     retry_on_empty=True,
                                                     timeout=self.timeout)
                elif self.type == 2:  # Serial Binary
                    from pymodbus.client.sync import ModbusSerialClient  # @Reimport
                    self.master = ModbusSerialClient(method='binary',
                                                     port=self.comport,
                                                     baudrate=self.baudrate,
                                                     bytesize=self.bytesize,
                                                     parity=self.parity,
                                                     stopbits=self.stopbits,
                                                     retry_on_empty=True,
                                                     timeout=self.timeout)
                elif self.type == 3:  # TCP
                    from pymodbus.client.sync import ModbusTcpClient
                    try:
                        self.master = ModbusTcpClient(
                            host=self.host,
                            port=self.port,
                            retry_on_empty=True,
                            retries=1,
                            timeout=0.9,  #self.timeout
                        )
                        self.readRetries = 0
                    except:
                        self.master = ModbusTcpClient(
                            host=self.host,
                            port=self.port,
                        )
                elif self.type == 4:  # UDP
                    from pymodbus.client.sync import ModbusUdpClient
                    try:
                        self.master = ModbusUdpClient(
                            host=self.host,
                            port=self.port,
                            retry_on_empty=True,
                            retries=3,
                            timeout=0.7,  #self.timeout
                        )
                    except:  # older versions of pymodbus don't support the retries, timeout nor the retry_on_empty arguments
                        self.master = ModbusUdpClient(
                            host=self.host,
                            port=self.port,
                        )
                else:  # Serial RTU
                    from pymodbus.client.sync import ModbusSerialClient  # @Reimport
                    self.master = ModbusSerialClient(
                        method='rtu',
                        port=self.comport,
                        baudrate=self.baudrate,
                        bytesize=self.bytesize,
                        parity=self.parity,
                        stopbits=self.stopbits,
                        retry_on_empty=False,
                        strict=
                        False,  # settings this to False disables the inter char timeout restriction
                        timeout=self.timeout)
                    #                    self.master.inter_char_timeout = 0.05
                    self.readRetries = 1
                self.master.connect()
                self.updateActiveRegisters()
                time.sleep(.5)  # avoid possible hickups on startup
                if self.isConnected() != None:
                    self.sendmessage(
                        QApplication.translate("Message",
                                               "Connected via MODBUS", None))
            except Exception as ex:
                _, _, exc_tb = sys.exc_info()
                self.adderror(
                    (QApplication.translate("Error Message", "Modbus Error:",
                                            None) + " connect() {0}").format(
                                                str(ex)), exc_tb.tb_lineno)

########## MODBUS optimizer for fetching register data in batches

# MODBUS code => slave => [registers]

    def updateActiveRegisters(self):
        self.activeRegisters = {}
        for c in range(self.channels):
            slave = self.inputSlaves[c]
            if slave != 0:
                register = self.inputRegisters[c]
                code = self.inputCodes[c]
                registers = [register]
                if self.inputFloats[c] or self.inputBCDs[c]:
                    registers.append(register + 1)
                if not (code in self.activeRegisters):
                    self.activeRegisters[code] = {}
                if slave in self.activeRegisters[code]:
                    self.activeRegisters[code][slave].extend(registers)
                else:
                    self.activeRegisters[code][slave] = registers

    def clearReadingsCache(self):
        self.readingsCache = {}

    def cacheReadings(self, code, slave, register, results):
        if not (code in self.readingsCache):
            self.readingsCache[code] = {}
        if not slave in self.readingsCache[code]:
            self.readingsCache[code][slave] = {}
        for i, v in enumerate(results):
            self.readingsCache[code][slave][register + i] = v

    def readActiveRegisters(self):
        if not self.optimizer:
            return
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.clearReadingsCache()
            for code in self.activeRegisters:
                for slave in self.activeRegisters[code]:
                    registers = sorted(self.activeRegisters[code][slave])
                    # split in successive sequences
                    gaps = [[s, e] for s, e in zip(registers, registers[1:])
                            if s + 1 < e]
                    edges = iter(registers[:1] + sum(gaps, []) +
                                 registers[-1:])
                    sequences = list(
                        zip(edges, edges)
                    )  # list of pairs of the form (start-register,end-register)
                    just_send = False
                    for seq in sequences:
                        retry = self.readRetries
                        register = seq[0]
                        count = seq[1] - seq[0] + 1
                        res = None
                        if just_send:
                            self.sleepBetween(
                            )  # we start with a sleep, as it could be that just a send command happend before the semaphore was catched
                        just_send = True
                        while True:
                            try:
                                # we cache only MODBUS function 3 and 4 (not 1 and 2!)
                                if code == 3:
                                    res = self.master.read_holding_registers(
                                        register, count, unit=slave)
                                elif code == 4:
                                    res = self.master.read_input_registers(
                                        register, count, unit=slave)
                            except Exception:
                                res = None
                            if res is None or res.isError(
                            ):  # requires pymodbus v1.5.1
                                if retry > 0:
                                    retry = retry - 1
                                    time.sleep(0.020)
                                else:
                                    raise Exception("Exception response")
                            else:
                                break
                        if res is not None:
                            if self.commError:  # we clear the previous error and send a message
                                self.commError = False
                                self.adderror(
                                    QApplication.translate(
                                        "Error Message",
                                        "Modbus Communication Resumed", None))
                            self.cacheReadings(code, slave, register,
                                               res.registers)

                        #note: logged chars should be unicode not binary
                        if self.aw.seriallogflag:
                            ser_str = "MODBUS readSingleRegister : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format(
                                self.comport, self.baudrate, self.bytesize,
                                self.parity, self.stopbits, self.timeout,
                                slave, register, code, res)
                            self.addserial(ser_str)

        except Exception:  # as ex:
            #            self.disconnect()
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
            self.commError = True
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

##########

# function 15 (Write Multiple Coils)

    def writeCoils(self, slave, register, values):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_coils(int(register),
                                    list(values),
                                    unit=int(slave))
            time.sleep(.3)  # avoid possible hickups on startup
        except Exception as ex:
            #            self.disconnect()
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeCoils() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 5 (Write Single Coil)
    def writeCoil(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_coil(int(register), value, unit=int(slave))
            time.sleep(.3)  # avoid possible hickups on startup
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeCoil() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # write value to register on slave (function 6 for int or function 16 for float)
    # value can be one of string (containing an int or float), an int or a float
    def writeRegister(self, slave, register, value):
        if stringp(value):
            if "." in value:
                self.writeWord(slave, register, value)
            else:
                self.writeSingleRegister(slave, register, value)
        elif isinstance(value, int):
            self.writeSingleRegister(slave, register, value)
        elif isinstance(value, float):
            self.writeWord(slave, register, value)

    # function 6 (Write Single Holding Register)
    def writeSingleRegister(self, slave, register, value):
        #        _logger.debug("writeSingleRegister(%d,%d,%d)" % (slave,register,value))
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_register(int(register),
                                       int(value),
                                       unit=int(slave))
            time.sleep(.03)  # avoid possible hickups on startup
        except Exception as ex:
            #            _logger.debug("writeSingleRegister exception: %s" % str(ex))
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeSingleRegister() {0}").format(str(ex)),
                exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 22 (Mask Write Register)
    # bits to be modified are "masked" with a 0 in the and_mask (not and_mask)
    # new bit values to be written are taken from the or_mask
    def maskWriteRegister(self, slave, register, and_mask, or_mask):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.mask_write_register(int(register),
                                            int(and_mask),
                                            int(or_mask),
                                            unit=int(slave))
            time.sleep(.03)
        except Exception as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeMask() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # a local variant of function 22 (Mask Write Register)
    # the masks are evaluated locally on the given integer value and the result is send via
    # using function 6
    def localMaskWriteRegister(self, slave, register, and_mask, or_mask,
                               value):
        new_val = (int(round(value)) & and_mask) | (or_mask &
                                                    (and_mask ^ 0xFFFF))
        self.writeSingleRegister(slave, register, int(new_val))

    # function 16 (Write Multiple Holding Registers)
    # values is a list of integers or one integer
    def writeRegisters(self, slave, register, values):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_registers(int(register), values, unit=int(slave))
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeRegisters() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 16 (Write Multiple Holding Registers)
    # value=int or float
    # writes a single precision 32bit float (2-registers)
    def writeWord(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            builder = getBinaryPayloadBuilder(self.byteorderLittle,
                                              self.wordorderLittle)
            builder.add_32bit_float(float(value))
            payload = builder.build()  # .tolist()
            self.master.write_registers(int(register),
                                        payload,
                                        unit=int(slave),
                                        skip_encode=True)
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # translates given int value int a 16bit BCD and writes it into one register
    def writeBCD(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            builder = getBinaryPayloadBuilder(self.byteorderLittle,
                                              self.wordorderLittle)
            r = convert_to_bcd(int(value))
            builder.add_16bit_uint(r)
            payload = builder.build()  # .tolist()
            self.master.write_registers(int(register),
                                        payload,
                                        unit=int(slave),
                                        skip_encode=True)
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers)
    def readFloat(self, slave, register, code=3):
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            if code in self.readingsCache and slave in self.readingsCache[code] and register in self.readingsCache[code][slave] \
                and register+1 in self.readingsCache[code][slave]:
                # cache hit
                res = [
                    self.readingsCache[code][slave][register],
                    self.readingsCache[code][slave][register + 1]
                ]
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res, self.byteorderLittle, self.wordorderLittle)
                return decoder.decode_32bit_float()
            else:
                retry = self.readRetries
                while True:
                    if code == 3:
                        res = self.master.read_holding_registers(
                            int(register), 2, unit=int(slave))
                    else:
                        res = self.master.read_input_registers(int(register),
                                                               2,
                                                               unit=int(slave))
                    if res is None or res.isError(
                    ):  # requires pymodbus v1.5.1
                        if retry > 0:
                            retry = retry - 1
                            #time.sleep(0.020)
                        else:
                            raise Exception("Exception response")
                    else:
                        break
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res.registers, self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_32bit_float()
                if self.commError:  # we clear the previous error and send a message
                    self.commError = False
                    self.adderror(
                        QApplication.translate("Error Message",
                                               "Modbus Communication Resumed",
                                               None))
                return r
        except Exception:  # as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readFloat() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            if self.aw.seriallogflag:
                ser_str = "MODBUS readFloat :{},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format(
                    self.comport, self.baudrate, self.bytesize, self.parity,
                    self.stopbits, self.timeout, slave, register, code, r)
                self.addserial(ser_str)

    # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers)
    def readBCD(self, slave, register, code=3):
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            if code in self.readingsCache and slave in self.readingsCache[code] and register in self.readingsCache[code][slave] \
                and register+1 in self.readingsCache[code][slave]:
                # cache hit
                res = [
                    self.readingsCache[code][slave][register],
                    self.readingsCache[code][slave][register + 1]
                ]
                decoder = getBinaryPayloadDecoderFromRegisters(
                    [res], self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_32bit_uint()
                return convert_from_bcd(r)
            else:
                retry = self.readRetries
                while True:
                    if code == 3:
                        res = self.master.read_holding_registers(
                            int(register), 2, unit=int(slave))
                    else:
                        res = self.master.read_input_registers(int(register),
                                                               2,
                                                               unit=int(slave))
                    if res is None or res.isError(
                    ):  # requires pymodbus v1.5.1
                        if retry > 0:
                            retry = retry - 1
                            #time.sleep(0.020)
                        else:
                            raise Exception("Exception response")
                    else:
                        break
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res.registers, self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_32bit_uint()
                if self.commError:  # we clear the previous error and send a message
                    self.commError = False
                    self.adderror(
                        QApplication.translate("Error Message",
                                               "Modbus Communication Resumed",
                                               None))
                time.sleep(
                    0.020
                )  # we add a small sleep between requests to help out the slow Loring electronic
                return convert_from_bcd(r)
        except Exception:  # as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readBCD() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            if self.aw.seriallogflag:
                ser_str = "MODBUS readBCD : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format(
                    self.comport, self.baudrate, self.bytesize, self.parity,
                    self.stopbits, self.timeout, slave, register, code, r)
                self.addserial(ser_str)

    # as readSingleRegister, but does not retry nor raise and error and returns a None instead
    # also does not reserve the port via a semaphore!
    def peekSingleRegister(self, slave, register, code=3):
        try:
            if code == 1:
                res = self.master.read_coils(int(register), 1, unit=int(slave))
            elif code == 2:
                res = self.master.read_discrete_inputs(int(register),
                                                       1,
                                                       unit=int(slave))
            elif code == 4:
                res = self.master.read_input_registers(int(register),
                                                       1,
                                                       unit=int(slave))
            else:  # code==3
                res = self.master.read_holding_registers(int(register),
                                                         1,
                                                         unit=int(slave))
        except Exception:
            res = None
        if res is not None and not res.isError():  # requires pymodbus v1.5.1
            if code in [1, 2]:
                if res is not None and res.bits[0]:
                    return 1
                else:
                    return 0
            else:
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res.registers, self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_16bit_uint()
                return r
        else:
            return None

    # function 1 (Read Coil)
    # function 2 (Read Discrete Input)
    # function 3 (Read Multiple Holding Registers) and
    # function 4 (Read Input Registers)
    def readSingleRegister(self, slave, register, code=3):
        #        import logging
        #        logging.basicConfig()
        #        log = logging.getLogger()
        #        log.setLevel(logging.DEBUG)
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            if code in self.readingsCache and slave in self.readingsCache[
                    code] and register in self.readingsCache[code][slave]:
                # cache hit
                res = self.readingsCache[code][slave][register]
                decoder = getBinaryPayloadDecoderFromRegisters(
                    [res], self.byteorderLittle, self.wordorderLittle)
                return decoder.decode_16bit_uint()
            else:
                retry = self.readRetries
                while True:
                    try:
                        if code == 1:
                            res = self.master.read_coils(int(register),
                                                         1,
                                                         unit=int(slave))
                        elif code == 2:
                            res = self.master.read_discrete_inputs(
                                int(register), 1, unit=int(slave))
                        elif code == 4:
                            res = self.master.read_input_registers(
                                int(register), 1, unit=int(slave))
                        else:  # code==3
                            res = self.master.read_holding_registers(
                                int(register), 1, unit=int(slave))
                    except Exception:
                        res = None
                    if res is None or res.isError(
                    ):  # requires pymodbus v1.5.1
                        if retry > 0:
                            retry = retry - 1
                            time.sleep(0.020)
                        else:
                            raise Exception("Exception response")
                    else:
                        break
                if code in [1, 2]:
                    if res is not None and res.bits[0]:
                        r = 1
                    else:
                        r = 0
                    if self.commError:  # we clear the previous error and send a message
                        self.commError = False
                        self.adderror(
                            QApplication.translate(
                                "Error Message",
                                "Modbus Communication Resumed", None))
                    return r
                else:
                    decoder = getBinaryPayloadDecoderFromRegisters(
                        res.registers, self.byteorderLittle,
                        self.wordorderLittle)
                    r = decoder.decode_16bit_uint()
                    if self.commError:  # we clear the previous error and send a message
                        self.commError = False
                        self.adderror(
                            QApplication.translate(
                                "Error Message",
                                "Modbus Communication Resumed", None))
                    return r
        except Exception:  # as ex:
            #            self.disconnect()
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
            self.commError = True
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            if self.aw.seriallogflag:
                ser_str = "MODBUS readSingleRegister : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format(
                    self.comport, self.baudrate, self.bytesize, self.parity,
                    self.stopbits, self.timeout, slave, register, code, r)
                self.addserial(ser_str)

    def setTarget(self, sv):
        if self.PID_slave_ID:
            multiplier = 1.
            if self.SVmultiplier == 1:
                multiplier = 10.
            elif self.SVmultiplier == 2:
                multiplier = 100.
            self.writeSingleRegister(self.PID_slave_ID, self.PID_SV_register,
                                     int(round(sv * multiplier)))

    def setPID(self, p, i, d):
        if self.PID_slave_ID and not (self.PID_p_register ==
                                      self.PID_i_register ==
                                      self.PID_d_register == 0):
            multiplier = 1.
            if self.PIDmultiplier == 1:
                multiplier = 10.
            elif self.PIDmultiplier == 2:
                multiplier = 100.
            self.writeSingleRegister(self.PID_slave_ID, self.PID_p_register,
                                     p * multiplier)
            self.writeSingleRegister(self.PID_slave_ID, self.PID_i_register,
                                     i * multiplier)
            self.writeSingleRegister(self.PID_slave_ID, self.PID_d_register,
                                     d * multiplier)
Exemplo n.º 2
0
class modbusport(object):
    """ this class handles the communications with all the modbus devices"""
    def __init__(self, sendmessage, adderror, addserial):
        self.sendmessage = sendmessage  # function to create an Artisan message to the user in the message line
        self.adderror = adderror  # signal an error to the user
        self.addserial = addserial  # add to serial log

        # retries
        self.readRetries = 1
        #default initial settings. They are changed by settingsload() at initiation of program acording to the device chosen
        self.comport = "COM5"  #NOTE: this string should not be translated.
        self.baudrate = 115200
        self.bytesize = 8
        self.parity = 'N'
        self.stopbits = 1
        self.timeout = 1.0
        self.PID_slave_ID = 0
        self.PID_SV_register = 0
        self.PID_p_register = 0
        self.PID_i_register = 0
        self.PID_d_register = 0
        self.PID_ON_action = ""
        self.PID_OFF_action = ""
        self.input1slave = 0
        self.input1register = 0
        self.input1float = False
        self.input1bcd = False
        self.input1code = 3
        self.input1div = 0  # 0: none, 1: 1/10, 2:1/100
        self.input1mode = "C"
        self.input2slave = 0
        self.input2register = 0
        self.input2float = False
        self.input2bcd = False
        self.input2code = 3
        self.input2div = 0
        self.input2mode = "C"
        self.input3slave = 0
        self.input3register = 0
        self.input3float = False
        self.input3bcd = False
        self.input3code = 3
        self.input3div = 0
        self.input3mode = "C"
        self.input4slave = 0
        self.input4register = 0
        self.input4float = False
        self.input4bcd = False
        self.input4code = 3
        self.input4div = 0
        self.input4mode = "C"
        self.input5slave = 0
        self.input5register = 0
        self.input5float = False
        self.input5bcd = False
        self.input5code = 3
        self.input5div = 0
        self.input5mode = "C"
        self.input6slave = 0
        self.input6register = 0
        self.input6float = False
        self.input6bcd = False
        self.input6code = 3
        self.input6div = 0
        self.input6mode = "C"
        self.SVmultiplier = 0
        self.PIDmultiplier = 0
        self.byteorderLittle = False
        self.wordorderLittle = True
        self.master = None
        self.COMsemaphore = QSemaphore(1)
        self.host = '127.0.0.1'  # the TCP/UDP host
        self.port = 502  # the TCP/UDP port
        self.type = 0
        # type =
        #    0: Serial RTU
        #    1: Serial ASCII
        #    2: Serial Binary
        #    3: TCP
        #    4: UDP
        self.lastReadResult = 0  # this is set by eventaction following some custom button/slider Modbus actions with "read" command

        self.commError = False  # True after a communication error was detected and not yet cleared by receiving proper data

    # this garantees a minimum of 30 miliseconds between readings and 80ms between writes (according to the Modbus spec) on serial connections
    # this sleep delays between requests seems to be beneficial on slow RTU serial connections like those of the FZ-94
    def sleepBetween(self, write=False):
        if write:
            #            if self.type in [3,4]: # TCP or UDP
            #                time.sleep(0.040)
            pass  # handled in MODBUS lib
            #            else:
            time.sleep(0.035)
        else:
            if self.type in [
                    3, 4
            ]:  # delay between writes only on serial connections
                pass
            else:
                time.sleep(0.035)

    def address2register(self, addr, code=3):
        if code == 3 or code == 6:
            return addr - 40001
        else:
            return addr - 30001

    def isConnected(self):
        return not (self.master is None) and self.master.socket

    def disconnect(self):
        try:
            self.master.close()
        except Exception:
            pass
        self.master = None

    def connect(self):
        #        if self.master and not self.master.socket:
        #            self.master = None
        if self.master is None:
            self.commError = False
            try:
                # as in the following the port is None, no port is opened on creation of the (py)serial object
                if self.type == 1:  # Serial ASCII
                    from pymodbus.client.sync import ModbusSerialClient
                    self.master = ModbusSerialClient(method='ascii',
                                                     port=self.comport,
                                                     baudrate=self.baudrate,
                                                     bytesize=self.bytesize,
                                                     parity=self.parity,
                                                     stopbits=self.stopbits,
                                                     retry_on_empty=True,
                                                     timeout=self.timeout)
                elif self.type == 2:  # Serial Binary
                    from pymodbus.client.sync import ModbusSerialClient
                    self.master = ModbusSerialClient(method='binary',
                                                     port=self.comport,
                                                     baudrate=self.baudrate,
                                                     bytesize=self.bytesize,
                                                     parity=self.parity,
                                                     stopbits=self.stopbits,
                                                     retry_on_empty=True,
                                                     timeout=self.timeout)
                elif self.type == 3:  # TCP
                    from pymodbus.client.sync import ModbusTcpClient
                    try:
                        self.master = ModbusTcpClient(
                            host=self.host,
                            port=self.port,
                            retry_on_empty=True,
                            retries=1,
                            timeout=0.9,  #self.timeout
                        )
                        self.readRetries = 0
                    except:
                        self.master = ModbusTcpClient(
                            host=self.host,
                            port=self.port,
                        )
                elif self.type == 4:  # UDP
                    from pymodbus.client.sync import ModbusUdpClient
                    try:
                        self.master = ModbusUdpClient(
                            host=self.host,
                            port=self.port,
                            retry_on_empty=True,
                            retries=3,
                            timeout=0.7,  #self.timeout
                        )
                    except:  # older versions of pymodbus don't support the retries, timeout nor the retry_on_empty arguments
                        self.master = ModbusUdpClient(
                            host=self.host,
                            port=self.port,
                        )
                else:  # Serial RTU
                    from pymodbus.client.sync import ModbusSerialClient
                    self.master = ModbusSerialClient(method='rtu',
                                                     port=self.comport,
                                                     baudrate=self.baudrate,
                                                     bytesize=self.bytesize,
                                                     parity=self.parity,
                                                     stopbits=self.stopbits,
                                                     retry_on_empty=False,
                                                     timeout=self.timeout)
                    self.readRetries = 1
                self.master.connect()
                self.adderror(
                    QApplication.translate("Error Message",
                                           "Connected via MODBUS", None))
                time.sleep(.5)  # avoid possible hickups on startup
            except Exception as ex:
                _, _, exc_tb = sys.exc_info()
                self.adderror(
                    (QApplication.translate("Error Message", "Modbus Error:",
                                            None) + " connect() {0}").format(
                                                str(ex)), exc_tb.tb_lineno)

    # function 15 (Write Multiple Coils)
    def writeCoils(self, slave, register, values):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_coils(int(register),
                                    list(values),
                                    unit=int(slave))
            time.sleep(.3)  # avoid possible hickups on startup
        except Exception as ex:
            #            self.disconnect()
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeCoils() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 5 (Write Single Coil)
    def writeCoil(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_coil(int(register), value, unit=int(slave))
            time.sleep(.3)  # avoid possible hickups on startup
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeCoil() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # write value to register on slave (function 6 for int or function 16 for float)
    # value can be one of string (containing an int or float), an int or a float
    def writeRegister(self, slave, register, value):
        #        print("writeRegister",slave,register,value)
        if stringp(value):
            if "." in value:
                self.writeWord(slave, register, value)
            else:
                self.writeSingleRegister(slave, register, value)
        elif isinstance(value, int):
            self.writeSingleRegister(slave, register, value)
        elif isinstance(value, float):
            self.writeWord(slave, register, value)

    # function 6 (Write Single Holding Register)
    def writeSingleRegister(self, slave, register, value):
        #        _logger.debug("writeSingleRegister(%d,%d,%d)" % (slave,register,value))
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_register(int(register),
                                       int(value),
                                       unit=int(slave))
            time.sleep(.03)  # avoid possible hickups on startup
        except Exception as ex:
            #            _logger.debug("writeSingleRegister exception: %s" % str(ex))
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeSingleRegister() {0}").format(str(ex)),
                exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 22 (Mask Write Register)
    def maskWriteRegister(self, slave, register, and_mask, or_mask):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.mask_write_register(int(register),
                                            int(and_mask),
                                            int(or_mask),
                                            unit=int(slave))
            time.sleep(.03)
        except Exception as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeMask() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 16 (Write Multiple Holding Registers)
    # values is a list of integers or one integer
    def writeRegisters(self, slave, register, values):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            self.master.write_registers(int(register), values, unit=int(slave))
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeRegisters() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 16 (Write Multiple Holding Registers)
    # value=int or float
    # writes a single precision 32bit float (2-registers)
    def writeWord(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            builder = getBinaryPayloadBuilder(self.byteorderLittle,
                                              self.wordorderLittle)
            builder.add_32bit_float(float(value))
            payload = builder.build()  # .tolist()
            self.master.write_registers(int(register),
                                        payload,
                                        unit=int(slave),
                                        skip_encode=True)
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # translates given int value int a 16bit BCD and writes it into one register
    def writeBCD(self, slave, register, value):
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            builder = getBinaryPayloadBuilder(self.byteorderLittle,
                                              self.wordorderLittle)
            r = convert_to_bcd(int(value))
            builder.add_16bit_uint(r)
            payload = builder.build()  # .tolist()
            self.master.write_registers(int(register),
                                        payload,
                                        unit=int(slave),
                                        skip_encode=True)
            time.sleep(.03)
        except Exception as ex:
            #            self.disconnect()
            _, _, exc_tb = sys.exc_info()
            self.adderror(
                (QApplication.translate("Error Message", "Modbus Error:", None)
                 + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno)
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)

    # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers)
    def readFloat(self, slave, register, code=3):
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            retry = self.readRetries
            while True:
                if code == 3:
                    res = self.master.read_holding_registers(int(register),
                                                             2,
                                                             unit=int(slave))
                else:
                    res = self.master.read_input_registers(int(register),
                                                           2,
                                                           unit=int(slave))
                if res is None or res.isError():  # requires pymodbus v1.5.1
                    if retry > 0:
                        retry = retry - 1
                        #time.sleep(0.020)
                    else:
                        raise Exception("Exception response")
                else:
                    break
            decoder = getBinaryPayloadDecoderFromRegisters(
                res.registers, self.byteorderLittle, self.wordorderLittle)
            r = decoder.decode_32bit_float()
            if self.commError:  # we clear the previous error and send a message
                self.commError = False
                self.adderror(
                    QApplication.translate("Error Message",
                                           "Modbus Communication Resumed",
                                           None))
            return r
        except Exception:  # as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readFloat() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            settings = str(self.comport) + "," + str(
                self.baudrate) + "," + str(self.bytesize) + "," + str(
                    self.parity) + "," + str(self.stopbits) + "," + str(
                        self.timeout)
            ser_str = "MODBUS readFloat :" + settings + " || Slave = " + str(
                slave) + " || Register = " + str(
                    register) + " || Code = " + str(code)
            if r is not None:
                ser_str = ser_str + " || Rx = " + str(r)
            self.addserial(ser_str)

    # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers)
    def readBCD(self, slave, register, code=3):
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            retry = self.readRetries
            while True:
                if code == 3:
                    res = self.master.read_holding_registers(int(register),
                                                             1,
                                                             unit=int(slave))
                else:
                    res = self.master.read_input_registers(int(register),
                                                           1,
                                                           unit=int(slave))
                if res is None or res.isError():  # requires pymodbus v1.5.1
                    if retry > 0:
                        retry = retry - 1
                        #time.sleep(0.020)
                    else:
                        raise Exception("Exception response")
                else:
                    break
            decoder = getBinaryPayloadDecoderFromRegisters(
                res.registers, self.byteorderLittle, self.wordorderLittle)
            r = decoder.decode_16bit_uint()
            if self.commError:  # we clear the previous error and send a message
                self.commError = False
                self.adderror(
                    QApplication.translate("Error Message",
                                           "Modbus Communication Resumed",
                                           None))
            time.sleep(
                0.020
            )  # we add a small sleep between requests to help out the slow Loring electronic
            return convert_from_bcd(r)
        except Exception:  # as ex:
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            self.disconnect()
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readBCD() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            settings = str(self.comport) + "," + str(
                self.baudrate) + "," + str(self.bytesize) + "," + str(
                    self.parity) + "," + str(self.stopbits) + "," + str(
                        self.timeout)
            ser_str = "MODBUS readBCD :" + settings + " || Slave = " + str(
                slave) + " || Register = " + str(
                    register) + " || Code = " + str(code)
            if r is not None:
                ser_str = ser_str + " || Rx = " + str(r)
            self.addserial(ser_str)

    # as readSingleRegister, but does not retry nor raise and error and returns a None instead
    # also does not reserve the port via a semaphore!
    def peekSingleRegister(self, slave, register, code=3):
        try:
            if code == 1:
                res = self.master.read_coils(int(register), 1, unit=int(slave))
            elif code == 2:
                res = self.master.read_discrete_inputs(int(register),
                                                       1,
                                                       unit=int(slave))
            elif code == 4:
                res = self.master.read_input_registers(int(register),
                                                       1,
                                                       unit=int(slave))
            else:  # code==3
                res = self.master.read_holding_registers(int(register),
                                                         1,
                                                         unit=int(slave))
        except Exception:
            res = None
        if res is not None and not res.isError():  # requires pymodbus v1.5.1
            if code in [1, 2]:
                if res is not None and res.bits[0]:
                    return 1
                else:
                    return 0
            else:
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res.registers, self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_16bit_uint()
                return r
        else:
            return None

    # function 1 (Read Coil)
    # function 2 (Read Discrete Input)
    # function 3 (Read Multiple Holding Registers) and
    # function 4 (Read Input Registers)
    def readSingleRegister(self, slave, register, code=3):
        #        import logging
        #        logging.basicConfig()
        #        log = logging.getLogger()
        #        log.setLevel(logging.DEBUG)
        r = None
        try:
            #### lock shared resources #####
            self.COMsemaphore.acquire(1)
            self.connect()
            retry = self.readRetries
            while True:
                try:
                    if code == 1:
                        res = self.master.read_coils(int(register),
                                                     1,
                                                     unit=int(slave))
                    elif code == 2:
                        res = self.master.read_discrete_inputs(int(register),
                                                               1,
                                                               unit=int(slave))
                    elif code == 4:
                        res = self.master.read_input_registers(int(register),
                                                               1,
                                                               unit=int(slave))
                    else:  # code==3
                        res = self.master.read_holding_registers(
                            int(register), 1, unit=int(slave))
                except Exception:
                    res = None
                if res is None or res.isError():  # requires pymodbus v1.5.1
                    if retry > 0:
                        retry = retry - 1
                        time.sleep(0.020)
                    else:
                        raise Exception("Exception response")
                else:
                    break
            if code in [1, 2]:
                if res is not None and res.bits[0]:
                    r = 1
                else:
                    r = 0
                return r
            else:
                decoder = getBinaryPayloadDecoderFromRegisters(
                    res.registers, self.byteorderLittle, self.wordorderLittle)
                r = decoder.decode_16bit_uint()
                if self.commError:  # we clear the previous error and send a message
                    self.commError = False
                    self.adderror(
                        QApplication.translate("Error Message",
                                               "Modbus Communication Resumed",
                                               None))
                return r
        except Exception:  # as ex:
            #            self.disconnect()
            #            import traceback
            #            traceback.print_exc(file=sys.stdout)
            #            _, _, exc_tb = sys.exc_info()
            #            self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno)
            self.adderror(
                QApplication.translate("Error Message",
                                       "Modbus Communication Error", None))
            self.commError = True
        finally:
            if self.COMsemaphore.available() < 1:
                self.COMsemaphore.release(1)
            #note: logged chars should be unicode not binary
            settings = str(self.comport) + "," + str(
                self.baudrate) + "," + str(self.bytesize) + "," + str(
                    self.parity) + "," + str(self.stopbits) + "," + str(
                        self.timeout)
            ser_str = "MODBUS readSingleRegister :" + settings + " || Slave = " + str(
                slave) + " || Register = " + str(
                    register) + " || Code = " + str(code)
            if r is not None:
                ser_str = ser_str + " || Rx = " + str(r)
            self.addserial(ser_str)

    def setTarget(self, sv):
        if self.PID_slave_ID:
            multiplier = 1.
            if self.SVmultiplier == 1:
                multiplier = 10.
            elif self.SVmultiplier == 2:
                multiplier = 100.
            self.writeSingleRegister(self.PID_slave_ID, self.PID_SV_register,
                                     int(round(sv * multiplier)))

    def setPID(self, p, i, d):
        if self.PID_slave_ID and not (self.PID_p_register ==
                                      self.PID_i_register ==
                                      self.PID_d_register == 0):
            multiplier = 1.
            if self.PIDmultiplier == 1:
                multiplier = 10.
            elif self.PIDmultiplier == 2:
                multiplier = 100.
            self.writeSingleRegister(self.PID_slave_ID, self.PID_p_register,
                                     p * multiplier)
            self.writeSingleRegister(self.PID_slave_ID, self.PID_i_register,
                                     i * multiplier)
            self.writeSingleRegister(self.PID_slave_ID, self.PID_d_register,
                                     d * multiplier)