Pymodbus部署二三事

42 篇文章 1 订阅
39 篇文章 1 订阅

 

1.可用代码

只处理了Modbus RTU 保持寄存器

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '.')
common_path = os.path.join(project_path, 'common')
sub_path = os.path.join(project_path, 'sub')
sys.path.append(project_path)
sys.path.append(current_dir)
sys.path.append(sub_path)
sys.path.append(common_path)
import json
import serial
import threading
import struct
from pymodbus.server.sync import StartSerialServer
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer
from serial import Serial
from pymodbus.datastore import ModbusSequentialDataBlock

# 定义要维护的寄存器的初始值[0~255]
# 定义要维护的寄存器的初始值
hr_block = ModbusSequentialDataBlock(0, [0] * 0x10000)  # Holding Register
block = ModbusSlaveContext(hr=hr_block)
store = ModbusServerContext(slaves=block, single=True)

import ctypes
import threading
import ctypes.util

# 定义调用的 C 库函数
libc = ctypes.CDLL(ctypes.util.find_library('c'))
pthread_setschedparam = libc.pthread_setschedparam
pthread_setschedparam.argtypes = [ctypes.c_ulong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_void_p)]

def set_thread_priority(thread_id, priority):
    param = ctypes.c_void_p()
    param.value = priority
    pthread_setschedparam(thread_id, 0, ctypes.byref(param))

import ctypes
import threading

def terminate_thread(thread):
    if not thread.is_alive():
        return

    # 终止线程的函数定义
    def _async_raise(tid, exctype):
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)):
            return
        raise ValueError("invalid thread id")

    thread_id = thread.ident
    _async_raise(thread_id, SystemExit)

class GpModbus:
    def __init__(self):
        self.modbusIf = None
        self.serial_thread = None
        self.modbusIf = block
        self.modbusAddress = None, 
        self.serialPortDevPath = None, 
        self.serial_baud = None
    
    def startModbusSlave(self, modbusAddress, serialPortDevPath, serial_baud):
        if isinstance(modbusAddress, tuple):
           modbusAddress = modbusAddress[0]
        if isinstance(serialPortDevPath, tuple):
           serialPortDevPath = serialPortDevPath[0]
        print(f'start modbus slave: {modbusAddress}, {serialPortDevPath}, {serial_baud}');
        self.modbusAddress = modbusAddress, 
        self.serialPortDevPath = serialPortDevPath, 
        self.serial_baud = serial_baud
        
        # 创建线程并启动
        self.serial_thread = threading.Thread(target=self.startModbusSlave_ThenBlock, args=[modbusAddress, serialPortDevPath, serial_baud])
        self.serial_thread.start()
        # 获取线程 ID
        #thread_id = self.serial_thread.ident
        # 设置线程的优先级
        #set_thread_priority(thread_id, 90) # 优先级值可以根据具体需求调整

    #address is u16 serial, serialPortDevPath="" means using 虚拟串口
    #禁止使用,应当使用startModbusSlave
    def startModbusSlave_ThenBlock(self, modbusAddress, serialPortDevPath, serial_baud):
        # 配置虚拟串口
        try:
            serialPort = None
            #print(f"start modbus slave {serialPortDevPath} - {serial_baud}")
            if(serialPortDevPath is not None):
                # 启动 Modbus RTU 从机
                server = StartSerialServer(context=store, identity=None, framer=ModbusRtuFramer, 
                    port=serialPortDevPath, 
                    baudrate=serial_baud,
                    bytesize=8,  # ASCII 通常使用 7 位数据位
                    parity='N',  # ASCII 通常使用偶校验
                    stopbits=1,
                    timeout=0.5,
                    broadcast_enable=True) #可以向用户广播
                server.start()
        except Exception as e:
            print('gpModbus_startVirtualModbusSlave()', e)
        print(f"self.modbusIf = {block}")

        try:
            while True:
                pass
        except KeyboardInterrupt:
            print("程序已停止")
    
    # 模拟寄存器读 0,123
    def write_register(self, register_addr, register_value):
       self.write_hold_register(register_addr, register_value)

    # 模拟寄存器读 0,123
    def write_registers(self, register_addr, register_values):
       self.write_hold_register(register_addr, register_value) 

    def gpU16ToReversed(self, value):
        if isinstance(value, list):
            ret = [self.gpU16ToReversed(elem) for elem in value]
        else:
            ret = struct.unpack('>H', struct.pack('<H', value))[0]
        return ret


    # 模拟保持寄存器写[resiter_value可以为数组]
    def write_hold_register(self, register_addr, register_value):
        try:
            #regisgter_addr_reversed = self.gpU16ToReversed(register_addr)
            #register_value_reversed = self.gpU16ToReversed(register_value)
            if isinstance(register_value, list):
                print(f'addr:{register_addr}, {register_value}, cnt={len(register_value)}')
                hr_block.setValues(register_addr+1, register_value)  # 获取 Holding Registers 的值
            else:
                print(f'addr:{register_addr}, {register_value}, cnt=1')
                hr_block.setValues(register_addr+1, [register_value])  # 获取 Holding Registers 的值
        except Exception as e:
            print(f'write register error {e}');

    def read_hold_registers(self, register_addr, nCnt):
        try:
            print(f'read:{self.modbusIf} {register_addr},cnt={nCnt}')
            if(self.modbusIf is None):
                return [];
            return hr_block.getValues(register_addr+1, nCnt)  # 获取 Holding Registers 的值
        except Exception as e:
            print(f'read_register register error{e}');
        return []

2.说明

  1.  创建外围线程的目的只是因为使用的Pymodbus的串口服务是个阻塞函数。
  2. 在修改寄存器值时需要修改的对象是最原始的hr_block。它有两个关联参数:
    1. 启动pymodbus是需要添加:broadcast_enable = true,使得对原始缓冲区中寄存器的修改会传递给所有的用户。
    2. 创建ServerStore是,使用:Single=True,表示所有的客户端共享同一个缓冲区。
  3. pyModbus在2=>3的过程中语法大变,上面的版本基于pymodbus==2.5.3
  4. 上面包含了大小端转换,但是最终没有用上,pymodbus模块自行处理了这个问题。在串口协议传递时,它按摩托罗拉的大端模式传递,但是在Python平台直接读写寄存器时,仍然是默认的小端模式——总之,就是大小端的转换对用户是透明的。
  5. 上面包含了线程的优先级和终止线程的处理,但未用到。

2.1 pip包下载语法:

python3 -m pip install pymodbus==2.5.3  --timeout 200 -i http://mirrors.aliyun.com/pypi/simple/  --trusted-host mirrors.aliyun.com

3.Modbus调试用工具UART Assist

 

串口调试助手是在5.0.1之后加入对modbus的支持的。点击最右侧的黄色窄带,即可弹出快捷工具箱。下载位置:UartAssist串口调试助手-软件工具-野人家园

,很意外地发现NetAssist也是这个野人家园出的。。。。

Last updated: Jul13,2024 (I think this is the final version)

pymodbus是一个基于PythonModbus通信库,用于实现Modbus通信协议在计算机和设备之间的数据交换。Modbus是一种常用的通信协议,被广泛应用于工业自动化领域。 使用pymodbus库,我们可以很方便地实现Modbus主站(Master)和从站(Slave)的通信。下面是一个简单的pymodbus实例。 首先,我们需要安装pymodbus库。可以使用pip命令在命令行中输入以下命令进行安装: pip install pymodbus 然后,在Python环境中导入所需的模块和类: from pymodbus.client.sync import ModbusTcpClient from pymodbus.exceptions import ModbusException from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder from pymodbus.constants import Endian 接下来,我们可以创建一个ModbusTcpClient对象实例,并连接到目标从站设备: client = ModbusTcpClient('localhost', 502) # 连接到本地主机,端口号为502 client.connect() # 连接到从站设备 接下来,我们可以使用该客户端对象来执行Modbus操作,例如读取从站设备的寄存器值: result = client.read_holding_registers(0, 2, unit=1) # 读取从站设备的寄存器0和寄存器1的值,从站设备地址为1 if isinstance(result, ModbusException): print("Modbus读取失败:", result) else: print("寄存器值:", result.registers) 最后,不要忘记关闭连接: client.close() # 关闭连接 通过以上步骤,我们可以成功实现使用pymodbus库进行Modbus通信的实例。此示例仅涵盖了最基本的读取寄存器值操作,实际应用中还可以实现写入寄存器值、读写其他类型的Modbus数据等操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子正

thanks, bro...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值