字节内的位序有2种排列选左低右高:micropython-modbus才能适配modbus poll、kepware、modscan

MicroPython Modbus library 学习笔记

MicroPython Modbus library — micropython-modbus 2.3.7 documentationhttps://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#https://micropython-modbus.readthedocs.io/en/latest/readme_link.html#

单片机:MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32

加载:MicroPython Modbus library 2.3.7 实现Slaver client = ModbusTCP()

问题1:读写线圈时,字节内位序不对。

看了不少资料学习LSB MSB 大端小端、python之struct,无奈基础差搞不定,最后试错摸出来了,试错就是增加print输出方便观察结果,其实原文早已说明了:

            # see https://github.com/brainelectronics/micropython-modbus/issues/22
            # output = sum(v << i for i, v in enumerate(byte))
            # see https://github.com/brainelectronics/micropython-modbus/issues/38

解决办法:

functions.py第313行 左高右低

output = (output << 1) | bit

替换为 左低又高

output = sum(v << i for i, v in enumerate(byte))

这是源库算法当然没得说,百度AI的结果通常有较大参考价值:

python byte转bool数组
在Python中,你可以将字节(byte)转换为布尔数组(bool array),这通常涉及到将每个字节的每个位(bit)单独处理。一个字节有8位,你可以选择将每个位转换成布尔值,或者将整个字节转换成布尔值(例如,非零字节为True,零字节为False)。【将每个位转换为布尔值】如果你希望将每个位(bit)转换为布尔值,可以使用位运算。例如,对于字节中的每个位,你可以检查该位是否为1(即,是否为True)。

def byte_to_bool_array(byte):
    bool_array = [(byte >> i) & 1 for i in range(7, -1, -1)]
    return bool_array
# 示例
byte = 0b10101010  # 二进制表示
bool_array = byte_to_bool_array(byte)
print(bool_array)  # 输出: [1, 0, 1, 0, 1, 0, 1, 0]

下面这段是从functions.py摘抄出来试错的,顺便知道了format这么强。 

# typing not natively supported on MicroPython
from typing import List, Optional, Union


def bytes_to_bool(byte_list: bytes, bit_qty: Optional[int] = 1) -> List[bool]:
    """
    Convert bytes to list of boolean values
    :param      byte_list:  The byte list
    :type       byte_list:  bytes
    :param      bit_qty:    Amount of bits received
    :type       bit_qty:    Optional[int]
    :returns:   Boolean representation
    :rtype:     List[bool]
    """
    bool_list = []

    for index, byte in enumerate(byte_list):
        this_qty = bit_qty

        if this_qty >= 8:
            this_qty = 8

        """
        'b' 二进制
        >>> print('{0:b}'.format(3))
        11
        >>> print('{0:b}'.format(257))
        100000001
        >>> print('{0:16b}'.format(257))
               100000001 右对齐,左边填充7个空
        # {:0" + str(this_qty) + "b}
        """
        fmt = "{:0" + str(this_qty) + "b}"
        # 列表方法区别 extend(list,tuple),append(single value)
        fmtbin = fmt.format(byte)
        print(fmtbin)
        # bool_list.extend([bool(int(x)) for x in fmt.format(byte)])
        bool_list.extend([bool((byte >> i) & 1) for i in range(8)])

        bit_qty -= 8

    return bool_list


# range(start, stop[, step]),分别是起始、终止和步长
def byte_to_bool_array(byte):
    # bool_array = [bool((byte >> i) & 1) for i in range(7, -1, -1)] #先高位
    bool_array = [bool((byte >> i) & 1) for i in range(8)]  # 先低位
    return bool_array


print(bytes_to_bool(b"\x31"))  # [True] 右对齐
print(bytes_to_bool(b"\x31", 3))  # [False, False, True] 填充2个0
print(bytes_to_bool(b"\x31", 5))  # [False, False, False, False, True] 填充4个0
# 示例
byte = 0b10100010  # 二进制表示
bool_array = byte_to_bool_array(byte)
print(bool_array)  # 输出: [True, False, True, False, True, False, True, False]

问题2:库文件tcp.py的ModbusTCP(Modbus)方法bind()默认max_connections:int=10个TCP连接到Slaver,但是试验连接多个TCP有问题。还没到考虑modbus协议多个Master冲突一主多从的结构问题,现在有多个Master(例如modbus poll、kepware至少有2个Master)都想从同一个Slaver存取数据,怎么办啊?我再研究下,待续,也希望有人能指点迷津,十分感谢!

micropython modbus 2.3.7 库可以实现上位机python的pymodbus一从多主效果吗???见下图:

tcp.py文件class TCPServer(object):"""Modbus TCP host class"""的_accept_request()方法如下,当self._sock.accept()有新接入的new_client_sock,就会关闭旧的self._client_sock.close(),导致多个modbus masters后浪退前浪,而这些调试软件都是断线重连的,所以就是你争我抢的轮询modbus slaver,这样保证了modbus一主多从结构。要解决多个masters轮询一个slaver,并不是要改变“modbus一主多从结构”,而是在TCP层面给多个master排队查询,这样就可以,开始看到了uasyncio模块,后来发现了select模块,研究这2个模块中的一个估计就能实现目标。

    def _accept_request(self,
                        accept_timeout: float,
                        unit_addr_list: list) -> Union[Request, None]:
        """
        Accept, read and decode a socket based request

        :param      accept_timeout:  The socket accept timeout
        :type       accept_timeout:  float
        :param      unit_addr_list:  The unit address list
        :type       unit_addr_list:  list
        """
        self._sock.settimeout(accept_timeout)
        new_client_sock = None

        try: # 接收连接请求,返回收发数据的套接字对象和客户端地址
            new_client_sock, client_address = self._sock.accept() 
            print(new_client_sock, client_address)
        except OSError as e:
            if e.args[0] != 11:     # 11 = timeout expired
                raise e

        if new_client_sock is not None:
            if self._client_sock is not None:
                self._client_sock.close()
                print('除旧self._client_sock.close()')

            self._client_sock = new_client_sock
            print('迎新self._client_sock = new_client_sock')

            # recv() timeout, setting to 0 might lead to the following error
            # "Modbus request error: [Errno 11] EAGAIN"
            # This is a socket timeout error
            self._client_sock.settimeout(0.5)

        if self._client_sock is not None:
            try:
                req = self._client_sock.recv(128)

                if len(req) == 0:                  
                    return None

                req_header_no_uid = req[:Const.MBAP_HDR_LENGTH - 1]
                self._req_tid, req_pid, req_len = struct.unpack('>HHH', req_header_no_uid)
                req_uid_and_pdu = req[Const.MBAP_HDR_LENGTH - 1:Const.MBAP_HDR_LENGTH + req_len - 1]
            except OSError:
                # MicroPython raises an OSError instead of socket.timeout
                # print("Socket OSError aka TimeoutError: {}".format(e))
                return None
            except Exception:
                # print("Modbus request error:", e)
                self._client_sock.close()
                self._client_sock = None
                return None

            if (req_pid != 0):
                # print("Modbus request error: PID not 0")
                self._client_sock.close()
                self._client_sock = None
                return None

            if ((unit_addr_list is not None) and (req_uid_and_pdu[0] not in unit_addr_list)):
                return None

            try:
                return Request(self, req_uid_and_pdu)
            except ModbusException as e:
                self.send_exception_response(req[0],
                                             e.function_code,
                                             e.exception_code)
                return None

下面开始试验select模块使用方法,后面再去修改tcp.py文件适应micropython。

 实现I/O多路复用机制,允许程序同时监控多个文件描述符(如套接字和管道),以查看它们是否可以进行读、写或出现异常条件

""" 
Micropython 没有 queue 请用 deque
from collections import deque
"""

import select, socket, sys, queue

# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(socket.AF_INET, socket.SOCK_STREAM)  # 2 1
server.setblocking(False)

# Bind the socket to the port
server_address = ("192.168.1.105", 10001)  # localhost = 127.0.0.1
print(sys.stderr, "starting up on %s port %s" % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen(5)

# Sockets from which we expect to read
# 开始只有服务端,后面进来新连接再放入监控列表inputs,此列表里面是空就会报错:监控什么?
inputs = [server]
print("初始监控列表里面只有1个服务端 =", inputs)

# Sockets to which we expect to write
outputs = []

message_queues = {}
while inputs:

    # Wait for at least one of the sockets to be ready for processing
    print("\nwaiting for the next event")
    # select接收任务和返回结果依据这3个列表
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
    # Handle inputs
    for s in readable:
        # s是服务端,只负责接收新连接,接收数据是connection socket对象的事情
        if s is server:
            # A "readable" server socket is ready to accept a connection
            connection, client_address = s.accept()  # 有新连接请求
            print("new connection from", client_address)
            connection.setblocking(False)  # 必须是非阻塞的才能并发
            inputs.append(connection)  # 新连接交给select监控
            # Give the connection a queue for data we want to send
            # message_queues字典的Key=connection,Value=专用队列
            message_queues[connection] = queue.Queue()
        else:
            data = s.recv(1024)
            if data:
                # A readable client socket has data
                print(sys.stderr, 'received "%s" from %s' % (data, s.getpeername()))
                # 有数据来就必有已准备好的专用队列,查字典put数据,交给select了
                message_queues[s].put(data)
                # Add output channel for response
                # 给select交代工作了:我有输出请处理
                if s not in outputs:
                    outputs.append(s)
            else:
                # Interpret empty result as closed connection
                print("closing", client_address, "after reading no data")
                # Stop listening for input on the connection
                # 既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
                if s in outputs:
                    outputs.remove(s)
                inputs.remove(s)  # inputs中也删除掉
                s.close()  # 把这个连接关闭掉

                # Remove message queue
                del message_queues[s]
    # Handle outputs
    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except queue.Empty:
            # No messages waiting so stop checking for writability.
            print("output queue for", s.getpeername(), "is empty")
            outputs.remove(s)
        else:
            print('sending "%s" to %s' % (next_msg, s.getpeername()))
            s.send(next_msg)
    # Handle "exceptional conditions"
    # 异常列表清单是select监控input任务socke的结果
    for s in exceptional:
        print("handling exceptional condition for", s.getpeername())
        # Stop listening for input on the connection
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()

        # Remove message queue
        del message_queues[s]

micropython里面没有queue,可用deque:

from collections import deque
# 创建一个空队列
# queue = deque()
# TypeError: function missing 2 required positional arguments
# classcollections.deque(iterable, maxlen[, flags])
queue = deque([1, 2, 3], 3)
# 向队列中添加元素
queue.append(1)
queue.append(2)
queue.append(3)
queue.extend(['I','am','deque'])
# 查看队列中的元素
print("Queue:", queue)
# Queue: <deque>
# 从队列中移除并返回第一个元素
item = queue.popleft()
print("Removed item:", item)
# Removed item: 1
item = queue.popleft()
print("Removed item:", item)

print("Queue after removal:", queue)
# Queue after removal: <deque>

尝试将上位机TCPServer的IO多路复用移植到单片机的micropython:

 

原文链接:https://docs.singtown.com/micropython/zh/latest/openmvcam/library/collections.html#
collections – 集合和容器类型
该模块实现了高级的集合和容器类型,用于保存/累积各种对象。
classcollections.deque(iterable, maxlen[, flags])
Deques(双端队列)是一种类似于列表的容器,支持从deque的任一端进行附加和弹出操作。可以使用以下参数创建新的deque:
iterable 是在创建时用于填充 deque 的可迭代对象。它可以是一个空元组或列表,用于创建一个初始为空的 deque。
必须指定 maxlen,deque将被限制为最大长度。一旦deque已满,添加的任何新项都将从另一端丢弃项。
可选的 flags 可以为 1,以检查添加项时是否溢出。
Deque 对象支持 bool、len、迭代以及下标加载和存储。它们还具有以下方法:
append(x)
将 x 添加到 deque 的右侧。如果启用了溢出检查且队列没有更多空间,将引发 IndexError。
appendleft(x)
将 x 添加到 deque 的左侧。如果启用了溢出检查且队列没有更多空间,将引发 IndexError。
pop()
从 deque 的右侧移除并返回一个项。如果队列为空,将引发 IndexError。
popleft()
从 deque 的左侧移除并返回一个项。如果队列为空,将引发 IndexError。
extend(iterable)
通过将 iterable 中的所有项追加到 deque 的右侧来扩展 deque。如果启用了溢出检查且 deque 没有更多空间,将引发 IndexError。

collection.deque实现了一个简单的队列,支持指定最大长度。
deque声明对象函数时,并不支持关键词参数,iterable和maxlen不得使用关键词指定,否则会报错。

原文链接:https://blog.youkuaiyun.com/weixin_45653183/article/details/141784084
deque()、list()、heapq主要区别
使用场景
deque()双端队列:
当需要在队列的两端进行频繁的插入和删除操作时,deque是理想的选择。
高效访问两端元素:
deque提供了从两端快速访问元素的能力,这使得它在某些特定场景下(如循环队列)非常有用。
队列和栈的实现:
deque可以作为队列和栈的底层实现,因为它支持从两端添加和删除元素。            

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值