目录

PyCharm Community Edition 2020.3 x64
YK-08


  官方客服资料:YK-04-08继电器模块资料

外观


  以下内容摘自《YK-08继电器模块使用说明 V1.00》


系统概述

  RS232/RS485 控制继电器模块,可利用电脑通过串口(没有串口的可利用 USB 转串口)连接控制器进行对设备的控制,接口采用开关输出,有常开常闭点。
  控制软件可自由设置每一路的名称,有配套傻瓜式简洁的界面,方便用户使用。
  可多机连在一块使用,由配置软件或自动控制软件选择模块地址。
  RS232 采用公用 DB9 串口接口(采用直通线,非交叉线),电源采用标准 DC5.5*2.1
圆孔电源座,和接线端子供电,电压可 5V 或者 7-32V,满足各种供电接口,并带反接保护,每个输出接口有常开常闭,可控制电压 AC250V,电流 10A。
  外壳采用专用导轨外壳安装方便,通讯协议采用校验码校验,可有效防止干扰等带来的控制器误动作。
  可与 PLC、组态软件等产品进行组网。广泛用于智能家居、灯光控制、自动化控制、电机控制、广告控制、自动化测试、洒店宾馆、商场厂房、物连网、工控设备、测试设备、智能管理,集中管理等场合。

特点

  1. 通讯接口:采用 RS232 和 RS485 两种接口满足各种通讯接口的应用。

  2. 电源接口:采用通用 5.5*2.1mm 通用电源接口和接线端子两种电源接口,满足各种接口需要。

  3. 输出接口:采用三端接线端子,公共,常开,常闭,使用起来更加方便灵活。

  4. 内部功能:内部控制继电器功能丰富,通讯协议开源。

  5. 所有开关都有 LED 状态指示,开关状态一目了然(软件上显示开关状态),有电源指示灯。

  6. 配置标准的工控外壳,背面标准导轨安装槽。

  7. 控制器可多个联机使用,可由软件设置地址。

技术参数

  1. 外形尺寸: 145mm × 90mm × 40mm

  2. 防爆性能:不防爆

  3. 防水性能:不防水

  4. 电源电压:5V/7-32V

  5. 电源电流:1A 以上

  6. 触点容量:AC220V10A,DC30V10A

  7. 输出指示:8 个

  8. 模块地址:1(可通过软件设置成 1-254)

硬件接口和连接方式

  通信和电源:

  YK-08 可适应 5V/7-32V 供电,并提供 3 组电源输入口,如上图,其中 7-32V 可通过DC5.5*2.1 座子,也可通过螺丝柱输入,5V 则只能使用螺丝柱输入(注意 5V 电压范围是 4.5V-5.5V,低了不工作或出现不正常,高了模块必烧!电流需 1A 以上,否则可能无法同时吸合多个继电器!)。3 组电源只要接任意一个即可工作。
  输出接法:可控制交流、直流,只要不超过极限参数即可。(感性负载需减半或以现场测试为准)

  以下部分内容摘自《串口模块通信协议(MODBUS 版) V2.03》

MODBUS RTU

通信参数

波特率:9600,
校验位:N,
数据位:8,
停止位:1

协议详解

设置单个继电器(功能码 0x05)

  (支持的模块型号:YX01、YX02、YX04、YK04、YX08、YK08、YK16、YXU04)

命令格式
地址(0x01-0xFE,1 字节)、
功能码(0x05,1 字节)、
继电器地址(0x0000-0x000F,2 字节)、
继电器状态(0x0000/0xFF00,2 字节)、
校验位(2 字节,低位先行)
回复格式
地址(0x01-0xFE,1 字节)、
功能码(0x05,1 字节)、
继电器地址(0x0000-0x000F,2 字节)、
继电器状态(0x0000/0xFF00,2 字节)、
校验位(2 字节,低位先行)
应用举例

  设置板地址 1 的继电器 2 吸合示例如下:
  发送:0x01 0x05 0x00 0x01 0xFF 0x00 0xDD 0xFA

  接收:0x01 0x05 0x00 0x01 0xFF 0x00 0xDD 0xFA
  继电器模块接收到正确的命令后,执行相应动作,并将应答指令发回主机。

读取继电器状态(功能码 0x01)

  (支持的模块型号:YX01、YX02、YX04、YK04、YX08、YK08、YK16)

命令格式
地址(0x01-0xFE,1 字节)、
功能码(0x01,1 字节)、
继电器起始地址(0x0000,2 字节)、
继电器数量(0x0010,2 字节)、
校验位(2 字节,低位先行)
回复格式
地址(0x01-0xFE,1 字节)、
功能码(0x01,1 字节)、
状态字节数(0x02,1 字节)、
继电器状态(0x0000, 2 字节)、
校验位(2 字节,低位先行)
应用举例

  读取板继电器 1 到 16 的状态示例如下:
  发送:0x01 0x01 0x00 0x00 0x00 0x10 0x3D 0xC6

  接收:0x01 0x01 0x02 0x03 0x00 0xB9 0x0C
  读到的数据“0x0300”,转换成 2 进制数为“00000011 00000000”,
  “00000011”从右至左分别对应继电器 1 到 8 的状态,即继电器 1 吸合、继电器 2 吸合、继电器 6-8 断开,
  “00000000”从右至左分别对应继电器 9 到 16 的状态,即继电器 9-16 断开。

控制所有继电器(功能码 0x0F)

  地址 0,长度 16,通过一条指令可以控制所有继电器,数值 0 对应断开,数值1 对应吸合
  (支持的模块型号:YX01、YX02、YX04、YK04、YX08、YK08、YK16)

命令格式
地址(0x01-0xFE,1 字节)、
功能码(0x0F,1 字节)、
继电器起始地址(0x0000,2 字节)、
继电器数量(0x0010,2 字节)、
写入数据字节(0x02,1 字节)、
写入字节(0x0000,2 字节)、
校验位(2 字节,低位先行)
回复格式
地址(0x01-0xFE,1 字节)、
功能码(0x01,1 字节)、
继电器起始地址(0x0000,2 字节)、
继电器数量(0x0010,2 字节)、
校验位(2 字节,低位先行)
应用举例

  控制继电器 1 和 3 吸合的示例如下:
  发送:0x01 0x0F 0x00 0x00 0x00 0x10 0x02 0x05 0x00 0xE1 0x70

  接收:0x01 0x0F 0x00 0x00 0x00 0x10 0x54 0x07
  模块接收到正确的命令后,执行相应动作,并将应答指令发回主机。

读取输入通道状态(功能码 0x04)

  (支持的模块型号:YXU04、YXS08、YKS08)

命令格式
地址(0x01-0xFE,1 字节)、
功能码(0x04,1 字节)、
输入通道起始地址(0x0000,2 字节)、
通道数量(0x000F,2 字节)、
校验位(2 字节,低位先行)
回复格式
地址(0x01-0xFE,1 字节)、
功能码(0x04,1 字节)、
数据长度(0x02,1 字节)、
16-1 输入通道状态(0x0000,2 字节)、
校验位(2 字节,低位先行)
应用举例

  读取输入通道 1 到 32 的状态示例如下:
  发送:0x01 0x04 0x00 0x00 0x00 0x0F 0xB0 0x0E

  接收:0x01 0x04 0x02 0x00 0x03 0xF9 0x31
  读到的数据 “0x0003”,转换成 2 进制数为 “00000000 00000011
  “00000000 00000011”从右至左分别对应输入通道 1 到 16 的状态,即通道 1 和 2 有输入,3 到 16 无输入。
  本功能码目前仅支持一次性读取全部输入通道的状态,即输入通道起始地址固定为 0x0000,读取长度固定为0x000F

常用命令清单

使用注意

  1. 由于模块的地址一直以来是以自定义的 0x24 协议为参考做的,MODBUS RTU 协议仅部分型号有做上,所以,如果需要读取/设置模块的地址,请参阅下面的0x24自定义通信协议!

  2. 模块仅实现了 MODBUS RTU 协议的以上列出部分功能,未列出的均不支持或已在程序上作了屏蔽。

0x24 协议

  以下内容摘自《(0x24 协议)串口继电器控制板通信协议 V2.00》

通信参数

  波特率:9600,
  校验位:N,
  数据位:8,
  停止位:1

协议格式

  帧头(1 字节)、地址(1 字节)、命令长度(1 字节)、命令(1 字节)、参数码(N 字节)、校验(1 字节)

协议详解

帧头

  0x24,固定为 0x24,占 1 字节

地址

  0x01-0xFE,表示 1-254 模块地址,0x00、0xFF 为全选地址。占 1 字节

长度

  命令+预留+参数+校验的字节长度,占 1 字节

命令

  占 1 字节

	0x00:查询所有继电器的状态
	0x01:设置所有继电器的状态
	0xA0:查询模块的地址
	0xA1:设置模块的地址
	0xA2:查询波特率
	0xA3:设置波特率

参数

  长度视命令不同而不同
命令:
   0x00:查询所有继电器的状态,

参数码无效,可任意填充或删除参数码;

  0x01:设置所有继电器的状态:

1-8 路:
  每个字节代表一个继电器,从高到低依次代表 N、N+1、N+2……。
  字节个数与模块的继电器数量相同。如 4 路的模块,此处将有 4 个字节。
16 路或以上:
  每 2Bit 表示 1 路继电器,从高到低依次代表 N、N+1、N+2……。
  每个字节可以表示 4 个继电器。如 16 路的模块,此处将有 4 个字节。
数据意义:(1-8 以及 16 路以上对应到每一路的数值意义均相同,虽然表示方法不同)
  数据值 1:继电器断开;
  数据值 2:继电器吸合;
  数据值 0:继电器无动作(或未知状态)。

0xA0:查询模块的地址,

参数码无效,可任意填充或删除参数码;

0xA1:设置模块的地址,

参数码即为模块的新地址,范围 0x01-0xFE,1 字节。

0xA2:查询波特率,

参数码无效,可任意填充或删除参数码;

0xA3:设置波特率,

参数码即为模块的新波特率,
范围 0x00-0x0B,其他值无效,分别对应
300、600、1200、2400、4800、9600、14400、19200、38400、56000、57600、115200。1 字节。
默认为 0x05(9600)

校验:

除校验码之外所有字节的累加和,取低 8 位,占 1 字节。

应用举例

  以下举例均在地址为 1 的 4 路模块上进行的,其他地址的模块请参考上面的通信协议自行理解测试!

  1. 查询所有继电器的状态:
    发送:
    0x24 0x01 0x02 0x00 0x27
    接收:
    0x24 0x01 0x06 0x00 0x02 0x02 0x02 0x02 0x33(当前继电器全开)

  2. 设置所有继电器的状态:
    发送:
    0x24 0x01 0x06 0x01 0x02 0x00 0x00 0x00 0x2E(第 1 路开,其他路不理会)
    接收:
    0x24 0x01 0x06 0x01 0x02 0x01 0x01 0x01 0x31

  3. 查询模块的地址:
    发送:
    0x24 0xFF 0x02 0xA0 0xC5(由于查询时不知道模块的地址,所以这里建议使用全选地址,0xFF)
    接收:
    0x24 0x01 0x03 0xA0 0x01 0xC9(当前模块地址为 1)

  4. 设置模块的地址:
    发送:
    0x24 0xFF 0x03 0xA1 0x01 0xC8(所有模块地址都改为 1)
    0x24 0x02 0x03 0xA1 0x01 0xCB(将地址为 2 的模块改为地址 1)
    接收:
    0x24 0x01 0x03 0xA1 0x01 0xCA

  特别注意:设置模块地址时,最好用 RS232 接口,这样可以确保命令只让一个模块收到,若使用 RS485 接口,此时总线中可能有多个模块,会使所有在线的模块都设置为新的地址,而出现总线中地址重复。所以,多个模块在线时,不能使用广播地址(0x00、0xFF)修改模块地址!且要注意修改后的地址不要与总线中其他模块地址冲突!

  1. 设置模块的通信波特率:
    发送:
    0x24 0x01 0x03 0xA3 0x04 0xCF(将模块的波特率设置为 4800)
    接收:
    0x24 0x01 0x03 0xA3 0x04 0xCF
      注意:写入波特率后,模块会重启。请 2 秒后再操作!

  2. 读取模块的通信波特率:
    发送:
    0x24 0x01 0x02 0xA2 0xC9
    接收:
    0x24 0x01 0x03 0xA2 0x05 0xCF(当前模块的波特率为 9600)

使用python控制

依赖

modbus_tk
pyserial

python脚本

  实测部分功能码使用方式与手册有出入,已进行修改:

import time
import serial
import serial.tools.list_ports
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu


def connect_serial(keyword: str = "", baud_rate: int = None, timeout: int = 1):
    """
    连接串口
    :param keyword: 串口名关键词
    :param baud_rate: 波特率
    :param timeout: 超时时间
    :return: 串口类
    """
    serial_list = list(serial.tools.list_ports.comports())
    serial_list_len = len(serial_list)
    if serial_list_len <= 0:
        raise ValueError("Can't find a serial port")
    else:
        if not keyword:
            print("找到如下串口:")
            for serial_port in serial_list:
                print("\t", str(serial_port))
            print("请输入要连接的串口关键词:")
            keyword = input()
        if not baud_rate:
            print("请输入使用的波特率:")
            baud_rate = input()
            try:
                baud_rate = int(baud_rate)
            except:
                baud_rate = 9600
        for _ in range(serial_list_len):
            if keyword.lower() in str(serial_list[_]).lower():
                serial_port = serial.Serial(serial_list[_].name, baud_rate, timeout=timeout)
                print("与", serial_list[_], "建立连接!")
                return serial_port
        raise ValueError("Can't find the serial port")


class relay_module:
    """
    继电器类
    """

    def __init__(self, serial_obj: serial.serialwin32.Serial, addr: int, amount: int = 16):
        """
        构造函数
        :param serial_obj: 串口类
        :param addr: 从机地址
        """
        self.serial_obj = serial_obj
        self.modbus_rtu_obj = modbus_rtu.RtuMaster(serial_obj)
        self.modbus_rtu_obj.set_timeout(1.0)
        self.addr = addr
        self.amount = amount
        self.baud = (300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200)

    def set_a_relay(self, relay_addr: int, state: bool):
        """
        设置单个继电器
        支持的模块型号:YX01、YX02、YX04、YK04、YX08、YK08、YK16、YXU04
        :param relay_addr: 0x0000~0x000F
        :param state: True, False
        :return: None
        """
        if state:
            self.modbus_rtu_obj.execute(self.addr, cst.WRITE_SINGLE_COIL, relay_addr, output_value=0xFF00)
        else:
            self.modbus_rtu_obj.execute(self.addr, cst.WRITE_SINGLE_COIL, relay_addr, output_value=0x0000)

    def read_relays_state(self):
        """
        读取继电器状态
        支持的模块型号:YX01、YX02、YX04、YK04、YX08、YK08、YK16

        :return: 元组,(继电器1的状态, 继电器2的状态, ..., 继电器amount的状态,)
        """
        return self.modbus_rtu_obj.execute(self.addr, cst.READ_COILS, 0x0000, self.amount)

    def set_all_relays(self, state: list):
        """
        控制所有继电器

        :param state: 继电器状态列表
        """
        if len(state) < self.amount:
            self.modbus_rtu_obj.execute(self.addr, cst.WRITE_MULTIPLE_COILS, 0x0000,
                                        output_value=state + [0] * (self.amount - len(state)))
            # 补齐长度,否则会报 ModbusInvalidResponseError: Response length is invalid 0
        else:
            self.modbus_rtu_obj.execute(self.addr, cst.WRITE_MULTIPLE_COILS, 0x0000,
                                        output_value=state[:self.amount])

    def read_channel_state(self):
        """
        读取输入通道状态
        TODO: 未测试,手头没有支持的模块
        支持的模块型号:YXU04、YXS08、YKS08
        :return:
        """
        return self.modbus_rtu_obj.execute(self.addr, cst.READ_INPUT_REGISTERS, 0x0000, self.amount)

    def read_0x24(self, timeout: int = 1):
        """
        私有0x24协议读取数据

        :param timeout: 超时时间,默认为1s
        """
        data_list = []
        read_ch = 0

        start_time = time.time()
        while read_ch != 0x24:
            read_ch = int.from_bytes(self.serial_obj.read(), byteorder='big')
            if (time.time() - start_time) > timeout:
                raise ValueError("Read_0x24 timeout!")
        data_list.append(read_ch)

        cmd = int.from_bytes(self.serial_obj.read(), byteorder='big')
        data_list.append(cmd)

        data_len = int.from_bytes(self.serial_obj.read(), byteorder='big')
        data_list.append(data_len)

        while data_len:
            data_list.append(int.from_bytes(self.serial_obj.read(), byteorder='big'))
            data_len -= 1

        return data_list

    def send_0x24(self, addr: int, cmd: int, data_list=None):
        """
        私有0x24协议发送数据

        :param addr: 模块地址
        :param cmd: 命令
        :param data_list: 参数码
        """
        if data_list is None:
            data_list = []
        cmd_list = [0x24, addr, 1 + len(data_list) + 1, cmd]
        cmd_list += data_list
        check_code = 0
        for _ in cmd_list:
            check_code += _
        cmd_list.append(check_code & 0xFF)
        self.serial_obj.write(cmd_list)

    def read_relays_state_0x24(self):
        """
        私有0x24协议读取继电器状态

        :return: 元组,(继电器1的状态, 继电器2的状态, ..., 继电器amount的状态,)
        """
        self.send_0x24(self.addr, 0x00)
        state_list = tuple(self.read_0x24()[4:-1])
        state_list = [x - 1 for x in state_list]
        return tuple(state_list)

    def set_all_relays_0x24(self, state_list=None):
        """
        私有0x24协议设置继电器状态

        :param state_list: 继电器状态列表:1吸合,0断开,-1无动作
        :return: 继电器状态元组
        """
        if state_list is None:
            state_list = [0]
        for _ in range(len(state_list)):
            if state_list[_] > 0:
                state_list[_] = 1
            elif state_list[_] < 0:
                state_list[_] = -1
            else:
                state_list[_] = 0
        if len(state_list) < self.amount:
            state_list += [0] * (self.amount - len(state_list))
        state_list = [x + 1 for x in state_list]
        self.send_0x24(self.addr, 0x01, state_list)
        return self.read_relays_state_0x24()

    def read_addr_0x24(self, addr: int = 0xFF):
        """
        私有0x24协议读取模块地址

        :param addr: 默认为0xFF,全选地址,读取所有
        :return:地址
        """
        self.send_0x24(addr, 0xA0)
        return self.read_0x24()[-2]

    def set_addr_0x24(self, old_addr: int = 0xFF, new_addr: int = 0x01):
        """
        私有0x24协议设置模块地址

        :param old_addr: 旧地址,默认为0xFF,全选地址
        :param new_addr: 新地址
        :return: 尝试读取新地址
        """
        self.send_0x24(old_addr, 0xA1, [new_addr])
        self.addr = new_addr
        old_addr = new_addr
        new_addr = self.read_addr_0x24(new_addr)
        if old_addr != new_addr:
            raise ValueError("设置地址失败!")
        return new_addr

    def read_baud_0x24(self):
        """
        私有0x24协议读取波特率

        :return: 波特率
        """
        self.send_0x24(self.addr, 0xA2)
        return self.baud[self.read_0x24()[-2]]

    def set_baud_0x24(self, new_baud: int):
        """
        私有0x24协议设置波特率

        :param new_baud: 新波特率,尽量使用115200或9600
        :return: 波特率
        """
        self.send_0x24(self.addr, 0xA3, [self.baud.index(new_baud)])
        old_baud = self.baud[self.read_0x24()[-2]]
        self.serial_obj.close()
        time.sleep(2)
        return old_baud


if __name__ == '__main__':
    YK_08 = relay_module(connect_serial("USB Serial Port", 9600), 1, 8)

    YK_08.set_a_relay(3, True)
    time.sleep(1)
    print(YK_08.read_relays_state())

    YK_08.set_a_relay(3, False)
    time.sleep(1)
    print(YK_08.read_relays_state())

    YK_08.set_all_relays([1, 1, 1, 1, 0, 0, 0, 0])
    time.sleep(1)
    print(YK_08.read_relays_state())

    YK_08.set_all_relays([0])
    time.sleep(1)
    print(YK_08.read_relays_state())

运行效果

与 COM7 - USB Serial Port (COM7) 建立连接!
(0, 0, 0, 1, 0, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 0, 0)
(1, 1, 1, 1, 0, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 0, 0)

进程已结束,退出代码0