网络继电器控制器(32 路):Y1-Y32继电器的开关

模块通讯协议—自定义协议

  1. 通过技术文档,下载相应的软件(网络IO控制板控制软件)。
  2. 使用调试软件SocketTool V4 ,和下载的软件做匹配调试,搜索设备里的网络协议,连接好调试器后,理解技术文档里的自定义协议的控制指令(集中控制指令and单路控制指令)。在vs2019的资源文件里提前下载好BytesIO.tcp库。
    vs2019/2022,c#实现。
    单路控制指令:
    单路命令码举例(十六进制)

    Y1开:483A0170010100004544
    Y1关:483a0170010000004544
    483a0170010102584544(将地址为 1 的控制板的第 1 路继电器打开延时 10 分钟后关闭)
    483a0170010100054544(将地址为 1 的控制板的第 1 路继电器打开延时 5 秒后关闭)
    Y2开:483A0170020100004544
    Y2关:483a0170020000004544
    Y3开:483A0170030100004544
    Y3关:483a0170030000004544
    Y4开:483A0170040100004544
    Y4关:483a0170040000004544
    Y5开:483A0170050100004544
    Y5关:483a0170050000004544
    Y6开:483A0170060100004544
    Y6关:483a0170060000004544
    Y7开:483A0170070100004544
    Y7关:483a0170070000004544
    Y8开:483A0170080100004544
    Y8关:483a0170080000004544
    Y9开:483A0170090100004544
    Y9关:483a0170090000004544
    Y10开:483A01700A0100004544
    Y10关:483a01700A0000004544
    Y11开:483A01700B0100004544
    Y11关:483a01700B0000004544
    Y12开:483A01700C0100004544
    Y12关:483a01700C0000004544
    Y13开:483A01700D0100004544
    Y13关:483a01700D0000004544
    Y14开:483A01700E0100004544
    Y14关:483a01700E0000004544
    Y15开:483A01700F0100004544
    Y15关:483a01700F0000004544
    Y16开:483A0170100000004544
    Y16关:483a0170100000004544
    Y17开:483A0170110000004544
    Y17关:483a0170110000004544
    Y18开:483A0170120100004544
    Y18关:483a0170120000004544
    Y19开:483A0170130100004544
    Y19关:483a0170130000004544
    Y20开:483A0170140100004544
    Y20关:483a0170140000004544
    Y21开:483A0170150100004544
    Y21关:483a0170150000004544
    Y22开:483A0170160100004544
    Y22关:483a0170160000004544
    Y23开:483A0170170100004544
    Y23关:483a0170170000004544
    Y24开:483A0170180100004544
    Y24关:483a0170180000004544
    Y25开:483A0170190100004544
    Y25关:483a0170190000004544
    Y26开:483A01701A0100004544
    Y26关:483a01701A0000004544
    Y27开:483A01701B0100004544
    Y27关:483a01701B0000004544
    Y28开:483A01701C0100004544
    Y28关:483a01701C0000004544
    Y29开:483A01701D0100004544
    Y29关:483a01701D0000004544
    Y30开:483A01701E0100004544
    Y30关:483a01701E0000004544
    Y31开:483A01701F0100004544
    Y31关:483a01701F0000004544
    Y32开:483A0170200100004544
    Y32关:483a0170200000004544

01~20为16进制的32位继电器序号,00和01为判断是否开关。
集中控制指令:
集中命令码举例(十六进制)
Y1和Y9开,其余继电器为断开:483a01570100010000000000dc4544
Y1开:483A01570100000000000000db4544
Y2开:483A01570400000000000000de4544
Y3开:483A01571000000000000000ea4544
Y4开:483A015740000000000000001a4544
Y1+Y3:483A01571100000000000000eb4544
Y2+Y3:483A01571400000000000000ee4544
Y5开:483A01570001000000000000db4544
Y6开:483A01570004000000000000de4544
Y7开:483A01570010000000000000ea4544
Y8开:483A015700400000000000001a4544

Y9开:483A01570000010000000000db4544
Y10开:483A01570000040000000000de4544
Y11开:483A01570000100000000000ea4544
Y12开:483A015700004000000000001a4544

Y13开:483A01570000000100000000db4544
Y14开:483A01570000000400000000de4544
Y15开:483A01570000001000000000ea4544
Y16开:483A015700000040000000001a4544

Y17开:483A01570000000001000000db4544
Y18开:483A01570000000004000000de4544
Y19开:483A01570000000010000000ea4544
Y20开:483A015700000000400000001a4544

Y21开:483A01570000000000010000db4544
Y22开:483A01570000000000040000de4544
Y23开:483A01570000000000100000ea4544
Y24开:483A015700000000004000001a4544

Y25开:483A01570000000000000100db4544
Y26开:483A01570000000000000400de4544
Y27开:483A01570000000000001000ea4544
Y28开:483A015700000000000040001a4544

Y29开:483A01570000000000000001db4544
Y30开:483A01570000000000000004de4544
Y31开:483A01570000000000000010ea4544
Y32开:483A015700000000000000401a4544
Y1+Y5+Y9+Y13+Y17+Y21+Y25+Y29开:
483A01570101010101010101e24544
Y4+Y8+Y12+Y16+Y20+Y24+Y28+Y32开:
483A01574040404040404040da4544
Y4+Y8+Y12:
483A015740404000000000009a4544
Y4+Y8+Y12+Y16:
483A01574040404000000000da4544
Y4+Y8+Y12+Y16+Y20:
483A015740404040400000001a4544
Y4+Y8+Y12+Y16+Y20+Y24:
483A015740404040404000005a4544
Y4+Y8+Y12+Y16+Y20+Y24+Y28:
483A015740404040404040009a4544
Y2+Y5:
483A01570401000000000000df4544
Y5+Y9:
483A01570001010000000000dc4544
不让Y5和Y9继电器改变状态:483A01570102010200000000e04544
中间的16位数码是由于8个数据位每个数据位有8个字符00000000,将每个数据位的8位字符转成16进制,再和前面的48 3A等16进制的控制命令码计算得到校验码(只取低八位,编译自动保留低八位byte类型)。

  1. 编程实现:

RelaySingleMsg类:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace RelayControllerService
{
    public struct RelaySingleMsg//定义的结构体类型,这里选用结构体不用类是因为主要进行的是数据传输,对象逻辑抽象等表现的并不多。
    {
        public static byte COMMAND_TYPE_WRITE_SYN = 0x70;//定义常量用static byte,常量命名格式
        public static byte COMMAND_TYPE_WRITE_ACK = 0x71;//写应答和读应答是一样的

        public static byte COMMAND_TYPE_READ_SYN = 0x72;
        public static byte COMMAND_TYPE_READ_ACK = 0x71;
        //byte类型的范围转换为二进制是00000000~11111111。
        //byte类型占用空间小,1个字节,int型占用空间大,4个字节。
        //当数据量小的时候,看不出区别。当数据量大的时候很大的时候,比如60亿人的年龄,肯定是0~255之间的,如果用int就浪费了很多空间。
        byte header1;
        byte header2;
        byte address;
        byte command;//结构体变量1
        byte num;//变量2
        byte OpenOrClose;//变量3
        byte TimeHeigh;
        byte TimeLow;
        byte tail1;
        byte tail2;
        //483A0170010100004544--Y1开
        //
        public RelaySingleMsg(byte command, byte num, bool OpenOrClose)//将变量写进函数参数,结构体内的函数,用于赋值。
        {
            this.header1 = 0x48;
            this.header2 = 0x3A;
            this.address = 0x01;
            this.command = command;//写继电器状态  0x72//读继电器状态
            this.num = num;
            this.OpenOrClose = OpenOrClose == true ? (byte)1 : (byte)0;//0x01
            this.TimeHeigh = 0x00;
            this.TimeLow = 0x00;
            this.tail1 = 0x45;
            this.tail2 = 0x44;
        }

        public byte[] ToBytes()//定义byte[]数组函数,将赋值好的结构体变量传给byte[]数组。
        {
            byte[] bytes = new byte[10];
            bytes[0] = header1;
            bytes[1] = header2;
            bytes[2] = address;
            bytes[3] = command;
            bytes[4] = num;
            bytes[5] = OpenOrClose;
            bytes[6] = TimeHeigh;
            bytes[7] = TimeLow;
            bytes[8] = tail1;
            bytes[9] = tail2;
            return bytes;//返回byte数组。
        }
        public static(int ,bool) GetStatus(byte[] buff)//buff用于存放byte[]数组里面读取的临时数据。 
        {//这里设定了static,在方法(函数)前用static修饰,表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。
            return (buff[4], buff[5] == 1);
            //返回一对数据,num和OpenOrClose,这里由用户输入,在使用文档时会有提醒,所以这里不再进行判断。
        }

        public static bool IsValid(byte[] buff)//这里bool类型的函数,对buff存放byte[]数组里读取的临时数据进行判断其是否合理。 
        {//这里设定了static,在方法(函数)前用static修饰,表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。
            if(buff.Length != 10)
            {
                return false;
            }
            if (buff[0] != 0x48)
                return false;
            if (buff[1] != 0x3A)
                return false;
            if (buff[2] != 0x70&& buff[2] != 0x71&& buff[2] != 0x72)
            {
                return false;
            }
            if (buff[3] != 0x48)
                return false;
            if (buff[6] != 0x00)
                return false;
            if (buff[7] != 0x00)
                return false;
            if (buff[8] != 0x45)
                return false;
            if (buff[9] != 0x44)
                return false;
            return true;//全部是正确的指令码则返回正确。
        }
    }
}

GroupRelayMsg类

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace RelayControllerService
{
    public struct GroupRelayMsg
    {
        public static byte GROUP_COMMAND_TYPE_READ_INPUT = 0x52;
        public static byte GROUP_COMMAND_TYPE_RELAY_READ_INPUT = 0x41;
        public static byte GROUP_COMMAND_TYPE_WRITE = 0x57;
        public static byte GROUP_COMMAND_TYPE_RELAY_WRITE = 0x54;
        public static byte GROUP_COMMAND_TYPE_READ = 0x53;
        public static byte GROUP_COMMAND_TYPE_RELAY_READ = 0x54;
        byte header1;
        byte header2;
        byte address;
        byte command;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]//在结构体里定义数组,需要这样的形式来进行安全定义。
        public byte[] data;
        byte check;
        byte tail1;
        byte tail2;
        //Y1开:483A0157  01 00 00 00 00 00 00 00  db  4544
        //
        //RelayMsg("1000 0000 0000 0000 0000 0000 0000 0000");
        public GroupRelayMsg(byte command,string InputString)
        {
            string NumberString = "";//字符串的初始化。
            StringBuilder builder = new StringBuilder();
            //字符串拼接工具StringBuilder()-详见转战C#---day2
            data = new byte[8];//新定义一个8个字节的byte类型的数组data。
            this.header1 = 0x48;
            this.header2 = 0x3A;
            this.address = 0x01;
            this.command = command;

            for (int i = 0; i < 32; i++)
            {
                if (InputString[i] == '0')
                {
                    builder.Append( "00");
                }
                else if (InputString[i] == '1')
                {
                    builder.Append( "01");
                }
                else
                {
                    Debug.Assert(false,"传入的字符串长度有误,应该是0和1组成的32个字符");
                    //仅当为false时,跳出信息提示框,或将显示简化提醒信息和提醒消息。
                    // 存在重载:
                    //Debug.Assert(<bool>);
                    //Debug.Assert(<bool>,"simplified string");
                    //Debug.Assert(<bool>,"simplified string", "string");
                }
            }

            NumberString = builder.ToString();//把builder里面的字符串传给字符串变量NumberString。
            Debug.Assert(NumberString.Length == 64, "传入的字符串长度有误,应该是0和1组成的32个字符");

            int k = 0;
            for (int i = 0; i < 8; i++)
            {
                if (k < 64)
                {
                    string bin = NumberString.Substring(k, 8);//字符串截取工具Substring()-详见转战C#---day2
                    this.data[i] = Convert.ToByte(bin, 2);//将转换成16进制的数传给刚刚新定义的名为data的byte类型的数组。
                }
                k = k + 8;
            }
            this.check = (byte)(0xDA + data[0] + data[1] + data[2] + data[3] + data[4] + data[5] + data[6] + data[7]);
            this.tail1 = 0x45;
            this.tail2 = 0x44;
        }
        public byte[] ToBytes()
        {
            byte[] bytes = new byte[15];
            bytes[0] = header1;
            bytes[1] = header2;
            bytes[2] = address;
            bytes[3] = command;
            for( int i= 4;i<= 11;i++)
            {
                bytes[i] = data[i - 4];//此处的处理多留意一下。
            }
            bytes[12] = check;
            bytes[13] = tail1;
            bytes[14] = tail2;
            return bytes;
        }
        
        bool IsValid(byte[] buff)
        {
            if (buff.Length != 15)
            {
                return false;
            }
            if (buff[0] != 0x48)
                return false;
            if (buff[1] != 0x3A)
                return false;
            if (buff[2] != 0x01)
                return false;
            if (buff[3] != 0x57&& buff[3] != 0x52 && buff[3] != 0x53 && buff[3] != 0x54 && buff[3] != 0x41)
                return false;
            for (int i = 4; i <= 11; i++)
            {
                if (buff[i] != data[i - 4])
                    return false;
            }
            if (buff[12] != (byte)(0xDA + data[0] + data[1] + data[2] + data[3] + data[4] + data[5] + data[6] + data[7]))
                return false;
            if (buff[13] != 0x45)
                return false;
            if (buff[14] != 0x44)
                return false;
            return true;
        }
    }
}

DataUnPacker类

using STTech.BytesIO.Core.Component;//调用抓包所需要要的函数库BytesIO.Core
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RealyConsoleService
{
    /// <summary>
    /// 数据协议
    /// </summary>
    class DataUnPacker : Unpacker//继承下载好的调包所需的类
    {
        public DataUnPacker()
        { 
            /// <summary>
            /// 协议起始标记
            /// </summary>
            StartMark = new byte[] {0x48,0x3A};
        }

        protected override int CalculatePacketLength(IEnumerable<byte> bytes)
        {//重写数据包长,IEnumerable接口(公开枚举数)具体见转战C#---day4
            if (bytes.Count() != 4)//对帧头判断,必须是4字节
            {
                return 0;
            }
            else
            {
                int len = 0;
                byte command = bytes.ElementAt(3);
                //传达字节组里的命令指令给新命令变量,使用的是字节栈(具体见转战C#---day4)来操作的。
                if(command == 0x71)
                {
                    len = 10;
                }else if (command == 0x41||command == 0x54)
                {
                    len = 15;
                }else{
                    return 0;
                }
                return len;//返回不同的命令指令所对应的长度。
            }
        }
    }
}

RelayController类

using RealyConsoleService;
using STTech.BytesIO.Core.Component;
using STTech.BytesIO.Tcp;
using System;
using System.Collections.Generic;
using System.Linq;

namespace RelayControllerService
{
    public class RelayController: IDisposable//这里对IDisposabl()函数进行了继承  IDisposable用法--详见转战C#---day3
    //当我们自定义的类及其业务逻辑中引用某些托管和非托管资源,就需要实现IDisposable接口,实现对这些资源对象的垃圾回收。
    {
        private TcpClient client;//下载好BytesIO Tcp包后便可定义TcpClient类型变量
        static DataUnPacker unPacker = new DataUnPacker();
        private List<bool> _PortList;
        //传入的两个字符,第一个字符是1到32的数字之一,第二个字符是0和其他,0代表false,其他代表true。
        public void Send(byte num, bool OpenorClose)//传输函数,传出两个变量num、OpenorClose
        {
            client.Send(new RelaySingleMsg(RelaySingleMsg.COMMAND_TYPE_WRITE_SYN, num, true).ToBytes());
            //new RelaySingleMsg(, ,).ToBytes();
            //把new RelaySingleMsg(, ,)里面的Bytes数据传给新定义的函数RelaySingleMsg(, ,),最后再由Send()函数发出。
        }
        public RelayController(string Ip,int port)//此函数为接受IP协议和端口号的函数,写入变量,具体数据由用户输入。
        {
            client = new TcpClient() { Host = Ip, Port = port };//新定义一个TcpClient()用来接收具体数据。
            _PortList = new List<bool> { false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false,
            false, false};//定义一个新链表给输入赋值,默认32个FALSE。

            client.OnDataReceived += Client_OnDataReceived;//客户端的数据接受
            unPacker.OnDataParsed += DataUnPacker_OnDataPased;//对数据进行解包
        }

        private void DataUnPacker_OnDataPased(object sender, DataParsedEventArgs e)
        {//对数据进行解包,从发送者那边接受数据。
           // Console.WriteLine("1111");
            byte[] receiveData = e.Data.ToArray();//定义byte[],接受从数组那边传输过来的数据。

            if (receiveData.Length != 10)//限定接受过来的数据长度。
                return;

            if (RelaySingleMsg.IsValid(receiveData))//判断是否是有效数据
            {
                int num;
                bool isOpen;
                (num, isOpen) = RelaySingleMsg.GetStatus(receiveData);//将有效数据传达。
                if (num - 1 > _PortList.Count || num <= 0)//判断数据长度是否合理。
                {
                    //打印错误日志
                }
                else
                {
                    _PortList[num - 1] = isOpen;//数据长度合理,再将isOpen的状态赋值给相应的开关。
                }
            }
        }

        //默认序号从1开始
        public  bool GetStatus(byte num)//接受开关位的状态
        {
            if (num-1 > _PortList.Count || num <= 0)//判断数据长度是否合理。
                return false;
            return _PortList[num-1];//返回开关位状态true/false
        }

        private void Client_OnDataReceived(object sender, STTech.BytesIO.Core.DataReceivedEventArgs e)//客户端的数据接受
        {
           // Console.WriteLine("接受到");
            unPacker.Input(e.Data);//将解包后的数据传入
        }

        //传出的字符串是0和1组成的32个字符。
        public void Send(string msg)//发送文本类型数据
        {
            client.Send(new GroupRelayMsg(GroupRelayMsg.GROUP_COMMAND_TYPE_WRITE, msg).ToBytes());
            //将传入的数据按文本字节传出。
        }

        public void StartService()
        {
            client.Connect();//调用连接函数
        }

        public void Dispose()
        {
            client.Disconnect();//调用断开函数
        }
    }
}

Program-Main类

using RelayControllerService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RealyConsoleService
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义一个变量controller,传入协议和端口号。
            RelayController controller = new RelayController("192.168.2.253", 1030);
            controller.StartService();//调用函数连接

            for (byte i = 1; i <= 32; i++)
            {
                controller.Send(i, true);//调用函数发送单路指令
                Thread.Sleep(3000);//设定固定睡眠时长
            }

            controller.Send("00000000000000000000000000000000");//调用函数发送集中控制指令
            Thread.Sleep(60000);//设定固定睡眠时长
            controller.Send("00011000000000000000000000000000");//调用函数发送集中控制指令
            controller.Dispose();//调用函数断开
            Thread.Sleep(1000);//设定固定睡眠时长
        }
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 输入继电器X:输入继电器X是PLC的基本输入元件,它用于检测外部信号状态(如传感器、开关等),并将状态转换为数字量输入到PLC中。在PLC程序中,输入继电器X通常表示为Xn,其中n表示继电器的编号。例如,X0表示输入继电器的第0个编号,X1表示输入继电器的第1个编号,以此类推。 2. 输出继电器Y:输出继电器Y是PLC的基本输出元件,它用于控制外部设备(如电机、灯、阀门等)的开关状态,将数字量输出到PLC外部。在PLC程序中,输出继电器Y通常表示为Yn,其中n表示继电器的编号。例如,Y0表示输出继电器的第0个编号,Y1表示输出继电器的第1个编号,以此类推。 3. 辅助继电器M:辅助继电器M是PLC程序中的中间元件,它用于辅助实现逻辑功能,如延时、计数等。辅助继电器M的状态由PLC程序控制,其状态可以影响PLC程序的执行。在PLC程序中,辅助继电器M通常表示为Mn,其中n表示继电器的编号。例如,M0表示辅助继电器的第0个编号,M1表示辅助继电器的第1个编号,以此类推。 4. 定时器T:定时器T是PLC程序中的计时元件,它用于实现时间控制功能。定时器T在PLC程序中可以设定预定时间,当定时器启动后,经过设定的时间后就会输出一个信号。在PLC程序中,定时器T通常表示为Tn,其中n表示定时器的编号。例如,T0表示定时器的第0个编号,T1表示定时器的第1个编号,以此类推。 5. 计数器C:计数器C是PLC程序中的计数元件,它用于实现计数功能。计数器C在PLC程序中可以设定计数次数,当计数器达到设定的次数后就会输出一个信号。在PLC程序中,计数器C通常表示为Cn,其中n表示计数器的编号。例如,C0表示计数器的第0个编号,C1表示计数器的第1个编号,以此类推。 6. 数据寄存器D:数据寄存器D是PLC程序中的数据存储元件,它用于存储程序中需要使用的数据。在PLC程序中,数据寄存器D通常表示为Dn,其中n表示数据寄存器的编号。例如,D0表示数据寄存器的第0个编号,D1表示数据寄存器的第1个编号,以此类推。 7. 状态寄存器S:状态寄存器S是PLC程序中的状态存储元件,它用于存储程序中需要使用的状态信息。在PLC程序中,状态寄存器S通常表示为Sn,其中n表示状态寄存器的编号。例如,S0表示状态寄存器的第0个编号,S1表示状态寄存器的第1个编号,以此类推。 M8000、M8002是辅助继电器的具体编号,其作用、表达和工作原理与其他辅助继电器M相同,只是其编号不同。在PLC程序中,可以根据具体的控制要求和实际情况选择使用不同的辅助继电器
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值