MicroPython Modbus library 学习笔记
单片机: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可以作为队列和栈的底层实现,因为它支持从两端添加和删除元素。