网络继电器控制器(32 路):Y1-Y32继电器的开关
创始人
2024-06-01 21:32:30

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

  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;//结构体变量1byte num;//变量2byte OpenOrClose;//变量3byte 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;//0x01this.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#---day2data = 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();//Debug.Assert(,"simplified string");//Debug.Assert(,"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#---day2this.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
{/// /// 数据协议/// class DataUnPacker : Unpacker//继承下载好的调包所需的类{public DataUnPacker(){ /// /// 协议起始标记/// StartMark = new byte[] {0x48,0x3A};}protected override int CalculatePacketLength(IEnumerable bytes){//重写数据包长,IEnumerable接口(公开枚举数)具体见转战C#---day4if (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 _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 { 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);//设定固定睡眠时长}}
}

相关内容

热门资讯

苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...