国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Python modbus_tk 庫源碼分析

這篇具有很好參考價值的文章主要介紹了Python modbus_tk 庫源碼分析。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

Python modbus_tk 庫源碼分析

前言

modbus_tcp 協(xié)議是工業(yè)項目中常見的一種基于 TCP/IP 協(xié)議的設備數(shù)據(jù)交互協(xié)議。

作為 TCP/IP 協(xié)議的上層協(xié)議,modbus_tcp 協(xié)議涉及到兩個概念:client 和 server。但更標準的叫法應該是 master 和 slave。

  • Slave:TCP/IP 協(xié)議中的 server 方
  • Master:TCP/IP 協(xié)議中的 client 方

而 modbus_tk 庫作為 Python 中著名的 modbus 協(xié)議封裝模塊,其源碼值得深入研究。

特別是在對并發(fā)量等方面有一定要求的情況下,如果需要在 modbus_tk 模塊的基礎上進行更進一步的開發(fā),就更應該仔細研究其源代碼和實現(xiàn)邏輯。

因此,我寫下了這篇文章,希望對你有所幫助。

實例化 TcpMaster 對象

導入 TcpMaster 類:

from modbus_tk.modbus_tcp import TcpMaster

TcpMaster 繼承于 Master,在其實例化的時候什么也沒做。

class TcpMaster(Master):
    def __init__(self, host="127.0.0.1", port=502, timeout_in_sec=5.0):
        super(TcpMaster, self).__init__(timeout_in_sec)
        self._host = host
        self._port = port
        self._sock = None

Master 的 __init__() 方法中也沒有做什么:

class Master(object):
    def __init__(self, timeout_in_sec, hooks=None):
        self._timeout = timeout_in_sec
        self._verbose = False
        self._is_opened = False   # 記住 _is_opened 現(xiàn)在為 False

建立 socket 鏈接

TcpMaster 的父類 Master 提供了 execute 方法,該方法提供以下參數(shù):

self,
slave,
function_code,
starting_address,
quantity_of_x=0,
output_value=0,
data_format="",
expected_length=-1,
write_starting_address_fc23=0,
number_file=None,
pdu="",
returns_raw=False

此方法基本上算該模塊的核心,無論是讀寫線圈、還是讀寫寄存器等都是調(diào)用該方法。

接下來其代碼體的具體實現(xiàn),我們將開始進行逐行分析:

is_read_function = False
nb_of_digits = 0
if number_file is None:
    number_file = tuple()

self.open()

is_read_function 這里賦值為 False、代表后續(xù)在 Master.execute() 方法真正執(zhí)行前,作者會先認為使用者調(diào)用的是 write 方法而非 read 方法。

接下來代碼中又調(diào)用了 self.open() 方法。 由于實例化 TcpMaster 類時什么也沒做, 所以 TCP 鏈接在此時是還沒有建立的,而 self.open() 方法就是創(chuàng)建一個 TCP 的 client 端。

def open(self):
    if not self._is_opened:  # 在初始化方法中,它默認是 False
        self._do_open()
        self._is_opened = True

這里執(zhí)行的 self._do_open() 方法由 TcpMaster 實現(xiàn):

def _do_open(self):
    if self._sock:  # 如果 self._sock 不是 None、就將 socket 對象關閉
        self._sock.close()

    # 創(chuàng)建一個 socket 對象,AF_INET 為 IPV4 地址家族
    # SOCK_STREAM 即為基于流的協(xié)議,也就是 TCP 協(xié)議
    self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 設置超時時間,即實例化 TcpMaster 傳入的值,默認參數(shù)為 5
    self.set_timeout(self.get_timeout())

    # 允許重用地址(解決端口占用問題)
    self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    call_hooks("modbus_tcp.TcpMaster.before_connect", (self, ))
    # 進行鏈接
    self._sock.connect((self._host, self._port))
    call_hooks("modbus_tcp.TcpMaster.after_connect", (self, ))

這里的 self.set_timeout 由 TcpMaster 實現(xiàn):

def set_timeout(self, timeout_in_sec):
    super(TcpMaster, self).set_timeout(timeout_in_sec)
    if self._sock:
        # 注意! 這里如果 timeout_in_sec 等于 0
        # 那么該 sock 對象就是一個鏈接時非阻塞的
        # 可用于 I/O 多路復用
        self._sock.setblocking(timeout_in_sec > 0)

        # 如果 timeout_in_sec 為 0,則設置為阻塞的 socket 對象
        # timeout 不應該傳遞負數(shù)
        if timeout_in_sec:
            self._sock.settimeout(timeout_in_sec)

看到這里,我們其實不難猜出 modbus_tk 模塊中 TcpMaster 的 Master.execute() 方法其實是能支持 self._sock 異常后的無感重聯(lián)的。

只需要在 slave 方失聯(lián)后重新調(diào)用一次 TcpMaster._do_open() 方法即可,即可實現(xiàn)無感知的重新鏈接。

寫入多個寄存器

接下來 Master.execute() 方法基本是對 TCP 協(xié)議的解包、組包代碼,我將具體的組包等過程代碼都先給注釋掉了:

@threadsafe_function
def execute(
    self, slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="",
    expected_length=-1, write_starting_address_fc23=0, number_file=None, pdu="", returns_raw=False
):

    is_read_function = False
    nb_of_digits = 0
    if number_file is None:
        number_file = tuple()

    self.open()

    if function_code == defines.READ_COILS or function_code == defines.READ_DISCRETE_INPUTS:
        pass
    elif function_code == defines.READ_INPUT_REGISTERS or function_code == defines.READ_HOLDING_REGISTERS:
        pass

    elif function_code == defines.READ_FILE_RECORD:
        pass

    elif (function_code == defines.WRITE_SINGLE_COIL) or (function_code == defines.WRITE_SINGLE_REGISTER):
        pass

    elif function_code == defines.WRITE_MULTIPLE_COILS:
        pass

    elif function_code == defines.WRITE_MULTIPLE_REGISTERS:
        pass

    elif function_code == defines.READ_EXCEPTION_STATUS:
        pass

    elif function_code == defines.DIAGNOSTIC:
        pass

    elif function_code == defines.READ_WRITE_MULTIPLE_REGISTERS:
        pass

    elif function_code == defines.RAW:
        pass

    elif function_code == defines.DEVICE_INFO:
        pass

    else:
        raise ModbusFunctionNotSupportedError("The {0} function code is not supported. ".format(function_code))

    query = self._make_query()

    request = query.build_request(pdu, slave)

    retval = call_hooks("modbus.Master.before_send", (self, request))
    if retval is not None:
        request = retval
    if self._verbose:
        LOGGER.debug(get_log_buffer("-> ", request))
    self._send(request)

    call_hooks("modbus.Master.after_send", (self, ))

    if slave != 0:
        pass

為了能夠繼續(xù)向下分析,我們這里先以寫入多個寄存器的邏輯入手接著向下看:

READ_WRITE_MULTIPLE_REGISTERS

其代碼為:

elif function_code == defines.WRITE_MULTIPLE_REGISTERS:
     # 輸出值和 format,如果指定了 format 和輸出值,將運行下面的邏輯
    if output_value and data_format:
        byte_count =  struct.calcsize(data_format)

    # 否則先計算整個 byte 的長度
    else:
        byte_count = 2 * len(output_value)

    # 使用 struct 對數(shù)據(jù)進行轉(zhuǎn)換, 采用大端排列
    pdu = struct.pack(">BHHB", function_code, starting_address, byte_count // 2, byte_count)

     # 輸出值和 format,如果指定了 format 和輸出值,將運行下面的邏輯
    if output_value and data_format:
        pdu += struct.pack(data_format, *output_value)

    # 一般我們不會指定 data_format,所以直接往下看
    else:
        for j in output_value:
            # 若 j 大于 0 fmt 就是 H 否則是 h
            fmt = "H" if j >= 0 else "h"
            # 繼續(xù)向 pdu 里加數(shù)據(jù)
            pdu += struct.pack(">" + fmt, j)
    data_format = ">HH"
    if expected_length < 0:
        expected_length = 8

構建數(shù)據(jù)包

無論是讀取、寫入線圈或者寄存器,每一個請求都會包含一個 pdu 數(shù)據(jù)單元。

在 Master.execute() 方法下面,每一種讀寫操作都會運行 TcpMaster._make_query() 方法:

query = self._make_query()
request = query.build_request(pdu, slave)

下面是 TcpMaster._make_query 的代碼:

def _make_query(self):
    return TcpQuery()

TcpQuery 屬于 Query 的派生類,但 Query 實際上是一個 interface 類,故沒有實際代碼:

class TcpQuery(Query):

    _last_transaction_id = 0  # 記住這個類屬性

    def __init__(self):
        super(TcpQuery, self).__init__()
        self._request_mbap = TcpMbap()
        self._response_mbap = TcpMbap()

TcpMbap 類的實例化過程也非常簡單,TcpQuery 中實際上就是封裝了一個 request 和 response 而已:

class TcpMbap(object):
    def __init__(self):
        self.transaction_id = 0
        self.protocol_id = 0
        self.length = 0
        self.unit_id = 0

TcpQuery.build_request() 的實現(xiàn):

def build_request(self, pdu, slave):
    if (slave < 0) or (slave > 255):
        raise InvalidArgumentError("{0} Invalid value for slave id".format(slave))

    self._request_mbap.length = len(pdu) + 1  #  pdu 數(shù)據(jù)單元的長度 + 1
    self._request_mbap.transaction_id = self._get_transaction_id() # 獲取一個事務 id
    self._request_mbap.unit_id = slave  # 站號
    mbap = self._request_mbap.pack()    # 打包
    # mbap 和 pdu 數(shù)據(jù)單元拼接并返回
    # mbap 可以認為是 head 而 pdu 則是 body
    return mbap + pdu

TcpQuery._get_transaction_id() 會在每次收發(fā)包時,都讓事務號自增 1,當事務號增加到 65535 后,置 0:

@threadsafe_function
def _get_transaction_id(self):
    if TcpQuery._last_transaction_id < 0xffff:  # 65535
        TcpQuery._last_transaction_id += 1
    else:
        TcpQuery._last_transaction_id = 0
    return TcpQuery._last_transaction_id

TcpMbap.pack() 方法會將所有 TcpMbap.__init__() 中的實例屬性通過 struct 進行封包:

def pack(self):
    # transaction_id 事務號
    # protocol_id 0
    # length pdu 數(shù)據(jù)單元的長度 + 1
    # unit_id 設備站號 slave
    return struct.pack(">HHHB", self.transaction_id, self.protocol_id, self.length, self.unit_id)

至此,request 請求已經(jīng)構建完畢了。

發(fā)送請求

讓我們接著回到 Master.execute() 方法中:

# call_hooks 實際上是運行鉤子函數(shù),在后面會有詳細介紹
retval = call_hooks("modbus.Master.before_send", (self, request))
if retval is not None:
    request = retval

# 是否需要打印更多的日志?這個可以通過 Master.set_verbose() 方法進行設置
# 其默認值為 False
if self._verbose:
    LOGGER.debug(get_log_buffer("-> ", request))

# 發(fā)送請求
self._send(request)

call_hooks("modbus.Master.after_send", (self, ))

在 TcpMaster._send() 方法中:

def _send(self, request):
    retval = call_hooks("modbus_tcp.TcpMaster.before_send", (self, request))
    if retval is not None:
        request = retval
    try:
        # 刷新 socket 確保鏈接可用
        flush_socket(self._sock, 3)
    except Exception as msg:
        LOGGER.error('Error while flushing the socket: {0}'.format(msg))
        # 異常后、將再次運行 TcpMaster._do_open() 嘗試重聯(lián)
        self._do_open()

    # 若 flush_socket() 函數(shù)運行沒有拋出異常,則代表鏈接是可用的。
    # 這時候才會發(fā)送數(shù)據(jù)
    self._sock.send(request)

flush_socket() 函數(shù)非常有趣,它通過 select 模塊來不斷的輪詢監(jiān)聽 sock 對象的可讀狀態(tài),當可讀時會自動讀取每一次的 1024 個字節(jié)數(shù)據(jù)并將他們拋棄,這里是為了保持發(fā)送數(shù)據(jù)前的連接狀態(tài)檢測沒有異常而做的一步操作:

def flush_socket(socks, lim=0):
    # lim 傳入的是 3, 代表最多讀 3 次
    input_socks = [socks]  # 做成一個監(jiān)聽列表
    cnt = 0 # 當前讀取到的次數(shù)
    while True:
        # 放入 可讀事件列表、可寫事件列表、錯誤事件列表 及監(jiān)聽對象
        # 它會返回一個列表:
        #  [[r_fd, r_fd], [w_fd, w_fd], [e_fd, e_fd]]
        # 而 [0] 則是指只拿到可讀的文件描述符列表
        # 循環(huán)事件時間設置的是 0.0 這代表它將一直阻塞在這里,直到 fd 事件被觸發(fā)
        # 若不為 0,則等待 n 秒,進行下一次的循環(huán)
        i_socks = select.select(input_socks, input_socks, input_socks, 0.0)[0]

        # 沒有可讀的文件描述符,則跳出 while 循環(huán)
        if len(i_socks) == 0:
            break

        # 若拿到了,就循環(huán)得到 socks 進行 recv
        # 其實這里應該也可以寫成 i_socks[0].recv(1024)
        # 因為可讀事件文件描述符
        for sock in i_socks:
            sock.recv(1024)

        # 超出了最大讀取限制, 這里應該代表的是連接斷開了
        if lim > 0:
            cnt += 1
            if cnt >= lim:
                raise Exception("flush_socket: maximum number of iterations reached")

至此、我們一次完整的組包及發(fā)送數(shù)據(jù)的源碼分析就走完了。

解析響應

我們接著來看 Master.execute() 方法中關于解析響應信息的代碼:

if slave != 0:
    response = self._recv(expected_length)
    pass

首先,如果站號不等于 0 就會執(zhí)行 TcpMaster._recv() 方法:

def _recv(self, expected_length=-1):
    # to_data 函數(shù)會根據(jù) Python 版本來返回不同的內(nèi)容
    # 若是 Python2 則直接返回 string ''
    # 若是 Python3 則會返回一個 bytearray('', 'ascii')
    response = to_data('')
    length = 255

    # 如果 response 小于 255, 則不斷的讀取
    while len(response) < length:
        rcv_byte = self._sock.recv(1)
        if rcv_byte:
            response += rcv_byte
            # 在第 6 個字節(jié)處、通過 struct.unpack() 進行拆包
            if len(response) == 6:
                to_be_recv_length = struct.unpack(">HHH", response)[2]
                length = to_be_recv_length + 6
        else:
            break
    retval = call_hooks("modbus_tcp.TcpMaster.after_recv", (self, response))
    if retval is not None:
        return retval
    return response

得到 response 后,Master.execute() 方法會開始解析響應信息:

retval = call_hooks("modbus.Master.after_recv", (self, response))
if retval is not None:
    response = retval
if self._verbose:
    LOGGER.debug(get_log_buffer("<- ", response))

response_pdu = query.parse_response(response)

TcpQuery.parse_response() 方法的代碼主要將 mbap 和 pdu 進行分離,并且通過 TcpMbap.unpack() 方法將 mbap 解包并通過 TcpMbap.check_response() 進行數(shù)據(jù)校驗:

def parse_response(self, response):
    if len(response) > 6:
        # 分別拿到 mbap 和 pdu
        mbap, pdu = response[:7], response[7:]
        # 解包
        self._response_mbap.unpack(mbap)
        # 校驗數(shù)據(jù),傳入請求的 mbap 以及 pdu 的長度
        self._response_mbap.check_response(self._request_mbap, len(pdu))
        # 返回 pdu
        return pdu
    else:
        raise ModbusInvalidResponseError("Response length is only {0} bytes. ".format(len(response)))

TcpMbap.unpack() 方法代碼如下,將 _response_mbap 的事務號協(xié)議 id 等信息進行更新:

def unpack(self, value):
    (self.transaction_id, self.protocol_id, self.length, self.unit_id) = struct.unpack(">HHHB", value)

TcpMbap.check_response() 方法代碼如下:

def check_response(self, request_mbap, response_pdu_length):
    error_str = self._check_ids(request_mbap)
    error_str += self.check_length(response_pdu_length)

    if len(error_str) > 0:
        raise ModbusInvalidMbapError(error_str)

TcpMbap._check_ids() 方法代碼如下:

    def _check_ids(self, request_mbap):
        # self 是響應體, request_mbap 是請求體
        # 對比他們的事務號等信息是否一致,若不一致則會在返回一個 error_str, 該 error_str 會在 TcpMbap.check_response()
        # 中被 raise
        error_str = ""

        if request_mbap.transaction_id != self.transaction_id:
            error_str += "Invalid transaction id: request={0} - response={1}. ".format(
                request_mbap.transaction_id, self.transaction_id)

        if request_mbap.protocol_id != self.protocol_id:
            error_str += "Invalid protocol id: request={0} - response={1}. ".format(
                request_mbap.protocol_id, self.protocol_id
            )

        if request_mbap.unit_id != self.unit_id:
            error_str += "Invalid unit id: request={0} - response={1}. ".format(request_mbap.unit_id, self.unit_id)

        return error_str

TcpMbap.check_length() 方法代碼如下:

def check_length(self, pdu_length):
    # 這里思考 pdu 長度為什么 + 1?
    # 因為 response 在 TcpMbap.unpack() 方法中,self.length 是 mbap + pdu 的長度
    # 所以這里 pdu_length 長度 + 1 實際上就是指整個 head + body 的長度
    following_bytes_length = pdu_length+1

    # 判斷長度是否相等、若不等可能造成的原因是數(shù)據(jù)拆包不正確 mbap 長了,或者 pdu 短了
    # 這種時候就直接返回一個字符串
    # TcpMbap.check_response() 中如果 error_str 的長度大于 0, 就會拋出異常了
    if self.length != following_bytes_length:
        return "Response length is {0} while receiving {1} bytes. ".format(self.length, following_bytes_length)
    return ""

至此,TcpQuery().parse_response() 方法就全部執(zhí)行完畢了。

Master.execute() 方法中就得到了數(shù)據(jù)單元 pdu。也就是整個數(shù)據(jù)體。

我們接著往下看 Master.execute() 方法,其實后面已經(jīng)沒有再深層次調(diào)用某些內(nèi)部代碼了,也沒有新的 I/O 操作了:


response_pdu = query.parse_response(response)

(return_code, byte_2) = struct.unpack(">BB", response_pdu[0:2])

# 如果返回的 code 大于 128,直接報錯
if return_code > 0x80:
    # the slave has returned an error
    exception_code = byte_2
    raise ModbusError(exception_code)
else:
    # 下面都是解析出一個 body 和一個 data_format
    # 分別是 讀操作、設備信息、寫操作
    # 他們所得到的 body 都不一樣
    if is_read_function:
        byte_count = byte_2
        data = response_pdu[2:]
        if byte_count != len(data):
            # the byte count in the pdu is invalid
            raise ModbusInvalidResponseError(
                "Byte count is {0} while actual number of bytes is {1}. ".format(byte_count, len(data))
            )
    elif function_code == defines.DEVICE_INFO:
        data = response_pdu[1:]
        data_format = ">" + (len(data) * "B")
    else:
        # returns what is returned by the slave after a writing function
        data = response_pdu[1:]

    # 默認為 False
    if returns_raw:
        return data

    # 解包,通過 讀、寫、設備信息所得到的 data_format 和 data
    # 對數(shù)據(jù)進行操作
    result = struct.unpack(data_format, data)

    # 只有 function_code 是 READ_COILS 時,nb_of_digits 才不為 0
    if nb_of_digits > 0:
        digits = []
        for byte_val in result:
            for i in range(8):
                if len(digits) >= nb_of_digits:
                    break
                digits.append(byte_val % 2)
                byte_val = byte_val >> 1
        result = tuple(digits)

    # 如果 function_code 是 READ_FILE_RECORD 讀取文件記錄,則也需要對 result 進行
    # 再次的修改
    if function_code == defines.READ_FILE_RECORD:
        sub_seq = list()
        ptr = 0
        while ptr < len(result):
            sub_seq += ((ptr + 2, ptr + 2 + result[ptr] // 2), )
            ptr += result[ptr] // 2 + 2
        result = tuple(map(lambda sub_seq_x: result[sub_seq_x[0]:sub_seq_x[1]], sub_seq))

    # 返回 result
    return result

threadsafe 裝飾器

threadsafe 是一個裝飾器函數(shù),在 Master.execute() 方法頭上和 TcpQuery._get_transaction_id() 方法頭上都加了這個裝飾器。

見名知意,該裝飾器的主要目的就是為了保障線程安全(有的設備可能不支持同時對其進行讀寫操作),但是該裝飾器也可能會帶來另一些問題。

我們先看它的源碼:

def threadsafe_function(fcn):
    # 實例化出了一把遞歸鎖
    lock = threading.RLock()

    def new(*args, **kwargs):
        # 當 Master.execute() 和 TcpQuery._get_transaction_id() 方法沒有通過
        # 關鍵字傳參傳入 threadsafe=False 時,將默認開啟線程安全模式來執(zhí)行
        # 這 2 個方法
        threadsafe = kwargs.pop('threadsafe', True)
        if threadsafe:
            lock.acquire()
        try:
            ret = fcn(*args, **kwargs)
        except Exception as excpt:
            raise excpt
        finally:
            if threadsafe:
                lock.release()
        return ret
    return new

這個 threading lock 會導致什么問題呢?當 Python 解釋器運行到 Master.execute() 方法頭上時,就會自動執(zhí)行該裝飾器。

而 lock 變量也就生成了,最后會返回內(nèi)部閉函數(shù) new()。

可以理解為這個 lock 已經(jīng)被當成了一個全局變量,后續(xù)無論是創(chuàng)建多少個 TcpMaster 的實例對象,lock 變量所指向的鎖都是同一個。

通過源碼分析我們得知,Master.execute() 方法中會去建立 socket 鏈接,一旦有 1 個 device 鏈接時間過長,也將會導致其他的 device 通信或鏈接阻塞。

因為它們都是用的同一個 lock 鎖。所以,一般來說在使用時我們會在 Master.execute() 方法中顯式的傳遞 threadsafe=False 的關鍵字參數(shù),自己實現(xiàn) lock 來解決同一 device 不能同時讀寫的問題。

一些 hooks 鉤子函數(shù)

在上面分析源碼時,我們會看到很多 call_hooks 的運行,他們其實是 modbus_tk 模塊所提供的鉤子函數(shù)。只要實現(xiàn)相應的鉤子函數(shù)就會在整個 modbus_tcp 的數(shù)據(jù)傳遞生命周期中自動運行。

以下是常見的鉤子函數(shù):文章來源地址http://www.zghlxwxcb.cn/news/detail-479008.html

def install_hook(name, fct):
    """
    Install one of the following hook

    modbus_rtu.RtuMaster.before_open((master,))
    modbus_rtu.RtuMaster.after_close((master,)
    modbus_rtu.RtuMaster.before_send((master, request)) returns modified request or None
    modbus_rtu.RtuMaster.after_recv((master, response)) returns modified response or None

    modbus_rtu.RtuServer.before_close((server, ))
    modbus_rtu.RtuServer.after_close((server, ))
    modbus_rtu.RtuServer.before_open((server, ))
    modbus_rtu.RtuServer.after_open(((server, ))
    modbus_rtu.RtuServer.after_read((server, request)) returns modified request or None
    modbus_rtu.RtuServer.before_write((server, response))  returns modified response or None
    modbus_rtu.RtuServer.after_write((server, response))
    modbus_rtu.RtuServer.on_error((server, excpt))

    modbus_tcp.TcpMaster.before_connect((master, ))
    modbus_tcp.TcpMaster.after_connect((master, ))
    modbus_tcp.TcpMaster.before_close((master, ))
    modbus_tcp.TcpMaster.after_close((master, ))
    modbus_tcp.TcpMaster.before_send((master, request))
    modbus_tcp.TcpServer.after_send((master, request))
    modbus_tcp.TcpMaster.after_recv((master, response))


    modbus_tcp.TcpServer.on_connect((server, client, address))
    modbus_tcp.TcpServer.on_disconnect((server, sock))
    modbus_tcp.TcpServer.after_recv((server, sock, request)) returns modified request or None
    modbus_tcp.TcpServer.before_send((server, sock, response)) returns modified response or None
    modbus_tcp.TcpServer.on_error((server, sock, excpt))

    modbus_rtu_over_tcp.RtuOverTcpMaster.after_recv((master, response))

    modbus.Master.before_send((master, request)) returns modified request or None
    modbus.Master.after_send((master))
    modbus.Master.after_recv((master, response)) returns modified response or None

    modbus.Slave.handle_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_write_multiple_coils_request((slave, request_pdu))
    modbus.Slave.handle_write_multiple_registers_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_write_single_register_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_write_single_coil_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_input_registers_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_holding_registers_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_discrete_inputs_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_coils_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_write_multiple_registers_request((slave, request_pdu)) returns modified response or None
    modbus.Slave.handle_read_exception_status_request((slave, request_pdu)) returns modified response or None

    modbus.Slave.on_handle_broadcast((slave, response_pdu)) returns modified response or None
    modbus.Slave.on_exception((slave, function_code, excpt))


    modbus.Databank.on_error((db, excpt, request_pdu))

    modbus.ModbusBlock.setitem((self, slice, value))

    modbus.Server.before_handle_request((server, request)) returns modified request or None
    modbus.Server.after_handle_request((server, response)) returns modified response or None
    modbus.Server.on_exception((server, excpt))
    """
    with _LOCK:
        try:
            _HOOKS[name].append(fct)
        except KeyError:
            _HOOKS[name] = [fct]

到了這里,關于Python modbus_tk 庫源碼分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 工控CTF之協(xié)議分析1——Modbus

    工控CTF之協(xié)議分析1——Modbus

    流量分析 主要以工控流量和惡意流量為主,難度較低的題目主要考察Wireshark使用和找規(guī)律,難度較高的題目主要考察協(xié)議定義和特征 簡單只能簡單得干篇一律,難可以難得五花八門 常見的工控協(xié)議有:Modbus、MMS、IEC60870、MQTT、CoAP、COTP、IEC104、IEC61850、S7comm、OMRON等 由于工控

    2024年02月02日
    瀏覽(18)
  • MODBUS TCP協(xié)議實例數(shù)據(jù)幀詳細分析

    MODBUS TCP協(xié)議實例數(shù)據(jù)幀詳細分析

    Modbus由MODICON公司于1979年開發(fā),是一種工業(yè)現(xiàn)場總線協(xié)議標準。1996年施耐德公司推出基于以太網(wǎng)TCP/IP的Modbus協(xié)議:ModbusTCP。Modbus協(xié)議是一項應用層報文傳輸協(xié)議,包括ASCII、RTU、TCP三種報文類型。標準的Modbus協(xié)議物理層接口有RS232、RS422、RS485和以太網(wǎng)接口,采用master/slave方式

    2024年02月05日
    瀏覽(39)
  • 工程師必備串口數(shù)據(jù)截取工具modbus命令分析串口數(shù)據(jù)分析

    工程師必備串口數(shù)據(jù)截取工具modbus命令分析串口數(shù)據(jù)分析。 主要功能: ·支持監(jiān)控COM端口類型:標準電腦端口,內(nèi)核虛擬COM端口,USB轉(zhuǎn)串口等; ·可以實時監(jiān)控并采集串口數(shù)據(jù); ·可以同時監(jiān)控多個串行端口; ·監(jiān)控已經(jīng)被其他應用程序打開的串口(需要服務啟動); ·支持

    2024年02月13日
    瀏覽(29)
  • 最新版Modbus RTU 51單片機從機(源碼)

    最新版Modbus RTU 51單片機從機(源碼)

    最新版Modbus?RTU?51單片機從機源碼,在原源碼基礎上添加了芯片型號選擇,適應的芯片型號多了STC12系列,STC15系列,STC8系列。 支持01,02,03,04,05,06,15,16等常用功能碼。 免去小白移植煩惱。 包括以前的版本加更新版本,贈送4個組態(tài)觸摸屏測試工程文件,下載地址: 51單片機modbu

    2024年02月13日
    瀏覽(25)
  • Modbus PLC攻擊分析:從Modbus Poll Slave到M340_intouch modbustcp 讀取 m340

    Modbus PLC攻擊分析:從Modbus Poll Slave到M340_intouch modbustcp 讀取 m340

    先自我介紹一下,小編浙江大學畢業(yè),去過華為、字節(jié)跳動等大廠,目前阿里P7 深知大多數(shù)程序員,想要提升技能,往往是自己摸索成長,但自己不成體系的自學效果低效又漫長,而且極易碰到天花板技術停滯不前! 因此收集整理了一份《2024年最新網(wǎng)絡安全全套學習資料》

    2024年04月28日
    瀏覽(25)
  • 基于python輿情分析可視化系統(tǒng)+情感分析+爬蟲+機器學習(源碼)?

    基于python輿情分析可視化系統(tǒng)+情感分析+爬蟲+機器學習(源碼)?

    大數(shù)據(jù)畢業(yè)設計:Python招聘數(shù)據(jù)采集分析可視化系統(tǒng)? 畢業(yè)設計:2023-2024年計算機專業(yè)畢業(yè)設計選題匯總(建議收藏) 畢業(yè)設計:2023-2024年最新最全計算機專業(yè)畢設選題推薦匯總 ?? 感興趣的可以先收藏起來,點贊、關注不迷路,大家在畢設選題,項目以及論文編寫等相關

    2024年01月20日
    瀏覽(31)
  • python高校輿情分析系統(tǒng)+可視化+情感分析 輿情分析+Flask框架(源碼+文檔)?

    python高校輿情分析系統(tǒng)+可視化+情感分析 輿情分析+Flask框架(源碼+文檔)?

    畢業(yè)設計:2023-2024年計算機專業(yè)畢業(yè)設計選題匯總(建議收藏) 畢業(yè)設計:2023-2024年最新最全計算機專業(yè)畢設選題推薦匯總 ?? 感興趣的可以先收藏起來,點贊、關注不迷路,大家在畢設選題,項目以及論文編寫等相關問題都可以給我留言咨詢,希望幫助同學們順利畢業(yè)?。

    2024年02月01日
    瀏覽(21)
  • python畢設 大數(shù)據(jù)用戶畫像分析系統(tǒng)(源碼分享)

    python畢設 大數(shù)據(jù)用戶畫像分析系統(tǒng)(源碼分享)

    Hi,大家好,這里是丹成學長,今天做一個電商銷售預測分析,這只是一個demo,嘗試對電影數(shù)據(jù)進行分析,并可視化系統(tǒng) ?? 這兩年開始畢業(yè)設計和畢業(yè)答辯的要求和難度不斷提升,傳統(tǒng)的畢設題目缺少創(chuàng)新和亮點,往往達不到畢業(yè)答辯的要求,這兩年不斷有學弟學妹告訴學

    2024年01月17日
    瀏覽(22)
  • 【python】python旅游網(wǎng)數(shù)據(jù)抓取分析(源碼+論文)【獨一無二】

    【python】python旅游網(wǎng)數(shù)據(jù)抓取分析(源碼+論文)【獨一無二】

    ??博__主??:米碼收割機 ??技__能??:C++/Python語言 ??公眾號??:測試開發(fā)自動化【獲取源碼+商業(yè)合作】 ??榮__譽??:阿里云博客專家博主、51CTO技術博主 ??專__注??:專注主流機器人、人工智能等相關領域的開發(fā)、測試技術。 每個爬蟲針對特定的目標網(wǎng)站(去哪兒網(wǎng)

    2024年02月04日
    瀏覽(20)
  • tk-mybatis使用介紹,springboot整合tk-mybatis、PageHelper實現(xiàn)分頁查詢

    tk-mybatis使用介紹,springboot整合tk-mybatis、PageHelper實現(xiàn)分頁查詢

    Mybatis-Plus極大簡化了我們的開發(fā),作為mybatis的增強版,Mybatis-Plus確實幫我們減少了很多SQL語句的編寫,通過其提供的API,可以方便地完成增刪查改操作。但是,其實除了Mybatis-Plus以外,還有一個技術tk-mybatis可以達到同樣的效果,只不過隨著MP的興起,tk-mybatis已經(jīng)被漸漸淡忘

    2024年02月12日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包